feat: new visualization system working

This commit is contained in:
Youwen Wu 2024-02-29 20:11:38 -08:00
parent 84dabc67f2
commit 5791179aec
10 changed files with 191 additions and 108 deletions

View file

@ -21,6 +21,8 @@
overflow: auto; /* or 'scroll' if you always want scrollability */ overflow: auto; /* or 'scroll' if you always want scrollability */
scrollbar-width: none; /* Hide scrollbar for Firefox */ scrollbar-width: none; /* Hide scrollbar for Firefox */
-ms-overflow-style: none; /* Hide scrollbar for IE 10+ and Edge */ -ms-overflow-style: none; /* Hide scrollbar for IE 10+ and Edge */
overscroll-behavior: none;
} }
body::-webkit-scrollbar { body::-webkit-scrollbar {
display: none; display: none;

View file

@ -1,4 +1,8 @@
<script lang="ts"> <script lang="ts">
import {
cameraControls,
cameraState,
} from '../../Dashboard/Visualization/CameraControls/utils/cameraStore'
import AppContainer from '../AppContainer.svelte' import AppContainer from '../AppContainer.svelte'
import { simulateMotion } from './simulateMotion' import { simulateMotion } from './simulateMotion'
import { import {
@ -6,6 +10,22 @@
increaseSpeedTo, increaseSpeedTo,
setStationaryTelemetry, setStationaryTelemetry,
} from './telemetrySimulators' } from './telemetrySimulators'
let cameraMode = 'orbit'
const changeCamera = () => {
if (cameraMode === 'follow-direction') {
cameraMode = 'orbit'
cameraState.set('mode', 'orbit')
cameraState.set('userControlled', false)
console.log($cameraState.mode)
} else {
cameraMode = 'follow-direction'
cameraState.set('mode', 'follow-direction')
cameraState.set('userControlled', true)
console.log($cameraState.mode)
}
}
</script> </script>
<AppContainer <AppContainer
@ -49,6 +69,7 @@
<button class="button" on:click={simulateMotion}> <button class="button" on:click={simulateMotion}>
Simulate random motion Simulate random motion
</button> </button>
<button class="button" on:click={changeCamera}> Change camera mode </button>
</AppContainer> </AppContainer>
<style lang="postcss"> <style lang="postcss">

View file

@ -1,15 +1,18 @@
import { get } from 'svelte/store' import { get } from 'svelte/store'
import { telemetryStore } from '../../stores/telemetryStore' import { telemetryStore } from '../../stores/telemetryStore'
import { increaseRotationTo, increaseSpeedTo } from './telemetrySimulators'
// simulate some turning for testing // simulate some turning for testing
export const simulateMotion = () => { export const simulateMotion = () => {
let delay = Math.random() * 4500 + 500 let delay = Math.random() * 4500 + 500
let randOffset = Math.random() * 360 let randOffset = Math.random() * 360
telemetryStore.update({
...get(telemetryStore), increaseSpeedTo(
'orientation': randOffset, Math.random() * 4 * (Math.random() < 0.5 ? -1 : 1),
'chassis-x-speed': Math.random() * 4 * (Math.random() < 0.5 ? -1 : 1), Math.random() * 4 * (Math.random() < 0.5 ? -1 : 1)
'chassis-y-speed': Math.random() * 4 * (Math.random() < 0.5 ? -1 : 1), )
})
increaseRotationTo(randOffset)
setTimeout(simulateMotion, delay) setTimeout(simulateMotion, delay)
} }

View file

@ -38,7 +38,7 @@ export const increaseSpeedTo = async (targetX: number, targetY: number) => {
} }
} }
const delay = () => new Promise(resolve => setTimeout(resolve, 1000)) // Assuming a 100ms tick for demonstration const delay = () => new Promise(resolve => setTimeout(resolve, 500)) // Assuming a 100ms tick for demonstration
const lerp = (start: number, end: number, alpha: number) => const lerp = (start: number, end: number, alpha: number) =>
start + (end - start) * alpha start + (end - start) * alpha
@ -69,3 +69,47 @@ export const changeGear = (gear: Gear) => {
gear: gear, gear: gear,
}) })
} }
let cancelPreviousCall = () => {} // Function to cancel the previous interpolation
const getAngle = () => {
return get(telemetryStore)['orientation']
}
const setAngle = (angle: number) => {
telemetryStore.update({
...get(telemetryStore),
orientation: angle,
})
}
export const increaseRotationTo = async (targetAngle: number) => {
let isCancelled = false
cancelPreviousCall() // Cancel any ongoing interpolation
cancelPreviousCall = () => {
isCancelled = true
} // Setup cancellation for the current call
const lerp = (start: number, end: number, alpha: number) =>
start + (end - start) * alpha
const tick = () => new Promise(resolve => setTimeout(resolve, 1000)) // Assuming a 100ms tick for demonstration
let currentAngle = getAngle() // Assume getAngle() retrieves the current angle
const steps = 10 // Number of steps for the interpolation
for (let i = 1; i <= steps; i++) {
if (isCancelled) return // Exit if a new target angle is set
const alpha = i / steps // Calculate interpolation fraction
// Interpolate angle
const nextAngle = lerp(currentAngle, targetAngle, alpha)
setAngle(nextAngle) // Update angle
await tick() // Wait for state update synchronization
// Update current angle for the next iteration
currentAngle = nextAngle
}
}

View file

@ -16,12 +16,10 @@
> >
<div <div
class="px-3 py-1 border-black rounded-xl border-2 flex flex-col text-center gap-1 transition-all" class="px-3 py-1 border-black rounded-xl border-2 flex flex-col text-center gap-1 transition-all"
>
<div class="text-lg font-medium">SPEED<br />LIMIT</div>
<div
class="text-2xl font-bold transition"
class:speed-limit-placeholder={placeholder} class:speed-limit-placeholder={placeholder}
> >
<div class="text-lg font-medium">SPEED<br />LIMIT</div>
<div class="text-2xl font-bold transition">
{speedLimit} {speedLimit}
</div> </div>
</div> </div>
@ -29,6 +27,6 @@
<style lang="postcss"> <style lang="postcss">
.speed-limit-placeholder { .speed-limit-placeholder {
@apply text-neutral-200 bg-neutral-200 animate-pulse rounded-lg; @apply text-neutral-300 bg-neutral-300 animate-pulse rounded-lg;
} }
</style> </style>

View file

@ -34,6 +34,7 @@
type PerspectiveCamera, type PerspectiveCamera,
} from 'three' } from 'three'
import { DEG2RAD } from 'three/src/math/MathUtils' import { DEG2RAD } from 'three/src/math/MathUtils'
import { cameraState } from './utils/cameraStore'
const subsetOfTHREE = { const subsetOfTHREE = {
Vector2, Vector2,
@ -70,11 +71,9 @@
const getControls = () => ref const getControls = () => ref
let disableAutoRotate = false
useTask( useTask(
delta => { delta => {
if (autoRotate && !disableAutoRotate) { if (autoRotate && !$cameraState.userControlled) {
getControls().azimuthAngle += 4 * delta * DEG2RAD * autoRotateSpeed getControls().azimuthAngle += 4 * delta * DEG2RAD * autoRotateSpeed
} }
const updated = getControls().update(delta) const updated = getControls().update(delta)
@ -91,13 +90,13 @@
<T <T
is={ref} is={ref}
on:controlstart={e => { on:controlstart={e => {
disableAutoRotate = true cameraState.set('userControlled', true)
}} }}
on:zoom={e => { on:zoom={e => {
console.log('zoomstart', e) console.log('zoomstart', e)
}} }}
on:controlend={() => { on:controlend={() => {
disableAutoRotate = false cameraState.set('userControlled', false)
}} }}
{...$$restProps} {...$$restProps}
bind:this={$forwardingComponent} bind:this={$forwardingComponent}

View file

@ -1,122 +1,96 @@
<script lang="ts"> <script lang="ts">
import { T, useTask } from "@threlte/core"; import { T, useTask } from '@threlte/core'
import { Grid } from "@threlte/extras"; import { Grid } from '@threlte/extras'
import CameraControls from "./CameraControls.svelte"; import CameraControls from './CameraControls.svelte'
import { cameraControls, mesh } from "./utils/cameraStore"; import { cameraControls, mesh, cameraState } from './utils/cameraStore'
import { Vector3 } from "three"; import { Vector3 } from 'three'
import { onMount } from "svelte"; import { onMount } from 'svelte'
import RobotDecimated from "../models/RobotDecimated.svelte"; import RobotDecimated from '../models/RobotDecimated.svelte'
import { telemetryReadonlyStore } from '../../../stores/telemetryStore'
import { DEG2RAD } from 'three/src/math/MathUtils.js'
function vectorFromObject() { const SPEED_MULTIPLIER = 4
let ideal: Vector3 = new Vector3(); const axis = new Vector3(0, 1, 0)
$cameraControls.getPosition(ideal, true);
ideal.applyQuaternion($mesh.quaternion);
ideal.add(
new Vector3($mesh.position.x, $mesh.position.y, $mesh.position.z)
);
return ideal;
}
const follow = (delta: number) => { const follow = (delta: number) => {
// the object's position is bound to the prop // the object's position is bound to the prop
if (!$mesh || !$cameraControls) return; if (!$mesh || !$cameraControls) return
// typescript HACKS! never do this! How does this work? who knows! const offsetPosition = new Vector3()
const robotPosition = vectorFromObject(); offsetPosition.copy($mesh.position)
const offsetVector = new Vector3(2.5, 0, -2)
const horizontalOffsetDistance = 12; // Distance behind the leading vector offsetVector.applyAxisAngle(axis, $mesh.rotation.y)
const direction = new Vector3(0, 0, 1); // Default forward direction in Three.js is negative z-axis, so behind is positive z-axis offsetPosition.add(offsetVector)
const verticalOffset = new Vector3(0, -2.8, 0);
// Calculate the offset vector
const offsetVector = direction
.normalize()
.multiplyScalar(horizontalOffsetDistance)
.add(verticalOffset);
// If the leading object is rotating, apply its rotation to the offset vector
const rotatedOffsetVector = offsetVector.applyQuaternion($mesh.quaternion);
// Calculate the trailing vector's position
const trailingVector = robotPosition.clone().sub(rotatedOffsetVector);
cameraState.set('mode', 'follow-direction')
if (($cameraState.mode = 'follow-facing')) {
$cameraControls.setLookAt( $cameraControls.setLookAt(
trailingVector.x, offsetPosition.x + 15 * Math.sin($mesh.rotation.y),
trailingVector.y, offsetPosition.y + 8,
trailingVector.z, offsetPosition.z + 15 * Math.cos($mesh.rotation.y),
$mesh.position.x, offsetPosition.x,
$mesh.position.y, offsetPosition.y,
$mesh.position.z, offsetPosition.z,
true true
); )
}; }
useTask((delta) => { if (($cameraState.mode = 'orbit')) {
// follow(delta) $cameraControls.moveTo(
// $cameraControls.moveTo( offsetPosition.x,
// $mesh.position.x, offsetPosition.y,
// $mesh.position.y, offsetPosition.z
// $mesh.position.z,
// true // true
// ) )
}); }
}
onMount(() => { useTask(delta => {
setTimeout(() => { $mesh.position.x +=
// $cameraControls.setLookAt( $telemetryReadonlyStore['chassis-y-speed'] * delta * SPEED_MULTIPLIER
// $mesh.position.x, $mesh.position.z +=
// $mesh.position.y, $telemetryReadonlyStore['chassis-x-speed'] * delta * SPEED_MULTIPLIER
// $mesh.position.z, $mesh.rotation.y = $telemetryReadonlyStore.orientation * DEG2RAD
// $mesh.position.x, follow(delta)
// $mesh.position.y, })
// $mesh.position.z,
// true onMount(() => {})
// )
$cameraControls.setLookAt(
30,
20,
-40,
$mesh.position.x,
$mesh.position.y,
$mesh.position.z,
true
);
}, 8000);
});
</script> </script>
<T.PerspectiveCamera <T.PerspectiveCamera
makeDefault makeDefault
position={[10, 10, 10]} position={[12, 10, 12]}
on:create={({ ref }) => { on:create={({ ref }) => {
ref.lookAt(0, 1, 0); ref.lookAt(0, 1, 0)
}} }}
> >
<CameraControls <CameraControls
on:create={({ ref }) => { on:create={({ ref }) => {
$cameraControls = ref; $cameraControls = ref
}} }}
autoRotate={$cameraState.mode === 'orbit'}
autoRotateSpeed={3}
/> />
</T.PerspectiveCamera> </T.PerspectiveCamera>
<T.DirectionalLight position={[3, 10, 7]} /> <T.DirectionalLight position={[3, 10, 7]} />
<T.AmbientLight color={'#f0f0f0'} intensity={0.1} />
<RobotDecimated <RobotDecimated
scale={[10, 10, 10]} scale={[10, 10, 10]}
position.y={0} position.y={0}
on:create={({ ref }) => { on:create={({ ref }) => {
// @ts-expect-error // @ts-expect-error
$mesh = ref; $mesh = ref
}} }}
/> />
<Grid <Grid
sectionColor={"#ff3e00"} sectionColor={'#ff3e00'}
sectionThickness={1} sectionThickness={1}
fadeDistance={100} fadeDistance={100}
cellSize={6} cellSize={6}
sectionSize={24} sectionSize={24}
cellColor={"#cccccc"} cellColor={'#cccccc'}
infiniteGrid infiniteGrid
/> />

View file

@ -1,7 +1,35 @@
import type CameraControls from "camera-controls"; import type CameraControls from 'camera-controls'
import { writable } from "svelte/store"; import { writable } from 'svelte/store'
import type { Mesh, Object3DEventMap } from "three"; import type { Mesh } from 'three'
import type { Group } from "three/examples/jsm/libs/tween.module.js";
export const cameraControls = writable<CameraControls>(); export const cameraControls = writable<CameraControls>()
export const mesh = writable<Mesh>(); export const mesh = writable<Mesh>()
type CameraMode =
| 'orbit'
| 'follow-facing'
| 'follow-direction'
| 'follow-position'
| 'showcase'
interface CameraState {
mode: CameraMode
userControlled: boolean
}
const { set, update, subscribe } = writable<CameraState>({
mode: 'orbit',
userControlled: false,
})
const createCameraState = () => {
return {
update,
subscribe,
set: (prop: keyof CameraState, val: any) =>
update(state => ({ ...state, [prop]: val })),
reset: () => set({ mode: 'orbit', userControlled: false }),
}
}
export const cameraState = createCameraState()

View file

@ -1,4 +1,4 @@
import { writable, readonly } from 'svelte/store' import { writable, readonly, get } from 'svelte/store'
let defaults: TelemetryData = { let defaults: TelemetryData = {
'orientation': 0, 'orientation': 0,
@ -28,6 +28,16 @@ const createTelemetryStore = () => {
return store return store
}) })
}, },
set: (key: keyof TelemetryData, value: any) => {
let newObj = {
...get(telemetryStore),
}
newObj = {
...newObj,
[key]: value,
}
set(newObj)
},
reset: () => set(defaults), reset: () => set(defaults),
} }
} }

View file

@ -34,7 +34,11 @@ export const initializeTelemetry = async (
}) })
const unlistenTelemetry = await listen('telemetry_data', event => { const unlistenTelemetry = await listen('telemetry_data', event => {
console.log(event.payload) const data = JSON.parse(event.payload as string)
// console.log(JSON.parse)
telemetryStore.set('connected', true)
telemetryStore.set(data['topic_name'], data['data'])
}) })
const unlistenAll = () => { const unlistenAll = () => {