const button = document.querySelector('[data-btn]')
const canvas = document.querySelector('.webgl')
const scene = new THREE.Scene()
scene.background = new THREE.Color(0x6c9de6)
// Helpers
const degreesToRadians = (degrees) => {
return degrees * (Math.PI / 180)
}
const random = (min, max, float = false) => {
const val = Math.random() * (max - min) + min
if (float) {
return val
}
return Math.floor(val)
}
// Params
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
const params = {
rx: 0,
ry: 0,
rx: 0,
headRotation: degreesToRadians(50),
tailRotation: degreesToRadians(-15),
tailColor: 0x792cb8
}
/**
* Renderer
*/
const renderer = new THREE.WebGLRenderer({
canvas
})
const render = (renderer) => {
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.render(scene, camera)
}
// Camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 1000)
camera.position.z = 60
scene.add(camera)
// Material
const material = new THREE.MeshLambertMaterial({
color: 0xffffff
})
// Lighting
const lightAmbient = new THREE.AmbientLight(0x9eaeff, 0.62)
scene.add(lightAmbient)
const lightDirectional = new THREE.DirectionalLight(0xffffff, 0.55)
scene.add(lightDirectional)
lightDirectional.position.set(8, 10, 25)
// Shadow
const textureLoader = new THREE.TextureLoader()
const shadow = textureLoader.load('https://assets.codepen.io/85648/shadow-02.jpg')
const planeGeometry = new THREE.PlaneGeometry(150, 150)
const planeMaterial = new THREE.MeshBasicMaterial({
color: 0x4580d9,
transparent: true,
alphaMap: shadow
})
const planeMesh = new THREE.Mesh(planeGeometry, planeMaterial)
scene.add(planeMesh)
planeMesh.rotation.x = degreesToRadians(-90)
planeMesh.position.y = -10.5
/* Animation */
const mainAnimation = gsap.to(params, {
rx: degreesToRadians(360),
ry: degreesToRadians(360),
z: degreesToRadians(360),
repeat: -1,
duration: 20,
ease: 'none'
})
const headAnimation = gsap.to(params, {
headRotation: degreesToRadians(60),
duration: 0.4,
repeat: -1,
yoyo: true,
})
const tailAnimation = gsap.to(params, {
tailRotation: degreesToRadians(15),
duration: 0.4,
repeat: -1,
yoyo: true,
})
const legRotations = [...Array(4)].map(() => {
return {
r: degreesToRadians(30)
}
})
const legAnimations = []
legRotations.forEach((leg, index) => {
const animation = gsap.to(leg, {
r: degreesToRadians(-10),
duration: 0.4,
repeat: -1,
yoyo: true,
delay: index * 0.3
})
legAnimations.push(animation)
})
class Unicorn {
constructor() {
this.group = new THREE.Group()
this.group.rotation.x = degreesToRadians(10)
this.group.rotation.y = degreesToRadians(30)
scene.add(this.group)
this.bodyMaterial = new THREE.MeshLambertMaterial({
color: 0xfef7ff
})
this.tailMaterial = new THREE.MeshLambertMaterial({
color: 0x792cb8
})
}
createLegs(bodyX, bodyY) {
this.legs = []
const {
rt,
rb,
height
} = {
rt: 1.5,
rb: 1,
height: 5.3
}
const geometry = new THREE.CylinderGeometry(rt, rb, height, 32)
for (let i = 0; i < 4; i++) {
const group = new THREE.Group()
const m = i % 2 === 0 ? -1 : 1
const legMesh = new THREE.Mesh(geometry, this.bodyMaterial)
let posX = i > 1 ? bodyY * -0.5 + rt : bodyY * 0.5 - rt
legMesh.position.y = height * -0.5
legMesh.position.z = height * 0.5 * m
group.add(legMesh)
group.position.x = posX
group.position.y = bodyY * -0.5 + 0.5
group.position.z = bodyY * 0.08 * m
this.bodyGroup.add(group)
this.legs.push(group)
}
}
createBody() {
const bodyX = 12
const bodyY = 10
this.bodyGroup = new THREE.Group()
const geometry = new THREE.BoxGeometry(bodyX, bodyY, bodyY)
const bodyMain = new THREE.Mesh(geometry, this.bodyMaterial)
this.bodyGroup.add(bodyMain)
this.group.add(this.bodyGroup)
this.createLegs(bodyX, bodyY)
}
createHead() {
const headX = 8
const headY = 12
const headZ = 8
this.headGroup = new THREE.Group()
const geometry = new THREE.BoxGeometry(headX, headY, headZ)
this.headMain = new THREE.Mesh(geometry, this.bodyMaterial)
this.headGroup.add(this.headMain)
this.group.add(this.headGroup)
this.headMain.position.x = headX * 0.5
this.headMain.position.y = headY * -0.5
this.headGroup.position.x = 4
this.headGroup.position.y = 10
// Neck
const neckGeometry = new THREE.BoxGeometry(1.5, 4, 8)
const neck = new THREE.Mesh(neckGeometry, this.bodyMaterial)
this.headGroup.add(neck)
neck.position.x = -0.75
neck.position.y = -2
this.createEyes(headX, headY)
this.createEars(headX, headY)
this.createHorn(headX, headY)
}
createEyes(headX, headY) {
const material = new THREE.MeshLambertMaterial({
color: 0x242c38
})
const geometry = new THREE.SphereGeometry(1.125, 12, 12)
for (let i = 0; i < 2; i++) {
const m = i % 2 === 0 ? -1 : 1
const eye = new THREE.Mesh(geometry, material)
eye.position.x = 4.5
eye.position.z = headX * 0.5 * m
eye.position.y = headY * -0.5
this.headGroup.add(eye)
}
}
createEars(headX, headY) {
const x = 1.5,
y = 4
const geometry = new THREE.ConeGeometry(x, y, 32)
for (let i = 0; i < 2; i++) {
const m = i % 2 === 0 ? -1 : 1
const ear = new THREE.Mesh(geometry, this.bodyMaterial)
this.headGroup.add(ear)
ear.position.x = headX - (x * 0.5)
ear.position.y = 1.2
ear.position.z = headX * 0.3 * m
ear.rotation.z = degreesToRadians(-30)
}
}
createTail() {
this.tailGroup = new THREE.Group()
const material = this.tailMaterial
let geometry = new THREE.SphereGeometry(2, 20, 20)
let tail = new THREE.Mesh(geometry, material)
tail.position.x = -7.5
this.tailGroup.add(tail)
geometry = new THREE.SphereGeometry(2.8, 20, 20)
tail = new THREE.Mesh(geometry, material)
tail.position.x = -9
tail.position.y = -0.5
tail.position.z = -0.5
this.tailGroup.add(tail)
geometry = new THREE.SphereGeometry(2.5, 20, 20)
tail = new THREE.Mesh(geometry, material)
tail.position.x = -9.5
tail.position.y = -2.1
tail.position.z = 0
this.tailGroup.add(tail)
geometry = new THREE.SphereGeometry(2, 20, 20)
tail = new THREE.Mesh(geometry, material)
tail.position.x = -11
tail.position.y = -2
tail.position.z = -1.1
this.tailGroup.add(tail)
geometry = new THREE.SphereGeometry(2, 20, 20)
tail = new THREE.Mesh(geometry, material)
tail.position.x = -10.5
tail.position.y = -3.7
tail.position.z = 0
this.tailGroup.add(tail)
this.group.add(this.tailGroup)
this.tailGroup.position.y = 3
}
createHorn(headX, headY) {
const geometry = new THREE.ConeGeometry(2, 6, 6)
const material = new THREE.MeshLambertMaterial({
color: 0xfc9cff
})
const horn = new THREE.Mesh(geometry, material)
this.headGroup.add(horn)
horn.position.x = headY - 1
horn.position.y = -3.5
horn.rotation.z = degreesToRadians(-90)
}
init() {
this.createBody()
this.createHead()
this.createTail()
}
}
const unicorn = new Unicorn()
unicorn.init()
const pointLight = new THREE.PointLight(0xff0000, 1, 100)
const pointLight2 = new THREE.PointLight(0xff00ff, 1, 100)
unicorn.group.add(pointLight)
unicorn.group.add(pointLight2)
pointLight.position.set(-10, 10, 10)
pointLight2.position.set(20, 0, -10)
// Draw
const draw = () => {
// unicorn.group.rotation.x = params.rx
unicorn.group.rotation.y = params.ry
unicorn.headGroup.rotation.z = params.headRotation
unicorn.legs.forEach((leg, index) => {
leg.rotation.z = legRotations[index].r
})
unicorn.tailGroup.rotation.x = params.tailRotation
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
// Update camera
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()
// Update renderer
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
render(renderer)
}
gsap.ticker.add(draw)
/* Pause / play animations */
let playing = true
button.addEventListener('click', () => {
if (playing) {
mainAnimation.pause()
// headAnimation.pause()
// legAnimations.forEach((animation) => animation.pause())
} else {
mainAnimation.play()
// headAnimation.play()
// legAnimations.forEach((animation) => animation.play())
}
playing = !playing
})