feat: add robot visualization via threlte
This commit is contained in:
parent
cb88e800f0
commit
fc82783845
15 changed files with 1450 additions and 43 deletions
190
client/package-lock.json
generated
190
client/package-lock.json
generated
|
@ -9,13 +9,15 @@
|
|||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.0.8",
|
||||
"@threlte/core": "^7.1.0",
|
||||
"@threlte/extras": "^8.8.0",
|
||||
"howler": "^2.2.4",
|
||||
"material-icons": "^1.13.12",
|
||||
"material-symbols": "^0.15.0",
|
||||
"overlayscrollbars-svelte": "^0.5.3",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"svelte-french-toast": "^1.2.0",
|
||||
"svrollbar": "^0.12.0"
|
||||
"three": "^0.161.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@svelte-plugins/tooltips": "^3.0.0",
|
||||
|
@ -24,6 +26,7 @@
|
|||
"@tauri-apps/cli": "^1.5.10",
|
||||
"@tsconfig/svelte": "^5.0.2",
|
||||
"@types/howler": "^2.2.11",
|
||||
"@types/three": "^0.161.2",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.35",
|
||||
"svelte": "^4.2.11",
|
||||
|
@ -995,6 +998,37 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@threlte/core": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@threlte/core/-/core-7.1.0.tgz",
|
||||
"integrity": "sha512-Qshrrt1Bqu8B0rWuNtFqGJpbmxrFUBNYTHOJZ29Tiqy2kpmwYZfzVSPRoGUfJTqUtpfxGCrDU3S2etWnB0KsUA==",
|
||||
"dependencies": {
|
||||
"mitt": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": ">=4",
|
||||
"three": ">=0.133"
|
||||
}
|
||||
},
|
||||
"node_modules/@threlte/extras": {
|
||||
"version": "8.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@threlte/extras/-/extras-8.8.0.tgz",
|
||||
"integrity": "sha512-NNGywihQlPGbDoWnr0ZHV59Gkg8nKM4kzdxPY0CVZ93pSxXpGdoua0mQBTaQbJ6w3Qeiz867va+4SQXeVRHs8w==",
|
||||
"dependencies": {
|
||||
"three-mesh-bvh": "^0.7.1",
|
||||
"three-perf": "^1.0.10",
|
||||
"troika-three-text": "^0.49.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": ">=4",
|
||||
"three": ">=0.133"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"three-mesh-bvh": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/svelte": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.2.tgz",
|
||||
|
@ -1025,6 +1059,30 @@
|
|||
"integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/stats.js": {
|
||||
"version": "0.17.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz",
|
||||
"integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/three": {
|
||||
"version": "0.161.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.161.2.tgz",
|
||||
"integrity": "sha512-DazpZ+cIfBzbW/p0zm6G8CS03HBMd748A3R1ZOXHpqaXZLv2I5zNgQUrRG//UfJ6zYFp2cUoCQaOLaz8ubH07w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/stats.js": "*",
|
||||
"@types/webxr": "*",
|
||||
"fflate": "~0.6.10",
|
||||
"meshoptimizer": "~0.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/webxr": {
|
||||
"version": "0.5.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.14.tgz",
|
||||
"integrity": "sha512-UEMMm/Xn3DtEa+gpzUrOcDj+SJS1tk5YodjwOxcqStNhCfPcwgyC5Srg2ToVKyg2Fhq16Ffpb0UWUQHqoT9AMA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.11.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
|
||||
|
@ -1144,6 +1202,14 @@
|
|||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/bidi-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
||||
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
||||
"dependencies": {
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
|
@ -1563,6 +1629,12 @@
|
|||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.6.10",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
|
||||
"integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
|
@ -1922,6 +1994,12 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/meshoptimizer": {
|
||||
"version": "0.18.1",
|
||||
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
|
||||
"integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
|
@ -1974,6 +2052,11 @@
|
|||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
|
||||
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
|
@ -2393,6 +2476,14 @@
|
|||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
|
@ -2955,11 +3046,6 @@
|
|||
"svelte": "^3.2.1 || ^4.0.0-next.1"
|
||||
}
|
||||
},
|
||||
"node_modules/svrollbar": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/svrollbar/-/svrollbar-0.12.0.tgz",
|
||||
"integrity": "sha512-okH0sz8bGtw+tgOfN1mpEtbveifxROcE3mbUMBJ1RQz8Q+1rVr+nVG7EAJ9b0G80cGDu7dskjAWuzj3iru0k5g=="
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz",
|
||||
|
@ -3030,6 +3116,58 @@
|
|||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/three": {
|
||||
"version": "0.161.0",
|
||||
"resolved": "https://registry.npmjs.org/three/-/three-0.161.0.tgz",
|
||||
"integrity": "sha512-LC28VFtjbOyEu5b93K0bNRLw1rQlMJ85lilKsYj6dgTu+7i17W+JCCEbvrpmNHF1F3NAUqDSWq50UD7w9H2xQw=="
|
||||
},
|
||||
"node_modules/three-mesh-bvh": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.3.tgz",
|
||||
"integrity": "sha512-3W6KjzmupjfE89GuHPT31kxKWZ4YGZPEZJNysJpiOZfQRsBQQgmK7v/VJPpjG6syhAvTnY+5Fr77EvIkTLpGSw==",
|
||||
"peerDependencies": {
|
||||
"three": ">= 0.151.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three-perf": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/three-perf/-/three-perf-1.0.10.tgz",
|
||||
"integrity": "sha512-lCur/i8U6m0ysWYhQ1yFGWOZB0QA2oVsDsfynYd65HhXxLxJfiAt8OsXmpv9PnTLacfaZclBcZHUOB9QKk3eaw==",
|
||||
"dependencies": {
|
||||
"troika-three-text": "^0.47.2",
|
||||
"tweakpane": "^3.1.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.151"
|
||||
}
|
||||
},
|
||||
"node_modules/three-perf/node_modules/troika-three-text": {
|
||||
"version": "0.47.2",
|
||||
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.47.2.tgz",
|
||||
"integrity": "sha512-qylT0F+U7xGs+/PEf3ujBdJMYWbn0Qci0kLqI5BJG2kW1wdg4T1XSxneypnF05DxFqJhEzuaOR9S2SjiyknMng==",
|
||||
"dependencies": {
|
||||
"bidi-js": "^1.0.2",
|
||||
"troika-three-utils": "^0.47.2",
|
||||
"troika-worker-utils": "^0.47.2",
|
||||
"webgl-sdf-generator": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.125.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three-perf/node_modules/troika-three-utils": {
|
||||
"version": "0.47.2",
|
||||
"resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.47.2.tgz",
|
||||
"integrity": "sha512-/28plhCxfKtH7MSxEGx8e3b/OXU5A0xlwl+Sbdp0H8FXUHKZDoksduEKmjQayXYtxAyuUiCRunYIv/8Vi7aiyg==",
|
||||
"peerDependencies": {
|
||||
"three": ">=0.125.0"
|
||||
}
|
||||
},
|
||||
"node_modules/three-perf/node_modules/troika-worker-utils": {
|
||||
"version": "0.47.2",
|
||||
"resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.47.2.tgz",
|
||||
"integrity": "sha512-mzss4MeyzUkYBppn4x5cdAqrhBHFEuVmMMgLMTyFV23x6GvQMyo+/R5E5Lsbrt7WSt5RfvewjcwD1DChRTA9lA=="
|
||||
},
|
||||
"node_modules/tiny-glob": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz",
|
||||
|
@ -3063,6 +3201,33 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/troika-three-text": {
|
||||
"version": "0.49.0",
|
||||
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.49.0.tgz",
|
||||
"integrity": "sha512-sn9BNC6eIX8OO3iAkPwjecJ7Pn21Ve8P1UNFMNeQzXx759rrqS4i4pSZs7FLMYdWyCKVXBFGimBySFwRKLjq/Q==",
|
||||
"dependencies": {
|
||||
"bidi-js": "^1.0.2",
|
||||
"troika-three-utils": "^0.49.0",
|
||||
"troika-worker-utils": "^0.49.0",
|
||||
"webgl-sdf-generator": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"three": ">=0.125.0"
|
||||
}
|
||||
},
|
||||
"node_modules/troika-three-utils": {
|
||||
"version": "0.49.0",
|
||||
"resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.49.0.tgz",
|
||||
"integrity": "sha512-umitFL4cT+Fm/uONmaQEq4oZlyRHWwVClaS6ZrdcueRvwc2w+cpNQ47LlJKJswpqtMFWbEhOLy0TekmcPZOdYA==",
|
||||
"peerDependencies": {
|
||||
"three": ">=0.125.0"
|
||||
}
|
||||
},
|
||||
"node_modules/troika-worker-utils": {
|
||||
"version": "0.49.0",
|
||||
"resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.49.0.tgz",
|
||||
"integrity": "sha512-1xZHoJrG0HFfCvT/iyN41DvI/nRykiBtHqFkGaGgJwq5iXfIZFBiPPEHFpPpgyKM3Oo5ITHXP5wM2TNQszYdVg=="
|
||||
},
|
||||
"node_modules/ts-interface-checker": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
|
@ -3075,6 +3240,14 @@
|
|||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tweakpane": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/tweakpane/-/tweakpane-3.1.10.tgz",
|
||||
"integrity": "sha512-rqwnl/pUa7+inhI2E9ayGTqqP0EPOOn/wVvSWjZsRbZUItzNShny7pzwL3hVlaN4m9t/aZhsP0aFQ9U5VVR2VQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/cocopon"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||
|
@ -3193,6 +3366,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webgl-sdf-generator": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
|
||||
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA=="
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"@tauri-apps/cli": "^1.5.10",
|
||||
"@tsconfig/svelte": "^5.0.2",
|
||||
"@types/howler": "^2.2.11",
|
||||
"@types/three": "^0.161.2",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"postcss": "^8.4.35",
|
||||
"svelte": "^4.2.11",
|
||||
|
@ -28,12 +29,14 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@fontsource/roboto": "^5.0.8",
|
||||
"@threlte/core": "^7.1.0",
|
||||
"@threlte/extras": "^8.8.0",
|
||||
"howler": "^2.2.4",
|
||||
"material-icons": "^1.13.12",
|
||||
"material-symbols": "^0.15.0",
|
||||
"overlayscrollbars-svelte": "^0.5.3",
|
||||
"socket.io-client": "^4.7.4",
|
||||
"svelte-french-toast": "^1.2.0",
|
||||
"svrollbar": "^0.12.0"
|
||||
"three": "^0.161.0"
|
||||
}
|
||||
}
|
||||
|
|
BIN
client/public/static/app-icons/dev-tools.png
Normal file
BIN
client/public/static/app-icons/dev-tools.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -1,50 +1,51 @@
|
|||
<script lang="ts">
|
||||
import "@fontsource/roboto/latin.css";
|
||||
import "material-icons/iconfont/material-icons.css";
|
||||
import Dashboard from "./lib/Dashboard/Dashboard.svelte";
|
||||
import "material-symbols";
|
||||
import AppBar from "./lib/Apps/AppBar.svelte";
|
||||
import { appList } from "./lib/Apps/appList";
|
||||
import { initializeTelemetry } from "./lib/utils/initializeTelemetry";
|
||||
import { onMount } from "svelte";
|
||||
import { Toaster } from "svelte-french-toast";
|
||||
import { initializationSequence } from "./lib/Sequences/sequences";
|
||||
import Loading from "./lib/Loading/Loading.svelte";
|
||||
import { settingsStore } from "./lib/stores/settingsStore";
|
||||
import getSettings from "./lib/utils/getSettings";
|
||||
import '@fontsource/roboto/latin.css'
|
||||
import 'material-icons/iconfont/material-icons.css'
|
||||
import Dashboard from './lib/Dashboard/Dashboard.svelte'
|
||||
import 'material-symbols'
|
||||
import AppBar from './lib/Apps/AppBar.svelte'
|
||||
import { appList } from './lib/Apps/appList'
|
||||
import { initializeTelemetry } from './lib/utils/initializeTelemetry'
|
||||
import { onMount } from 'svelte'
|
||||
import { Toaster } from 'svelte-french-toast'
|
||||
import { initializationSequence } from './lib/Sequences/sequences'
|
||||
import Loading from './lib/Loading/Loading.svelte'
|
||||
import { settingsStore } from './lib/stores/settingsStore'
|
||||
import getSettings from './lib/utils/getSettings'
|
||||
import { Canvas } from '@threlte/core'
|
||||
|
||||
let activeApp: App = "camera";
|
||||
let activeApp: App = 'camera'
|
||||
let topics: TelemetryTopics = {
|
||||
doubles: [
|
||||
"orientation",
|
||||
"chassis-x-speed",
|
||||
"chassis-y-speed",
|
||||
"accx",
|
||||
"accy",
|
||||
"accz",
|
||||
"jerk-x",
|
||||
"jerk-y",
|
||||
"voltage",
|
||||
'orientation',
|
||||
'chassis-x-speed',
|
||||
'chassis-y-speed',
|
||||
'accx',
|
||||
'accy',
|
||||
'accz',
|
||||
'jerk-x',
|
||||
'jerk-y',
|
||||
'voltage',
|
||||
],
|
||||
strings: ["acc-profile", "gear"],
|
||||
booleans: ["ebrake", "reorient", "gpws"],
|
||||
};
|
||||
strings: ['acc-profile', 'gear'],
|
||||
booleans: ['ebrake', 'reorient', 'gpws'],
|
||||
}
|
||||
|
||||
let loading = $settingsStore.fastStartup ? false : true;
|
||||
let loading = $settingsStore.fastStartup ? false : true
|
||||
|
||||
onMount(() => {
|
||||
let savedSettings = getSettings();
|
||||
let savedSettings = getSettings()
|
||||
if (savedSettings !== false) {
|
||||
settingsStore.set(savedSettings);
|
||||
settingsStore.set(savedSettings)
|
||||
}
|
||||
window.ResizeObserver = ResizeObserver;
|
||||
window.ResizeObserver = ResizeObserver
|
||||
// disabled while migrating away from python
|
||||
// initializeTelemetry(topics, 200);
|
||||
initializeTelemetry(topics, 200)
|
||||
setTimeout(() => {
|
||||
loading = false;
|
||||
initializationSequence();
|
||||
}, 3000);
|
||||
});
|
||||
loading = false
|
||||
initializationSequence()
|
||||
}, 3000)
|
||||
})
|
||||
</script>
|
||||
|
||||
<main
|
||||
|
@ -76,7 +77,7 @@
|
|||
|
||||
<style lang="postcss">
|
||||
main {
|
||||
font-family: "Roboto", sans-serif;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.infotainment-container {
|
||||
|
|
11
client/src/assets/models/license.txt
Normal file
11
client/src/assets/models/license.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
Model Information:
|
||||
* title: Low Poly F/A-18 Hornet
|
||||
* source: https://sketchfab.com/3d-models/low-poly-fa-18-hornet-9b48c88e91ba40fc8f518b616f44f714
|
||||
* author: cs09736 (https://sketchfab.com/cs09736)
|
||||
|
||||
Model License:
|
||||
* license type: CC-BY-SA-4.0 (http://creativecommons.org/licenses/by-sa/4.0/)
|
||||
* requirements: Author must be credited. Modified versions must have the same license. Commercial use is allowed.
|
||||
|
||||
If you use this 3D model in your project be sure to copy paste this credit wherever you share it:
|
||||
This work is based on "Low Poly F/A-18 Hornet" (https://sketchfab.com/3d-models/low-poly-fa-18-hornet-9b48c88e91ba40fc8f518b616f44f714) by cs09736 (https://sketchfab.com/cs09736) licensed under CC-BY-SA-4.0 (http://creativecommons.org/licenses/by-sa/4.0/)
|
BIN
client/src/assets/models/scene.bin
Normal file
BIN
client/src/assets/models/scene.bin
Normal file
Binary file not shown.
754
client/src/assets/models/scene.gltf
Normal file
754
client/src/assets/models/scene.gltf
Normal file
|
@ -0,0 +1,754 @@
|
|||
{
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 2,
|
||||
"componentType": 5126,
|
||||
"count": 62,
|
||||
"max": [
|
||||
0.28601938486099243,
|
||||
-0.03159094601869583,
|
||||
1.3521472215652466
|
||||
],
|
||||
"min": [
|
||||
-0.28601938486099243,
|
||||
-0.3833470940589905,
|
||||
-2.612701416015625
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 744,
|
||||
"componentType": 5126,
|
||||
"count": 62,
|
||||
"max": [
|
||||
0.9978463053703308,
|
||||
0.9706209897994995,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
-0.9978463053703308,
|
||||
-0.2406158298254013,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"componentType": 5126,
|
||||
"count": 62,
|
||||
"max": [
|
||||
0.7755247354507446,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"componentType": 5125,
|
||||
"count": 90,
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 1488,
|
||||
"componentType": 5126,
|
||||
"count": 744,
|
||||
"max": [
|
||||
0.26639726758003235,
|
||||
0.11264356225728989,
|
||||
-2.2082409858703613
|
||||
],
|
||||
"min": [
|
||||
-0.26639726758003235,
|
||||
-0.13609451055526733,
|
||||
-2.7548983097076416
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 10416,
|
||||
"componentType": 5126,
|
||||
"count": 744,
|
||||
"max": [
|
||||
0.9659259915351868,
|
||||
0.9659258723258972,
|
||||
0.15793786942958832
|
||||
],
|
||||
"min": [
|
||||
-0.9659259915351868,
|
||||
-0.9659259915351868,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 496,
|
||||
"componentType": 5126,
|
||||
"count": 744,
|
||||
"max": [
|
||||
0.48029109835624695,
|
||||
0.4802909791469574
|
||||
],
|
||||
"min": [
|
||||
0.01970890536904335,
|
||||
0.019708896055817604
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 360,
|
||||
"componentType": 5125,
|
||||
"count": 1356,
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 19344,
|
||||
"componentType": 5126,
|
||||
"count": 6762,
|
||||
"max": [
|
||||
1.8555290699005127,
|
||||
0.828994631767273,
|
||||
2.448202133178711
|
||||
],
|
||||
"min": [
|
||||
-1.8555290699005127,
|
||||
-0.4696495532989502,
|
||||
-2.894035577774048
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 100488,
|
||||
"componentType": 5126,
|
||||
"count": 6762,
|
||||
"max": [
|
||||
1.0,
|
||||
0.9996607303619385,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
-1.0,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 6448,
|
||||
"componentType": 5126,
|
||||
"count": 6762,
|
||||
"max": [
|
||||
1.382123351097107,
|
||||
1.0000001192092896
|
||||
],
|
||||
"min": [
|
||||
0.0,
|
||||
-0.3820711672306061
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 5784,
|
||||
"componentType": 5125,
|
||||
"count": 10506,
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 181632,
|
||||
"componentType": 5126,
|
||||
"count": 108,
|
||||
"max": [
|
||||
0.15389108657836914,
|
||||
0.40167421102523804,
|
||||
1.4429497718811035
|
||||
],
|
||||
"min": [
|
||||
-0.15389108657836914,
|
||||
0.1835811734199524,
|
||||
0.3292185962200165
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 182928,
|
||||
"componentType": 5126,
|
||||
"count": 108,
|
||||
"max": [
|
||||
0.93450927734375,
|
||||
0.9493585228919983,
|
||||
0.32783323526382446
|
||||
],
|
||||
"min": [
|
||||
-0.93450927734375,
|
||||
0.34994035959243774,
|
||||
-0.14981554448604584
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 60544,
|
||||
"componentType": 5126,
|
||||
"count": 108,
|
||||
"max": [
|
||||
0.75,
|
||||
0.7012776732444763
|
||||
],
|
||||
"min": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 47808,
|
||||
"componentType": 5125,
|
||||
"count": 156,
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 184224,
|
||||
"componentType": 5126,
|
||||
"count": 458,
|
||||
"max": [
|
||||
0.25538086891174316,
|
||||
0.1017007827758789,
|
||||
-2.4583163261413574
|
||||
],
|
||||
"min": [
|
||||
-0.25538086891174316,
|
||||
-0.1249837875366211,
|
||||
-3.5668210983276367
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 189720,
|
||||
"componentType": 5126,
|
||||
"count": 458,
|
||||
"max": [
|
||||
0.9996814727783203,
|
||||
0.9996814727783203,
|
||||
0.6308417916297913
|
||||
],
|
||||
"min": [
|
||||
-0.9996814727783203,
|
||||
-0.9996814727783203,
|
||||
-0.6931174397468567
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 61408,
|
||||
"componentType": 5126,
|
||||
"count": 458,
|
||||
"max": [
|
||||
0.9998925924301147,
|
||||
0.6052115559577942
|
||||
],
|
||||
"min": [
|
||||
0.0001073777093552053,
|
||||
0.0001073777093552053
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 48432,
|
||||
"componentType": 5125,
|
||||
"count": 1896,
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 195216,
|
||||
"componentType": 5126,
|
||||
"count": 1993,
|
||||
"max": [
|
||||
0.457694411277771,
|
||||
0.019436508417129517,
|
||||
0.9355756044387817
|
||||
],
|
||||
"min": [
|
||||
-0.457694411277771,
|
||||
-0.5843615531921387,
|
||||
-1.0792063474655151
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 219132,
|
||||
"componentType": 5126,
|
||||
"count": 1993,
|
||||
"max": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
-1.0,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 65072,
|
||||
"componentType": 5126,
|
||||
"count": 1993,
|
||||
"max": [
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 56016,
|
||||
"componentType": 5125,
|
||||
"count": 5028,
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 243048,
|
||||
"componentType": 5126,
|
||||
"count": 832,
|
||||
"max": [
|
||||
0.457694411277771,
|
||||
-0.341027170419693,
|
||||
0.7706074714660645
|
||||
],
|
||||
"min": [
|
||||
-0.457694411277771,
|
||||
-0.6231322884559631,
|
||||
-1.1222631931304932
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 253032,
|
||||
"componentType": 5126,
|
||||
"count": 832,
|
||||
"max": [
|
||||
1.0,
|
||||
0.9659259915351868,
|
||||
0.9659262299537659
|
||||
],
|
||||
"min": [
|
||||
-1.0,
|
||||
-0.965925931930542,
|
||||
-0.9659261107444763
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 81016,
|
||||
"componentType": 5126,
|
||||
"count": 832,
|
||||
"max": [
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
7.450580596923828e-08,
|
||||
0.5
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 76128,
|
||||
"componentType": 5125,
|
||||
"count": 1440,
|
||||
"type": "SCALAR"
|
||||
}
|
||||
],
|
||||
"asset": {
|
||||
"extras": {
|
||||
"author": "cs09736 (https://sketchfab.com/cs09736)",
|
||||
"license": "CC-BY-SA-4.0 (http://creativecommons.org/licenses/by-sa/4.0/)",
|
||||
"source": "https://sketchfab.com/3d-models/low-poly-fa-18-hornet-9b48c88e91ba40fc8f518b616f44f714",
|
||||
"title": "Low Poly F/A-18 Hornet"
|
||||
},
|
||||
"generator": "Sketchfab-12.68.0",
|
||||
"version": "2.0"
|
||||
},
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 81888,
|
||||
"name": "floatBufferViews",
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 87672,
|
||||
"byteOffset": 81888,
|
||||
"byteStride": 8,
|
||||
"name": "floatBufferViews",
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 263016,
|
||||
"byteOffset": 169560,
|
||||
"byteStride": 12,
|
||||
"name": "floatBufferViews",
|
||||
"target": 34962
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 432576,
|
||||
"uri": "scene.bin"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"doubleSided": true,
|
||||
"name": "clay",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.0,
|
||||
"roughnessFactor": 0.4
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided": true,
|
||||
"emissiveFactor": [
|
||||
0.214041,
|
||||
0.000464395,
|
||||
0.0
|
||||
],
|
||||
"name": "engine_inside",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.8,
|
||||
0.212337,
|
||||
0.0981736,
|
||||
1.0
|
||||
],
|
||||
"roughnessFactor": 0.742424
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided": true,
|
||||
"name": "body_paint",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.465996,
|
||||
0.488985,
|
||||
0.522522,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.1,
|
||||
"roughnessFactor": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided": true,
|
||||
"name": "glass",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.360226,
|
||||
0.603827,
|
||||
0.476213,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.7,
|
||||
"roughnessFactor": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided": true,
|
||||
"emissiveFactor": [
|
||||
1.0,
|
||||
0.0571504,
|
||||
0.00509488
|
||||
],
|
||||
"name": "after_burner",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.4407909999999996,
|
||||
0.17297200000000015,
|
||||
0.08980889999999998,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.0,
|
||||
"roughnessFactor": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided": true,
|
||||
"name": "strut",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.616618,
|
||||
0.6473,
|
||||
0.692071,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.1,
|
||||
"roughnessFactor": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided": true,
|
||||
"name": "tyre",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.00424211,
|
||||
0.00424211,
|
||||
0.00424211,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.0,
|
||||
"roughnessFactor": 0.6
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"name": "Object_0",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 1,
|
||||
"POSITION": 0,
|
||||
"TEXCOORD_0": 2
|
||||
},
|
||||
"indices": 3,
|
||||
"material": 0,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Object_1",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 5,
|
||||
"POSITION": 4,
|
||||
"TEXCOORD_0": 6
|
||||
},
|
||||
"indices": 7,
|
||||
"material": 1,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Object_2",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 9,
|
||||
"POSITION": 8,
|
||||
"TEXCOORD_0": 10
|
||||
},
|
||||
"indices": 11,
|
||||
"material": 2,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Object_3",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 13,
|
||||
"POSITION": 12,
|
||||
"TEXCOORD_0": 14
|
||||
},
|
||||
"indices": 15,
|
||||
"material": 3,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Object_4",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 17,
|
||||
"POSITION": 16,
|
||||
"TEXCOORD_0": 18
|
||||
},
|
||||
"indices": 19,
|
||||
"material": 4,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Object_5",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 21,
|
||||
"POSITION": 20,
|
||||
"TEXCOORD_0": 22
|
||||
},
|
||||
"indices": 23,
|
||||
"material": 5,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Object_6",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 25,
|
||||
"POSITION": 24,
|
||||
"TEXCOORD_0": 26
|
||||
},
|
||||
"indices": 27,
|
||||
"material": 6,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"children": [
|
||||
1
|
||||
],
|
||||
"matrix": [
|
||||
2.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
4.440892098500626e-16,
|
||||
-2.0,
|
||||
0.0,
|
||||
0.0,
|
||||
2.0,
|
||||
4.440892098500626e-16,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"name": "Sketchfab_model"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
2
|
||||
],
|
||||
"name": "root"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
3
|
||||
],
|
||||
"matrix": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
2.220446049250313e-16,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0,
|
||||
2.220446049250313e-16,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"name": "GLTF_SceneRootNode"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10
|
||||
],
|
||||
"name": "aircraft_0"
|
||||
},
|
||||
{
|
||||
"mesh": 0,
|
||||
"name": "Object_4"
|
||||
},
|
||||
{
|
||||
"mesh": 1,
|
||||
"name": "Object_5"
|
||||
},
|
||||
{
|
||||
"mesh": 2,
|
||||
"name": "Object_6"
|
||||
},
|
||||
{
|
||||
"mesh": 3,
|
||||
"name": "Object_7"
|
||||
},
|
||||
{
|
||||
"mesh": 4,
|
||||
"name": "Object_8"
|
||||
},
|
||||
{
|
||||
"mesh": 5,
|
||||
"name": "Object_9"
|
||||
},
|
||||
{
|
||||
"mesh": 6,
|
||||
"name": "Object_10"
|
||||
}
|
||||
],
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"name": "Sketchfab_Scene",
|
||||
"nodes": [
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
26
client/src/lib/Apps/DevTools/DevTools.svelte
Normal file
26
client/src/lib/Apps/DevTools/DevTools.svelte
Normal file
|
@ -0,0 +1,26 @@
|
|||
<script lang="ts">
|
||||
import AppContainer from '../AppContainer.svelte'
|
||||
</script>
|
||||
|
||||
<AppContainer
|
||||
class="flex gap-6 bg-blue-200 bg-opacity-25 backdrop-blur-xl media-background rounded-3xl flex-wrap px-10 py-20"
|
||||
>
|
||||
<h1 class="text-5xl font-medium text-slate-100 basis-full">
|
||||
Developer Tools
|
||||
</h1>
|
||||
<h2 class="basis-full">
|
||||
This is an app where developers can add buttons that trigger code to help
|
||||
during development.
|
||||
</h2>
|
||||
<p class="basis-full">
|
||||
Make sure the entry for this app is commented out in appList.ts before
|
||||
building for production.
|
||||
</p>
|
||||
<button class="button">Button</button>
|
||||
</AppContainer>
|
||||
|
||||
<style lang="postcss">
|
||||
.button {
|
||||
@apply px-4 py-2 bg-blue-500 hover:brightness-75 font-medium rounded-lg w-min;
|
||||
}
|
||||
</style>
|
|
@ -9,6 +9,7 @@ const resolveIconPath = (slug: keyof typeof appList, format: Format) => {
|
|||
|
||||
import GBAEmulator from './GBAEmulator/GBAEmulator.svelte'
|
||||
import JsDoom from './JSDoom/JSDoom.svelte'
|
||||
import DevTools from './DevTools/DevTools.svelte'
|
||||
|
||||
interface AppList {
|
||||
[key: string]: {
|
||||
|
@ -50,4 +51,10 @@ export const appList: AppList = {
|
|||
icon: resolveIconPath('settings', 'png'),
|
||||
external: false,
|
||||
},
|
||||
'dev-tools': {
|
||||
name: 'DevTools',
|
||||
component: DevTools,
|
||||
icon: resolveIconPath('dev-tools', 'png'),
|
||||
external: false,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import Compass from './Compass.svelte'
|
||||
import { telemetryReadonlyStore } from '../stores/telemetryStore'
|
||||
import Bottom from './Bottom.svelte'
|
||||
import Visualization from './Visualization/Visualization.svelte'
|
||||
|
||||
$: speedResolved = Math.hypot(
|
||||
$telemetryReadonlyStore['chassis-x-speed'],
|
||||
|
@ -36,6 +37,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="left-0 mt-2 h-[475px] w-[35vw]">
|
||||
<Visualization />
|
||||
</div>
|
||||
|
||||
<Bottom>
|
||||
<div class="mb-10">
|
||||
<Compass
|
||||
|
|
178
client/src/lib/Dashboard/Visualization/Controls.svelte
Normal file
178
client/src/lib/Dashboard/Visualization/Controls.svelte
Normal file
|
@ -0,0 +1,178 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, onDestroy } from 'svelte'
|
||||
import { Camera, Vector2, Vector3, Quaternion } from 'three'
|
||||
import { useThrelte, useParent, useTask } from '@threlte/core'
|
||||
|
||||
export let object: Camera
|
||||
export let rotateSpeed = 1.0
|
||||
|
||||
$: if (object) {
|
||||
// console.log(object)
|
||||
// object.position.y = 10
|
||||
// // Calculate the direction vector towards (0, 0, 0)
|
||||
// const target = new Vector3(0, 0, 0)
|
||||
// const direction = target.clone().sub(object.position).normalize()
|
||||
// // Extract the forward direction from the object's current rotation matrix
|
||||
// const currentDirection = new Vector3(0, 1, 0)
|
||||
// currentDirection.applyQuaternion(object.quaternion)
|
||||
// // Calculate the axis and angle to rotate the object
|
||||
// const rotationAxis = currentDirection.clone().cross(direction).normalize()
|
||||
// const rotationAngle = Math.acos(currentDirection.dot(direction))
|
||||
// // Rotate the object using rotateOnAxis()
|
||||
// object.rotateOnAxis(rotationAxis, rotationAngle)
|
||||
}
|
||||
|
||||
export let idealOffset = { x: -0.5, y: 2, z: -3 }
|
||||
export let idealLookAt = { x: 0, y: 1, z: 5 }
|
||||
|
||||
const currentPosition = new Vector3()
|
||||
const currentLookAt = new Vector3()
|
||||
|
||||
let isOrbiting = false
|
||||
let pointerDown = false
|
||||
|
||||
const rotateStart = new Vector2()
|
||||
const rotateEnd = new Vector2()
|
||||
const rotateDelta = new Vector2()
|
||||
|
||||
const axis = new Vector3(0, 1, 0)
|
||||
const rotationQuat = new Quaternion()
|
||||
|
||||
const { renderer, invalidate } = useThrelte()
|
||||
|
||||
const domElement = renderer.domElement
|
||||
const camera = useParent()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const isCamera = (p: any): p is Camera => {
|
||||
return p.isCamera
|
||||
}
|
||||
|
||||
if (!isCamera($camera)) {
|
||||
throw new Error(
|
||||
'Parent missing: <PointerLockControls> need to be a child of a <Camera>'
|
||||
)
|
||||
}
|
||||
|
||||
// domElement.addEventListener('pointerdown', onPointerDown)
|
||||
// domElement.addEventListener('pointermove', onPointerMove)
|
||||
// domElement.addEventListener('pointerleave', onPointerLeave)
|
||||
// domElement.addEventListener('pointerup', onPointerUp)
|
||||
|
||||
onDestroy(() => {
|
||||
// domElement.removeEventListener('pointerdown', onPointerDown)
|
||||
// domElement.removeEventListener('pointermove', onPointerMove)
|
||||
// domElement.removeEventListener('pointerleave', onPointerLeave)
|
||||
// domElement.removeEventListener('pointerup', onPointerUp)
|
||||
})
|
||||
|
||||
// This is basically your update function
|
||||
useTask(delta => {
|
||||
// the object's position is bound to the prop
|
||||
if (!object) return
|
||||
|
||||
// camera is based on character so we rotation character first
|
||||
// rotationQuat.setFromAxisAngle(axis, -rotateDelta.x * rotateSpeed * delta)
|
||||
// object.quaternion.multiply(rotationQuat)
|
||||
|
||||
// then we calculate our ideal's
|
||||
const offset = vectorFromObject(idealOffset)
|
||||
const lookAt = vectorFromObject(idealLookAt)
|
||||
|
||||
// and how far we should move towards them
|
||||
const t = 1.0 - Math.pow(0.001, delta)
|
||||
currentPosition.lerp(offset, t)
|
||||
currentLookAt.lerp(lookAt, t)
|
||||
|
||||
// then finally set the camera, a bit behind the model
|
||||
$camera!.position.copy(currentPosition)
|
||||
const behindOffset = currentPosition
|
||||
.clone()
|
||||
.normalize()
|
||||
.multiplyScalar(8)
|
||||
.setY(0.5)
|
||||
|
||||
$camera!.position.copy(currentPosition).add(behindOffset)
|
||||
|
||||
$camera!.lookAt(currentLookAt)
|
||||
})
|
||||
|
||||
function onPointerMove(event: PointerEvent) {
|
||||
const { x, y } = event
|
||||
if (pointerDown && !isOrbiting) {
|
||||
// calculate distance from init down
|
||||
const distCheck =
|
||||
Math.sqrt(
|
||||
Math.pow(x - rotateStart.x, 2) + Math.pow(y - rotateStart.y, 2)
|
||||
) > 10
|
||||
if (distCheck) {
|
||||
isOrbiting = true
|
||||
}
|
||||
}
|
||||
if (!isOrbiting) return
|
||||
|
||||
rotateEnd.set(x, y)
|
||||
rotateDelta.subVectors(rotateEnd, rotateStart).multiplyScalar(rotateSpeed)
|
||||
rotateStart.copy(rotateEnd)
|
||||
|
||||
invalidate()
|
||||
dispatch('change')
|
||||
}
|
||||
|
||||
function onPointerDown(event: PointerEvent) {
|
||||
const { x, y } = event
|
||||
rotateStart.set(x, y)
|
||||
pointerDown = true
|
||||
}
|
||||
|
||||
function onPointerUp() {
|
||||
rotateDelta.set(0, 0)
|
||||
pointerDown = false
|
||||
isOrbiting = false
|
||||
}
|
||||
|
||||
function onPointerLeave() {
|
||||
rotateDelta.set(0, 0)
|
||||
pointerDown = false
|
||||
isOrbiting = false
|
||||
}
|
||||
|
||||
function vectorFromObject(vec: { x: number; y: number; z: number }) {
|
||||
const { x, y, z } = vec
|
||||
const ideal = new Vector3(x, y, z)
|
||||
ideal.applyQuaternion(object.quaternion)
|
||||
ideal.add(
|
||||
new Vector3(object.position.x, object.position.y, object.position.z)
|
||||
)
|
||||
return ideal
|
||||
}
|
||||
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case 'a':
|
||||
rotateDelta.x = -2 * rotateSpeed
|
||||
break
|
||||
case 'd':
|
||||
rotateDelta.x = 2 * rotateSpeed
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function onKeyUp(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case 'a':
|
||||
rotateDelta.x = 0
|
||||
break
|
||||
case 'd':
|
||||
rotateDelta.x = 0
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- <svelte:window on:keydown={onKeyDown} on:keyup={onKeyUp} /> -->
|
141
client/src/lib/Dashboard/Visualization/Scene.svelte
Normal file
141
client/src/lib/Dashboard/Visualization/Scene.svelte
Normal file
|
@ -0,0 +1,141 @@
|
|||
<script lang="ts">
|
||||
import { T, useTask } from '@threlte/core'
|
||||
import { ContactShadows, Float, Grid, OrbitControls } from '@threlte/extras'
|
||||
import Hornet from './models/Hornet.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import Controls from './Controls.svelte'
|
||||
import type { Camera, Group, Object3D, Object3DEventMap } from 'three'
|
||||
import {
|
||||
telemetryReadonlyStore,
|
||||
telemetryStore,
|
||||
} from '../../stores/telemetryStore'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
// const rotate90 = () => {
|
||||
// const originalRot = rot
|
||||
// }
|
||||
|
||||
// onMount(() => {
|
||||
// setTimeout(rotate90, 5000)
|
||||
// })
|
||||
|
||||
// CONSTANTS
|
||||
const maxAngularVelocity = 2 // Max angular velocity, in radians per second
|
||||
const stoppingThreshold = 0.005 // Threshold in radians for when to consider the rotation close enough to stop
|
||||
|
||||
// Proportional control factor
|
||||
const kP = 2 // Adjust this value based on responsiveness and stability needs
|
||||
|
||||
// simulate some turning for testing
|
||||
const simulateTurning = () => {
|
||||
let delay = Math.random() * 4500 + 500
|
||||
let randOffset = Math.random() * 170 * (Math.random() < 0.5 ? -1 : 1)
|
||||
telemetryStore.update({
|
||||
...get(telemetryReadonlyStore),
|
||||
orientation: get(telemetryReadonlyStore)['orientation'] + randOffset,
|
||||
})
|
||||
setTimeout(simulateTurning, delay)
|
||||
}
|
||||
simulateTurning()
|
||||
|
||||
// Sync robot orientation with target rotation
|
||||
let targetRot = 0
|
||||
telemetryReadonlyStore.subscribe(value => {
|
||||
targetRot = (value['orientation'] * Math.PI) / 180 // convert deg to rad
|
||||
})
|
||||
|
||||
// Updates rotation to match target with PID controller (intended to be invoked in useTask)
|
||||
let rot = 0 // (initial) rotation in radians
|
||||
let angularVelocity = 0
|
||||
const updateRotation = (delta: number) => {
|
||||
let angleDifference = targetRot - rot
|
||||
|
||||
// Normalize angle difference to the range [-π, π]
|
||||
angleDifference = ((angleDifference + Math.PI) % (2 * Math.PI)) - Math.PI
|
||||
|
||||
// Calculate the desired angular velocity based on the angle difference
|
||||
let desiredVelocity =
|
||||
Math.sign(angleDifference) *
|
||||
Math.min(maxAngularVelocity, Math.abs(kP * angleDifference))
|
||||
|
||||
// If the object is very close to the target, adjust the desired velocity to zero to prevent overshooting
|
||||
if (Math.abs(angleDifference) < stoppingThreshold) {
|
||||
desiredVelocity = 0
|
||||
}
|
||||
|
||||
// Adjust angular velocity towards desired velocity
|
||||
angularVelocity = desiredVelocity
|
||||
|
||||
// Update rotation
|
||||
rot += angularVelocity * delta
|
||||
|
||||
// Normalize rot to the range [0, 2π]
|
||||
if (rot < 0) rot += 2 * Math.PI
|
||||
else if (rot > 2 * Math.PI) rot -= 2 * Math.PI
|
||||
|
||||
// Snap to the target rotation to prevent tiny oscillations if close enough
|
||||
if (Math.abs(angleDifference) < stoppingThreshold) {
|
||||
rot = targetRot
|
||||
angularVelocity = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Assuming useTask is called every frame with the time delta
|
||||
useTask(delta => {
|
||||
updateRotation(delta)
|
||||
})
|
||||
|
||||
let capsule: Group<Object3DEventMap>
|
||||
let capRef: Camera
|
||||
$: if (capsule) {
|
||||
// typescript hacks because i dont know what im doing
|
||||
capRef = capsule as unknown as Camera
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- <T.PerspectiveCamera makeDefault position={[-10, 10, 10]} fov={15}>
|
||||
<OrbitControls
|
||||
autoRotate
|
||||
enableZoom={true}
|
||||
enableDamping
|
||||
autoRotateSpeed={0.5}
|
||||
target.y={1.5}
|
||||
/>
|
||||
</T.PerspectiveCamera> -->
|
||||
|
||||
<T.PerspectiveCamera makeDefault position={[0, 8, -20]} fov={75} on:create>
|
||||
<OrbitControls
|
||||
enableZoom={true}
|
||||
enableDamping
|
||||
autoRotateSpeed={5}
|
||||
target.y={1.5}
|
||||
autoRotate
|
||||
/>
|
||||
<Controls bind:object={capRef} />
|
||||
</T.PerspectiveCamera>
|
||||
|
||||
<T.DirectionalLight intensity={0.8} position.x={5} position.y={10} />
|
||||
<T.AmbientLight intensity={0.2} />
|
||||
|
||||
<Grid
|
||||
position.y={1}
|
||||
cellColor="#ffffff"
|
||||
sectionColor="#ffffff"
|
||||
sectionThickness={0}
|
||||
fadeDistance={50}
|
||||
cellSize={2}
|
||||
/>
|
||||
|
||||
<ContactShadows scale={10} blur={2} far={2.5} opacity={0.5} />
|
||||
|
||||
<Float floatIntensity={1} floatingRange={[0, 0.5]}>
|
||||
<!-- <T.Mesh > -->
|
||||
<Hornet
|
||||
position={[0, 1.7, 0]}
|
||||
scale={[0.8, 0.8, 0.8]}
|
||||
bind:ref={capsule}
|
||||
rotation.y={rot}
|
||||
/>
|
||||
<!-- <T.MeshStandardMaterial color="#F8EBCE" /> -->
|
||||
<!-- </T.Mesh> -->
|
||||
</Float>
|
|
@ -0,0 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { Canvas } from '@threlte/core'
|
||||
import Scene from './Scene.svelte'
|
||||
</script>
|
||||
|
||||
<Canvas>
|
||||
<Scene />
|
||||
</Canvas>
|
95
client/src/lib/Dashboard/Visualization/models/Hornet.svelte
Normal file
95
client/src/lib/Dashboard/Visualization/models/Hornet.svelte
Normal file
|
@ -0,0 +1,95 @@
|
|||
<!--
|
||||
Auto-generated by: https://github.com/threlte/threlte/tree/main/packages/gltf
|
||||
Command: npx @threlte/gltf@2.0.1 /Users/youwenw/Projects/JS/threlte-demo/static/models/scene.gltf --root /models/ --types --printwidth 120 --precision 2
|
||||
Author: cs09736 (https://sketchfab.com/cs09736)
|
||||
License: CC-BY-SA-4.0 (http://creativecommons.org/licenses/by-sa/4.0/)
|
||||
Source: https://sketchfab.com/3d-models/low-poly-fa-18-hornet-9b48c88e91ba40fc8f518b616f44f714
|
||||
Title: Low Poly F/A-18 Hornet
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type * as THREE from 'three'
|
||||
import { Group } from 'three'
|
||||
import {
|
||||
T,
|
||||
type Props,
|
||||
type Events,
|
||||
type Slots,
|
||||
forwardEventHandlers,
|
||||
} from '@threlte/core'
|
||||
import { useGltf } from '@threlte/extras'
|
||||
|
||||
type $$Props = Props<THREE.Group>
|
||||
type $$Events = Events<THREE.Group>
|
||||
type $$Slots = Slots<THREE.Group> & { fallback: {}; error: { error: any } }
|
||||
|
||||
export const ref = new Group()
|
||||
|
||||
type GLTFResult = {
|
||||
nodes: {
|
||||
Object_4: THREE.Mesh
|
||||
Object_5: THREE.Mesh
|
||||
Object_6: THREE.Mesh
|
||||
Object_7: THREE.Mesh
|
||||
Object_8: THREE.Mesh
|
||||
Object_9: THREE.Mesh
|
||||
Object_10: THREE.Mesh
|
||||
}
|
||||
materials: {
|
||||
clay: THREE.MeshStandardMaterial
|
||||
engine_inside: THREE.MeshStandardMaterial
|
||||
body_paint: THREE.MeshStandardMaterial
|
||||
glass: THREE.MeshStandardMaterial
|
||||
after_burner: THREE.MeshStandardMaterial
|
||||
strut: THREE.MeshStandardMaterial
|
||||
tyre: THREE.MeshStandardMaterial
|
||||
}
|
||||
}
|
||||
|
||||
const gltf = useGltf<GLTFResult>('/src/assets/models/scene.gltf')
|
||||
|
||||
const component = forwardEventHandlers()
|
||||
</script>
|
||||
|
||||
<T is={ref} dispose={false} {...$$restProps} bind:this={$component}>
|
||||
{#await gltf}
|
||||
<slot name="fallback" />
|
||||
{:then gltf}
|
||||
<T.Group rotation={[-Math.PI / 2, 0, 0]} scale={2}>
|
||||
<T.Group rotation={[Math.PI / 2, 0, 0]}>
|
||||
<T.Mesh
|
||||
geometry={gltf.nodes.Object_4.geometry}
|
||||
material={gltf.materials.clay}
|
||||
/>
|
||||
<T.Mesh
|
||||
geometry={gltf.nodes.Object_5.geometry}
|
||||
material={gltf.materials.engine_inside}
|
||||
/>
|
||||
<T.Mesh
|
||||
geometry={gltf.nodes.Object_6.geometry}
|
||||
material={gltf.materials.body_paint}
|
||||
/>
|
||||
<T.Mesh
|
||||
geometry={gltf.nodes.Object_7.geometry}
|
||||
material={gltf.materials.glass}
|
||||
/>
|
||||
<T.Mesh
|
||||
geometry={gltf.nodes.Object_8.geometry}
|
||||
material={gltf.materials.after_burner}
|
||||
/>
|
||||
<T.Mesh
|
||||
geometry={gltf.nodes.Object_9.geometry}
|
||||
material={gltf.materials.strut}
|
||||
/>
|
||||
<T.Mesh
|
||||
geometry={gltf.nodes.Object_10.geometry}
|
||||
material={gltf.materials.tyre}
|
||||
/>
|
||||
</T.Group>
|
||||
</T.Group>
|
||||
{:catch error}
|
||||
<slot name="error" {error} />
|
||||
{/await}
|
||||
|
||||
<slot {ref} />
|
||||
</T>
|
0
client/src/lib/Dashboard/Visualization/utils.ts
Normal file
0
client/src/lib/Dashboard/Visualization/utils.ts
Normal file
Loading…
Reference in a new issue