Files
portfolio_02/src/components/Character/Scene.tsx
2026-01-30 03:53:38 +07:00

161 lines
5.2 KiB
TypeScript

import { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import setCharacter from "./utils/character";
import setLighting from "./utils/lighting";
import { useLoading } from "../../context/LoadingProvider";
import handleResize from "./utils/resizeUtils";
import {
handleMouseMove,
handleTouchEnd,
handleHeadRotation,
handleTouchMove,
} from "./utils/mouseUtils";
import setAnimations from "./utils/animationUtils";
import { setProgress } from "../Loading";
const Scene = () => {
const canvasDiv = useRef<HTMLDivElement | null>(null);
const hoverDivRef = useRef<HTMLDivElement>(null);
const sceneRef = useRef(new THREE.Scene());
const { setLoading } = useLoading();
const [character, setChar] = useState<THREE.Object3D | null>(null);
useEffect(() => {
if (canvasDiv.current) {
let rect = canvasDiv.current.getBoundingClientRect();
let container = { width: rect.width, height: rect.height };
const aspect = container.width / container.height;
const scene = sceneRef.current;
const renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true,
});
renderer.setSize(container.width, container.height);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
canvasDiv.current.appendChild(renderer.domElement);
const camera = new THREE.PerspectiveCamera(14.5, aspect, 0.1, 1000);
camera.position.z = 10;
camera.position.set(0, 13.1, 24.7);
camera.zoom = 1.1;
camera.updateProjectionMatrix();
let headBone: THREE.Object3D | null = null;
let screenLight: any | null = null;
let mixer: THREE.AnimationMixer;
const clock = new THREE.Clock();
const light = setLighting(scene);
let progress = setProgress((value) => setLoading(value));
const { loadCharacter } = setCharacter(renderer, scene, camera);
loadCharacter().then((gltf) => {
if (gltf) {
const animations = setAnimations(gltf);
hoverDivRef.current && animations.hover(gltf, hoverDivRef.current);
mixer = animations.mixer;
let character = gltf.scene;
setChar(character);
scene.add(character);
headBone = character.getObjectByName("spine006") || null;
screenLight = character.getObjectByName("screenlight") || null;
progress.loaded().then(() => {
setTimeout(() => {
light.turnOnLights();
animations.startIntro();
}, 2500);
});
window.addEventListener("resize", () =>
handleResize(renderer, camera, canvasDiv, character)
);
}
});
let mouse = { x: 0, y: 0 },
interpolation = { x: 0.1, y: 0.2 };
const onMouseMove = (event: MouseEvent) => {
handleMouseMove(event, (x, y) => (mouse = { x, y }));
};
let debounce: number | undefined;
const onTouchStart = (event: TouchEvent) => {
const element = event.target as HTMLElement;
debounce = setTimeout(() => {
element?.addEventListener("touchmove", (e: TouchEvent) =>
handleTouchMove(e, (x, y) => (mouse = { x, y }))
);
}, 200);
};
const onTouchEnd = () => {
handleTouchEnd((x, y, interpolationX, interpolationY) => {
mouse = { x, y };
interpolation = { x: interpolationX, y: interpolationY };
});
};
document.addEventListener("mousemove", (event) => {
onMouseMove(event);
});
const landingDiv = document.getElementById("landingDiv");
if (landingDiv) {
landingDiv.addEventListener("touchstart", onTouchStart);
landingDiv.addEventListener("touchend", onTouchEnd);
}
const animate = () => {
requestAnimationFrame(animate);
if (headBone) {
handleHeadRotation(
headBone,
mouse.x,
mouse.y,
interpolation.x,
interpolation.y,
THREE.MathUtils.lerp
);
light.setPointLight(screenLight);
}
const delta = clock.getDelta();
if (mixer) {
mixer.update(delta);
}
renderer.render(scene, camera);
};
animate();
return () => {
clearTimeout(debounce);
scene.clear();
renderer.dispose();
window.removeEventListener("resize", () =>
handleResize(renderer, camera, canvasDiv, character!)
);
if (canvasDiv.current) {
canvasDiv.current.removeChild(renderer.domElement);
}
if (landingDiv) {
document.removeEventListener("mousemove", onMouseMove);
landingDiv.removeEventListener("touchstart", onTouchStart);
landingDiv.removeEventListener("touchend", onTouchEnd);
}
};
}
}, []);
return (
<>
<div className="character-container">
<div className="character-model" ref={canvasDiv}>
<div className="character-rim"></div>
<div className="character-hover" ref={hoverDivRef}></div>
</div>
</div>
</>
);
};
export default Scene;