web3d / src /index.ts
Julien Chaumond
[Workflow] We don't actually use the vscode task
4030e5c
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();