import * as THREE from "three" import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader" import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import { VRMLoaderPlugin } from "@pixiv/three-vrm"; import { createVRMAnimationClip, VRMAnimationLoaderPlugin } from "@pixiv/three-vrm-animation"; import { Color, DirectionalLight, Fog, HemisphereLight } from 'three'; import { GridHelper, Mesh, MeshLambertMaterial, BoxGeometry, Vector3 } from 'three'; import { VRMSpringBoneManager, VRMSpringBoneJoint, VRMSpringBoneJointHelper } from '@pixiv/three-vrm-springbone'; window.addEventListener("DOMContentLoaded", () => { const canvas = document.getElementById("canvas"); if (canvas == null) return; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 30, canvas.clientWidth/canvas.clientHeight, 0.1, 20); camera.position.set(0.0, 0.9, -4.0) camera.rotation.set(0.0, Math.PI, 0.0) camera.lookAt(new THREE.Vector3(0, 0, 0)); // https://threejs.org/docs/#api/en/constants/Renderer const renderer = new THREE.WebGLRenderer({antialias: true, alpha: true}); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(canvas.clientWidth, canvas.clientHeight); renderer.setClearColor(0x7fbfff, 1.0); renderer.shadowMap.enabled = true; renderer.outputColorSpace = THREE.SRGBColorSpace; renderer.toneMapping = THREE.ReinhardToneMapping; renderer.toneMapping = THREE.NeutralToneMapping; canvas.appendChild(renderer.domElement); renderer.toneMappingExposure = 1.5; //renderer.toneMapping = THREE.ACESFilmicToneMapping; const light = new THREE.DirectionalLight(0xffffff, Math.PI); light.position.set(1.0, 1.0, 1.0); scene.add(light); let currentVrm: any = undefined; let currentVrmAnimation: any = undefined; let currentMixer:any = undefined; function load(url: string) { loader.load( url, (gltf) => { tryInitVRM(gltf); tryInitVRMA(gltf); }, (progress) => console.log( "Loading model...", 100.0 * (progress.loaded / progress.total), "%" ), (error) => console.error(error) ); } function tryInitVRM(gltf: any) { const vrm = gltf.userData.vrm; if ( vrm == null ) { return; } currentVrm = vrm; scene.add(vrm.scene); initAnimationClip(); } function tryInitVRMA(gltf: any) { const vrmAnimations = gltf.userData.vrmAnimations; if (vrmAnimations == null) { return; } currentVrmAnimation = vrmAnimations[0] ?? null; initAnimationClip(); } function initAnimationClip() { if (currentVrm && currentVrmAnimation) { currentMixer = new THREE.AnimationMixer(currentVrm.scene); const clip = createVRMAnimationClip(currentVrmAnimation, currentVrm); currentMixer.clipAction(clip).play(); } } const loader = new GLTFLoader(); loader.register((parser) => { return new VRMLoaderPlugin(parser); }); loader.register((parser) => { return new VRMAnimationLoaderPlugin(parser); }); load("/vrma/ai.vrm"); load("/vrma/fly_c.vrma"); const clock = new THREE.Clock(); clock.start(); const update = () => { controls.update(); requestAnimationFrame(update); const deltaTime = clock.getDelta(); if (currentMixer) { currentMixer.update(deltaTime); } if (currentVrm) { currentVrm.update(deltaTime); } renderer.render(scene, camera); } scene.background = new THREE.Color( 0xffffff ); const directionalLight = new THREE.DirectionalLight(0xffffff); directionalLight.position.set(1, 1, 1); scene.add(directionalLight); const ambientLight = new THREE.AmbientLight(0x333333); scene.add(ambientLight); const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.2; controls.enableRotate = true; controls.target.set( 0.0, 1.0, 0.0 ); function floor_default(){ const floor = new Mesh( new BoxGeometry(50, 100), new MeshLambertMaterial({ color: 0xffffff, depthWrite: true, }) ); floor.position.y = -1.0; floor.rotation.x = -Math.PI / 2; floor.name = "floor"; scene.add(floor); } function floor_grid(){ const grid = new GridHelper(50, 100, 0xffffff, 0xffffff); scene.add(grid); grid.position.set(Math.round(0), 0, Math.round(0)); } function floor_bg(){ scene.fog = new Fog(0xffffff, 3, 20); scene.fog?.color.set(0xffffff); } floor_default(); floor_grid(); floor_bg(); update(); function animate() { requestAnimationFrame(animate); scene.rotation.y += 0.005; renderer.render(scene, camera); } animate(); function random_happy() { // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0/expressions.ja.md currentVrm.expressionManager.setValue('relaxed', 0.5); } function random_head() { // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0/lookAt.ja.md currentVrm.lookAt.target = camera; currentVrm.VRMLookAtBoneApplier = camera; currentVrm.VRMLookAtExpressionApplier = camera; // https://github.com/vrm-c/vrm-specification/blob/master/specification/VRMC_vrm-1.0/humanoid.ja.md const head = currentVrm.humanoid.getRawBoneNode("head"); head.target = camera; } function random_blink(){ setInterval(() => { currentVrm.expressionManager.setValue('relaxed', 0); currentVrm.expressionManager.setValue('blink', 0); random_head(); const r = Math.floor(Math.random() * 3); if (r == 1) { setTimeout(() => { currentVrm.expressionManager.setValue('blink', 1); }, 5000); setTimeout(() => { currentVrm.expressionManager.setValue('blink', 0); }, 5500); }; setTimeout(() => { currentVrm.expressionManager.setValue('relaxed', 0.5); currentVrm.expressionManager.setValue('blink', 1); }, 6000); }, 6500); } random_blink(); setInterval(() => { const r = Math.floor(Math.random() * 4 + 1); load("/vrma/" + r + ".vrma"); setTimeout(() => { load("/vrma/fly_c.vrma"); }, 10000); }, 15000); function light_off(){ renderer.toneMapping = THREE.ACESFilmicToneMapping; light.intensity = -0.5; scene.background = new THREE.Color(0x000000); scene.fog = new Fog(0x000000, 3, 20); } //light_off(); })