feat: add robot visualization via threlte

This commit is contained in:
Youwen Wu 2024-02-25 21:51:33 -08:00
parent cb88e800f0
commit fc82783845
15 changed files with 1450 additions and 43 deletions

190
client/package-lock.json generated
View file

@ -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",

View file

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -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 {

View 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/)

Binary file not shown.

View 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
]
}
]
}

View 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>

View file

@ -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,
},
}

View file

@ -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

View 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} /> -->

View 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>

View file

@ -0,0 +1,8 @@
<script lang="ts">
import { Canvas } from '@threlte/core'
import Scene from './Scene.svelte'
</script>
<Canvas>
<Scene />
</Canvas>

View 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>