canvas#mimiCanvas
View Compiled
body {
width: 100vw;
height: 100vh;
background-image: linear-gradient(#31a2f2, #225af6);
}
canvas {
vertical-align: middle;
}
View Compiled
"use strict";
import * as THREE from "https://cdn.skypack.dev/[email protected]";
import gsap from "https://cdn.skypack.dev/[email protected]";
// DELETEME: Just for modelling
// import {
// OrbitControls
// } from "https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls.js";
console.clear();
const IS_DEBUG = false;
const ASSETS_PATH = "https://assets.codepen.io/430361";
const mimiCanvas = document.getElementById("mimiCanvas");
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
45, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({
canvas: mimiCanvas,
alpha: true,
});
// DELETEME: Just for modelling
// const controls = new OrbitControls(camera, mimiCanvas);
// controls.target.set(0, 0, 0);
// controls.update();
const mimiModel = new THREE.Group();
const tl = gsap.timeline({
repeat: -1,
delay: 1,
repeatDelay: 2,
});
function getRadian(degree) {
return degree * Math.PI / 180;
}
function setRenderer() {
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
}
function setLighting() {
const ambientColor = 0xFFFFFF;
const ambientIntensity = 0.7;
const ambientLight = new THREE.AmbientLight(ambientColor, ambientIntensity);
scene.add(ambientLight);
const directionalColor = 0xFFFFFF;
const directionalIntensity = 1;
const directionalLight = new THREE.DirectionalLight(
directionalColor, directionalIntensity);
const directionalX = -1;
const directionalY = 1;
const directionalZ = 2;
directionalLight.position.set(directionalX, directionalY, directionalZ);
directionalLight.castShadow = true;
scene.add(directionalLight);
scene.add(directionalLight.target);
if (IS_DEBUG) {
scene.add(new THREE.CameraHelper(directionalLight.shadow.camera));
}
}
////////////////////////////////////////////////////////////////////////////////
// MODELLING START //
////////////////////////////////////////////////////////////////////////////////
function createMesh(geometry, color) {
const material = new THREE.MeshStandardMaterial({
color: (typeof color === "number") ? color : 0xff0000,
roughness: 0.8,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);
return mesh;
}
function createMeshTexture(geometry, textureName, color) {
return new Promise(resolve => {
const loader = new THREE.TextureLoader();
loader.load(`${ASSETS_PATH}/${textureName}`, texture => {
const material = new THREE.MeshStandardMaterial({
map: texture,
roughness: 0.8,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(geometry, material);
resolve(mesh);
});
});
}
function createSphere(opt, texture) {
// Default value is based on ThreeJS documentation
// Except widthSegments and heightSegments
opt = {
radius: opt.radius !== undefined ? opt.radius : 1,
widthSegments: opt.widthSegments !== undefined ? opt.widthSegments : 16,
heightSegments: opt.heightSegments !== undefined ? opt.heightSegments : 12,
phiStart: opt.phiStart !== undefined ? opt.phiStart : 0,
phiLength: opt.phiLength !== undefined ? opt.phiLength : Math.PI * 2,
thetaStart: opt.thetaStart !== undefined ? opt.thetaStart : 0,
thetaLength: opt.thetaLength !== undefined ? opt.thetaLength : Math.PI,
};
const geometry = new THREE.SphereGeometry(
opt.radius,
opt.widthSegments,
opt.heightSegments,
opt.phiStart,
opt.phiLength,
opt.thetaStart,
opt.thetaLength,
);
if (typeof texture === "number") {
return createMesh(geometry, texture);
}
return new Promise(async (resolve) => {
resolve(createMeshTexture(geometry, texture));
});
}
function createCylinder(opt, texture) {
// Default value is based on ThreeJS documentation
// Except radialSegments
opt = {
radiusTop: opt.radiusTop != undefined ? opt.radiusTop : 1,
radiusBottom: opt.radiusBottom != undefined ? opt.radiusBottom : 1,
height: opt.height != undefined ? opt.height : 1,
radialSegments: opt.radialSegments != undefined ? opt.radialSegments : 16,
heightSegments: opt.heightSegments != undefined ? opt.heightSegments : 1,
openEnded: opt.openEnded != undefined ? opt.openEnded : false,
thetaStart: opt.thetaStart != undefined ? opt.thetaStart : 0,
thetaLength: opt.thetaLength != undefined ? opt.thetaLength : Math.PI * 2,
};
const geometry = new THREE.CylinderGeometry(
opt.radiusTop,
opt.radiusBottom,
opt.height,
opt.radialSegments,
opt.heightSegments,
opt.openEnded,
opt.thetaStart,
opt.thetaLength,
);
if (typeof texture === "number") {
return createMesh(geometry, texture);
}
return new Promise(async (resolve) => {
resolve(createMeshTexture(geometry, texture));
});
}
function createBox(opt, texture) {
// Default value is based on ThreeJS documentation
opt = {
width: opt.width != undefined ? opt.width : 1,
height: opt.height != undefined ? opt.height : 1,
depth: opt.depth != undefined ? opt.depth : 1,
widthSegments: opt.widthSegments != undefined ? opt.widthSegments : 1,
heightSegments: opt.heightSegments != undefined ? opt.heightSegments : 1,
depthSegments: opt.depthSegments != undefined ? opt.depthSegments : 1,
};
const geometry = new THREE.BoxGeometry(
opt.width,
opt.height,
opt.depth,
opt.widthSegments,
opt.heightSegments,
opt.depthSegments,
);
if (typeof texture === "number") {
return createMesh(geometry, texture);
}
return new Promise(async (resolve) => {
resolve(createMeshTexture(geometry, texture));
});
}
function createPlane(opt, texture) {
// Default value is based on ThreeJS documentation
opt = {
width: opt.width != undefined ? opt.width : 1,
height: opt.height != undefined ? opt.height : 1,
widthSegments: opt.widthSegments != undefined ? opt.widthSegments : 1,
heightSegments: opt.heightSegments != undefined ? opt.heightSegments : 1,
};
const geometry = new THREE.PlaneGeometry(
opt.width,
opt.height,
opt.widthSegments,
opt.heightSegments,
);
if (typeof texture === "number") {
return createMesh(geometry, texture);
}
return new Promise(async (resolve) => {
resolve(createMeshTexture(geometry, texture));
});
}
function createCircle(opt, texture) {
// Default value is based on ThreeJS documentation
// Except segments
opt = {
radius: opt.radius !== undefined ? opt.radius : 1,
segments: opt.segments !== undefined ? opt.segments : 16,
thetaStart: opt.thetaStart !== undefined ? opt.thetaStart : 0,
thetaLength: opt.thetaLength !== undefined ? opt.thetaLength : Math.PI,
};
const geometry = new THREE.CircleGeometry(
opt.radius,
opt.segments,
opt.thetaStart,
opt.thetaLength,
);
if (typeof texture === "number") {
return createMesh(geometry, texture);
}
return new Promise(async (resolve) => {
resolve(createMeshTexture(geometry, texture));
});
}
async function createHead() {
const head = new THREE.Group();
return new Promise(async (resolve) => {
const face = await createSphere({
radius: 0.3,
phiStart: getRadian(-90),
}, "mimi-face.png");
const leftEar = createCylinder({
radiusTop: 0,
radiusBottom: 0.15,
height: 0.29,
radialSegments: 3,
thetaStart: getRadian(2 / 3 * 100),
}, 0xcccccc);
leftEar.position.y = 0.27;
leftEar.position.x = 0.24;
leftEar.position.z = 0.1;
leftEar.rotation.z = getRadian(-35);
leftEar.rotation.x = getRadian(15);
const rightEar = leftEar.clone();
rightEar.position.x = -0.24;
rightEar.rotation.z = getRadian(35);
head.add(face, leftEar, rightEar);
resolve(head);
});
}
async function createBody() {
const body = new THREE.Group();
return new Promise(async (resolve) => {
const topBody = await createCylinder({
radiusTop: 0.1,
radiusBottom: 0.2,
height: 0.4,
radialSegments: 16,
thetaStart: getRadian(180),
}, "mimi-body.png");
const bottomBody = createSphere({
radius: 0.2825,
phiStart: 0,
thetaStart: getRadian(135),
thetaLength: getRadian(45),
}, 0x493c2b);
bottomBody.position.y = 0;
body.add(topBody, bottomBody);
resolve(body);
});
}
async function createArms() {
const arms = new THREE.Group();
return new Promise(async (resolve) => {
const leftArm = new THREE.Group();
const leftShoulder = createSphere({ radius: 0.05 }, 0xa46422);
const leftArm1 = createCylinder({
radiusTop: 0.05,
radiusBottom: 0.05,
height: 0.1,
}, 0xa46422);
const leftElbow = leftShoulder.clone();
const leftArm2 = leftArm1.clone();
const leftHand = createSphere({ radius: 0.075 }, 0xcccccc);
leftArm.position.x = 0.12;
leftArm.position.y = -0.13;
leftArm.rotation.z = getRadian(-45);
leftArm.add(leftShoulder);
leftShoulder.add(leftArm1);
leftArm1.rotation.z = getRadian(90);
leftArm1.position.x = 0.05;
leftArm1.add(leftElbow);
leftElbow.position.y = -0.05;
leftElbow.add(leftArm2);
leftArm2.position.y = -0.05;
leftArm2.add(leftHand);
leftHand.position.y = -0.11;
const rightArm = leftArm.clone();
rightArm.position.x = -0.12;
rightArm.rotation.z = getRadian(225);
arms.add(leftArm, rightArm);
resolve(arms);
});
}
async function createLegs() {
const legs = new THREE.Group();
return new Promise(async (resolve) => {
const leftLeg = new THREE.Group();
const leftHip = createSphere({ radius: 0.05 }, 0x493c2b);
const leftLeg1 = createCylinder({
radiusTop: 0.05,
radiusBottom: 0.05,
height: 0.1,
}, 0x493c2b);
const leftKnee = leftHip.clone();
const leftLeg2 = leftLeg1.clone();
const leftShoe1 = createCylinder({
radiusTop: 0.06,
radiusBottom: 0.06,
height: 0.15,
thetaLength: Math.PI,
}, 0x1b2632);
const leftShoe2 = createSphere({
radius: 0.06,
phiStart: Math.PI / 2,
phiLength: Math.PI,
}, 0x1b2632);
const leftSole1 = createPlane({
width: 0.12,
height: 0.15,
}, 0x1b2632);
const leftSole2 = createCircle({
radius: 0.06,
thetaLength: Math.PI,
}, 0x1b2632);
leftLeg.position.y = -0.46;
leftLeg.position.x = 0.09;
leftLeg.add(leftHip);
leftHip.add(leftLeg1);
leftLeg1.position.y = -0.05;
leftLeg1.add(leftKnee);
leftKnee.position.y = -0.05;
leftKnee.add(leftLeg2);
leftLeg2.position.y = -0.05;
leftLeg2.add(leftShoe1);
leftShoe1.position.y = -0.1;
leftShoe1.position.z = 0.01;
leftShoe1.rotation.x = getRadian(90);
leftShoe1.rotation.y = getRadian(90);
leftShoe1.add(leftShoe2);
leftShoe1.add(leftSole1);
leftShoe2.position.y = 0.08;
leftShoe2.add(leftSole2);
leftSole1.rotation.y = getRadian(90);
leftSole2.rotation.y = getRadian(-90);
const rightLeg = leftLeg.clone();
rightLeg.position.x = -0.09;
legs.add(leftLeg, rightLeg);
resolve(legs);
});
}
function createModel() {
return new Promise(async (resolve) => {
const head = await createHead();
const body = await createBody();
const arms = await createArms();
const legs = await createLegs();
head.position.y = 0.2;
body.position.y = -0.2;
mimiModel.add(head, body, arms, legs);
mimiModel.castShadow = true;
scene.add(mimiModel);
resolve();
});
}
////////////////////////////////////////////////////////////////////////////////
// MODELLING END //
////////////////////////////////////////////////////////////////////////////////
function addPlatform() {
return new Promise(async (resolve) => {
const platform = await createPlane({
width: 2,
height: 2,
}, "infinite-cars-grass.png");
platform.rotation.x = Math.PI / 2;
platform.position.y = -0.72;
platform.receiveShadow = true;
scene.add(platform);
resolve();
});
}
////////////////////////////////////////////////////////////////////////////////
// ANIMATION START //
////////////////////////////////////////////////////////////////////////////////
function animateArms(arms) {
const rightArm = arms.children[1];
const rightShoulder = rightArm.children[0];
const rightElbow = rightShoulder.children[0].children[0];
// rightElbow.rotation.z = getRadian(-30);
tl.to(rightShoulder.rotation, {
z: getRadian(-70),
ease: "linear",
duration: 0.5,
}, 0);
tl.to(rightElbow.rotation, {
z: getRadian(-45),
ease: "linear",
duration: 0.4,
repeat: 5,
yoyo: true,
yoyoEase: true,
});
tl.to(rightShoulder.rotation, {
z: 0,
ease: "linear",
duration: 0.3,
});
}
function animateLegs(legs) {
const leftLeg = legs.children[0];
const leftHip = leftLeg.children[0];
tl.to(leftHip.rotation, {
z: getRadian(25),
duration: 0.2,
}, 0);
tl.to(leftHip.rotation, {
z: 0,
ease: "linear",
duration: 0.1,
}, 2.7);
}
function animateBody() {
tl.to(mimiModel.position, {
y: 0.1,
ease: "power2.out",
duration: 0.2,
}, 0);
tl.to(mimiModel.position, {
y: 0,
ease: "power2.in",
duration: 0.1,
}, 0.2);
tl.to(mimiModel.rotation, {
z: getRadian(-25),
ease: "power2.out",
duration: 0.2,
}, 0);
tl.to(mimiModel.position, {
y: 0.1,
ease: "power2.out",
duration: 0.2,
}, 2.7);
tl.to(mimiModel.position, {
y: 0,
ease: "power2.in",
duration: 0.1,
}, 2.9);
tl.to(mimiModel.rotation, {
z: 0,
ease: "power2.out",
duration: 0.2,
}, 2.7);
}
function setAnimation() {
animateArms(mimiModel.children[2]);
animateLegs(mimiModel.children[3]);
animateBody();
}
////////////////////////////////////////////////////////////////////////////////
// ANIMATION END //
////////////////////////////////////////////////////////////////////////////////
function update() {
requestAnimationFrame(update);
renderer.render(scene, camera);
mimiModel.rotation.y += 0.0075;
// DELETEME: Just for modelling
// controls.update();
}
async function initialize() {
setRenderer();
// Initialize camera position
camera.position.z = 2.5;
if (IS_DEBUG) {
scene.add(new THREE.CameraHelper(camera));
}
setLighting();
await createModel();
// await addPlatform();
setAnimation();
update();
}
initialize();
window.addEventListener("resize", evt => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.