@kite3d/engine 0.15.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/dist/authoring.d.ts +54 -0
- package/dist/authoring.js +165 -0
- package/dist/authoring.js.map +1 -0
- package/dist/authoringValidation.d.ts +110 -0
- package/dist/authoringValidation.js +488 -0
- package/dist/authoringValidation.js.map +1 -0
- package/dist/defaults.d.ts +2 -0
- package/dist/fileTypes.d.ts +5 -0
- package/dist/fileTypes.js +51 -0
- package/dist/fileTypes.js.map +1 -0
- package/dist/importMap.d.ts +15 -0
- package/dist/importMap.js +62 -0
- package/dist/importMap.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +2756 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations.js +5 -0
- package/dist/migrations.js.map +1 -0
- package/dist/paths.d.ts +9 -0
- package/dist/paths.js +13 -0
- package/dist/paths.js.map +1 -0
- package/dist/plugins/GeneratorComponent.d.ts +40 -0
- package/dist/plugins/HtmlUiComponent.d.ts +52 -0
- package/dist/plugins/HtmlUiComponent.example.d.ts +0 -0
- package/dist/plugins/cannon/Cannon3DBodyComponent.d.ts +27 -0
- package/dist/plugins/cannon/Cannon3DShapeComponent.d.ts +57 -0
- package/dist/plugins/cannon/CannonPhysicsPlugin.d.ts +63 -0
- package/dist/plugins/cannon/CannonRagdollComponent.d.ts +148 -0
- package/dist/plugins/cannon/helper.d.ts +24 -0
- package/dist/plugins/cannon/threeToCannon.d.ts +63 -0
- package/dist/plugins/cannon/utils.d.ts +9 -0
- package/dist/projectFormat.js +108 -0
- package/dist/projectFormat.js.map +1 -0
- package/dist/runtime/createGame.d.ts +29 -0
- package/dist/runtime/index.d.ts +18 -0
- package/dist/runtime/migrations.d.ts +5 -0
- package/dist/runtime/nestedAssets.d.ts +26 -0
- package/dist/runtime/projectFormat.d.ts +83 -0
- package/dist/runtime/version.d.ts +1 -0
- package/dist/runtime.js +72835 -0
- package/dist/runtime.js.map +1 -0
- package/dist/sceneSerialization.d.ts +17 -0
- package/dist/sceneSerialization.js +174 -0
- package/dist/sceneSerialization.js.map +1 -0
- package/dist/scripts.d.ts +17 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +86 -0
- package/src/authoring.ts +293 -0
- package/src/authoringValidation.ts +815 -0
- package/src/defaults.ts +277 -0
- package/src/fileTypes.ts +53 -0
- package/src/importMap.ts +105 -0
- package/src/index.ts +15 -0
- package/src/paths.ts +9 -0
- package/src/plugins/GeneratorComponent.ts +275 -0
- package/src/plugins/HtmlUiComponent.example.ts +202 -0
- package/src/plugins/HtmlUiComponent.md +219 -0
- package/src/plugins/HtmlUiComponent.ts +320 -0
- package/src/plugins/cannon/Cannon3DBodyComponent.ts +227 -0
- package/src/plugins/cannon/Cannon3DShapeComponent.ts +258 -0
- package/src/plugins/cannon/CannonPhysicsPlugin.ts +409 -0
- package/src/plugins/cannon/CannonRagdollComponent.ts +1170 -0
- package/src/plugins/cannon/helper.ts +255 -0
- package/src/plugins/cannon/threeToCannon.ts +441 -0
- package/src/plugins/cannon/utils.ts +185 -0
- package/src/runtime/createGame.ts +337 -0
- package/src/runtime/index.ts +19 -0
- package/src/runtime/migrations.ts +6 -0
- package/src/runtime/nestedAssets.ts +226 -0
- package/src/runtime/projectFormat.ts +233 -0
- package/src/runtime/version.ts +3 -0
- package/src/sceneSerialization.ts +289 -0
- package/src/scripts.ts +52 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// https://github.com/pmndrs/cannon-es-debugger/blob/master/src/cannon-es-debugger.ts
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Box,
|
|
5
|
+
ConvexPolyhedron,
|
|
6
|
+
Cylinder,
|
|
7
|
+
Heightfield,
|
|
8
|
+
Quaternion as CannonQuaternion,
|
|
9
|
+
Shape,
|
|
10
|
+
Sphere,
|
|
11
|
+
Trimesh,
|
|
12
|
+
Vec3 as CannonVector3,
|
|
13
|
+
} from 'cannon-es'
|
|
14
|
+
import {
|
|
15
|
+
BoxGeometry,
|
|
16
|
+
BufferGeometry,
|
|
17
|
+
CylinderGeometry,
|
|
18
|
+
Float32BufferAttribute,
|
|
19
|
+
LineMaterial2,
|
|
20
|
+
Mesh,
|
|
21
|
+
PlaneGeometry,
|
|
22
|
+
SphereGeometry,
|
|
23
|
+
Vector3 as ThreeVector3,
|
|
24
|
+
Wireframe,
|
|
25
|
+
WireframeGeometry3
|
|
26
|
+
} from "threepipe";
|
|
27
|
+
|
|
28
|
+
type ComplexShape = Shape & {geometryId?: number}
|
|
29
|
+
|
|
30
|
+
export function cannonDebugger() {
|
|
31
|
+
const scale = 1
|
|
32
|
+
|
|
33
|
+
const _tempVec0 = new CannonVector3()
|
|
34
|
+
const _tempVec1 = new CannonVector3()
|
|
35
|
+
const _tempVec2 = new CannonVector3()
|
|
36
|
+
const _tempQuat0 = new CannonQuaternion()
|
|
37
|
+
const _sphereGeometry = new SphereGeometry(1)
|
|
38
|
+
const _boxGeometry = new BoxGeometry(1, 1, 1)
|
|
39
|
+
const _planeGeometry = new PlaneGeometry(10, 10, 10, 10)
|
|
40
|
+
// const _material = new MeshBasicMaterial({color: color ?? 0x00ff00, wireframe: true})
|
|
41
|
+
const _material = new LineMaterial2({
|
|
42
|
+
color: 0x00ff00,
|
|
43
|
+
linewidth: 3, // in world units with size attenuation, pixels otherwise
|
|
44
|
+
vertexColors: false,
|
|
45
|
+
worldUnits: false,
|
|
46
|
+
|
|
47
|
+
dashed: false,
|
|
48
|
+
alphaToCoverage: true,
|
|
49
|
+
|
|
50
|
+
toneMapped: false,
|
|
51
|
+
transparent: true,
|
|
52
|
+
depthTest: true,
|
|
53
|
+
depthWrite: false,
|
|
54
|
+
|
|
55
|
+
allowOverride: false,
|
|
56
|
+
})
|
|
57
|
+
_material.userData.renderToGBuffer = false
|
|
58
|
+
_material.userData.renderToDepth = false
|
|
59
|
+
|
|
60
|
+
// Move the planeGeometry forward a little bit to prevent z-fighting
|
|
61
|
+
_planeGeometry.translate(0, 0, 0.0001)
|
|
62
|
+
|
|
63
|
+
function createConvexPolyhedronGeometry(shape: ConvexPolyhedron): BufferGeometry {
|
|
64
|
+
const geometry = new BufferGeometry()
|
|
65
|
+
|
|
66
|
+
// Add vertices
|
|
67
|
+
const positions = []
|
|
68
|
+
for (let i = 0; i < shape.vertices.length; i++) {
|
|
69
|
+
const vertex = shape.vertices[i]
|
|
70
|
+
positions.push(vertex.x, vertex.y, vertex.z)
|
|
71
|
+
}
|
|
72
|
+
geometry.setAttribute('position', new Float32BufferAttribute(positions, 3))
|
|
73
|
+
|
|
74
|
+
// Add faces
|
|
75
|
+
const indices = []
|
|
76
|
+
for (let i = 0; i < shape.faces.length; i++) {
|
|
77
|
+
const face = shape.faces[i]
|
|
78
|
+
const a = face[0]
|
|
79
|
+
for (let j = 1; j < face.length - 1; j++) {
|
|
80
|
+
const b = face[j]
|
|
81
|
+
const c = face[j + 1]
|
|
82
|
+
indices.push(a, b, c)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
geometry.setIndex(indices)
|
|
87
|
+
geometry.computeBoundingSphere()
|
|
88
|
+
geometry.computeVertexNormals()
|
|
89
|
+
return geometry
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function createTrimeshGeometry(shape: Trimesh): BufferGeometry {
|
|
93
|
+
const geometry = new BufferGeometry()
|
|
94
|
+
const positions = []
|
|
95
|
+
const v0 = _tempVec0
|
|
96
|
+
const v1 = _tempVec1
|
|
97
|
+
const v2 = _tempVec2
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < shape.indices.length / 3; i++) {
|
|
100
|
+
shape.getTriangleVertices(i, v0, v1, v2)
|
|
101
|
+
positions.push(v0.x, v0.y, v0.z)
|
|
102
|
+
positions.push(v1.x, v1.y, v1.z)
|
|
103
|
+
positions.push(v2.x, v2.y, v2.z)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
geometry.setAttribute('position', new Float32BufferAttribute(positions, 3))
|
|
107
|
+
geometry.computeBoundingSphere()
|
|
108
|
+
geometry.computeVertexNormals()
|
|
109
|
+
return geometry
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function createHeightfieldGeometry(shape: Heightfield): BufferGeometry {
|
|
113
|
+
const geometry = new BufferGeometry()
|
|
114
|
+
const s = shape.elementSize || 1 // assumes square heightfield, else i*x, j*y
|
|
115
|
+
const positions = shape.data.flatMap((row, i) => row.flatMap((z, j) => [i * s, j * s, z]))
|
|
116
|
+
const indices = []
|
|
117
|
+
|
|
118
|
+
for (let xi = 0; xi < shape.data.length - 1; xi++) {
|
|
119
|
+
for (let yi = 0; yi < shape.data[xi].length - 1; yi++) {
|
|
120
|
+
const stride = shape.data[xi].length
|
|
121
|
+
const index = xi * stride + yi
|
|
122
|
+
indices.push(index + 1, index + stride, index + stride + 1)
|
|
123
|
+
indices.push(index + stride, index + 1, index)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
geometry.setIndex(indices)
|
|
128
|
+
geometry.setAttribute('position', new Float32BufferAttribute(positions, 3))
|
|
129
|
+
geometry.computeBoundingSphere()
|
|
130
|
+
geometry.computeVertexNormals()
|
|
131
|
+
return geometry
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function createMesh(shape: Shape): Mesh {
|
|
135
|
+
let mesh = new Mesh()
|
|
136
|
+
const {SPHERE, BOX, PLANE, CYLINDER, CONVEXPOLYHEDRON, TRIMESH, HEIGHTFIELD} = Shape.types
|
|
137
|
+
|
|
138
|
+
switch (shape.type) {
|
|
139
|
+
case SPHERE: {
|
|
140
|
+
mesh = new Wireframe(new WireframeGeometry3(_sphereGeometry), _material)
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
case BOX: {
|
|
144
|
+
mesh = new Wireframe(new WireframeGeometry3(_boxGeometry), _material)
|
|
145
|
+
break
|
|
146
|
+
}
|
|
147
|
+
case PLANE: {
|
|
148
|
+
mesh = new Wireframe(new WireframeGeometry3(_planeGeometry), _material)
|
|
149
|
+
break
|
|
150
|
+
}
|
|
151
|
+
case CYLINDER: {
|
|
152
|
+
const geometry = new CylinderGeometry(
|
|
153
|
+
(shape as Cylinder).radiusTop,
|
|
154
|
+
(shape as Cylinder).radiusBottom,
|
|
155
|
+
(shape as Cylinder).height,
|
|
156
|
+
(shape as Cylinder).numSegments
|
|
157
|
+
)
|
|
158
|
+
mesh = new Wireframe(new WireframeGeometry3(geometry), _material)
|
|
159
|
+
;(shape as ComplexShape).geometryId = geometry.id
|
|
160
|
+
break
|
|
161
|
+
}
|
|
162
|
+
case CONVEXPOLYHEDRON: {
|
|
163
|
+
const geometry = createConvexPolyhedronGeometry(shape as ConvexPolyhedron)
|
|
164
|
+
mesh = new Wireframe(new WireframeGeometry3(geometry), _material)
|
|
165
|
+
;(shape as ComplexShape).geometryId = geometry.id
|
|
166
|
+
break
|
|
167
|
+
}
|
|
168
|
+
case TRIMESH: {
|
|
169
|
+
const geometry = createTrimeshGeometry(shape as Trimesh)
|
|
170
|
+
mesh = new Wireframe(new WireframeGeometry3(geometry), _material)
|
|
171
|
+
;(shape as ComplexShape).geometryId = geometry.id
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
case HEIGHTFIELD: {
|
|
175
|
+
const geometry = createHeightfieldGeometry(shape as Heightfield)
|
|
176
|
+
mesh = new Wireframe(new WireframeGeometry3(geometry), _material)
|
|
177
|
+
;(shape as ComplexShape).geometryId = geometry.id
|
|
178
|
+
break
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return mesh
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function scaleMesh(mesh: Mesh, shape: Shape | ComplexShape): void {
|
|
185
|
+
const {SPHERE, BOX, PLANE, CYLINDER, CONVEXPOLYHEDRON, TRIMESH, HEIGHTFIELD} = Shape.types
|
|
186
|
+
switch (shape.type) {
|
|
187
|
+
case SPHERE: {
|
|
188
|
+
const {radius} = shape as Sphere
|
|
189
|
+
mesh.scale.set(radius * scale, radius * scale, radius * scale)
|
|
190
|
+
break
|
|
191
|
+
}
|
|
192
|
+
case BOX: {
|
|
193
|
+
mesh.scale.copy((shape as Box).halfExtents as unknown as ThreeVector3)
|
|
194
|
+
mesh.scale.multiplyScalar(2 * scale)
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
case PLANE: {
|
|
198
|
+
break
|
|
199
|
+
}
|
|
200
|
+
case CYLINDER: {
|
|
201
|
+
mesh.scale.set(1 * scale, 1 * scale, 1 * scale)
|
|
202
|
+
break
|
|
203
|
+
}
|
|
204
|
+
case CONVEXPOLYHEDRON: {
|
|
205
|
+
mesh.scale.set(1 * scale, 1 * scale, 1 * scale)
|
|
206
|
+
break
|
|
207
|
+
}
|
|
208
|
+
case TRIMESH: {
|
|
209
|
+
mesh.scale.copy((shape as Trimesh).scale as unknown as ThreeVector3).multiplyScalar(scale)
|
|
210
|
+
break
|
|
211
|
+
}
|
|
212
|
+
case HEIGHTFIELD: {
|
|
213
|
+
mesh.scale.set(1 * scale, 1 * scale, 1 * scale)
|
|
214
|
+
break
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function typeMatch(mesh: Mesh|undefined, shape: Shape | ComplexShape): boolean {
|
|
220
|
+
if (!mesh) return false
|
|
221
|
+
const {geometry} = mesh
|
|
222
|
+
return (
|
|
223
|
+
geometry instanceof SphereGeometry && shape.type === Shape.types.SPHERE ||
|
|
224
|
+
geometry instanceof BoxGeometry && shape.type === Shape.types.BOX ||
|
|
225
|
+
geometry instanceof PlaneGeometry && shape.type === Shape.types.PLANE ||
|
|
226
|
+
// geometry.id === (shape as ComplexShape).geometryId && shape.type === Shape.types.BOX ||
|
|
227
|
+
// geometry.id === (shape as ComplexShape).geometryId && shape.type === Shape.types.SPHERE ||
|
|
228
|
+
// geometry.id === (shape as ComplexShape).geometryId && shape.type === Shape.types.PLANE ||
|
|
229
|
+
geometry.id === (shape as ComplexShape).geometryId && shape.type === Shape.types.CONVEXPOLYHEDRON ||
|
|
230
|
+
geometry.id === (shape as ComplexShape).geometryId && shape.type === Shape.types.TRIMESH ||
|
|
231
|
+
geometry.id === (shape as ComplexShape).geometryId && shape.type === Shape.types.HEIGHTFIELD
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function updateMesh(mesh: Mesh|undefined, shape: Shape | ComplexShape) {
|
|
236
|
+
if (!typeMatch(mesh, shape)) {
|
|
237
|
+
mesh = createMesh(shape)
|
|
238
|
+
}
|
|
239
|
+
mesh && scaleMesh(mesh, shape)
|
|
240
|
+
return mesh
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
createConvexPolyhedronGeometry,
|
|
246
|
+
createTrimeshGeometry,
|
|
247
|
+
createHeightfieldGeometry,
|
|
248
|
+
createMesh,
|
|
249
|
+
scaleMesh,
|
|
250
|
+
typeMatch,
|
|
251
|
+
updateMesh,
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export const CannonDebugger = cannonDebugger();
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
// https://github.com/donmccurdy/three-to-cannon/blob/main/src/index.ts
|
|
2
|
+
import {Box, ConvexPolyhedron, Cylinder, Quaternion as CQuaternion, Shape, Sphere, Trimesh, Vec3} from 'cannon-es';
|
|
3
|
+
import {ConvexHull, Box3, BufferGeometry, CylinderGeometry, MathUtils, Mesh, Object3D, SphereGeometry, Vector3} from 'threepipe';
|
|
4
|
+
import {getComponent, getGeometry, getVertices} from './utils';
|
|
5
|
+
|
|
6
|
+
const PI_2 = Math.PI / 2;
|
|
7
|
+
|
|
8
|
+
export type BoxParameters = { x: number, y: number, z: number };
|
|
9
|
+
|
|
10
|
+
export type CylinderParameters = { radiusTop: number, radiusBottom: number, height: number, segments: number };
|
|
11
|
+
|
|
12
|
+
export type SphereParameters = { radius: number };
|
|
13
|
+
|
|
14
|
+
export type ConvexPolyhedronParameters = { vertices: Float32Array, faces: number[][] };
|
|
15
|
+
|
|
16
|
+
export type TrimeshParameters = { vertices: Float32Array, indices: Uint32Array };
|
|
17
|
+
|
|
18
|
+
type ShapeTypeToShapeParameters = {
|
|
19
|
+
Box: BoxParameters,
|
|
20
|
+
Cylinder: CylinderParameters,
|
|
21
|
+
Sphere: SphereParameters,
|
|
22
|
+
ConvexPolyhedron: ConvexPolyhedronParameters,
|
|
23
|
+
Trimesh: TrimeshParameters,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export enum ShapeType {
|
|
27
|
+
BOX = 'Box',
|
|
28
|
+
CYLINDER = 'Cylinder',
|
|
29
|
+
SPHERE = 'Sphere',
|
|
30
|
+
HULL = 'ConvexPolyhedron',
|
|
31
|
+
MESH = 'Trimesh',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ShapeOptions {
|
|
35
|
+
type?: ShapeType,
|
|
36
|
+
cylinderAxis?: 'x' | 'y' | 'z',
|
|
37
|
+
sphereRadius?: number,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ShapeParameters<T extends ShapeType = ShapeType> {
|
|
41
|
+
type: T,
|
|
42
|
+
params: ShapeTypeToShapeParameters[T],
|
|
43
|
+
offset?: Vec3,
|
|
44
|
+
orientation?: CQuaternion,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ShapeResult<T extends Shape = Shape> {
|
|
48
|
+
shape: T,
|
|
49
|
+
offset?: Vec3,
|
|
50
|
+
orientation?: CQuaternion,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Given a THREE.Object3D instance, creates parameters for a CANNON shape.
|
|
55
|
+
*/
|
|
56
|
+
export const getShapeParameters = function (object: Object3D, options: ShapeOptions = {}): ShapeParameters | null {
|
|
57
|
+
let geometry: BufferGeometry | null;
|
|
58
|
+
|
|
59
|
+
if (options.type === ShapeType.BOX) {
|
|
60
|
+
return getBoundingBoxParameters(object);
|
|
61
|
+
} else if (options.type === ShapeType.CYLINDER) {
|
|
62
|
+
return getBoundingCylinderParameters(object, options);
|
|
63
|
+
} else if (options.type === ShapeType.SPHERE) {
|
|
64
|
+
return getBoundingSphereParameters(object, options);
|
|
65
|
+
} else if (options.type === ShapeType.HULL) {
|
|
66
|
+
return getConvexPolyhedronParameters(object);
|
|
67
|
+
} else if (options.type === ShapeType.MESH) {
|
|
68
|
+
geometry = getGeometry(object);
|
|
69
|
+
return geometry ? getTrimeshParameters(geometry) : null;
|
|
70
|
+
} else if (options.type) {
|
|
71
|
+
throw new Error(`[CANNON.getShapeParameters] Invalid type "${options.type}".`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
geometry = getGeometry(object);
|
|
75
|
+
if (!geometry) return null;
|
|
76
|
+
|
|
77
|
+
switch (geometry.type) {
|
|
78
|
+
case 'BoxGeometry':
|
|
79
|
+
case 'BoxBufferGeometry':
|
|
80
|
+
return getBoxParameters(geometry);
|
|
81
|
+
case 'CylinderGeometry':
|
|
82
|
+
case 'CylinderBufferGeometry':
|
|
83
|
+
return getCylinderParameters(geometry as CylinderGeometry);
|
|
84
|
+
case 'PlaneGeometry':
|
|
85
|
+
case 'PlaneBufferGeometry':
|
|
86
|
+
return getPlaneParameters(geometry);
|
|
87
|
+
case 'SphereGeometry':
|
|
88
|
+
case 'SphereBufferGeometry':
|
|
89
|
+
return getSphereParameters(geometry as SphereGeometry);
|
|
90
|
+
case 'TubeGeometry':
|
|
91
|
+
case 'BufferGeometry':
|
|
92
|
+
return getBoundingBoxParameters(object);
|
|
93
|
+
default:
|
|
94
|
+
console.warn(
|
|
95
|
+
'Unrecognized geometry: "%s". Using bounding box as shape.', geometry.type
|
|
96
|
+
);
|
|
97
|
+
return getBoxParameters(geometry);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Given a THREE.Object3D instance, creates a corresponding CANNON shape.
|
|
103
|
+
*/
|
|
104
|
+
export const threeToCannon = function (object: Object3D, options: ShapeOptions = {}, shapeParameters?: ShapeParameters|null): ShapeResult | null {
|
|
105
|
+
shapeParameters = shapeParameters || getShapeParameters(object, options);
|
|
106
|
+
if (!shapeParameters) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const { type, params, offset, orientation } = shapeParameters;
|
|
111
|
+
|
|
112
|
+
let shape: Shape;
|
|
113
|
+
if (type === ShapeType.BOX) {
|
|
114
|
+
shape = createBox(params as BoxParameters);
|
|
115
|
+
} else if (type === ShapeType.CYLINDER) {
|
|
116
|
+
shape = createCylinder(params as CylinderParameters);
|
|
117
|
+
} else if (type === ShapeType.SPHERE) {
|
|
118
|
+
shape = createSphere(params as SphereParameters);
|
|
119
|
+
} else if (type === ShapeType.HULL) {
|
|
120
|
+
shape = createConvexPolyhedron(params as ConvexPolyhedronParameters);
|
|
121
|
+
} else {
|
|
122
|
+
shape = createTrimesh(params as TrimeshParameters);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
shape,
|
|
127
|
+
offset,
|
|
128
|
+
orientation,
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/******************************************************************************
|
|
133
|
+
* Shape construction
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
function createBox (params: BoxParameters): Box {
|
|
137
|
+
const { x, y, z } = params;
|
|
138
|
+
const shape = new Box(new Vec3(x || 0.025, y || 0.025, z || 0.025));
|
|
139
|
+
return shape;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function createCylinder (params: CylinderParameters): Cylinder {
|
|
143
|
+
const { radiusTop, radiusBottom, height, segments } = params;
|
|
144
|
+
|
|
145
|
+
const shape = new Cylinder(radiusTop, radiusBottom, height, segments);
|
|
146
|
+
|
|
147
|
+
// Include metadata for serialization.
|
|
148
|
+
// TODO(cleanup): Is this still necessary?
|
|
149
|
+
shape.radiusTop = radiusBottom;
|
|
150
|
+
shape.radiusBottom = radiusBottom;
|
|
151
|
+
shape.height = height;
|
|
152
|
+
shape.numSegments = segments;
|
|
153
|
+
|
|
154
|
+
return shape;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function createSphere (params: SphereParameters): Sphere {
|
|
158
|
+
const shape = new Sphere(params.radius);
|
|
159
|
+
|
|
160
|
+
return shape;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function createConvexPolyhedron (params: ConvexPolyhedronParameters): ConvexPolyhedron {
|
|
164
|
+
const { faces, vertices: verticesArray } = params;
|
|
165
|
+
|
|
166
|
+
const vertices: Vec3[] = [];
|
|
167
|
+
for (let i = 0; i < verticesArray.length; i += 3) {
|
|
168
|
+
vertices.push(new Vec3(
|
|
169
|
+
verticesArray[i],
|
|
170
|
+
verticesArray[i + 1],
|
|
171
|
+
verticesArray[i + 2]
|
|
172
|
+
));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const shape = new ConvexPolyhedron({
|
|
176
|
+
faces,
|
|
177
|
+
vertices
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return shape;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function createTrimesh (params: TrimeshParameters): Trimesh {
|
|
184
|
+
const { vertices, indices } = params
|
|
185
|
+
const shape = new Trimesh(
|
|
186
|
+
vertices as unknown as number[],
|
|
187
|
+
indices as unknown as number[],
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return shape;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/******************************************************************************
|
|
194
|
+
* Shape parameters
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
function getBoxParameters (geometry: BufferGeometry): ShapeParameters<ShapeType.BOX> | null {
|
|
198
|
+
const vertices = getVertices(geometry);
|
|
199
|
+
|
|
200
|
+
if (!vertices.length) return null;
|
|
201
|
+
|
|
202
|
+
geometry.computeBoundingBox();
|
|
203
|
+
const box = geometry.boundingBox!;
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
type: ShapeType.BOX,
|
|
207
|
+
params: {
|
|
208
|
+
x: (box.max.x - box.min.x) / 2,
|
|
209
|
+
y: (box.max.y - box.min.y) / 2,
|
|
210
|
+
z: (box.max.z - box.min.z) / 2,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Bounding box needs to be computed with the entire subtree, not just geometry. */
|
|
216
|
+
function getBoundingBoxParameters (object: Object3D): ShapeParameters<ShapeType.BOX> | null {
|
|
217
|
+
const clone = object.clone();
|
|
218
|
+
clone.quaternion.set(0, 0, 0, 1);
|
|
219
|
+
clone.updateMatrixWorld();
|
|
220
|
+
|
|
221
|
+
const box = new Box3().setFromObject(clone);
|
|
222
|
+
|
|
223
|
+
if (!isFinite(box.min.lengthSq())) return null;
|
|
224
|
+
|
|
225
|
+
const localPosition = box.translate(clone.position.negate()).getCenter(new Vector3());
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
type: ShapeType.BOX,
|
|
229
|
+
params: {
|
|
230
|
+
x: (box.max.x - box.min.x) / 2,
|
|
231
|
+
y: (box.max.y - box.min.y) / 2,
|
|
232
|
+
z: (box.max.z - box.min.z) / 2,
|
|
233
|
+
},
|
|
234
|
+
offset: localPosition.lengthSq()
|
|
235
|
+
? new Vec3(localPosition.x, localPosition.y, localPosition.z)
|
|
236
|
+
: undefined,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Computes 3D convex hull as a CANNON.ConvexPolyhedron. */
|
|
241
|
+
function getConvexPolyhedronParameters (object: Object3D): ShapeParameters<ShapeType.HULL> | null {
|
|
242
|
+
const geometry = getGeometry(object);
|
|
243
|
+
|
|
244
|
+
if (!geometry) return null;
|
|
245
|
+
|
|
246
|
+
// Perturb.
|
|
247
|
+
const eps = 1e-4;
|
|
248
|
+
for (let i = 0; i < geometry.attributes.position.count; i++) {
|
|
249
|
+
geometry.attributes.position.setXYZ(
|
|
250
|
+
i,
|
|
251
|
+
geometry.attributes.position.getX(i) + (Math.random() - 0.5) * eps,
|
|
252
|
+
geometry.attributes.position.getY(i) + (Math.random() - 0.5) * eps,
|
|
253
|
+
geometry.attributes.position.getZ(i) + (Math.random() - 0.5) * eps,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Compute the 3D convex hull and collect convex hull vertices and faces.
|
|
258
|
+
const [ positions, indices ] = convexHullToJSON(new ConvexHull().setFromObject(new Mesh(geometry)));
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
type: ShapeType.HULL,
|
|
262
|
+
params: {
|
|
263
|
+
vertices: new Float32Array(positions),
|
|
264
|
+
faces: indices,
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function getCylinderParameters (
|
|
270
|
+
geometry: CylinderGeometry
|
|
271
|
+
): ShapeParameters<ShapeType.CYLINDER> | null {
|
|
272
|
+
const params = geometry.parameters;
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
type: ShapeType.CYLINDER,
|
|
276
|
+
params: {
|
|
277
|
+
radiusTop: params.radiusTop,
|
|
278
|
+
radiusBottom: params.radiusBottom,
|
|
279
|
+
height: params.height,
|
|
280
|
+
segments: params.radialSegments,
|
|
281
|
+
},
|
|
282
|
+
orientation: new CQuaternion()
|
|
283
|
+
.setFromEuler(MathUtils.degToRad(-90), 0, 0, 'XYZ')
|
|
284
|
+
.normalize(),
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function getBoundingCylinderParameters (
|
|
289
|
+
object: Object3D,
|
|
290
|
+
options: ShapeOptions
|
|
291
|
+
): ShapeParameters<ShapeType.CYLINDER> | null {
|
|
292
|
+
const axes = ['x', 'y', 'z'];
|
|
293
|
+
const majorAxis = options.cylinderAxis || 'y';
|
|
294
|
+
const minorAxes = axes.splice(axes.indexOf(majorAxis), 1) && axes;
|
|
295
|
+
const box = new Box3().setFromObject(object);
|
|
296
|
+
|
|
297
|
+
if (!isFinite(box.min.lengthSq())) return null;
|
|
298
|
+
|
|
299
|
+
// Compute cylinder dimensions.
|
|
300
|
+
const height = box.max[majorAxis] - box.min[majorAxis];
|
|
301
|
+
const radius = 0.5 * Math.max(
|
|
302
|
+
getComponent(box.max, minorAxes[0]) - getComponent(box.min, minorAxes[0]),
|
|
303
|
+
getComponent(box.max, minorAxes[1]) - getComponent(box.min, minorAxes[1]),
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const eulerX = majorAxis === 'y' ? PI_2 : 0;
|
|
307
|
+
const eulerY = majorAxis === 'z' ? PI_2 : 0;
|
|
308
|
+
|
|
309
|
+
return {
|
|
310
|
+
type: ShapeType.CYLINDER,
|
|
311
|
+
params: {
|
|
312
|
+
radiusTop: radius,
|
|
313
|
+
radiusBottom: radius,
|
|
314
|
+
height,
|
|
315
|
+
segments: 12,
|
|
316
|
+
},
|
|
317
|
+
orientation: new CQuaternion()
|
|
318
|
+
.setFromEuler(eulerX, eulerY, 0, 'XYZ')
|
|
319
|
+
.normalize(),
|
|
320
|
+
offset: new Vec3(
|
|
321
|
+
(box.max.x + box.min.x) / 2,
|
|
322
|
+
(box.max.y + box.min.y) / 2,
|
|
323
|
+
(box.max.z + box.min.z) / 2,
|
|
324
|
+
),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function getPlaneParameters (geometry: BufferGeometry): ShapeParameters<ShapeType.BOX> | null {
|
|
329
|
+
geometry.computeBoundingBox();
|
|
330
|
+
const box = geometry.boundingBox!;
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
type: ShapeType.BOX,
|
|
334
|
+
params: {
|
|
335
|
+
x: (box.max.x - box.min.x) / 2 || 0.1,
|
|
336
|
+
y: (box.max.y - box.min.y) / 2 || 0.1,
|
|
337
|
+
z: (box.max.z - box.min.z) / 2 || 0.1,
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function getSphereParameters (geometry: SphereGeometry): ShapeParameters<ShapeType.SPHERE> | null {
|
|
343
|
+
return {
|
|
344
|
+
type: ShapeType.SPHERE,
|
|
345
|
+
params: { radius: geometry.parameters.radius },
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function getBoundingSphereParameters (
|
|
350
|
+
object: Object3D,
|
|
351
|
+
options: ShapeOptions
|
|
352
|
+
): ShapeParameters<ShapeType.SPHERE> | null {
|
|
353
|
+
if (options.sphereRadius) {
|
|
354
|
+
return {
|
|
355
|
+
type: ShapeType.SPHERE,
|
|
356
|
+
params: { radius: options.sphereRadius },
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
const geometry = getGeometry(object);
|
|
360
|
+
if (!geometry) return null;
|
|
361
|
+
geometry.computeBoundingSphere();
|
|
362
|
+
const sphere = geometry.boundingSphere;
|
|
363
|
+
if(!sphere) return null;
|
|
364
|
+
if(!isFinite(sphere.radius)) return null;
|
|
365
|
+
|
|
366
|
+
// const localPosition = sphere.center.clone().sub(object.position);
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
type: ShapeType.SPHERE,
|
|
370
|
+
params: { radius: geometry.boundingSphere!.radius },
|
|
371
|
+
// offset: localPosition.lengthSq()
|
|
372
|
+
// ? new Vec3(localPosition.x, localPosition.y, localPosition.z)
|
|
373
|
+
// : undefined,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function getTrimeshParameters (geometry: BufferGeometry): ShapeParameters<ShapeType.MESH> | null {
|
|
378
|
+
const vertices = getVertices(geometry);
|
|
379
|
+
|
|
380
|
+
if (!vertices.length) return null;
|
|
381
|
+
|
|
382
|
+
const indices = new Uint32Array(vertices.length);
|
|
383
|
+
for (let i = 0; i < vertices.length; i++) {
|
|
384
|
+
indices[i] = i;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
type: ShapeType.MESH,
|
|
389
|
+
params: {
|
|
390
|
+
vertices,
|
|
391
|
+
indices,
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function convexHullToJSON(hull: ConvexHull) {
|
|
397
|
+
// Original ('src') indices do not include interior vertices,
|
|
398
|
+
// but 'hull.vertices' (the list they index) does. Output ('dst')
|
|
399
|
+
// arrays have interior vertices omitted.
|
|
400
|
+
|
|
401
|
+
const srcIndices = hull.faces.map((f) => {
|
|
402
|
+
const indices = [];
|
|
403
|
+
let edge = f.edge;
|
|
404
|
+
do {
|
|
405
|
+
const vertex = edge.head()
|
|
406
|
+
const index = hull.vertices.indexOf(vertex)
|
|
407
|
+
if(index >=0) indices.push(index);
|
|
408
|
+
edge = edge.next;
|
|
409
|
+
} while (edge !== f.edge);
|
|
410
|
+
return indices;
|
|
411
|
+
});
|
|
412
|
+
const uniqueSrcIndices = Array.from(new Set(srcIndices.flat())).sort();
|
|
413
|
+
|
|
414
|
+
// Output vertex positions, omitting interior vertices.
|
|
415
|
+
const dstPositions = [];
|
|
416
|
+
for (let i = 0; i < uniqueSrcIndices.length; i++) {
|
|
417
|
+
dstPositions.push(
|
|
418
|
+
hull.vertices[uniqueSrcIndices[i]].point.x,
|
|
419
|
+
hull.vertices[uniqueSrcIndices[i]].point.y,
|
|
420
|
+
hull.vertices[uniqueSrcIndices[i]].point.z,
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Mapping from 'src' (hull.vertices) to 'dst' (dstPositions) indices.
|
|
425
|
+
const srcToDstIndexMap = new Map();
|
|
426
|
+
for (let i = 0; i < uniqueSrcIndices.length; i++) {
|
|
427
|
+
srcToDstIndexMap.set(uniqueSrcIndices[i], i);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Output triangles, as indices on dstPositions.
|
|
431
|
+
const dstIndices = [];
|
|
432
|
+
for (let i = 0; i < srcIndices.length; i++) {
|
|
433
|
+
dstIndices.push([
|
|
434
|
+
srcToDstIndexMap.get(srcIndices[i][0]),
|
|
435
|
+
srcToDstIndexMap.get(srcIndices[i][1]),
|
|
436
|
+
srcToDstIndexMap.get(srcIndices[i][2]),
|
|
437
|
+
]);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return [dstPositions, dstIndices] as const;
|
|
441
|
+
}
|