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