@pmndrs/viverse 0.1.13 → 0.1.15

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.
@@ -38,6 +38,9 @@ export declare class BvhCharacterPhysics {
38
38
  private readonly stateVelocity;
39
39
  readonly inputVelocity: Vector3;
40
40
  private notGroundedSeconds;
41
+ private readonly segment;
42
+ private readonly aabbox;
43
+ private radius;
41
44
  get isGrounded(): boolean;
42
45
  constructor(character: Object3D, world: BvhPhysicsWorld);
43
46
  applyVelocity(velocity: Vector3): void;
@@ -45,6 +48,7 @@ export declare class BvhCharacterPhysics {
45
48
  * @param delta in seconds
46
49
  */
47
50
  update(fullDelta: number, options?: BvhCharacterPhysicsOptions): void;
51
+ private updateBoundingShapes;
48
52
  destroy(): void;
49
53
  shapecastCapsule(position: Vector3, maxGroundSlope: number, options: Exclude<BvhCharacterPhysicsOptions, boolean>): boolean;
50
54
  }
@@ -1,8 +1,5 @@
1
1
  import { Box3, Line3, Matrix4, Vector3 } from 'three';
2
2
  //for this is a kinematic character controller
3
- //helper variables
4
- const aabbox = new Box3();
5
- const segment = new Line3();
6
3
  const triPoint = new Vector3();
7
4
  const capsulePoint = new Vector3();
8
5
  const collisionFreePosition = new Vector3();
@@ -19,6 +16,9 @@ export class BvhCharacterPhysics {
19
16
  stateVelocity = new Vector3();
20
17
  inputVelocity = new Vector3();
21
18
  notGroundedSeconds = 0;
19
+ segment = new Line3();
20
+ aabbox = new Box3();
21
+ radius = 0;
22
22
  get isGrounded() {
23
23
  return this.notGroundedSeconds < 0.2;
24
24
  }
@@ -82,49 +82,55 @@ export class BvhCharacterPhysics {
82
82
  this.stateVelocity.set(0, (options.gravity ?? -20) * 0.01, 0);
83
83
  }
84
84
  }
85
+ this.updateBoundingShapes(options);
86
+ this.world.updateSensors((bounds) => bounds.intersectsBox(this.aabbox), (triangle) => triangle.closestPointToSegment(this.segment) < this.radius);
87
+ }
88
+ updateBoundingShapes(options) {
89
+ //compute the bounding capsule and bounding box
90
+ this.radius = options.capsuleRadius ?? 0.4;
91
+ const height = options.capsuleHeight ?? 1.7;
92
+ this.segment.start.copy(position);
93
+ this.segment.start.y += this.radius;
94
+ this.segment.end.copy(position);
95
+ this.segment.end.y += height - this.radius;
96
+ this.aabbox.makeEmpty();
97
+ this.aabbox.expandByPoint(this.segment.start);
98
+ this.aabbox.expandByPoint(this.segment.end);
99
+ this.aabbox.min.addScalar(-this.radius);
100
+ this.aabbox.max.addScalar(this.radius);
85
101
  }
86
102
  destroy() {
87
103
  this.destroyed = true;
88
104
  }
89
105
  shapecastCapsule(position, maxGroundSlope, options) {
90
- const radius = options.capsuleRadius ?? 0.4;
91
- const height = options.capsuleHeight ?? 1.7;
92
- segment.start.copy(position);
93
- segment.start.y += radius;
94
- segment.end.copy(position);
95
- segment.end.y += height - radius;
96
- aabbox.makeEmpty();
97
- aabbox.expandByPoint(segment.start);
98
- aabbox.expandByPoint(segment.end);
99
- aabbox.min.addScalar(-radius);
100
- aabbox.max.addScalar(radius);
106
+ this.updateBoundingShapes(options);
101
107
  let grounded = false;
102
- this.world.shapecast((bounds) => bounds.intersectsBox(aabbox), (tri) => {
108
+ this.world.shapecast((bounds) => bounds.intersectsBox(this.aabbox), (tri) => {
103
109
  // Use your existing triangle vs segment closestPointToSegment
104
- const distance = tri.closestPointToSegment(segment, triPoint, capsulePoint);
110
+ const distance = tri.closestPointToSegment(this.segment, triPoint, capsulePoint);
105
111
  if (distance === 0) {
106
- const isCloserToSegmentStart = capsulePoint.distanceTo(segment.start) < capsulePoint.distanceTo(segment.end);
112
+ const isCloserToSegmentStart = capsulePoint.distanceTo(this.segment.start) < capsulePoint.distanceTo(this.segment.end);
107
113
  if (isCloserToSegmentStart) {
108
114
  grounded = true;
109
115
  }
110
- const scaledDirection = capsulePoint.sub(isCloserToSegmentStart ? segment.start : segment.end);
111
- scaledDirection.y += radius;
112
- segment.start.add(scaledDirection);
113
- segment.end.add(scaledDirection);
116
+ const scaledDirection = capsulePoint.sub(isCloserToSegmentStart ? this.segment.start : this.segment.end);
117
+ scaledDirection.y += this.radius;
118
+ this.segment.start.add(scaledDirection);
119
+ this.segment.end.add(scaledDirection);
114
120
  }
115
- else if (distance < radius) {
116
- const depthInsideCapsule = radius - distance;
121
+ else if (distance < this.radius) {
122
+ const depthInsideCapsule = this.radius - distance;
117
123
  const direction = capsulePoint.sub(triPoint).divideScalar(distance);
118
124
  const slope = Math.tan(Math.acos(direction.dot(YAxis)));
119
125
  if (direction.y > 0 && slope <= maxGroundSlope) {
120
126
  grounded = true;
121
127
  }
122
- segment.start.addScaledVector(direction, depthInsideCapsule);
123
- segment.end.addScaledVector(direction, depthInsideCapsule);
128
+ this.segment.start.addScaledVector(direction, depthInsideCapsule);
129
+ this.segment.end.addScaledVector(direction, depthInsideCapsule);
124
130
  }
125
131
  });
126
- position.copy(segment.start);
127
- position.y -= radius;
132
+ position.copy(this.segment.start);
133
+ position.y -= this.radius;
128
134
  return grounded;
129
135
  }
130
136
  }
@@ -1,14 +1,20 @@
1
1
  import { Box3, Object3D, Ray } from 'three';
2
2
  import { ExtendedTriangle } from 'three-mesh-bvh';
3
3
  export declare class BvhPhysicsWorld {
4
- private map;
4
+ private bodies;
5
+ private sensors;
5
6
  /**
6
7
  * @deprecated use addBody(object, false) instead
7
8
  */
8
9
  addFixedBody(object: Object3D): void;
10
+ addSensor(object: Object3D, isStatic: boolean, onIntersectedChanged: (intersected: boolean) => void): void;
11
+ removeSensor(object: Object3D): void;
9
12
  addBody(object: Object3D, kinematic: boolean): void;
13
+ private computeBvhEntries;
10
14
  removeBody(object: Object3D): void;
11
15
  private computeMatrix;
16
+ updateSensors(intersectsBounds: (box: Box3) => boolean, intersectsTriangle: (triangle: ExtendedTriangle) => boolean): void;
12
17
  shapecast(intersectsBounds: (box: Box3) => boolean, intersectsTriangle: (triangle: ExtendedTriangle) => void): void;
18
+ private shapecastEntry;
13
19
  raycast(ray: Ray, far: number): number | undefined;
14
20
  }
@@ -6,47 +6,59 @@ const boxHelper = new Box3();
6
6
  const triangleHelper = new ExtendedTriangle();
7
7
  const matrixHelper = new Matrix4();
8
8
  export class BvhPhysicsWorld {
9
- map = [];
9
+ bodies = [];
10
+ sensors = [];
10
11
  /**
11
12
  * @deprecated use addBody(object, false) instead
12
13
  */
13
14
  addFixedBody(object) {
14
15
  this.addBody(object, false);
15
16
  }
17
+ addSensor(object, isStatic, onIntersectedChanged) {
18
+ this.sensors.push(...this.computeBvhEntries(object, isStatic).map((entry) => ({
19
+ ...entry,
20
+ onIntersectedChanged,
21
+ intersected: false,
22
+ })));
23
+ }
24
+ removeSensor(object) {
25
+ this.sensors = this.sensors.filter((entry) => entry.object != object);
26
+ }
16
27
  addBody(object, kinematic) {
28
+ this.bodies.push(...this.computeBvhEntries(object, !kinematic));
29
+ }
30
+ computeBvhEntries(object, isStatic) {
17
31
  object.updateWorldMatrix(true, true);
18
32
  if (!(object instanceof InstancedMesh)) {
19
33
  const parent = object.parent;
20
- if (kinematic) {
34
+ if (!isStatic) {
21
35
  object.parent = null;
22
36
  object.updateMatrixWorld(true);
23
37
  }
24
38
  const geometry = new StaticGeometryGenerator(object).generate();
25
- this.map.push({ object, bvh: computeBoundsTree.apply(geometry), kinematic });
26
- if (kinematic) {
39
+ const bvh = computeBoundsTree.apply(geometry);
40
+ if (!isStatic) {
27
41
  object.parent = parent;
28
42
  object.updateMatrixWorld(true);
29
43
  }
30
- return;
44
+ return [{ object, bvh, isStatic }];
31
45
  }
32
46
  if (object.children.length > 0) {
33
47
  throw new Error(`cannot add InstancedMesh with children`);
34
48
  }
35
49
  const bvh = computeBoundsTree.apply(object.geometry);
36
- for (let i = 0; i < object.instanceMatrix.count; i++) {
37
- this.map.push({
38
- object,
39
- bvh,
40
- instanceIndex: i,
41
- kinematic,
42
- });
43
- }
50
+ return new Array(object.instanceMatrix).fill(undefined).map((_, i) => ({
51
+ object,
52
+ bvh,
53
+ instanceIndex: i,
54
+ isStatic,
55
+ }));
44
56
  }
45
57
  removeBody(object) {
46
- this.map = this.map.filter((entry) => entry.object != object);
58
+ this.bodies = this.bodies.filter((entry) => entry.object != object);
47
59
  }
48
- computeMatrix({ kinematic, object, instanceIndex }, target) {
49
- if (!kinematic && instanceIndex == null) {
60
+ computeMatrix({ isStatic, object, instanceIndex }, target) {
61
+ if (isStatic && instanceIndex == null) {
50
62
  return false;
51
63
  }
52
64
  if (instanceIndex == null) {
@@ -58,31 +70,48 @@ export class BvhPhysicsWorld {
58
70
  target.premultiply(object.matrixWorld);
59
71
  return true;
60
72
  }
73
+ updateSensors(intersectsBounds, intersectsTriangle) {
74
+ console.log('update sensors', this.sensors.length);
75
+ for (const entry of this.sensors) {
76
+ const intersected = this.shapecastEntry(entry, intersectsBounds, intersectsTriangle);
77
+ console.log(intersected);
78
+ if (entry.intersected === intersected) {
79
+ continue;
80
+ }
81
+ entry.onIntersectedChanged(intersected);
82
+ entry.intersected = intersected;
83
+ }
84
+ }
61
85
  shapecast(intersectsBounds, intersectsTriangle) {
62
- for (const entry of this.map) {
63
- entry.bvh.shapecast({
64
- intersectsBounds: (box) => {
65
- boxHelper.copy(box);
66
- if (this.computeMatrix(entry, matrixHelper)) {
67
- boxHelper.applyMatrix4(matrixHelper);
68
- }
69
- return intersectsBounds(boxHelper);
70
- },
71
- intersectsTriangle: (triangle) => {
72
- triangleHelper.copy(triangle);
73
- if (this.computeMatrix(entry, matrixHelper)) {
74
- triangleHelper.a.applyMatrix4(matrixHelper);
75
- triangleHelper.b.applyMatrix4(matrixHelper);
76
- triangleHelper.c.applyMatrix4(matrixHelper);
77
- }
78
- intersectsTriangle(triangleHelper);
79
- },
80
- });
86
+ for (const entry of this.bodies) {
87
+ this.shapecastEntry(entry, intersectsBounds, intersectsTriangle);
81
88
  }
82
89
  }
90
+ shapecastEntry(entry, intersectsBounds, intersectsTriangle) {
91
+ return entry.bvh.shapecast({
92
+ intersectsBounds: (box) => {
93
+ boxHelper.copy(box);
94
+ if (this.computeMatrix(entry, matrixHelper)) {
95
+ boxHelper.applyMatrix4(matrixHelper);
96
+ }
97
+ return intersectsBounds(boxHelper);
98
+ },
99
+ intersectsTriangle: (triangle) => {
100
+ triangleHelper.copy(triangle);
101
+ if (this.computeMatrix(entry, matrixHelper)) {
102
+ triangleHelper.a.applyMatrix4(matrixHelper);
103
+ triangleHelper.b.applyMatrix4(matrixHelper);
104
+ triangleHelper.c.applyMatrix4(matrixHelper);
105
+ }
106
+ if (intersectsTriangle(triangleHelper) === true) {
107
+ return true;
108
+ }
109
+ },
110
+ });
111
+ }
83
112
  raycast(ray, far) {
84
113
  let result;
85
- for (const entry of this.map.values()) {
114
+ for (const entry of this.bodies.values()) {
86
115
  rayHelper.copy(ray);
87
116
  let farHelper = far;
88
117
  if (this.computeMatrix(entry, matrixHelper)) {
@@ -1,5 +1,5 @@
1
1
  import { VRM, VRMUtils } from '@pixiv/three-vrm';
2
- import { action, animationFinished, build, timePassed, forever, parallel, graph, } from '@pmndrs/timeline';
2
+ import { action, animationFinished, start, timePassed, forever, parallel, graph, } from '@pmndrs/timeline';
3
3
  import { AnimationMixer, Euler, Group, LoopOnce, Quaternion, Vector3, } from 'three';
4
4
  import { simpleCharacterAnimationNames, getSimpleCharacterModelAnimationOptions as getSimpleCharacterModelAnimationOptions, loadCharacterModelAnimation as loadCharacterModelAnimation, } from './animation/index.js';
5
5
  import { SimpleCharacterCameraBehavior } from './camera.js';
@@ -310,7 +310,7 @@ export class SimpleCharacter extends Group {
310
310
  this.actions.jumpForward.loop = LoopOnce;
311
311
  this.actions.jumpForward.clampWhenFinished = true;
312
312
  }
313
- this.updateTimeline = build(SimpleCharacterTimeline(camera, this));
313
+ this.updateTimeline = start(SimpleCharacterTimeline(camera, this));
314
314
  this.dispatchEvent({ type: 'loaded' });
315
315
  }
316
316
  update(delta) {
package/package.json CHANGED
@@ -21,12 +21,12 @@
21
21
  "peerDependencies": {
22
22
  "three": "*"
23
23
  },
24
- "version": "0.1.13",
24
+ "version": "0.1.15",
25
25
  "type": "module",
26
26
  "dependencies": {
27
27
  "@pixiv/three-vrm": "^3.4.2",
28
28
  "@pixiv/three-vrm-animation": "^3.4.2",
29
- "@pmndrs/timeline": "^0.1.10",
29
+ "@pmndrs/timeline": "^0.1.15",
30
30
  "@viverse/sdk": "1.2.10-alpha.0",
31
31
  "three-mesh-bvh": "^0.9.1"
32
32
  },