What’s on our mind?

Collection of articles, design, site, and resources made by designers and publisher @Menu View


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
})