UNPKG

three-glyph

Version:

Provide geometry and material in THREE.js for MSDF (multi-channel signed distance fields) glyph and more

292 lines (260 loc) 7.03 kB
import * as THREE from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js'; import { Glyph, GlyphGeometry, GlyphMaterial } from '../../src/index.js' import { Pane } from 'tweakpane'; import * as TextareaPlugin from "@pangenerator/tweakpane-textarea-plugin"; import { VertexNormalsHelper } from 'three/addons/helpers/VertexNormalsHelper.js'; class App { glyph = null; PARAMS = { // text: 'LOVERS', text: 'LOVERS STORY. -LOVERS STORY. -LOVERS STORY.', color: 0xece9e3, anchor: { x: 0.5, y: 0.5 }, align: 'left', // width: null, letterSpacing: 0, opacity: 1, lineHeight: 1, progress: 1, duration: 1, stagger: 0.1, }; scene = new THREE.Scene(); renderer = new THREE.WebGLRenderer({ antialias: false, depth: false }); camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 3000); controls = new OrbitControls(this.camera, this.renderer.domElement); pane = new Pane(); manager = new THREE.LoadingManager(); fontLoader = new FontLoader(this.manager); textureLoader = new THREE.TextureLoader(this.manager); constructor() { this.pane.registerPlugin(TextareaPlugin); this.fontLoader.load( 'https://raw.githubusercontent.com/trinketmage/three-glyph/main/examples/simple/Love.json', ( raw ) => { this.font = raw.data; }, ); this.texture = this.textureLoader.load( "https://raw.githubusercontent.com/trinketmage/three-glyph/main/examples/simple/public/Love.png"); this.manager.onLoad = () => { this.init(); this.setComponents(); }; } init() { this.camera.position.z = 500; this.renderer.setAnimationLoop(this.animate.bind(this)); this.handleResize() window.addEventListener('resize', this.handleResize.bind(this), false); document.body.appendChild(this.renderer.domElement); } setComponents() { const { PARAMS, texture, font, scene } = this; const glyph = new Glyph({ text: PARAMS.text, font, map: texture, color: new THREE.Color(PARAMS.color), addons: { progress: true, shaderChunks: { 'position_pars_vertex': ` // https://github.com/glslify/glsl-easings/ float quadraticOut(float t) { return -t * (t - 2.0); } float cubicOut(float t) { float f = t - 1.0; return f * f * f + 1.0; } `, 'transformed_vertex': ` transformed.x += 260.0 * 0.5 * (1.0 - quadraticOut(vProgress)); transformed.z -= 260.0 * 0.5 * (1.0 - cubicOut(vProgress)); `, 'color_fragment': ` diffuseColor = vec3( color.x, color.y * vProgress, color.z * vProgress ); `, 'alpha_fragment': `alpha *= vProgress * opacity;`, } } }); glyph.mesh.frustumCulled = false; glyph.center(); scene.add(glyph); glyph.children[0].geometry.computeVertexNormals(); const helper = new VertexNormalsHelper( glyph.children[0], 1, 0xff0000 ); scene.add(helper); this.glyph = glyph; this.setDebug() } handleResize() { const { camera, renderer } = this; camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } animate() { this.controls.update(); this.renderer.render(this.scene, this.camera); } setDebug() { const { pane, PARAMS, glyph } = this; pane .addBinding(PARAMS, 'text', { view: 'textarea' }) .on('change', () => { glyph.update({ text: PARAMS.text }) }); const updateGlyph = () => { glyph.update({ letterSpacing: PARAMS.letterSpacing, lineHeight: PARAMS.lineHeight, align: PARAMS.align, }) } const basic = pane.addFolder({ title: 'Basic', expanded: false }); basic .addBinding( PARAMS, 'color', { label: 'color', view: 'color' } ) .on('change', () => { glyph.material.uniforms.color.value = new THREE.Color(PARAMS.color) }); basic .addBinding( PARAMS, 'opacity', { step: 0.01, min: 0, max: 1, } ) .on('change', () => { glyph.material.uniforms.opacity.value = PARAMS.opacity }); basic .addBinding( PARAMS, 'letterSpacing', { step: 1 } ) .on('change', () => { updateGlyph(); }); if (PARAMS.lineHeight) { PARAMS.lineHeight = this.font.common.lineHeight basic .addBinding( PARAMS, 'lineHeight', { step: 1 } ) .on('change', () => { updateGlyph(); }); } basic .addBinding(PARAMS, 'align', { label: 'textAlign', options: { left: 'left', center: 'center', right: 'right' }, }) .on('change', () => { updateGlyph(); }); basic .addBinding(PARAMS, 'anchor', { x: { step: 0.01, min: 0, max: 1 }, y: { step: 0.01, min: 0, max: 1, inverted: true }, }) .on('change', () => { const { x, y } = PARAMS.anchor glyph.anchor.set(x, y) updateGlyph(); }); const folder = basic.addFolder({ title: "Glyph presets", expanded: false }); ['center', 'alignTop', 'alignRight', 'alignBottom', 'alignLeft'].forEach((property) => { folder .addButton({ title: property }) .on('click', () => { glyph[property]() PARAMS.anchor.x = glyph.anchor.x; PARAMS.anchor.y = glyph.anchor.y; pane.refresh(); }); }); const progress = pane.addFolder({ title: 'Progress', expanded: false }); progress .addBinding( PARAMS, 'duration', { step: 0.01, min: 0.1, } ) .on('change', () => { glyph.material.uniforms.duration.value = PARAMS.duration }); progress .addBinding( PARAMS, 'stagger', { step: 0.01, min: 0, } ) .on('change', () => { glyph.material.uniforms.stagger.value = PARAMS.stagger }); glyph.material.uniforms.progress.value = PARAMS.progress; progress .addBinding( PARAMS, 'progress', { step: 0.01, min: 0, max: 1, } ) .on('change', () => { glyph.material.uniforms.progress.value = PARAMS.progress }); } } new App();