7.2 Example codes (11~20)
Click the Open Sandbox button. Then, you can run all the examples on this page by changing the value of the variable example in App.jsx.
import React, { useRef } from "react";
import { useFrame, useRefEffect } from "threefy";
import {
gridCoords,
randomHSLColor,
randomMatrix,
transformMatrix,
} from "./ThreeUtils";
import * as THREE from "three";
import crate_gif from "../public/images/crate.gif";
import brickWall_diffuse_jpg from "../public/images/brickWall/diffuse.jpg";
import brickWall_normal_jpg from "../public/images/brickWall/normal.jpg";
const DemoInstMesh = () => {
const instCount = 1000;
const ref = useRefEffect((instMesh) => {
for (let i = 0; i < instCount; i++) {
const m = randomMatrix(40, 40, 40, 0.5, 1);
const c = randomHSLColor();
instMesh.setMatrixAt(i, m);
instMesh.setColorAt(i, c);
}
});
useFrame((t) => {
if (ref.current) {
ref.current.rotation.x = Math.sin(t * 1.2);
ref.current.rotation.y = Math.sin(t * 2.4);
}
});
return (
<instancedMesh ref={ref} count={instCount}>
<boxGeometry />
<meshStandardMaterial />
</instancedMesh>
);
};
const DemoInstMesh2 = () => {
const amount = 10;
const example = 1;
if (example === 1) {
const updateInstMesh = (instMesh, t = 0) => {
const posFn = (x, y, z, t) => [x, y, z];
const rotFn = (x, y, z, t) => {
const ry =
Math.sin(x / 4 + t) + Math.sin(y / 4 + t) + Math.sin(z / 4 + t);
return [0, ry, ry * 2];
};
gridCoords(amount, 1, 3).map((p, i) => {
const trfm = transformMatrix([...p, t], posFn, rotFn);
instMesh.setMatrixAt(i, trfm);
const col = randomHSLColor(0.95, 0.1, 0.8, 0, 0.6, 0.4);
instMesh.setColorAt(i, col);
});
instMesh.instanceMatrix.needsUpdate = true;
};
// initial update
const ref = useRefEffect((instMesh) => updateInstMesh(instMesh, 0));
// dynamic update
useFrame((t) => ref.current && updateInstMesh(ref.current, t));
return (
<instancedMesh ref={ref} count={Math.pow(amount, 3)}>
<boxGeometry args={[0.6, 0.6, 0.6]} />
<meshStandardMaterial />
</instancedMesh>
);
}
if (example === 2) {
const ref = useRef(null);
const createMatrices = (amount, t = 0) => {
const trfArray = [];
const posFn = (x, y, z, t) => [x, y, z];
const rotFn = (x, y, z, t) => {
const ry =
Math.sin(x / 4 + t) + Math.sin(y / 4 + t) + Math.sin(z / 4 + t);
return [0, ry, ry * 2];
};
gridCoords(amount, 1, 3).map((p) => {
const trfm = transformMatrix([...p, t], posFn, rotFn);
trfArray.push(trfm.toArray());
});
return Float32Array.from(trfArray.flat());
};
const createColors = (amount) => {
const instCount = Math.pow(amount, 3);
return Float32Array.from(
Array(instCount)
.fill()
.flatMap((x) =>
randomHSLColor(0.25, 0.1, 0.9, 0.0, 0.6, 0.4).toArray()
)
);
};
const instCount = Math.pow(amount, 3);
const matrixArray = createMatrices(amount);
const colorArray = createColors(amount);
useFrame((t) => {
// (cf) threefy = { scene, camera, renderer }
if (ref.current) {
ref.current.instanceMatrix.array = createMatrices(amount, t);
ref.current.instanceMatrix.needsUpdate = true;
}
});
return (
<instancedMesh ref={ref} count={instCount}>
<instancedBufferAttribute
attach={"instanceMatrix"}
args={[matrixArray, 16]}
/>
<instancedBufferAttribute
attach={"instanceColor"}
args={[colorArray, 3]}
/>
<boxGeometry args={[0.6, 0.6, 0.6]} />
<meshStandardMaterial />
</instancedMesh>
);
}
};
const DemoInstBufferGeometry = () => {
const amount = 10;
const ref = useRef(null);
const createOffsets = (amount, t = 0) => {
return Float32Array.from(gridCoords(amount, 1, 3).flat());
};
const createColors = (amount) => {
const instCount = Math.pow(amount, 3);
return Float32Array.from(
Array(instCount)
.fill()
.flatMap((x) => randomHSLColor(0.5, 0.1, 0.8, 0, 0.6, 0.4).toArray())
);
};
const createMatrices = (amount, t = 0) => {
const trfArray = [];
const rotFn = (x, y, z, t) => {
const ry =
Math.sin(x / 4 + t) + Math.sin(y / 4 + t) + Math.sin(z / 4 + t);
return [0, ry, ry * 2];
};
gridCoords(amount, 1, 3).map((p) => {
const trfm = transformMatrix([...p, t], undefined, rotFn);
trfArray.push(trfm.toArray());
});
return Float32Array.from(trfArray.flat());
};
const onBeforeCompile = (shader) => {
shader.vertexShader = shader.vertexShader
.replace(
// before main() (for attribute or varying)
"#include <common>",
`#include <common>
attribute vec3 offset;
attribute vec3 color;
attribute mat4 matrix;
varying vec3 vColor;`
)
.replace(
// inside main() (for varying)
"#include <uv_vertex>",
`#include <uv_vertex>
vColor = color;`
)
.replace(
// inside main() (for vertex transform)
"#include <begin_vertex>",
`#include <begin_vertex>
vNormal = mat3(matrix) * normal;
vNormal = normalize(vNormal);
transformed = (matrix * vec4(transformed, 1.0)).xyz;
transformed = transformed + offset;`
);
shader.fragmentShader = shader.fragmentShader
.replace(
// before main() (for uniform or varying)
"#include <common>",
`#include <common>
varying vec3 vColor;`
)
.replace(
// inside main() (for coloring or textures)
"vec4 diffuseColor = vec4( diffuse, opacity );",
`vec4 diffuseColor = vec4( diffuse, opacity );
diffuseColor.rgb *= vColor.rgb*3.0;`
);
};
useFrame((t) => {
// (cf) threefy = { scene, camera, renderer }
if (ref.current) {
const mesh = ref.current;
const matrixAttrib = mesh.geometry.attributes.matrix;
matrixAttrib.array = createMatrices(amount, t);
matrixAttrib.needsUpdate = true;
}
});
const instCount = Math.pow(amount, 3);
const boxGeometry = new THREE.BoxGeometry(0.6, 0.6, 0.6).toNonIndexed();
const positionArray = boxGeometry.attributes.position.array;
const normalArray = boxGeometry.attributes.normal.array;
const uvArray = boxGeometry.attributes.uv.array;
const offsetArray = createOffsets(amount);
const colorArray = createColors(amount);
const matrixArray = createMatrices(amount);
return (
<mesh ref={ref}>
<instancedBufferGeometry instanceCount={instCount}>
<bufferAttribute
attach={"attributes-position"}
args={[positionArray, 3]}
/>
<bufferAttribute attach={"attributes-normal"} args={[normalArray, 3]} />
<bufferAttribute attach={"attributes-uv"} args={[uvArray, 2]} />
<instancedBufferAttribute
attach={"attributes-offset"}
args={[offsetArray, 3]}
/>
<instancedBufferAttribute
attach={"attributes-color"}
args={[colorArray, 3]}
/>
<instancedBufferAttribute
attach={"attributes-matrix"}
args={[matrixArray, 16]}
/>
</instancedBufferGeometry>
<meshStandardMaterial
map={brickWall_diffuse_jpg}
onBeforeCompile={onBeforeCompile}
/>
</mesh>
);
};
const DemoBatchedMesh = () => {
const amount = 512;
const useBatchedMaterial = true;
const rotationMatrices = [];
const roughnessValues = [];
const metalnessValues = [];
const geometries = [
new THREE.ConeGeometry(1.0, 2.0),
new THREE.BoxGeometry(2.0, 2.0, 2.0),
new THREE.SphereGeometry(1.0, 16, 8),
];
// 1) initial setting
const rotation = new THREE.Euler();
const randomRotation = () => {
rotation.x = Math.random() * 0.1;
rotation.y = Math.random() * 0.1;
rotation.z = Math.random() * 0.1;
return rotation;
};
for (let i = 0; i < amount; i++) {
// rotation
const matrix = new THREE.Matrix4();
matrix.makeRotationFromEuler(randomRotation());
rotationMatrices.push(matrix);
if (useBatchedMaterial) {
// roughness
// let roughness = 0.6 + Math.random() * 0.1;
// roughnessValues.push( roughness );
roughnessValues.push(0);
// metalness
// let metalness = Math.random();
// metalness = metalness < 0.1 ? 0.5 : 0;
// metalnessValues.push( metalness );
metalnessValues.push(0);
}
}
// 2) initial update after creation
const ids = [];
const ref = useRefEffect((batchMesh) => {
// geometry
const geometryIds = [
batchMesh.addGeometry(geometries[0]),
batchMesh.addGeometry(geometries[1]),
batchMesh.addGeometry(geometries[2]),
];
const material = batchMesh.material;
for (let i = 0; i < amount; i++) {
// instance
const id = batchMesh.addInstance(geometryIds[i % geometryIds.length]);
ids.push(id);
// matrix
batchMesh.setMatrixAt(id, randomMatrix(40, 40, 40, 0.5, 1));
if (useBatchedMaterial) {
// roughness & metalness
material.setValue(id, "roughness", roughnessValues[i]);
material.setValue(id, "metalness", metalnessValues[i]);
}
}
});
// 3) update the mesh every frame
const matrix = new THREE.Matrix4();
const color = new THREE.Color();
const c0 = new THREE.Color().setHSL(
Math.random() * 0.05,
1,
Math.random() * 0.2 + 0.5
);
const c1 = new THREE.Color().setHSL(
Math.random() * 0.05 + 0.6,
1,
Math.random() * 0.2 + 0.5
);
useFrame((t) => {
if (!ref.current) return;
const batchMesh = ref.current;
const material = batchMesh.material;
for (let i = 0; i < amount; i++) {
const id = ids[i]; // id = instanceId
// matrix
batchMesh.getMatrixAt(id, matrix);
batchMesh.setMatrixAt(id, matrix.multiply(rotationMatrices[i]));
if (useBatchedMaterial) {
// roughness & metalness
material.setValue(id, "roughness", roughnessValues[i]);
material.setValue(id, "metalness", metalnessValues[i]);
// diffuse
color.lerpColors(c0, c1, Math.sin(1.5 * t) * 0.5 + i / amount);
material.setValue(id, "diffuse", ...color);
// emissive
let intensity = 0; //0.01 + 0.01*Math.sin(0.5*t + i/2);
color.multiplyScalar(intensity);
material.setValue(id, "emissive", ...color);
}
}
});
return (
<batchedMesh ref={ref} args={[amount, amount * 512, amount * 1024]}>
{!useBatchedMaterial && <meshStandardMaterial map={crate_gif} />}
{useBatchedMaterial && (
<batchedMaterial args={[amount]} map={crate_gif} />
)}
</batchedMesh>
);
};
const DemoBatchedMesh2 = () => {
const example = 2;
const count = 1000;
const lightRef = useRef(null);
// PointLight
const PointLight = (props) => {
const { ref = useRef(null), args, ..._props } = props;
return (
<pointLight ref={ref} args={args}>
<sphere {..._props} />
</pointLight>
);
};
// 1) initial update to finalize mesh setup
const ref = useRefEffect((batchMesh) => {
const instIds = [];
if (example === 1) {
// addGeometry & addInstance <== from attached (geometries)
batchMesh.geometries.forEach((geometry) => {
const geomId = batchMesh.addGeometry(geometry);
const instId = batchMesh.addInstance(geomId);
instIds.push(instId);
});
delete batchMesh.geometries; // remove geometries temporarily created
}
if (example === 2) {
// addGeometry & addInstance <== from children (object3Ds)
batchMesh.children.forEach((child) => {
const geometry = child.geometry;
const geomId = batchMesh.addGeometry(geometry);
const instId = batchMesh.addInstance(geomId);
instIds.push(instId);
});
batchMesh.children.length = 0; // remove children temporarily created
}
const material = batchMesh.material;
for (let i = 0; i < count; i++) {
const id = instIds[i];
batchMesh.setMatrixAt(id, randomMatrix(200, 200, 200, 3, 8));
material.setValue(id, "diffuse", ...randomHSLColor());
material.setValue(id, "roughness", 0);
material.setValue(id, "metalness", 0);
}
});
// 2) update the mesh every frame
useFrame((t) => {
if (ref.current) {
const batchMesh = ref.current;
batchMesh.rotation.x = t * 0.25;
batchMesh.rotation.y = t * 0.5;
batchMesh.scale.setScalar(0.7 + 0.5 * Math.sin(t * 0.5));
}
if (lightRef.current) {
const light = lightRef.current;
light.position.x = 20 * Math.sin(t);
light.position.y = 10 * Math.cos(t);
light.position.z = 20 * Math.sin(t);
}
});
// cf: maxLimit
// (count, maxVertexCount, maxIndexCount) = (10,000, 65,536,000, 65,536,000*2)
return (
<>
<PointLight
ref={lightRef}
args={[0xffffff, Math.PI * 50, 50, 0.5]}
scale={0.5}
emissive={"white"}
/>
<batchedMesh ref={ref} args={[count, 6553600, 6553600 * 2]}>
{/* using attach */}
{example === 1 &&
Array(count)
.fill(0)
.map((x, i) => {
if (i < (count * 1) / 5)
return <boxGeometry key={i} attach={`geometries-${i}`} />;
else if (i < (count * 2) / 5)
return (
<sphereGeometry
key={i}
attach={`geometries-${i}`}
args={[0.7]}
/>
);
else if (i < (count * 3) / 5)
return (
<torusKnotGeometry
key={i}
attach={`geometries-${i}`}
args={[0.7, 0.25]}
/>
);
else if (i < (count * 4) / 5)
return (
<coneGeometry
key={i}
attach={`geometries-${i}`}
args={[0.5, 1]}
/>
);
else
return (
<cylinderGeometry
key={i}
attach={`geometries-${i}`}
args={[0.4, 0.4, 1]}
/>
);
})}
{/* using children */}
{example === 2 &&
Array(count)
.fill(0)
.map((x, i) => {
if (i < (count * 1) / 5) return <box key={i} />;
else if (i < (count * 2) / 5)
return <sphere key={i} args={[0.7]} />;
else if (i < (count * 3) / 5)
return <torusKnot key={i} args={[0.7, 0.25]} />;
else if (i < (count * 4) / 5)
return <cone key={i} args={[0.5, 1]} />;
else return <cylinder key={i} args={[0.4, 0.4, 1]} />;
})}
<batchedMaterial
args={[count]}
map={brickWall_diffuse_jpg}
normalMap={brickWall_normal_jpg}
/>
</batchedMesh>
</>
);
};
const DemoDashProps = () => {
// access to nested property (eg: mesh.scale.x)
return (
<mesh scale-x={10} scale-y={5} rotation-z={Math.PI / 3}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
);
};
const DemoAttachMaterials = () => {
const cols = [0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff, 0x00ffff];
return (
<mesh scale={5}>
<boxGeometry attach="geometry" />
{cols.map((col, index) => (
<meshBasicMaterial
key={index}
color={col}
attach={`material-${index}`}
/>
))}
</mesh>
);
};
const DemoBufferGeometry = () => {
function rotate(dt, ct) {
this.rotation.x = Math.sin(ct * 0.25);
this.rotation.y = Math.sin(ct * 0.5);
}
const randomTriangles = () => {
const triangles = 16000;
let positions = [];
let normals = [];
let colors = [];
const color = new THREE.Color();
const n = 200,
n2 = n / 2; // triangles spread in the cube
const d = 3,
d2 = d / 2; // individual triangle size
const pA = new THREE.Vector3();
const pB = new THREE.Vector3();
const pC = new THREE.Vector3();
const cb = new THREE.Vector3();
const ab = new THREE.Vector3();
for (let i = 0; i < triangles; i++) {
// positions
const x = Math.random() * n - n2;
const y = Math.random() * n - n2;
const z = Math.random() * n - n2;
const ax = x + Math.random() * d - d2;
const ay = y + Math.random() * d - d2;
const az = z + Math.random() * d - d2;
const bx = x + Math.random() * d - d2;
const by = y + Math.random() * d - d2;
const bz = z + Math.random() * d - d2;
const cx = x + Math.random() * d - d2;
const cy = y + Math.random() * d - d2;
const cz = z + Math.random() * d - d2;
positions.push(ax, ay, az);
positions.push(bx, by, bz);
positions.push(cx, cy, cz);
// flat face normals
pA.set(ax, ay, az);
pB.set(bx, by, bz);
pC.set(cx, cy, cz);
cb.subVectors(pC, pB);
ab.subVectors(pA, pB);
cb.cross(ab);
cb.normalize();
const nx = cb.x;
const ny = cb.y;
const nz = cb.z;
normals.push(nx, ny, nz);
normals.push(nx, ny, nz);
normals.push(nx, ny, nz);
// colors
const vx = x / n + 0.5;
const vy = y / n + 0.5;
const vz = z / n + 0.5;
color.setRGB(vx, vy, vz);
const alpha = Math.random();
colors.push(color.r, color.g, color.b, alpha);
colors.push(color.r, color.g, color.b, alpha);
colors.push(color.r, color.g, color.b, alpha);
}
positions = Float32Array.from(positions);
normals = Float32Array.from(normals);
colors = Float32Array.from(colors);
return { positions, normals, colors };
};
const { positions, normals, colors } = randomTriangles();
return (
<mesh update={rotate}>
<bufferGeometry>
<bufferAttribute attach="attributes-position" args={[positions, 3]} />
<bufferAttribute attach="attributes-normal" args={[normals, 3]} />
<bufferAttribute attach="attributes-color" args={[colors, 4]} />
</bufferGeometry>
<meshStandardMaterial
color={0xd5d5d5}
side={THREE.DoubleSide}
vertexColors
transparent
/>
</mesh>
);
};
const DemoPrimitive = () => {
const example = 2; // two examples produce the same result
if (example === 1) {
const g = new THREE.BoxGeometry();
const m = new THREE.MeshStandardMaterial();
const mesh = new THREE.Mesh(g, m);
return <primitive object={mesh} scale={20} />;
}
if (example === 2) {
const g = new THREE.BoxGeometry();
const m = new THREE.MeshStandardMaterial();
return (
<mesh scale={20}>
<primitive attach="geometry" object={g} />
<primitive attach="material" object={m} />
</mesh>
);
}
};
const DemoEvents = () => {
return (
<mesh
scale={10}
onClick={(e, mesh) => console.log("onClick:", e.type, mesh)}
onContextMenu={(e, mesh) => console.log("onContextMenu:", e.type, mesh)}
onDoubleClick={(e, mesh) => console.log("onDoubleClick:", e.type, mesh)}
onWheel={(e, mesh) => console.log("onWheel:", e.type, mesh)}
onPointerMove={(e, mesh) => console.log("onPointerMove:", e.type, mesh)}
onPointerEnter={(e, mesh) => console.log("onPointerEnter:", e.type, mesh)}
onPointerLeave={(e, mesh) => console.log("onPointerLeave:", e.type, mesh)}
onPointerDown={(e, mesh) => console.log("onPointerDown:", e.type, mesh)}
onPointerUp={(e, mesh) => console.log("onPointerUp:", e.type, mesh)}
>
<boxGeometry />
<meshStandardMaterial color={"red"} />
</mesh>
);
};
const DemoExamples = ({ example }) => {
switch (example) {
case 11:
return <DemoInstMesh />;
case 12:
return <DemoInstMesh2 />;
case 13:
return <DemoInstBufferGeometry />;
case 14:
return <DemoBatchedMesh />;
case 15:
return <DemoBatchedMesh2 />;
case 16:
return <DemoDashProps />;
case 17:
return <DemoAttachMaterials />;
case 18:
return <DemoBufferGeometry />;
case 19:
return <DemoPrimitive />;
case 20:
return <DemoEvents />;
}
};
export { DemoExamples };
Last updated