7.3 Example codes (21~30)
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, { Suspense, useEffect, useRef, useState } from "react";
import {
useAnimate,
useFrame,
useLoader,
useRefCallback,
useRefEffect,
useSearch,
useThree,
} from "threefy";
import { Model } from "./ThreeModels";
import { fitCameraToObject } 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";
import moss_diffuse_jpg from "../public/images/moss/diffuse.jpg";
import moss_normal_jpg from "../public/images/moss/normal.jpg";
import moss_displacement_jpg from "../public/images/moss/displacement.jpg";
import raymarch_pebbles_jpg from "../public/images/raymarch/pebbles.jpg";
import bathroom_diffuse_jpg from "../public/images/bathroom/diffuse.jpg";
import gravel_diffuse_jpg from "../public/images/gravel/diffuse.jpg";
import gravel_normal_jpg from "../public/images/gravel/normal.jpg";
import gravel_aoRoughMetal_jpg from "../public/images/gravel/aoRoughMetal.jpg";
import rockMossy_diffuse_jpg from "../public/images/rockMossy/diffuse.jpg";
import Soldier_glb from "../public/models/Soldier.glb";
import jump_animation_glb from "../public/models/jump-animation.glb";
import ratamahatta_md2_zip from "../public/models/ratamahatta_md2.zip";
import Cow_zip from "../public/models/Cow.zip";
import LeePerrySmith_glb from "../public/models/LeePerrySmith.glb";
const DemoUseThree = () => {
const { list, get, set } = useThree();
//======================
// get( target )
//======================
if (true) {
const results = [];
// objects (= threefy properties)
results.push(get("scene"));
results.push(get("camera"));
results.push(get("renderer"));
results.push(get("clock"));
results.push(get("controls"));
results.push(get("animator"));
results.push(get("raycaster"));
// object properties
results.push(get("scene.background"));
results.push(get("scene.fog"));
results.push(get("scene.children"));
results.push(get("camera.aspect"));
results.push(get("renderer.outputColorSpace"));
results.push(get("renderer.info"));
results.push(get("clock.startTime"));
results.push(get("controls.keys"));
results.push(get("animator.mixer"));
results.push(get("animator.mixer.timeScale"));
results.push(get("raycaster.ray"));
results.push(get("raycaster.params"));
// object methods
results.push(get("camera.getViewSize")(1, new THREE.Vector2()));
results.push(get("camera.getWorldDirection")(new THREE.Vector3()));
results.push(get("renderer.getPixelRatio")());
results.push(get("renderer.getSize")(new THREE.Vector2()));
results.push(get("renderer.getViewport")(new THREE.Vector4()));
results.push(get("renderer.getClearColor")(new THREE.Color()));
results.push(get("clock.getElapsedTime")());
results.push(get("controls.getDistance")());
results.push(get("animator.mixer.getRoot")());
// multiple gets
results.push(
get("scene.id", "camera.projectionMatrix", "renderer.toneMapping")
);
// example: creating a new function with object methods
const [delta, update] = get("clock.getDelta", "controls.update");
const updateControls = () => update(delta());
results.push({ updateControls });
// example: renderer size
const [width, height] = get("width", "height");
results.push({ width, height });
// example: viewport
const viewport = () => {
const v = new THREE.Vector4();
get("renderer.getViewport")(v);
return { x: v.x, y: v.y, width: v.z, height: v.w };
};
results.push({ viewport: viewport() });
// example: canvas element
results.push({ canvas: get("renderer.domElement") });
// example: device pixel ratio (or dpr)
results.push({ dpr: get("renderer.getPixelRatio")() });
console.log(results);
}
//======================
// list( target )
//======================
if (true) {
const results = [];
results.push(list(get("scene")));
results.push(list(get("camera")));
results.push(list(get("composer")));
results.push(list(get("animator.mixer")));
console.log(results);
}
//======================
// set( target, value )
//======================
useEffect(() => {
// const createScene = () =>
// {
// const scene = new THREE.Scene();
// scene.add( new THREE.AmbientLight() );
// scene.add( new THREE.DirectionalLight() );
// const g = new THREE.SphereGeometry( 1, 32, 32 );
// const m = new THREE.MeshStandardMaterial( { color: 0xff0000 } );
// scene.add( new THREE.Mesh( g, m ) );
// return scene;
// }
const createOrthographicCamera = (w, h) => {
w = w ?? window.innerWidth;
h = h ?? (w * window.innerHeight) / window.innerWidth;
const orthographicCamera = new THREE.OrthographicCamera(
-w / 2,
w / 2,
h / 2,
-h / 2,
0.1,
1000
);
orthographicCamera.position.z = 500;
return orthographicCamera;
};
const renderer = get("renderer");
const mesh = get("scene.children[1]");
// replace the camera
set("camera", createOrthographicCamera(50));
// replace the scene
// set( 'scene', createScene() );
// NOTE*:
// target (= string, not object) should start with one of the
// scene, camera, renderer, clock, controls, animator, or raycaster,
// like target = 'scene.background', target = 'camera.fov', etc.
// 4 ways to use
const usage = 4;
if (usage === 1) {
set("renderer.outputColorSpace", THREE.SRGBColorSpace);
set("renderer.toneMapping", THREE.ACESFilmicToneMapping);
} else if (usage === 2) {
set({
"renderer.outputColorSpace": THREE.SRGBColorSpace,
"renderer.toneMapping": THREE.ACESFilmicToneMapping,
});
} else if (usage === 3) {
set("renderer", {
outputColorSpace: THREE.SRGBColorSpace,
toneMapping: THREE.ACESFilmicToneMapping,
});
} else if (usage === 4) {
set(renderer, {
outputColorSpace: THREE.SRGBColorSpace,
toneMapping: THREE.ACESFilmicToneMapping,
});
}
// example: scene.background
switch (3) {
case 1:
set("scene.background", 0xcdcdcd);
break;
case 2:
set("scene.background", "skyblue");
break;
case 3:
set("scene.background", brickWall_diffuse_jpg);
break;
case 4:
set(
"scene.background",
new THREE.TextureLoader().load(
brickWall_diffuse_jpg,
(tex) => (tex.colorSpace = THREE.SRGBColorSpace)
)
);
break;
}
// example: renderer (modified by its methods)
set(renderer, {
setPixelRatio: 0.5, // renderer.setPixelRatio( 0.5 )
setViewport: [100, 100, 700, 700], // renderer.setViewport( 100, 100, 700, 700 )
});
// example: mesh material
if (false) {
set("scene.children[4].material", {
color: [1, 1, 0],
map: brickWall_diffuse_jpg,
});
} else {
set(mesh.material, {
color: [1, 1, 0],
map: brickWall_diffuse_jpg,
});
}
// example: mesh transform
if (false) {
set("scene.children[4]", {
rotation: [Math.PI / 6, Math.PI / 3, 0],
scale: [20, 10, 10], // or 20
});
} else {
set(mesh, {
rotation: [Math.PI / 6, Math.PI / 3, 0],
scale: [20, 10, 10], // or 20
});
}
}, []);
return <box scale={10} type={"phong"} color={"red"} />;
};
const DemoShadow = () => {
const [left, right, top, bottom, near, far] = [-20, 20, 20, -20, 0.5, 70];
const ref = useRefEffect((light, scene) => {
scene.add(new THREE.DirectionalLightHelper(light, 2));
});
useFrame((t) => {
if (!ref.current) return;
const light = ref.current;
light.position.x = 30 * Math.cos(t);
light.position.z = 30 * Math.sin(t);
});
return (
<>
<directionalLight
ref={ref}
castShadow
position={[10, 30, 0]}
shadow-mapSize={[512, 512]}
shadow-camera-left={left}
shadow-camera-right={right}
shadow-camera-top={top}
shadow-camera-bottom={bottom}
shadow-camera-near={near}
shadow-camera-far={far}
/>
<box
castShadow
position={[-10, 15, 0]}
scale={5}
type={"lambert"}
color={"red"}
/>
<box
castShadow
position={[0, 15, 0]}
scale={5}
type={"lambert"}
color={"green"}
/>
<box
castShadow
position={[10, 15, 0]}
scale={5}
type={"lambert"}
color={"blue"}
/>
<box
castShadow
position={[0, 15, -10]}
scale={5}
type={"lambert"}
color={"yellow"}
/>
<box
castShadow
position={[0, 15, 10]}
scale={5}
type={"lambert"}
color={"cyan"}
/>
<box
receiveShadow
position={[0, 0, 0]}
scale={[80, 2, 80]}
type={"lambert"}
color={"white"}
/>
</>
);
};
const DemoShadow2 = () => {
const MossGround = ({ sizes }) => {
const textures = useLoader([
moss_diffuse_jpg,
moss_normal_jpg,
moss_displacement_jpg,
]);
textures.forEach((tex, i) => {
tex.wrapS = THREE.RepeatWrapping;
tex.wrapT = THREE.RepeatWrapping;
tex.repeat.set(4, 4);
if (i === 0) tex.colorSpace = THREE.SRGBColorSpace;
});
return (
<plane
receiveShadow
rotation-x={Math.PI / -2}
args={[...sizes, 64, 64]}
type={"standard"}
map={textures[0]}
normalMap={textures[1]}
displacementMap={textures[2]}
displacementScale={5}
/>
);
};
const ref = useRefEffect((group, scene) => {
group.children.forEach((light) => {
if (light.isLight) {
scene.add(new THREE.DirectionalLightHelper(light, 2));
}
});
});
useFrame((t) => {
if (!ref.current) return;
const group = ref.current;
group.rotation.y = t * 0.5;
});
return (
<>
<MossGround sizes={[300, 300]} />
<box
castShadow
position={[-60, 20, 0]}
scale={20}
type={"lambert"}
color={"red"}
update={function (dt, ct) {
this.rotation.x += Math.sin(dt) * 2.0;
}}
/>
<box
castShadow
position={[0, 20, 0]}
scale={20}
type={"lambert"}
color={"green"}
update={function (dt, ct) {
this.rotation.y += Math.sin(dt) * 1.0;
}}
/>
<box
castShadow
position={[60, 20, 0]}
scale={20}
type={"lambert"}
color={"blue"}
update={function (dt, ct) {
this.rotation.z += Math.sin(dt) * -2.0;
}}
/>
<box
castShadow
position={[0, 20, -60]}
scale={20}
type={"lambert"}
color={"yellow"}
update={function (dt, ct) {
this.rotation.x += Math.sin(dt) * 3.0;
}}
/>
<box
castShadow
position={[0, 20, 60]}
scale={20}
type={"lambert"}
color={"cyan"}
update={function (dt, ct) {
this.rotation.z += Math.sin(dt) * 2.0;
}}
/>
<group ref={ref}>
<shadowDirectionalLight position={[0, 100, 0]} intensity={5} />
<shadowDirectionalLight position={[50, 70, 50]} intensity={5} />
</group>
</>
);
};
const DemoShadow3 = () => {
const example = 2;
// shadowDirectionalLight
if (example === 1) {
const ref = useRefEffect((light, scene) => {
// turn off the existing three-point-lighting object
const searched = useSearch("Object3D", "threePointLighting");
searched[0].visible = false;
scene.add(new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 0.2));
scene.add(new THREE.DirectionalLightHelper(light, 5));
});
const onLoad = (model) => {
model.traverse((child) => {
if (child.isMesh) {
child.castShadow = true; // required
child.receiveShadow = true; // optional
}
});
};
useFrame((t) => {
const light = ref.current;
if (light) {
light.position.x = Math.cos(t / 2) * -40;
light.position.z = Math.sin(t / 2) * -40;
}
});
return (
<group position-y={-10}>
<shadowDirectionalLight
ref={ref}
position={[-40, 40, 0]}
color={"skyblue"}
intensity={Math.PI}
/>
<shadowPlaneReceiver width={100} type={"shadow"} />
<fogExp2 args={[0xcccccc, 0.001]} />
<background color={0x494949} />
<Model url={Soldier_glb} scale={12} onLoad={onLoad} />
</group>
);
}
// shadowSpotLight
if (example === 2) {
const ref = useRefEffect((light, scene) => {
// turn off the existing three-point-lighting object
const searched = useSearch("Object3D", "threePointLighting");
searched[0].visible = false;
scene.add(new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 0.2));
scene.add(new THREE.SpotLightHelper(light));
});
const onLoad = (model) => {
model.traverse((child) => {
if (child.isMesh) {
child.castShadow = true; // required
child.receiveShadow = true; // optional
}
});
};
useFrame((t) => {
const light = ref.current;
if (light) {
light.position.x = Math.cos(t / 3) * 40;
light.position.z = Math.sin(t / 3) * 40;
}
});
return (
<group position-y={-10}>
<shadowSpotLight
ref={ref}
position={[40, 50, 0]}
intensity={1000}
color={"skyblue"}
map={raymarch_pebbles_jpg}
/>
<shadowPlaneReceiver
width={500}
height={500}
type={"phong"}
color={0xffffff}
/>
<fogExp2 args={[0xcccccc, 0.001]} />
<background color={0x494949} />
<Model url={Soldier_glb} scale={12} onLoad={onLoad} />
</group>
);
}
// shadowPointLight
if (example === 3) {
const ref = useRefEffect((light, scene) => {
// turn off the existing three-point-lighting object
const searched = useSearch("Object3D", "threePointLighting");
searched[0].visible = false;
scene.add(new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 0.2));
scene.add(new THREE.PointLightHelper(light, 1));
});
const onLoad = (model) => {
model.traverse((child) => {
if (child.isMesh) {
child.castShadow = true; // required
child.receiveShadow = true; // optional
}
});
};
useFrame((t) => {
const light = ref.current;
if (light) {
light.position.x = Math.cos(t / 3) * 40;
light.position.z = Math.sin(t / 3) * 40;
}
});
return (
<group position-y={-10}>
<shadowPointLight
ref={ref}
position={[20, 50, 0]}
intensity={300}
color={"skyblue"}
/>
<shadowPlaneReceiver
width={500}
height={500}
type={"phong"}
color={0xffffff}
map={bathroom_diffuse_jpg}
/>
<fogExp2 args={[0xcccccc, 0.001]} />
<background color={0x494949} />
<Model url={Soldier_glb} scale={12} onLoad={onLoad} />
</group>
);
}
};
const DemoUseLoader = () => {
const example = 2;
if (example === 1) {
const onAnimate = (model) => {
fitCameraToObject(model);
if (model.animations) {
const { animator } = useThree();
animator.playAction(model, model.animations[0]);
}
};
return <Model url={jump_animation_glb} scale={1} onLoad={onAnimate} />;
}
if (example === 2) {
const { animator } = useThree();
const onAnimate = (model) => {
if (model.animations) {
animator.playAction(model, model.animations[0]);
}
};
const onTransform = (model) => {
model.position.y += 1.2;
model.position.z += 2.0;
model.scale.setScalar(0.05);
model.rotation.y = Math.PI / 4;
if (model.animations) {
animator.playAction(model, model.animations[0]);
}
};
return (
<group scale={10}>
<Model url={jump_animation_glb} onLoad={onAnimate} />
<Model url={ratamahatta_md2_zip} onLoad={onTransform} />
</group>
);
}
if (example === 3) {
const [tex1, tex2, tex3] = useLoader([
crate_gif,
brickWall_diffuse_jpg,
brickWall_normal_jpg,
]);
tex1.colorSpace = THREE.SRGBColorSpace;
tex2.colorSpace = THREE.SRGBColorSpace;
tex3.colorSpace = THREE.SRGBColorSpace;
const g = new THREE.BoxGeometry(10, 10, 10);
const m1 = new THREE.MeshBasicMaterial({ map: tex1 });
const m2 = new THREE.MeshStandardMaterial({ map: tex2, normalMap: tex3 });
return (
<group>
<mesh geometry={g} material={m1} position-x={-8} />
<mesh geometry={g} material={m2} position-x={8} />
</group>
);
}
};
const DemoUseSearch = () => {
const example = 1;
const urls = [jump_animation_glb, ratamahatta_md2_zip];
const objects = useLoader(urls);
const onLoad = (model) => {
if (example === 1) {
const searched = useSearch("Object3D", urls[0]);
const target = objects[0];
const found = searched[0];
console.log("object to search:", target);
console.log("object searched:", found);
console.log("same?", target.uuid === found.uuid);
}
if (example === 2) {
const searched = useSearch("Mesh", "Ch03");
console.log(searched);
}
if (example === 3) {
const searched = useSearch("Texture");
console.log(searched);
}
if (example === 4) {
const searched = useSearchObject(objects[0], "Material");
console.log(searched);
}
if (example === 5) {
const searched = useSearchObject(objects[0], "BufferGeometry");
console.log(searched);
}
};
return <primitive object={objects[0]} scale={10} onLoad={onLoad} />;
};
const DemoUseRefCallback = () => {
const [params, setParams] = useState({
rx: 0,
ry: 0,
col: [0, 0, 0],
});
const setMaterialMap = useRefCallback(
(object, url, mapType = "map", repeat = [1, 1]) => {
const tex = useLoader(url);
object.material[mapType] = tex;
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
tex.repeat.set(repeat[0], repeat[1]);
switch (mapType) {
case "map":
case "emissiveMap":
tex.colorSpace = THREE.SRGBColorSpace;
break;
case "envMap":
case "lightMap":
tex.colorSpace = THREE.LinearSRGBColorSpace;
break;
default:
tex.colorSpace = THREE.NoColorSpace;
break;
}
}
);
const ref = useRefEffect((box) => {
box.geometry.attributes.uv2 = box.geometry.attributes.uv;
setMaterialMap(box, gravel_diffuse_jpg, "map", [2, 2]);
setMaterialMap(box, gravel_normal_jpg, "normalMap", [2, 2]);
setMaterialMap(box, gravel_aoRoughMetal_jpg, "aoMap", [2, 2]);
setMaterialMap(box, gravel_aoRoughMetal_jpg, "roughnessMap", [2, 2]);
setMaterialMap(box, gravel_aoRoughMetal_jpg, "metalnessMap", [2, 2]);
box.material.normalScale = new THREE.Vector2(5, 5);
});
useFrame((t) => {
const r = THREE.MathUtils.lerp(0, 1, Math.sin(t) * 0.5 + 0.5);
const g = THREE.MathUtils.lerp(0, 1, Math.cos(t * 2) * 0.5 + 0.5);
setParams({
rx: t * 0.25,
ry: t * 0.5,
col: [r, g, 1 - r],
});
});
return (
<box
ref={ref}
scale={30}
rotation={[params.rx, params.ry, 0]}
type={"standard"}
color={params.col}
/>
);
};
const DemoSuspense = () => {
const example = 2;
if (example === 1) {
// rendering order: box > urls[0 or 1]
const urls = [ratamahatta_md2_zip, jump_animation_glb];
return (
<Suspense fallback={<box scale={5} color="red" />}>
<Model url={urls[0]} scale={0.25} />
<Model url={urls[1]} scale={10} />
</Suspense>
);
}
if (example === 2) {
// rendering order: urls[0] > urls[1 or 2] > urls[3]
const urls = [
Cow_zip,
ratamahatta_md2_zip,
LeePerrySmith_glb,
jump_animation_glb,
];
return (
<Suspense fallback={<Model url={urls[0]} scale={2} />}>
<Model url={urls[1]} scale={0.25} />
<Suspense fallback={<Model scale={0.01} url={urls[2]} />}>
<Model url={urls[3]} scale={10} />
</Suspense>
</Suspense>
);
}
};
const DemoHoverClick = () => {
const example = 2;
if (example === 1) {
const ref = useRef(null);
const [index, setIndex] = useState(0);
const [hovered, setHovered] = useState(false);
const sizes = [7, 10, 13, 16];
const colors = ["red", "green", "blue", "yellow"];
const size = sizes[index];
const color = hovered ? colors[index] : "white";
useFrame((t) => {
if (ref.current) ref.current.rotation.y = t;
});
return (
<mesh
ref={ref}
onClick={() => setIndex((index + 1) % 4)}
onPointerOver={() => setHovered(true)}
onPointerOut={() => setHovered(false)}
>
<boxGeometry args={[size, size, size]} />
<meshStandardMaterial color={color} />
</mesh>
);
}
if (example === 2) {
const ref = useRef(null);
const types = ["box", "sphere", "cylinder", "torus"];
const argss = [
[16, 16, 16],
[10, 32, 16],
[10, 10, 18],
[10, 4, 24, 48],
];
const diffuses = [
crate_gif,
brickWall_diffuse_jpg,
moss_diffuse_jpg,
rockMossy_diffuse_jpg,
];
const [index, setIndex] = useState(0);
const [hovered, setHovered] = useState(false);
const type = types[index];
const args = argss[index];
const diffuse = diffuses[index];
const opacity = hovered ? 1.0 : 0.2;
const onHover = (value) => setHovered(value);
const onClick = () => setIndex((index + 1) % 4);
useFrame((t) => {
if (ref.current) {
ref.current.rotation.x = t * 0.8;
ref.current.rotation.y = t;
}
});
return (
<mesh
ref={ref}
onClick={onClick}
onPointerOver={() => onHover(true)}
onPointerOut={() => onHover(false)}
>
<geometry type={type} args={args} />
<meshStandardMaterial transparent opacity={opacity} map={diffuse} />
</mesh>
);
}
};
const DemoUseAnimate = () => {
const ref = useRef(null);
const [hovered, setHovered] = useState(false);
const [scale, setScale] = useState(1);
const { replay, pause, flush } = useAnimate();
const color = hovered ? "green" : "white";
const onHover = (value) => {
setHovered(value);
value ? pause() : replay();
};
const onClick = () => {
setScale(scale === 1 ? 1.5 : 1);
flush();
};
useFrame((t) => {
if (ref.current) {
ref.current.rotation.y = t;
}
});
return (
<mesh
ref={ref}
scale={scale}
onClick={onClick}
onPointerOver={() => onHover(true)}
onPointerOut={() => onHover(false)}
>
<boxGeometry args={[7, 15, 9]} />
<meshStandardMaterial color={color} map={brickWall_diffuse_jpg} />
</mesh>
);
};
const DemoExamples = ({ example }) => {
switch (example) {
case 21:
return <DemoUseThree />;
case 22:
return <DemoShadow />;
case 23:
return <DemoShadow2 />;
case 24:
return <DemoShadow3 />;
case 25:
return <DemoUseLoader />;
case 26:
return <DemoUseSearch />;
case 27:
return <DemoUseRefCallback />;
case 28:
return <DemoSuspense />;
case 29:
return <DemoHoverClick />;
case 30:
return <DemoUseAnimate />;
}
};
export { DemoExamples };
Last updated