Просмотр исходного кода

FontLoader: Add text direction. (#31683)

* Rebase from latest dev branch

* Change random color to fix

* Revert webgl_loader_ttf example

* Move location of MPLUS font and rename

* Fix font path

* zip MPLUSRounded1c to reduce size

* Fix un-used class

* Update TextGeometry.js

* Update FontLoader.js

---------

Co-authored-by: Michael Herzog <michael.herzog@human-interactive.org>
Aless Li 4 месяцев назад
Родитель
Сommit
1225f52302

BIN
examples/fonts/MPLUSRounded1c/MPLUSRounded1c-Regular.typeface.json.zip


+ 91 - 0
examples/fonts/MPLUSRounded1c/OFL.txt

@@ -0,0 +1,91 @@
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+https://openfontlicense.org
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded, 
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.

+ 2 - 1
examples/jsm/geometries/TextGeometry.js

@@ -43,7 +43,7 @@ class TextGeometry extends ExtrudeGeometry {
 
 		} else {
 
-			const shapes = font.generateShapes( text, parameters.size );
+			const shapes = font.generateShapes( text, parameters.size, parameters.direction );
 
 			// defaults
 
@@ -76,6 +76,7 @@ class TextGeometry extends ExtrudeGeometry {
  * @property {number} [bevelSize=8] - Distance from the shape outline that the bevel extends.
  * @property {number} [bevelOffset=0] - Distance from the shape outline that the bevel starts.
  * @property {number} [bevelSegments=3] - Number of bevel layers.
+ * @property {string} [direction='ltr'] - Char direction: ltr(left to right), rtl(right to left) & tb(top bottom).
  * @property {?Curve} [extrudePath=null] - A 3D spline path along which the shape should be extruded. Bevels not supported for path extrusion.
  * @property {Object} [UVGenerator] - An object that provides UV generator functions for custom UV generation.
  **/

+ 22 - 4
examples/jsm/loaders/FontLoader.js

@@ -109,12 +109,13 @@ class Font {
 	 *
 	 * @param {string} text - The text.
 	 * @param {number} [size=100] - The text size.
+	 * @param {string} [direction='ltr'] - Char direction: ltr(left to right), rtl(right to left) & tb(top bottom).
 	 * @return {Array<Shape>} An array of shapes representing the text.
 	 */
-	generateShapes( text, size = 100 ) {
+	generateShapes( text, size = 100, direction = 'ltr' ) {
 
 		const shapes = [];
-		const paths = createPaths( text, size, this.data );
+		const paths = createPaths( text, size, this.data, direction );
 
 		for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
 
@@ -128,7 +129,7 @@ class Font {
 
 }
 
-function createPaths( text, size, data ) {
+function createPaths( text, size, data, direction ) {
 
 	const chars = Array.from( text );
 	const scale = size / data.resolution;
@@ -138,6 +139,12 @@ function createPaths( text, size, data ) {
 
 	let offsetX = 0, offsetY = 0;
 
+	if ( direction == 'rtl' || direction == 'tb' ) {
+
+		chars.reverse();
+
+	}
+
 	for ( let i = 0; i < chars.length; i ++ ) {
 
 		const char = chars[ i ];
@@ -150,7 +157,18 @@ function createPaths( text, size, data ) {
 		} else {
 
 			const ret = createPath( char, scale, offsetX, offsetY, data );
-			offsetX += ret.offsetX;
+
+			if ( direction == 'tb' ) {
+
+				offsetX = 0;
+				offsetY += data.ascender * scale;
+
+			} else {
+
+				offsetX += ret.offsetX;
+
+			}
+
 			paths.push( ret.path );
 
 		}

BIN
examples/screenshots/webgl_geometry_text_stroke.jpg


+ 96 - 59
examples/webgl_geometry_text_stroke.html

@@ -35,7 +35,8 @@
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { SVGLoader } from 'three/addons/loaders/SVGLoader.js';
-			import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+			import { Font } from 'three/addons/loaders/FontLoader.js';
+			import { unzipSync, strFromU8 } from 'three/addons/libs/fflate.module.js';
 
 			let camera, scene, renderer;
 
@@ -44,108 +45,144 @@
 			function init( ) {
 
 				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
-				camera.position.set( 0, - 400, 600 );
+				camera.position.set( 0, - 400, 1000 );
 
 				scene = new THREE.Scene();
 				scene.background = new THREE.Color( 0xf0f0f0 );
 
-				const loader = new FontLoader();
-				loader.load( 'fonts/helvetiker_regular.typeface.json', function ( font ) {
+				new THREE.FileLoader()
+					.setResponseType( 'arraybuffer' )
+					.load( 'fonts/MPLUSRounded1c/MPLUSRounded1c-Regular.typeface.json.zip', function ( data ) {
+						const zip = unzipSync( new Uint8Array( data ) );
+						const strArray = strFromU8( new Uint8Array( zip[ 'MPLUSRounded1c-Regular.typeface.json' ].buffer ) );
 
-					const color = new THREE.Color( 0x006699 );
+						const font = new Font( JSON.parse( strArray ) );
+						const color = new THREE.Color( 0x006699 );
 
-					const matDark = new THREE.MeshBasicMaterial( {
-						color: color,
-						side: THREE.DoubleSide
-					} );
+						const matDark = new THREE.MeshBasicMaterial( {
+							color: color,
+							side: THREE.DoubleSide
+						} );
 
-					const matLite = new THREE.MeshBasicMaterial( {
-						color: color,
-						transparent: true,
-						opacity: 0.4,
-						side: THREE.DoubleSide
-					} );
+						const matLite = new THREE.MeshBasicMaterial( {
+							color: color,
+							transparent: true,
+							opacity: 0.4,
+							side: THREE.DoubleSide
+						} );
 
-					const message = '   Three.js\nStroke text.';
+						const material = {
+							dark: matDark,
+							lite: matLite,
+							color: color
+						};
 
-					const shapes = font.generateShapes( message, 100 );
+						const english = '   Three.js\nStroke text.'; // Left to right
 
-					const geometry = new THREE.ShapeGeometry( shapes );
+						const hebrew = 'טקסט קו'; // Right to left
 
-					geometry.computeBoundingBox();
+						const chinese = '文字描邊'; // Top to bottom
 
-					const xMid = - 0.5 * ( geometry.boundingBox.max.x - geometry.boundingBox.min.x );
+						const message1 = generateStrokeText( font, material, english, 80, 'ltr' );
 
-					geometry.translate( xMid, 0, 0 );
+						const message2 = generateStrokeText( font, material, hebrew, 80, 'rtl' );
 
-					// make shape ( N.B. edge view not visible )
+						const message3 = generateStrokeText( font, material, chinese, 80, 'tb' );
 
-					const text = new THREE.Mesh( geometry, matLite );
-					text.position.z = - 150;
-					scene.add( text );
+						message1.position.x = - 100;
 
-					// make line shape ( N.B. edge view remains visible )
+						message2.position.x = - 100;
+						message2.position.y = - 300;
 
-					const holeShapes = [];
+						message3.position.x = 300;
+						message3.position.y = - 300;
 
-					for ( let i = 0; i < shapes.length; i ++ ) {
+						scene.add( message1, message2, message3 );
 
-						const shape = shapes[ i ];
+						render();
 
-						if ( shape.holes && shape.holes.length > 0 ) {
+				} ); //end load function
 
-							for ( let j = 0; j < shape.holes.length; j ++ ) {
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
 
-								const hole = shape.holes[ j ];
-								holeShapes.push( hole );
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, 0, 0 );
+				controls.update();
 
-							}
+				controls.addEventListener( 'change', render );
 
-						}
+				window.addEventListener( 'resize', onWindowResize );
 
-					}
+			} // end init
+
+			function generateStrokeText(font, material, message, size, direction = 'ltr') {
+
+				const shapes = font.generateShapes( message, size, direction );
+
+				const geometry = new THREE.ShapeGeometry( shapes );
+
+				const strokeText = new THREE.Group();
+
+				geometry.computeBoundingBox();
+
+				const xMid = - 0.5 * ( geometry.boundingBox.max.x - geometry.boundingBox.min.x );
+
+				geometry.translate( xMid, 0, 0 );
+
+				// make shape ( N.B. edge view not visible )
 
-					shapes.push( ...holeShapes );
+				const text = new THREE.Mesh( geometry, material.lite );
 
-					const style = SVGLoader.getStrokeStyle( 5, color.getStyle() );
+				text.position.z = - 150;
 
-					const strokeText = new THREE.Group();
+				strokeText.add(text);
 
-					for ( let i = 0; i < shapes.length; i ++ ) {
+				// make line shape ( N.B. edge view remains visible )
 
-						const shape = shapes[ i ];
+				const holeShapes = [];
 
-						const points = shape.getPoints();
+				for ( let i = 0; i < shapes.length; i ++ ) {
 
-						const geometry = SVGLoader.pointsToStroke( points, style );
+					const shape = shapes[ i ];
 
-						geometry.translate( xMid, 0, 0 );
+					if ( shape.holes && shape.holes.length > 0 ) {
 
-						const strokeMesh = new THREE.Mesh( geometry, matDark );
-						strokeText.add( strokeMesh );
+						for ( let j = 0; j < shape.holes.length; j ++ ) {
+
+							const hole = shape.holes[ j ];
+							holeShapes.push( hole );
+
+						}
 
 					}
 
-					scene.add( strokeText );
+				}
 
-					render();
+				shapes.push( ...holeShapes );
 
-				} ); //end load function
+				const style = SVGLoader.getStrokeStyle( 5, material.color.getStyle() );
 
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				document.body.appendChild( renderer.domElement );
+				for ( let i = 0; i < shapes.length; i ++ ) {
 
-				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.target.set( 0, 0, 0 );
-				controls.update();
+					const shape = shapes[ i ];
 
-				controls.addEventListener( 'change', render );
+					const points = shape.getPoints();
 
-				window.addEventListener( 'resize', onWindowResize );
+					const geometry = SVGLoader.pointsToStroke( points, style );
 
-			} // end init
+					geometry.translate( xMid, 0, 0 );
+
+					const strokeMesh = new THREE.Mesh( geometry, material.dark );
+					strokeText.add( strokeMesh );
+
+				}
+
+				return strokeText;
+
+			}
 
 			function onWindowResize() {
 

粤ICP备19079148号