7.4 Example codes (31~40)

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, useState } from "react";
import { useFrame, useLoader, useRefEffect, useThree } from "threefy";
import {
  grid3D,
  gridCoords,
  randomHSLColor,
  transformMatrix,
} from "./ThreeUtils";
import { InstancedModel, Model } from "./ThreeModels";
import { Grass, Sky, SkyDome, Terrain, Water } from "./ThreeNatures";
import * as THREE from "three";
import * as SkeletonUtils from "three/examples/jsm/utils/SkeletonUtils";

import crate_gif from "../public/images/crate.gif";
import brickWall_diffuse_jpg from "../public/images/brickWall/diffuse.jpg";
import moss_diffuse_jpg from "../public/images/moss/diffuse.jpg";
import hdr_overcast_soil_puresky_2k_hdr from "../public/images/hdr/overcast_soil_puresky_2k.hdr";
import terrain_Rugged_Terrain_diffuse_jpg from "../public/images/terrain/Rugged Terrain/diffuse.jpg";
import terrain_Rugged_Terrain_heightmap_jpg from "../public/images/terrain/Rugged Terrain/heightmap.jpg";

import jump_animation_glb from "../public/models/jump-animation.glb";
import Cow_zip from "../public/models/Cow.zip";
import blackPearl_zip from "../public/models/blackPearl.zip";
import LeePerrySmith_glb from "../public/models/LeePerrySmith.glb";

const DemoModelLoading = () => {
  const [model, setModel] = useState(null);

  if (!model) {
    const onLoad = (model) => setModel(model);
    return <Model url={jump_animation_glb} onLoad={onLoad} />;
  } else {
    // remove model from scene
    const removeFromScene = (model) => {
      if (!model) return undefined;
      const parent = model.parent;
      if (parent) {
        parent.remove(model);
        model.parent = null;
      }
      return parent;
    };
    removeFromScene(model);

    const dim = 3; //2;
    const n = 10;
    const dx = 2;
    const x0 = (n / 2 - 0.5) * dx;

    if (dim === 1) {
      return (
        <>
          {grid1D(-x0, x0, dx).map((p, i) => (
            <primitive
              key={i}
              object={SkeletonUtils.clone(model)}
              position={[p, 0, 0]}
            />
          ))}
        </>
      );
    }

    if (dim === 2) {
      return (
        <>
          {grid2D(-x0, x0, dx, -x0, x0, dx).map((p, i) => (
            <primitive
              key={i}
              object={SkeletonUtils.clone(model)}
              position={[p[0], 0, p[1]]}
            />
          ))}
        </>
      );
    }

    if (dim === 3) {
      return (
        <>
          {grid3D(-x0, x0, dx, -x0, x0, dx, -x0, x0, dx).map((p, i) => (
            <primitive
              key={i}
              object={SkeletonUtils.clone(model)}
              position={[p[0], p[1], p[2]]}
            />
          ))}
        </>
      );
    }
  }
};

const DemoModelCreation = () => {
  const DemoModelBox = () => {
    const ref = useRef(null);

    useFrame((t) => {
      // (cf) ref.current: Object3D ==> Mesh
      if (ref.current) ref.current.rotation.y = t;
    });

    return (
      <Model
        ref={ref}
        object={{ type: "Mesh", name: "box" }}
        geometry={{ type: "BoxGeometry", parameters: [20, 20, 20] }}
        material={{ type: "MeshStandardMaterial", map: crate_gif }}
      />
    );
  };

  const DemoModelSphere = () => {
    const flakesTexture = (width = 512, height = 512) => {
      const canvas = document.createElement("canvas");
      canvas.width = width;
      canvas.height = height;

      const context = canvas.getContext("2d");
      context.fillStyle = "rgb(127,127,255)";
      context.fillRect(0, 0, width, height);

      for (let i = 0; i < 4000; i++) {
        const x = Math.random() * width;
        const y = Math.random() * height;
        const r = Math.random() * 3 + 3;

        let nx = Math.random() * 2 - 1;
        let ny = Math.random() * 2 - 1;
        let nz = 1.5;

        const l = Math.sqrt(nx * nx + ny * ny + nz * nz);

        nx /= l;
        ny /= l;
        nz /= l;

        context.fillStyle =
          "rgb(" +
          (nx * 127 + 127) +
          "," +
          (ny * 127 + 127) +
          "," +
          nz * 255 +
          ")";
        context.beginPath();
        context.arc(x, y, r, 0, Math.PI * 2);
        context.fill();
      }

      return new THREE.CanvasTexture(canvas);
    };

    const scene = useThree((threefy) => threefy.scene);
    const renderer = useThree((threefy) => threefy.renderer);

    // flakes texture
    const flakesTex = flakesTexture();
    flakesTex.wrapS = THREE.RepeatWrapping;
    flakesTex.wrapT = THREE.RepeatWrapping;
    flakesTex.repeat.x = 10;
    flakesTex.repeat.y = 6;
    const normalTex = flakesTex;

    // envMap texture
    let envMap = undefined;
    if (scene.environment) {
      const envMapLoader = new THREE.PMREMGenerator(renderer);
      envMap = envMapLoader.fromCubemap(scene.environment);
    }

    return (
      <Model
        object={{ type: "Mesh", name: "sphere" }}
        geometry={{
          type: "SphereGeometry",
          parameters: [20, 64, 64],
        }}
        material={{
          type: "MeshPhysicalMaterial",
          clearcoat: 1.0,
          clearcoatRoughness: 0.1,
          clearcoatNormalMap: flakesTex,
          clearcoatNormalScale: new THREE.Vector2(0.15, 0.15),
          metalness: 0.9,
          roughness: 0.5,
          color: 0xff0000,
          normalMap: normalTex,
          normalScale: new THREE.Vector2(0.15, 0.15),
          // envMap: envMap?.texture
        }}
      />
    );
  };

  const DemoModelPoints = () => {
    const amount = 4096;
    const range = 100;
    const randFloatSpread = THREE.MathUtils.randFloatSpread;
    const positions = [];
    const colors = [];
    for (let i = 0; i < amount; i++) {
      const x = randFloatSpread(range);
      const y = randFloatSpread(range);
      const z = randFloatSpread(range);
      positions.push(x, y, z);
    }
    for (let i = 0; i < amount; i++) {
      const r = Math.random();
      const g = Math.random();
      const b = Math.random();
      colors.push(r, g, b);
    }
    return (
      <Model
        object={{ type: "Points", name: "points" }}
        geometry={{
          type: "BufferGeometry",
          attributes: {
            position: positions,
            color: colors,
          },
        }}
        material={{
          type: "PointsMaterial",
          map: "images/sprites/metalBall.png", //'images/ball.png'
          color: 0xffffff,
          size: 3.0,
          transparent: true,
          depthTest: false,
          vertexColors: true,
        }}
      />
    );
  };

  const DemoModelGeometries = () => {
    const loader = new THREE.TextureLoader();
    const diffuseMap = loader.load("images/brickWall/diffuse.jpg");
    const normalMap = loader.load("images/brickWall/normal.jpg");

    const model1 = {
      object: { type: "Mesh", name: "box", position: [0, -20, 0] },
      geometry: { type: "BoxGeometry", parameters: [10, 10, 10] }, // width, height, depth
      material: {
        type: "MeshStandardMaterial",
        map: diffuseMap,
        normalMap: normalMap,
      },
    };
    const model2 = {
      object: { type: "Mesh", name: "cylinder", position: [0, -10, 0] },
      geometry: { type: "CylinderGeometry", parameters: [5, 5, 10, 32] }, // radiusTop, radiusBottom, height, radialSegments
      material: {
        type: "MeshStandardMaterial",
        map: diffuseMap,
        normalMap: normalMap,
      },
    };
    const model3 = {
      object: { type: "Mesh", name: "sphere", position: [0, 0, 0] },
      geometry: { type: "SphereGeometry", parameters: [5, 32, 16] }, // radius, widthSegments, heightSegments
      material: {
        type: "MeshStandardMaterial",
        map: diffuseMap,
        normalMap: normalMap,
      },
    };
    const model4 = {
      object: { type: "Mesh", name: "torus", position: [0, 10, 0] },
      geometry: { type: "TorusGeometry", parameters: [5, 1.5, 128, 32] }, // radius, tube, radialSegments(int), tubularSegments(int)
      material: {
        type: "MeshStandardMaterial",
        map: diffuseMap,
        normalMap: normalMap,
      },
    };
    const model5 = {
      object: { type: "Mesh", name: "torusKnot", position: [0, 20, 0] },
      geometry: { type: "TorusKnotGeometry", parameters: [5, 1.5, 128, 32] }, // radius, tube, tubularSegments(int), radialSegments(int)
      material: {
        type: "MeshStandardMaterial",
        map: diffuseMap,
        normalMap: normalMap,
      },
    };
    const model6 = {
      object: {
        type: "Sprite",
        position: [10, 0, 0],
        scale: [5, 5, 5],
        name: "sprite",
      },
      material: {
        type: "SpriteMaterial",
        map: diffuseMap,
        rotation: Math.PI / 4,
        transparent: true,
        opacity: 0.75,
        depthWrite: false,
      },
    };
    return <Model models={[model1, model2, model3, model4, model5, model6]} />;
  };

  const DemoModelShader = () => {
    const vshader = `
              varying vec3 vPos;
              uniform float time;
              void main()
              {
                  vec4 result = vec4(
                      position.x,
                      1.0 * sin( position.x / 3.0 + time ) + position.y,
                      position.z,
                      1.0
                  );
                  vPos = position;
                  gl_Position = projectionMatrix * (modelViewMatrix * result);
              }
          `;
    const fshader = `
              varying vec3 vPos;
              uniform float time;
              void main()
              {
                  if( vPos.x >= 0.0 )
                  {
                      float r = 1.0;
                      float g = abs(mod( vPos.y, 4.0 ));
                      float b = abs(sin( time ));
                      gl_FragColor = vec4( r, g, b, 1.0 );
                  }
                  else
                  {
                      float r = 1.0;
                      float g = abs(cos( time ));
                      float b = abs(mod( vPos.z, 4.0 ));
                      gl_FragColor = vec4( r, g, b, 1.0 );
                  }
              }
          `;

    const ref = useRef(null);

    useFrame((t) => {
      if (!ref.current) return;
      // const group = ref.current;
      // const uniforms = group.children[0].material.uniforms;
      const mesh = ref.current;
      const uniforms = mesh.material.uniforms;
      uniforms["time"].value = t;
    });

    return (
      <Model
        ref={ref}
        object={{ type: "Mesh", name: "shaderBox" }}
        geometry={new THREE.BoxGeometry(60, 10, 10, 60, 10, 10)}
        material={{
          type: "ShaderMaterial",
          wireframe: true,
          uniforms: { time: { value: 0.0 } },
          vertexShader: vshader,
          fragmentShader: fshader,
        }}
      />
    );
  };

  const DemoModelTween = () => {
    // NOTE*: tween required to import @tweenjs/tween.js
    return (
      <Model
        object={{
          type: "Mesh",
          name: "box",
          tween: [
            {
              from: { px: -30, rx: 0, ry: 0, sx: 1 },
              to: { px: 30, rx: Math.PI * 0.7, ry: Math.PI * 0.4, sx: 2 },
              dtime: 3000,
            },
            {
              from: { px: 30, rx: Math.PI * 0.7, ry: Math.PI * 0.4, sx: 2 },
              to: { px: -30, rx: 0, ry: 0, sx: 1 },
              dtime: 3000,
              cycling: true,
            },
          ],
        }}
        geometry={{ type: "BoxGeometry", parameters: [10, 10, 10] }}
        material={{ type: "MeshStandardMaterial", map: "images/crate.gif" }}
      />
    );
  };

  const example = 1;

  switch (example) {
    case 1:
      return <DemoModelBox />;
    case 2:
      return <DemoModelSphere />;
    case 3:
      return <DemoModelPoints />;
    case 4:
      return <DemoModelGeometries />;
    case 5:
      return <DemoModelShader />;
    case 6:
      return <DemoModelTween />;
  }
};

const DemoGeometries = () => {
  // material
  const map = useLoader(brickWall_diffuse_jpg);
  // EdgesGeometry
  const sph = new THREE.SphereGeometry(0.75);
  // Polyhedron
  const verticesOfCube = [
    -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1,
    -1, 1, 1,
  ];
  const indicesOfFaces = [
    2, 1, 0, 0, 3, 2, 0, 4, 7, 7, 3, 0, 0, 1, 5, 5, 4, 0, 1, 2, 6, 6, 5, 1, 2,
    3, 7, 7, 6, 2, 4, 5, 6, 6, 7, 4,
  ];
  const radius = 0.5,
    detail = 2;
  // TubeGeometry
  class CustomSinCurve extends THREE.Curve {
    constructor(scale = 1) {
      super();
      this.scale = scale;
    }
    getPoint(t, optionalTarget = new THREE.Vector3()) {
      const tx = t * 3 - 1.5;
      const ty = Math.sin(2 * Math.PI * t);
      const tz = 0;
      return optionalTarget.set(tx, ty, tz).multiplyScalar(this.scale);
    }
  }
  const path = new CustomSinCurve(0.6);
  // ShapeGeometry
  const x = -0.2,
    y = -0.8,
    k = 0.08;
  const heartShape = new THREE.Shape();
  heartShape.moveTo(x + k * 5, y + k * 5);
  heartShape.bezierCurveTo(x + k * 5, y + k * 5, x + k * 4, y, x, y);
  heartShape.bezierCurveTo(
    x - k * 6,
    y,
    x - k * 6,
    y + k * 7,
    x - k * 6,
    y + k * 7
  );
  heartShape.bezierCurveTo(
    x - k * 6,
    y + k * 11,
    x - k * 3,
    y + k * 15.4,
    x + k * 5,
    y + k * 19
  );
  heartShape.bezierCurveTo(
    x + k * 12,
    y + k * 15.4,
    x + k * 16,
    y + k * 11,
    x + k * 16,
    y + k * 7
  );
  heartShape.bezierCurveTo(x + k * 16, y + k * 7, x + k * 16, y, x + k * 10, y);
  heartShape.bezierCurveTo(
    x + k * 7,
    y,
    x + k * 5,
    y + k * 5,
    x + k * 5,
    y + k * 5
  );

  const Box = (props) => (
    <mesh {...props}>
      <boxGeometry />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Capsule = (props) => (
    <mesh {...props}>
      <capsuleGeometry args={[0.5, 0.5]} />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Circle = (props) => (
    <mesh {...props}>
      <circleGeometry args={[0.75]} />
      <meshStandardMaterial map={map} side={THREE.DoubleSide} />
    </mesh>
  );
  const Cone = (props) => (
    <mesh {...props}>
      <coneGeometry />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Cylinder = (props) => (
    <mesh {...props}>
      <cylinderGeometry args={[0.75, 0.75]} />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Dodecahedron = (props) => (
    <mesh {...props}>
      <dodecahedronGeometry />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Extrude = (props) => (
    <mesh {...props}>
      <extrudeGeometry />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Icosahedron = (props) => (
    <mesh {...props}>
      <icosahedronGeometry />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Lathe = (props) => (
    <mesh {...props}>
      <latheGeometry />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Octahedron = (props) => (
    <mesh {...props}>
      <octahedronGeometry />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Plane = (props) => (
    <mesh {...props}>
      <planeGeometry />
      <meshStandardMaterial map={map} side={THREE.DoubleSide} />
    </mesh>
  );
  const Polyhedron = (props) => (
    <mesh {...props}>
      <polyhedronGeometry
        args={[verticesOfCube, indicesOfFaces, radius, detail]}
      />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Ring = (props) => (
    <mesh {...props}>
      <ringGeometry />
      <meshBasicMaterial map={map} side={THREE.DoubleSide} />
    </mesh>
  );
  const Shape = (props) => (
    <mesh {...props}>
      <shapeGeometry args={[heartShape]} />
      <meshStandardMaterial map={map} side={THREE.DoubleSide} />
    </mesh>
  );
  const Sphere = (props) => (
    <mesh {...props}>
      <sphereGeometry args={[0.75]} />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Tetrahedron = (props) => (
    <mesh {...props}>
      <tetrahedronGeometry />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Torus = (props) => (
    <mesh {...props}>
      <torusGeometry args={[0.5, 0.2]} />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const TorusKnot = (props) => (
    <mesh {...props}>
      <torusKnotGeometry args={[0.5, 0.2]} />
      <meshStandardMaterial map={map} />
    </mesh>
  );
  const Tube = (props) => (
    <mesh {...props}>
      <tubeGeometry args={[path, 20, 0.2, 8, false]} />
      <meshStandardMaterial map={map} side={THREE.DoubleSide} />
    </mesh>
  );

  const Edges = (props) => (
    <lineSegments {...props}>
      <edgesGeometry args={[sph]} />
      <lineBasicMaterial color={0xaaaaaa} />
    </lineSegments>
  );
  const Wireframe = (props) => (
    <lineSegments {...props}>
      <wireframeGeometry args={[sph]} />
      <lineBasicMaterial
        color={0xaaaaaa}
        depthTest={false}
        opacity={0.25}
        transparent={true}
      />
    </lineSegments>
  );
  const geomModels = [
    Box,
    Capsule,
    Circle,
    Cone,
    Cylinder,
    Dodecahedron,
    Extrude,
    Icosahedron,
    Lathe,
    Octahedron,
    Plane,
    Polyhedron,
    Ring,
    Shape,
    Sphere,
    Tetrahedron,
    Torus,
    TorusKnot,
    Tube,
    Edges,
    Wireframe,
  ];

  const refs = geomModels.map((x) => useRef(null));

  useFrame((t) => {
    if (!refs[0].current) return;

    refs.forEach((ref) => {
      const geomModel = ref.current;
      geomModel.rotation.x = t * 3;
      geomModel.rotation.y = t * 1.5;
    });
  });

  return (
    <group scale={5} position={[-12.5, -7.5, 0]}>
      {geomModels.map((GeomModel, i) => (
        <GeomModel
          ref={refs[i]}
          key={i}
          scale={0.5}
          position={[i % 6, parseInt(i / 6), 0]}
        />
      ))}
    </group>
  );
};

const DemoGeometries2 = () => {
  const example = 2;

  // material
  const map = useLoader(brickWall_diffuse_jpg);
  // Polyhedron
  const verticesOfCube = [
    -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1,
    -1, 1, 1,
  ];
  const indicesOfFaces = [
    2, 1, 0, 0, 3, 2, 0, 4, 7, 7, 3, 0, 0, 1, 5, 5, 4, 0, 1, 2, 6, 6, 5, 1, 2,
    3, 7, 7, 6, 2, 4, 5, 6, 6, 7, 4,
  ];
  const radius = 0.5,
    detail = 2;
  // TubeGeometry
  class CustomSinCurve extends THREE.Curve {
    constructor(scale = 1) {
      super();
      this.scale = scale;
    }
    getPoint(t, optionalTarget = new THREE.Vector3()) {
      const tx = t * 3 - 1.5;
      const ty = Math.sin(2 * Math.PI * t);
      const tz = 0;
      return optionalTarget.set(tx, ty, tz).multiplyScalar(this.scale);
    }
  }
  const path = new CustomSinCurve(0.6);
  // ShapeGeometry
  const x = -0.2,
    y = -0.8,
    k = 0.08;
  const heartShape = new THREE.Shape();
  heartShape.moveTo(x + k * 5, y + k * 5);
  heartShape.bezierCurveTo(x + k * 5, y + k * 5, x + k * 4, y, x, y);
  heartShape.bezierCurveTo(
    x - k * 6,
    y,
    x - k * 6,
    y + k * 7,
    x - k * 6,
    y + k * 7
  );
  heartShape.bezierCurveTo(
    x - k * 6,
    y + k * 11,
    x - k * 3,
    y + k * 15.4,
    x + k * 5,
    y + k * 19
  );
  heartShape.bezierCurveTo(
    x + k * 12,
    y + k * 15.4,
    x + k * 16,
    y + k * 11,
    x + k * 16,
    y + k * 7
  );
  heartShape.bezierCurveTo(x + k * 16, y + k * 7, x + k * 16, y, x + k * 10, y);
  heartShape.bezierCurveTo(
    x + k * 7,
    y,
    x + k * 5,
    y + k * 5,
    x + k * 5,
    y + k * 5
  );

  if (example === 1) {
    const meshes = [],
      helperLines = [];
    const refs = Array(8)
      .fill()
      .map((x, i) => useRef(null));
    const colors = Array(8)
      .fill()
      .map((x, i) => randomHSLColor().toArray());
    let i = 0;
    {
      meshes.push(
        <box
          key={i}
          ref={refs[i]}
          scale={0.5}
          position={[i % 3, parseInt(i / 3), 0]}
          type={"basic"}
          color={colors[i]}
          map={map}
        />
      );
      i++;
      meshes.push(
        <cone
          key={i}
          ref={refs[i]}
          scale={0.5}
          position={[i % 3, parseInt(i / 3), 0]}
          type={"phong"}
          color={colors[i]}
          map={map}
        />
      );
      i++;
      meshes.push(
        <cylinder
          key={i}
          ref={refs[i]}
          args={[0.75, 0.75]}
          scale={0.5}
          position={[i % 3, parseInt(i / 3), 0]}
          type={"standard"}
          color={colors[i]}
          map={map}
        />
      );
      i++;
      meshes.push(
        <icosahedron
          key={i}
          ref={refs[i]}
          scale={0.5}
          position={[i % 3, parseInt(i / 3), 0]}
          type={"points"}
          color={colors[i]}
          map={map}
          size={0.5}
        />
      );
      i++;
      meshes.push(
        <dodecahedron
          key={i}
          ref={refs[i]}
          scale={0.5}
          position={[i % 3, parseInt(i / 3), 0]}
          type={"points"}
          color={colors[i]}
          map={map}
          size={0.5}
        />
      );
      i++;
      meshes.push(
        <torusKnot
          key={i}
          ref={refs[i]}
          args={[0.5, 0.2]}
          scale={0.5}
          position={[i % 3, parseInt(i / 3), 0]}
          type={"points"}
          color={colors[i]}
          map={map}
          size={0.5}
        />
      );
      i++;
      helperLines.push(
        <edges
          key={i}
          ref={refs[i]}
          args={[new THREE.OctahedronGeometry()]}
          scale={0.5}
          position={[i % 3, parseInt(i / 3), 0]}
          type={"line"}
          color={colors[i]}
        />
      );
      i++;
      helperLines.push(
        <wireframe
          key={i}
          ref={refs[i]}
          args={[new THREE.SphereGeometry(0.75)]}
          scale={0.5}
          position={[i % 3, parseInt(i / 3), 0]}
          type={"line"}
          color={colors[i]}
          depthTest={false}
          opacity={0.25}
          transparent={true}
        />
      );
      i++;
    }

    useFrame((t) => {
      if (!refs[0].current) return;

      refs.forEach((ref, i) => {
        const model = ref.current;
        model.rotation.x = t * 1.5 * ((i % 3) + 1);
        model.rotation.y = t * 0.8 * ((i % 4) + 1);
      });
    });

    return (
      <group scale={5} position={[-5, -5, 0]}>
        {meshes}
        {helperLines}
      </group>
    );
  }

  if (example === 2) {
    const meshRefs = Array(19)
      .fill()
      .map((x, i) => useRef(null));
    const lineRefs = Array(19)
      .fill()
      .map((x, i) => useRef(null));
    const pointRefs = Array(19)
      .fill()
      .map((x, i) => useRef(null));
    const refs = [...meshRefs, ...lineRefs, ...pointRefs];
    const colors = Array(19)
      .fill()
      .map((x, i) => randomHSLColor().toArray());

    const elems = [
      "box",
      "capsule",
      "circle",
      "cone",
      "cylinder",
      "dodecahedron",
      "extrude",
      "icosahedron",
      "lathe",
      "octahedron",
      "plane",
      "polyhedron",
      "ring",
      "shape",
      "sphere",
      "tetrahedron",
      "torus",
      "torusKnot",
      "tube",
    ];
    const args = [
      [],
      [0.5, 0.5],
      [0.75],
      [],
      [0.75, 0.75],
      [],
      [],
      [],
      [],
      [],
      [],
      [verticesOfCube, indicesOfFaces, radius, detail],
      [],
      [heartShape],
      [0.75],
      [],
      [0.5, 0.2],
      [0.5, 0.2],
      [path, 20, 0.2, 8, false],
    ];
    const sides = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1];
    const types = [
      "basic",
      "lambert",
      "matcap",
      "normal",
      "phong",
      "physical",
      "standard",
      "toon",
      "basic",
      "lambert",
      "matcap",
      "normal",
      "phong",
      "physical",
      "standard",
      "toon",
      "basic",
      "lambert",
      "matcap",
      "normal",
    ];

    useFrame((t) => {
      if (!refs[0].current) return;

      refs.forEach((ref, i) => {
        const model = ref.current;
        model.rotation.x = t * 1.5 * ((i % 3) + 1);
        model.rotation.y = t * 0.8 * ((i % 4) + 1);
      });
    });

    return (
      <group>
        {/* meshes */}
        <group scale={5} position={[-8, -15, 0]}>
          {elems.map((Elem, i) => (
            <Elem
              key={"mesh" + i}
              ref={meshRefs[i]}
              // geometry arguments
              args={args[i]}
              // material parameters
              type={types[i]}
              color={colors[i]}
              map={map}
              side={sides[i] ? THREE.DoubleSide : THREE.FrontSide}
              // object3D properties
              scale={0.5}
              position={[i % 3, parseInt(i / 3), 0]}
            />
          ))}
        </group>

        {/* lines */}
        <group scale={5} position={[-8 - 20, -15, 0]}>
          {elems.map((Elem, i) => (
            <Elem
              key={"line" + i}
              ref={lineRefs[i]}
              // geometry arguments
              args={args[i]}
              // material parameters
              type={"line"}
              color={colors[i]}
              map={map}
              // object3D properties
              scale={0.5}
              position={[i % 3, parseInt(i / 3), 0]}
            />
          ))}
        </group>

        {/* points */}
        <group scale={5} position={[-8 + 20, -15, 0]}>
          {elems.map((Elem, i) => (
            <Elem
              key={"point" + i}
              ref={pointRefs[i]}
              // geometry arguments
              args={args[i]}
              // material parameters
              type={"points"}
              color={colors[i]}
              map={map}
              size={0.5}
              // object3D properties
              scale={0.5}
              position={[i % 3, parseInt(i / 3), 0]}
            />
          ))}
        </group>
      </group>
    );
  }
};

const DemoOceanScene = () => {
  const url = hdr_overcast_soil_puresky_2k_hdr;

  const boxRef = useRef(null);
  const waterRef = useRef(null);

  useFrame((t) => {
    // water
    if (waterRef.current) {
      const water = waterRef.current;
      water.material.uniforms["time"].value += 1.0 / 60.0;
    }
    // box
    if (boxRef.current) {
      const box = boxRef.current;
      const size = box.scale.x;
      box.position.y = Math.sin(t) * (size * 0.7) + size * 0.2;
      box.rotation.x = t * 0.5;
      box.rotation.y = t * 0.51;
    }
  });

  return (
    <scene>
      <background url={url} />
      <hemisphereLight
        args={[0x7cbfff, 0xffe5bc, 0.2 * Math.PI]}
        position={[0, 1, 0]}
      />
      <box ref={boxRef} scale={10} type={"standard"} map={crate_gif} />
      <Water ref={waterRef} />
    </scene>
  );
};

const DemoShipOnTheSea = () => {
  const url = hdr_overcast_soil_puresky_2k_hdr;

  const waterRef = useRef(null);
  const shipRef = useRef(null);

  const ship = useLoader(blackPearl_zip);

  let shipLoaded = false;
  let vsize = new THREE.Vector3();

  const onLoad = (ship) => {
    shipRef.current = ship;
    const materials = ship.children[0].material; // (cf) 9 materials
    materials[3].side = THREE.DoubleSide;
    shipLoaded = true;

    const camera = useThree().camera;
    camera.position.set(50, 15, 50);
  };

  useFrame((t) => {
    // water
    if (waterRef.current) {
      const water = waterRef.current;
      water.material.uniforms["time"].value += 1.0 / 60.0;
    }
    // ship
    if (shipRef.current && shipLoaded) {
      const ship = shipRef.current;
      const mesh = ship.children[0];
      if (!mesh.geometry.boundingBox) {
        mesh.geometry.computeBoundingBox();
        mesh.geometry.boundingBox.getSize(vsize);
      }
      const shipLength = vsize.z; // 459
      const shipHeight = vsize.y; // 364
      const shipWidth = vsize.x; // 170
      const size = shipWidth * 0.05;
      ship.position.y = Math.sin(t) * (size * 0.05) + size * 0.3;
      ship.rotation.x = (Math.PI / 64) * Math.sin(t * 0.5); // x = pitching
      ship.rotation.y = (Math.PI / 64) * Math.sin(t * 0.1); // y = yawing
      ship.rotation.z = (Math.PI / 24) * Math.sin(t * 1.0); // z = rolling
    }
  });

  return (
    <scene>
      <background url={url} />
      <hemisphereLight
        args={[0x7cbfff, 0xffe5bc, 0.2 * Math.PI]}
        position={[0, 1, 0]}
      />
      <primitive ref={shipRef} object={ship} scale={0.1} onLoad={onLoad} />
      <Water ref={waterRef} />
    </scene>
  );
};

const DemoSkyTerrain = () => {
  // choose one of the following terrains
  const terrains = [];
  terrains.push({
    mapURL: terrain_Rugged_Terrain_diffuse_jpg,
    heightURL: terrain_Rugged_Terrain_heightmap_jpg,
    bumpURL: "",
    size: [500, 300, 500],
    altitude: -100,
  });
  // terrains.push({
  //   mapURL: "images/terrain/Great Lakes/diffuse.jpg",
  //   heightURL: "images/terrain/Great Lakes/heightmap.jpg",
  //   bumpURL: "",
  //   size: [500, 500, 500],
  //   altitude: -30,
  // });
  // terrains.push({
  //   mapURL: "images/terrain/Grand Mountain/diffuse.jpg",
  //   heightURL: "images/terrain/Grand Mountain/heightmap.jpg",
  //   bumpURL: "images/terrain/Grand Mountain/bump.jpg",
  //   size: [500, 300, 500],
  //   altitude: -20,
  // });
  // terrains.push({
  //   mapURL: "images/terrain/Rolling Hills/diffuse.jpg",
  //   heightURL: "images/terrain/Rolling Hills/heightmap.jpg",
  //   bumpURL: "",
  //   size: [500, 200, 500],
  //   altitude: -20,
  // });
  // terrains.push({
  //   mapURL: "images/terrain/Irvine Lake/diffuse.jpg",
  //   heightURL: "images/terrain/Irvine Lake/heightmap.jpg",
  //   bumpURL: "",
  //   size: [500, 100, 500],
  //   altitude: -30,
  // });
  const terrain = terrains[0];

  // update sky and sunPosition
  const skyRef = useRefEffect((sky) => {
    const { scene, renderer } = useThree();

    // sunPosition <== (azimuth, elevation)
    const sunElevation = 2; // vertical angular toward the y (up) axis
    const sunAzimuth = 180; // clockwise angle between North (North=0, East=90, South=180, West=270)
    const phi = THREE.MathUtils.degToRad(90 - sunElevation); // polar angle from the y (up) axis
    const theta = THREE.MathUtils.degToRad(sunAzimuth); // equator angle around the y (up) axis
    const sunPosition = new THREE.Vector3().setFromSphericalCoords(
      1,
      phi,
      theta
    );
    sky.material.uniforms["sunPosition"].value.copy(sunPosition);

    // scene.environment <== sky
    const sceneEnv = new THREE.Scene();
    const pmremGenerator = new THREE.PMREMGenerator(renderer);
    sceneEnv.add(sky);
    const renderTarget = pmremGenerator.fromScene(sceneEnv);
    scene.add(sky);
    scene.environment = renderTarget.texture;
  });

  return (
    <scene>
      <Sky ref={skyRef} />
      <Terrain
        position-y={terrain.altitude}
        xsize={terrain.size[0]}
        ysize={terrain.size[1]}
        zsize={terrain.size[2]}
        map={terrain.mapURL}
        heightMap={terrain.heightURL}
        bumpMap={terrain.bumpURL}
      />
    </scene>
  );
};

const DemoGrassScene = () => {
  const url = hdr_overcast_soil_puresky_2k_hdr;

  const terrain = {
    mapURL: terrain_Rugged_Terrain_diffuse_jpg,
    heightURL: terrain_Rugged_Terrain_heightmap_jpg,
    bumpURL: "",
    size: [500, 300, 500],
    altitude: -100,
  };

  return (
    <scene>
      <directionalLight args={[0xffffff, Math.PI]} position={[100, 20, 120]} />
      <ambientLight args={[0xffffff, 0.5]} />
      <background url={url} />
      <Terrain
        position-y={terrain.altitude}
        xsize={terrain.size[0]}
        ysize={terrain.size[1]}
        zsize={terrain.size[2]}
        map={terrain.mapURL}
        heightMap={terrain.heightURL}
        bumpMap={terrain.bumpURL}
      />
      <Grass
        position-y={terrain.altitude} // y value in world coords
        numBlades={100000} // # of blades
        bladeScale={0.3} // scale of blade length
        width={terrain.size[0]} // width of grass terrain
        hsize={terrain.size[1]} // height of grass terrain
        heightMap={terrain.heightURL} // heightmap of grass terrain or its texture url
      />
    </scene>
  );
};

const DemoInstancedModel = () => {
  const ref = useRef(null);

  const dim = 2; // two-dim grids
  const nlines = 10; // number of grid lines along each axis
  const spacing = 20; // grid spacing along each axis
  const count = Math.pow(nlines, dim);
  const col = new THREE.Color();

  const matrices = [],
    colors = [];
  for (let i = 0; i < count; i++) {
    matrices.push(new THREE.Matrix4());
    colors.push(new THREE.Color());
  }

  const posFn = (x, y, z, t) => [
    x + Math.sin(x / 2),
    0.3 * Math.sin(x + t) * Math.sin(z + t),
    z + Math.sin(z / 2),
  ];
  const rotFn = (x, y, z, t) => [
    0.2 * Math.sin(x / 4 + t),
    Math.atan2(x, z),
    0,
  ];
  const scaleFn = (x, y, z, t) => {
    const s = Math.sin(x + y + t) * 0.5 + 1.5;
    return [s, s, s];
  };
  const colorFn = (x, y, z, t) => [
    Math.sin(x + t) * 0.5 + 0.5,
    1,
    Math.cos(z + t) * 0.5 + 0.5,
  ];

  const transformInstance = (t = 0) => {
    gridCoords(nlines, spacing, dim).forEach((p, i) => {
      const trfm = transformMatrix([p[0], 0, p[1], t], posFn, rotFn, scaleFn);
      matrices[i].copy(trfm);
    });
    return matrices;
  };

  const colorInstance = (t = 0) => {
    gridCoords(nlines, spacing, dim).forEach((p, i) => {
      col.fromArray(colorFn(p[0], 0, p[1], t));
      colors[i].copy(col);
    });
    return colors;
  };

  const initTransform = () => transformInstance(0);
  const updateTransform = (t) => transformInstance(t);
  const initColor = () => colorInstance(0);
  const updateColor = (t) => colorInstance(t);

  useFrame((t) => {
    if (!ref.current) return;
    const instMesh = ref.current;
    instMesh.rotation.y = t * 0.5;
  });

  return (
    <InstancedModel
      ref={ref}
      count={count}
      scale={4}
      url={LeePerrySmith_glb}
      animIdx={2}
      initTransform={initTransform}
      updateTransform={updateTransform}
      initColor={initColor}
      updateColor={updateColor}
    />
  );
};

const DemoInstancedModel2 = () => {
  const near = 0.1;
  const far = 1000;
  const aspect = window.innerWidth / window.innerHeight;
  const width = far / 2;

  const dim = 2;
  const nlines = 10;
  const count = Math.pow(nlines, dim);
  const tmat = new THREE.Matrix4();
  const smat = new THREE.Matrix4();

  const matrices = [];
  for (let i = 0; i < count; i++) matrices.push(new THREE.Matrix4());

  const example = 2;

  const initTransform = (instMeshes) => {
    if (example === 1) {
      const spacing = width / nlines;
      gridCoords(nlines, spacing, dim).forEach((p, i) => {
        tmat
          .makeRotationY(Math.PI * (Math.random() * 2 - 1))
          .setPosition(p[0], 0, p[1]);
        smat.makeScale(3.5, 3.5, 3.5);
        tmat.multiply(smat);
        matrices[i].copy(tmat);
      });
      return matrices;
    }

    if (example === 2) {
      for (let i = 0; i < count; i++) {
        tmat.makeScale(3.5, 3.5, 3.5);
        const x = Math.random() * width - width / 2;
        const z = Math.random() * width - width / 2;
        tmat.setPosition(x, 0.0, z);
        matrices[i].copy(tmat);
      }
      return matrices;
    }
  };

  const deltaTransform = (t, instMeshes) => {
    if (example === 1) {
      for (let i = 0; i < count; i++) {
        tmat
          .makeRotationY(0.01 * Math.random())
          .setPosition(0, 0, 0.1 * Math.random());
        matrices[i].copy(tmat);
      }
      return matrices;
    }

    if (example === 2 && instMeshes.length > 0) {
      const zmax = width / 2;
      for (let i = 0; i < count; i++) {
        // move the front cow to the back
        instMeshes[0].getMatrixAt(i, tmat);
        const zpos = tmat.elements[14]; // zpos = z position of i-th instance
        const dz = zpos > zmax ? -zmax / 1.7 : 0.1 * Math.random();

        tmat.makeRotationY(0.01 * (Math.random() - 0.5)).setPosition(0, 0, dz);
        matrices[i].copy(tmat);
      }
      return matrices;
    }
  };

  const camRef = useRef(null);
  const cowRef = useRef(null);

  useFrame((t) => {
    if (camRef.current) {
      const camera = camRef.current;
      const r = far / 5;
      camera.position.set(
        r * Math.sin(t / 5),
        r / 2 + (r / 3) * Math.cos(t / 2.5),
        r * Math.cos(t / 5)
      );
      camera.lookAt(0, 0, 0);
    }
  });

  return (
    <>
      <perspectiveCamera ref={camRef} args={[60, aspect, near, far]} />
      <plane
        rotation-x={Math.PI / -2}
        args={[width + 100, width + 100]}
        type={"lambert"}
        map={moss_diffuse_jpg}
      />
      <SkyDome />
      <Grass
        width={width + 100}
        bladeScale={0.4}
        numBlades={50000}
        position-y={-width / 200}
      />
      <InstancedModel
        ref={cowRef}
        count={count}
        url={Cow_zip}
        animIdx={2}
        initTransform={initTransform}
        deltaTransform={deltaTransform}
        isRandomColor={true}
      />
    </>
  );
};

const DemoExamples = ({ example }) => {
  switch (example) {
    case 31:
      return <DemoModelLoading />;
    case 32:
      return <DemoModelCreation />;
    case 33:
      return <DemoGeometries />;
    case 34:
      return <DemoGeometries2 />;
    case 35:
      return <DemoOceanScene />;
    case 36:
      return <DemoShipOnTheSea />;
    case 37:
      return <DemoSkyTerrain />;
    case 38:
      return <DemoGrassScene />;
    case 39:
      return <DemoInstancedModel />;
    case 40:
      return <DemoInstancedModel2 />;
  }
};

export { DemoExamples };

Last updated