Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Non-ascii text-animation-component and reusing fonts of text-geometry #340

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions components/text-animation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# text-animation

this component is used to animate *text-geometry* text,such as Chinese characters.

## Usage

```html
<body>
<a-scene stats inspector="url: http://localhost:5500/components/aframe-inspector.min.js" background="color: #135">
<a-assets>
<a-asset-item id="myFont" src="./fonts/LXGW_WenKai_Lite_Regular.json"></a-asset-item>
</a-assets>
<a-camera>
<!-- <a-entity cursor></a-entity> -->
<a-cursor></a-cursor>
</a-camera>

<a-entity text-animation="text: MSDF是计算机图形学中用于字体渲染和其他图形渲染的技术,特别是在需要高效率和可扩展性时。位图字体在放大时变模糊的问题在实现细节和渲染效果上有所不同。在AFrame中中文的渲染是意见困难的事情; font: #myFont; fontSize: 0.5 ; color: white; charsPerLine: 10; indent: 2; _function: textAnimation; position: -3 2 -7; letterSpacing: 0.2; lineHeight: 1.5;"></a-entity>
<a-entity position="0 1.8 2">
<a-entity camera look-controls wasd-controls></a-entity>
</a-entity>
<a-entity pico-controls="hand: left" id="primaryHand"></a-entity>
<a-entity pico-controls="hand: right" id="secondaryHand"></a-entity>
</a-scene>
</body>
```
and
```js
function textAnimation(textEl, index, position, data) {
const baseDelay = 500; // 基础延迟
setTimeout(() => {
textEl.setAttribute('animation', {
property: 'material.opacity',
to: 1,
dur: 500
});
}, index * 200 + baseDelay); // 延迟确保逐字显示的效果
}
```
we use the `textAnimation` function to animate the text, in this situation, we set the `property` to `material.opacity` and `to` to 1, and set the `dur` to 500, which means the opacity will change from 0 to 1 in 500ms.Chinese characters will be displayed one by one with a delay of 200ms.
you can customize the `textAnimation` function according to your needs.

## Tips

### font
you can use facetype.js to convert ttf or otf to json format, and then use the json file as the `font` attribute of the `text-geometry` component.
### text-geometry
text-geometry is a good choice for rendering Chinese characters, but it has some limitations, such as:

- It cannot reuse the same texture for different characters, which means the texture will be loaded multiple times.

so I edited the `text-geometry` component to support the reuse of the same texture for different characters:

```js
/**
* TextGeometry component for A-Frame.
*/
require('./lib/FontLoader')
require('./lib/TextGeometry')
var debug = AFRAME.utils.debug;
var error = debug('aframe-text-component:error');
var fontLoader = new THREE.FontLoader();
var fontCache = {}; // cache for loaded fonts
AFRAME.registerComponent('text-geometry', {
schema: {
bevelEnabled: { default: false },
bevelSize: { default: 8, min: 0 },
bevelThickness: { default: 12, min: 0 },
curveSegments: { default: 12, min: 0 },
font: { type: 'asset', default: 'https://rawgit.com/ngokevin/kframe/master/components/text-geometry/lib/helvetiker_regular.typeface.json' },
height: { default: 0.05, min: 0 },
size: { default: 0.5, min: 0 },
style: { default: 'normal', oneOf: ['normal', 'italics'] },
weight: { default: 'normal', oneOf: ['normal', 'bold'] },
value: { default: '' }
},

/**
* Called when component is attached and when component data changes.
* Generally modifies the entity based on the data.
*/
update: function (oldData) {
var data = this.data;
var el = this.el;

// check if font is already cached
if (fontCache[data.font]) {
this.createTextGeometry(fontCache[data.font]);
} else {
if (data.font.constructor === String) {
fontLoader.load(data.font, (response) => {
fontCache[data.font] = response; // cache font
this.createTextGeometry(response);
});
} else if (data.font.constructor === Object) {
this.createTextGeometry(data.font);
} else {
error('Must provide `font` (typeface.json) or `fontPath` (string) to text component.');
}
}
},

createTextGeometry: function (font) {
var data = this.data;
var el = this.el;

var textData = AFRAME.utils.clone(data);
textData.font = font;
el.getOrCreateObject3D('mesh', THREE.Mesh).geometry = new THREE.TextGeometry(data.value, textData);
}
});
```
64 changes: 64 additions & 0 deletions components/text-animation/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
AFRAME.registerComponent('text-animation', {
schema: {
text: { type: 'string' },
font: { type: 'selector' },
fontSize: { type: 'number', default: 0.5 },
color: { type: 'string', default: '#FFF' },
charsPerLine: { type: 'number', default: 10 },
indent: { type: 'number', default: 2 },
position: { type: 'vec3', default: { x: 0, y: 2, z: -5 } },
letterSpacing: { type: 'number', default: 0 },
lineHeight: { type: 'number', default: 1.5 },
curveSegments: { type: 'number', default: 4 },
height: { type: 'number', default: 0.05 },
},
init: function () {
const data = this.data;
const el = this.el;
const functionName = data._function;
delete this._function;
const characters = data.text.split('');
let currentLine = 0;
let charIndex = 0;
let index = 0;

const animateText = () => {
if (index >= characters.length) return; // if all characters have been animated, exit function
const char = characters[index];
if (charIndex >= data.charsPerLine || (currentLine === 0 && charIndex >= data.charsPerLine - data.indent)) {
currentLine++;
charIndex = 0;
}
const deltaPosition = {
x: (charIndex + (currentLine === 0 ? data.indent : 0)) * (data.fontSize + data.letterSpacing),
y: -currentLine * (data.fontSize * data.lineHeight),
z: 0
};
const curPosition = {
x: data.position.x + deltaPosition.x,
y: data.position.y + deltaPosition.y,
z: data.position.z + deltaPosition.z
};
const textEl = document.createElement('a-entity');
textEl.setAttribute('text-geometry', {
value: char,
font: data.font.getAttribute('src'),
size: data.fontSize,
bevelEnabled: false,
curveSegments: data.curveSegments,
height:data.height
});
textEl.setAttribute('material', { color: data.color, transparent: true, opacity: 0 });
textEl.setAttribute('position', curPosition);
if (functionName && typeof window[functionName] === 'function') {
window[functionName](textEl, index, curPosition, data);
}
else { console.log('no function provided'); }
el.appendChild(textEl);
charIndex++;
index++;
requestAnimationFrame(animateText);
};
animateText();
}
});
59 changes: 33 additions & 26 deletions components/text-geometry/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,22 @@
*/
require('./lib/FontLoader')
require('./lib/TextGeometry')

var debug = AFRAME.utils.debug;

var error = debug('aframe-text-component:error');

var fontLoader = new THREE.FontLoader();

var fontCache = {}; // cache for loaded fonts
AFRAME.registerComponent('text-geometry', {
schema: {
bevelEnabled: {default: false},
bevelSize: {default: 8, min: 0},
bevelThickness: {default: 12, min: 0},
curveSegments: {default: 12, min: 0},
font: {type: 'asset', default: 'https://rawgit.com/ngokevin/kframe/master/components/text-geometry/lib/helvetiker_regular.typeface.json'},
height: {default: 0.05, min: 0},
size: {default: 0.5, min: 0},
style: {default: 'normal', oneOf: ['normal', 'italics']},
weight: {default: 'normal', oneOf: ['normal', 'bold']},
value: {default: ''}
bevelEnabled: { default: false },
bevelSize: { default: 8, min: 0 },
bevelThickness: { default: 12, min: 0 },
curveSegments: { default: 12, min: 0 },
font: { type: 'asset', default: 'https://rawgit.com/ngokevin/kframe/master/components/text-geometry/lib/helvetiker_regular.typeface.json' },
height: { default: 0.05, min: 0 },
size: { default: 0.5, min: 0 },
style: { default: 'normal', oneOf: ['normal', 'italics'] },
weight: { default: 'normal', oneOf: ['normal', 'bold'] },
value: { default: '' }
},

/**
Expand All @@ -32,19 +29,29 @@ AFRAME.registerComponent('text-geometry', {
var data = this.data;
var el = this.el;

var mesh = el.getOrCreateObject3D('mesh', THREE.Mesh);
if (data.font.constructor === String) {
// Load typeface.json font.
fontLoader.load(data.font, function (response) {
var textData = AFRAME.utils.clone(data);
textData.font = response;
mesh.geometry = new THREE.TextGeometry(data.value, textData);
});
} else if (data.font.constructor === Object) {
// Set font if already have a typeface.json through setAttribute.
mesh.geometry = new THREE.TextGeometry(data.value, data);
// check if font is already cached
if (fontCache[data.font]) {
this.createTextGeometry(fontCache[data.font]);
} else {
error('Must provide `font` (typeface.json) or `fontPath` (string) to text component.');
if (data.font.constructor === String) {
fontLoader.load(data.font, (response) => {
fontCache[data.font] = response; // cache font
this.createTextGeometry(response);
});
} else if (data.font.constructor === Object) {
this.createTextGeometry(data.font);
} else {
error('Must provide `font` (typeface.json) or `fontPath` (string) to text component.');
}
}
},

createTextGeometry: function (font) {
var data = this.data;
var el = this.el;

var textData = AFRAME.utils.clone(data);
textData.font = font;
el.getOrCreateObject3D('mesh', THREE.Mesh).geometry = new THREE.TextGeometry(data.value, textData);
}
});