@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.
Files changed (74) hide show
  1. package/dist/authoring.d.ts +54 -0
  2. package/dist/authoring.js +165 -0
  3. package/dist/authoring.js.map +1 -0
  4. package/dist/authoringValidation.d.ts +110 -0
  5. package/dist/authoringValidation.js +488 -0
  6. package/dist/authoringValidation.js.map +1 -0
  7. package/dist/defaults.d.ts +2 -0
  8. package/dist/fileTypes.d.ts +5 -0
  9. package/dist/fileTypes.js +51 -0
  10. package/dist/fileTypes.js.map +1 -0
  11. package/dist/importMap.d.ts +15 -0
  12. package/dist/importMap.js +62 -0
  13. package/dist/importMap.js.map +1 -0
  14. package/dist/index.d.ts +15 -0
  15. package/dist/index.js +2756 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/migrations.js +5 -0
  18. package/dist/migrations.js.map +1 -0
  19. package/dist/paths.d.ts +9 -0
  20. package/dist/paths.js +13 -0
  21. package/dist/paths.js.map +1 -0
  22. package/dist/plugins/GeneratorComponent.d.ts +40 -0
  23. package/dist/plugins/HtmlUiComponent.d.ts +52 -0
  24. package/dist/plugins/HtmlUiComponent.example.d.ts +0 -0
  25. package/dist/plugins/cannon/Cannon3DBodyComponent.d.ts +27 -0
  26. package/dist/plugins/cannon/Cannon3DShapeComponent.d.ts +57 -0
  27. package/dist/plugins/cannon/CannonPhysicsPlugin.d.ts +63 -0
  28. package/dist/plugins/cannon/CannonRagdollComponent.d.ts +148 -0
  29. package/dist/plugins/cannon/helper.d.ts +24 -0
  30. package/dist/plugins/cannon/threeToCannon.d.ts +63 -0
  31. package/dist/plugins/cannon/utils.d.ts +9 -0
  32. package/dist/projectFormat.js +108 -0
  33. package/dist/projectFormat.js.map +1 -0
  34. package/dist/runtime/createGame.d.ts +29 -0
  35. package/dist/runtime/index.d.ts +18 -0
  36. package/dist/runtime/migrations.d.ts +5 -0
  37. package/dist/runtime/nestedAssets.d.ts +26 -0
  38. package/dist/runtime/projectFormat.d.ts +83 -0
  39. package/dist/runtime/version.d.ts +1 -0
  40. package/dist/runtime.js +72835 -0
  41. package/dist/runtime.js.map +1 -0
  42. package/dist/sceneSerialization.d.ts +17 -0
  43. package/dist/sceneSerialization.js +174 -0
  44. package/dist/sceneSerialization.js.map +1 -0
  45. package/dist/scripts.d.ts +17 -0
  46. package/dist/version.js +7 -0
  47. package/dist/version.js.map +1 -0
  48. package/package.json +86 -0
  49. package/src/authoring.ts +293 -0
  50. package/src/authoringValidation.ts +815 -0
  51. package/src/defaults.ts +277 -0
  52. package/src/fileTypes.ts +53 -0
  53. package/src/importMap.ts +105 -0
  54. package/src/index.ts +15 -0
  55. package/src/paths.ts +9 -0
  56. package/src/plugins/GeneratorComponent.ts +275 -0
  57. package/src/plugins/HtmlUiComponent.example.ts +202 -0
  58. package/src/plugins/HtmlUiComponent.md +219 -0
  59. package/src/plugins/HtmlUiComponent.ts +320 -0
  60. package/src/plugins/cannon/Cannon3DBodyComponent.ts +227 -0
  61. package/src/plugins/cannon/Cannon3DShapeComponent.ts +258 -0
  62. package/src/plugins/cannon/CannonPhysicsPlugin.ts +409 -0
  63. package/src/plugins/cannon/CannonRagdollComponent.ts +1170 -0
  64. package/src/plugins/cannon/helper.ts +255 -0
  65. package/src/plugins/cannon/threeToCannon.ts +441 -0
  66. package/src/plugins/cannon/utils.ts +185 -0
  67. package/src/runtime/createGame.ts +337 -0
  68. package/src/runtime/index.ts +19 -0
  69. package/src/runtime/migrations.ts +6 -0
  70. package/src/runtime/nestedAssets.ts +226 -0
  71. package/src/runtime/projectFormat.ts +233 -0
  72. package/src/runtime/version.ts +3 -0
  73. package/src/sceneSerialization.ts +289 -0
  74. package/src/scripts.ts +52 -0
@@ -0,0 +1,258 @@
1
+ import {
2
+ AHelperWidget,
3
+ ComponentDefn,
4
+ ComponentJSON,
5
+ EntityComponentPlugin, Group,
6
+ IMaterial,
7
+ IObject3D,
8
+ literalStrings,
9
+ Mesh,
10
+ Object3DComponent, Object3DWidgetsPlugin,
11
+ PartialRecord
12
+ } from "threepipe"
13
+ import {getShapeParameters, ShapeParameters, ShapeResult, ShapeType, threeToCannon} from "./threeToCannon"
14
+ import {Box, Quaternion as CQuaternion, Shape, Vec3} from "cannon-es"
15
+ import {Cannon3DBodyComponent} from "./Cannon3DBodyComponent.ts";
16
+ import {CannonDebugger} from "./helper.ts";
17
+
18
+ export const typeToCannon = {
19
+ autoBox: ShapeType.BOX,
20
+ autoCylinder: ShapeType.CYLINDER,
21
+ autoSphere: ShapeType.SPHERE,
22
+ autoConvex: ShapeType.HULL,
23
+ autoTrimesh: ShapeType.MESH,
24
+ box: ShapeType.BOX,
25
+ sphere: ShapeType.SPHERE,
26
+ cylinder: ShapeType.CYLINDER,
27
+ convex: ShapeType.HULL,
28
+ trimesh: ShapeType.MESH,
29
+ // plane: ShapeType.PLANE,
30
+ } as const
31
+
32
+ export type CannonShapeType = keyof typeof typeToCannon
33
+
34
+ export const cannonShapeTypes = Object.keys(typeToCannon) as CannonShapeType[]
35
+
36
+ export const cannonShapeTypeParams: PartialRecord<CannonShapeType, ShapeParameters['params']> = {
37
+ box: {x: 1, y: 1, z: 1},
38
+ sphere: {radius: 1},
39
+ cylinder: {radiusTop: 1, radiusBottom: 1, height: 1, segments: 8},
40
+ convex: {vertices: new Float32Array([1, 1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1, 0, 0, -1]), faces: [[0, 1, 2, 3], [4, 0, 3], [4, 3, 2], [4, 2, 1], [4, 1, 0]]},
41
+ trimesh: {vertices: new Float32Array([1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1]), indices: new Uint32Array([0, 1, 2, 0, 3, 1, 0, 2, 3, 1, 3, 2])},
42
+ // not for auto
43
+ }
44
+
45
+ export class Cannon3DShapeComponent extends Object3DComponent {
46
+ static ComponentType = 'Cannon3DShapeComponent'
47
+ static StateProperties: ComponentDefn['StateProperties'] = [{
48
+ key: 'type',
49
+ type: literalStrings(cannonShapeTypes),
50
+ }]
51
+ static EmptyResult: ShapeResult = {shape: new Box(new Vec3(0.025, 0.025, 0.025))}
52
+
53
+ result: ShapeResult = Cannon3DShapeComponent.EmptyResult
54
+ bodyRef: Cannon3DBodyComponent | undefined
55
+ type: CannonShapeType = 'autoBox'
56
+ params: ShapeParameters | null = null
57
+
58
+ constructor() {
59
+ super()
60
+ this.onStateChange('type', () => {
61
+ // const shapeType = typeToCannon[this.shapeType]
62
+ const params = cannonShapeTypeParams[this.type]
63
+ if (params) {
64
+ const params1 = {}
65
+ Object.entries(params).forEach(([k, v]) => {
66
+ (params1 as any)[k] = (v as Array<any>).slice ? (v as Array<any>).slice() : v
67
+ })
68
+ this.params = {
69
+ type: typeToCannon[this.type],
70
+ params: params1 as any,
71
+ offset: new Vec3(),
72
+ orientation: new CQuaternion(),
73
+ }
74
+ } else {
75
+ this.params = null
76
+ }
77
+ if (!this.object) return
78
+ this.refreshShape()
79
+ })
80
+ }
81
+
82
+ // static IsCompatible(_object: IObject3D) { return true }
83
+
84
+ init(object: IObject3D, state: ComponentJSON['state']) {
85
+ super.init(object, state)
86
+ this.refreshShape(false)
87
+ const body = EntityComponentPlugin.GetComponentInParent(object, Cannon3DBodyComponent)
88
+ if (!body) {
89
+ // console.warn('Cannon3DShapeComponent: No Cannon3DBodyComponent found in parent hierarchy')
90
+ } else {
91
+ body.addShape(this)
92
+ }
93
+ this.object.addEventListener('objectUpdate', this.objectUpdate)
94
+ this.object.addEventListener('childadded', this.childadded)
95
+ this.object.addEventListener('childremoved', this.childremoved)
96
+ this.ctx.viewer.addEventListener('preFrame', this.preFrame)
97
+ }
98
+
99
+ destroy(): Record<string, any> {
100
+ if (this.bodyRef) {
101
+ this.bodyRef.removeShape(this, false)
102
+ this.bodyRef = undefined
103
+ }
104
+ this.object.removeEventListener('objectUpdate', this.objectUpdate)
105
+ this.ctx.viewer.removeEventListener('preFrame', this.preFrame)
106
+ this.result = Cannon3DShapeComponent.EmptyResult
107
+ return super.destroy()
108
+ }
109
+
110
+ refreshShape(refreshBody = true) {
111
+ // todo auto refresh on geometry or hierarchy changed if set to auto
112
+ if (!this.object.__cannonShapes) this.object.__cannonShapes = []
113
+
114
+ if(this.result){
115
+ const index = this.object.__cannonShapes.indexOf(this.result)
116
+ if(index !== -1) this.object.__cannonShapes.splice(index, 1)
117
+ }
118
+
119
+ const shapeType = typeToCannon[this.type]
120
+ const shapeParameters = this.params ?? (shapeType ? getShapeParameters(this.object, {type: shapeType}) : null)
121
+
122
+ // console.log('shapeParameters', shapeParameters)
123
+ if (shapeParameters) {
124
+ this.result = threeToCannon(this.object, undefined, shapeParameters)!
125
+ } else {
126
+ this.result = Cannon3DShapeComponent.EmptyResult
127
+ }
128
+
129
+ if (!this.object.__cannonShapes.includes(this.result)) this.object.__cannonShapes.push(this.result)
130
+
131
+ if (refreshBody && this.bodyRef) this.bodyRef.refreshShapes()
132
+
133
+ this.object.setDirty?.()
134
+ this.ctx.plugin(Object3DWidgetsPlugin)?.refreshObject(this.object)
135
+ }
136
+
137
+ private _needsUpdate = false
138
+ objectUpdate = (e: any)=>{
139
+ }
140
+
141
+ childadded = (e: any)=>{
142
+ this._needsUpdate = true
143
+ e.child.addEventListener('objectUpdate', this.objectUpdate)
144
+ e.child.addEventListener('childadded', this.childadded)
145
+ e.child.addEventListener('childremoved', this.childremoved)
146
+ // console.log('childadded', {...e})
147
+ }
148
+ childremoved = (e: any)=>{
149
+ this._needsUpdate = true
150
+ e.child.removeEventListener('objectUpdate', this.objectUpdate)
151
+ e.child.removeEventListener('childadded', this.childadded)
152
+ e.child.removeEventListener('childremoved', this.childremoved)
153
+ // console.log('childremoved', {...e})
154
+ }
155
+
156
+ preFrame = ()=>{
157
+ if(this._needsUpdate){
158
+ this._needsUpdate = false
159
+ this.refreshShape()
160
+ }
161
+ }
162
+ }
163
+
164
+ export class Cannon3DShapeHelper extends AHelperWidget {
165
+ autoUpgradeChildren = false // used elsewhere
166
+ private _shapes: ShapeResult[] = []
167
+ private _meshes = new WeakMap<Shape, Mesh>()
168
+ declare object: IObject3D | undefined
169
+
170
+ private _gp = new Group()
171
+ constructor(object: IObject3D) {
172
+ super(object, false)
173
+
174
+ this.attach(object)
175
+ this.add(this._gp)
176
+ this.visible = false
177
+ }
178
+
179
+ update() {
180
+ super.update()
181
+
182
+ this._gp.scale.setFromMatrixScale(this.matrixWorld)
183
+ this._gp.scale.set(
184
+ 1./this._gp.scale.x,
185
+ 1./this._gp.scale.y,
186
+ 1./this._gp.scale.z
187
+ )
188
+
189
+ const shapes = (this.object?.__cannonShapes || []) as ShapeResult[]
190
+ const added = shapes.filter(s => !this._shapes.includes(s))
191
+ const removed = this._shapes.filter(s => !shapes.includes(s))
192
+ if (!added.length && !removed.length) return
193
+
194
+ for (const shape of removed) {
195
+ const mesh = this._meshes.get(shape.shape)
196
+ if (mesh) {
197
+ this._gp.remove(mesh)
198
+ mesh.geometry.dispose()
199
+ ;(mesh.material as IMaterial).dispose()
200
+ this._meshes.delete(shape.shape)
201
+ }
202
+ }
203
+ this._shapes = []
204
+ for (const shape of shapes) {
205
+ const current = this._meshes.get(shape.shape)
206
+ // update
207
+ const mesh = CannonDebugger.updateMesh(current, shape.shape)
208
+ if (mesh !== current && mesh) {
209
+ this._gp.add(mesh)
210
+ if (current) {
211
+ current.removeFromParent()
212
+ current.geometry.dispose()
213
+ ;(current.material as IMaterial).dispose()
214
+ }
215
+ this._meshes.set(shape.shape, mesh)
216
+ }
217
+ if(mesh){
218
+ if(shape.offset){
219
+ mesh.position.set(shape.offset.x, shape.offset.y, shape.offset.z)
220
+ }
221
+ if(shape.orientation){
222
+ mesh.quaternion.set(shape.orientation.x, shape.orientation.y, shape.orientation.z, shape.orientation.w)
223
+ }
224
+ }
225
+ this._shapes.push(shape)
226
+ }
227
+
228
+ }
229
+
230
+ dispose() {
231
+ this._shapes = []
232
+ this.children.forEach(c => {
233
+ if (c instanceof Mesh) {
234
+ c.geometry.dispose()
235
+ ;(c.material as IMaterial).dispose()
236
+ }
237
+ })
238
+ super.dispose()
239
+ }
240
+
241
+ static Check(obj: IObject3D) {
242
+ return !!EntityComponentPlugin.GetComponentData(obj, Cannon3DShapeComponent)
243
+ }
244
+
245
+ static Create(obj: IObject3D) {
246
+ return new Cannon3DShapeHelper(obj as any)
247
+ }
248
+ }
249
+
250
+ declare module 'threepipe' {
251
+ interface IObject3D{
252
+ /**
253
+ * Shapes associated with this object
254
+ * @internal - for cannon js physics
255
+ */
256
+ ['__cannonShapes']?: ShapeResult[]
257
+ }
258
+ }
@@ -0,0 +1,409 @@
1
+ import {Body, ContactMaterial, Material as CannonMaterial, Shape, World} from "cannon-es"
2
+ import {
3
+ AViewerPluginSync,
4
+ EntityComponentPlugin,
5
+ FrameFadePlugin, generateUUID,
6
+ IObject3D,
7
+ IViewerEvent,
8
+ Matrix4,
9
+ Object3DWidgetsPlugin,
10
+ Quaternion,
11
+ serialize,
12
+ ThreeSerialization,
13
+ ThreeViewer,
14
+ TObject3DComponent,
15
+ TypeSystem,
16
+ uiConfig,
17
+ uiFolderContainer, uiInput, uiMonitor, uiNumber,
18
+ uiToggle,
19
+ Vector3
20
+ } from "threepipe"
21
+ import {Cannon3DShapeComponent, Cannon3DShapeHelper} from "./Cannon3DShapeComponent.ts";
22
+ import {Cannon3DBodyComponent} from "./Cannon3DBodyComponent.ts";
23
+ import {CannonRagdollComponent} from "./CannonRagdollComponent.ts";
24
+
25
+ @uiFolderContainer('Cannon Material')
26
+ export class CannonMaterial2 extends CannonMaterial{
27
+ @uiInput()
28
+ declare name: string
29
+ @uiMonitor()
30
+ uuid: string = generateUUID()
31
+ @uiNumber()
32
+ declare friction
33
+ @uiNumber()
34
+ declare restitution
35
+ }
36
+ ThreeSerialization.MakeSerializable(CannonMaterial as any, 'CannonMaterial', ['friction', 'restitution']) // this is not needed actually, but just in case
37
+ ThreeSerialization.MakeSerializable(CannonMaterial2 as any, 'CannonMaterial', ['name', 'uuid', 'friction', 'restitution']) // this should be after CannonMaterial
38
+ ThreeSerialization.MakeSerializable(ContactMaterial as any, 'CannonContactMaterial', [/* 'materials', */'friction', 'restitution', 'contactEquationStiffness', 'contactEquationRelaxation', 'frictionEquationStiffness', 'frictionEquationRelaxation'])
39
+ TypeSystem.AddClass({
40
+ key: 'CannonMaterial',
41
+ getId(obj: CannonMaterial2) {
42
+ return obj.uuid
43
+ },
44
+ getLabel(obj?: CannonMaterial2) {
45
+ return obj ? obj.name : 'Cannon Material'
46
+ },
47
+ setName(obj: CannonMaterial2, name: string) {
48
+ obj.name = name
49
+ },
50
+ ctor: CannonMaterial2,
51
+ })
52
+
53
+ // uses https://github.com/pmndrs/cannon-es
54
+
55
+ @uiFolderContainer('Physics')
56
+ export class CannonPhysicsPlugin extends AViewerPluginSync {
57
+ public static readonly PluginType = 'CannonPhysicsPlugin'
58
+ private _world: World = new World()
59
+ get world() { return this._world }
60
+ // dependencies: Class<IViewerPlugin<any>>[]
61
+
62
+ @serialize()
63
+ @uiToggle()
64
+ enabled = true
65
+
66
+ static CannonTypes = {
67
+ Body, Shape, World, Material: CannonMaterial, ContactMaterial,
68
+ }
69
+ @uiToggle('Running', (_that: CannonPhysicsPlugin)=>({onChange: ()=>{return}}))
70
+ running = true
71
+
72
+ nextSteps = 0
73
+
74
+ @uiConfig()
75
+ stepPhysics: any = {
76
+ stepCount: 100,
77
+ delta: 0.1,
78
+ step: () => {
79
+ this.nextSteps = this.stepPhysics.stepCount
80
+ },
81
+ }
82
+
83
+ @uiConfig()
84
+ @serialize()
85
+ defaultMaterial = new CannonMaterial2('default')
86
+
87
+ // @uiButton('Make Root Bodies')
88
+ // makeRootBodies = () => {
89
+ // this._viewer?.scene.modelRoot.children.forEach((child) => {
90
+ // this.makeBody(child)
91
+ // })
92
+ // }
93
+
94
+ // stepMultiple = () => {
95
+ // console.log(this.enabled, this.nextSteps)
96
+ // if (this.enabled) return
97
+ // this.nextSteps = 100
98
+ // // this.enabled = true
99
+ // // this._viewer?.doOnce('postFrame', () => {
100
+ // // // Step the physics world
101
+ // // for (let i = 0; i < 100; i++) {
102
+ // // this._world.step(0.1)
103
+ // // }
104
+ // // // this._viewer?.doOnce('postFrame', () => {
105
+ // // // this.enabled = false
106
+ // // // })
107
+ // // })
108
+ // // this._viewer?.setDirty()
109
+ // }
110
+
111
+ constructor(enabled = true, running = false) {
112
+ super()
113
+ this.enabled = enabled
114
+ this.running = running
115
+
116
+ // replaced for UI
117
+ this.defaultMaterial.friction = 0
118
+ this.defaultMaterial.restitution = 0
119
+ this._world.defaultMaterial = this.defaultMaterial
120
+ this._world.defaultContactMaterial.materials = [this.defaultMaterial, this.defaultMaterial]
121
+
122
+ const collisionEv: CollisionEvent = {} as any
123
+ this._world.addEventListener('beginContact', (e: any)=>{
124
+ const compA = this.cannonBodies.get(e.bodyA)
125
+ const compB = this.cannonBodies.get(e.bodyB)
126
+ if(!compA || !compB){
127
+ console.warn('[CannonPhysicsPlugin] Contact bodies not managed by plugin', e.bodyA, e.bodyB)
128
+ return
129
+ }
130
+ collisionEv.object = compB.object
131
+ collisionEv.body = compB
132
+ EntityComponentPlugin.ObjectDispatch(compA.object, 'onBeginContact', collisionEv)
133
+ collisionEv.object = compA.object
134
+ collisionEv.body = compA
135
+ EntityComponentPlugin.ObjectDispatch(compB.object, 'onBeginContact', collisionEv)
136
+ })
137
+ this._world.addEventListener('endContact', (e: any)=>{
138
+ const compA = this.cannonBodies.get(e.bodyA)
139
+ const compB = this.cannonBodies.get(e.bodyB)
140
+ if(!compA || !compB){
141
+ console.warn('[CannonPhysicsPlugin] Contact bodies not managed by plugin', e.bodyA, e.bodyB)
142
+ return
143
+ }
144
+ collisionEv.object = compB.object
145
+ collisionEv.body = compB
146
+ EntityComponentPlugin.ObjectDispatch(compA.object, 'onEndContact', collisionEv)
147
+ collisionEv.object = compA.object
148
+ collisionEv.body = compA
149
+ EntityComponentPlugin.ObjectDispatch(compB.object, 'onEndContact', collisionEv)
150
+ })
151
+ // this._world.addEventListener('beginShapeContact', (e)=>{
152
+ // console.log(e)
153
+ // })
154
+ // this._world.addEventListener('endShapeContact', (e)=>{
155
+ // console.log(e)
156
+ // })
157
+ // this._world.addEventListener(Body.COLLIDE_EVENT_NAME, (e)=>{
158
+ // console.log(e)
159
+ // })
160
+ this._world.gravity.set(0, -9.81, 0)
161
+ // this._world.gravity.set(0, 0, 0)
162
+
163
+ //
164
+ // // Max solver iterations: Use more for better force propagation, but keep in mind that it's not very computationally cheap!
165
+ // this._world.solver.iterations = 20
166
+ //
167
+ // // Tweak contact properties.
168
+ // // Contact stiffness - use to make softer/harder contacts
169
+ // this._world.defaultContactMaterial.contactEquationStiffness = 1e10
170
+ // this._world.defaultContactMaterial.contactEquationRelaxation = 10
171
+ //
172
+
173
+ // coeff of restitution: 0 = no bounce, 1 = perfect bounce
174
+
175
+ // test
176
+
177
+ // const mat = new CannonMaterial('defaultMat')
178
+ // mat.friction = 0.351
179
+ // mat.restitution = 0.63
180
+ // const contactMat = new ContactMaterial(mat, mat, {friction: 0.13, restitution: 0.563})
181
+ //
182
+ // const mjson = ThreeSerialization.Serialize(mat)
183
+ // const mat2 = ThreeSerialization.Deserialize(mjson, null) as CannonMaterial
184
+ // const mat3 = ThreeSerialization.Deserialize(mjson, mat) as CannonMaterial
185
+ // console.log(mjson, mat2, mat3, mat3 === mat)
186
+ //
187
+ // const cmjson = ThreeSerialization.Serialize(contactMat)
188
+ // const contactMat2 = ThreeSerialization.Deserialize(cmjson, null) as ContactMaterial
189
+ // const contactMat3 = ThreeSerialization.Deserialize(cmjson, contactMat) as ContactMaterial
190
+ // console.log(cmjson, contactMat2, contactMat3, contactMat3 === contactMat)
191
+
192
+ }
193
+
194
+ dependencies = [EntityComponentPlugin]
195
+
196
+ // private _bodyMeshMap = new WeakMap<Body, Object3D>()
197
+
198
+ private _v1 = new Vector3()
199
+ private _v2 = new Vector3()
200
+ private _s2 = new Vector3()
201
+ private _q1 = new Quaternion()
202
+ private _q2 = new Quaternion()
203
+ private _m1 = new Matrix4()
204
+ private _m2 = new Matrix4()
205
+ frameFadeToggled = false
206
+
207
+ protected _preFrame = (e: IViewerEvent) => {
208
+ const viewer = this._viewer
209
+ if (!viewer) return
210
+ if(this.isDisabled()) return
211
+ const frameFadePlugin = viewer.getPlugin(FrameFadePlugin)
212
+ // todo use isDisabled
213
+ if (!this.running) {
214
+ if (this.nextSteps > 0) this.running = true
215
+ else {
216
+ if (frameFadePlugin && this.frameFadeToggled) {
217
+ frameFadePlugin.enable(CannonPhysicsPlugin.PluginType)
218
+ this.frameFadeToggled = false
219
+ }
220
+ this._dirty = false
221
+ return
222
+ }
223
+ }
224
+ if (frameFadePlugin && !this.frameFadeToggled) {
225
+ frameFadePlugin.disable(CannonPhysicsPlugin.PluginType)
226
+ }
227
+
228
+ try {
229
+ // Step the physics world
230
+ if (this.nextSteps > 0) {
231
+ for (let i = 0; i < this.stepPhysics.stepCount; i++) {
232
+ this._world.step(this.stepPhysics.delta)
233
+ }
234
+ } else {
235
+ // todo use dt
236
+ // const dt = e.deltaTime
237
+ this._world.fixedStep()
238
+ }
239
+ }catch (e) {
240
+ console.error('[CannonPhysicsPlugin] Error stepping physics world')
241
+ console.error(e)
242
+ }
243
+
244
+ this._dirty = false
245
+ let dirty = false
246
+ // Update the bodies
247
+ for (const bodyC of this.bodyComponents.values()) {
248
+ if (bodyC.mass === 0) continue
249
+ const mesh = bodyC.object
250
+ const body = bodyC.body
251
+ if (!mesh) continue
252
+ dirty = false
253
+ mesh.updateWorldMatrix(true, false)
254
+ mesh.getWorldPosition(this._v2)
255
+ mesh.getWorldQuaternion(this._q2)
256
+ mesh.getWorldScale(this._s2)
257
+ this._v1.copy(body.position)
258
+ if (!dirty && this._v2.manhattanDistanceTo(this._v1) > 0) {
259
+ dirty = true
260
+ // world = parent.local
261
+ // parent-1 . world = local
262
+ }
263
+ this._q1.copy(body.quaternion as any)
264
+ if (!dirty && this._q2.angleTo(this._q1) > 0) {
265
+ dirty = true
266
+ }
267
+ if (dirty) {
268
+ this._m1.compose(this._v1, this._q1, this._s2)
269
+ if (!mesh.parent) {
270
+ // throw new Error('no parent')
271
+ console.error('[CannonPhysicsPlugin] Mesh has no parent, cannot set world transform', mesh)
272
+ }else {
273
+ this._m2.copy(mesh.parent.matrixWorld).invert()
274
+ this._m2.multiply(this._m1)
275
+ this._m2.decompose(this._v1, this._q1, this._s2)
276
+ mesh.position.copy(this._v1)
277
+ mesh.quaternion.copy(this._q1)
278
+ mesh.setDirty && mesh.setDirty({change: 'transform', source: 'CannonPhysicsPlugin'})
279
+ }
280
+ }
281
+ }
282
+ this._dirty = dirty
283
+ viewer.setDirty()
284
+ // viewer.scene.setDirty({sceneUpdate: false})
285
+ viewer.renderManager.resetShadows()
286
+
287
+ if (this.nextSteps > 0) {
288
+ this.running = false
289
+ this.nextSteps = 0
290
+ }
291
+ }
292
+
293
+ private _onRemove: (()=>void)[] = []
294
+
295
+ async onAdded(viewer: ThreeViewer): Promise<
296
+ void> {
297
+ super.onAdded(viewer)
298
+
299
+ const offUpdate = viewer.on('preFrame', {
300
+ order: -1, // before most things
301
+ callback: this._preFrame,
302
+ })
303
+ if (offUpdate) this._onRemove.push(offUpdate)
304
+
305
+ Object.values(this.componentTypes).forEach((compType) => {
306
+ viewer.getPlugin(EntityComponentPlugin)?.addComponentType(compType)
307
+ })
308
+
309
+ viewer.object3dManager.getObjects().forEach(object=>this._objectAdd({object}))
310
+ viewer.object3dManager.addEventListener('objectAdd', this._objectAdd)
311
+ viewer.object3dManager.addEventListener('objectRemove', this._objectRemove)
312
+
313
+ viewer.forPlugin(Object3DWidgetsPlugin, (wplugin) => {
314
+ wplugin.helpers.push(Cannon3DShapeHelper)
315
+ }, (wplugin)=>{
316
+ const i = wplugin.helpers.indexOf(Cannon3DShapeHelper)
317
+ if (i >= 0) wplugin.helpers.splice(i, 1)
318
+ })
319
+
320
+ // viewer.scene.addEventListener('update', (e) => {
321
+ // this._bodyMeshMap.forEach((mesh, body) => {
322
+ // body.velocity.set(0, 0, 0)
323
+ // body.angularVelocity.set(0, 0, 0)
324
+ // })
325
+ // })
326
+ }
327
+
328
+ private _objectAdd = (e: {object?: IObject3D})=>{
329
+ const obj = e.object
330
+ // console.log('pbject add', obj?.name, obj?.userData, obj)
331
+ if (obj?.userData?.physicsMass === undefined) return
332
+ const ecs = this._viewer?.getPlugin(EntityComponentPlugin)
333
+ if (!ecs) return
334
+ const existing = EntityComponentPlugin.GetComponentData(obj, this.componentTypes.body)
335
+ if (existing) return
336
+ // const pBody = ecs.addComponent(obj, this.componentTypes.body).component
337
+ // const pShape = ecs.addComponent(obj, this.componentTypes.shape).component
338
+ // if (!pBody || !pShape) return
339
+ // pBody.mass = typeof obj.userData.physicsMass === 'number' ? obj.userData.physicsMass : 0
340
+ // if (obj.userData.physicsBodyType !== undefined) {
341
+ // if (typeof obj.userData.physicsBodyType === 'string' && physicsBodyType.includes(obj.userData.physicsBodyType as PhysicsBodyType)) {
342
+ // pBody.type = obj.userData.physicsBodyType as PhysicsBodyType
343
+ // }
344
+ // }
345
+ // if (obj.userData.physicsShape !== undefined) {
346
+ // if (typeof obj.userData.physicsShape === 'string' && cannonShapeTypes.includes(obj.userData.physicsShape as CannonShapeType)) {
347
+ // pShape.type = obj.userData.physicsShape as CannonShapeType
348
+ // }
349
+ // }
350
+ }
351
+
352
+ private _objectRemove = (e: {object?: IObject3D})=>{
353
+ const obj = e.object
354
+ if (!obj?.userData?.physicsMass === undefined) return
355
+
356
+ }
357
+
358
+ onRemove(viewer: ThreeViewer) {
359
+ this._onRemove.forEach(off=>off())
360
+ Object.values(this.componentTypes).forEach((compType) => {
361
+ viewer.getPlugin(EntityComponentPlugin)?.removeComponentType(compType)
362
+ })
363
+ viewer.object3dManager.removeEventListener('objectAdd', this._objectAdd)
364
+ viewer.object3dManager.removeEventListener('objectRemove', this._objectRemove)
365
+ viewer.object3dManager.getObjects().forEach(object=>this._objectRemove({object}))
366
+
367
+ // todo assert all bodies removed?
368
+
369
+ super.onRemove(viewer)
370
+ }
371
+
372
+ readonly componentTypes = {
373
+ body: Cannon3DBodyComponent,
374
+ shape: Cannon3DShapeComponent,
375
+ ragdoll: CannonRagdollComponent,
376
+ } satisfies Record<string, TObject3DComponent>
377
+
378
+ bodyComponents = new Set<Cannon3DBodyComponent>()
379
+ cannonBodies = new WeakMap<Body, Cannon3DBodyComponent>()
380
+ addBody(body: Cannon3DBodyComponent) {
381
+ if (this.bodyComponents.has(body)) return
382
+ this.bodyComponents.add(body)
383
+ this._world.addBody(body.body)
384
+ this.cannonBodies.set(body.body, body)
385
+ // this._bodyMeshMap.set(body.body, body.object)
386
+ // this._viewer?.setDirty()
387
+ }
388
+ removeBody(body: Cannon3DBodyComponent) {
389
+ if (!this.bodyComponents.has(body)) return
390
+ this.bodyComponents.delete(body)
391
+ this._world.removeBody(body.body)
392
+ this.cannonBodies.delete(body.body)
393
+ // this._bodyMeshMap.delete(body.body)
394
+ // this._viewer?.setDirty()
395
+ }
396
+
397
+ }
398
+
399
+ export interface CollisionEvent{
400
+ object: IObject3D
401
+ body: Cannon3DBodyComponent
402
+ }
403
+ declare module 'threepipe'{
404
+ interface Object3DComponent{
405
+ onBeginContact?(collisionEvent: any):void
406
+ onEndContact?(collisionEvent: any):void
407
+
408
+ }
409
+ }