Skip to content

Commit 4a6e671

Browse files
alex-shorttstevenpaulhowardspacesvr
authored
Merge pull request #101 from musehq/dev
* Refactor Image component + add ktx support (#99) * Refactor Image component + add ktx support * Make Texture Component more general * export index type & clean up syntax Co-authored-by: Alex Shortt <alexander.shortt@gmail.com> Co-authored-by: Muse <40903382+spacesvr@users.noreply.github.com> * Collidable Modifier + Refactors (#100) * add collidable modifier * replace bvh logic with geo simplifier * add optional raycaster prop to interactable modifier * slight refactors to image idea * fix up exports * clean up keyboard layout code, don't run check if in iframe * v2.1.0 Co-authored-by: stevenpaulhoward <40086690+stevenpaulhoward@users.noreply.github.com> Co-authored-by: Muse <40903382+spacesvr@users.noreply.github.com>
2 parents ad6ef46 + fc5936e commit 4a6e671

File tree

16 files changed

+942
-48
lines changed

16 files changed

+942
-48
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,18 @@ An infinite floor styled to the Lost World.
233233

234234
### /modifiers/
235235

236+
#### Collidable
237+
238+
Enables colliders for its children either by a named collider mesh or using all meshes and capping collective triangle count to triLimit prop.
239+
240+
```tsx
241+
<Collidable
242+
triLimit={1000} // max number of triangles before it uses bvh
243+
enabled={true}
244+
hideCollisionMeshes={false} // set visible to false on meshes used for collision
245+
/>
246+
```
247+
236248
#### FacePlayer
237249

238250
Turns its children into a billboard, always facing the camera.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "spacesvr",
3-
"version": "2.0.5",
3+
"version": "2.1.0",
44
"private": true,
55
"description": "A standardized reality for future of the 3D Web",
66
"keywords": [

src/ideas/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1+
export * from "./mediated/LostFloor";
2+
export * from "./modifiers/Collidable";
3+
export * from "./modifiers/Interactable";
14
export * from "./modifiers/FacePlayer";
2-
export * from "./modifiers/LookAtPlayer";
35
export * from "./modifiers/Floating";
4-
export * from "./modifiers/Interactable";
6+
export * from "./modifiers/LookAtPlayer";
57
export * from "./modifiers/Spinning";
68
export * from "./modifiers/Tool";
9+
export * from "./physical/Image";
710
export * from "./physical/Arrow";
811
export * from "./physical/Audio";
912
export * from "./physical/Background";
1013
export * from "./physical/Fog";
1114
export * from "./physical/Frame";
1215
export * from "./physical/HDRI";
13-
export * from "./physical/Image";
1416
export * from "./physical/InfinitePlane";
1517
export * from "./physical/Video";
16-
export * from "./mediated/LostFloor";
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { BufferGeometry, Euler, Group, Quaternion, Vector3 } from "three";
2+
import { useMemo, useRef, useState } from "react";
3+
import { useLimiter } from "../../../../logic/limiter";
4+
import { useFrame } from "@react-three/fiber";
5+
import { useTrimeshCollision } from "../utils/trimesh";
6+
7+
type TrimeshColliderProps = {
8+
geo: BufferGeometry;
9+
};
10+
11+
export default function TrimeshCollider(props: TrimeshColliderProps) {
12+
const { geo } = props;
13+
14+
const group = useRef<Group>(null);
15+
16+
const dummyPos = useMemo(() => new Vector3(), []);
17+
const dummyQuat = useMemo(() => new Quaternion(), []);
18+
const dummyEuler = useMemo(() => new Euler(), []);
19+
const dummyScale = useMemo(() => new Vector3(1, 1, 1), []);
20+
const [scale, setScale] = useState(new Vector3(1, 1, 1));
21+
22+
const geometry = useMemo(() => {
23+
const g = geo.clone().scale(scale.x, scale.y, scale.z);
24+
g.computeVertexNormals();
25+
return g;
26+
}, [geo, scale]);
27+
28+
const [, api] = useTrimeshCollision(geometry);
29+
30+
const limiter = useLimiter(5);
31+
useFrame(({ clock }) => {
32+
if (!limiter.isReady(clock) || !group.current) return;
33+
34+
group.current.getWorldPosition(dummyPos);
35+
group.current.getWorldQuaternion(dummyQuat);
36+
group.current.getWorldScale(dummyScale);
37+
38+
api.position.copy(dummyPos);
39+
api.rotation.copy(dummyEuler.setFromQuaternion(dummyQuat));
40+
if (!dummyScale.equals(scale)) {
41+
setScale(dummyScale.clone());
42+
}
43+
});
44+
45+
return <group ref={group} />;
46+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { ReactNode, useEffect, useRef, useState } from "react";
2+
import { BufferGeometry, Group, Mesh } from "three";
3+
import {
4+
findColliderMeshes,
5+
getGeometryTriCount,
6+
getMeshes,
7+
getMeshesUUID,
8+
getTransformedMeshGeo,
9+
} from "./utils/mesh";
10+
import TrimeshCollider from "./components/TrimeshCollider";
11+
import { generateSimplifiedGeo } from "./utils/simplify";
12+
13+
type CollidableProps = {
14+
children: ReactNode | ReactNode[];
15+
triLimit?: number;
16+
enabled?: boolean;
17+
hideCollisionMeshes?: boolean;
18+
};
19+
20+
export default function Collidable(props: CollidableProps) {
21+
const {
22+
children,
23+
triLimit = 1000,
24+
enabled = true,
25+
hideCollisionMeshes = false,
26+
} = props;
27+
28+
const group = useRef<Group>(null);
29+
const geoUUID = useRef<string>();
30+
const [collisionMeshes, setCollisionMeshes] = useState<Mesh[]>();
31+
const [collisionGeos, setCollisionGeos] = useState<BufferGeometry[]>();
32+
33+
// func to create uuid to know when to regenerate
34+
const createUUID = (meshes: Mesh[]) => triLimit + "-" + getMeshesUUID(meshes);
35+
36+
// register collision meshes and collision geos
37+
useEffect(() => {
38+
if (!group.current || !enabled) {
39+
setCollisionGeos(undefined);
40+
setCollisionMeshes(undefined);
41+
geoUUID.current = undefined;
42+
return;
43+
}
44+
45+
// if the user names the meshes themselves, give them full control
46+
const colliderMeshes = findColliderMeshes(group.current);
47+
if (colliderMeshes) {
48+
if (createUUID(colliderMeshes) === geoUUID.current) return;
49+
setCollisionMeshes(colliderMeshes);
50+
const geos = colliderMeshes.map((m) =>
51+
getTransformedMeshGeo(m, group.current as Group)
52+
);
53+
setCollisionGeos(geos);
54+
geoUUID.current = createUUID(colliderMeshes);
55+
return;
56+
}
57+
58+
// otherwise, use all the meshes in the model
59+
const meshes = getMeshes(group.current);
60+
if (createUUID(meshes) === geoUUID.current) return;
61+
setCollisionMeshes(meshes);
62+
geoUUID.current = createUUID(meshes);
63+
64+
// aggregate geos in the model
65+
const geos = meshes.map((m) =>
66+
getTransformedMeshGeo(m, group.current as Group)
67+
);
68+
69+
// either use geo directly or bvh version, depending on tri count
70+
const triCount = geos.reduce((c, g) => c + getGeometryTriCount(g), 0);
71+
if (triCount < triLimit) {
72+
setCollisionGeos(geos);
73+
} else {
74+
const perc = triLimit / triCount;
75+
const simpGeos = geos
76+
.map((g) => generateSimplifiedGeo(g, getGeometryTriCount(g) * perc))
77+
.filter((g) => g) as BufferGeometry[];
78+
setCollisionGeos(simpGeos);
79+
}
80+
}, [children, triLimit, enabled]);
81+
82+
// hide or show collision meshes
83+
useEffect(() => {
84+
if (!collisionMeshes) return;
85+
collisionMeshes.map(
86+
(collider) => (collider.visible = !hideCollisionMeshes)
87+
);
88+
return () => {
89+
collisionMeshes.map((collider) => (collider.visible = true));
90+
};
91+
}, [collisionMeshes, hideCollisionMeshes]);
92+
93+
return (
94+
<group name="collidable" ref={group}>
95+
{children}
96+
{enabled &&
97+
collisionGeos &&
98+
collisionGeos.map((geo, i) => (
99+
<TrimeshCollider key={geo.uuid} geo={geo} />
100+
))}
101+
</group>
102+
);
103+
}

0 commit comments

Comments
 (0)