@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,320 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComponentDefn,
|
|
3
|
+
ComponentJSON,
|
|
4
|
+
IObject3D,
|
|
5
|
+
IObject3DEventMap,
|
|
6
|
+
ISceneEventMap,
|
|
7
|
+
literalStrings,
|
|
8
|
+
Object3DComponent,
|
|
9
|
+
Vector3,
|
|
10
|
+
ViewerEventMap
|
|
11
|
+
} from "threepipe"
|
|
12
|
+
|
|
13
|
+
export const uiPositionMode = ['world', 'screen', 'viewport'] as const
|
|
14
|
+
export type UiPositionMode = typeof uiPositionMode[number]
|
|
15
|
+
|
|
16
|
+
export class HtmlUiComponent extends Object3DComponent {
|
|
17
|
+
static ComponentType = 'HtmlUiComponent'
|
|
18
|
+
static StateProperties: ComponentDefn['StateProperties'] = [
|
|
19
|
+
'enabled',
|
|
20
|
+
{
|
|
21
|
+
key: 'htmlData',
|
|
22
|
+
type: 'string',
|
|
23
|
+
uiConfig: {
|
|
24
|
+
multiline: true,
|
|
25
|
+
rows: 5,
|
|
26
|
+
autoResize: true,
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
'width',
|
|
30
|
+
'height',
|
|
31
|
+
'scrollable',
|
|
32
|
+
'interactable',
|
|
33
|
+
{
|
|
34
|
+
key: 'positionMode',
|
|
35
|
+
type: literalStrings(uiPositionMode),
|
|
36
|
+
},
|
|
37
|
+
'offsetX',
|
|
38
|
+
'offsetY',
|
|
39
|
+
'zIndex',
|
|
40
|
+
'visible',
|
|
41
|
+
'appendToBody'
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
declare element: HTMLDivElement
|
|
45
|
+
|
|
46
|
+
enabled = true
|
|
47
|
+
|
|
48
|
+
// HTML content
|
|
49
|
+
htmlData = '<div>Hello World</div>'
|
|
50
|
+
|
|
51
|
+
// Size properties
|
|
52
|
+
width = -1 // in pixels
|
|
53
|
+
height = -1 // in pixels
|
|
54
|
+
|
|
55
|
+
// Behavior properties
|
|
56
|
+
scrollable = false
|
|
57
|
+
interactable = true
|
|
58
|
+
visible = true
|
|
59
|
+
appendToBody = false // false = append to viewer.container (default), true = append to document.body
|
|
60
|
+
|
|
61
|
+
// Position properties
|
|
62
|
+
positionMode: UiPositionMode = 'world' // world follows 3D object, screen is fixed, viewport is relative to viewport
|
|
63
|
+
offsetX = 0 // pixel offset from calculated position
|
|
64
|
+
offsetY = 0 // pixel offset from calculated position
|
|
65
|
+
zIndex = 1000 // CSS z-index
|
|
66
|
+
|
|
67
|
+
private _worldPosition = new Vector3()
|
|
68
|
+
private _animationFrameId?: number
|
|
69
|
+
|
|
70
|
+
constructor() {
|
|
71
|
+
super()
|
|
72
|
+
|
|
73
|
+
this.onStateChange('htmlData', (v) => {
|
|
74
|
+
if (!this.element) return
|
|
75
|
+
this.element.innerHTML = v
|
|
76
|
+
// this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
this.onStateChange('width', (v) => {
|
|
80
|
+
if (!this.element) return
|
|
81
|
+
this.element.style.width = v < 0 ? 'auto' : `${v}px`
|
|
82
|
+
this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
this.onStateChange('height', (v) => {
|
|
86
|
+
if (!this.element) return
|
|
87
|
+
this.element.style.height = v < 0 ? 'auto' : `${v}px`
|
|
88
|
+
this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
this.onStateChange('scrollable', (v) => {
|
|
92
|
+
if (!this.element) return
|
|
93
|
+
this.element.style.overflow = v ? 'auto' : 'hidden'
|
|
94
|
+
this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
this.onStateChange('interactable', (v) => {
|
|
98
|
+
if (!this.element) return
|
|
99
|
+
this.element.style.pointerEvents = v ? 'auto' : 'none'
|
|
100
|
+
this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
this.onStateChange('visible', (v) => {
|
|
104
|
+
if (!this.element) return
|
|
105
|
+
this.element.style.display = v ? 'block' : 'none'
|
|
106
|
+
this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
this.onStateChange('positionMode', () => {
|
|
110
|
+
if (!this.element) return
|
|
111
|
+
this._updateElementPosition()
|
|
112
|
+
this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
this.onStateChange('offsetX', () => {
|
|
116
|
+
if (!this.element) return
|
|
117
|
+
this._updateElementPosition()
|
|
118
|
+
this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
this.onStateChange('offsetY', () => {
|
|
122
|
+
if (!this.element) return
|
|
123
|
+
this._updateElementPosition()
|
|
124
|
+
this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
this.onStateChange('zIndex', (v) => {
|
|
128
|
+
if (!this.element) return
|
|
129
|
+
this.element.style.zIndex = `${v}`
|
|
130
|
+
this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
this.onStateChange('appendToBody', (v) => {
|
|
134
|
+
if (!this.element) return
|
|
135
|
+
// Remove from current parent
|
|
136
|
+
if (this.element.parentNode) {
|
|
137
|
+
this.element.parentNode.removeChild(this.element)
|
|
138
|
+
}
|
|
139
|
+
// Append to new container
|
|
140
|
+
const container = v ? document.body : (this.ctx?.viewer?.container || document.body)
|
|
141
|
+
container.appendChild(this.element)
|
|
142
|
+
this.object.setDirty?.({source: 'HtmlUiComponent'})
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private _objectUpdate = (e: IObject3DEventMap['objectUpdate'] | ISceneEventMap['mainCameraUpdate']) => {
|
|
147
|
+
if (e.source === 'HtmlUiComponent') return
|
|
148
|
+
const changeKey = e?.change ?? e?.key
|
|
149
|
+
const update = !changeKey || changeKey === 'position' || changeKey === 'transform' || changeKey === 'quaternion' || changeKey === 'visible' || changeKey === 'controls'
|
|
150
|
+
if (update && this.positionMode === 'world') {
|
|
151
|
+
this._updateElementPosition()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
init(object: IObject3D, state: ComponentJSON['state']) {
|
|
156
|
+
super.init(object, state)
|
|
157
|
+
|
|
158
|
+
// Create the HTML element
|
|
159
|
+
this.element = document.createElement('div')
|
|
160
|
+
this.element.className = 'threepipe-html-ui'
|
|
161
|
+
|
|
162
|
+
// Apply initial styles
|
|
163
|
+
this.element.style.position = 'absolute'
|
|
164
|
+
this.element.style.width = this.width < 0 ? 'auto' : `${this.width}px`
|
|
165
|
+
this.element.style.height = this.height < 0 ? 'auto' : `${this.height}px`
|
|
166
|
+
this.element.style.overflow = this.scrollable ? 'auto' : 'hidden'
|
|
167
|
+
this.element.style.pointerEvents = this.interactable ? 'auto' : 'none'
|
|
168
|
+
this.element.style.zIndex = `${this.zIndex}`
|
|
169
|
+
this.element.style.display = this.visible ? 'block' : 'none'
|
|
170
|
+
this.element.style.boxSizing = 'border-box'
|
|
171
|
+
|
|
172
|
+
// Set HTML content
|
|
173
|
+
this.element.innerHTML = this.htmlData
|
|
174
|
+
|
|
175
|
+
// Add to document or viewer container
|
|
176
|
+
const container = this.appendToBody ? document.body : (this.ctx?.viewer?.container || document.body)
|
|
177
|
+
container.appendChild(this.element)
|
|
178
|
+
|
|
179
|
+
// Listen for object updates
|
|
180
|
+
object.addEventListener('objectUpdate', this._objectUpdate)
|
|
181
|
+
this.ctx.viewer.scene.addEventListener('mainCameraUpdate', this._objectUpdate)
|
|
182
|
+
|
|
183
|
+
// Start position tracking for world-positioned elements
|
|
184
|
+
this._updateElementPosition()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
destroy(): Record<string, any> {
|
|
188
|
+
// Stop position tracking
|
|
189
|
+
this._stopPositionTracking()
|
|
190
|
+
|
|
191
|
+
// Remove event listener
|
|
192
|
+
this.object.removeEventListener('objectUpdate', this._objectUpdate)
|
|
193
|
+
this.ctx.viewer.scene.removeEventListener('mainCameraUpdate', this._objectUpdate)
|
|
194
|
+
|
|
195
|
+
// Remove element from DOM
|
|
196
|
+
if (this.element && this.element.parentNode) {
|
|
197
|
+
this.element.parentNode.removeChild(this.element)
|
|
198
|
+
}
|
|
199
|
+
this.element = null as any
|
|
200
|
+
|
|
201
|
+
return super.destroy()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private _stopPositionTracking() {
|
|
205
|
+
if (this._animationFrameId !== undefined) {
|
|
206
|
+
cancelAnimationFrame(this._animationFrameId)
|
|
207
|
+
this._animationFrameId = undefined
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
preFrame({resized}: ViewerEventMap["preFrame"]): boolean | void {
|
|
212
|
+
if(!this.element || !this.enabled) return
|
|
213
|
+
// world - on camera change, canvas size change, object transform change
|
|
214
|
+
// screen - no change
|
|
215
|
+
// viewport - on canvas size change
|
|
216
|
+
if(this.positionMode === 'screen') return
|
|
217
|
+
if(!resized && this.positionMode === 'viewport') return
|
|
218
|
+
if(!resized && this.positionMode === 'world') return // todo: tracked separately
|
|
219
|
+
this._updateElementPosition()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private _updateElementPosition() {
|
|
223
|
+
if (!this.element) return
|
|
224
|
+
|
|
225
|
+
switch (this.positionMode) {
|
|
226
|
+
case 'world':
|
|
227
|
+
this._updateWorldPosition()
|
|
228
|
+
break
|
|
229
|
+
case 'screen':
|
|
230
|
+
this._updateScreenPosition()
|
|
231
|
+
break
|
|
232
|
+
case 'viewport':
|
|
233
|
+
this._updateViewportPosition()
|
|
234
|
+
break
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private _updateWorldPosition() {
|
|
239
|
+
// Get the world position of the object
|
|
240
|
+
this.object.getWorldPosition(this._worldPosition)
|
|
241
|
+
|
|
242
|
+
// Project to screen space
|
|
243
|
+
const camera = this.ctx?.viewer?.scene?.mainCamera
|
|
244
|
+
if (!camera) return
|
|
245
|
+
|
|
246
|
+
const canvas = this.ctx?.viewer?.canvas
|
|
247
|
+
if (!canvas) return
|
|
248
|
+
|
|
249
|
+
// Clone position and project it
|
|
250
|
+
const projected = this._worldPosition.clone()
|
|
251
|
+
projected.project(camera)
|
|
252
|
+
|
|
253
|
+
// Convert to screen coordinates
|
|
254
|
+
const x = (projected.x * 0.5 + 0.5) * canvas.clientWidth
|
|
255
|
+
const y = (projected.y * -0.5 + 0.5) * canvas.clientHeight
|
|
256
|
+
|
|
257
|
+
// Apply position with offset
|
|
258
|
+
this.element.style.left = `${x + this.offsetX}px`
|
|
259
|
+
this.element.style.top = `${y + this.offsetY}px`
|
|
260
|
+
this.element.style.transform = 'translate(-50%, -50%)' // Center on point
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private _updateScreenPosition() {
|
|
264
|
+
// Fixed screen position
|
|
265
|
+
this.element.style.left = `${this.offsetX}px`
|
|
266
|
+
this.element.style.top = `${this.offsetY}px`
|
|
267
|
+
this.element.style.transform = 'none'
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
private _updateViewportPosition() {
|
|
271
|
+
// Viewport-relative position (percentage-based)
|
|
272
|
+
const canvas = this.ctx?.viewer?.canvas
|
|
273
|
+
if (!canvas) return
|
|
274
|
+
|
|
275
|
+
const x = (this.offsetX / 100) * canvas.clientWidth
|
|
276
|
+
const y = (this.offsetY / 100) * canvas.clientHeight
|
|
277
|
+
|
|
278
|
+
this.element.style.left = `${x}px`
|
|
279
|
+
this.element.style.top = `${y}px`
|
|
280
|
+
this.element.style.transform = 'none'
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Update the HTML content programmatically
|
|
285
|
+
*/
|
|
286
|
+
setHtml(html: string) {
|
|
287
|
+
this.htmlData = html
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Set the position
|
|
292
|
+
*/
|
|
293
|
+
setPosition(x: number, y: number) {
|
|
294
|
+
this.offsetX = x
|
|
295
|
+
this.offsetY = y
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Set the size
|
|
300
|
+
*/
|
|
301
|
+
setSize(width: number, height: number) {
|
|
302
|
+
this.width = width
|
|
303
|
+
this.height = height
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Show the element
|
|
308
|
+
*/
|
|
309
|
+
show() {
|
|
310
|
+
this.visible = true
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Hide the element
|
|
315
|
+
*/
|
|
316
|
+
hide() {
|
|
317
|
+
this.visible = false
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { Body, Vec3, Quaternion as CQuaternion } from "cannon-es"
|
|
2
|
+
import {
|
|
3
|
+
ComponentDefn,
|
|
4
|
+
ComponentJSON, EntityComponentPlugin,
|
|
5
|
+
IObject3D,
|
|
6
|
+
IObject3DEventMap,
|
|
7
|
+
literalStrings,
|
|
8
|
+
Object3DComponent, Quaternion, Vector3
|
|
9
|
+
} from "threepipe"
|
|
10
|
+
import {Cannon3DShapeComponent} from "./Cannon3DShapeComponent.ts";
|
|
11
|
+
import {CannonMaterial2, CannonPhysicsPlugin} from "./CannonPhysicsPlugin.ts";
|
|
12
|
+
|
|
13
|
+
export const physicsBodyType = ['static', 'dynamic', 'kinematic'] as const
|
|
14
|
+
export type PhysicsBodyType = typeof physicsBodyType[number]
|
|
15
|
+
|
|
16
|
+
export class Cannon3DBodyComponent extends Object3DComponent {
|
|
17
|
+
static ComponentType = 'Cannon3DBodyComponent'
|
|
18
|
+
static StateProperties: ComponentDefn['StateProperties'] = ['mass', {
|
|
19
|
+
key: 'type',
|
|
20
|
+
type: literalStrings(physicsBodyType),
|
|
21
|
+
}, 'isTrigger', 'material']
|
|
22
|
+
|
|
23
|
+
declare body: Body
|
|
24
|
+
|
|
25
|
+
mass = 1
|
|
26
|
+
type: PhysicsBodyType = 'dynamic'
|
|
27
|
+
isTrigger = false
|
|
28
|
+
material: CannonMaterial2 = new CannonMaterial2()
|
|
29
|
+
|
|
30
|
+
private _typeToCannon(type: PhysicsBodyType) {
|
|
31
|
+
if (type === 'static') return Body.STATIC
|
|
32
|
+
else if (type === 'dynamic') return Body.DYNAMIC
|
|
33
|
+
else if (type === 'kinematic') return Body.KINEMATIC
|
|
34
|
+
return Body.STATIC
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
updateMassProperties(){
|
|
38
|
+
if (!this.body) return
|
|
39
|
+
try {
|
|
40
|
+
this.body.updateMassProperties()
|
|
41
|
+
}catch (e) {
|
|
42
|
+
console.error('[Cannon3DBodyComponent] updateMassProperties error', e)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
updateBoundingRadius(){
|
|
46
|
+
if (!this.body) return
|
|
47
|
+
try {
|
|
48
|
+
this.body.updateBoundingRadius()
|
|
49
|
+
}catch (e) {
|
|
50
|
+
console.error('[Cannon3DBodyComponent] updateBoundingRadius error', e)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
constructor() {
|
|
56
|
+
super()
|
|
57
|
+
this.onStateChange('mass', (v)=>{
|
|
58
|
+
if (!this.body) return
|
|
59
|
+
this.body.mass = v
|
|
60
|
+
this.updateMassProperties()
|
|
61
|
+
this.object.setDirty?.({source: 'Cannon3DBodyComponent'})
|
|
62
|
+
})
|
|
63
|
+
this.onStateChange('type', (v)=>{
|
|
64
|
+
if (!this.body) return
|
|
65
|
+
this.body.type = this._typeToCannon(v)
|
|
66
|
+
this.updateMassProperties()
|
|
67
|
+
this.object.setDirty?.({source: 'Cannon3DBodyComponent'})
|
|
68
|
+
})
|
|
69
|
+
this.onStateChange('isTrigger', (v)=>{
|
|
70
|
+
if (!this.body) return
|
|
71
|
+
this.body.isTrigger = v
|
|
72
|
+
this.object.setDirty?.({source: 'Cannon3DBodyComponent'})
|
|
73
|
+
})
|
|
74
|
+
this.onStateChange('material', (v)=>{
|
|
75
|
+
if (!this.body) return
|
|
76
|
+
this.body.material = v
|
|
77
|
+
this.uiConfig?.uiRefresh?.()
|
|
78
|
+
this.object.setDirty?.({source: 'Cannon3DBodyComponent'})
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private _objectUpdate = (e: IObject3DEventMap['objectUpdate'])=>{
|
|
83
|
+
if (e.source === 'Cannon3DBodyComponent' || e.source === 'CannonPhysicsPlugin') return
|
|
84
|
+
const changeKey = e?.change ?? e?.key
|
|
85
|
+
const update = !changeKey || changeKey === 'position' || changeKey === 'transform' || changeKey === 'quaternion'
|
|
86
|
+
if (update) this.resetTransform(changeKey)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
init(object: IObject3D, state: ComponentJSON['state']) {
|
|
90
|
+
super.init(object, state)
|
|
91
|
+
const cannon = this.ctx.plugin(CannonPhysicsPlugin)
|
|
92
|
+
this.body = new Body()
|
|
93
|
+
this.body.material = this.material
|
|
94
|
+
this.body.mass = this.mass
|
|
95
|
+
this.body.type = this._typeToCannon(this.type)
|
|
96
|
+
this.body.isTrigger = this.isTrigger
|
|
97
|
+
this.updateMassProperties()
|
|
98
|
+
this.resetTransform()
|
|
99
|
+
object.addEventListener('objectUpdate', this._objectUpdate)
|
|
100
|
+
cannon.addBody(this)
|
|
101
|
+
|
|
102
|
+
object.traverseModels && object.traverseModels(m=>{
|
|
103
|
+
EntityComponentPlugin.GetComponents(m, Cannon3DShapeComponent).forEach(sh=>{
|
|
104
|
+
this.addShape(sh, false)
|
|
105
|
+
})
|
|
106
|
+
}, {widgets: false, visible: false})
|
|
107
|
+
if (this.shapeRefs.length) {
|
|
108
|
+
this.refreshShapes()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
destroy(): Record<string, any> {
|
|
114
|
+
this.shapeRefs.forEach(shape=>{
|
|
115
|
+
this.removeShape(shape, false)
|
|
116
|
+
})
|
|
117
|
+
this.refreshShapes(false)
|
|
118
|
+
|
|
119
|
+
this.object.removeEventListener('objectUpdate', this._objectUpdate)
|
|
120
|
+
const cannon = this.ctx.plugin(CannonPhysicsPlugin)
|
|
121
|
+
cannon.removeBody(this)
|
|
122
|
+
this.body = null as any
|
|
123
|
+
return super.destroy()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// update({time}: IAnimationLoopEvent) {
|
|
127
|
+
//
|
|
128
|
+
// }
|
|
129
|
+
|
|
130
|
+
resetTransform(changeKey?: string) {
|
|
131
|
+
// const changeKey = e?.change ?? e?.key
|
|
132
|
+
if (!changeKey || changeKey === 'position' || changeKey === 'transform')
|
|
133
|
+
this.body.position.set(...this.object.getWorldPosition(new Vector3()).toArray())
|
|
134
|
+
if (!changeKey || changeKey === 'quaternion' || changeKey === 'transform')
|
|
135
|
+
this.body.quaternion.set(...(this.object.getWorldQuaternion(new Quaternion()).toArray() as [number, number, number, number]))
|
|
136
|
+
this.body.angularVelocity.set(0, 0, 0)
|
|
137
|
+
this.body.velocity.set(0, 0, 0)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// static IsCompatible(_object: IObject3D) { return true }
|
|
141
|
+
|
|
142
|
+
shapeRefs: Cannon3DShapeComponent[] = []
|
|
143
|
+
addShape(shape: Cannon3DShapeComponent, refresh = true) {
|
|
144
|
+
if (!this.shapeRefs.includes(shape)) {
|
|
145
|
+
if (shape.bodyRef && shape.bodyRef !== this) {
|
|
146
|
+
// ignore
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
this.shapeRefs.push(shape)
|
|
150
|
+
shape.bodyRef = this
|
|
151
|
+
if (refresh) this.refreshShapes()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
removeShape(shape: Cannon3DShapeComponent, refresh = true) {
|
|
155
|
+
const index = this.shapeRefs.indexOf(shape)
|
|
156
|
+
if (index !== -1) {
|
|
157
|
+
if (shape.bodyRef === this) shape.bodyRef = undefined
|
|
158
|
+
this.shapeRefs.splice(index, 1)
|
|
159
|
+
if (refresh) this.refreshShapes()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
refreshShapes(update = true) {
|
|
163
|
+
let changed = false
|
|
164
|
+
for (const s of this.body.shapes) {
|
|
165
|
+
if (!this.shapeRefs.find(sh=>sh.result.shape === s)) {
|
|
166
|
+
const index = this.body.shapes.indexOf(s)
|
|
167
|
+
if (index !== -1) {
|
|
168
|
+
this.body.shapes.splice(index, 1)
|
|
169
|
+
this.body.shapeOffsets.splice(index, 1)
|
|
170
|
+
this.body.shapeOrientations.splice(index, 1)
|
|
171
|
+
s.body = null
|
|
172
|
+
changed = true
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const bodyObj = this.object
|
|
177
|
+
bodyObj.updateMatrixWorld(true)
|
|
178
|
+
const bodyWorldPos = bodyObj.getWorldPosition(new Vector3())
|
|
179
|
+
const bodyWorldQuat = bodyObj.getWorldQuaternion(new Quaternion())
|
|
180
|
+
for (const s of this.shapeRefs) {
|
|
181
|
+
// s.bodyRef = this
|
|
182
|
+
if (!this.body.shapes.includes(s.result.shape)) {
|
|
183
|
+
const shape = s.result.shape
|
|
184
|
+
this.body.shapes.push(shape)
|
|
185
|
+
const offset = new Vec3()
|
|
186
|
+
const quat = new CQuaternion()
|
|
187
|
+
if(s.result.offset){
|
|
188
|
+
offset.x += s.result.offset.x
|
|
189
|
+
offset.y += s.result.offset.y
|
|
190
|
+
offset.z += s.result.offset.z
|
|
191
|
+
}
|
|
192
|
+
if(s.result.orientation){
|
|
193
|
+
quat.x = s.result.orientation.x
|
|
194
|
+
quat.y = s.result.orientation.y
|
|
195
|
+
quat.z = s.result.orientation.z
|
|
196
|
+
quat.w = s.result.orientation.w
|
|
197
|
+
}
|
|
198
|
+
const shapeObj = s.object
|
|
199
|
+
if(bodyObj !== shapeObj){
|
|
200
|
+
shapeObj.updateMatrixWorld(true)
|
|
201
|
+
const worldPos = shapeObj.getWorldPosition(new Vector3())
|
|
202
|
+
offset.x += worldPos.x - bodyWorldPos.x
|
|
203
|
+
offset.y += worldPos.y - bodyWorldPos.y
|
|
204
|
+
offset.z += worldPos.z - bodyWorldPos.z
|
|
205
|
+
const worldQuat = shapeObj.getWorldQuaternion(new Quaternion())
|
|
206
|
+
const relQuat = worldQuat.multiply(bodyWorldQuat.invert())
|
|
207
|
+
// todo we need to multiply here
|
|
208
|
+
quat.x = relQuat.x
|
|
209
|
+
quat.y = relQuat.y
|
|
210
|
+
quat.z = relQuat.z
|
|
211
|
+
quat.w = relQuat.w
|
|
212
|
+
}
|
|
213
|
+
this.body.shapeOffsets.push(offset)
|
|
214
|
+
this.body.shapeOrientations.push(quat)
|
|
215
|
+
shape.body = this.body
|
|
216
|
+
changed = true
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (update && changed) {
|
|
220
|
+
this.updateMassProperties()
|
|
221
|
+
this.updateBoundingRadius()
|
|
222
|
+
|
|
223
|
+
this.body.aabbNeedsUpdate = true
|
|
224
|
+
this.object.setDirty?.({source: 'Cannon3DBodyComponent'})
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|