spoint 0.1.68 → 0.1.69
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/package.json +1 -1
- package/src/apps/AppContext.js +14 -6
- package/src/physics/GLBLoader.js +64 -1
package/package.json
CHANGED
package/src/apps/AppContext.js
CHANGED
|
@@ -85,12 +85,20 @@ export class AppContext {
|
|
|
85
85
|
},
|
|
86
86
|
addConvexFromModel: (meshIndex = 0) => {
|
|
87
87
|
if (!ent.model) return
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
try {
|
|
89
|
+
const mesh = extractMeshFromGLB(runtime.resolveAssetPath(ent.model), meshIndex)
|
|
90
|
+
const points = Array.from(mesh.vertices)
|
|
91
|
+
ent.collider = { type: 'convex', points }
|
|
92
|
+
if (runtime._physics) {
|
|
93
|
+
const mt = ent.bodyType === 'dynamic' ? 'dynamic' : ent.bodyType === 'kinematic' ? 'kinematic' : 'static'
|
|
94
|
+
ent._physicsBodyId = runtime._physics.addBody('convex', points, ent.position, mt, { rotation: ent.rotation, mass: ent.mass })
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (err.message.includes('Draco-compressed')) {
|
|
98
|
+
runtime._debug?.warn(`[physics] ${err.message}`)
|
|
99
|
+
} else {
|
|
100
|
+
throw err
|
|
101
|
+
}
|
|
94
102
|
}
|
|
95
103
|
},
|
|
96
104
|
addForce: (f) => {
|
package/src/physics/GLBLoader.js
CHANGED
|
@@ -1,18 +1,47 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Extract mesh from GLB file for physics collider creation.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: Draco-compressed meshes require decompression before vertex extraction.
|
|
7
|
+
* This function detects Draco compression and provides clear error guidance.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} filepath - Path to GLB file
|
|
10
|
+
* @param {number} meshIndex - Mesh index (default 0)
|
|
11
|
+
* @returns {Object} {vertices, indices, vertexCount, triangleCount, name}
|
|
12
|
+
* @throws {Error} If mesh is Draco-compressed or invalid
|
|
13
|
+
*/
|
|
3
14
|
export function extractMeshFromGLB(filepath, meshIndex = 0) {
|
|
4
15
|
const buf = readFileSync(filepath)
|
|
5
16
|
if (buf.toString('ascii', 0, 4) !== 'glTF') throw new Error('Not a GLB file')
|
|
17
|
+
|
|
6
18
|
const jsonLen = buf.readUInt32LE(12)
|
|
7
19
|
const json = JSON.parse(buf.toString('utf-8', 20, 20 + jsonLen))
|
|
8
20
|
const binOffset = 20 + jsonLen + 8
|
|
21
|
+
|
|
9
22
|
const mesh = json.meshes[meshIndex]
|
|
10
23
|
if (!mesh) throw new Error(`Mesh index ${meshIndex} not found`)
|
|
24
|
+
|
|
11
25
|
const prim = mesh.primitives[0]
|
|
26
|
+
|
|
27
|
+
// Check for Draco compression - this is the critical issue
|
|
28
|
+
if (prim.extensions?.KHR_draco_mesh_compression) {
|
|
29
|
+
const dracoExt = prim.extensions.KHR_draco_mesh_compression
|
|
30
|
+
const bufViewIdx = dracoExt.bufferView
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Cannot extract collider from Draco-compressed mesh '${mesh.name}'. \n\n` +
|
|
33
|
+
`SOLUTION: Use gltfpack to decompress the model before physics import:\n` +
|
|
34
|
+
` gltfpack -i model.glb -o model-uncompressed.glb -noq\n\n` +
|
|
35
|
+
`Or mark this model as 'no-physics' in entity config and use trigger colliders instead.`
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Standard uncompressed GLB mesh extraction
|
|
12
40
|
const posAcc = json.accessors[prim.attributes.POSITION]
|
|
13
41
|
const posView = json.bufferViews[posAcc.bufferView]
|
|
14
42
|
const posOff = binOffset + (posView.byteOffset || 0) + (posAcc.byteOffset || 0)
|
|
15
43
|
const vertices = new Float32Array(buf.buffer.slice(buf.byteOffset + posOff, buf.byteOffset + posOff + posAcc.count * 12))
|
|
44
|
+
|
|
16
45
|
let indices = null
|
|
17
46
|
if (prim.indices !== undefined) {
|
|
18
47
|
const idxAcc = json.accessors[prim.indices]
|
|
@@ -25,5 +54,39 @@ export function extractMeshFromGLB(filepath, meshIndex = 0) {
|
|
|
25
54
|
indices = new Uint32Array(buf.buffer.slice(buf.byteOffset + idxOff, buf.byteOffset + idxOff + idxAcc.count * 4))
|
|
26
55
|
}
|
|
27
56
|
}
|
|
28
|
-
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
vertices,
|
|
60
|
+
indices,
|
|
61
|
+
vertexCount: posAcc.count,
|
|
62
|
+
triangleCount: indices ? indices.length / 3 : 0,
|
|
63
|
+
name: mesh.name
|
|
64
|
+
}
|
|
29
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check if a GLB file has Draco-compressed meshes without attempting extraction.
|
|
69
|
+
* Useful for validation and error reporting.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} filepath - Path to GLB file
|
|
72
|
+
* @returns {Object} {hasDraco: boolean, meshes: Array<{name, hasDraco}>}
|
|
73
|
+
*/
|
|
74
|
+
export function detectDracoInGLB(filepath) {
|
|
75
|
+
try {
|
|
76
|
+
const buf = readFileSync(filepath)
|
|
77
|
+
if (buf.toString('ascii', 0, 4) !== 'glTF') return { hasDraco: false, meshes: [] }
|
|
78
|
+
|
|
79
|
+
const jsonLen = buf.readUInt32LE(12)
|
|
80
|
+
const json = JSON.parse(buf.toString('utf-8', 20, 20 + jsonLen))
|
|
81
|
+
|
|
82
|
+
const meshes = (json.meshes || []).map(mesh => ({
|
|
83
|
+
name: mesh.name || 'unnamed',
|
|
84
|
+
hasDraco: mesh.primitives?.some(p => p.extensions?.KHR_draco_mesh_compression) || false
|
|
85
|
+
}))
|
|
86
|
+
|
|
87
|
+
const hasDraco = meshes.some(m => m.hasDraco)
|
|
88
|
+
return { hasDraco, meshes }
|
|
89
|
+
} catch (e) {
|
|
90
|
+
return { hasDraco: false, meshes: [], error: e.message }
|
|
91
|
+
}
|
|
92
|
+
}
|