Needle Engine

Changes between version 3.27.2-beta and 3.27.3-beta
Files changed (9) hide show
  1. src/engine-components/api.ts +2 -0
  2. src/engine-components/Component.ts +8 -45
  3. src/engine/engine_instancing.ts +21 -3
  4. src/engine/engine_physics.ts +59 -20
  5. src/engine-components/ui/Image.ts +1 -1
  6. src/engine-components/ParticleSystem.ts +54 -17
  7. src/engine/codegen/register_types.ts +2 -2
  8. src/engine-components/Renderer.ts +39 -3
  9. src/engine-components/RigidBody.ts +51 -15
src/engine-components/api.ts CHANGED
@@ -18,3 +18,5 @@
18
18
  import "./CameraUtils.js"
19
19
  import "./AnimationUtils.js"
20
20
 
21
+ export { ParticleSystemBaseBehaviour, type QParticle, type QParticleBehaviour } from "./ParticleSystem.js"
22
+
src/engine-components/Component.ts CHANGED
@@ -572,23 +572,8 @@
572
572
  }
573
573
  }
574
574
 
575
- // TODO move this to threeutils
576
- // we need a copy for modifying the values to local space
577
- private static _worldPositionBuffer: Vector3 = new Vector3();
578
- private static _worldQuaternionBuffer: Quaternion = new Quaternion();
579
- private static _worldEulerBuffer: Euler = new Euler();
580
-
581
- private _worldPosition: Vector3 | undefined = undefined;
582
- private _worldQuaternion: Quaternion | undefined = undefined;
583
- private static _tempQuaternionBuffer2: Quaternion = new Quaternion();
584
- private _worldEuler: Euler | undefined = undefined;
585
- private _worldRotation: Vector3 | undefined = undefined;
586
-
587
575
  get worldPosition(): Vector3 {
588
- if (!this._worldPosition) this._worldPosition = new Vector3();
589
- threeutils.getWorldPosition(this.gameObject, this._worldPosition);
590
- // this.gameObject.getWorldPosition(this._worldPosition);
591
- return this._worldPosition;
576
+ return threeutils.getWorldPosition(this.gameObject);
592
577
  }
593
578
 
594
579
  set worldPosition(val: Vector3) {
@@ -596,48 +581,33 @@
596
581
  }
597
582
 
598
583
  setWorldPosition(x: number, y: number, z: number) {
599
- Component._worldPositionBuffer.set(x, y, z);
600
- this.worldPosition = Component._worldPositionBuffer;
584
+ threeutils.setWorldPositionXYZ(this.gameObject, x, y, z);
601
585
  }
602
586
 
603
587
 
604
588
  get worldQuaternion(): Quaternion {
605
- if (!this._worldQuaternion) this._worldQuaternion = new Quaternion();
606
- return threeutils.getWorldQuaternion(this.gameObject, this._worldQuaternion);
589
+ return threeutils.getWorldQuaternion(this.gameObject);
607
590
  }
608
591
  set worldQuaternion(val: Quaternion) {
609
592
  threeutils.setWorldQuaternion(this.gameObject, val);
610
593
  }
611
594
  setWorldQuaternion(x: number, y: number, z: number, w: number) {
612
- Component._worldQuaternionBuffer.set(x, y, z, w);
613
- this.worldQuaternion = Component._worldQuaternionBuffer;
595
+ threeutils.setWorldQuaternionXYZW(this.gameObject, x, y, z, w);
614
596
  }
615
597
 
616
-
617
598
  // world euler (in radians)
618
599
  get worldEuler(): Euler {
619
- if (!this._worldEuler) this._worldEuler = new Euler();
620
- this._worldEuler.setFromQuaternion(this.worldQuaternion);
621
- return this._worldEuler;
600
+ return threeutils.getWorldEuler(this.gameObject);
622
601
  }
623
602
 
624
603
  // world euler (in radians)
625
604
  set worldEuler(val: Euler) {
626
- if (!this._worldQuaternion) this._worldQuaternion = new Quaternion();
627
- this._worldQuaternion?.setFromEuler(val);
628
- this.worldQuaternion = this._worldQuaternion;
605
+ threeutils.setWorldEuler(this.gameObject, val);
629
606
  }
630
607
 
631
608
  // returns rotation in degrees
632
609
  get worldRotation(): Vector3 {
633
- const rot = this.worldEuler;
634
- if (!this._worldRotation) this._worldRotation = new Vector3();
635
- const wr = this._worldRotation;
636
- wr.set(rot.x, rot.y, rot.z);
637
- wr.x = Mathf.toDegrees(wr.x);
638
- wr.y = Mathf.toDegrees(wr.y);
639
- wr.z = Mathf.toDegrees(wr.z);
640
- return wr;
610
+ return this.gameObject.worldRotation;;
641
611
  }
642
612
 
643
613
  set worldRotation(val: Vector3) {
@@ -645,14 +615,7 @@
645
615
  }
646
616
 
647
617
  setWorldRotation(x: number, y: number, z: number, degrees: boolean = true) {
648
- if (degrees) {
649
- x = Mathf.toRadians(x);
650
- y = Mathf.toRadians(y);
651
- z = Mathf.toRadians(z);
652
- }
653
- Component._worldEulerBuffer.set(x, y, z);
654
- Component._worldQuaternionBuffer.setFromEuler(Component._worldEulerBuffer);
655
- this.worldQuaternion = Component._worldQuaternionBuffer;
618
+ threeutils.setWorldRotationXYZ(this.gameObject, x, y, z, degrees);
656
619
  }
657
620
 
658
621
  private static _forward: Vector3 = new Vector3();
src/engine/engine_instancing.ts CHANGED
@@ -1,13 +1,31 @@
1
+ import { InstancedMesh, Object3D } from "three";
2
+
1
3
  export const NEED_UPDATE_INSTANCE_KEY = Symbol("NEEDLE_NEED_UPDATE_INSTANCE");
2
4
 
3
5
  export const $isUsingInstancing = Symbol("isUsingInstancing");
6
+ export const $instancingRenderer = Symbol("instancingRenderer");
7
+ export const $instancingAutoUpdateBounds = Symbol("instancingAutoUpdateBounds");
4
8
 
5
9
  export class InstancingUtil {
6
-
7
- static isUsingInstancing(instance: THREE.Object3D): boolean { return instance[$isUsingInstancing] === true; }
8
10
 
11
+ /** Is this object rendered using a InstancedMesh */
12
+ static isUsingInstancing(instance: Object3D): boolean { return instance[$isUsingInstancing] === true; }
13
+ /** Returns the instanced mesh IF the object is rendered by an instanced mesh
14
+ * @link https://threejs.org/docs/#api/en/objects/InstancedMesh
15
+ */
16
+ static getRenderer(instance: Object3D): InstancedMesh | null { return instance[$instancingRenderer] || null; }
17
+
18
+ setAutoUpdateBounds(instance: Object3D, value: boolean) {
19
+ const renderer = InstancingUtil.getRenderer(instance);
20
+ if (renderer) {
21
+ renderer[$instancingAutoUpdateBounds] = value;
22
+ }
23
+ }
24
+
25
+
9
26
  // TODO: change this so it does not set matrix world directly but some flag that is only used by instancing
10
- static markDirty(go: THREE.Object3D | null, recursive: boolean = true) {
27
+ /** Mark an instanced object dirty so the instance matrix will be updated */
28
+ static markDirty(go: Object3D | null, recursive: boolean = true) {
11
29
  if (!go) return;
12
30
  // potential optimization:
13
31
  // if(go.matrixWorldNeedsUpdate) return;
src/engine/engine_physics.ts CHANGED
@@ -11,25 +11,62 @@
11
11
  export declare type RaycastTestObjectReturnType = void | boolean | "continue in children";
12
12
  export declare type RaycastTestObjectCallback = (obj: Object3D) => RaycastTestObjectReturnType;
13
13
 
14
- export class RaycastOptions {
15
- ray: Ray | undefined = undefined;
16
- cam: Camera | undefined | null = undefined;
17
- screenPoint: Vector2 | undefined = undefined;
18
- raycaster: Raycaster | undefined = undefined;
19
- results: Array<Intersection> | undefined = undefined;
20
- targets: Array<Object3D> | undefined = undefined;
21
- recursive: boolean | undefined = true;
22
- minDistance: number | undefined = undefined;
23
- maxDistance: number | undefined = undefined;
24
- lineThreshold: number | undefined = undefined;
14
+ declare interface IRaycastOptions {
15
+ /** Optionally a custom raycaster can be provided. Other properties will then be set on this raycaster */
16
+ raycaster?: Raycaster;
17
+ /** Optional ray that can be used for raycasting
18
+ * @link https://threejs.org/docs/#api/en/math/Ray
19
+ * */
20
+ ray?: Ray;
21
+ /** The camera to use for the raycaster */
22
+ cam?: Camera | null;
23
+ /** Point on screen in raycast space / normalized device coordinates (-1 to 1).
24
+ * @link https://threejs.org/docs/#api/en/core/Raycaster.setFromCamera */
25
+ screenPoint?: Vector2;
26
+ /** Raycast results array. You can provide an array here to avoid creating a new one (note that if your array already contains items they will be removed) */
27
+ results?: Array<Intersection>;
28
+ /** Objects to raycast against. If no target array is provided the whole scene will be raycasted */
29
+ targets?: Array<Object3D>;
30
+ recursive?: boolean;
31
+ /** Set the raycaster near distance:
32
+ * The near factor of the raycaster. This value indicates which objects can be discarded based on the distance. This value shouldn't be negative and should be smaller than the far property.
33
+ * @link https://threejs.org/docs/#api/en/core/Raycaster.near
34
+ */
35
+ minDistance?: number;
36
+ /** Set the raycaster far distance:
37
+ * The far factor of the raycaster. This value indicates which objects can be discarded based on the distance. This value shouldn't be negative and should be larger than the near property.
38
+ * @link https://threejs.org/docs/#api/en/core/Raycaster.far
39
+ */
40
+ maxDistance?: number;
41
+ /** @link https://threejs.org/docs/#api/en/core/Raycaster.params */
42
+ lineThreshold?: number;
25
43
  /** raw layer mask, use setLayer to set an individual layer active */
26
- layerMask: Layers | number | undefined = undefined;
27
- ignore: Object3D[] | undefined = undefined;
28
- /** if defined it's called per object before tested for intersections.
44
+ layerMask?: Layers | number;
45
+ ignore?: Object3D[];
46
+ /** Optional calback function to be called per object before tested for intersections.
47
+ * This can be used to filter objects.
29
48
  * Return `false` to ignore the object completely or `"continue in children"` to skip the object but continue to traverse its children (if you do raycast with `recursive` enabled)
30
49
  * */
31
- testObject?: RaycastTestObjectCallback = undefined;
50
+ testObject?: RaycastTestObjectCallback;
51
+ }
32
52
 
53
+ export class RaycastOptions implements IRaycastOptions {
54
+ public static readonly AllLayers = 0xFFFFFFFF;
55
+
56
+ ray?: Ray;
57
+ cam?: Camera | null;
58
+ screenPoint?: Vector2;
59
+ raycaster?: Raycaster;
60
+ results?: Array<Intersection>;
61
+ targets?: Array<Object3D>;
62
+ recursive?: boolean = true;
63
+ minDistance?: number;
64
+ maxDistance?: number;
65
+ lineThreshold?: number;
66
+ layerMask?: Layers | number;
67
+ ignore?: Object3D[];
68
+ testObject?: RaycastTestObjectCallback;
69
+
33
70
  screenPointFromOffset(ox: number, oy: number) {
34
71
  if (this.screenPoint === undefined) this.screenPoint = new Vector2();
35
72
  this.screenPoint.x = ox / window.innerWidth * 2 - 1;
@@ -51,7 +88,6 @@
51
88
  else this.layerMask = mask;
52
89
  }
53
90
 
54
- public static readonly AllLayers = 0xFFFFFFFF;
55
91
  }
56
92
 
57
93
  export class SphereIntersection implements Intersection {
@@ -154,7 +190,10 @@
154
190
  public raycastFromRay(ray: Ray, options: RaycastOptions | null = null): Array<Intersection> {
155
191
  const opts = options ?? this.defaultRaycastOptions;
156
192
  opts.ray = ray;
157
- return this.raycast(opts);
193
+ const res = this.raycast(opts);
194
+ // reset the default options ray
195
+ if (opts === this.defaultRaycastOptions) opts.ray = undefined;
196
+ return res;
158
197
  }
159
198
 
160
199
  /** raycast against rendered three objects. This might be very slow depending on your scene complexity.
@@ -163,7 +202,7 @@
163
202
  * Use raycastPhysics for raycasting against physic colliders only. Depending on your scenario this might be faster.
164
203
  * @param options raycast options. If null, default options will be used.
165
204
  */
166
- public raycast(options: RaycastOptions | null = null): Array<Intersection> {
205
+ public raycast(options: IRaycastOptions | RaycastOptions | null = null): Array<Intersection> {
167
206
  if (!options) options = this.defaultRaycastOptions;
168
207
  const mp = options.screenPoint ?? this.context.input.mousePositionRC;
169
208
  const rc = options.raycaster ?? this.raycaster;
@@ -230,7 +269,7 @@
230
269
  return results;
231
270
  }
232
271
 
233
- private intersect(raycaster: Raycaster, objects: Object3D[], results: Intersection[], options: RaycastOptions) {
272
+ private intersect(raycaster: Raycaster, objects: Object3D[], results: Intersection[], options: IRaycastOptions) {
234
273
  for (const obj of objects) {
235
274
  const testResult = options.testObject?.(obj);
236
275
  if (testResult === false) continue;
@@ -239,7 +278,7 @@
239
278
  raycaster.intersectObject(obj, false, results);
240
279
  }
241
280
 
242
- if (options.recursive) {
281
+ if (options.recursive !== false) {
243
282
  this.intersect(raycaster, obj.children, results, options);
244
283
  }
245
284
  }
src/engine-components/ui/Image.ts CHANGED
@@ -48,7 +48,7 @@
48
48
  }
49
49
  // this is a hack/workaround for production builds where the name of the sprite is missing
50
50
  // need to remove this!!!!
51
- if (this.sprite?.texture?.image?.width === 32 && this.sprite?.texture?.image?.height === 32)
51
+ if (!this.sprite?.texture?.name?.length && this.sprite?.texture?.image?.width === 32 && this.sprite?.texture?.image?.height === 32)
52
52
  return true;
53
53
  return false;
54
54
  }
src/engine-components/ParticleSystem.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  import { getWorldPosition, getWorldQuaternion, getWorldScale, setWorldScale } from "../engine/engine_three_utils.js";
13
13
  import { assign } from "../engine/engine_serialization_core.js";
14
14
  import { ParticleSystem as _ParticleSystem, ConstantValue, ConstantColor, BatchedParticleRenderer, TrailBatch, TrailParticle, RenderMode } from "three.quarks";
15
- import type { BatchedRenderer, Behavior, BillBoardSettings, BurstParameters, ColorGenerator, EmissionState, EmitSubParticleSystem, EmitterShape, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, IntervalValue, MeshSettings, Particle, ParticleEmitter, ParticleSystemParameters, PointEmitter, RecordState, RotationGenerator, SizeOverLife, TrailSettings, ValueGenerator } from "three.quarks";
15
+ import type { BatchedRenderer, Behavior, BehaviorPlugin, BillBoardSettings, BurstParameters, ColorGenerator, EmissionState, EmitSubParticleSystem, EmitterShape, FunctionColorGenerator, FunctionJSON, FunctionValueGenerator, IntervalValue, MeshSettings, Particle, ParticleEmitter, ParticleSystemParameters, PointEmitter, RecordState, RotationGenerator, SizeOverLife, TrailSettings, VFXBatchSettings, ValueGenerator } from "three.quarks";
16
16
  import { createFlatTexture } from "../engine/engine_shaders.js";
17
17
  import { Mathf } from "../engine/engine_math.js";
18
18
  import { Context } from "../engine/engine_setup.js";
@@ -25,6 +25,11 @@
25
25
  const suppressProgressiveLoading = getParam("noprogressive");
26
26
  const debugProgressiveLoading = getParam("debugprogressive");
27
27
 
28
+
29
+ export type { Behavior as QParticleBehaviour, Particle as QParticle } from "three.quarks"
30
+
31
+
32
+
28
33
  export enum SubEmitterType {
29
34
  Birth = 0,
30
35
  Collision = 1,
@@ -248,29 +253,25 @@
248
253
  }
249
254
  }
250
255
 
251
- abstract class ParticleSystemBaseBehaviour implements Behavior {
252
- readonly system: ParticleSystem;
256
+ export abstract class ParticleSystemBaseBehaviour implements Behavior {
253
257
 
254
- // get scaleFactorDiff(): number {
255
- // return this.system.scale - this.system.worldScale.x;
256
- // }
258
+ system!: ParticleSystem;
259
+ get context() { return this.system.context; }
257
260
 
258
- constructor(ps: ParticleSystem) {
259
- this.system = ps;
261
+ constructor(ps?: ParticleSystem) {
262
+ this.type = Object.getPrototypeOf(this).constructor.name || "ParticleSystemBaseBehaviour";
263
+ if (ps)
264
+ this.system = ps;
260
265
  }
261
266
 
262
- abstract type: string;
267
+ type: string;
263
268
 
264
- initialize(_particle: Particle): void {
265
- }
266
- update(_particle: Particle, _delta: number): void {
267
- }
268
- frameUpdate(_delta: number): void {
269
- }
269
+ initialize(_particle: Particle): void { }
270
+ update(_particle: Particle, _delta: number): void { }
271
+ frameUpdate(_delta: number): void { }
270
272
  toJSON() { throw new Error("Method not implemented."); }
271
273
  clone(): Behavior { throw new Error("Method not implemented."); }
272
- reset() {
273
- }
274
+ reset() { }
274
275
  }
275
276
 
276
277
  const $startFrame = Symbol("startFrame")
@@ -843,6 +844,42 @@
843
844
  return this._isUsedAsSubsystem;
844
845
  }
845
846
 
847
+ /** Add a custom quarks behaviour to the particle system. You can add a quarks.Behaviour type.
848
+ * @link https://github.com/Alchemist0823/three.quarks
849
+ */
850
+ addBehaviour(particleSystemBehaviour: Behavior | ParticleSystemBaseBehaviour): boolean {
851
+ if (!this._particleSystem) {
852
+ return false;
853
+ }
854
+ if (particleSystemBehaviour instanceof ParticleSystemBaseBehaviour) {
855
+ particleSystemBehaviour.system = this;
856
+ }
857
+ if (isDevEnvironment()) console.log("add behaviour", particleSystemBehaviour);
858
+ this._particleSystem.addBehavior(particleSystemBehaviour);
859
+ return true;
860
+ }
861
+ /** Removes all behaviours from the particle system
862
+ * **Note:** this will also remove the default behaviours like SizeBehaviour, ColorBehaviour etc.
863
+ */
864
+ removeAllBehaviours() {
865
+ if (!this._particleSystem) {
866
+ return false;
867
+ }
868
+ this._particleSystem.behaviors.length = 0;
869
+ return true;
870
+ }
871
+ /** Get the particlesystem behaviours. This can be used to fully customize the behaviour of the particles. */
872
+ get behaviours(): Behavior[] | null {
873
+ if (!this._particleSystem) return null;
874
+ return this._particleSystem.behaviors;
875
+ }
876
+ /** Get access to the underlying quarks particle system if you need more control
877
+ * @link https://github.com/Alchemist0823/three.quarks
878
+ */
879
+ get particleSystem(): _ParticleSystem | null {
880
+ return this._particleSystem ?? null;
881
+ }
882
+
846
883
  private _renderer!: ParticleSystemRenderer;
847
884
  private _batchSystem?: BatchedRenderer;
848
885
  private _particleSystem?: _ParticleSystem;
src/engine/codegen/register_types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TypeStore } from "./../engine_typestore.js"
2
-
2
+
3
3
  // Import types
4
4
  import { __Ignore } from "../../engine-components/codegen/components.js";
5
5
  import { ActionBuilder } from "../../engine-components/export/usdz/extensions/behavior/BehavioursBuilder.js";
@@ -219,7 +219,7 @@
219
219
  import { XRGrabRendering } from "../../engine-components/webxr/WebXRGrabRendering.js";
220
220
  import { XRRig } from "../../engine-components/webxr/WebXRRig.js";
221
221
  import { XRState } from "../../engine-components/XRFlag.js";
222
-
222
+
223
223
  // Register types
224
224
  TypeStore.add("__Ignore", __Ignore);
225
225
  TypeStore.add("ActionBuilder", ActionBuilder);
src/engine-components/Renderer.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  import { AxesHelper, Material, Matrix4, Mesh, Object3D, SkinnedMesh, Texture, Vector4 } from "three";
9
9
  import { NEEDLE_render_objects } from "../engine/extensions/NEEDLE_render_objects.js";
10
10
  import { NEEDLE_progressive } from "../engine/extensions/NEEDLE_progressive.js";
11
- import { InstancingUtil, NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing.js";
11
+ import { $instancingAutoUpdateBounds, $instancingRenderer, InstancingUtil, NEED_UPDATE_INSTANCE_KEY } from "../engine/engine_instancing.js";
12
12
  import type { IRenderer, ISharedMaterials } from "../engine/engine_types.js";
13
13
  import { ReflectionProbe } from "./ReflectionProbe.js";
14
14
  import { setCustomVisibility } from "../engine/js-extensions/Layers.js";
@@ -912,8 +912,8 @@
912
912
  this.instanceIndex = instanceIndex;
913
913
  this.object = originalObject;
914
914
  this.instancer = instancer;
915
+ originalObject[$instancingRenderer] = instancer;
915
916
  GameObject.markAsInstancedRendered(originalObject, true);
916
- // this.object.visible = false;
917
917
  }
918
918
 
919
919
  updateInstanceMatrix(updateChildren: boolean = false) {
@@ -939,7 +939,21 @@
939
939
  }
940
940
 
941
941
  class InstancedMeshRenderer {
942
-
942
+ /** The three instanced mesh
943
+ * @link https://threejs.org/docs/#api/en/objects/InstancedMesh
944
+ */
945
+ get mesh() {
946
+ return this.inst;
947
+ }
948
+ get visible(): boolean {
949
+ return this.inst.visible;
950
+ }
951
+ set visible(val: boolean) {
952
+ this.inst.visible = val;
953
+ }
954
+ get castShadow(): boolean {
955
+ return this.inst.castShadow;
956
+ }
943
957
  set castShadow(val: boolean) {
944
958
  this.inst.castShadow = val;
945
959
  }
@@ -947,6 +961,14 @@
947
961
  this.inst.receiveShadow = val;
948
962
  }
949
963
 
964
+ updateBounds(box: boolean = true, sphere: boolean = true) {
965
+ this._needUpdateBounds = false;
966
+ if (box)
967
+ this.inst.computeBoundingBox();
968
+ if (sphere)
969
+ this.inst.computeBoundingSphere();
970
+ }
971
+
950
972
  public name: string = "";
951
973
  public geo: THREE.BufferGeometry;
952
974
  public material: THREE.Material;
@@ -963,6 +985,8 @@
963
985
  return this.currentCount >= this.maxCount;
964
986
  }
965
987
 
988
+ private _needUpdateBounds: boolean = false;
989
+
966
990
  constructor(name: string, geo: THREE.BufferGeometry, material: THREE.Material, count: number, context: Context) {
967
991
  this.name = name;
968
992
  this.geo = geo;
@@ -973,9 +997,18 @@
973
997
  material = new THREE.MeshBasicMaterial({ color: this.randomColor() });
974
998
  }
975
999
  this.inst = new THREE.InstancedMesh(geo, material, count);
1000
+ this.inst[$instancingAutoUpdateBounds] = true;
1001
+ this.inst.onBeforeRender = () => {
1002
+ if (this._needUpdateBounds && this.inst[$instancingAutoUpdateBounds] === true) {
1003
+ if (debugInstancing)
1004
+ console.log("Update instancing bounds", this.name);
1005
+ this.updateBounds();
1006
+ }
1007
+ };
976
1008
  this.inst.count = 0;
977
1009
  this.inst.layers.set(2);
978
1010
  this.inst.visible = true;
1011
+
979
1012
  // this.inst.castShadow = true;
980
1013
  // this.inst.receiveShadow = true;
981
1014
  this.context.scene.add(this.inst);
@@ -1023,6 +1056,7 @@
1023
1056
  this.inst.setMatrixAt(handle.instanceIndex, handle.object.matrixWorld);
1024
1057
  this.inst.instanceMatrix.needsUpdate = true;
1025
1058
  this.inst.count += 1;
1059
+ this._needUpdateBounds = true;
1026
1060
 
1027
1061
  if (this.inst.count > 0)
1028
1062
  this.inst.visible = true;
@@ -1061,6 +1095,7 @@
1061
1095
  if (this.inst.count > 0)
1062
1096
  this.inst.count -= 1;
1063
1097
  handle.instanceIndex = -1;
1098
+ this._needUpdateBounds = true;
1064
1099
 
1065
1100
  if (this.inst.count <= 0)
1066
1101
  this.inst.visible = false;
@@ -1071,5 +1106,6 @@
1071
1106
  updateInstance(mat: THREE.Matrix4, index: number) {
1072
1107
  this.inst.setMatrixAt(index, mat);
1073
1108
  this.inst.instanceMatrix.needsUpdate = true;
1109
+ this._needUpdateBounds = true;
1074
1110
  }
1075
1111
  }
src/engine-components/RigidBody.ts CHANGED
@@ -137,6 +137,9 @@
137
137
  @validate()
138
138
  autoMass: boolean = true;
139
139
 
140
+ /** By default the mass will be automatically calculated (see `autoMass`) by the physics engine using the collider sizes
141
+ * To set the mass manually you can either set the `mass` value or set `autoMass` to `false`
142
+ */
140
143
  @serializable()
141
144
  set mass(value: number) {
142
145
  if (value === this._mass) return;
@@ -166,10 +169,12 @@
166
169
  @serializable()
167
170
  isKinematic: boolean = false;
168
171
 
172
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#damping */
169
173
  @validate()
170
174
  @serializable()
171
175
  drag: number = 0;
172
176
 
177
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#damping */
173
178
  @validate()
174
179
  @serializable()
175
180
  angularDrag: number = 1;
@@ -178,58 +183,77 @@
178
183
  @serializable()
179
184
  detectCollisions: boolean = true;
180
185
 
186
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#sleeping */
181
187
  @validate()
182
188
  @serializable()
183
189
  sleepThreshold: number = 0.01;
184
190
 
191
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#continuous-collision-detection */
185
192
  @validate()
186
193
  @serializable()
187
194
  collisionDetectionMode: CollisionDetectionMode = CollisionDetectionMode.Discrete;
188
195
 
196
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
189
197
  get lockPositionX() {
190
198
  return (this.constraints & RigidbodyConstraints.FreezePositionX) !== 0;
191
199
  }
200
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
192
201
  get lockPositionY() {
193
202
  return (this.constraints & RigidbodyConstraints.FreezePositionY) !== 0;
194
203
  }
204
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
195
205
  get lockPositionZ() {
196
206
  return (this.constraints & RigidbodyConstraints.FreezePositionZ) !== 0;
197
207
  }
208
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
198
209
  get lockRotationX() {
199
210
  return (this.constraints & RigidbodyConstraints.FreezeRotationX) !== 0;
200
211
  }
212
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
201
213
  get lockRotationY() {
202
214
  return (this.constraints & RigidbodyConstraints.FreezeRotationY) !== 0;
203
215
  }
216
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
204
217
  get lockRotationZ() {
205
218
  return (this.constraints & RigidbodyConstraints.FreezeRotationZ) !== 0;
206
219
  }
207
220
 
221
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
208
222
  set lockPositionX(v: boolean) {
209
223
  if (v) this.constraints |= RigidbodyConstraints.FreezePositionX;
210
224
  else this.constraints &= ~RigidbodyConstraints.FreezePositionX;
211
225
  }
226
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
212
227
  set lockPositionY(v: boolean) {
213
228
  if (v) this.constraints |= RigidbodyConstraints.FreezePositionY;
214
229
  else this.constraints &= ~RigidbodyConstraints.FreezePositionY;
215
230
  }
231
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
216
232
  set lockPositionZ(v: boolean) {
217
233
  if (v) this.constraints |= RigidbodyConstraints.FreezePositionZ;
218
234
  else this.constraints &= ~RigidbodyConstraints.FreezePositionZ;
219
235
  }
236
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
220
237
  set lockRotationX(v: boolean) {
221
238
  if (v) this.constraints |= RigidbodyConstraints.FreezeRotationX;
222
239
  else this.constraints &= ~RigidbodyConstraints.FreezeRotationX;
223
240
  }
241
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
224
242
  set lockRotationY(v: boolean) {
225
243
  if (v) this.constraints |= RigidbodyConstraints.FreezeRotationY;
226
244
  else this.constraints &= ~RigidbodyConstraints.FreezeRotationY;
227
245
  }
246
+ /** @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#locking-translationsrotations */
228
247
  set lockRotationZ(v: boolean) {
229
248
  if (v) this.constraints |= RigidbodyConstraints.FreezeRotationZ;
230
249
  else this.constraints &= ~RigidbodyConstraints.FreezeRotationZ;
231
250
  }
232
251
 
252
+ /** Gravity is such a common force that it is implemented as a special case (even if it could easily be implemented by the user using force application). Note however that a change of gravity won't automatically wake-up the sleeping bodies so keep in mind that you may want to wake them up manually before a gravity change.
253
+ *
254
+ * It is possible to change the way gravity affects a specific rigid-body by setting the rigid-body's gravity scale to a value other than 1.0. The magnitude of the gravity applied to this body will be multiplied by this scaling factor. Therefore, a gravity scale set to 0.0 will disable gravity for the rigid-body whereas a gravity scale set to 2.0 will make it twice as strong. A negative value will flip the direction of the gravity for this rigid-body.
255
+ * @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#gravity
256
+ */
233
257
  set gravityScale(val: number) {
234
258
  this._gravityScale = val;
235
259
  }
@@ -240,7 +264,7 @@
240
264
  private _gravityScale: number = 1;
241
265
 
242
266
  /** Rigidbodies with higher dominance will be immune to forces originating from contacts with rigidbodies of lower dominance.
243
- * Read more here: https://rapier.rs/docs/user_guides/javascript/rigid_bodies#dominance
267
+ * @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#dominance
244
268
  */
245
269
  @validate()
246
270
  dominanceGroup : number = 0;
@@ -299,6 +323,7 @@
299
323
  }
300
324
  }
301
325
 
326
+ /** Will reset forces before setting the object world position */
302
327
  public teleport(pt: { x: number, y: number, z: number }, localspace: boolean = true) {
303
328
  this._watch?.reset(true);
304
329
  if (localspace) this.gameObject.position.set(pt.x, pt.y, pt.z);
@@ -325,18 +350,27 @@
325
350
  this.resetTorques();
326
351
  }
327
352
 
353
+ /** When a dynamic rigid-body doesn't move (or moves very slowly) during a few seconds, it will be marked as sleeping by the physics pipeline. Rigid-bodies marked as sleeping are no longer simulated by the physics engine until they are woken up. That way the physics engine doesn't waste any computational resources simulating objects that don't actually move. They are woken up automatically whenever another non-sleeping rigid-body starts interacting with them (either with a joint, or with one of its attached colliders generating contacts).
354
+ * @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#sleeping */
328
355
  public wakeUp() {
329
356
  this.context.physics.engine?.wakeup(this);
330
357
  }
331
358
 
359
+ /** Forces affect the rigid-body's acceleration whereas impulses affect the rigid-body's velocity
360
+ * the acceleration change is equal to the force divided by the mass:
361
+ * @link see https://rapier.rs/docs/user_guides/javascript/rigid_bodies#forces-and-impulses */
332
362
  public applyForce(vec: Vector3 | Vec3, _rel?: THREE.Vector3, wakeup: boolean = true) {
333
363
  this.context.physics.engine?.addForce(this, vec, wakeup);
334
364
  }
335
365
 
366
+ /** Forces affect the rigid-body's acceleration whereas impulses affect the rigid-body's velocity
367
+ * the velocity change is equal to the impulse divided by the mass
368
+ * @link see https://rapier.rs/docs/user_guides/javascript/rigid_bodies#forces-and-impulses */
336
369
  public applyImpulse(vec: Vector3 | Vec3, wakeup: boolean = true) {
337
370
  this.context.physics.engine?.applyImpulse(this, vec, wakeup);
338
371
  }
339
372
 
373
+ /** @link see https://rapier.rs/docs/user_guides/javascript/rigid_bodies#forces-and-impulses */
340
374
  public setForce(x: Vector3 | Vec3 | number, y?: number, z?: number, wakeup: boolean = true) {
341
375
  this.context.physics.engine?.resetForces(this, wakeup);
342
376
  if (typeof x === "number") {
@@ -349,6 +383,8 @@
349
383
  }
350
384
  }
351
385
 
386
+ /** The velocity of a dynamic rigid-body controls how fast it is moving in time. The velocity is applied at the center-of-mass of the rigid-body. This method returns the current linear velocity of the rigid-body.
387
+ * @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#velocity */
352
388
  public getVelocity(): Vector3 {
353
389
  const vel = this.context.physics.engine?.getLinearVelocity(this);
354
390
  if (!vel) return this._currentVelocity.set(0, 0, 0);
@@ -368,6 +404,17 @@
368
404
  this.context.physics.engine?.setLinearVelocity(this, { x: x, y: y, z: z }, wakeup);
369
405
  }
370
406
 
407
+ /** The velocity of a dynamic rigid-body controls how fast it is moving in time. The velocity is applied at the center-of-mass of the rigid-body. This method returns the current angular velocity of the rigid-body.
408
+ * @link https://rapier.rs/docs/user_guides/javascript/rigid_bodies#velocity */
409
+ public getAngularVelocity(): Vector3 {
410
+ const vel = this.context.physics.engine?.getAngularVelocity(this);
411
+ if (!vel) return this._currentVelocity.set(0, 0, 0);
412
+ this._currentVelocity.x = vel.x;
413
+ this._currentVelocity.y = vel.y;
414
+ this._currentVelocity.z = vel.z;
415
+ return this._currentVelocity;
416
+ }
417
+
371
418
  public setAngularVelocity(x: number | Vector3, y?: number, z?: number, wakeup: boolean = true) {
372
419
  if (x instanceof Vector3) {
373
420
  const vec = x;
@@ -378,35 +425,24 @@
378
425
  this.context.physics.engine?.setAngularVelocity(this, { x: x, y: y, z: z }, wakeup);
379
426
  }
380
427
 
381
- public getAngularVelocity(): Vector3 {
382
- const vel = this.context.physics.engine?.getAngularVelocity(this);
383
- if (!vel) return this._currentVelocity.set(0, 0, 0);
384
- this._currentVelocity.x = vel.x;
385
- this._currentVelocity.y = vel.y;
386
- this._currentVelocity.z = vel.z;
387
- return this._currentVelocity;
388
- }
389
-
428
+ /** Set the angular velocity of a rigidbody (equivalent to calling `setAngularVelocity`) */
390
429
  public setTorque(x: number | Vector3, y: number, z: number) {
391
430
  this.setAngularVelocity(x, y, z);
392
431
  }
393
432
 
433
+ /** Returns the rigidbody velocity smoothed over ~ 10 frames */
394
434
  public get smoothedVelocity(): Vector3 {
395
435
  this._smoothedVelocityGetter.copy(this._smoothedVelocity);
396
436
  return this._smoothedVelocityGetter.multiplyScalar(1 / this.context.time.deltaTime);
397
437
  }
398
438
 
399
- // public get smoothedVelocity() { return this._smoothedVelocity; }
400
439
 
401
-
402
-
403
440
  /**d
404
- * @deprecated not used anymore
441
+ * @deprecated not used anymore and will be removed in a future update
405
442
  */
406
443
  public setBodyFromGameObject(_velocity: THREE.Vector3 | null | { x: number, y: number, z: number } = null) { }
407
444
 
408
445
 
409
-
410
446
  private captureVelocity() {
411
447
  const wp = getWorldPosition(this.gameObject);
412
448
  Rigidbody.tempPosition.copy(wp);