lecodes-gl-lite 2.0.0
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 +27 -0
- package/package.json +33 -0
- package/src/assets/decoders/README.md +13 -0
- package/src/assets/decoders/basis_transcoder.js +19 -0
- package/src/assets/decoders/basis_transcoder.wasm +0 -0
- package/src/assets/decoders/draco_decoder.wasm +0 -0
- package/src/assets/decoders/draco_wasm_wrapper.js +117 -0
- package/src/assets/neutral_ibl128.ktx +0 -0
- package/src/core/animBridge.ts +51 -0
- package/src/core/boneNames.ts +22 -0
- package/src/core/frame.ts +230 -0
- package/src/core/glbStructure.ts +100 -0
- package/src/core/headlessBridge.ts +92 -0
- package/src/core/mapView.ts +195 -0
- package/src/core/math3d.ts +275 -0
- package/src/core/particleBridge.ts +154 -0
- package/src/core/physicsBridge.ts +305 -0
- package/src/core/recording.ts +59 -0
- package/src/core/sceneCore.ts +833 -0
- package/src/core/serialize.ts +144 -0
- package/src/gen/unsupported.gen.ts +212 -0
- package/src/index.ts +5 -0
- package/src/vendor/creator-anim/dist/creatorAnim.d.ts +123 -0
- package/src/vendor/creator-anim/dist/creatorAnim.js +14 -0
- package/src/vendor/creator-anim/js/index.ts +656 -0
- package/src/vendor/creator-nav/dist/creatorNav.d.ts +56 -0
- package/src/vendor/creator-nav/dist/creatorNav.js +14 -0
- package/src/vendor/creator-nav/js/index.ts +400 -0
- package/src/vendor/creator-particles/dist/creatorParticles.d.ts +43 -0
- package/src/vendor/creator-particles/dist/creatorParticles.js +14 -0
- package/src/vendor/creator-particles/js/index.ts +137 -0
- package/src/vendor/creator-physics/dist/creatorPhysics.d.ts +107 -0
- package/src/vendor/creator-physics/dist/creatorPhysics.js +14 -0
- package/src/vendor/creator-physics/js/index.ts +515 -0
- package/src/vite-env.d.ts +1 -0
- package/src/webgl/batching.ts +453 -0
- package/src/webgl/createViewerLite.ts +1627 -0
- package/src/webgl/customMaterial.ts +228 -0
- package/src/webgl/decoders/basis.ts +123 -0
- package/src/webgl/decoders/draco.ts +110 -0
- package/src/webgl/decoders/index.ts +68 -0
- package/src/webgl/decoders/worker.ts +45 -0
- package/src/webgl/editor.ts +1297 -0
- package/src/webgl/glb.ts +593 -0
- package/src/webgl/ibl.ts +137 -0
- package/src/webgl/shaders.ts +779 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// The web hosts' physics system: creator-physics' wasm (JoltPhysics) behind the same `_creator`
|
|
2
|
+
// physics / character / vehicle / ragdoll method shapes creator-gl exposes natively — ONE simulation
|
|
3
|
+
// for every host, no kinematic stand-in. Shared by viewer-lite's WebGL engine and lecodes-headless
|
|
4
|
+
// (headless); the host only supplies its scene graph (`PhysicsHost`).
|
|
5
|
+
//
|
|
6
|
+
// Lazy like the animation system: the module loads asynchronously (`loadCreatorPhysics()`, awaited by
|
|
7
|
+
// createViewerLite / runProject before the scene core exists), and the real bridge is created on the
|
|
8
|
+
// first physics call. `physicsHasSupport()` answers whether the module is resident — a host that never
|
|
9
|
+
// loaded it reports no physics and the SDK's aspects no-op, exactly like a physics-less native build.
|
|
10
|
+
//
|
|
11
|
+
// On top of the simulation this layer RECORDS what the SDK configured onto the entities (Ent.physics /
|
|
12
|
+
// Ent.ragdoll + the world config) for the headless JSON snapshots: the collider a node carries, its
|
|
13
|
+
// motion / mass / friction, a vehicle's tunables, a ragdoll's parts — so `lecodes render` shows the
|
|
14
|
+
// setup, and the simulation shows what it did.
|
|
15
|
+
|
|
16
|
+
import { createPhysicsBridge, loadCreatorPhysics, isCreatorPhysicsLoaded, type PhysicsHost, type PhysicsBridge } from "../vendor/creator-physics/js/index"
|
|
17
|
+
import type { Ent } from "./sceneCore"
|
|
18
|
+
|
|
19
|
+
export { loadCreatorPhysics, isCreatorPhysicsLoaded }
|
|
20
|
+
export type { PhysicsHost, PhysicsBridge }
|
|
21
|
+
|
|
22
|
+
type Methods = PhysicsBridge["methods"]
|
|
23
|
+
const METHOD_NAMES: (keyof Methods)[] = [
|
|
24
|
+
"physicsConfigure", "setInterpolation",
|
|
25
|
+
"physicsBuildBox", "physicsBuildSphere", "physicsBuildCylinder", "physicsBuildCapsule", "physicsBuildMesh",
|
|
26
|
+
"physicsBuildMeshFromEntity", "physicsBuildHeightField", "physicsUpdateHeightField", "glbTriangles",
|
|
27
|
+
"physicsSetShapeOrigin", "physicsSetBodyShape", "physicsDestroyShape",
|
|
28
|
+
"physicsCreateBody", "physicsSetPickable", "physicsSetFriction", "physicsGetFriction", "physicsRaycast",
|
|
29
|
+
"physicsCreateBox", "physicsCreateSphere", "physicsCreateCylinder",
|
|
30
|
+
"physicsSetLinearVelocity", "physicsGetLinearVelocity", "physicsSetAngularVelocity", "physicsGetAngularVelocity",
|
|
31
|
+
"physicsApplyImpulse", "physicsApplyImpulseAt", "physicsSetBodyPosition", "physicsSetBodyRotation", "physicsRemoveBody",
|
|
32
|
+
"characterCreate", "characterDestroy", "characterMove", "characterMoveFree", "characterSetVerticalVelocity",
|
|
33
|
+
"characterSetVelocity", "characterSetGravityScale", "characterSetMaxSlope", "characterSetShape",
|
|
34
|
+
"characterGetVelocity", "characterGetGroundState", "characterSetPosition",
|
|
35
|
+
"vehicleCreate", "vehicleDestroy", "vehicleSetInput", "vehicleSetTuning",
|
|
36
|
+
"vehicleGetState", "vehicleReset", "vehicleBodyId",
|
|
37
|
+
"ragdollCreate", "ragdollDestroy", "ragdollActivate", "ragdollDeactivate", "ragdollActive", "ragdollBodyId", "ragdollSetDrive",
|
|
38
|
+
"setOnPhysicsEvent",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
export type PhysicsSystemHost = PhysicsHost & { ent(id: number): Ent }
|
|
42
|
+
|
|
43
|
+
const FIXED_DT = 1 / 60
|
|
44
|
+
const MOTION = ["static", "kinematic", "dynamic"]
|
|
45
|
+
|
|
46
|
+
// Vehicle settings blob v15 — see bridges.d.ts for the field-by-field layout.
|
|
47
|
+
const VEHICLE_HEADER = 34
|
|
48
|
+
const VEHICLE_WHEEL = 15
|
|
49
|
+
const VEHICLE_WHEELS_AT = 32
|
|
50
|
+
const VEHICLE_CURVES_AT = 33
|
|
51
|
+
/** The half of the blob a live retune may change, decoded for the snapshot. */
|
|
52
|
+
const vehicleTunables = (s: Float32Array, curveCount: number): Record<string, unknown> => ({
|
|
53
|
+
engine: { torque: s[4], maxRpm: s[5], idleRpm: s[6], inertia: s[7], braking: s[8] },
|
|
54
|
+
clutchStrength: s[9],
|
|
55
|
+
clutchCapacity: s[31],
|
|
56
|
+
differential: s[10]! <= 0 ? "open" : s[10],
|
|
57
|
+
antiRoll: s[11],
|
|
58
|
+
maxTilt: s[12],
|
|
59
|
+
aero: { downforce: s[13], drag: s[14] },
|
|
60
|
+
steering: s[15] ? { feedback: { inertia: s[16], damping: s[17], parkingFriction: s[18], powerSteering: s[24], friction: s[29] }, caster: s[19], trail: s[20] } : { caster: s[19], trail: s[20] },
|
|
61
|
+
...(curveCount > 0
|
|
62
|
+
? { torqueCurve: Array.from(s.subarray(VEHICLE_HEADER, VEHICLE_HEADER + curveCount * 2)) }
|
|
63
|
+
: {}),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
export const createPhysicsSystem = (host: PhysicsSystemHost) => {
|
|
67
|
+
let real: PhysicsBridge | null = null
|
|
68
|
+
const get = (): PhysicsBridge => {
|
|
69
|
+
if (!real) {
|
|
70
|
+
if (!isCreatorPhysicsLoaded()) throw new Error("creator-physics is not loaded: await loadCreatorPhysics() (viewer-lite/src/gl/core/physicsBridge) before the first physics call")
|
|
71
|
+
real = createPhysicsBridge(host)
|
|
72
|
+
}
|
|
73
|
+
return real
|
|
74
|
+
}
|
|
75
|
+
const sim = {} as Methods
|
|
76
|
+
for (const name of METHOD_NAMES) {
|
|
77
|
+
(sim as Record<string, unknown>)[name] = (...args: unknown[]) => (get().methods[name] as (...a: unknown[]) => unknown)(...args)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---- the JSON record (what the SDK configured; the simulation moves the entities) ----
|
|
81
|
+
const shapes = new Map<number, Record<string, unknown>>()
|
|
82
|
+
const bodyOwner = new Map<number, number>() // bodyId → entity
|
|
83
|
+
const charOwner = new Map<number, number>() // charId → entity
|
|
84
|
+
const vehicleOwner = new Map<number, number>() // vehicleId → entity
|
|
85
|
+
const ragdollOwner = new Map<number, number>() // ragdollId → model entity
|
|
86
|
+
let config: Record<string, unknown> | undefined
|
|
87
|
+
const shapeRec = (id: number, rec: Record<string, unknown>): number => { if (id) shapes.set(id, rec); return id }
|
|
88
|
+
const physicsOf = (owner: Map<number, number>, id: number): Record<string, unknown> | undefined => {
|
|
89
|
+
const e = owner.get(id)
|
|
90
|
+
return e === undefined ? undefined : host.ent(e).physics
|
|
91
|
+
}
|
|
92
|
+
const legacyBody = (id: number, entityId: number, motion: number, mass: number, shape: Record<string, unknown>): number => {
|
|
93
|
+
if (id) { bodyOwner.set(id, entityId); host.ent(entityId).physics = { motion: MOTION[motion] ?? motion, mass, shape } }
|
|
94
|
+
return id
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const methods = {
|
|
98
|
+
...sim,
|
|
99
|
+
physicsHasSupport: (): boolean => isCreatorPhysicsLoaded(),
|
|
100
|
+
physicsConfigure(gx: number, gy: number, gz: number, maxBodies: number): void {
|
|
101
|
+
config = { gravity: [gx, gy, gz], maxBodies }
|
|
102
|
+
sim.physicsConfigure(gx, gy, gz, maxBodies)
|
|
103
|
+
},
|
|
104
|
+
physicsBuildBox: (hx: number, hy: number, hz: number): number =>
|
|
105
|
+
shapeRec(sim.physicsBuildBox(hx, hy, hz), { type: "box", size: [hx * 2, hy * 2, hz * 2] }),
|
|
106
|
+
physicsBuildSphere: (radius: number): number =>
|
|
107
|
+
shapeRec(sim.physicsBuildSphere(radius), { type: "sphere", radius }),
|
|
108
|
+
physicsBuildCylinder: (halfHeight: number, radius: number): number =>
|
|
109
|
+
shapeRec(sim.physicsBuildCylinder(halfHeight, radius), { type: "cylinder", height: halfHeight * 2, radius }),
|
|
110
|
+
physicsBuildCapsule: (halfHeight: number, radius: number): number =>
|
|
111
|
+
shapeRec(sim.physicsBuildCapsule(halfHeight, radius), { type: "capsule", height: halfHeight * 2, radius }),
|
|
112
|
+
physicsBuildMesh: (vertices: Float32Array, indices: Uint32Array, convex: boolean, sx: number, sy: number, sz: number): number =>
|
|
113
|
+
shapeRec(sim.physicsBuildMesh(vertices, indices, convex, sx, sy, sz),
|
|
114
|
+
{ type: convex ? "convexHull" : "mesh", vertices: vertices.length / 3, triangles: Math.floor(indices.length / 3), scale: [sx, sy, sz] }),
|
|
115
|
+
physicsBuildMeshFromEntity: (entityId: number, convex: boolean, sx: number, sy: number, sz: number): number =>
|
|
116
|
+
shapeRec(sim.physicsBuildMeshFromEntity(entityId, convex, sx, sy, sz),
|
|
117
|
+
{ type: convex ? "convexHull" : "mesh", source: entityId, scale: [sx, sy, sz] }),
|
|
118
|
+
physicsBuildHeightField(heights: Float32Array, sizeX: number, sizeZ: number, cellSize: number, holes: Uint8Array | null, sx: number, sy: number, sz: number): number {
|
|
119
|
+
let holeCount = 0
|
|
120
|
+
if (holes) for (let i = 0; i < holes.length; i++) if (holes[i]) holeCount++
|
|
121
|
+
return shapeRec(sim.physicsBuildHeightField(heights, sizeX, sizeZ, cellSize, holes, sx, sy, sz),
|
|
122
|
+
{ type: "heightfield", size: [sizeX, sizeZ], cellSize, samples: heights.length, holes: holeCount, scale: [sx, sy, sz] })
|
|
123
|
+
},
|
|
124
|
+
// Shape `origin`: on the shape record too (a JSON snapshot shows where the collider sits relative
|
|
125
|
+
// to the node). Zero clears it, matching the engine's "set, don't accumulate" rule.
|
|
126
|
+
physicsSetShapeOrigin(shapeId: number, x: number, y: number, z: number): void {
|
|
127
|
+
const shape = shapes.get(shapeId)
|
|
128
|
+
if (shape) { if (x === 0 && y === 0 && z === 0) delete shape.origin; else shape.origin = [x, y, z] }
|
|
129
|
+
sim.physicsSetShapeOrigin(shapeId, x, y, z)
|
|
130
|
+
},
|
|
131
|
+
physicsSetBodyShape(bodyId: number, shapeId: number, updateMass: boolean): void {
|
|
132
|
+
const p = physicsOf(bodyOwner, bodyId)
|
|
133
|
+
if (p) p.shape = shapes.get(shapeId)
|
|
134
|
+
sim.physicsSetBodyShape(bodyId, shapeId, updateMass)
|
|
135
|
+
},
|
|
136
|
+
physicsDestroyShape(shapeId: number): void { shapes.delete(shapeId); sim.physicsDestroyShape(shapeId) },
|
|
137
|
+
physicsCreateBody(entityId: number, shapeId: number, motion: number, mass: number, sensor: boolean, pickOnly: boolean): number {
|
|
138
|
+
const id = sim.physicsCreateBody(entityId, shapeId, motion, mass, sensor, pickOnly)
|
|
139
|
+
if (id) {
|
|
140
|
+
bodyOwner.set(id, entityId)
|
|
141
|
+
host.ent(entityId).physics = {
|
|
142
|
+
motion: MOTION[motion] ?? motion, mass,
|
|
143
|
+
...(sensor && { sensor }), ...(pickOnly && { pickOnly }),
|
|
144
|
+
shape: shapes.get(shapeId),
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return id
|
|
148
|
+
},
|
|
149
|
+
physicsSetFriction(bodyId: number, friction: number): void {
|
|
150
|
+
const p = physicsOf(bodyOwner, bodyId)
|
|
151
|
+
if (p) p.friction = friction
|
|
152
|
+
sim.physicsSetFriction(bodyId, friction)
|
|
153
|
+
},
|
|
154
|
+
physicsRemoveBody(bodyId: number): void { bodyOwner.delete(bodyId); sim.physicsRemoveBody(bodyId) },
|
|
155
|
+
physicsCreateBox: (entityId: number, hx: number, hy: number, hz: number, motionType: number, mass: number): number =>
|
|
156
|
+
legacyBody(sim.physicsCreateBox(entityId, hx, hy, hz, motionType, mass), entityId, motionType, mass, { type: "box", size: [hx * 2, hy * 2, hz * 2] }),
|
|
157
|
+
physicsCreateSphere: (entityId: number, radius: number, motionType: number, mass: number): number =>
|
|
158
|
+
legacyBody(sim.physicsCreateSphere(entityId, radius, motionType, mass), entityId, motionType, mass, { type: "sphere", radius }),
|
|
159
|
+
physicsCreateCylinder: (entityId: number, halfHeight: number, radius: number, motionType: number, mass: number): number =>
|
|
160
|
+
legacyBody(sim.physicsCreateCylinder(entityId, halfHeight, radius, motionType, mass), entityId, motionType, mass, { type: "cylinder", height: halfHeight * 2, radius }),
|
|
161
|
+
|
|
162
|
+
characterCreate(entityId: number, shapeId: number, maxSlopeDeg: number): number {
|
|
163
|
+
const id = sim.characterCreate(entityId, shapeId, maxSlopeDeg)
|
|
164
|
+
if (id) {
|
|
165
|
+
charOwner.set(id, entityId)
|
|
166
|
+
host.ent(entityId).physics = { character: true, maxSlopeDeg, shape: shapes.get(shapeId) }
|
|
167
|
+
}
|
|
168
|
+
return id
|
|
169
|
+
},
|
|
170
|
+
characterDestroy(charId: number): void { charOwner.delete(charId); sim.characterDestroy(charId) },
|
|
171
|
+
characterSetMaxSlope(charId: number, maxSlopeDeg: number): void {
|
|
172
|
+
const p = physicsOf(charOwner, charId)
|
|
173
|
+
if (p) p.maxSlopeDeg = maxSlopeDeg
|
|
174
|
+
sim.characterSetMaxSlope(charId, maxSlopeDeg)
|
|
175
|
+
},
|
|
176
|
+
characterSetShape(charId: number, shapeId: number): boolean {
|
|
177
|
+
const ok = sim.characterSetShape(charId, shapeId)
|
|
178
|
+
if (ok) { const p = physicsOf(charOwner, charId); if (p) p.shape = shapes.get(shapeId) }
|
|
179
|
+
return ok
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// The blob layout is the one in bridges.d.ts (v5) — decoded back to readable keys so a JSON
|
|
183
|
+
// snapshot shows the car the SDK actually configured.
|
|
184
|
+
vehicleCreate(entityId: number, shapeId: number, settings: Float32Array): number {
|
|
185
|
+
const id = sim.vehicleCreate(entityId, shapeId, settings)
|
|
186
|
+
if (!id) return 0
|
|
187
|
+
const wheelCount = settings[VEHICLE_WHEELS_AT] ?? 0
|
|
188
|
+
const curveCount = settings[VEHICLE_CURVES_AT] ?? 0
|
|
189
|
+
let w = VEHICLE_HEADER + curveCount * 2
|
|
190
|
+
const wheels: Record<string, unknown>[] = []
|
|
191
|
+
for (let i = 0; i < wheelCount; i++) {
|
|
192
|
+
// Variable length: the fixed slots, then the two tire curves (their point counts are the last two).
|
|
193
|
+
const sideCount = settings[w + 13] ?? 0
|
|
194
|
+
const forwardCount = settings[w + 14] ?? 0
|
|
195
|
+
wheels.push({
|
|
196
|
+
position: [settings[w], settings[w + 1], settings[w + 2]],
|
|
197
|
+
radius: settings[w + 3], width: settings[w + 4],
|
|
198
|
+
...(settings[w + 5] ? { steerDeg: settings[w + 5] } : {}),
|
|
199
|
+
...(settings[w + 6] ? { driven: settings[w + 6] } : {}),
|
|
200
|
+
axle: settings[w + 7],
|
|
201
|
+
suspension: { travel: settings[w + 8], stiffness: settings[w + 9], damping: settings[w + 10] },
|
|
202
|
+
tire: { traction: settings[w + 11], circle: settings[w + 12], side: sideCount, forward: forwardCount },
|
|
203
|
+
})
|
|
204
|
+
w += VEHICLE_WHEEL + (sideCount + forwardCount) * 2
|
|
205
|
+
}
|
|
206
|
+
vehicleOwner.set(id, entityId)
|
|
207
|
+
host.ent(entityId).physics = {
|
|
208
|
+
motion: "dynamic", mass: settings[1], vehicle: true,
|
|
209
|
+
...vehicleTunables(settings, curveCount),
|
|
210
|
+
wheels, shape: shapes.get(shapeId),
|
|
211
|
+
}
|
|
212
|
+
return id
|
|
213
|
+
},
|
|
214
|
+
vehicleDestroy(vehicleId: number): void { vehicleOwner.delete(vehicleId); sim.vehicleDestroy(vehicleId) },
|
|
215
|
+
vehicleSetTuning(vehicleId: number, settings: Float32Array): void {
|
|
216
|
+
const p = physicsOf(vehicleOwner, vehicleId)
|
|
217
|
+
if (p) Object.assign(p, vehicleTunables(settings, settings[VEHICLE_CURVES_AT] ?? 0))
|
|
218
|
+
sim.vehicleSetTuning(vehicleId, settings)
|
|
219
|
+
},
|
|
220
|
+
vehicleSetInput(vehicleId: number, input: Float32Array): void {
|
|
221
|
+
const p = physicsOf(vehicleOwner, vehicleId)
|
|
222
|
+
if (p) p.input = { throttle: input[0], steer: input[1], steerForce: input[2], ratio: input[3], clutch: input[4], brakes: Array.from(input.subarray(5)) }
|
|
223
|
+
sim.vehicleSetInput(vehicleId, input)
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
// The parts are decoded from the blob (layout in bridges.d.ts) onto the model entity so a JSON
|
|
227
|
+
// snapshot shows the skeleton the SDK handed to physics.
|
|
228
|
+
ragdollCreate(rootEntityId: number, bones: Uint32Array, settings: Float32Array): number {
|
|
229
|
+
const id = sim.ragdollCreate(rootEntityId, bones, settings)
|
|
230
|
+
if (!id) return 0
|
|
231
|
+
const header = 16 // blob v3 (bridges.d.ts)
|
|
232
|
+
const count = settings[1] ?? 0, stride = settings[2] ?? 12
|
|
233
|
+
const parts: Record<string, unknown>[] = []
|
|
234
|
+
for (let i = 0; i < count; i++) {
|
|
235
|
+
const o = header + i * stride
|
|
236
|
+
parts.push({
|
|
237
|
+
bone: bones[i * 2], ...(bones[i * 2 + 1] ? { to: bones[i * 2 + 1] } : {}),
|
|
238
|
+
parent: settings[o], radius: settings[o + 1], ...(settings[o + 2] ? { length: settings[o + 2] } : {}),
|
|
239
|
+
mass: settings[o + 3],
|
|
240
|
+
...(settings[o + 6]
|
|
241
|
+
? { hinge: { axis: [settings[o + 7], settings[o + 8], settings[o + 9]], min: settings[o + 10], max: settings[o + 11] } }
|
|
242
|
+
: { swing: settings[o + 4], twist: settings[o + 5] }),
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
ragdollOwner.set(id, rootEntityId)
|
|
246
|
+
host.ent(rootEntityId).ragdoll = {
|
|
247
|
+
friction: settings[3],
|
|
248
|
+
collide: settings[6] ? "static" : "all", freeze: !!settings[7], freezeAfter: settings[8] ?? 0,
|
|
249
|
+
drive: { frequency: settings[9], damping: settings[10], torque: settings[11] }, jointFriction: settings[12],
|
|
250
|
+
limits: settings[13] ? "clips" : "table", limitsMargin: settings[14],
|
|
251
|
+
parts,
|
|
252
|
+
}
|
|
253
|
+
return id
|
|
254
|
+
},
|
|
255
|
+
ragdollDestroy(ragdollId: number): void { ragdollOwner.delete(ragdollId); sim.ragdollDestroy(ragdollId) },
|
|
256
|
+
ragdollActivate(ragdollId: number, vx: number, vy: number, vz: number): boolean {
|
|
257
|
+
const ok = sim.ragdollActivate(ragdollId, vx, vy, vz)
|
|
258
|
+
const e = ragdollOwner.get(ragdollId)
|
|
259
|
+
const rag = e === undefined ? undefined : host.ent(e).ragdoll
|
|
260
|
+
if (ok && rag) { rag.active = true; rag.launch = [vx, vy, vz] }
|
|
261
|
+
return ok
|
|
262
|
+
},
|
|
263
|
+
ragdollDeactivate(ragdollId: number, blend: number): void {
|
|
264
|
+
const e = ragdollOwner.get(ragdollId)
|
|
265
|
+
const rag = e === undefined ? undefined : host.ent(e).ragdoll
|
|
266
|
+
if (rag) { rag.active = false; delete rag.launch; if (blend > 0) rag.blendOut = blend }
|
|
267
|
+
sim.ragdollDeactivate(ragdollId, blend)
|
|
268
|
+
},
|
|
269
|
+
ragdollSetDrive(ragdollId: number, strength: number, anchor: boolean): void {
|
|
270
|
+
const e = ragdollOwner.get(ragdollId)
|
|
271
|
+
const rag = e === undefined ? undefined : host.ent(e).ragdoll
|
|
272
|
+
if (rag) { rag.strength = strength; rag.anchored = anchor }
|
|
273
|
+
sim.ragdollSetDrive(ragdollId, strength, anchor)
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let acc = 0
|
|
278
|
+
return {
|
|
279
|
+
methods,
|
|
280
|
+
/** The simulation exists (something was created). */
|
|
281
|
+
loaded: (): boolean => real !== null,
|
|
282
|
+
/** One frame of GAME time: the fixed steps with the SDK's fixed phase between them, the render
|
|
283
|
+
* poses onto the entities, the contact events. Without a simulation (nothing created yet, or the
|
|
284
|
+
* module never loaded) the fixed phase still runs on its own 1/60 accumulator — creator-gl's
|
|
285
|
+
* physics-less build does the same (init.cpp). */
|
|
286
|
+
frame(dt: number, fixedUpdate: ((dt: number) => void) | null): void {
|
|
287
|
+
if (real) { real.frame(dt, fixedUpdate); return }
|
|
288
|
+
if (!fixedUpdate) { acc = 0; return }
|
|
289
|
+
acc += dt
|
|
290
|
+
let steps = 0
|
|
291
|
+
while (acc >= FIXED_DT && steps < 4) { fixedUpdate(FIXED_DT); acc -= FIXED_DT; steps++ }
|
|
292
|
+
if (steps === 4) acc = 0
|
|
293
|
+
},
|
|
294
|
+
/** After the animators: the animated poses are cached (velocities / the motors' target), then the
|
|
295
|
+
* active ragdolls' bodies overwrite their bones. */
|
|
296
|
+
syncRagdolls(dt: number): void { if (real) real.syncRagdolls(dt) },
|
|
297
|
+
/** Root motion → a CharacterController on the node or above it. False = nobody took it. */
|
|
298
|
+
applyRootMotion: (entityId: number, wx: number, wz: number, dt: number, yaw: number): boolean =>
|
|
299
|
+
real ? real.applyRootMotion(entityId, wx, wz, dt, yaw) : false,
|
|
300
|
+
/** The world config the SDK set (serialized at scene level), undefined if never configured. */
|
|
301
|
+
config: (): Record<string, unknown> | undefined => config,
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export type PhysicsSystem = ReturnType<typeof createPhysicsSystem>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Headless recording method groups for the lite 3D scene core — the parts of the `_creator` bridge
|
|
2
|
+
// whose behavior legitimately differs per consumer (gl-lite-core-plan.md "Capabilities"): AR/VR &
|
|
3
|
+
// anchors, colliders, scene-material and capture. Headless records them (so SDK code paths execute
|
|
4
|
+
// and get captured); the WebGL backend supplies its own honest versions (AR throws). All of these
|
|
5
|
+
// mutate the shared core state (entity.collider/anchor, scene.arMode), so a recorded scene serializes
|
|
6
|
+
// with the parameters the SDK actually set.
|
|
7
|
+
// Physics, particles and noise are NOT recorded here any more: the core simulates them for real
|
|
8
|
+
// (physicsBridge.ts / particleBridge.ts — creator-physics' and creator-particles' wasm) and records
|
|
9
|
+
// the configuration alongside.
|
|
10
|
+
|
|
11
|
+
import type { SceneCore } from "./sceneCore"
|
|
12
|
+
|
|
13
|
+
export type RecordingHandle = {
|
|
14
|
+
methods: Record<string, (...args: any[]) => any>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createRecording(core: SceneCore): RecordingHandle {
|
|
18
|
+
const { ent, newEnt, scenes, sourceName, materials } = core
|
|
19
|
+
|
|
20
|
+
const methods = {
|
|
21
|
+
// --- AR / VR lifecycle (permissive: records + completes) ---
|
|
22
|
+
launchAR(_sceneId: number, onComplete: () => void): void { onComplete() },
|
|
23
|
+
stopAR(): void {},
|
|
24
|
+
launchVR(_sceneId: number, onComplete: () => void): void { onComplete() },
|
|
25
|
+
stopVR(): void {},
|
|
26
|
+
warmRender(_sceneId: number, onComplete: () => void): void { onComplete() },
|
|
27
|
+
|
|
28
|
+
// --- colliders (recorded on the entity for serialization; the CPU pick path has no raycast headless) ---
|
|
29
|
+
setColliderFromMesh(entityId: number, _meshEntityId: number, form: number): void { ent(entityId).collider = { type: "mesh", form } },
|
|
30
|
+
setColliderBox(entityId: number, cx: number, cy: number, cz: number, sx: number, sy: number, sz: number): void {
|
|
31
|
+
ent(entityId).collider = { type: "box", center: [cx, cy, cz], size: [sx, sy, sz] }
|
|
32
|
+
},
|
|
33
|
+
setColliderSphere(entityId: number, cx: number, cy: number, cz: number, radius: number): void {
|
|
34
|
+
ent(entityId).collider = { type: "sphere", center: [cx, cy, cz], radius }
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// --- AR controllers + anchors ---
|
|
38
|
+
createARController(sceneId: number, _cameraId: number, mode: string): void {
|
|
39
|
+
const s = scenes.get(sceneId)
|
|
40
|
+
if (s) s.arMode = mode
|
|
41
|
+
},
|
|
42
|
+
createRootAnchor(_sceneId: number): number {
|
|
43
|
+
const e = newEnt()
|
|
44
|
+
e.anchor = "root"
|
|
45
|
+
return e.id
|
|
46
|
+
},
|
|
47
|
+
createAnchor(_sceneId: number, physicalWidth: number, systemId: number): number {
|
|
48
|
+
const e = newEnt()
|
|
49
|
+
e.anchor = sourceName(systemId) ?? "image"
|
|
50
|
+
void physicalWidth
|
|
51
|
+
return e.id
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// --- capture (can't rasterize headless) ---
|
|
55
|
+
captureImage(_sceneId: number, _onComplete: unknown, onReject: () => void): void { onReject() },
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { methods }
|
|
59
|
+
}
|