|
import T, { applyEventProps, useEngine, useOnUpdate } from "@hmans/trinity" |
|
import React, { FC, RefObject, useEffect, useRef } from "react" |
|
import * as THREE from "three" |
|
import { ManagedInstancedMesh, useInstance, useInstances } from "../trinity-extras/instances" |
|
import { ManagedInstancedMeshComponent } from "../trinity-extras/instances/trinity" |
|
import { inside, insideUnitSphere, pick } from "../trinity-math/Random" |
|
import { make } from "../wurlde/tools" |
|
import { DemoSetup } from "./DemoSetup" |
|
import { getSimplexVector } from "./simplex" |
|
|
|
export const DodecahedronCloud = ({ rotationPerSecond = 0.5 }) => { |
|
const { triggerFrame } = useEngine() |
|
|
|
const imeshRef = useRef<ManagedInstancedMesh>(null) |
|
|
|
/* Animate the whole InstancedMesh */ |
|
useOnUpdate((dt) => { |
|
imeshRef.current!.rotateY(rotationPerSecond * dt) |
|
triggerFrame() |
|
}) |
|
|
|
return ( |
|
<ManagedInstancedMeshComponent ref={imeshRef} maxInstances={6000} castShadow receiveShadow> |
|
<T.DodecahedronBufferGeometry /> |
|
<T.MeshStandardMaterial /> |
|
|
|
<InstanceSwarm |
|
imeshRef={imeshRef} |
|
count={2000} |
|
offset={2000} |
|
colors={[new THREE.Color("#111"), new THREE.Color("#c22")]} |
|
/> |
|
|
|
<InstanceSwarm |
|
imeshRef={imeshRef} |
|
count={2000} |
|
offset={3000} |
|
colors={[new THREE.Color("#eee"), new THREE.Color("#393")]} |
|
/> |
|
|
|
{make(2000, (i) => ( |
|
<SingleInstance key={i} imeshRef={imeshRef} /> |
|
))} |
|
</ManagedInstancedMeshComponent> |
|
) |
|
} |
|
|
|
const singleInstanceColors = [ |
|
new THREE.Color("#111"), |
|
new THREE.Color("#333"), |
|
new THREE.Color("#555") |
|
] |
|
|
|
const SingleInstance: FC<{ imeshRef: RefObject<ManagedInstancedMesh> }> = ({ imeshRef }) => { |
|
/* Reference to a scene object we will be rendering below */ |
|
const ref = useRef<THREE.Object3D>(null) |
|
|
|
/* Create an instance with a random color. The factory function will run as a side-effect |
|
after React is done rendering, so we're safe to use our refs here. */ |
|
const instanceRef = useInstance(imeshRef, () => ({ |
|
object: ref.current!, |
|
color: pick(singleInstanceColors) |
|
})) |
|
|
|
/* Animate the instance on every frame */ |
|
useOnUpdate((dt) => { |
|
const imesh = imeshRef.current! |
|
const instance = instanceRef.current! |
|
const object = ref.current! |
|
|
|
/* Note we're just modifying the facade object directly. */ |
|
object.rotation.x = object.rotation.z += dt |
|
|
|
/* We do have to tell the managed imesh that this instance has updated, though. */ |
|
imesh.updateInstance(instance) |
|
}) |
|
|
|
/* Here's our scene object that acts as a facade for the actual instance. Use it like |
|
any other scene object. */ |
|
return ( |
|
<T.Object3D |
|
ref={ref} |
|
position={insideUnitSphere().multiplyScalar(50).toArray()} |
|
scale={Math.pow(inside(0.3, 1), 5)} |
|
/* |
|
Pointer events can be created on the facade object like on any other scene object! |
|
*/ |
|
onPointerEnter={(e) => { |
|
const imesh = imeshRef.current! |
|
const instance = instanceRef.current! |
|
|
|
instance.data.oldColor = instance.color |
|
|
|
imesh.updateInstance(instance, { |
|
color: new THREE.Color("hotpink") |
|
}) |
|
}} |
|
onPointerLeave={(e) => { |
|
const imesh = imeshRef.current! |
|
const instance = instanceRef.current! |
|
|
|
imesh.updateInstance(instance, { |
|
color: instance.data.oldColor |
|
}) |
|
}} |
|
/> |
|
) |
|
} |
|
|
|
/* |
|
Here's an example of a component that imperatively creates and animates a whole bunch |
|
of instances (instead of rendering a React component for each, which would become problematic |
|
as soon as you want to render tens of thousands of them.) |
|
*/ |
|
const InstanceSwarm: FC<{ |
|
count?: number |
|
offset?: number |
|
imeshRef: RefObject<ManagedInstancedMesh> |
|
colors?: THREE.Color[] |
|
}> = ({ |
|
count = 100, |
|
offset = 0, |
|
colors = [new THREE.Color("#222"), new THREE.Color("#c22")], |
|
imeshRef |
|
}) => { |
|
const instancesRef = useInstances(count, imeshRef, () => { |
|
const object = new THREE.Object3D() |
|
object.position.copy(insideUnitSphere().multiplyScalar(50)) |
|
object.scale.setScalar(inside(0.05, 0.3)) |
|
|
|
const color = pick(colors) |
|
|
|
return { object, color } |
|
}) |
|
|
|
/* Assign event handlers */ |
|
useEffect(() => { |
|
const imesh = imeshRef.current! |
|
|
|
for (const instance of instancesRef.current) { |
|
applyEventProps(instance.object, { |
|
onPointerEnter: () => { |
|
instance.data.oldColor = instance.color |
|
|
|
imesh.updateInstance(instance, { |
|
color: new THREE.Color("hotpink") |
|
}) |
|
}, |
|
onPointerLeave: () => { |
|
imesh.updateInstance(instance, { |
|
color: instance.data.oldColor |
|
}) |
|
} |
|
}) |
|
} |
|
}) |
|
|
|
/* Animate instances */ |
|
let accTime = 0 |
|
const offsetPosition = new THREE.Vector3() |
|
const rotation = new THREE.Vector3() |
|
|
|
useOnUpdate((dt) => { |
|
accTime += dt |
|
const instances = instancesRef.current |
|
const imesh = imeshRef.current! |
|
|
|
/* Animate the instances */ |
|
for (let i = 0; i < instances.length; i++) { |
|
const instance = instances[i] |
|
const t = (accTime + i * 0.0001) * 0.2 |
|
|
|
/* Offset */ |
|
getSimplexVector(t * 0.5, offset + i * 200 + t, offsetPosition).multiplyScalar( |
|
5 * Math.pow(Math.sin(i), 3) |
|
) |
|
|
|
/* Position */ |
|
getSimplexVector(t, offset, instance.object.position).multiplyScalar(10).add(offsetPosition) |
|
|
|
/* Rotation */ |
|
getSimplexVector(t * 2, offset + i * 200 + t, rotation) |
|
instance.object.rotation.set(rotation.x, rotation.y, rotation.z) |
|
|
|
imesh.updateInstance(instance) |
|
} |
|
}) |
|
|
|
return null |
|
} |
|
|
|
export const InstancedMeshesDemo = () => ( |
|
<DemoSetup shadowMapSize={20}> |
|
<DodecahedronCloud /> |
|
</DemoSetup> |
|
) |