Skip to content

Examples : Bender

Description

Demonstrates Bending BufferGeometries in Threejs.

Bender GitHub Repository : https://github.com/Sean-Bradley/Bender

This examples demonstrates,

  • Bending a THREE.BoxGeometry
  • Bending a THREE.TextGeometry
  • Replacing a meshes geometry at runtime
  • Replacing the text of a THREE.TextGeometry at runtime
  • Using the THREE.FontLoader

Code

./src/client/bender.ts

//MIT License
//Copyright (c) 2020-2021 Sean Bradley
//https://github.com/Sean-Bradley/Bender/blob/main/LICENSE

export default class Bender {
    public bend(geometry: THREE.BufferGeometry, axis: string, angle: number) {
        let theta = 0
        if (angle !== 0) {
            const v = (geometry as THREE.BufferGeometry).attributes.position
                .array as number[]
            for (let i = 0; i < v.length; i += 3) {
                let x = v[i]
                let y = v[i + 1]
                let z = v[i + 2]

                switch (axis) {
                    case 'x':
                        theta = z * angle
                        break
                    case 'y':
                        theta = x * angle
                        break
                    default:
                        //z
                        theta = x * angle
                        break
                }

                let sinTheta = Math.sin(theta)
                let cosTheta = Math.cos(theta)

                switch (axis) {
                    case 'x':
                        v[i] = x
                        v[i + 1] = (y - 1.0 / angle) * cosTheta + 1.0 / angle
                        v[i + 2] = -(y - 1.0 / angle) * sinTheta
                        break
                    case 'y':
                        v[i] = -(z - 1.0 / angle) * sinTheta
                        v[i + 1] = y
                        v[i + 2] = (z - 1.0 / angle) * cosTheta + 1.0 / angle
                        break
                    default:
                        //z
                        v[i] = -(y - 1.0 / angle) * sinTheta
                        v[i + 1] = (y - 1.0 / angle) * cosTheta + 1.0 / angle
                        v[i + 2] = z
                        break
                }
            }
            geometry.attributes.position.needsUpdate = true
        }
    }
}

./src/client/client.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import Stats from 'three/examples/jsm/libs/stats.module'
import { GUI } from 'three/examples/jsm/libs/dat.gui.module'
import Bender from './bender'

const bender = new Bender()

const scene = new THREE.Scene()

const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
)
camera.position.set(4, 4, 6)

const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true

const modelOptions = ['Cube', 'Text', 'Plane']
const axisOptions = ['x', 'y', 'z']
const data = {
    model: modelOptions[0],
    axis: axisOptions[1],
    angle: Math.PI / 16,
    text: 'seanwasere Threejs'
}

const material: THREE.MeshBasicMaterial = new THREE.MeshBasicMaterial({
    color: 0x00ff00,
    wireframe: true
})
const mesh: THREE.Mesh = new THREE.Mesh(new THREE.BufferGeometry(), material)
scene.add(mesh)

let font: THREE.Font
const loader = new THREE.FontLoader()
loader.load('fonts/helvetiker_regular.typeface.json', function (f) {
    font = f
})

window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
    render()
}

const gui = new GUI()
gui.add(data, 'model', modelOptions).onChange(regenerateGeometry)
gui.add(data, 'text').onFinishChange(regenerateGeometry)
gui.add(data, 'axis', axisOptions).onChange(regenerateGeometry)
gui.add(data, 'angle', -Math.PI / 2, Math.PI / 2, 0.01).onChange(
    regenerateGeometry
)
gui.open()

function regenerateGeometry() {
    let newGeometry
    if (data.model === 'Cube') {
        newGeometry = new THREE.BoxGeometry(5, 5, 5, 10, 10, 10)
    } else if (data.model === 'Plane') {
        newGeometry = new THREE.PlaneGeometry(10, 5, 20, 10)
    } else {
        newGeometry = new THREE.TextGeometry(data.text, {
            font: font,
            size: 1,
            height: 0.2,
            curveSegments: 2
        })
    }

    newGeometry.center()
    bender.bend(newGeometry, data.axis, data.angle)
    mesh.geometry.dispose()
    mesh.geometry = newGeometry
}

const stats = Stats()
document.body.appendChild(stats.dom)

function animate() {
    requestAnimationFrame(animate)

    controls.update()

    render()

    stats.update()
}

function render() {
    renderer.render(scene, camera)
}

regenerateGeometry()
animate()