@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,19 +1,110 @@
|
|
|
1
1
|
import type { KeyboardEvent } from '../../types/events/keyboard';
|
|
2
2
|
import type { PointerEvent } from '../../types/events/pointer';
|
|
3
|
+
import type { GamepadEvent } from '../../types/events/gamepad';
|
|
4
|
+
import type { SystemEvent } from '../../types/events/system';
|
|
5
|
+
import type { UiEvent } from '../../types/events/ui';
|
|
3
6
|
import type { WindowEvent } from '../../types/events/window';
|
|
4
7
|
import type {
|
|
8
|
+
GamepadStateComponent,
|
|
5
9
|
InputStateComponent,
|
|
10
|
+
SystemEventStateComponent,
|
|
6
11
|
System,
|
|
12
|
+
UiEventStateComponent,
|
|
7
13
|
WindowStateComponent,
|
|
8
14
|
} from '../ecs';
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
|
-
*
|
|
17
|
+
* Mirrors inbound core events into world-level ECS state components.
|
|
18
|
+
*
|
|
19
|
+
* This system owns runtime input snapshots for keyboard, pointer, window,
|
|
20
|
+
* gamepad, system, and UI event streams.
|
|
12
21
|
*/
|
|
13
22
|
export const InputMirrorSystem: System = (world) => {
|
|
23
|
+
const resolveWindowSize = (data: {
|
|
24
|
+
windowWidth?: number;
|
|
25
|
+
windowHeight?: number;
|
|
26
|
+
}): [number, number] | undefined => {
|
|
27
|
+
if (
|
|
28
|
+
typeof data.windowWidth === 'number' &&
|
|
29
|
+
typeof data.windowHeight === 'number'
|
|
30
|
+
) {
|
|
31
|
+
return [data.windowWidth, data.windowHeight];
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const resolveTargetSize = (data: {
|
|
37
|
+
targetWidth?: number;
|
|
38
|
+
targetHeight?: number;
|
|
39
|
+
}): [number, number] | undefined => {
|
|
40
|
+
if (
|
|
41
|
+
typeof data.targetWidth === 'number' &&
|
|
42
|
+
typeof data.targetHeight === 'number'
|
|
43
|
+
) {
|
|
44
|
+
return [data.targetWidth, data.targetHeight];
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const applyPointerPosition = (
|
|
50
|
+
inputState: InputStateComponent,
|
|
51
|
+
globalPosition: [number, number],
|
|
52
|
+
targetPosition?: [number, number],
|
|
53
|
+
targetId?: number,
|
|
54
|
+
targetUv?: [number, number],
|
|
55
|
+
windowSize?: [number, number],
|
|
56
|
+
targetSize?: [number, number],
|
|
57
|
+
): void => {
|
|
58
|
+
const oldGlobalPosition = inputState.pointerPosition;
|
|
59
|
+
inputState.pointerDelta = [
|
|
60
|
+
globalPosition[0] - oldGlobalPosition[0],
|
|
61
|
+
globalPosition[1] - oldGlobalPosition[1],
|
|
62
|
+
];
|
|
63
|
+
inputState.pointerPosition = globalPosition;
|
|
64
|
+
inputState.pointerWindowSize = windowSize;
|
|
65
|
+
|
|
66
|
+
if (targetPosition) {
|
|
67
|
+
const previousTargetId = inputState.pointerTargetId;
|
|
68
|
+
const previousTargetPosition = inputState.pointerPositionTarget;
|
|
69
|
+
if (
|
|
70
|
+
previousTargetId === targetId &&
|
|
71
|
+
previousTargetPosition !== undefined
|
|
72
|
+
) {
|
|
73
|
+
inputState.pointerTargetDelta = [
|
|
74
|
+
targetPosition[0] - previousTargetPosition[0],
|
|
75
|
+
targetPosition[1] - previousTargetPosition[1],
|
|
76
|
+
];
|
|
77
|
+
} else {
|
|
78
|
+
inputState.pointerTargetDelta = [0, 0];
|
|
79
|
+
}
|
|
80
|
+
inputState.pointerPositionTarget = targetPosition;
|
|
81
|
+
inputState.pointerTargetId = targetId;
|
|
82
|
+
inputState.pointerTargetSize = targetSize;
|
|
83
|
+
inputState.pointerTargetUv = targetUv;
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
inputState.pointerPositionTarget = undefined;
|
|
88
|
+
inputState.pointerTargetDelta = undefined;
|
|
89
|
+
inputState.pointerTargetId = undefined;
|
|
90
|
+
inputState.pointerTargetSize = undefined;
|
|
91
|
+
inputState.pointerTargetUv = undefined;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const clearPointerTargetState = (inputState: InputStateComponent): void => {
|
|
95
|
+
inputState.pointerPositionTarget = undefined;
|
|
96
|
+
inputState.pointerTargetDelta = undefined;
|
|
97
|
+
inputState.pointerTargetId = undefined;
|
|
98
|
+
inputState.pointerTargetSize = undefined;
|
|
99
|
+
inputState.pointerTargetUv = undefined;
|
|
100
|
+
};
|
|
101
|
+
|
|
14
102
|
// Ensure InputState component exists for the world
|
|
15
103
|
let inputState: InputStateComponent | undefined;
|
|
16
104
|
let windowState: WindowStateComponent | undefined;
|
|
105
|
+
let gamepadState: GamepadStateComponent | undefined;
|
|
106
|
+
let systemEventState: SystemEventStateComponent | undefined;
|
|
107
|
+
let uiEventState: UiEventStateComponent | undefined;
|
|
17
108
|
|
|
18
109
|
// Find or create InputState (attached to a special entity ID 0)
|
|
19
110
|
const WORLD_ENTITY_ID = 0;
|
|
@@ -31,12 +122,15 @@ export const InputMirrorSystem: System = (world) => {
|
|
|
31
122
|
keysPressed: new Set(),
|
|
32
123
|
keysJustPressed: new Set(),
|
|
33
124
|
keysJustReleased: new Set(),
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
125
|
+
pointerButtons: new Set(),
|
|
126
|
+
pointerPosition: [0, 0],
|
|
127
|
+
pointerDelta: [0, 0],
|
|
128
|
+
pointerJustPressed: new Set(),
|
|
129
|
+
pointerJustReleased: new Set(),
|
|
130
|
+
pointerWindowSize: undefined,
|
|
131
|
+
pointerTargetSize: undefined,
|
|
39
132
|
scrollDelta: [0, 0],
|
|
133
|
+
imeEnabled: false,
|
|
40
134
|
};
|
|
41
135
|
worldStore.set('InputState', inputState);
|
|
42
136
|
}
|
|
@@ -57,21 +151,60 @@ export const InputMirrorSystem: System = (world) => {
|
|
|
57
151
|
worldStore.set('WindowState', windowState);
|
|
58
152
|
}
|
|
59
153
|
|
|
154
|
+
gamepadState = worldStore.get('GamepadState') as GamepadStateComponent;
|
|
155
|
+
if (!gamepadState) {
|
|
156
|
+
gamepadState = {
|
|
157
|
+
type: 'GamepadState',
|
|
158
|
+
connected: new Map(),
|
|
159
|
+
buttons: new Map(),
|
|
160
|
+
axes: new Map(),
|
|
161
|
+
eventsThisFrame: [],
|
|
162
|
+
};
|
|
163
|
+
worldStore.set('GamepadState', gamepadState);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
systemEventState = worldStore.get(
|
|
167
|
+
'SystemEventState',
|
|
168
|
+
) as SystemEventStateComponent;
|
|
169
|
+
if (!systemEventState) {
|
|
170
|
+
systemEventState = {
|
|
171
|
+
type: 'SystemEventState',
|
|
172
|
+
eventsThisFrame: [],
|
|
173
|
+
};
|
|
174
|
+
worldStore.set('SystemEventState', systemEventState);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
uiEventState = worldStore.get('UiEventState') as UiEventStateComponent;
|
|
178
|
+
if (!uiEventState) {
|
|
179
|
+
uiEventState = {
|
|
180
|
+
type: 'UiEventState',
|
|
181
|
+
eventsThisFrame: [],
|
|
182
|
+
};
|
|
183
|
+
worldStore.set('UiEventState', uiEventState);
|
|
184
|
+
}
|
|
185
|
+
|
|
60
186
|
// Clear "just pressed/released" from previous frame
|
|
61
187
|
inputState.keysJustPressed.clear();
|
|
62
188
|
inputState.keysJustReleased.clear();
|
|
63
|
-
inputState.
|
|
64
|
-
inputState.
|
|
65
|
-
inputState.
|
|
189
|
+
inputState.pointerJustPressed.clear();
|
|
190
|
+
inputState.pointerJustReleased.clear();
|
|
191
|
+
inputState.pointerDelta = [0, 0];
|
|
192
|
+
inputState.pointerTargetDelta = inputState.pointerPositionTarget
|
|
193
|
+
? [0, 0]
|
|
194
|
+
: undefined;
|
|
66
195
|
inputState.scrollDelta = [0, 0];
|
|
196
|
+
inputState.imeCommitText = undefined;
|
|
67
197
|
windowState.resizedThisFrame = false;
|
|
68
198
|
windowState.movedThisFrame = false;
|
|
69
199
|
windowState.focusChangedThisFrame = false;
|
|
70
200
|
windowState.closeRequested = false;
|
|
201
|
+
gamepadState.eventsThisFrame.length = 0;
|
|
202
|
+
systemEventState.eventsThisFrame.length = 0;
|
|
203
|
+
uiEventState.eventsThisFrame.length = 0;
|
|
71
204
|
|
|
72
205
|
// Process inbound events
|
|
73
|
-
|
|
74
|
-
const event = world.inboundEvents
|
|
206
|
+
for (let i = 0; i < world.inboundEvents.length; i++) {
|
|
207
|
+
const event = world.inboundEvents[i];
|
|
75
208
|
if (!event) continue;
|
|
76
209
|
|
|
77
210
|
// Keyboard events
|
|
@@ -90,6 +223,21 @@ export const InputMirrorSystem: System = (world) => {
|
|
|
90
223
|
inputState.keysPressed.delete(keyCode);
|
|
91
224
|
inputState.keysJustReleased.add(keyCode);
|
|
92
225
|
}
|
|
226
|
+
} else if (kbEvent.event === 'on-ime-enable') {
|
|
227
|
+
inputState.imeEnabled = true;
|
|
228
|
+
} else if (kbEvent.event === 'on-ime-preedit') {
|
|
229
|
+
inputState.imeEnabled = true;
|
|
230
|
+
inputState.imePreeditText = kbEvent.data.text;
|
|
231
|
+
inputState.imeCursorRange = kbEvent.data.cursorRange;
|
|
232
|
+
} else if (kbEvent.event === 'on-ime-commit') {
|
|
233
|
+
inputState.imeEnabled = true;
|
|
234
|
+
inputState.imeCommitText = kbEvent.data.text;
|
|
235
|
+
inputState.imePreeditText = undefined;
|
|
236
|
+
inputState.imeCursorRange = undefined;
|
|
237
|
+
} else if (kbEvent.event === 'on-ime-disable') {
|
|
238
|
+
inputState.imeEnabled = false;
|
|
239
|
+
inputState.imePreeditText = undefined;
|
|
240
|
+
inputState.imeCursorRange = undefined;
|
|
93
241
|
}
|
|
94
242
|
}
|
|
95
243
|
|
|
@@ -98,25 +246,43 @@ export const InputMirrorSystem: System = (world) => {
|
|
|
98
246
|
const ptrEvent = event.content as PointerEvent;
|
|
99
247
|
|
|
100
248
|
if (ptrEvent.event === 'on-move') {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
249
|
+
inputState.pointerWindowId = ptrEvent.data.windowId;
|
|
250
|
+
applyPointerPosition(
|
|
251
|
+
inputState,
|
|
252
|
+
ptrEvent.data.position,
|
|
253
|
+
ptrEvent.data.positionTarget,
|
|
254
|
+
ptrEvent.data.trace?.targetId,
|
|
255
|
+
ptrEvent.data.trace?.uv,
|
|
256
|
+
resolveWindowSize(ptrEvent.data),
|
|
257
|
+
resolveTargetSize(ptrEvent.data),
|
|
258
|
+
);
|
|
105
259
|
} else if (ptrEvent.event === 'on-button') {
|
|
106
260
|
const button = ptrEvent.data.button;
|
|
107
261
|
const pressed = ptrEvent.data.state === 'pressed';
|
|
108
262
|
|
|
109
263
|
if (pressed) {
|
|
110
|
-
if (!inputState.
|
|
111
|
-
inputState.
|
|
264
|
+
if (!inputState.pointerButtons.has(button)) {
|
|
265
|
+
inputState.pointerJustPressed.add(button);
|
|
112
266
|
}
|
|
113
|
-
inputState.
|
|
267
|
+
inputState.pointerButtons.add(button);
|
|
114
268
|
} else {
|
|
115
|
-
inputState.
|
|
116
|
-
inputState.
|
|
269
|
+
inputState.pointerButtons.delete(button);
|
|
270
|
+
inputState.pointerJustReleased.add(button);
|
|
117
271
|
}
|
|
118
|
-
inputState.
|
|
272
|
+
inputState.pointerWindowId = ptrEvent.data.windowId;
|
|
273
|
+
applyPointerPosition(
|
|
274
|
+
inputState,
|
|
275
|
+
ptrEvent.data.position,
|
|
276
|
+
ptrEvent.data.positionTarget,
|
|
277
|
+
ptrEvent.data.trace?.targetId,
|
|
278
|
+
ptrEvent.data.trace?.uv,
|
|
279
|
+
resolveWindowSize(ptrEvent.data),
|
|
280
|
+
resolveTargetSize(ptrEvent.data),
|
|
281
|
+
);
|
|
119
282
|
} else if (ptrEvent.event === 'on-scroll') {
|
|
283
|
+
inputState.pointerWindowId = ptrEvent.data.windowId;
|
|
284
|
+
inputState.pointerWindowSize = resolveWindowSize(ptrEvent.data);
|
|
285
|
+
inputState.pointerTargetSize = resolveTargetSize(ptrEvent.data);
|
|
120
286
|
const delta = ptrEvent.data.delta;
|
|
121
287
|
if (delta.type === 'line') {
|
|
122
288
|
inputState.scrollDelta = delta.value;
|
|
@@ -124,6 +290,25 @@ export const InputMirrorSystem: System = (world) => {
|
|
|
124
290
|
// Convert pixel to approximate line delta (rough estimate)
|
|
125
291
|
inputState.scrollDelta = [delta.value[0] / 20, delta.value[1] / 20];
|
|
126
292
|
}
|
|
293
|
+
} else if (ptrEvent.event === 'on-touch') {
|
|
294
|
+
inputState.pointerWindowId = ptrEvent.data.windowId;
|
|
295
|
+
applyPointerPosition(
|
|
296
|
+
inputState,
|
|
297
|
+
ptrEvent.data.position,
|
|
298
|
+
ptrEvent.data.positionTarget,
|
|
299
|
+
ptrEvent.data.trace?.targetId,
|
|
300
|
+
ptrEvent.data.trace?.uv,
|
|
301
|
+
resolveWindowSize(ptrEvent.data),
|
|
302
|
+
resolveTargetSize(ptrEvent.data),
|
|
303
|
+
);
|
|
304
|
+
} else if (ptrEvent.event === 'on-leave') {
|
|
305
|
+
inputState.pointerWindowId = ptrEvent.data.windowId;
|
|
306
|
+
inputState.pointerWindowSize = resolveWindowSize(ptrEvent.data);
|
|
307
|
+
clearPointerTargetState(inputState);
|
|
308
|
+
} else if (ptrEvent.event === 'on-enter') {
|
|
309
|
+
inputState.pointerWindowId = ptrEvent.data.windowId;
|
|
310
|
+
inputState.pointerWindowSize = resolveWindowSize(ptrEvent.data);
|
|
311
|
+
inputState.pointerTargetSize = resolveTargetSize(ptrEvent.data);
|
|
127
312
|
}
|
|
128
313
|
}
|
|
129
314
|
|
|
@@ -148,5 +333,56 @@ export const InputMirrorSystem: System = (world) => {
|
|
|
148
333
|
windowState.scaleFactor = winEvent.data.scaleFactor;
|
|
149
334
|
}
|
|
150
335
|
}
|
|
336
|
+
// Gamepad events
|
|
337
|
+
else if (event.type === 'gamepad') {
|
|
338
|
+
const gpEvent = event.content as GamepadEvent;
|
|
339
|
+
gamepadState.eventsThisFrame.push(gpEvent);
|
|
340
|
+
if (gpEvent.event === 'on-connect') {
|
|
341
|
+
gamepadState.connected.set(gpEvent.data.gamepadId, {
|
|
342
|
+
name: gpEvent.data.name,
|
|
343
|
+
});
|
|
344
|
+
} else if (gpEvent.event === 'on-disconnect') {
|
|
345
|
+
gamepadState.connected.delete(gpEvent.data.gamepadId);
|
|
346
|
+
gamepadState.buttons.delete(gpEvent.data.gamepadId);
|
|
347
|
+
gamepadState.axes.delete(gpEvent.data.gamepadId);
|
|
348
|
+
} else if (gpEvent.event === 'on-button') {
|
|
349
|
+
const id = gpEvent.data.gamepadId;
|
|
350
|
+
let buttons = gamepadState.buttons.get(id);
|
|
351
|
+
if (!buttons) {
|
|
352
|
+
buttons = new Map();
|
|
353
|
+
gamepadState.buttons.set(id, buttons);
|
|
354
|
+
}
|
|
355
|
+
buttons.set(gpEvent.data.button, {
|
|
356
|
+
pressed: gpEvent.data.state === 'pressed',
|
|
357
|
+
value: gpEvent.data.value,
|
|
358
|
+
});
|
|
359
|
+
} else if (gpEvent.event === 'on-axis') {
|
|
360
|
+
const id = gpEvent.data.gamepadId;
|
|
361
|
+
let axes = gamepadState.axes.get(id);
|
|
362
|
+
if (!axes) {
|
|
363
|
+
axes = new Map();
|
|
364
|
+
gamepadState.axes.set(id, axes);
|
|
365
|
+
}
|
|
366
|
+
axes.set(gpEvent.data.axis, gpEvent.data.value);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// System events
|
|
370
|
+
else if (event.type === 'system') {
|
|
371
|
+
const sysEvent = event.content as SystemEvent;
|
|
372
|
+
systemEventState.eventsThisFrame.push(sysEvent);
|
|
373
|
+
if (sysEvent.event === 'error') {
|
|
374
|
+
systemEventState.lastError = {
|
|
375
|
+
scope: sysEvent.data.scope,
|
|
376
|
+
message: sysEvent.data.message,
|
|
377
|
+
commandId: sysEvent.data.commandId,
|
|
378
|
+
commandType: sysEvent.data.commandType,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// UI events
|
|
383
|
+
else if (event.type === 'ui') {
|
|
384
|
+
uiEventState.eventsThisFrame.push(event.content as UiEvent);
|
|
385
|
+
}
|
|
151
386
|
}
|
|
387
|
+
world.inboundEvents.length = 0;
|
|
152
388
|
};
|
|
@@ -1,13 +1,95 @@
|
|
|
1
1
|
import type { TextureCreateMode } from '../../types/kinds';
|
|
2
|
+
import type {
|
|
3
|
+
PrimitiveOptions,
|
|
4
|
+
} from '../../types/cmds/geometry';
|
|
2
5
|
import { enqueueCommand } from '../bridge/dispatch';
|
|
3
|
-
import type { System } from '../ecs';
|
|
6
|
+
import type { GeometryProps, System } from '../ecs';
|
|
4
7
|
import { normalizeMaterialOptions, normalizePrimitiveOptions } from './utils';
|
|
5
8
|
|
|
9
|
+
const RESOURCE_INTENT_TYPES = [
|
|
10
|
+
'create-material',
|
|
11
|
+
'dispose-material',
|
|
12
|
+
'create-geometry',
|
|
13
|
+
'dispose-geometry',
|
|
14
|
+
'create-texture',
|
|
15
|
+
'dispose-texture',
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
function buildPrimitiveOptions(
|
|
19
|
+
primitiveProps: Extract<GeometryProps, { type: 'primitive' }>,
|
|
20
|
+
): PrimitiveOptions {
|
|
21
|
+
const { shape, options } = primitiveProps;
|
|
22
|
+
if (shape === 'cube') {
|
|
23
|
+
return {
|
|
24
|
+
type: 'cube',
|
|
25
|
+
content: {
|
|
26
|
+
size: options?.size ?? [1.0, 1.0, 1.0],
|
|
27
|
+
subdivisions: 1,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (shape === 'plane') {
|
|
32
|
+
return {
|
|
33
|
+
type: 'plane',
|
|
34
|
+
content: {
|
|
35
|
+
size: options?.size ?? [1.0, 1.0, 1.0],
|
|
36
|
+
subdivisions: options?.subdivisions ?? 1,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (shape === 'sphere') {
|
|
41
|
+
return {
|
|
42
|
+
type: 'sphere',
|
|
43
|
+
content: {
|
|
44
|
+
radius: options?.radius ?? 0.5,
|
|
45
|
+
sectors: options?.sectors ?? 32,
|
|
46
|
+
stacks: options?.stacks ?? 16,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (shape === 'cylinder') {
|
|
51
|
+
return {
|
|
52
|
+
type: 'cylinder',
|
|
53
|
+
content: {
|
|
54
|
+
radius: options?.radius ?? 0.5,
|
|
55
|
+
height: options?.height ?? 1.0,
|
|
56
|
+
sectors: options?.sectors ?? options?.segments ?? 32,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (shape === 'torus') {
|
|
61
|
+
return {
|
|
62
|
+
type: 'torus',
|
|
63
|
+
content: {
|
|
64
|
+
majorRadius: options?.majorRadius ?? 0.5,
|
|
65
|
+
minorRadius: options?.minorRadius ?? 0.25,
|
|
66
|
+
majorSegments: options?.majorSegments ?? options?.radialSegments ?? 32,
|
|
67
|
+
minorSegments: options?.minorSegments ?? options?.tubularSegments ?? 16,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
type: 'pyramid',
|
|
73
|
+
content: {
|
|
74
|
+
size: options?.size ?? [1.0, 1.0, 1.0],
|
|
75
|
+
subdivisions: options?.subdivisions ?? 1,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Processes resource intents and emits corresponding core resource commands.
|
|
82
|
+
*
|
|
83
|
+
* Covered resources:
|
|
84
|
+
* - material
|
|
85
|
+
* - geometry (custom and primitive)
|
|
86
|
+
* - texture (from buffer or solid color)
|
|
87
|
+
*/
|
|
6
88
|
export const ResourceUploadSystem: System = (world, context) => {
|
|
7
|
-
const
|
|
89
|
+
const intents = world.intentStore.takeMany(RESOURCE_INTENT_TYPES);
|
|
8
90
|
|
|
9
|
-
for (let i = 0; i <
|
|
10
|
-
const intent =
|
|
91
|
+
for (let i = 0; i < intents.length; i++) {
|
|
92
|
+
const intent = intents[i];
|
|
11
93
|
if (!intent) continue;
|
|
12
94
|
|
|
13
95
|
if (intent.type === 'create-material') {
|
|
@@ -20,78 +102,49 @@ export const ResourceUploadSystem: System = (world, context) => {
|
|
|
20
102
|
},
|
|
21
103
|
};
|
|
22
104
|
|
|
23
|
-
enqueueCommand(context.worldId, 'cmd-material-
|
|
24
|
-
windowId: context.worldId,
|
|
105
|
+
enqueueCommand(context.worldId, 'cmd-material-upsert', {
|
|
25
106
|
materialId: intent.resourceId,
|
|
26
107
|
label: intent.props.label || `Mat ${intent.resourceId}`,
|
|
27
108
|
kind: intent.props.kind ?? 'standard',
|
|
28
109
|
options: options,
|
|
29
110
|
});
|
|
30
|
-
intentsToRemove.push(i);
|
|
31
111
|
} else if (intent.type === 'dispose-material') {
|
|
32
112
|
enqueueCommand(context.worldId, 'cmd-material-dispose', {
|
|
33
|
-
windowId: context.worldId,
|
|
34
113
|
materialId: intent.resourceId,
|
|
35
114
|
});
|
|
36
|
-
intentsToRemove.push(i);
|
|
37
115
|
} else if (intent.type === 'create-geometry') {
|
|
38
116
|
if (intent.props.type === 'primitive') {
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const shapeOptions = intent.props.options;
|
|
43
|
-
if (shapeOptions) {
|
|
44
|
-
content = shapeOptions;
|
|
45
|
-
} else {
|
|
46
|
-
if (shape === 'cube' || shape === 'plane' || shape === 'pyramid') {
|
|
47
|
-
content = { size: [1.0, 1.0, 1.0], subdivisions: 1 };
|
|
48
|
-
} else if (shape === 'sphere') {
|
|
49
|
-
content = { radius: 0.5, sectors: 32, stacks: 16 };
|
|
50
|
-
} else if (shape === 'torus') {
|
|
51
|
-
content = {
|
|
52
|
-
majorRadius: 0.5,
|
|
53
|
-
minorRadius: 0.25,
|
|
54
|
-
majorSegments: 32,
|
|
55
|
-
minorSegments: 16,
|
|
56
|
-
};
|
|
57
|
-
} else if (shape === 'cylinder') {
|
|
58
|
-
content = { radius: 0.5, height: 1.0, sectors: 32 };
|
|
59
|
-
} else {
|
|
60
|
-
content = { size: [1.0, 1.0, 1.0], subdivisions: 1 };
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const options = normalizePrimitiveOptions({
|
|
65
|
-
type: shape,
|
|
66
|
-
content: content,
|
|
67
|
-
} as any);
|
|
117
|
+
const options = normalizePrimitiveOptions(
|
|
118
|
+
buildPrimitiveOptions(intent.props),
|
|
119
|
+
);
|
|
68
120
|
|
|
69
121
|
enqueueCommand(context.worldId, 'cmd-primitive-geometry-create', {
|
|
70
|
-
windowId: context.worldId,
|
|
71
122
|
geometryId: intent.resourceId,
|
|
72
123
|
label: intent.props.label || `Geo ${intent.resourceId}`,
|
|
73
|
-
shape: shape,
|
|
124
|
+
shape: intent.props.shape,
|
|
74
125
|
options: options,
|
|
75
126
|
});
|
|
76
127
|
} else {
|
|
77
|
-
enqueueCommand(context.worldId, 'cmd-geometry-
|
|
78
|
-
windowId: context.worldId,
|
|
128
|
+
enqueueCommand(context.worldId, 'cmd-geometry-upsert', {
|
|
79
129
|
geometryId: intent.resourceId,
|
|
80
130
|
label: intent.props.label || `Geo ${intent.resourceId}`,
|
|
81
131
|
entries: intent.props.entries,
|
|
82
132
|
});
|
|
83
133
|
}
|
|
84
|
-
intentsToRemove.push(i);
|
|
85
134
|
} else if (intent.type === 'dispose-geometry') {
|
|
86
135
|
enqueueCommand(context.worldId, 'cmd-geometry-dispose', {
|
|
87
|
-
windowId: context.worldId,
|
|
88
136
|
geometryId: intent.resourceId,
|
|
89
137
|
});
|
|
90
|
-
intentsToRemove.push(i);
|
|
91
138
|
} else if (intent.type === 'create-texture') {
|
|
92
139
|
if (intent.props.source.type === 'color') {
|
|
93
|
-
|
|
94
|
-
|
|
140
|
+
const cmd: {
|
|
141
|
+
textureId: number;
|
|
142
|
+
label: string;
|
|
143
|
+
color: [number, number, number, number];
|
|
144
|
+
srgb: boolean;
|
|
145
|
+
mode?: TextureCreateMode;
|
|
146
|
+
atlasOptions?: { tilePx: number; layers: number };
|
|
147
|
+
} = {
|
|
95
148
|
textureId: intent.resourceId,
|
|
96
149
|
label: intent.props.label || `Tex ${intent.resourceId}`,
|
|
97
150
|
color: Array.from(intent.props.source.color) as [
|
|
@@ -101,10 +154,16 @@ export const ResourceUploadSystem: System = (world, context) => {
|
|
|
101
154
|
number,
|
|
102
155
|
],
|
|
103
156
|
srgb: intent.props.srgb ?? true,
|
|
104
|
-
}
|
|
157
|
+
};
|
|
158
|
+
if (intent.props.mode !== undefined) {
|
|
159
|
+
cmd.mode = intent.props.mode;
|
|
160
|
+
}
|
|
161
|
+
if (intent.props.atlasOptions !== undefined) {
|
|
162
|
+
cmd.atlasOptions = intent.props.atlasOptions;
|
|
163
|
+
}
|
|
164
|
+
enqueueCommand(context.worldId, 'cmd-texture-create-solid-color', cmd);
|
|
105
165
|
} else {
|
|
106
166
|
const cmd: {
|
|
107
|
-
windowId: number;
|
|
108
167
|
textureId: number;
|
|
109
168
|
label: string;
|
|
110
169
|
bufferId: number;
|
|
@@ -112,7 +171,6 @@ export const ResourceUploadSystem: System = (world, context) => {
|
|
|
112
171
|
mode?: TextureCreateMode;
|
|
113
172
|
atlasOptions?: { tilePx: number; layers: number };
|
|
114
173
|
} = {
|
|
115
|
-
windowId: context.worldId,
|
|
116
174
|
textureId: intent.resourceId,
|
|
117
175
|
label: intent.props.label || `Tex ${intent.resourceId}`,
|
|
118
176
|
bufferId: intent.props.source.bufferId,
|
|
@@ -126,18 +184,10 @@ export const ResourceUploadSystem: System = (world, context) => {
|
|
|
126
184
|
}
|
|
127
185
|
enqueueCommand(context.worldId, 'cmd-texture-create-from-buffer', cmd);
|
|
128
186
|
}
|
|
129
|
-
intentsToRemove.push(i);
|
|
130
187
|
} else if (intent.type === 'dispose-texture') {
|
|
131
188
|
enqueueCommand(context.worldId, 'cmd-texture-dispose', {
|
|
132
|
-
windowId: context.worldId,
|
|
133
189
|
textureId: intent.resourceId,
|
|
134
190
|
});
|
|
135
|
-
intentsToRemove.push(i);
|
|
136
191
|
}
|
|
137
192
|
}
|
|
138
|
-
|
|
139
|
-
for (let i = intentsToRemove.length - 1; i >= 0; i--) {
|
|
140
|
-
const idx = intentsToRemove[i];
|
|
141
|
-
if (idx !== undefined) world.pendingIntents.splice(idx, 1);
|
|
142
|
-
}
|
|
143
193
|
};
|