|
@@ -0,0 +1,404 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="en">
|
|
|
+ <head>
|
|
|
+ <title>three.js webgl - vector - text</title>
|
|
|
+ <meta charset="utf-8">
|
|
|
+ <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
|
|
+ <style>
|
|
|
+ body {
|
|
|
+ font-family: Monospace;
|
|
|
+ background-color: #f0f0f0;
|
|
|
+ margin: 0px;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+
|
|
|
+ <script src="../build/three.min.js"></script>
|
|
|
+ <script src="./js/controls/OrbitControls.js"></script>
|
|
|
+
|
|
|
+ <script src="js/libs/stats.min.js"></script>
|
|
|
+
|
|
|
+ <!-- load the font file from canvas-text -->
|
|
|
+
|
|
|
+ <script src="fonts/helvetiker_regular.typeface.js"></script>
|
|
|
+
|
|
|
+ <script type="x-shader/x-fragment" id="fs">
|
|
|
+
|
|
|
+ varying vec2 vUv;
|
|
|
+ varying float flip;
|
|
|
+ uniform vec3 color;
|
|
|
+
|
|
|
+ float inCurve(vec2 uv) {
|
|
|
+ return uv.x * uv.x - uv.y;
|
|
|
+ }
|
|
|
+
|
|
|
+ float delta = 0.1;
|
|
|
+
|
|
|
+ void main() {
|
|
|
+ float x = inCurve(vUv);
|
|
|
+
|
|
|
+ if (x * flip > 0.) discard;
|
|
|
+ gl_FragColor = vec4(color, 1.);
|
|
|
+ }
|
|
|
+
|
|
|
+ </script>
|
|
|
+
|
|
|
+ <script type="x-shader/x-vertex" id="vs">
|
|
|
+
|
|
|
+ varying vec2 vUv;
|
|
|
+ attribute float invert;
|
|
|
+ varying float flip;
|
|
|
+
|
|
|
+ void main() {
|
|
|
+
|
|
|
+ vUv = uv;
|
|
|
+ flip = invert;
|
|
|
+ vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
|
|
+ gl_Position = projectionMatrix * mvPosition;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ </script>
|
|
|
+
|
|
|
+
|
|
|
+ <script>
|
|
|
+
|
|
|
+ var container, stats;
|
|
|
+
|
|
|
+ var camera, scene, renderer, controls;
|
|
|
+
|
|
|
+ var group, text;
|
|
|
+
|
|
|
+ var targetRotation = 0;
|
|
|
+ var targetRotationOnMouseDown = 0;
|
|
|
+
|
|
|
+ var mouseX = 0;
|
|
|
+ var mouseXOnMouseDown = 0;
|
|
|
+
|
|
|
+ var windowHalfX = window.innerWidth / 2;
|
|
|
+ var windowHalfY = window.innerHeight / 2;
|
|
|
+
|
|
|
+ var t = false;
|
|
|
+
|
|
|
+ function toggle() {
|
|
|
+
|
|
|
+ if ( t ) {
|
|
|
+
|
|
|
+ text2.visible = 0;
|
|
|
+ text1.visible = 1;
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ text2.visible = 1;
|
|
|
+ text1.visible = 0;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ t = !t;
|
|
|
+ }
|
|
|
+
|
|
|
+ init();
|
|
|
+ animate();
|
|
|
+
|
|
|
+ function init() {
|
|
|
+
|
|
|
+ container = document.createElement( 'div' );
|
|
|
+ document.body.appendChild( container );
|
|
|
+
|
|
|
+ var info = document.createElement( 'div' );
|
|
|
+ info.style.position = 'absolute';
|
|
|
+ info.style.top = '10px';
|
|
|
+ info.style.width = '100%';
|
|
|
+ info.style.textAlign = 'center';
|
|
|
+ info.innerHTML = 'Resolution-Independent Vector Fonts<br/><a href="https://github.com/mrdoob/three.js/issues/4746">rendered with three.js and WebGL </a>';
|
|
|
+ container.appendChild( info );
|
|
|
+
|
|
|
+ camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
|
|
|
+ camera.position.set( 0, 150, 500 );
|
|
|
+
|
|
|
+
|
|
|
+ controls = new THREE.OrbitControls( camera );
|
|
|
+
|
|
|
+ scene = new THREE.Scene();
|
|
|
+
|
|
|
+
|
|
|
+ var theText = "&"; // i % & j b 8
|
|
|
+
|
|
|
+ var options = {
|
|
|
+ size: 180,
|
|
|
+ height: 20,
|
|
|
+ curveSegments: 2,
|
|
|
+ font: "helvetiker",
|
|
|
+ bevelEnabled: false
|
|
|
+ };
|
|
|
+
|
|
|
+ group = new THREE.Object3D();
|
|
|
+ scene.add( group );
|
|
|
+
|
|
|
+
|
|
|
+ var textMaterial = new THREE.MeshBasicMaterial( { color: new THREE.Color(0, 0, 1 ), overdraw: 0.5, wireframe: true, side: THREE.DoubleSide } );
|
|
|
+
|
|
|
+ textShapes = THREE.FontUtils.generateShapes( theText, options );
|
|
|
+
|
|
|
+ text3d = new THREE.ShapeGeometry( textShapes );
|
|
|
+
|
|
|
+ text3d.computeBoundingBox();
|
|
|
+ var centerOffset = -0.5 * ( text3d.boundingBox.max.x - text3d.boundingBox.min.x );
|
|
|
+ text = new THREE.Mesh( text3d, textMaterial );
|
|
|
+
|
|
|
+ text.position.x = centerOffset - 150;
|
|
|
+ group.add( text );
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ vA = new THREE.Vector2();
|
|
|
+ vB = new THREE.Vector2();
|
|
|
+ vDot = new THREE.Vector2();
|
|
|
+
|
|
|
+ function processShape(path, reverse) {
|
|
|
+
|
|
|
+ var pts = []; // bigger area (convex hull)
|
|
|
+ var pts2 = []; // smaller area (full solid shapes)
|
|
|
+ var beziers = []; // quad bezier points
|
|
|
+ var invert = [];
|
|
|
+ var z;
|
|
|
+
|
|
|
+ var wind;
|
|
|
+
|
|
|
+ pts.push( path[0].getPoint(0) );
|
|
|
+ pts2.push( path[0].getPoint(0) );
|
|
|
+
|
|
|
+ for (var i=0; i < path.length; i++) {
|
|
|
+ curve = path[i];
|
|
|
+ if (curve instanceof THREE.LineCurve) {
|
|
|
+ pts.push( curve.v2 );
|
|
|
+ pts2.push( curve.v2 );
|
|
|
+ } else if (curve instanceof THREE.QuadraticBezierCurve) {
|
|
|
+ vA = vA.subVectors( curve.v1, curve.v0 ); // .normalize()
|
|
|
+ vB = vB.subVectors( curve.v2, curve.v1 );
|
|
|
+ z = vA.x * vB.y - vA.y * vB.x; // z component of cross Production
|
|
|
+ wind = z < 0; // clockwise/anticlock wind
|
|
|
+ // if (reverse) wind = !wind;
|
|
|
+
|
|
|
+ // console.log(z, wind , wind ? 'clockwise' : 'anti');
|
|
|
+
|
|
|
+ if (wind) {
|
|
|
+ pts.push( curve.v1 );
|
|
|
+ pts.push( curve.v2 );
|
|
|
+ pts2.push( curve.v2 );
|
|
|
+ } else {
|
|
|
+ pts.push( curve.v2 );
|
|
|
+ pts2.push( curve.v1 );
|
|
|
+ pts2.push( curve.v2 );
|
|
|
+ }
|
|
|
+
|
|
|
+ var flip = wind ? 1 : -1;
|
|
|
+ // if (reverse) flip *= -1;
|
|
|
+
|
|
|
+ invert.push(flip, flip, flip);
|
|
|
+ beziers.push( curve.v0, curve.v1, curve.v2);
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ pts: pts,
|
|
|
+ pts2: pts2,
|
|
|
+ beziers: beziers,
|
|
|
+ invert: invert
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ var subshape;
|
|
|
+ var convexhullShapeGroup = [];
|
|
|
+ var solidShapeGroup = [];
|
|
|
+
|
|
|
+ var beziers = [], invert = [];
|
|
|
+
|
|
|
+ for (var s=0;s<textShapes.length;s++) {
|
|
|
+
|
|
|
+ subshape = textShapes[s];
|
|
|
+ var process = processShape(subshape.curves);
|
|
|
+
|
|
|
+ pts = process.pts;
|
|
|
+ pts2 = process.pts2;
|
|
|
+ beziers = beziers.concat(process.beziers);
|
|
|
+ invert = invert.concat(process.invert);
|
|
|
+
|
|
|
+ convexhullShape = new THREE.Shape( pts );
|
|
|
+ solidShape = new THREE.Shape( pts2 );
|
|
|
+
|
|
|
+ convexhullShapeGroup.push( convexhullShape );
|
|
|
+ solidShapeGroup.push( solidShape );
|
|
|
+
|
|
|
+ for (var i=0; i<subshape.holes.length;i++) {
|
|
|
+ hole = subshape.holes[i];
|
|
|
+ console.log('hole', hole);
|
|
|
+
|
|
|
+ process = processShape(hole.curves, true);
|
|
|
+
|
|
|
+ pts = process.pts;
|
|
|
+ pts2 = process.pts2;
|
|
|
+ beziers = beziers.concat(process.beziers);
|
|
|
+ invert = invert.concat(process.invert);
|
|
|
+
|
|
|
+ convexhullShape.holes.push(new THREE.Shape(pts));
|
|
|
+ solidShape.holes.push(new THREE.Shape(pts2));
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } // end of subshape
|
|
|
+
|
|
|
+ bezierGeometry = new THREE.Geometry();
|
|
|
+
|
|
|
+ for (var i=0;i<beziers.length;i++) {
|
|
|
+ p = beziers[i];
|
|
|
+ bezierGeometry.vertices.push( new THREE.Vector3(p.x, p.y, 0) );
|
|
|
+ }
|
|
|
+
|
|
|
+ for (i=0;i<beziers.length;i+=3) {
|
|
|
+ bezierGeometry.faces.push( new THREE.Face3(i, i+1, i+2) );
|
|
|
+ bezierGeometry.faceVertexUvs[0].push( [
|
|
|
+ new THREE.Vector2(0, 0),
|
|
|
+ new THREE.Vector2(0.5, 0),
|
|
|
+ new THREE.Vector2(1, 1)
|
|
|
+ ] );
|
|
|
+ }
|
|
|
+
|
|
|
+ text3d = new THREE.ShapeGeometry( convexhullShapeGroup );
|
|
|
+ text3d.computeBoundingBox();
|
|
|
+ var centerOffset = -0.5 * ( text3d.boundingBox.max.x - text3d.boundingBox.min.x );
|
|
|
+
|
|
|
+ text1 = new THREE.Mesh( text3d, textMaterial );
|
|
|
+
|
|
|
+ text1.position.x = centerOffset + 150;
|
|
|
+
|
|
|
+ group.add( text1 );
|
|
|
+
|
|
|
+ text3d = new THREE.ShapeGeometry( solidShapeGroup );
|
|
|
+ text3d.computeBoundingBox();
|
|
|
+ var centerOffset = -0.5 * ( text3d.boundingBox.max.x - text3d.boundingBox.min.x );
|
|
|
+
|
|
|
+ text2 = new THREE.Mesh( text3d, new THREE.MeshBasicMaterial( { color: new THREE.Color(1, 0, 0 ), side: THREE.DoubleSide, wireframe: true } ) );
|
|
|
+
|
|
|
+ text2.position.x = centerOffset + 150;
|
|
|
+
|
|
|
+ group.add( text2 );
|
|
|
+
|
|
|
+ //
|
|
|
+ bezierGeometry.computeBoundingBox();
|
|
|
+ bezierGeometry.computeFaceNormals();
|
|
|
+ bezierGeometry.computeVertexNormals();
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ var uniforms = {
|
|
|
+ color: { type: 'c', value: new THREE.Color(0.45 * 0xffffff) }
|
|
|
+ };
|
|
|
+ var vertexShader = document.getElementById( 'vs' ).textContent;
|
|
|
+ var fragmentShader = document.getElementById( 'fs' ).textContent;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ newMaterial = new THREE.ShaderMaterial({
|
|
|
+ attributes: { invert: { type: 'f', value: invert } },
|
|
|
+ uniforms: uniforms,
|
|
|
+ vertexShader: vertexShader,
|
|
|
+ fragmentShader: fragmentShader,
|
|
|
+ side: THREE.DoubleSide
|
|
|
+ });
|
|
|
+
|
|
|
+
|
|
|
+ text = new THREE.Mesh( bezierGeometry, newMaterial );
|
|
|
+
|
|
|
+ text.position.x = centerOffset;
|
|
|
+ text.position.y = 0;
|
|
|
+ text.position.z = 0;
|
|
|
+
|
|
|
+ text.rotation.x = 0;
|
|
|
+ text.rotation.y = Math.PI * 2;
|
|
|
+
|
|
|
+ group.add( text );
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+
|
|
|
+ text3d = new THREE.ShapeGeometry( solidShapeGroup );
|
|
|
+ text3d.computeBoundingBox();
|
|
|
+
|
|
|
+ text = new THREE.Mesh( text3d, new THREE.MeshBasicMaterial( { color: 0.45 * 0xffffff, side: THREE.DoubleSide } ) );
|
|
|
+ text.position.x = centerOffset;
|
|
|
+ text.position.y = 0;
|
|
|
+ text.position.z = 0;
|
|
|
+
|
|
|
+ text.rotation.x = 0;
|
|
|
+ text.rotation.y = Math.PI * 2;
|
|
|
+
|
|
|
+ group.add( text );
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ renderer = new THREE.WebGLRenderer();
|
|
|
+ renderer.setClearColor( 0xf0f0f0 );
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
+
|
|
|
+ container.appendChild( renderer.domElement );
|
|
|
+
|
|
|
+ stats = new Stats();
|
|
|
+ stats.domElement.style.position = 'absolute';
|
|
|
+ stats.domElement.style.top = '0px';
|
|
|
+ container.appendChild( stats.domElement );
|
|
|
+
|
|
|
+ document.addEventListener( 'mousedown', onDocumentMouseDown, false );
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ window.addEventListener( 'resize', onWindowResize, false );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function onWindowResize() {
|
|
|
+
|
|
|
+ windowHalfX = window.innerWidth / 2;
|
|
|
+ windowHalfY = window.innerHeight / 2;
|
|
|
+
|
|
|
+ camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
+
|
|
|
+ renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ function onDocumentMouseDown( event ) {
|
|
|
+
|
|
|
+ toggle();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ //
|
|
|
+
|
|
|
+ function animate() {
|
|
|
+
|
|
|
+ requestAnimationFrame( animate );
|
|
|
+
|
|
|
+ render();
|
|
|
+ stats.update();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function render() {
|
|
|
+
|
|
|
+ controls.update();
|
|
|
+ renderer.render( scene, camera );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ </script>
|
|
|
+
|
|
|
+ </body>
|
|
|
+</html>
|