@pmndrs/viverse 0.2.23 → 0.2.25

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.
@@ -31,6 +31,17 @@ export type CharacterModel = {
31
31
  scene: Object3D;
32
32
  currentAnimations: Map<string | undefined, AnimationAction>;
33
33
  boneRotationOffset?: Quaternion;
34
+ /**
35
+ * The character's real height in world units, measured once from the loaded mesh in its
36
+ * bind pose. Use this to size things to the character (nameplates, hitboxes, scale
37
+ * normalization) instead of `new Box3().setFromObject(scene)` — that includes the skeleton
38
+ * bones and the internal rest-pose reference copy and changes as the animation plays, so it
39
+ * returns a height several times the visible body.
40
+ *
41
+ * Always set by `loadCharacterModel`; optional only so hand-built `CharacterModel` objects
42
+ * (e.g. cloned NPC instances) stay valid — copy it across when you clone.
43
+ */
44
+ height?: number;
34
45
  };
35
46
  export declare function loadCharacterModel(url?: string, type?: Exclude<CharacterModelOptions, boolean>['type'], boneRotationOffset?: Quaternion, castShadow?: boolean, receiveShadow?: boolean, boneMap?: BoneMap, useDraco?: boolean): Promise<CharacterModel>;
36
47
  export declare function getBone(model: CharacterModel, name: VRMHumanBoneName): Object3D | undefined;
@@ -1,5 +1,5 @@
1
1
  import { VRM } from '@pixiv/three-vrm';
2
- import { AnimationMixer, Euler, Quaternion } from 'three';
2
+ import { AnimationMixer, Box3, Euler, Quaternion, Vector3 } from 'three';
3
3
  import { loadGltfCharacterModel } from './gltf.js';
4
4
  import { loadVrmCharacterModel } from './vrm.js';
5
5
  export { VRMHumanBoneName } from '@pixiv/three-vrm';
@@ -56,11 +56,15 @@ export async function loadCharacterModel(url, type, boneRotationOffset, castShad
56
56
  obj.receiveShadow = true;
57
57
  }
58
58
  });
59
+ // Measure the real height here: the scene is still in its bind pose, the mixer has not run,
60
+ // and the rest-pose clone below has not been added yet — so the bounding box is just the
61
+ // visible mesh and is stable. Measuring later (after the clone, once animating) inflates it.
62
+ const height = new Box3().setFromObject(result.scene).getSize(new Vector3()).y;
59
63
  const restPose = result.scene.clone();
60
64
  restPose.visible = false;
61
65
  restPose.traverse((object) => (object.name = `rest_${object.name}`));
62
66
  result.scene.add(restPose);
63
- return Object.assign(result, { mixer: new AnimationMixer(result.scene), currentAnimations: new Map() });
67
+ return Object.assign(result, { mixer: new AnimationMixer(result.scene), currentAnimations: new Map(), height });
64
68
  }
65
69
  export function getBone(model, name) {
66
70
  return model instanceof VRM ? (model.humanoid.getRawBoneNode(name) ?? undefined) : model.scene.getObjectByName(name);
@@ -1,10 +1,19 @@
1
- import { Box3, DoubleSide, InstancedMesh, Matrix4, Mesh, Ray, Vector3 } from 'three';
1
+ import { Box3, DoubleSide, InstancedMesh, Matrix4, Mesh, Ray, Vector3, } from 'three';
2
2
  import { computeBoundsTree, StaticGeometryGenerator, ExtendedTriangle } from 'three-mesh-bvh';
3
3
  const rayHelper = new Ray();
4
4
  const farPointHelper = new Vector3();
5
5
  const boxHelper = new Box3();
6
6
  const triangleHelper = new ExtendedTriangle();
7
7
  const matrixHelper = new Matrix4();
8
+ /**
9
+ * Identifies which geometries StaticGeometryGenerator can merge together. Geometries are mergeable when they share
10
+ * the same set of attributes and agree on whether they are indexed, so two geometries with an equal signature are
11
+ * guaranteed not to trigger the "Make sure all geometries have the same number of attributes." error.
12
+ */
13
+ function geometryAttributeSignature(geometry) {
14
+ const attributeNames = Object.keys(geometry.attributes).sort().join(',');
15
+ return `${geometry.index == null ? 'non-indexed' : 'indexed'}:${attributeNames}`;
16
+ }
8
17
  export class BvhPhysicsWorld {
9
18
  bodies = [];
10
19
  sensors = [];
@@ -30,35 +39,45 @@ export class BvhPhysicsWorld {
30
39
  computeBvhEntries(object, isStatic) {
31
40
  object.updateWorldMatrix(true, true);
32
41
  const result = [];
33
- let hasNonInstancedMeshes = false;
42
+ //StaticGeometryGenerator merges all meshes into a single geometry and throws if they don't all expose
43
+ //the same attributes ("Make sure all geometries have the same number of attributes."). Group the meshes by
44
+ //their attribute signature so every merge only ever sees compatible geometries and generate one bvh per group.
45
+ const meshGroups = new Map();
34
46
  object.traverse((entry) => {
35
47
  if (entry instanceof InstancedMesh) {
36
48
  const bvh = computeBoundsTree.apply(entry.geometry);
37
- result.push(...new Array(entry.instanceMatrix.count).fill(undefined).map((_, i) => ({
49
+ result.push(...Array.from({ length: entry.instanceMatrix.count }, (_, instanceIndex) => ({
38
50
  object: entry,
39
51
  bvh,
40
- instanceIndex: i,
52
+ instanceIndex,
41
53
  isStatic,
42
54
  })));
43
55
  return;
44
56
  }
45
57
  if (entry instanceof Mesh) {
46
- hasNonInstancedMeshes = true;
58
+ const signature = geometryAttributeSignature(entry.geometry);
59
+ let group = meshGroups.get(signature);
60
+ if (group == null) {
61
+ meshGroups.set(signature, (group = []));
62
+ }
63
+ group.push(entry);
47
64
  }
48
65
  });
49
- if (hasNonInstancedMeshes) {
66
+ if (meshGroups.size > 0) {
50
67
  const parent = object.parent;
51
68
  if (!isStatic) {
52
69
  object.parent = null;
53
70
  object.updateMatrixWorld(true);
54
71
  }
55
- const geometry = new StaticGeometryGenerator(object).generate();
56
- const bvh = computeBoundsTree.apply(geometry);
72
+ for (const meshes of meshGroups.values()) {
73
+ const geometry = new StaticGeometryGenerator(meshes).generate();
74
+ const bvh = computeBoundsTree.apply(geometry);
75
+ result.push({ object, bvh, isStatic });
76
+ }
57
77
  if (!isStatic) {
58
78
  object.parent = parent;
59
79
  object.updateMatrixWorld(true);
60
80
  }
61
- result.push({ object, bvh, isStatic });
62
81
  }
63
82
  return result;
64
83
  }
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "peerDependencies": {
22
22
  "three": "*"
23
23
  },
24
- "version": "0.2.23",
24
+ "version": "0.2.25",
25
25
  "type": "module",
26
26
  "dependencies": {
27
27
  "@pixiv/three-vrm": "^3.4.2",