Spaces:
Running
Running
import * as THREE from 'three'; | |
import * as TWEEN from '@tweenjs/tween.js'; | |
const scene = new THREE.Scene(); | |
scene.background = new THREE.Color( | |
// 0xcccccc | |
'white' | |
); | |
const clock = new THREE.Clock(); | |
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000); | |
camera.position.set(0, 30, 50); | |
camera.lookAt(0, 3, 0); | |
const controls = new (<any>THREE).OrbitControls(camera); | |
const ambientLight = new THREE.AmbientLight(0xffffff, 1); | |
scene.add(ambientLight); | |
const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
renderer.setPixelRatio(window.devicePixelRatio); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
document.body.appendChild(renderer.domElement); | |
const stats = new Stats(); | |
document.body.appendChild(stats.dom); | |
/// Anim mixer | |
const mixers: THREE.AnimationMixer[] = []; | |
class Assets { | |
private static loadEggMtl(): Promise<THREE.Material[]> { | |
return new Promise((resolve, reject) => { | |
const loader: THREE.AnyLoader = new (<any>THREE).MTLLoader(); | |
loader.load( | |
`models/Egg_from_Poly_uovo/Egg from Poly uovo.mtl`, | |
(materials) => { | |
materials.preload(); | |
resolve(materials); | |
}, | |
(xhr) => {}, | |
reject | |
); | |
}); | |
} | |
private static loadEggObj(materials: THREE.Material[]): Promise<THREE.Object3D> { | |
return new Promise((resolve, reject) => { | |
const loader: THREE.AnyLoader = new (<any>THREE).OBJLoader(); | |
(<any>loader).setMaterials(materials); | |
loader.load( | |
`models/Egg_from_Poly_uovo/Egg from Poly uovo.obj`, | |
(object: THREE.Object3D) => { | |
resolve(object); | |
}, | |
(xhr) => { | |
// c.log(`${ xhr.loaded / xhr.total * 100 }% loaded`); | |
}, | |
(error) => { | |
c.error(error); | |
reject(error); | |
} | |
); | |
}); | |
} | |
static async loadEgg(): Promise<THREE.Object3D> { | |
const materialCreator = await this.loadEggMtl(); | |
return this.loadEggObj(materialCreator); | |
} | |
static loadEggGltf(): Promise<THREE.Scene> { | |
return new Promise((resolve, reject) => { | |
const loader: THREE.AnyLoader = new (<any>THREE).GLTFLoader(); | |
loader.load( | |
`models/Egg_gltf/Egg from Poly uovo copy.gltf`, | |
(gltf) => { | |
c.log(gltf); | |
resolve(gltf.scene); | |
} | |
); | |
}); | |
} | |
static loadDogDae(): Promise<{ | |
animations: THREE.AnimationClip[]; | |
scene: THREE.Group; | |
}> { | |
/// In Dae/Collada: did not manage to get | |
/// either the anims or the texture. | |
return new Promise((resolve, reject) => { | |
const loader: THREE.AnyLoader = new (<any>THREE).ColladaLoader(); | |
loader.load( | |
`models/dog/pup_lohound.dae`, | |
(collada) => { | |
resolve(collada); | |
} | |
); | |
}); | |
} | |
static loadDogFbx(): Promise<THREE.Group> { | |
return new Promise((resolve, reject) => { | |
const loader: THREE.AnyLoader = new (<any>THREE).FBXLoader(); | |
loader.load( | |
`models/dog_fbx/puppy-snapchat.fbx`, | |
(fbx) => { | |
resolve(fbx); | |
} | |
); | |
}); | |
} | |
static loadBoloss(): Promise<{ | |
animations: THREE.AnimationClip[]; | |
scene: THREE.Group; | |
}> { | |
return new Promise((resolve, reject) => { | |
const loader: THREE.AnyLoader = new (<any>THREE).ColladaLoader(); | |
loader.load( | |
`models/boloss/Boloss-3d v10.dae`, | |
(collada) => { | |
resolve(collada); | |
} | |
); | |
}); | |
} | |
} | |
class TUtils { | |
static boundingBox(o: THREE.Object3D): THREE.Box3 { | |
const bbox = new THREE.Box3().setFromObject(o); | |
return bbox; | |
} | |
static flushYZero(o: THREE.Object3D) { | |
o.position.y = -(this.boundingBox(o)).min.y; | |
} | |
static perform(tween: TWEEN.Tween): Promise<void> { | |
return new Promise(resolve => { | |
tween.onComplete(resolve).start(); | |
}); | |
} | |
} | |
(async () => { | |
/** | |
* scene construction | |
*/ | |
const gridHelper = new THREE.GridHelper(100, 100); | |
scene.add(gridHelper); | |
const axesHelper = new THREE.AxesHelper(50); | |
scene.add(axesHelper); | |
{ | |
const egg = await Assets.loadEgg(); | |
c.log(egg); | |
egg.scale.setScalar(.2); | |
egg.rotateX(-Math.PI / 2); | |
egg.position.x = -18; | |
TUtils.flushYZero(egg); | |
const box = new THREE.BoxHelper(egg); | |
scene.add(box); | |
scene.add(egg); | |
///// Manually set the material, for fun. | |
// const eggFace = egg.getObjectByName("CallKit-IconMask") as THREE.Mesh; | |
// c.log(eggFace.material); | |
// (<THREE.MeshPhongMaterial>(eggFace.material)).color.set(0x000000); | |
} | |
{ | |
const egg = await Assets.loadEggGltf(); | |
c.log(egg); | |
egg.scale.setScalar(100); | |
egg.position.x = -28; | |
TUtils.flushYZero(egg); | |
egg.remove(egg.getObjectByName('Camera')!); | |
scene.add(egg); | |
// c.log(Utils.boundingBox(egg)); | |
const box = new THREE.BoxHelper(egg, new THREE.Color('red')); | |
scene.add(box); | |
} | |
{ | |
////// dog_fbx | |
const dog = await Assets.loadDogFbx(); | |
// c.log((<any>dog).animations); | |
const mixer = new THREE.AnimationMixer(dog); | |
const clip: THREE.AnimationClip = (<any>dog).animations.find(clip => clip.name === "lohound|lohoundAction"); | |
/// ^^ this is the main parent animation! Do not play all children. | |
c.log(clip); | |
mixer.clipAction(clip).play(); | |
mixers.push(mixer); | |
const container = new THREE.Group(); | |
container.add(dog); | |
container.scale.setScalar(0.007); /// <- scale a container, not the dog itself or it'll fuck the anims. | |
container.position.x = -6; | |
scene.add(container); | |
const box = new THREE.BoxHelper(container, new THREE.Color('green')); | |
scene.add(box); | |
} | |
{ | |
const boloss = (await Assets.loadBoloss()).scene; | |
c.log(boloss); | |
boloss.position.x = 16; | |
TUtils.flushYZero(boloss); | |
scene.add(boloss); | |
const box = new THREE.BoxHelper(boloss, new THREE.Color('blue')); | |
scene.add(box); | |
/// Anims like in AudioBoloss | |
const rootModel = boloss.getObjectByName(`SketchUp`)!; | |
const pupilL = boloss.getObjectByName(`Pupil-left`)!; | |
const pupilR = boloss.getObjectByName(`Pupil-right`)!; | |
const pupils = new THREE.Group(); | |
pupils.add(pupilL, pupilR); | |
rootModel.add(pupils); | |
(async () => { | |
while (true) { | |
const translatePupil = new TWEEN.Tween(pupils.position) | |
.to({ x: "-1", y: "-1" }, 200) | |
.easing(TWEEN.Easing.Quadratic.Out) | |
; | |
const translatePupilRev = new TWEEN.Tween(pupils.position) | |
.to({ x: "+1", y: "+1" }, 200) | |
.easing(TWEEN.Easing.Quadratic.Out) | |
; | |
await TUtils.perform(translatePupil); | |
await Utils.wait(4, 1); | |
await TUtils.perform(translatePupilRev); | |
await Utils.wait(8, 3); | |
} | |
})(); | |
const eyebrowL = boloss.getObjectByName(`Eyebrow-left`)!; | |
const eyebrowR = boloss.getObjectByName(`Eyebrow-right`)!; | |
const eyebrows = new THREE.Group(); | |
eyebrows.add(eyebrowL, eyebrowR); | |
rootModel.add(eyebrows); | |
(async () => { | |
while (true) { | |
const scaleEyebrow = new TWEEN.Tween(eyebrows.scale) | |
.to({ x: 1.08, y: 1.08, z: 1.08 }, 100) | |
.easing(TWEEN.Easing.Quadratic.InOut) | |
; | |
const scaleEyebrowRev = new TWEEN.Tween(eyebrows.scale) | |
.to({ x: 1, y: 1, z: 1 }, 100) | |
.easing(TWEEN.Easing.Quadratic.InOut) | |
; | |
await Utils.wait(6, 6); | |
await TUtils.perform(scaleEyebrow); | |
await TUtils.perform(scaleEyebrowRev); | |
await Utils.wait(0.14); | |
await TUtils.perform(scaleEyebrow); | |
await TUtils.perform(scaleEyebrowRev); | |
} | |
})(); | |
(async () => { | |
while (true) { | |
const angle = Utils.randomFloat(-0.2, 0.3); | |
const dummyL = new THREE.Object3D(); | |
dummyL.rotateOnAxis(new THREE.Vector3(0, 1, 0.8), angle); | |
const dummyR = new THREE.Object3D(); | |
dummyR.rotateOnAxis(new THREE.Vector3(0, -1, -0.8), angle); | |
/// ^^ exact same result as keeping the same vector and negating the angle. | |
const rotateBrowL = new TWEEN.Tween(eyebrowL.rotation) | |
.to({ | |
x: dummyL.rotation.x, | |
y: dummyL.rotation.y, | |
z: dummyL.rotation.z, | |
}, 300) | |
; | |
const rotateBrowR = new TWEEN.Tween(eyebrowR.rotation) | |
.to({ | |
x: dummyR.rotation.x, | |
y: dummyR.rotation.y, | |
z: dummyR.rotation.z, | |
}, 300) | |
; | |
await Promise.all([ | |
TUtils.perform(rotateBrowL), | |
TUtils.perform(rotateBrowR), | |
]); | |
await Utils.wait(1, 1); | |
await Promise.all([ | |
TUtils.perform( | |
new TWEEN.Tween(eyebrowL.rotation).to({ x: 0, y: 0, z: 0 }, 300) | |
), | |
TUtils.perform( | |
new TWEEN.Tween(eyebrowR.rotation).to({ x: 0, y: 0, z: 0 }, 300) | |
), | |
]); | |
await Utils.wait(1, 1); | |
/// ^^ not the exact same behavior as in AudioBoloss (all waits are actually randoms there.) | |
} | |
})(); | |
} | |
})(); | |
/** | |
* MAIN() | |
*/ | |
window.addEventListener('resize', onWindowResize, false); | |
function onWindowResize() { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
} | |
function render() { | |
const delta = clock.getDelta(); | |
for (const mixer of mixers) { | |
mixer.update(delta); | |
} | |
renderer.render(scene, camera); | |
} | |
function animate() { | |
requestAnimationFrame(animate); | |
TWEEN.update(); | |
render(); | |
stats.update(); | |
} | |
animate(); | |