@viamrobotics/motion-tools 1.34.4 → 1.34.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/App.svelte +14 -2
- package/dist/components/App.svelte.d.ts +6 -2
- package/dist/components/AxesHelper.svelte +3 -1
- package/dist/components/AxesHelper.svelte.d.ts +1 -1
- package/dist/components/Entities/Arrows/Arrows.svelte +0 -9
- package/dist/components/Entities/AxesHelper.svelte +38 -0
- package/dist/components/Entities/AxesHelper.svelte.d.ts +8 -0
- package/dist/components/Entities/AxesHelpers.svelte +13 -0
- package/dist/{plugins/LLMSceneBuilder/AISettings.svelte.d.ts → components/Entities/AxesHelpers.svelte.d.ts} +6 -14
- package/dist/components/Entities/Boxes.svelte +290 -0
- package/dist/components/Entities/Boxes.svelte.d.ts +14 -0
- package/dist/components/Entities/Entities.svelte +10 -5
- package/dist/components/Entities/GLTF.svelte +0 -9
- package/dist/components/Entities/Line.svelte +0 -9
- package/dist/components/Entities/Mesh.svelte +5 -23
- package/dist/components/Entities/Points.svelte +1 -9
- package/dist/components/Entities/composeBoxMatrix.d.ts +12 -0
- package/dist/components/Entities/composeBoxMatrix.js +29 -0
- package/dist/components/Entities/hooks/useEntityEvents.svelte.d.ts +27 -0
- package/dist/components/Entities/hooks/useEntityEvents.svelte.js +87 -39
- package/dist/components/Scene.svelte +0 -1
- package/dist/components/Selected.svelte +14 -3
- package/dist/components/SelectedTransformControls.svelte +3 -5
- package/dist/components/overlay/Details.svelte +9 -4
- package/dist/hooks/plugins/bvh.svelte.js +9 -0
- package/dist/hooks/useConfigFrames.svelte.js +5 -3
- package/dist/hooks/useFragmentInfo.svelte.d.ts +24 -0
- package/dist/hooks/useFragmentInfo.svelte.js +86 -0
- package/dist/hooks/useFramelessComponents.svelte.js +3 -1
- package/dist/hooks/usePartConfig.svelte.d.ts +0 -6
- package/dist/hooks/usePartConfig.svelte.js +5 -60
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plugins/Focus/FocusBox.svelte +12 -1
- package/dist/plugins/LLMSceneBuilder/frameDeltaAdapter.d.ts +9 -2
- package/dist/plugins/LLMSceneBuilder/frameDeltaAdapter.js +65 -10
- package/dist/plugins/LLMSceneBuilder/useSceneBuilder.svelte.js +29 -5
- package/dist/plugins/Selection/Ellipse.svelte +9 -26
- package/dist/plugins/Selection/Ellipse.svelte.d.ts +2 -1
- package/dist/plugins/Selection/Lasso.svelte +9 -26
- package/dist/plugins/Selection/Lasso.svelte.d.ts +2 -1
- package/dist/plugins/Selection/SelectionTool.svelte +18 -3
- package/dist/plugins/Selection/SelectionTool.svelte.d.ts +2 -0
- package/dist/plugins/TopDownLock/TopDownLock.svelte +25 -0
- package/dist/plugins/TopDownLock/TopDownLock.svelte.d.ts +3 -0
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/index.js +1 -0
- package/dist/three/OBBHelper.d.ts +8 -1
- package/dist/three/OBBHelper.js +11 -1
- package/package.json +3 -2
- package/dist/plugins/LLMSceneBuilder/AISettings.svelte +0 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { type Component, onMount, type Snippet } from 'svelte'
|
|
11
11
|
import { ThemeUtils } from 'svelte-tweakpane-ui'
|
|
12
12
|
|
|
13
|
-
import type { FragmentInfo } from '../hooks/
|
|
13
|
+
import type { FragmentInfo } from '../hooks/useFragmentInfo.svelte'
|
|
14
14
|
|
|
15
15
|
import Controls from './overlay/controls/Controls.svelte'
|
|
16
16
|
import Dashboard from './overlay/dashboard/Dashboard.svelte'
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import { provideWorld, traits, useQuery } from '../ecs'
|
|
21
21
|
import { type CameraPose, provideCameraControls } from '../hooks/useControls.svelte'
|
|
22
22
|
import { provideEnvironment } from '../hooks/useEnvironment.svelte'
|
|
23
|
+
import { provideFragmentInfo } from '../hooks/useFragmentInfo.svelte'
|
|
23
24
|
import { providePartConfig } from '../hooks/usePartConfig.svelte'
|
|
24
25
|
import { createPartIDContext } from '../hooks/usePartID.svelte'
|
|
25
26
|
import { provideSettings } from '../hooks/useSettings.svelte'
|
|
@@ -39,7 +40,6 @@
|
|
|
39
40
|
interface LocalConfigProps {
|
|
40
41
|
current: Struct
|
|
41
42
|
isDirty: boolean
|
|
42
|
-
componentNameToFragmentInfo: Record<string, FragmentInfo>
|
|
43
43
|
setLocalPartConfig: (config: Struct) => void
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -48,6 +48,12 @@
|
|
|
48
48
|
inputBindingsEnabled?: boolean
|
|
49
49
|
localConfigProps?: LocalConfigProps
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Maps a component name to the fragment that defines it. Embedded hosts
|
|
53
|
+
* supply this; in standalone it is computed from fragment queries (omit).
|
|
54
|
+
*/
|
|
55
|
+
componentNameToFragmentInfo?: Record<string, FragmentInfo>
|
|
56
|
+
|
|
51
57
|
/**
|
|
52
58
|
* Allows adding additional tabs to the settings panel
|
|
53
59
|
*/
|
|
@@ -81,6 +87,7 @@
|
|
|
81
87
|
partID = '',
|
|
82
88
|
inputBindingsEnabled = true,
|
|
83
89
|
localConfigProps,
|
|
90
|
+
componentNameToFragmentInfo,
|
|
84
91
|
cameraPose,
|
|
85
92
|
settingsTabs,
|
|
86
93
|
children: appChildren,
|
|
@@ -104,6 +111,11 @@
|
|
|
104
111
|
|
|
105
112
|
let root = $state.raw<HTMLElement>()
|
|
106
113
|
|
|
114
|
+
provideFragmentInfo(
|
|
115
|
+
() => partID,
|
|
116
|
+
() => componentNameToFragmentInfo
|
|
117
|
+
)
|
|
118
|
+
|
|
107
119
|
providePartConfig(
|
|
108
120
|
() => partID,
|
|
109
121
|
() => localConfigProps
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import type { Struct } from '@viamrobotics/sdk';
|
|
2
2
|
import type { Entity } from 'koota';
|
|
3
3
|
import { type Component, type Snippet } from 'svelte';
|
|
4
|
-
import type { FragmentInfo } from '../hooks/
|
|
4
|
+
import type { FragmentInfo } from '../hooks/useFragmentInfo.svelte';
|
|
5
5
|
import { type CameraPose } from '../hooks/useControls.svelte';
|
|
6
6
|
interface LocalConfigProps {
|
|
7
7
|
current: Struct;
|
|
8
8
|
isDirty: boolean;
|
|
9
|
-
componentNameToFragmentInfo: Record<string, FragmentInfo>;
|
|
10
9
|
setLocalPartConfig: (config: Struct) => void;
|
|
11
10
|
}
|
|
12
11
|
interface Props {
|
|
13
12
|
partID?: string;
|
|
14
13
|
inputBindingsEnabled?: boolean;
|
|
15
14
|
localConfigProps?: LocalConfigProps;
|
|
15
|
+
/**
|
|
16
|
+
* Maps a component name to the fragment that defines it. Embedded hosts
|
|
17
|
+
* supply this; in standalone it is computed from fragment queries (omit).
|
|
18
|
+
*/
|
|
19
|
+
componentNameToFragmentInfo?: Record<string, FragmentInfo>;
|
|
16
20
|
/**
|
|
17
21
|
* Allows adding additional tabs to the settings panel
|
|
18
22
|
*/
|
|
@@ -12,11 +12,12 @@
|
|
|
12
12
|
depthTest?: boolean
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
let {
|
|
16
16
|
length = 1,
|
|
17
17
|
width = 0.1,
|
|
18
18
|
axesColors = ['red', 'green', 'blue'],
|
|
19
19
|
depthTest = true,
|
|
20
|
+
ref = $bindable(),
|
|
20
21
|
...rest
|
|
21
22
|
}: Props = $props()
|
|
22
23
|
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
is={line}
|
|
67
68
|
{...rest}
|
|
68
69
|
bvh={{ enabled: false }}
|
|
70
|
+
bind:ref
|
|
69
71
|
>
|
|
70
72
|
<T is={geometry} />
|
|
71
73
|
<T
|
|
@@ -6,6 +6,6 @@ interface Props extends ThrelteProps<Line2> {
|
|
|
6
6
|
axesColors?: [x: string, y: string, z: string];
|
|
7
7
|
depthTest?: boolean;
|
|
8
8
|
}
|
|
9
|
-
declare const AxesHelper: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
declare const AxesHelper: import("svelte").Component<Props, {}, "ref">;
|
|
10
10
|
type AxesHelper = ReturnType<typeof AxesHelper>;
|
|
11
11
|
export default AxesHelper;
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
import type { InstancedArrows } from '../../../three/InstancedArrows/InstancedArrows'
|
|
7
7
|
|
|
8
|
-
import AxesHelper from '../../AxesHelper.svelte'
|
|
9
8
|
import { useEntityEvents } from '../hooks/useEntityEvents.svelte'
|
|
10
9
|
import { traits, useTag, useTrait } from '../../../ecs'
|
|
11
10
|
import { meshBoundsRaycast, raycast } from '../../../three/InstancedArrows/raycast'
|
|
@@ -21,7 +20,6 @@
|
|
|
21
20
|
const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
|
|
22
21
|
const selected = useTag(() => entity, traits.Selected)
|
|
23
22
|
const invisible = useTrait(() => entity, traits.InheritedInvisible)
|
|
24
|
-
const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
|
|
25
23
|
|
|
26
24
|
const events = useEntityEvents(() => entity)
|
|
27
25
|
|
|
@@ -59,11 +57,4 @@
|
|
|
59
57
|
bvh={{ enabled: false }}
|
|
60
58
|
raycast={() => null}
|
|
61
59
|
/>
|
|
62
|
-
{#if showAxesHelper.current}
|
|
63
|
-
<AxesHelper
|
|
64
|
-
name={entity}
|
|
65
|
-
width={3}
|
|
66
|
-
length={0.1}
|
|
67
|
-
/>
|
|
68
|
-
{/if}
|
|
69
60
|
</T>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Entity } from 'koota'
|
|
3
|
+
import type { Line2 } from 'three/addons'
|
|
4
|
+
|
|
5
|
+
import { useThrelte } from '@threlte/core'
|
|
6
|
+
|
|
7
|
+
import { traits, useTrait } from '../../ecs'
|
|
8
|
+
|
|
9
|
+
import AxesHelper from '../AxesHelper.svelte'
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
entity: Entity
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { entity }: Props = $props()
|
|
16
|
+
|
|
17
|
+
const { invalidate } = useThrelte()
|
|
18
|
+
|
|
19
|
+
let ref = $state.raw<Line2>()
|
|
20
|
+
|
|
21
|
+
const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
|
|
22
|
+
|
|
23
|
+
$effect(() => {
|
|
24
|
+
if (worldMatrix.current) {
|
|
25
|
+
ref?.matrix.copy(worldMatrix.current)
|
|
26
|
+
ref?.updateMatrixWorld()
|
|
27
|
+
invalidate()
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<AxesHelper
|
|
33
|
+
bind:ref
|
|
34
|
+
name={entity}
|
|
35
|
+
width={3}
|
|
36
|
+
length={0.1}
|
|
37
|
+
matrixAutoUpdate={false}
|
|
38
|
+
/>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Entity } from 'koota';
|
|
2
|
+
import AxesHelper from '../AxesHelper.svelte';
|
|
3
|
+
interface Props {
|
|
4
|
+
entity: Entity;
|
|
5
|
+
}
|
|
6
|
+
declare const AxesHelper: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type AxesHelper = ReturnType<typeof AxesHelper>;
|
|
8
|
+
export default AxesHelper;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Not } from 'koota'
|
|
3
|
+
|
|
4
|
+
import { traits, useQuery } from '../../ecs'
|
|
5
|
+
|
|
6
|
+
import AxesHelper from './AxesHelper.svelte'
|
|
7
|
+
|
|
8
|
+
const entities = useQuery(traits.ShowAxesHelper, Not(traits.Invisible))
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
{#each entities.current as entity (entity)}
|
|
12
|
+
<AxesHelper {entity} />
|
|
13
|
+
{/each}
|
|
@@ -1,18 +1,5 @@
|
|
|
1
|
-
export default AISettings;
|
|
2
|
-
type AISettings = SvelteComponent<{
|
|
3
|
-
[x: string]: never;
|
|
4
|
-
}, {
|
|
5
|
-
[evt: string]: CustomEvent<any>;
|
|
6
|
-
}, {}> & {
|
|
7
|
-
$$bindings?: string | undefined;
|
|
8
|
-
};
|
|
9
|
-
declare const AISettings: $$__sveltets_2_IsomorphicComponent<{
|
|
10
|
-
[x: string]: never;
|
|
11
|
-
}, {
|
|
12
|
-
[evt: string]: CustomEvent<any>;
|
|
13
|
-
}, {}, {}, string>;
|
|
14
1
|
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
15
|
-
new (options: import(
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
16
3
|
$$bindings?: Bindings;
|
|
17
4
|
} & Exports;
|
|
18
5
|
(internal: unknown, props: {
|
|
@@ -24,3 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
|
|
|
24
11
|
};
|
|
25
12
|
z_$$bindings?: Bindings;
|
|
26
13
|
}
|
|
14
|
+
declare const AxesHelpers: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type AxesHelpers = InstanceType<typeof AxesHelpers>;
|
|
18
|
+
export default AxesHelpers;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
|
|
4
|
+
Renders every entity with `Box` + `WorldMatrix` traits as one pair of
|
|
5
|
+
instanced draw calls (toon-shaded faces + edge lines) instead of a mesh
|
|
6
|
+
per box. Trait events are coalesced into a microtask flush, mirroring
|
|
7
|
+
the `WorldMatrix` system, so a burst of changes (one reconcile tick)
|
|
8
|
+
becomes a single batch of instance writes and one `invalidate()`.
|
|
9
|
+
|
|
10
|
+
The faces mesh is also the pointer-interaction surface: `InstancedMesh2`
|
|
11
|
+
raycasts per instance (skipping invisible ones) and stamps `instanceId`
|
|
12
|
+
on each hit, which `useInstancedEntityEvents` maps back to the entity.
|
|
13
|
+
-->
|
|
14
|
+
<script lang="ts">
|
|
15
|
+
import type { Entity } from 'koota'
|
|
16
|
+
|
|
17
|
+
import { createRadixSort, InstancedMesh2 } from '@three.ez/instanced-mesh'
|
|
18
|
+
import { T, useThrelte } from '@threlte/core'
|
|
19
|
+
import {
|
|
20
|
+
BoxGeometry,
|
|
21
|
+
Color,
|
|
22
|
+
EdgesGeometry,
|
|
23
|
+
LineBasicMaterial,
|
|
24
|
+
Matrix4,
|
|
25
|
+
MeshToonMaterial,
|
|
26
|
+
Sphere,
|
|
27
|
+
Vector3,
|
|
28
|
+
} from 'three'
|
|
29
|
+
|
|
30
|
+
import { asColor } from '../../buffer'
|
|
31
|
+
import { colors, darkenColor, subtypeToColor } from '../../color'
|
|
32
|
+
import { traits, useWorld } from '../../ecs'
|
|
33
|
+
import { useResourceByName } from '../../hooks/useResourceByName.svelte'
|
|
34
|
+
|
|
35
|
+
import { composeBoxMatrix } from './composeBoxMatrix'
|
|
36
|
+
import { useInstancedEntityEvents } from './hooks/useEntityEvents.svelte'
|
|
37
|
+
|
|
38
|
+
const { invalidate, renderer } = useThrelte()
|
|
39
|
+
const world = useWorld()
|
|
40
|
+
const resourceByName = useResourceByName()
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Shared unit geometries — every instance references these and sets its
|
|
44
|
+
* dimensions through the per-instance matrix scale, so resizing never
|
|
45
|
+
* rebuilds GPU buffers.
|
|
46
|
+
*/
|
|
47
|
+
const unitBox = new BoxGeometry(1, 1, 1)
|
|
48
|
+
const unitBoxEdges = new EdgesGeometry(unitBox, 0)
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Box meshes render transparent by default (`Opacity` trait absent → 0.7,
|
|
52
|
+
* depth write off — same as `Mesh.svelte`); per-instance alpha is written
|
|
53
|
+
* via `setOpacityAt`. The base color stays white so per-instance colors
|
|
54
|
+
* aren't tinted. Whole-object culling is disabled because the library
|
|
55
|
+
* culls per instance against a bounding sphere it derives from each
|
|
56
|
+
* instance matrix.
|
|
57
|
+
*/
|
|
58
|
+
const instancedBoxes = new InstancedMesh2(
|
|
59
|
+
unitBox,
|
|
60
|
+
new MeshToonMaterial({ transparent: true, depthWrite: false }),
|
|
61
|
+
{ renderer }
|
|
62
|
+
)
|
|
63
|
+
instancedBoxes.sortObjects = true
|
|
64
|
+
instancedBoxes.customSort = createRadixSort(instancedBoxes)
|
|
65
|
+
instancedBoxes.frustumCulled = false
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Keep raycasts on the library's linear (non-BVH) path, but neutralize
|
|
69
|
+
* its gate: the whole-object bounding sphere is computed once on the
|
|
70
|
+
* first raycast (usually before any boxes have streamed in) and never
|
|
71
|
+
* invalidated, leaving instances unhittable. Pin it open and let the
|
|
72
|
+
* per-instance early-outs do the pruning — for an always-animating
|
|
73
|
+
* scene this beats `computeBVH()`, which would re-insert every moving
|
|
74
|
+
* box into the tree on every kinematics tick.
|
|
75
|
+
*/
|
|
76
|
+
instancedBoxes.boundingSphere = new Sphere(new Vector3(), Infinity)
|
|
77
|
+
|
|
78
|
+
const instancedBoxEdges = new InstancedMesh2(unitBoxEdges, new LineBasicMaterial(), {
|
|
79
|
+
renderer,
|
|
80
|
+
})
|
|
81
|
+
instancedBoxEdges.frustumCulled = false
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* `InstancedMesh2` extends `Mesh`, so on its own it would draw the edge
|
|
85
|
+
* geometry as triangles. Re-tagging the object makes the renderer emit
|
|
86
|
+
* `gl.LINES`; the library's instancing shader patch still applies because
|
|
87
|
+
* `LineBasicMaterial` compiles from the same chunk-based `basic` program
|
|
88
|
+
* its patched shader chunks target.
|
|
89
|
+
*
|
|
90
|
+
* @three.ez/instanced-mesh ^0.3.15 — patches the 'basic' shader chunks shared
|
|
91
|
+
* by MeshBasicMaterial and LineBasicMaterial. Re-validate if upgrading the library.
|
|
92
|
+
*/
|
|
93
|
+
Object.assign(instancedBoxEdges, { isMesh: false, isLine: true, isLineSegments: true })
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Faces and edges are separate meshes with independent free lists, so each
|
|
97
|
+
* entity tracks its faces id and edges id separately. `entityByInstanceId`
|
|
98
|
+
* is keyed by faces id — only the faces mesh raycasts (edges set
|
|
99
|
+
* `raycast={() => null}`), so a hit's `instanceId` is always a faces id.
|
|
100
|
+
*/
|
|
101
|
+
type InstanceIds = { face: number; edge: number }
|
|
102
|
+
const instanceIdByEntity = new Map<Entity, InstanceIds>()
|
|
103
|
+
const entityByInstanceId = new Map<number, Entity>()
|
|
104
|
+
|
|
105
|
+
const events = useInstancedEntityEvents((event) =>
|
|
106
|
+
event.instanceId === undefined ? undefined : entityByInstanceId.get(event.instanceId)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const matrix = new Matrix4()
|
|
110
|
+
const colorUtil = new Color()
|
|
111
|
+
|
|
112
|
+
/** Same resolution order as `Frame.svelte`. */
|
|
113
|
+
const resolveColor = (entity: Entity): Color => {
|
|
114
|
+
const vertexColors = entity.get(traits.Colors)
|
|
115
|
+
if (vertexColors && vertexColors.length >= 3) {
|
|
116
|
+
return asColor(vertexColors, colorUtil)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const color = entity.get(traits.Color)
|
|
120
|
+
if (color) {
|
|
121
|
+
return colorUtil.setRGB(color.r, color.g, color.b)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const subtype = resourceByName.current[entity.get(traits.Name) ?? '']?.subtype
|
|
125
|
+
return subtypeToColor(subtype) ?? colorUtil.set(colors.default)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const writeAppearance = (entity: Entity, ids: InstanceIds) => {
|
|
129
|
+
const color = resolveColor(entity)
|
|
130
|
+
const visible = !entity.has(traits.InheritedInvisible)
|
|
131
|
+
|
|
132
|
+
instancedBoxes.setColorAt(ids.face, color)
|
|
133
|
+
instancedBoxes.setOpacityAt(ids.face, entity.get(traits.Opacity) ?? 0.7)
|
|
134
|
+
instancedBoxes.setVisibilityAt(ids.face, visible)
|
|
135
|
+
|
|
136
|
+
instancedBoxEdges.setColorAt(ids.edge, darkenColor(color, 10))
|
|
137
|
+
instancedBoxEdges.setVisibilityAt(ids.edge, visible)
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Mirrors `useEntityEvents`' invisibility watcher: an instance that
|
|
141
|
+
* vanishes under a motionless cursor gets no pointerleave until the
|
|
142
|
+
* pointer moves, so drop its hover state here.
|
|
143
|
+
*/
|
|
144
|
+
if (!visible && entity.has(traits.Hovered)) {
|
|
145
|
+
entity.remove(traits.Hovered)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Caller composes the instance transform into `matrix` first. */
|
|
150
|
+
const addInstance = (entity: Entity) => {
|
|
151
|
+
let face = -1
|
|
152
|
+
instancedBoxes.addInstances(1, (_obj, index) => {
|
|
153
|
+
face = index
|
|
154
|
+
})
|
|
155
|
+
instancedBoxes.setMatrixAt(face, matrix)
|
|
156
|
+
|
|
157
|
+
let edge = -1
|
|
158
|
+
instancedBoxEdges.addInstances(1, (_obj, index) => {
|
|
159
|
+
edge = index
|
|
160
|
+
})
|
|
161
|
+
instancedBoxEdges.setMatrixAt(edge, matrix)
|
|
162
|
+
|
|
163
|
+
const ids = { face, edge }
|
|
164
|
+
instanceIdByEntity.set(entity, ids)
|
|
165
|
+
entityByInstanceId.set(face, entity)
|
|
166
|
+
writeAppearance(entity, ids)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const removeInstance = (entity: Entity, ids: InstanceIds) => {
|
|
170
|
+
instanceIdByEntity.delete(entity)
|
|
171
|
+
entityByInstanceId.delete(ids.face)
|
|
172
|
+
instancedBoxes.removeInstances(ids.face)
|
|
173
|
+
instancedBoxEdges.removeInstances(ids.edge)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Transform work (matrix/dimension changes, adds, removes) is tracked
|
|
178
|
+
* separately from appearance work (color/opacity/visibility) so a robot
|
|
179
|
+
* in motion only rewrites matrices, not the color texture.
|
|
180
|
+
*/
|
|
181
|
+
const dirtyTransform = new Set<Entity>()
|
|
182
|
+
const dirtyAppearance = new Set<Entity>()
|
|
183
|
+
let scheduled = false
|
|
184
|
+
|
|
185
|
+
const flush = () => {
|
|
186
|
+
if (dirtyTransform.size === 0 && dirtyAppearance.size === 0) {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const entity of dirtyTransform) {
|
|
191
|
+
const ids = instanceIdByEntity.get(entity)
|
|
192
|
+
|
|
193
|
+
if (entity.isAlive() && composeBoxMatrix(entity, matrix)) {
|
|
194
|
+
if (ids === undefined) {
|
|
195
|
+
addInstance(entity)
|
|
196
|
+
} else {
|
|
197
|
+
instancedBoxes.setMatrixAt(ids.face, matrix)
|
|
198
|
+
instancedBoxEdges.setMatrixAt(ids.edge, matrix)
|
|
199
|
+
}
|
|
200
|
+
} else if (ids !== undefined) {
|
|
201
|
+
removeInstance(entity, ids)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const entity of dirtyAppearance) {
|
|
206
|
+
const ids = instanceIdByEntity.get(entity)
|
|
207
|
+
if (ids !== undefined && entity.isAlive()) {
|
|
208
|
+
writeAppearance(entity, ids)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
dirtyTransform.clear()
|
|
213
|
+
dirtyAppearance.clear()
|
|
214
|
+
invalidate()
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const schedule = () => {
|
|
218
|
+
if (scheduled) return
|
|
219
|
+
scheduled = true
|
|
220
|
+
queueMicrotask(() => {
|
|
221
|
+
scheduled = false
|
|
222
|
+
flush()
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* `WorldMatrix` changes fire for every entity on every kinematics tick —
|
|
228
|
+
* filter to box entities before touching the dirty sets. `instanceIdByEntity`
|
|
229
|
+
* catches entities whose `Box` trait was just removed.
|
|
230
|
+
*/
|
|
231
|
+
const enqueue = (dirty: Set<Entity>) => (entity: Entity) => {
|
|
232
|
+
if (!entity.has(traits.Box) && !instanceIdByEntity.has(entity)) return
|
|
233
|
+
dirty.add(entity)
|
|
234
|
+
schedule()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const enqueueTransform = enqueue(dirtyTransform)
|
|
238
|
+
const enqueueAppearance = enqueue(dirtyAppearance)
|
|
239
|
+
|
|
240
|
+
$effect(() => {
|
|
241
|
+
// Initial sync: existing boxes need both an instance allocated (transform)
|
|
242
|
+
// and appearance written once. At runtime the sets diverge — motion enqueues
|
|
243
|
+
// transform alone, so appearance buffers aren't rewritten per kinematics tick.
|
|
244
|
+
for (const entity of world.query(traits.Box)) {
|
|
245
|
+
dirtyTransform.add(entity)
|
|
246
|
+
dirtyAppearance.add(entity)
|
|
247
|
+
}
|
|
248
|
+
if (dirtyTransform.size > 0) schedule()
|
|
249
|
+
|
|
250
|
+
const unsubs = [
|
|
251
|
+
world.onAdd(traits.Box, enqueueTransform),
|
|
252
|
+
world.onChange(traits.Box, enqueueTransform),
|
|
253
|
+
world.onRemove(traits.Box, enqueueTransform),
|
|
254
|
+
world.onAdd(traits.WorldMatrix, enqueueTransform),
|
|
255
|
+
world.onChange(traits.WorldMatrix, enqueueTransform),
|
|
256
|
+
world.onRemove(traits.WorldMatrix, enqueueTransform),
|
|
257
|
+
world.onAdd(traits.Center, enqueueTransform),
|
|
258
|
+
world.onChange(traits.Center, enqueueTransform),
|
|
259
|
+
world.onRemove(traits.Center, enqueueTransform),
|
|
260
|
+
|
|
261
|
+
world.onAdd(traits.Color, enqueueAppearance),
|
|
262
|
+
world.onChange(traits.Color, enqueueAppearance),
|
|
263
|
+
world.onRemove(traits.Color, enqueueAppearance),
|
|
264
|
+
world.onAdd(traits.Colors, enqueueAppearance),
|
|
265
|
+
world.onChange(traits.Colors, enqueueAppearance),
|
|
266
|
+
world.onRemove(traits.Colors, enqueueAppearance),
|
|
267
|
+
world.onAdd(traits.Opacity, enqueueAppearance),
|
|
268
|
+
world.onChange(traits.Opacity, enqueueAppearance),
|
|
269
|
+
world.onRemove(traits.Opacity, enqueueAppearance),
|
|
270
|
+
world.onAdd(traits.InheritedInvisible, enqueueAppearance),
|
|
271
|
+
world.onRemove(traits.InheritedInvisible, enqueueAppearance),
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
return () => {
|
|
275
|
+
for (const unsub of unsubs) unsub()
|
|
276
|
+
dirtyTransform.clear()
|
|
277
|
+
dirtyAppearance.clear()
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
</script>
|
|
281
|
+
|
|
282
|
+
<T
|
|
283
|
+
is={instancedBoxes}
|
|
284
|
+
{...events}
|
|
285
|
+
/>
|
|
286
|
+
|
|
287
|
+
<T
|
|
288
|
+
is={instancedBoxEdges}
|
|
289
|
+
raycast={() => null}
|
|
290
|
+
/>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders every entity with `Box` + `WorldMatrix` traits as one pair of
|
|
3
|
+
* instanced draw calls (toon-shaded faces + edge lines) instead of a mesh
|
|
4
|
+
* per box. Trait events are coalesced into a microtask flush, mirroring
|
|
5
|
+
* the `WorldMatrix` system, so a burst of changes (one reconcile tick)
|
|
6
|
+
* becomes a single batch of instance writes and one `invalidate()`.
|
|
7
|
+
*
|
|
8
|
+
* The faces mesh is also the pointer-interaction surface: `InstancedMesh2`
|
|
9
|
+
* raycasts per instance (skipping invisible ones) and stamps `instanceId`
|
|
10
|
+
* on each hit, which `useInstancedEntityEvents` maps back to the entity.
|
|
11
|
+
*/
|
|
12
|
+
declare const Boxes: import("svelte").Component<Record<string, never>, {}, "">;
|
|
13
|
+
type Boxes = ReturnType<typeof Boxes>;
|
|
14
|
+
export default Boxes;
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { useSettings } from '../../hooks/useSettings.svelte'
|
|
6
6
|
|
|
7
7
|
import Arrows from './Arrows/ArrowGroups.svelte'
|
|
8
|
+
import AxesHelpers from './AxesHelpers.svelte'
|
|
9
|
+
import Boxes from './Boxes.svelte'
|
|
8
10
|
import Frame from './Frame.svelte'
|
|
9
11
|
import Geometry from './Geometry.svelte'
|
|
10
12
|
import GLTF from './GLTF.svelte'
|
|
@@ -32,8 +34,8 @@
|
|
|
32
34
|
*/
|
|
33
35
|
const worldStateEntities = useQuery(
|
|
34
36
|
traits.WorldStateStoreAPI,
|
|
35
|
-
Not(traits.Points, traits.LinePositions, traits.GLTF),
|
|
36
|
-
Or(traits.
|
|
37
|
+
Not(traits.Points, traits.Box, traits.LinePositions, traits.GLTF),
|
|
38
|
+
Or(traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
|
|
37
39
|
)
|
|
38
40
|
|
|
39
41
|
/**
|
|
@@ -42,8 +44,8 @@
|
|
|
42
44
|
*/
|
|
43
45
|
const drawServiceEntities = useQuery(
|
|
44
46
|
traits.DrawServiceAPI,
|
|
45
|
-
Not(traits.Points, traits.LinePositions, traits.GLTF),
|
|
46
|
-
Or(traits.
|
|
47
|
+
Not(traits.Points, traits.Box, traits.LinePositions, traits.GLTF),
|
|
48
|
+
Or(traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
|
|
47
49
|
)
|
|
48
50
|
|
|
49
51
|
/**
|
|
@@ -55,7 +57,8 @@
|
|
|
55
57
|
Not(traits.WorldStateStoreAPI),
|
|
56
58
|
Not(traits.DrawServiceAPI),
|
|
57
59
|
Not(traits.Points),
|
|
58
|
-
|
|
60
|
+
Not(traits.Box),
|
|
61
|
+
Or(traits.Capsule, traits.Sphere, traits.BufferGeometry, traits.ReferenceFrame)
|
|
59
62
|
)
|
|
60
63
|
|
|
61
64
|
const points = useQuery(traits.Points)
|
|
@@ -102,6 +105,8 @@
|
|
|
102
105
|
{/each}
|
|
103
106
|
|
|
104
107
|
<Arrows />
|
|
108
|
+
<AxesHelpers />
|
|
109
|
+
<Boxes />
|
|
105
110
|
|
|
106
111
|
{#if enableLabels}
|
|
107
112
|
<Labels />
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
|
|
22
22
|
import { traits, useTrait } from '../../ecs'
|
|
23
23
|
|
|
24
|
-
import AxesHelper from '../AxesHelper.svelte'
|
|
25
24
|
import { useEntityEvents } from './hooks/useEntityEvents.svelte'
|
|
26
25
|
|
|
27
26
|
interface Props extends ThrelteProps<Object3D> {
|
|
@@ -36,7 +35,6 @@
|
|
|
36
35
|
const worldMatrix = useTrait(() => entity, traits.WorldMatrix)
|
|
37
36
|
const gltfTrait = useTrait(() => entity, traits.GLTF)
|
|
38
37
|
const invisible = useTrait(() => entity, traits.InheritedInvisible)
|
|
39
|
-
const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
|
|
40
38
|
const events = useEntityEvents(() => entity)
|
|
41
39
|
|
|
42
40
|
const animationName = $derived(gltfTrait.current?.animationName)
|
|
@@ -83,13 +81,6 @@
|
|
|
83
81
|
</script>
|
|
84
82
|
|
|
85
83
|
<T is={group}>
|
|
86
|
-
{#if showAxesHelper.current}
|
|
87
|
-
<AxesHelper
|
|
88
|
-
name={entity}
|
|
89
|
-
width={3}
|
|
90
|
-
length={0.1}
|
|
91
|
-
/>
|
|
92
|
-
{/if}
|
|
93
84
|
{#if $gltf}
|
|
94
85
|
<T
|
|
95
86
|
is={$gltf.scene as Object3D}
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
import { isVertexColors, STRIDE } from '../../buffer'
|
|
10
10
|
import { traits, useTrait } from '../../ecs'
|
|
11
11
|
|
|
12
|
-
import AxesHelper from '../AxesHelper.svelte'
|
|
13
12
|
import { useEntityEvents } from './hooks/useEntityEvents.svelte'
|
|
14
13
|
import LineDots from './LineDots.svelte'
|
|
15
14
|
import LineGeometry from './LineGeometry.svelte'
|
|
@@ -35,7 +34,6 @@
|
|
|
35
34
|
const opacity = useTrait(() => entity, traits.Opacity)
|
|
36
35
|
const screenSpace = useTrait(() => entity, traits.ScreenSpace)
|
|
37
36
|
const invisible = useTrait(() => entity, traits.InheritedInvisible)
|
|
38
|
-
const showAxesHelper = useTrait(() => entity, traits.ShowAxesHelper)
|
|
39
37
|
|
|
40
38
|
const events = useEntityEvents(() => entity)
|
|
41
39
|
|
|
@@ -96,13 +94,6 @@
|
|
|
96
94
|
linewidth={(lineWidth.current ?? 5) * (screenSpace.current ? 1 : 0.001)}
|
|
97
95
|
depthTest={materialProps.current?.depthTest ?? true}
|
|
98
96
|
/>
|
|
99
|
-
{#if showAxesHelper.current}
|
|
100
|
-
<AxesHelper
|
|
101
|
-
name={entity}
|
|
102
|
-
width={3}
|
|
103
|
-
length={0.1}
|
|
104
|
-
/>
|
|
105
|
-
{/if}
|
|
106
97
|
|
|
107
98
|
{#if linePositions.current && dotSize.current}
|
|
108
99
|
<LineDots
|