@saboit-dev/thomas 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +138 -0
- package/index.cjs.js +1 -0
- package/index.d.ts +2 -0
- package/index.js +6 -0
- package/package.json +54 -0
- package/text_engine_2d/MSDFTextGeometry/TextLayout.cjs.js +1 -0
- package/text_engine_2d/MSDFTextGeometry/TextLayout.d.ts +48 -0
- package/text_engine_2d/MSDFTextGeometry/TextLayout.js +357 -0
- package/text_engine_2d/MSDFTextGeometry/index.cjs.js +1 -0
- package/text_engine_2d/MSDFTextGeometry/index.d.ts +51 -0
- package/text_engine_2d/MSDFTextGeometry/index.js +159 -0
- package/text_engine_2d/MSDFTextGeometry/utils.cjs.js +1 -0
- package/text_engine_2d/MSDFTextGeometry/utils.d.ts +3 -0
- package/text_engine_2d/MSDFTextGeometry/utils.js +46 -0
- package/text_engine_2d/MSDFTextGeometry/vertices.cjs.js +1 -0
- package/text_engine_2d/MSDFTextGeometry/vertices.d.ts +28 -0
- package/text_engine_2d/MSDFTextGeometry/vertices.js +84 -0
- package/text_engine_2d/MSDFTextMaterial/fragment.glsl.js +3 -0
- package/text_engine_2d/MSDFTextMaterial/index.cjs.js +1 -0
- package/text_engine_2d/MSDFTextMaterial/index.d.ts +4 -0
- package/text_engine_2d/MSDFTextMaterial/index.js +38 -0
- package/text_engine_2d/MSDFTextMaterial/vertex.glsl.js +3 -0
- package/text_engine_2d/index.cjs.js +1 -0
- package/text_engine_2d/index.d.ts +6 -0
- package/text_engine_2d/index.js +18 -0
- package/text_engine_2d/react/SDFText.cjs.js +1 -0
- package/text_engine_2d/react/SDFText.d.ts +9 -0
- package/text_engine_2d/react/SDFText.js +63 -0
- package/text_engine_2d/react/SDFTextProvider.cjs.js +1 -0
- package/text_engine_2d/react/SDFTextProvider.d.ts +6 -0
- package/text_engine_2d/react/SDFTextProvider.js +191 -0
- package/text_engine_2d/react/context.cjs.js +1 -0
- package/text_engine_2d/react/context.d.ts +3 -0
- package/text_engine_2d/react/context.js +5 -0
- package/text_engine_2d/react/types.cjs.js +1 -0
- package/text_engine_2d/react/types.d.ts +33 -0
- package/text_engine_2d/react/types.js +1 -0
- package/text_engine_2d/react/utils.cjs.js +1 -0
- package/text_engine_2d/react/utils.d.ts +2 -0
- package/text_engine_2d/react/utils.js +6 -0
- package/text_engine_2d/register.cjs.js +1 -0
- package/text_engine_2d/register.d.ts +12 -0
- package/text_engine_2d/register.js +12 -0
- package/text_engine_3d/InstancedText.cjs.js +1 -0
- package/text_engine_3d/InstancedText.d.ts +13 -0
- package/text_engine_3d/InstancedText.js +127 -0
- package/text_engine_3d/InstancedTextPlaceholder.cjs.js +1 -0
- package/text_engine_3d/InstancedTextPlaceholder.d.ts +23 -0
- package/text_engine_3d/InstancedTextPlaceholder.js +50 -0
- package/text_engine_3d/InstancedTextProvider.cjs.js +1 -0
- package/text_engine_3d/InstancedTextProvider.d.ts +15 -0
- package/text_engine_3d/InstancedTextProvider.js +124 -0
- package/text_engine_3d/index.cjs.js +1 -0
- package/text_engine_3d/index.d.ts +3 -0
- package/text_engine_3d/index.js +11 -0
- package/text_engine_3d/instancedTextBuffers.cjs.js +1 -0
- package/text_engine_3d/instancedTextBuffers.d.ts +28 -0
- package/text_engine_3d/instancedTextBuffers.js +105 -0
- package/text_engine_3d/types.cjs.js +1 -0
- package/text_engine_3d/types.d.ts +37 -0
- package/text_engine_3d/types.js +8 -0
- package/text_engine_3d/useInstancedTextMaterial.cjs.js +1 -0
- package/text_engine_3d/useInstancedTextMaterial.d.ts +4 -0
- package/text_engine_3d/useInstancedTextMaterial.js +73 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import _extends from '@babel/runtime/helpers/esm/extends';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { useRef, useLayoutEffect, useEffect, useMemo } from 'react';
|
|
4
|
+
import { Color, Box3, Sphere, Vector3 } from 'three';
|
|
5
|
+
import { useInstancedTextAPI } from './InstancedTextProvider.js';
|
|
6
|
+
import { TextAlignment } from './types.js';
|
|
7
|
+
|
|
8
|
+
const defaultColor = new Color('white');
|
|
9
|
+
function InstancedText({
|
|
10
|
+
text,
|
|
11
|
+
thickness,
|
|
12
|
+
fontSize,
|
|
13
|
+
textAlign,
|
|
14
|
+
scale,
|
|
15
|
+
color,
|
|
16
|
+
opacity,
|
|
17
|
+
font,
|
|
18
|
+
...rest
|
|
19
|
+
}) {
|
|
20
|
+
var _scale$x, _scale$y, _scale$z;
|
|
21
|
+
const unknownGlyphIndex = font.fontMetadata.charToIndex['?'];
|
|
22
|
+
const api = useInstancedTextAPI();
|
|
23
|
+
const groupRef = useRef(null);
|
|
24
|
+
const myPtrRef = useRef(null);
|
|
25
|
+
useLayoutEffect(() => {
|
|
26
|
+
const group = groupRef.current;
|
|
27
|
+
if (!group) return;
|
|
28
|
+
const fontScale = font.fontMetadata.fontScale;
|
|
29
|
+
const lineHeight = (font.fontMetadata.boundingBox.yMax - font.fontMetadata.boundingBox.yMin + font.fontMetadata.underlineThickness) * fontScale;
|
|
30
|
+
const scales = new Float32Array(text.length);
|
|
31
|
+
const shifts = new Float32Array(text.length * 2);
|
|
32
|
+
let xPos = 0;
|
|
33
|
+
let yPos = 0;
|
|
34
|
+
const totalWidths = [];
|
|
35
|
+
Array.from(text).forEach((char, i) => {
|
|
36
|
+
if (char === '\n') {
|
|
37
|
+
totalWidths.push(xPos);
|
|
38
|
+
xPos = 0;
|
|
39
|
+
yPos -= lineHeight;
|
|
40
|
+
} else {
|
|
41
|
+
var _font$fontMetadata$ad;
|
|
42
|
+
// THREE.js's TextGeometry will use "?" as fallback, so we have to use it as well
|
|
43
|
+
const glyphIndex = font.fontMetadata.charToIndex[char];
|
|
44
|
+
const ha = (_font$fontMetadata$ad = font.fontMetadata.advances[glyphIndex]) !== null && _font$fontMetadata$ad !== void 0 ? _font$fontMetadata$ad : font.fontMetadata.advances[unknownGlyphIndex];
|
|
45
|
+
scales[i] = fontScale;
|
|
46
|
+
shifts[i * 2] = xPos;
|
|
47
|
+
shifts[i * 2 + 1] = yPos;
|
|
48
|
+
xPos += ha * fontScale;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
totalWidths.push(xPos);
|
|
52
|
+
|
|
53
|
+
// Since we can have multiple lines, we take the widest one
|
|
54
|
+
const totalWidth = Math.max(...totalWidths);
|
|
55
|
+
const yShift = -((font.fontMetadata.boundingBox.yMax - font.fontMetadata.boundingBox.yMin) / 2 + font.fontMetadata.boundingBox.yMin) * fontScale;
|
|
56
|
+
|
|
57
|
+
// Set alignment
|
|
58
|
+
Array.from(text).forEach((_, i) => {
|
|
59
|
+
if (scales[i] > 0) {
|
|
60
|
+
if (textAlign === TextAlignment.left) {
|
|
61
|
+
shifts[i * 2 + 1] += yShift;
|
|
62
|
+
}
|
|
63
|
+
if (textAlign === TextAlignment.right) {
|
|
64
|
+
shifts[i * 2] -= totalWidth;
|
|
65
|
+
shifts[i * 2 + 1] += yShift;
|
|
66
|
+
}
|
|
67
|
+
if (textAlign === TextAlignment.center) {
|
|
68
|
+
shifts[i * 2] -= totalWidth / 2;
|
|
69
|
+
shifts[i * 2 + 1] += yShift;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Compute the bounding box and sphere
|
|
75
|
+
if (!group.geometry.boundingBox) group.geometry.boundingBox = new Box3();
|
|
76
|
+
if (!group.geometry.boundingSphere) group.geometry.boundingSphere = new Sphere();
|
|
77
|
+
group.geometry.boundingBox.setFromPoints([new Vector3(0, font.fontMetadata.boundingBox.yMin * fontScale, 0), new Vector3(totalWidth, font.fontMetadata.boundingBox.yMax * fontScale, thickness)]);
|
|
78
|
+
group.geometry.boundingSphere.setFromPoints([new Vector3(0, font.fontMetadata.boundingBox.yMin * fontScale, 0), new Vector3(totalWidth, font.fontMetadata.boundingBox.yMax * fontScale, thickness)]);
|
|
79
|
+
if (textAlign === TextAlignment.left) {
|
|
80
|
+
group.geometry.boundingBox.translate(new Vector3(0, yShift, 0));
|
|
81
|
+
group.geometry.boundingSphere.translate(new Vector3(0, yShift, 0));
|
|
82
|
+
}
|
|
83
|
+
if (textAlign === TextAlignment.right) {
|
|
84
|
+
group.geometry.boundingBox.translate(new Vector3(-totalWidth, yShift, 0));
|
|
85
|
+
group.geometry.boundingSphere.translate(new Vector3(-totalWidth, yShift, 0));
|
|
86
|
+
}
|
|
87
|
+
if (textAlign === TextAlignment.center) {
|
|
88
|
+
group.geometry.boundingBox.translate(new Vector3(-totalWidth / 2, yShift, 0));
|
|
89
|
+
group.geometry.boundingSphere.translate(new Vector3(-totalWidth / 2, yShift, 0));
|
|
90
|
+
}
|
|
91
|
+
const myPtr = api.addInstances(text, font, color !== null && color !== void 0 ? color : defaultColor, shifts, group);
|
|
92
|
+
if (myPtr) {
|
|
93
|
+
myPtrRef.current = myPtr;
|
|
94
|
+
return () => api.removeInstances(myPtr);
|
|
95
|
+
}
|
|
96
|
+
}, [text, color, font.fontMetadata, unknownGlyphIndex, thickness, textAlign, api]);
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (myPtrRef.current) {
|
|
99
|
+
api.setOpacity(myPtrRef.current, opacity !== null && opacity !== void 0 ? opacity : 1);
|
|
100
|
+
}
|
|
101
|
+
}, [api, opacity]);
|
|
102
|
+
const textAPI = useMemo(() => ({
|
|
103
|
+
getBuffers: () => myPtrRef.current && api.getBuffers(myPtrRef.current),
|
|
104
|
+
getPtr: () => myPtrRef.current,
|
|
105
|
+
setGlyphs: text => {
|
|
106
|
+
if (!myPtrRef.current) return;
|
|
107
|
+
const buffers = api.getBuffers(myPtrRef.current);
|
|
108
|
+
if (!buffers) return;
|
|
109
|
+
const glyphIds = Array.from(text).map(c => font.fontMetadata.charToIndex[c]);
|
|
110
|
+
buffers.instanceGIndexBuffer.set(glyphIds, myPtrRef.current.start);
|
|
111
|
+
buffers.instanceGIndexBufferAttribute.needsUpdate = true;
|
|
112
|
+
}
|
|
113
|
+
}), [font.fontMetadata.charToIndex]);
|
|
114
|
+
return (
|
|
115
|
+
/*#__PURE__*/
|
|
116
|
+
// @ts-ignore
|
|
117
|
+
React.createElement("instancedTextPlaceholder", _extends({
|
|
118
|
+
scale: [fontSize * ((_scale$x = scale == null ? void 0 : scale.x) !== null && _scale$x !== void 0 ? _scale$x : 1), fontSize * ((_scale$y = scale == null ? void 0 : scale.y) !== null && _scale$y !== void 0 ? _scale$y : 1), thickness * ((_scale$z = scale == null ? void 0 : scale.z) !== null && _scale$z !== void 0 ? _scale$z : 1)]
|
|
119
|
+
}, rest, {
|
|
120
|
+
textAPI: textAPI,
|
|
121
|
+
ref: groupRef,
|
|
122
|
+
frustumCulled: false
|
|
123
|
+
}))
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export { InstancedText };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@react-three/fiber"),n=require("three");class t{boundingBox=null;boundingSphere=null;computeBoundingBox(){this.boundingBox||(this.boundingBox=new n.Box3)}computeBoundingSphere(){this.boundingSphere||(this.boundingSphere=new n.Sphere)}}const o=new n.Matrix4,r=new n.Ray,i=new n.Sphere,s=new n.Vector3;class u extends n.Group{textAPI=void 0;geometry=new t;raycast(e,n){const t=this.geometry,u=this.matrixWorld;null===t.boundingSphere&&t.computeBoundingSphere(),i.copy(t.boundingSphere),i.applyMatrix4(u),!1!==e.ray.intersectsSphere(i)&&(o.copy(u).invert(),r.copy(e.ray).applyMatrix4(o),null!==t.boundingBox&&null===r.intersectBox(t.boundingBox,s)||n.push({distance:s.distanceTo(e.ray.origin),point:s.clone(),object:this}))}}exports.InstancedTextPlaceholder=u,exports.registerInstancedTextPlaceholderToR3F=function(){e.extend({InstancedTextPlaceholder:u})};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ReactThreeFiber } from '@react-three/fiber';
|
|
2
|
+
import { Box3, Group, Intersection, Raycaster, Sphere } from 'three';
|
|
3
|
+
import { InstancedTextAPI } from './types';
|
|
4
|
+
export declare function registerInstancedTextPlaceholderToR3F(): void;
|
|
5
|
+
declare global {
|
|
6
|
+
namespace JSX {
|
|
7
|
+
interface IntrinsicElements {
|
|
8
|
+
instancedTextPlaceholder: ReactThreeFiber.Object3DNode<InstancedTextPlaceholder, typeof InstancedTextPlaceholder>;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
declare class InstancedTextPlaceholderGeometry {
|
|
13
|
+
boundingBox: Box3 | null;
|
|
14
|
+
boundingSphere: Sphere | null;
|
|
15
|
+
computeBoundingBox(): void;
|
|
16
|
+
computeBoundingSphere(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare class InstancedTextPlaceholder extends Group {
|
|
19
|
+
textAPI?: InstancedTextAPI;
|
|
20
|
+
geometry: InstancedTextPlaceholderGeometry;
|
|
21
|
+
raycast(raycaster: Raycaster, intersects: Intersection[]): void;
|
|
22
|
+
}
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { extend } from '@react-three/fiber';
|
|
2
|
+
import { Matrix4, Ray, Sphere, Vector3, Group, Box3 } from 'three';
|
|
3
|
+
|
|
4
|
+
function registerInstancedTextPlaceholderToR3F() {
|
|
5
|
+
extend({
|
|
6
|
+
InstancedTextPlaceholder
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
class InstancedTextPlaceholderGeometry {
|
|
10
|
+
boundingBox = null;
|
|
11
|
+
boundingSphere = null;
|
|
12
|
+
computeBoundingBox() {
|
|
13
|
+
// Will be computed in InstancedText.tsx
|
|
14
|
+
if (!this.boundingBox) this.boundingBox = new Box3();
|
|
15
|
+
}
|
|
16
|
+
computeBoundingSphere() {
|
|
17
|
+
// Will be computed in InstancedText.tsx
|
|
18
|
+
if (!this.boundingSphere) this.boundingSphere = new Sphere();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const _inverseMatrix = new Matrix4();
|
|
22
|
+
const _ray = new Ray();
|
|
23
|
+
const _sphere = new Sphere();
|
|
24
|
+
const _vA = new Vector3();
|
|
25
|
+
class InstancedTextPlaceholder extends Group {
|
|
26
|
+
textAPI = undefined;
|
|
27
|
+
|
|
28
|
+
// For correct bounding box
|
|
29
|
+
geometry = new InstancedTextPlaceholderGeometry();
|
|
30
|
+
raycast(raycaster, intersects) {
|
|
31
|
+
const geometry = this.geometry;
|
|
32
|
+
const matrixWorld = this.matrixWorld;
|
|
33
|
+
// Checking boundingSphere distance to ray
|
|
34
|
+
if (geometry.boundingSphere === null) geometry.computeBoundingSphere();
|
|
35
|
+
_sphere.copy(geometry.boundingSphere);
|
|
36
|
+
_sphere.applyMatrix4(matrixWorld);
|
|
37
|
+
if (raycaster.ray.intersectsSphere(_sphere) === false) return;
|
|
38
|
+
_inverseMatrix.copy(matrixWorld).invert();
|
|
39
|
+
_ray.copy(raycaster.ray).applyMatrix4(_inverseMatrix);
|
|
40
|
+
// Check boundingBox before continuing
|
|
41
|
+
if (geometry.boundingBox !== null && _ray.intersectBox(geometry.boundingBox, _vA) === null) return;
|
|
42
|
+
intersects.push({
|
|
43
|
+
distance: _vA.distanceTo(raycaster.ray.origin),
|
|
44
|
+
point: _vA.clone(),
|
|
45
|
+
object: this
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { InstancedTextPlaceholder, registerInstancedTextPlaceholderToR3F };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@react-three/fiber"),t=require("react"),r=require("./instancedTextBuffers.cjs.js"),n=require("./InstancedTextPlaceholder.cjs.js"),s=require("./useInstancedTextMaterial.cjs.js");function a(e){if(e&&e.__esModule)return e;var t=Object.create(null);return e&&Object.keys(e).forEach((function(r){if("default"!==r){var n=Object.getOwnPropertyDescriptor(e,r);Object.defineProperty(t,r,n.get?n:{enumerable:!0,get:function(){return e[r]}})}})),t.default=e,Object.freeze(t)}require("three"),require("@react-three/drei");var c=a(t);const u=t.createContext(null);const o=t.forwardRef((function({font:n},a){const u=s.useInstancedTextMaterial(s.useFontTexture(n.offsetsPath),s.useFontTexture(n.normalsPath),n.fontMetadata),[{instanceAllocated:o,geometry:l},f]=t.useState({instanceAllocated:0,geometry:null}),i=t.useRef(null),d=t.useCallback((e=>(i.current=r.reallocInstancedTextBuffers(i.current,e,n.fontMetadata.maxVertices),f({instanceAllocated:e,geometry:i.current.geometry}),i.current)),[n.fontMetadata.maxVertices]);return t.useImperativeHandle(a,(()=>({font:n,addInstances:(e,t,n,s,a)=>{var c;const u=null!==(c=i.current)&&void 0!==c?c:d(2*e.length),o=r.addTextInstances(u,t.fontMetadata.charToIndex,e,t,n,s,a);return u.currentGlyphs.length>u.instanceAllocated?d(2*u.currentGlyphs.length):r.updateTextInstancingBuffersAfterAdd(u),o},removeInstances:e=>{const t=i.current;t&&r.removeTextInstances(t,e)},setOpacity:(e,t)=>{const r=i.current;if(r&&r.instanceColorBuffer&&r.instanceColorBufferAttribute){for(let n=0;n<e.length;n++)r.currentColors[4*(e.start+n)+3]=t,r.instanceColorBuffer[4*(e.start+n)+3]=t;r.instanceColorBufferAttribute.needsUpdate=!0}},getBuffers:()=>i.current})),[n,d]),e.useFrame((()=>{const e=i.current;e&&r.textInstancingUpdateTransforms(e)})),l?c.createElement("instancedMesh",{frustumCulled:!1,args:[l,u,o]}):null}));exports.InstancedTextProvider=function({children:e,styles:r}){t.useMemo(n.registerInstancedTextPlaceholderToR3F,[]);const s=t.useRef(new WeakMap),a=t.useCallback((e=>{e&&s.current.set(e.font,e)}),[]),l=t.useMemo((()=>({addInstances:(e,t,r,n,a)=>{const c=s.current.get(t);return null==c?void 0:c.addInstances(e,t,r,n,a)},removeInstances:e=>{const t=s.current.get(e.font);null==t||t.removeInstances(e)},setOpacity:(e,t)=>{const r=s.current.get(e.font);null==r||r.setOpacity(e,t)},getBuffers:e=>{var t;const r=s.current.get(e.font);return null!==(t=null==r?void 0:r.getBuffers(e))&&void 0!==t?t:null}})),[]);return c.createElement(u.Provider,{value:l},r.map((e=>c.createElement(o,{key:e.offsetsPath,font:e,ref:a}))),e)},exports.useInstancedTextAPI=function(){const e=t.useContext(u);if(!e)throw new Error("No instanced text context");return e};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react';
|
|
2
|
+
import { Color, Object3D } from 'three';
|
|
3
|
+
import { InstancedTextBuffers, TextInstancesPtr } from './instancedTextBuffers';
|
|
4
|
+
import { Font3D } from './types';
|
|
5
|
+
interface IInstancedTextAPI {
|
|
6
|
+
addInstances: (text: string, font: Font3D, color: Color, shifts: Float32Array, parentGroup: Object3D) => TextInstancesPtr | undefined;
|
|
7
|
+
removeInstances: (ptr: TextInstancesPtr) => void;
|
|
8
|
+
setOpacity: (ptr: TextInstancesPtr, opacity: number) => void;
|
|
9
|
+
getBuffers: (ptr: TextInstancesPtr) => InstancedTextBuffers | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function useInstancedTextAPI(): IInstancedTextAPI;
|
|
12
|
+
export declare function InstancedTextProvider({ children, styles }: PropsWithChildren<{
|
|
13
|
+
styles: Font3D[];
|
|
14
|
+
}>): JSX.Element;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { useFrame } from '@react-three/fiber';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { useMemo, useRef, useCallback, createContext, useContext, forwardRef, useState, useImperativeHandle } from 'react';
|
|
4
|
+
import { reallocInstancedTextBuffers, addTextInstances, updateTextInstancingBuffersAfterAdd, removeTextInstances, textInstancingUpdateTransforms } from './instancedTextBuffers.js';
|
|
5
|
+
import { registerInstancedTextPlaceholderToR3F } from './InstancedTextPlaceholder.js';
|
|
6
|
+
import { useInstancedTextMaterial, useFontTexture } from './useInstancedTextMaterial.js';
|
|
7
|
+
|
|
8
|
+
const instancedTextContext = /*#__PURE__*/createContext(null);
|
|
9
|
+
function useInstancedTextAPI() {
|
|
10
|
+
const contextValue = useContext(instancedTextContext);
|
|
11
|
+
if (!contextValue) throw new Error('No instanced text context');
|
|
12
|
+
return contextValue;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// To be able to support italic and regular, we segment our instances managers into several style groups
|
|
16
|
+
const StyleGroup = /*#__PURE__*/forwardRef(function StyleGroup({
|
|
17
|
+
font
|
|
18
|
+
}, ref) {
|
|
19
|
+
const textMat = useInstancedTextMaterial(useFontTexture(font.offsetsPath), useFontTexture(font.normalsPath), font.fontMetadata);
|
|
20
|
+
const [{
|
|
21
|
+
instanceAllocated,
|
|
22
|
+
geometry
|
|
23
|
+
}, setInstanceData] = useState({
|
|
24
|
+
// Starts as 0 to force an initial realloc
|
|
25
|
+
instanceAllocated: 0,
|
|
26
|
+
geometry: null
|
|
27
|
+
});
|
|
28
|
+
const buffersRef = useRef(null);
|
|
29
|
+
const realloc = useCallback(instanceAllocated => {
|
|
30
|
+
buffersRef.current = reallocInstancedTextBuffers(buffersRef.current, instanceAllocated, font.fontMetadata.maxVertices);
|
|
31
|
+
setInstanceData({
|
|
32
|
+
instanceAllocated,
|
|
33
|
+
geometry: buffersRef.current.geometry
|
|
34
|
+
});
|
|
35
|
+
return buffersRef.current;
|
|
36
|
+
}, [font.fontMetadata.maxVertices]);
|
|
37
|
+
useImperativeHandle(ref, () => ({
|
|
38
|
+
font,
|
|
39
|
+
addInstances: (text, font, color, shifts, parentGroup) => {
|
|
40
|
+
var _buffersRef$current;
|
|
41
|
+
// Initial alloc
|
|
42
|
+
const buffers = (_buffersRef$current = buffersRef.current) !== null && _buffersRef$current !== void 0 ? _buffersRef$current : realloc(text.length * 2);
|
|
43
|
+
const instance = addTextInstances(buffers, font.fontMetadata.charToIndex, text, font, color, shifts, parentGroup);
|
|
44
|
+
if (buffers.currentGlyphs.length > buffers.instanceAllocated) {
|
|
45
|
+
// Grow only, doubles every time
|
|
46
|
+
realloc(buffers.currentGlyphs.length * 2);
|
|
47
|
+
} else {
|
|
48
|
+
updateTextInstancingBuffersAfterAdd(buffers);
|
|
49
|
+
}
|
|
50
|
+
// Realloc already updates the buffers
|
|
51
|
+
|
|
52
|
+
return instance;
|
|
53
|
+
},
|
|
54
|
+
removeInstances: ptr => {
|
|
55
|
+
const buffers = buffersRef.current;
|
|
56
|
+
if (!buffers) return;
|
|
57
|
+
removeTextInstances(buffers, ptr);
|
|
58
|
+
},
|
|
59
|
+
setOpacity: (ptr, opacity) => {
|
|
60
|
+
const buffers = buffersRef.current;
|
|
61
|
+
if (!buffers) return;
|
|
62
|
+
if (!buffers.instanceColorBuffer || !buffers.instanceColorBufferAttribute) return;
|
|
63
|
+
for (let index = 0; index < ptr.length; index++) {
|
|
64
|
+
buffers.currentColors[(ptr.start + index) * 4 + 3] = opacity;
|
|
65
|
+
buffers.instanceColorBuffer[(ptr.start + index) * 4 + 3] = opacity;
|
|
66
|
+
}
|
|
67
|
+
buffers.instanceColorBufferAttribute.needsUpdate = true;
|
|
68
|
+
},
|
|
69
|
+
getBuffers: () => {
|
|
70
|
+
return buffersRef.current;
|
|
71
|
+
}
|
|
72
|
+
}), [font, realloc]);
|
|
73
|
+
useFrame(() => {
|
|
74
|
+
const buffers = buffersRef.current;
|
|
75
|
+
if (buffers) {
|
|
76
|
+
textInstancingUpdateTransforms(buffers);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
if (!geometry) return null;
|
|
80
|
+
return /*#__PURE__*/React.createElement("instancedMesh", {
|
|
81
|
+
frustumCulled: false,
|
|
82
|
+
args: [geometry, textMat, instanceAllocated]
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
function InstancedTextProvider({
|
|
86
|
+
children,
|
|
87
|
+
styles
|
|
88
|
+
}) {
|
|
89
|
+
useMemo(registerInstancedTextPlaceholderToR3F, []);
|
|
90
|
+
const stylesApisRef = useRef(new WeakMap());
|
|
91
|
+
const setApiRefFn = useCallback(v => {
|
|
92
|
+
if (v) {
|
|
93
|
+
stylesApisRef.current.set(v.font, v);
|
|
94
|
+
}
|
|
95
|
+
}, []);
|
|
96
|
+
const api = useMemo(() => ({
|
|
97
|
+
addInstances: (text, font, color, shifts, parentGroup) => {
|
|
98
|
+
const api = stylesApisRef.current.get(font);
|
|
99
|
+
return api == null ? void 0 : api.addInstances(text, font, color, shifts, parentGroup);
|
|
100
|
+
},
|
|
101
|
+
removeInstances: ptr => {
|
|
102
|
+
const api = stylesApisRef.current.get(ptr.font);
|
|
103
|
+
api == null ? void 0 : api.removeInstances(ptr);
|
|
104
|
+
},
|
|
105
|
+
setOpacity: (ptr, opacity) => {
|
|
106
|
+
const api = stylesApisRef.current.get(ptr.font);
|
|
107
|
+
api == null ? void 0 : api.setOpacity(ptr, opacity);
|
|
108
|
+
},
|
|
109
|
+
getBuffers: ptr => {
|
|
110
|
+
var _api$getBuffers;
|
|
111
|
+
const api = stylesApisRef.current.get(ptr.font);
|
|
112
|
+
return (_api$getBuffers = api == null ? void 0 : api.getBuffers(ptr)) !== null && _api$getBuffers !== void 0 ? _api$getBuffers : null;
|
|
113
|
+
}
|
|
114
|
+
}), []);
|
|
115
|
+
return /*#__PURE__*/React.createElement(instancedTextContext.Provider, {
|
|
116
|
+
value: api
|
|
117
|
+
}, styles.map(font => /*#__PURE__*/React.createElement(StyleGroup, {
|
|
118
|
+
key: font.offsetsPath,
|
|
119
|
+
font: font,
|
|
120
|
+
ref: setApiRefFn
|
|
121
|
+
})), children);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { InstancedTextProvider, useInstancedTextAPI };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("./InstancedText.cjs.js"),r=require("./InstancedTextProvider.cjs.js");require("@babel/runtime/helpers/extends"),require("react"),require("three"),require("./types.cjs.js"),require("@react-three/fiber"),require("./instancedTextBuffers.cjs.js"),require("./InstancedTextPlaceholder.cjs.js"),require("./useInstancedTextMaterial.cjs.js"),require("@react-three/drei"),exports.InstancedText=e.InstancedText,exports.InstancedTextProvider=r.InstancedTextProvider;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { InstancedText } from './InstancedText.js';
|
|
2
|
+
export { InstancedTextProvider } from './InstancedTextProvider.js';
|
|
3
|
+
import '@babel/runtime/helpers/esm/extends';
|
|
4
|
+
import 'react';
|
|
5
|
+
import 'three';
|
|
6
|
+
import './types.js';
|
|
7
|
+
import '@react-three/fiber';
|
|
8
|
+
import './instancedTextBuffers.js';
|
|
9
|
+
import './InstancedTextPlaceholder.js';
|
|
10
|
+
import './useInstancedTextMaterial.js';
|
|
11
|
+
import '@react-three/drei';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=require("three");const e=new t.Matrix4,n=new t.Matrix4;exports.addTextInstances=function(t,e,n,r,s,u,c){const a=t.currentGlyphs.length,i=n.length,o=Array.from(n).map((t=>e[t])),f=Array.from(n).flatMap((()=>[s.r,s.g,s.b,1]));t.currentGlyphs.push(...o),t.currentShifts.push(...u),t.currentColors.push(...f);const l={start:a,length:i,text:n,parentGroup:c,font:r};return t.currentInstances.push(l),l},exports.reallocInstancedTextBuffers=function(e,n,r){var s,u,c,a;null==e||e.geometry.dispose();const i=new t.InstancedBufferGeometry;i.instanceCount=n;const o=new Float32Array(3*r),f=new t.Float32BufferAttribute(o,3,!1);i.setAttribute("position",f);const l=new Float32Array(16*i.instanceCount),d=new t.InstancedBufferAttribute(l,16,!1,1);i.setAttribute("instanceMatrix",d);const p=new Float32Array(i.instanceCount);e&&p.set(e.currentGlyphs);const x=new t.InstancedBufferAttribute(p,1,!1,1);i.setAttribute("instanceGIndex",x);const h=new Float32Array(4*i.instanceCount);e&&h.set(e.currentColors);const A=new t.InstancedBufferAttribute(h,4,!0,1);return i.setAttribute("instanceColor",A),{instanceAllocated:n,geometry:i,currentInstances:null!==(s=null==e?void 0:e.currentInstances)&&void 0!==s?s:[],currentGlyphs:null!==(u=null==e?void 0:e.currentGlyphs)&&void 0!==u?u:[],currentShifts:null!==(c=null==e?void 0:e.currentShifts)&&void 0!==c?c:[],currentColors:null!==(a=null==e?void 0:e.currentColors)&&void 0!==a?a:[],instanceMatrixBuffer:l,instanceMatrixBufferAttribute:d,instanceGIndexBuffer:p,instanceGIndexBufferAttribute:x,instanceColorBuffer:h,instanceColorBufferAttribute:A}},exports.removeTextInstances=function(t,e){const n=t.currentInstances.indexOf(e);if(-1!==n){for(let r=0;r<t.currentInstances.length;r++)r>n&&(t.currentInstances[r].start-=e.length);t.currentInstances.splice(n,1),t.currentGlyphs.splice(e.start,e.length),t.currentShifts.splice(2*e.start,2*e.length),t.currentColors.splice(4*e.start,4*e.length),t.instanceGIndexBuffer.fill(0),t.instanceGIndexBuffer.set(t.currentGlyphs),t.instanceGIndexBufferAttribute.needsUpdate=!0,t.instanceColorBuffer.fill(0),t.instanceColorBuffer.set(t.currentColors),t.instanceColorBufferAttribute.needsUpdate=!0,t.instanceMatrixBuffer.fill(0),t.instanceMatrixBufferAttribute.needsUpdate=!0}},exports.textInstancingUpdateTransforms=function(t){t.currentInstances.forEach((r=>{for(let s=0;s<r.length;s++){const u=t.currentShifts[2*(r.start+s)],c=t.currentShifts[2*(r.start+s)+1];n.copy(r.parentGroup.matrixWorld),e.identity().setPosition(u,c,0),n.multiply(e),n.toArray(t.instanceMatrixBuffer,16*(r.start+s))}})),t.instanceMatrixBufferAttribute.needsUpdate=!0},exports.updateTextInstancingBuffersAfterAdd=function(t){t.instanceGIndexBuffer.set(t.currentGlyphs),t.instanceGIndexBufferAttribute.needsUpdate=!0,t.instanceColorBuffer.set(t.currentColors),t.instanceColorBufferAttribute.needsUpdate=!0};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Color, InstancedBufferAttribute, InstancedBufferGeometry, Object3D } from 'three';
|
|
2
|
+
import { Font3D } from './types';
|
|
3
|
+
export type TextInstancesPtr = {
|
|
4
|
+
start: number;
|
|
5
|
+
length: number;
|
|
6
|
+
text: string;
|
|
7
|
+
font: Font3D;
|
|
8
|
+
parentGroup: Object3D;
|
|
9
|
+
};
|
|
10
|
+
export type InstancedTextBuffers = {
|
|
11
|
+
instanceAllocated: number;
|
|
12
|
+
geometry: InstancedBufferGeometry;
|
|
13
|
+
currentInstances: TextInstancesPtr[];
|
|
14
|
+
currentGlyphs: number[];
|
|
15
|
+
currentShifts: number[];
|
|
16
|
+
currentColors: number[];
|
|
17
|
+
instanceMatrixBuffer: Float32Array;
|
|
18
|
+
instanceMatrixBufferAttribute: InstancedBufferAttribute;
|
|
19
|
+
instanceGIndexBuffer: Float32Array;
|
|
20
|
+
instanceGIndexBufferAttribute: InstancedBufferAttribute;
|
|
21
|
+
instanceColorBuffer: Float32Array;
|
|
22
|
+
instanceColorBufferAttribute: InstancedBufferAttribute;
|
|
23
|
+
};
|
|
24
|
+
export declare function reallocInstancedTextBuffers(buffers: InstancedTextBuffers | null, instanceAllocated: number, maxVertices: number): InstancedTextBuffers;
|
|
25
|
+
export declare function addTextInstances(buffers: InstancedTextBuffers, charToIndex: Record<string, number>, text: string, font: Font3D, color: Color, shifts: Float32Array, parentGroup: Object3D): TextInstancesPtr;
|
|
26
|
+
export declare function updateTextInstancingBuffersAfterAdd(buffers: InstancedTextBuffers): void;
|
|
27
|
+
export declare function removeTextInstances(buffers: InstancedTextBuffers, ptr: TextInstancesPtr): void;
|
|
28
|
+
export declare function textInstancingUpdateTransforms(buffers: InstancedTextBuffers): void;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { InstancedBufferGeometry, Float32BufferAttribute, InstancedBufferAttribute, Matrix4 } from 'three';
|
|
2
|
+
|
|
3
|
+
/** Reallocates a new geometry, a new set of buffers and attributes with a given size */
|
|
4
|
+
function reallocInstancedTextBuffers(buffers, instanceAllocated, maxVertices) {
|
|
5
|
+
var _buffers$currentInsta, _buffers$currentGlyph, _buffers$currentShift, _buffers$currentColor;
|
|
6
|
+
buffers == null ? void 0 : buffers.geometry.dispose();
|
|
7
|
+
const geom = new InstancedBufferGeometry();
|
|
8
|
+
geom.instanceCount = instanceAllocated;
|
|
9
|
+
const positionBuffer = new Float32Array(maxVertices * 3);
|
|
10
|
+
const positionBufferAttribute = new Float32BufferAttribute(positionBuffer, 3, false);
|
|
11
|
+
geom.setAttribute('position', positionBufferAttribute);
|
|
12
|
+
const instanceMatrixBuffer = new Float32Array(geom.instanceCount * 16);
|
|
13
|
+
const instanceMatrixBufferAttribute = new InstancedBufferAttribute(instanceMatrixBuffer, 16, false, 1);
|
|
14
|
+
geom.setAttribute('instanceMatrix', instanceMatrixBufferAttribute);
|
|
15
|
+
const instanceGIndexBuffer = new Float32Array(geom.instanceCount);
|
|
16
|
+
buffers && instanceGIndexBuffer.set(buffers.currentGlyphs);
|
|
17
|
+
const instanceGIndexBufferAttribute = new InstancedBufferAttribute(instanceGIndexBuffer, 1, false, 1);
|
|
18
|
+
geom.setAttribute('instanceGIndex', instanceGIndexBufferAttribute);
|
|
19
|
+
const instanceColorBuffer = new Float32Array(geom.instanceCount * 4);
|
|
20
|
+
buffers && instanceColorBuffer.set(buffers.currentColors);
|
|
21
|
+
const instanceColorBufferAttribute = new InstancedBufferAttribute(instanceColorBuffer, 4, true, 1);
|
|
22
|
+
geom.setAttribute('instanceColor', instanceColorBufferAttribute);
|
|
23
|
+
return {
|
|
24
|
+
instanceAllocated,
|
|
25
|
+
geometry: geom,
|
|
26
|
+
currentInstances: (_buffers$currentInsta = buffers == null ? void 0 : buffers.currentInstances) !== null && _buffers$currentInsta !== void 0 ? _buffers$currentInsta : [],
|
|
27
|
+
currentGlyphs: (_buffers$currentGlyph = buffers == null ? void 0 : buffers.currentGlyphs) !== null && _buffers$currentGlyph !== void 0 ? _buffers$currentGlyph : [],
|
|
28
|
+
currentShifts: (_buffers$currentShift = buffers == null ? void 0 : buffers.currentShifts) !== null && _buffers$currentShift !== void 0 ? _buffers$currentShift : [],
|
|
29
|
+
currentColors: (_buffers$currentColor = buffers == null ? void 0 : buffers.currentColors) !== null && _buffers$currentColor !== void 0 ? _buffers$currentColor : [],
|
|
30
|
+
instanceMatrixBuffer,
|
|
31
|
+
instanceMatrixBufferAttribute,
|
|
32
|
+
instanceGIndexBuffer,
|
|
33
|
+
instanceGIndexBufferAttribute,
|
|
34
|
+
instanceColorBuffer,
|
|
35
|
+
instanceColorBufferAttribute
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Adds new instances to the temp buffers, without affecting the geometry. You will need to update the attributes manually after this call. */
|
|
40
|
+
function addTextInstances(buffers, charToIndex, text, font, color, shifts, parentGroup) {
|
|
41
|
+
const start = buffers.currentGlyphs.length;
|
|
42
|
+
const length = text.length;
|
|
43
|
+
const glyphIds = Array.from(text).map(c => charToIndex[c]);
|
|
44
|
+
const colors = Array.from(text).flatMap(() => [color.r, color.g, color.b, 1]);
|
|
45
|
+
buffers.currentGlyphs.push(...glyphIds);
|
|
46
|
+
buffers.currentShifts.push(...shifts);
|
|
47
|
+
buffers.currentColors.push(...colors);
|
|
48
|
+
const instance = {
|
|
49
|
+
start,
|
|
50
|
+
length,
|
|
51
|
+
text,
|
|
52
|
+
parentGroup,
|
|
53
|
+
font
|
|
54
|
+
};
|
|
55
|
+
buffers.currentInstances.push(instance);
|
|
56
|
+
return instance;
|
|
57
|
+
}
|
|
58
|
+
function updateTextInstancingBuffersAfterAdd(buffers) {
|
|
59
|
+
buffers.instanceGIndexBuffer.set(buffers.currentGlyphs);
|
|
60
|
+
buffers.instanceGIndexBufferAttribute.needsUpdate = true;
|
|
61
|
+
buffers.instanceColorBuffer.set(buffers.currentColors);
|
|
62
|
+
buffers.instanceColorBufferAttribute.needsUpdate = true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Removes instances from both the temp buffers and attribute buffers. */
|
|
66
|
+
function removeTextInstances(buffers, ptr) {
|
|
67
|
+
const index = buffers.currentInstances.indexOf(ptr);
|
|
68
|
+
if (index === -1) return;
|
|
69
|
+
|
|
70
|
+
// Update all the indices after the element to remove
|
|
71
|
+
for (let i = 0; i < buffers.currentInstances.length; i++) {
|
|
72
|
+
if (i > index) {
|
|
73
|
+
buffers.currentInstances[i].start -= ptr.length;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
buffers.currentInstances.splice(index, 1);
|
|
77
|
+
buffers.currentGlyphs.splice(ptr.start, ptr.length);
|
|
78
|
+
buffers.currentShifts.splice(ptr.start * 2, ptr.length * 2);
|
|
79
|
+
buffers.currentColors.splice(ptr.start * 4, ptr.length * 4);
|
|
80
|
+
buffers.instanceGIndexBuffer.fill(0);
|
|
81
|
+
buffers.instanceGIndexBuffer.set(buffers.currentGlyphs);
|
|
82
|
+
buffers.instanceGIndexBufferAttribute.needsUpdate = true;
|
|
83
|
+
buffers.instanceColorBuffer.fill(0);
|
|
84
|
+
buffers.instanceColorBuffer.set(buffers.currentColors);
|
|
85
|
+
buffers.instanceColorBufferAttribute.needsUpdate = true;
|
|
86
|
+
buffers.instanceMatrixBuffer.fill(0);
|
|
87
|
+
buffers.instanceMatrixBufferAttribute.needsUpdate = true;
|
|
88
|
+
}
|
|
89
|
+
const tempMatrix = new Matrix4();
|
|
90
|
+
const tempMatrixWorld = new Matrix4();
|
|
91
|
+
function textInstancingUpdateTransforms(buffers) {
|
|
92
|
+
buffers.currentInstances.forEach(inst => {
|
|
93
|
+
for (let j = 0; j < inst.length; j++) {
|
|
94
|
+
const xShift = buffers.currentShifts[(inst.start + j) * 2];
|
|
95
|
+
const yShift = buffers.currentShifts[(inst.start + j) * 2 + 1];
|
|
96
|
+
tempMatrixWorld.copy(inst.parentGroup.matrixWorld);
|
|
97
|
+
tempMatrix.identity().setPosition(xShift, yShift, 0);
|
|
98
|
+
tempMatrixWorld.multiply(tempMatrix);
|
|
99
|
+
tempMatrixWorld.toArray(buffers.instanceMatrixBuffer, (inst.start + j) * 16);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
buffers.instanceMatrixBufferAttribute.needsUpdate = true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export { addTextInstances, reallocInstancedTextBuffers, removeTextInstances, textInstancingUpdateTransforms, updateTextInstancingBuffersAfterAdd };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});let e=function(e){return e.left="left",e.right="right",e.center="center",e}({});exports.TextAlignment=e;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { InstancedTextBuffers, TextInstancesPtr } from './instancedTextBuffers';
|
|
2
|
+
export declare enum TextAlignment {
|
|
3
|
+
left = "left",
|
|
4
|
+
right = "right",
|
|
5
|
+
center = "center"
|
|
6
|
+
}
|
|
7
|
+
export interface FontInfo3D {
|
|
8
|
+
fontScale: number;
|
|
9
|
+
underlineThickness: number;
|
|
10
|
+
boundingBox: BoundingBox;
|
|
11
|
+
advances: number[];
|
|
12
|
+
maxVertices: number;
|
|
13
|
+
charToIndex: Record<string, number>;
|
|
14
|
+
xShift: number;
|
|
15
|
+
yShift: number;
|
|
16
|
+
zShift: number;
|
|
17
|
+
xScale: number;
|
|
18
|
+
yScale: number;
|
|
19
|
+
zScale: number;
|
|
20
|
+
}
|
|
21
|
+
export interface Font3D {
|
|
22
|
+
fontMetadata: FontInfo3D;
|
|
23
|
+
offsetsPath: string;
|
|
24
|
+
normalsPath: string;
|
|
25
|
+
}
|
|
26
|
+
interface BoundingBox {
|
|
27
|
+
xMin: number;
|
|
28
|
+
yMin: number;
|
|
29
|
+
xMax: number;
|
|
30
|
+
yMax: number;
|
|
31
|
+
}
|
|
32
|
+
export interface InstancedTextAPI {
|
|
33
|
+
getBuffers: () => InstancedTextBuffers | null;
|
|
34
|
+
getPtr: () => TextInstancesPtr | null;
|
|
35
|
+
setGlyphs: (text: string) => void;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),r=require("three"),t=require("@react-three/drei");const a=()=>new r.MeshPhongMaterial;exports.useFontTexture=function(e){const a=t.useTexture(e);return a.flipY=!1,a.magFilter=r.NearestFilter,a.minFilter=r.NearestFilter,a},exports.useInstancedTextMaterial=function(t,n,i,l=a){return e.useMemo((()=>{const e=l();return e.onBeforeCompile=e=>{e.uniforms={...e.uniforms,offsets:{value:t},normals:{value:n},shift:{value:new r.Vector3(i.xShift,i.yShift,i.zShift)},scale:{value:new r.Vector3(i.xScale,i.yScale,i.zScale)}},e.vertexShader=e.vertexShader.replace(/#include <morphtarget_pars_vertex>/gi,""),e.vertexShader=e.vertexShader.replace(/#include <morphtarget_vertex>/gi,""),e.vertexShader=e.vertexShader.replace(/#include <morphcolor_vertex>/gi,""),e.vertexShader=e.vertexShader.replace(/#include <morphnormal_vertex>/gi,""),e.vertexShader=e.vertexShader.replace(/#include <skinning_pars_vertex>/gi,""),e.vertexShader=e.vertexShader.replace(/#include <skinbase_vertex>/gi,""),e.vertexShader=e.vertexShader.replace(/#include <skinning_vertex>/gi,""),e.vertexShader=e.vertexShader.replace(/#include <skinnormal_vertex>/gi,""),e.vertexShader=e.vertexShader.replace(/#include <displacementmap_pars_vertex>/gi,""),e.vertexShader=e.vertexShader.replace(/#include <displacementmap_vertex>/gi,""),e.vertexShader=e.vertexShader.replace(/>[\n]*void/,">\n uniform sampler2D offsets;\n uniform sampler2D normals;\n uniform vec3 shift;\n uniform vec3 scale;\n in float instanceGIndex;\n\n void"),e.vertexShader=e.vertexShader.replace(/void main\(\) {/,"void main() { vec3 offset = texelFetch(offsets, ivec2(gl_VertexID, instanceGIndex), 0).rgb * scale + shift;"),e.vertexShader=e.vertexShader.replace(/#include <beginnormal_vertex>/,"vec3 objectNormal = (texelFetch(normals, ivec2(gl_VertexID, instanceGIndex), 0).rgb) - 0.5;"),e.vertexShader=e.vertexShader.replace(/#include <begin_vertex>/,"vec3 transformed = offset;")},e}),[t,n,i,l])};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { MeshPhongMaterial, Texture } from 'three';
|
|
2
|
+
import { FontInfo3D } from './types';
|
|
3
|
+
export declare function useInstancedTextMaterial(offsetsTexture: Texture, normalsTexture: Texture, fontMetadata: FontInfo3D, originalMaterial?: () => MeshPhongMaterial): MeshPhongMaterial;
|
|
4
|
+
export declare function useFontTexture(texPath: string): Texture;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { Vector3, NearestFilter, MeshPhongMaterial } from 'three';
|
|
3
|
+
import { useTexture } from '@react-three/drei';
|
|
4
|
+
|
|
5
|
+
const defaultMaterial = () => new MeshPhongMaterial();
|
|
6
|
+
function useInstancedTextMaterial(offsetsTexture, normalsTexture, fontMetadata, originalMaterial = defaultMaterial) {
|
|
7
|
+
const textMat = useMemo(() => {
|
|
8
|
+
const material = originalMaterial();
|
|
9
|
+
material.onBeforeCompile = shader => {
|
|
10
|
+
shader.uniforms = {
|
|
11
|
+
...shader.uniforms,
|
|
12
|
+
offsets: {
|
|
13
|
+
value: offsetsTexture
|
|
14
|
+
},
|
|
15
|
+
normals: {
|
|
16
|
+
value: normalsTexture
|
|
17
|
+
},
|
|
18
|
+
shift: {
|
|
19
|
+
value: new Vector3(fontMetadata.xShift, fontMetadata.yShift, fontMetadata.zShift)
|
|
20
|
+
},
|
|
21
|
+
scale: {
|
|
22
|
+
value: new Vector3(fontMetadata.xScale, fontMetadata.yScale, fontMetadata.zScale)
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Strip out features we definitely don't need
|
|
27
|
+
// Strip morph targets
|
|
28
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <morphtarget_pars_vertex>/gi, '');
|
|
29
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <morphtarget_vertex>/gi, '');
|
|
30
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <morphcolor_vertex>/gi, '');
|
|
31
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <morphnormal_vertex>/gi, '');
|
|
32
|
+
|
|
33
|
+
// Strip animated vertex skinning
|
|
34
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <skinning_pars_vertex>/gi, '');
|
|
35
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <skinbase_vertex>/gi, '');
|
|
36
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <skinning_vertex>/gi, '');
|
|
37
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <skinnormal_vertex>/gi, '');
|
|
38
|
+
|
|
39
|
+
// Strip displacement mapping
|
|
40
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <displacementmap_pars_vertex>/gi, '');
|
|
41
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <displacementmap_vertex>/gi, '');
|
|
42
|
+
|
|
43
|
+
// Insert global chunk
|
|
44
|
+
shader.vertexShader = shader.vertexShader.replace(/>[\n]*void/, `>
|
|
45
|
+
uniform sampler2D offsets;
|
|
46
|
+
uniform sampler2D normals;
|
|
47
|
+
uniform vec3 shift;
|
|
48
|
+
uniform vec3 scale;
|
|
49
|
+
in float instanceGIndex;
|
|
50
|
+
|
|
51
|
+
void`);
|
|
52
|
+
|
|
53
|
+
// Insert program chunk
|
|
54
|
+
shader.vertexShader = shader.vertexShader.replace(/void main\(\) {/, `void main() { vec3 offset = texelFetch(offsets, ivec2(gl_VertexID, instanceGIndex), 0).rgb * scale + shift;`);
|
|
55
|
+
|
|
56
|
+
// Replace normal init
|
|
57
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <beginnormal_vertex>/, `vec3 objectNormal = (texelFetch(normals, ivec2(gl_VertexID, instanceGIndex), 0).rgb) - 0.5;`);
|
|
58
|
+
// Replace position init
|
|
59
|
+
shader.vertexShader = shader.vertexShader.replace(/#include <begin_vertex>/, `vec3 transformed = offset;`);
|
|
60
|
+
};
|
|
61
|
+
return material;
|
|
62
|
+
}, [offsetsTexture, normalsTexture, fontMetadata, originalMaterial]);
|
|
63
|
+
return textMat;
|
|
64
|
+
}
|
|
65
|
+
function useFontTexture(texPath) {
|
|
66
|
+
const fontTexture = useTexture(texPath);
|
|
67
|
+
fontTexture.flipY = false;
|
|
68
|
+
fontTexture.magFilter = NearestFilter;
|
|
69
|
+
fontTexture.minFilter = NearestFilter;
|
|
70
|
+
return fontTexture;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { useFontTexture, useInstancedTextMaterial };
|