@vulfram/engine 0.14.8-alpha → 0.19.2-alpha
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/README.md +106 -0
- package/package.json +60 -4
- package/src/core.ts +14 -0
- package/src/ecs.ts +1 -0
- package/src/engine/api.ts +222 -24
- package/src/engine/bridge/dispatch.ts +260 -40
- package/src/engine/bridge/guards.ts +4 -1
- package/src/engine/bridge/protocol.ts +69 -52
- package/src/engine/ecs/components.ts +340 -0
- package/src/engine/ecs/index.ts +3 -518
- package/src/engine/ecs/intents.ts +184 -0
- package/src/engine/ecs/systems.ts +26 -0
- package/src/engine/intents/store.ts +72 -0
- package/src/engine/state.ts +136 -5
- package/src/engine/systems/command-intent.ts +159 -14
- package/src/engine/systems/constraint-solve.ts +167 -0
- package/src/engine/systems/core-command-builder.ts +9 -268
- package/src/engine/systems/diagnostics.ts +20 -29
- package/src/engine/systems/index.ts +3 -1
- package/src/engine/systems/input-mirror.ts +257 -21
- package/src/engine/systems/resource-upload.ts +108 -58
- package/src/engine/systems/response-decode.ts +86 -15
- package/src/engine/systems/scene-sync.ts +305 -0
- package/src/engine/systems/ui-bridge.ts +381 -0
- package/src/engine/systems/utils.ts +86 -1
- package/src/engine/systems/world-lifecycle.ts +43 -114
- package/src/engine/window/manager.ts +168 -0
- package/src/engine/world/entities.ts +998 -91
- package/src/engine/world/mount.ts +195 -0
- package/src/engine/world/types.ts +71 -0
- package/src/engine/world/world-ui.ts +313 -0
- package/src/engine/world/world3d.ts +529 -0
- package/src/helpers/collision.ts +487 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/raycast.ts +442 -0
- package/src/index.ts +30 -1
- package/src/mount.ts +2 -0
- package/src/types/cmds/audio.ts +73 -48
- package/src/types/cmds/camera.ts +12 -8
- package/src/types/cmds/environment.ts +9 -3
- package/src/types/cmds/geometry.ts +15 -16
- package/src/types/cmds/index.ts +234 -162
- package/src/types/cmds/input.ts +39 -0
- package/src/types/cmds/light.ts +12 -11
- package/src/types/cmds/material.ts +19 -21
- package/src/types/cmds/model.ts +17 -15
- package/src/types/cmds/realm.ts +23 -0
- package/src/types/cmds/system.ts +29 -0
- package/src/types/cmds/target.ts +96 -0
- package/src/types/cmds/texture.ts +13 -3
- package/src/types/cmds/ui.ts +220 -0
- package/src/types/cmds/window.ts +41 -204
- package/src/types/events/index.ts +4 -1
- package/src/types/events/keyboard.ts +2 -2
- package/src/types/events/pointer.ts +85 -13
- package/src/types/events/system.ts +188 -30
- package/src/types/events/ui.ts +21 -0
- package/src/types/index.ts +1 -0
- package/src/types/json.ts +15 -0
- package/src/window.ts +8 -0
- package/src/world-ui.ts +2 -0
- package/src/world3d.ts +10 -0
- package/tsconfig.json +0 -29
|
@@ -1,28 +1,99 @@
|
|
|
1
1
|
import type { System } from '../ecs';
|
|
2
|
+
import { enqueueCommand, markRoutingIndexDirty } from '../bridge/dispatch';
|
|
2
3
|
import { engineState } from '../state';
|
|
3
4
|
|
|
5
|
+
const MAX_REALM_CREATE_RETRIES = 8;
|
|
6
|
+
const BASE_REALM_RETRY_DELAY_MS = 32;
|
|
7
|
+
|
|
8
|
+
function flushPendingTargetLayerBinds(world: Parameters<System>[0], worldId: number): void {
|
|
9
|
+
const realmId = world.coreRealmId;
|
|
10
|
+
if (realmId === undefined) return;
|
|
11
|
+
|
|
12
|
+
for (const binding of world.targetLayerBindings.values()) {
|
|
13
|
+
enqueueCommand(worldId, 'cmd-target-layer-upsert', {
|
|
14
|
+
realmId,
|
|
15
|
+
targetId: binding.targetId,
|
|
16
|
+
layout: binding.layout,
|
|
17
|
+
cameraId: binding.cameraId,
|
|
18
|
+
environmentId: binding.environmentId,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Decodes and applies command responses routed to a world.
|
|
25
|
+
*
|
|
26
|
+
* Responsibilities:
|
|
27
|
+
* - track realm/window ids returned by core
|
|
28
|
+
* - retry realm creation on transient host-window races
|
|
29
|
+
* - complete deferred target binds after target-upsert acknowledgements
|
|
30
|
+
*/
|
|
4
31
|
export const ResponseDecodeSystem: System = (world, context) => {
|
|
5
|
-
|
|
6
|
-
const res = world.inboundResponses
|
|
32
|
+
for (let i = 0; i < world.inboundResponses.length; i++) {
|
|
33
|
+
const res = world.inboundResponses[i]!;
|
|
7
34
|
const content = res.content as { success?: boolean; message?: string };
|
|
8
35
|
|
|
9
|
-
if (engineState.flags.debugEnabled) {
|
|
10
|
-
const debugKey = '__vulframDebugRespAllCount';
|
|
11
|
-
const count =
|
|
12
|
-
(globalThis as unknown as Record<string, number>)[debugKey] || 0;
|
|
13
|
-
if (count < 10) {
|
|
14
|
-
console.debug('[Debug] Response', res.type, JSON.stringify(content));
|
|
15
|
-
(globalThis as unknown as Record<string, number>)[debugKey] = count + 1;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
36
|
if (content && typeof content.success === 'boolean' && !content.success) {
|
|
37
|
+
if (
|
|
38
|
+
res.type === 'realm-create' &&
|
|
39
|
+
world.coreRealmId === undefined &&
|
|
40
|
+
typeof content.message === 'string' &&
|
|
41
|
+
content.message.includes('Host window') &&
|
|
42
|
+
content.message.includes('not found')
|
|
43
|
+
) {
|
|
44
|
+
if (world.realmCreateRetryCount < MAX_REALM_CREATE_RETRIES) {
|
|
45
|
+
const nowMs = engineState.clock.lastTime;
|
|
46
|
+
if (nowMs >= world.nextRealmCreateRetryAtMs) {
|
|
47
|
+
const retryDelay =
|
|
48
|
+
BASE_REALM_RETRY_DELAY_MS * (1 << world.realmCreateRetryCount);
|
|
49
|
+
world.realmCreateRetryCount += 1;
|
|
50
|
+
world.nextRealmCreateRetryAtMs = nowMs + retryDelay;
|
|
51
|
+
enqueueCommand(
|
|
52
|
+
context.worldId,
|
|
53
|
+
'cmd-realm-create',
|
|
54
|
+
world.realmCreateArgs,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
} else if (world.realmCreateRetryCount === MAX_REALM_CREATE_RETRIES) {
|
|
58
|
+
console.error(
|
|
59
|
+
`[World ${context.worldId}] realm-create retries exhausted (${MAX_REALM_CREATE_RETRIES}). Last error: ${content.message}`,
|
|
60
|
+
);
|
|
61
|
+
world.realmCreateRetryCount += 1;
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
20
65
|
console.error(
|
|
21
66
|
`[World ${context.worldId}] Command ${res.type} (ID: ${res.id}) failed: ${content.message}`,
|
|
22
67
|
);
|
|
23
|
-
} else {
|
|
24
|
-
|
|
25
|
-
|
|
68
|
+
} else if (res.type === 'realm-create') {
|
|
69
|
+
const created = res.content as { realmId?: number };
|
|
70
|
+
if (typeof created.realmId === 'number') {
|
|
71
|
+
world.coreRealmId = created.realmId;
|
|
72
|
+
world.realmCreateRetryCount = 0;
|
|
73
|
+
world.nextRealmCreateRetryAtMs = 0;
|
|
74
|
+
markRoutingIndexDirty();
|
|
75
|
+
flushPendingTargetLayerBinds(world, context.worldId);
|
|
76
|
+
}
|
|
77
|
+
} else if (res.type === 'window-create') {
|
|
78
|
+
const created = res.content as {
|
|
79
|
+
realmId?: number;
|
|
80
|
+
surfaceId?: number;
|
|
81
|
+
presentId?: number;
|
|
82
|
+
};
|
|
83
|
+
if (typeof created.realmId === 'number') {
|
|
84
|
+
world.coreRealmId = created.realmId;
|
|
85
|
+
world.realmCreateRetryCount = 0;
|
|
86
|
+
world.nextRealmCreateRetryAtMs = 0;
|
|
87
|
+
markRoutingIndexDirty();
|
|
88
|
+
flushPendingTargetLayerBinds(world, context.worldId);
|
|
89
|
+
}
|
|
90
|
+
if (typeof created.surfaceId === 'number') {
|
|
91
|
+
world.coreSurfaceId = created.surfaceId;
|
|
92
|
+
}
|
|
93
|
+
if (typeof created.presentId === 'number') {
|
|
94
|
+
world.corePresentId = created.presentId;
|
|
95
|
+
}
|
|
26
96
|
}
|
|
27
97
|
}
|
|
98
|
+
world.inboundResponses.length = 0;
|
|
28
99
|
};
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { mat4, vec3 } from 'gl-matrix';
|
|
2
|
+
import type { CameraKind, LightKind } from '../../types/kinds';
|
|
3
|
+
import { enqueueCommand } from '../bridge/dispatch';
|
|
4
|
+
import type {
|
|
5
|
+
CameraComponent,
|
|
6
|
+
Component,
|
|
7
|
+
LightComponent,
|
|
8
|
+
ModelComponent,
|
|
9
|
+
System,
|
|
10
|
+
} from '../ecs';
|
|
11
|
+
import {
|
|
12
|
+
getResolvedEntityTransformMatrix,
|
|
13
|
+
toVec2,
|
|
14
|
+
toVec3,
|
|
15
|
+
toVec4,
|
|
16
|
+
} from './utils';
|
|
17
|
+
|
|
18
|
+
const SCENE_SYNC_INTENT_TYPES = [
|
|
19
|
+
'attach-model',
|
|
20
|
+
'attach-camera',
|
|
21
|
+
'attach-light',
|
|
22
|
+
'detach-component',
|
|
23
|
+
'gizmo-draw-line',
|
|
24
|
+
'gizmo-draw-aabb',
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
function copyMatrixToScratch(
|
|
28
|
+
world: Parameters<System>[0],
|
|
29
|
+
entityId: number,
|
|
30
|
+
matrix: ArrayLike<number>,
|
|
31
|
+
): number[] {
|
|
32
|
+
let scratch = world.sceneSyncMatrixScratch.get(entityId);
|
|
33
|
+
if (!scratch) {
|
|
34
|
+
scratch = new Array<number>(16);
|
|
35
|
+
world.sceneSyncMatrixScratch.set(entityId, scratch);
|
|
36
|
+
}
|
|
37
|
+
for (let i = 0; i < 16; i++) {
|
|
38
|
+
scratch[i] = matrix[i] ?? 0;
|
|
39
|
+
}
|
|
40
|
+
return scratch;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Synchronizes ECS scene state with core scene objects.
|
|
45
|
+
*
|
|
46
|
+
* This system consumes attach/detach/gizmo intents and emits upsert/dispose
|
|
47
|
+
* commands. It also pushes transform updates for entities whose resolved
|
|
48
|
+
* constraint matrix changed in the current tick.
|
|
49
|
+
*/
|
|
50
|
+
export const SceneSyncSystem: System = (world, context) => {
|
|
51
|
+
const realmId = world.coreRealmId;
|
|
52
|
+
if (realmId === undefined) return;
|
|
53
|
+
const intents = world.intentStore.takeMany(SCENE_SYNC_INTENT_TYPES);
|
|
54
|
+
|
|
55
|
+
for (let i = 0; i < intents.length; i++) {
|
|
56
|
+
const intent = intents[i];
|
|
57
|
+
if (!intent) continue;
|
|
58
|
+
|
|
59
|
+
if (intent.type === 'attach-model') {
|
|
60
|
+
const modelId = world.nextCoreId++;
|
|
61
|
+
const transform = getResolvedEntityTransformMatrix(world, intent.entityId);
|
|
62
|
+
const castShadow = intent.props.castShadow ?? true;
|
|
63
|
+
const receiveShadow = intent.props.receiveShadow ?? true;
|
|
64
|
+
const castOutline = intent.props.castOutline ?? false;
|
|
65
|
+
const outlineColor = intent.props.outlineColor ?? ([0, 0, 0, 0] as const);
|
|
66
|
+
|
|
67
|
+
enqueueCommand(context.worldId, 'cmd-model-upsert', {
|
|
68
|
+
realmId,
|
|
69
|
+
modelId,
|
|
70
|
+
geometryId: intent.props.geometryId,
|
|
71
|
+
materialId: intent.props.materialId,
|
|
72
|
+
transform: copyMatrixToScratch(world, intent.entityId, transform),
|
|
73
|
+
layerMask: 0x7fffffff,
|
|
74
|
+
castShadow,
|
|
75
|
+
receiveShadow,
|
|
76
|
+
castOutline,
|
|
77
|
+
outlineColor: [...outlineColor] as [number, number, number, number],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
let store = world.components.get(intent.entityId);
|
|
81
|
+
if (!store) {
|
|
82
|
+
store = new Map();
|
|
83
|
+
world.components.set(intent.entityId, store);
|
|
84
|
+
}
|
|
85
|
+
store.set('Model', {
|
|
86
|
+
type: 'Model',
|
|
87
|
+
id: modelId,
|
|
88
|
+
geometryId: intent.props.geometryId,
|
|
89
|
+
materialId: intent.props.materialId,
|
|
90
|
+
castShadow: intent.props.castShadow ?? true,
|
|
91
|
+
receiveShadow: intent.props.receiveShadow ?? true,
|
|
92
|
+
castOutline,
|
|
93
|
+
outlineColor: [...outlineColor] as [number, number, number, number],
|
|
94
|
+
skipUpdate: true,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
} else if (intent.type === 'attach-camera') {
|
|
98
|
+
const cameraId = world.nextCoreId++;
|
|
99
|
+
const transform = getResolvedEntityTransformMatrix(world, intent.entityId);
|
|
100
|
+
|
|
101
|
+
enqueueCommand(context.worldId, 'cmd-camera-upsert', {
|
|
102
|
+
realmId,
|
|
103
|
+
cameraId,
|
|
104
|
+
label: `Cam ${cameraId}`,
|
|
105
|
+
kind: intent.props.kind ?? ('perspective' as CameraKind),
|
|
106
|
+
flags: 0,
|
|
107
|
+
nearFar: [intent.props.near ?? 0.1, intent.props.far ?? 1000],
|
|
108
|
+
order: intent.props.order ?? 0,
|
|
109
|
+
transform: copyMatrixToScratch(world, intent.entityId, transform),
|
|
110
|
+
layerMask: 0x7fffffff,
|
|
111
|
+
orthoScale: 1.0,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
let store = world.components.get(intent.entityId);
|
|
115
|
+
if (!store) {
|
|
116
|
+
store = new Map();
|
|
117
|
+
world.components.set(intent.entityId, store);
|
|
118
|
+
}
|
|
119
|
+
store.set('Camera', {
|
|
120
|
+
type: 'Camera',
|
|
121
|
+
id: cameraId,
|
|
122
|
+
kind: intent.props.kind ?? ('perspective' as CameraKind),
|
|
123
|
+
near: intent.props.near ?? 0.1,
|
|
124
|
+
far: intent.props.far ?? 1000,
|
|
125
|
+
order: intent.props.order ?? 0,
|
|
126
|
+
orthoScale: 1.0,
|
|
127
|
+
skipUpdate: true,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Auto-attach the first available camera to realm target layers that were bound
|
|
131
|
+
// before a camera existed (common when presenting world before scene setup).
|
|
132
|
+
for (const binding of world.targetLayerBindings.values()) {
|
|
133
|
+
if (binding.cameraId !== undefined) continue;
|
|
134
|
+
enqueueCommand(context.worldId, 'cmd-target-layer-upsert', {
|
|
135
|
+
realmId,
|
|
136
|
+
targetId: binding.targetId,
|
|
137
|
+
layout: binding.layout,
|
|
138
|
+
cameraId,
|
|
139
|
+
environmentId: binding.environmentId,
|
|
140
|
+
});
|
|
141
|
+
binding.cameraId = cameraId;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
} else if (intent.type === 'attach-light') {
|
|
145
|
+
const lightId = world.nextCoreId++;
|
|
146
|
+
const transform = getResolvedEntityTransformMatrix(world, intent.entityId);
|
|
147
|
+
|
|
148
|
+
const pos = vec3.create();
|
|
149
|
+
mat4.getTranslation(pos, transform);
|
|
150
|
+
const direction = intent.props.direction
|
|
151
|
+
? toVec3(intent.props.direction)
|
|
152
|
+
: ([0, 0, -1] as [number, number, number]);
|
|
153
|
+
const color = intent.props.color
|
|
154
|
+
? toVec3(intent.props.color)
|
|
155
|
+
: ([1, 1, 1] as [number, number, number]);
|
|
156
|
+
|
|
157
|
+
const lightCmd: {
|
|
158
|
+
realmId: number;
|
|
159
|
+
lightId: number;
|
|
160
|
+
kind: LightKind;
|
|
161
|
+
color: [number, number, number, number];
|
|
162
|
+
intensity: number;
|
|
163
|
+
range: number;
|
|
164
|
+
castShadow: boolean;
|
|
165
|
+
position: [number, number, number, number];
|
|
166
|
+
layerMask: number;
|
|
167
|
+
direction?: [number, number, number, number];
|
|
168
|
+
spotInnerOuter?: [number, number];
|
|
169
|
+
} = {
|
|
170
|
+
realmId,
|
|
171
|
+
lightId,
|
|
172
|
+
kind: intent.props.kind ?? ('directional' as LightKind),
|
|
173
|
+
color: [...color, 1] as [number, number, number, number],
|
|
174
|
+
intensity: intent.props.intensity ?? 1.0,
|
|
175
|
+
range: intent.props.range ?? 10.0,
|
|
176
|
+
castShadow: intent.props.castShadow ?? true,
|
|
177
|
+
position: [pos[0], pos[1], pos[2], 1],
|
|
178
|
+
layerMask: 0x7fffffff,
|
|
179
|
+
};
|
|
180
|
+
if (direction) {
|
|
181
|
+
const [dirX, dirY, dirZ] = direction;
|
|
182
|
+
lightCmd.direction = [dirX, dirY, dirZ, 0];
|
|
183
|
+
}
|
|
184
|
+
if (intent.props.spotInnerOuter) {
|
|
185
|
+
lightCmd.spotInnerOuter = toVec2(intent.props.spotInnerOuter);
|
|
186
|
+
}
|
|
187
|
+
enqueueCommand(context.worldId, 'cmd-light-upsert', lightCmd);
|
|
188
|
+
|
|
189
|
+
let store = world.components.get(intent.entityId);
|
|
190
|
+
if (!store) {
|
|
191
|
+
store = new Map();
|
|
192
|
+
world.components.set(intent.entityId, store);
|
|
193
|
+
}
|
|
194
|
+
store.set('Light', {
|
|
195
|
+
type: 'Light',
|
|
196
|
+
id: lightId,
|
|
197
|
+
kind: intent.props.kind ?? ('directional' as LightKind),
|
|
198
|
+
color,
|
|
199
|
+
intensity: intent.props.intensity ?? 1.0,
|
|
200
|
+
range: intent.props.range ?? 10.0,
|
|
201
|
+
castShadow: intent.props.castShadow ?? true,
|
|
202
|
+
direction,
|
|
203
|
+
spotInnerOuter: intent.props.spotInnerOuter
|
|
204
|
+
? toVec2(intent.props.spotInnerOuter)
|
|
205
|
+
: [0.2, 0.6],
|
|
206
|
+
skipUpdate: true,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
} else if (intent.type === 'detach-component') {
|
|
210
|
+
const store = world.components.get(intent.entityId);
|
|
211
|
+
if (store) {
|
|
212
|
+
const comp = store.get(intent.componentType) as Component | undefined;
|
|
213
|
+
if (comp && 'id' in comp) {
|
|
214
|
+
if (intent.componentType === 'Model') {
|
|
215
|
+
const modelComp = comp as ModelComponent;
|
|
216
|
+
enqueueCommand(context.worldId, 'cmd-model-dispose', {
|
|
217
|
+
realmId,
|
|
218
|
+
modelId: modelComp.id,
|
|
219
|
+
});
|
|
220
|
+
} else if (intent.componentType === 'Camera') {
|
|
221
|
+
const cameraComp = comp as CameraComponent;
|
|
222
|
+
enqueueCommand(context.worldId, 'cmd-camera-dispose', {
|
|
223
|
+
realmId,
|
|
224
|
+
cameraId: cameraComp.id,
|
|
225
|
+
});
|
|
226
|
+
} else if (intent.componentType === 'Light') {
|
|
227
|
+
const lightComp = comp as LightComponent;
|
|
228
|
+
enqueueCommand(context.worldId, 'cmd-light-dispose', {
|
|
229
|
+
realmId,
|
|
230
|
+
lightId: lightComp.id,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
store.delete(intent.componentType);
|
|
235
|
+
}
|
|
236
|
+
} else if (intent.type === 'gizmo-draw-line') {
|
|
237
|
+
enqueueCommand(context.worldId, 'cmd-gizmo-draw-line', {
|
|
238
|
+
start: toVec3(intent.start),
|
|
239
|
+
end: toVec3(intent.end),
|
|
240
|
+
color: toVec4(intent.color),
|
|
241
|
+
});
|
|
242
|
+
} else if (intent.type === 'gizmo-draw-aabb') {
|
|
243
|
+
enqueueCommand(context.worldId, 'cmd-gizmo-draw-aabb', {
|
|
244
|
+
min: toVec3(intent.min),
|
|
245
|
+
max: toVec3(intent.max),
|
|
246
|
+
color: toVec4(intent.color),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for (const entityId of world.constraintChangedEntities) {
|
|
252
|
+
const store = world.components.get(entityId);
|
|
253
|
+
if (!store) continue;
|
|
254
|
+
|
|
255
|
+
const matrix = getResolvedEntityTransformMatrix(world, entityId);
|
|
256
|
+
let matrixArray: number[] | undefined;
|
|
257
|
+
|
|
258
|
+
const model = store.get('Model') as ModelComponent | undefined;
|
|
259
|
+
if (model) {
|
|
260
|
+
if (model.skipUpdate) {
|
|
261
|
+
model.skipUpdate = false;
|
|
262
|
+
} else {
|
|
263
|
+
matrixArray = matrixArray ?? copyMatrixToScratch(world, entityId, matrix);
|
|
264
|
+
enqueueCommand(context.worldId, 'cmd-model-upsert', {
|
|
265
|
+
realmId,
|
|
266
|
+
modelId: model.id,
|
|
267
|
+
transform: matrixArray,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const camera = store.get('Camera') as CameraComponent | undefined;
|
|
273
|
+
if (camera) {
|
|
274
|
+
if (camera.skipUpdate) {
|
|
275
|
+
camera.skipUpdate = false;
|
|
276
|
+
} else {
|
|
277
|
+
matrixArray = matrixArray ?? copyMatrixToScratch(world, entityId, matrix);
|
|
278
|
+
enqueueCommand(context.worldId, 'cmd-camera-upsert', {
|
|
279
|
+
realmId,
|
|
280
|
+
cameraId: camera.id,
|
|
281
|
+
transform: matrixArray,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const light = store.get('Light') as LightComponent | undefined;
|
|
287
|
+
if (light) {
|
|
288
|
+
if (light.skipUpdate) {
|
|
289
|
+
light.skipUpdate = false;
|
|
290
|
+
} else {
|
|
291
|
+
const pos = vec3.create();
|
|
292
|
+
mat4.getTranslation(pos, matrix);
|
|
293
|
+
enqueueCommand(context.worldId, 'cmd-light-upsert', {
|
|
294
|
+
realmId,
|
|
295
|
+
lightId: light.id,
|
|
296
|
+
position: [pos[0], pos[1], pos[2], 1],
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/** Backward-compatible alias while migrating existing integrations. */
|
|
305
|
+
export const CoreCommandBuilderSystem = SceneSyncSystem;
|