@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.
Files changed (65) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +138 -0
  3. package/index.cjs.js +1 -0
  4. package/index.d.ts +2 -0
  5. package/index.js +6 -0
  6. package/package.json +54 -0
  7. package/text_engine_2d/MSDFTextGeometry/TextLayout.cjs.js +1 -0
  8. package/text_engine_2d/MSDFTextGeometry/TextLayout.d.ts +48 -0
  9. package/text_engine_2d/MSDFTextGeometry/TextLayout.js +357 -0
  10. package/text_engine_2d/MSDFTextGeometry/index.cjs.js +1 -0
  11. package/text_engine_2d/MSDFTextGeometry/index.d.ts +51 -0
  12. package/text_engine_2d/MSDFTextGeometry/index.js +159 -0
  13. package/text_engine_2d/MSDFTextGeometry/utils.cjs.js +1 -0
  14. package/text_engine_2d/MSDFTextGeometry/utils.d.ts +3 -0
  15. package/text_engine_2d/MSDFTextGeometry/utils.js +46 -0
  16. package/text_engine_2d/MSDFTextGeometry/vertices.cjs.js +1 -0
  17. package/text_engine_2d/MSDFTextGeometry/vertices.d.ts +28 -0
  18. package/text_engine_2d/MSDFTextGeometry/vertices.js +84 -0
  19. package/text_engine_2d/MSDFTextMaterial/fragment.glsl.js +3 -0
  20. package/text_engine_2d/MSDFTextMaterial/index.cjs.js +1 -0
  21. package/text_engine_2d/MSDFTextMaterial/index.d.ts +4 -0
  22. package/text_engine_2d/MSDFTextMaterial/index.js +38 -0
  23. package/text_engine_2d/MSDFTextMaterial/vertex.glsl.js +3 -0
  24. package/text_engine_2d/index.cjs.js +1 -0
  25. package/text_engine_2d/index.d.ts +6 -0
  26. package/text_engine_2d/index.js +18 -0
  27. package/text_engine_2d/react/SDFText.cjs.js +1 -0
  28. package/text_engine_2d/react/SDFText.d.ts +9 -0
  29. package/text_engine_2d/react/SDFText.js +63 -0
  30. package/text_engine_2d/react/SDFTextProvider.cjs.js +1 -0
  31. package/text_engine_2d/react/SDFTextProvider.d.ts +6 -0
  32. package/text_engine_2d/react/SDFTextProvider.js +191 -0
  33. package/text_engine_2d/react/context.cjs.js +1 -0
  34. package/text_engine_2d/react/context.d.ts +3 -0
  35. package/text_engine_2d/react/context.js +5 -0
  36. package/text_engine_2d/react/types.cjs.js +1 -0
  37. package/text_engine_2d/react/types.d.ts +33 -0
  38. package/text_engine_2d/react/types.js +1 -0
  39. package/text_engine_2d/react/utils.cjs.js +1 -0
  40. package/text_engine_2d/react/utils.d.ts +2 -0
  41. package/text_engine_2d/react/utils.js +6 -0
  42. package/text_engine_2d/register.cjs.js +1 -0
  43. package/text_engine_2d/register.d.ts +12 -0
  44. package/text_engine_2d/register.js +12 -0
  45. package/text_engine_3d/InstancedText.cjs.js +1 -0
  46. package/text_engine_3d/InstancedText.d.ts +13 -0
  47. package/text_engine_3d/InstancedText.js +127 -0
  48. package/text_engine_3d/InstancedTextPlaceholder.cjs.js +1 -0
  49. package/text_engine_3d/InstancedTextPlaceholder.d.ts +23 -0
  50. package/text_engine_3d/InstancedTextPlaceholder.js +50 -0
  51. package/text_engine_3d/InstancedTextProvider.cjs.js +1 -0
  52. package/text_engine_3d/InstancedTextProvider.d.ts +15 -0
  53. package/text_engine_3d/InstancedTextProvider.js +124 -0
  54. package/text_engine_3d/index.cjs.js +1 -0
  55. package/text_engine_3d/index.d.ts +3 -0
  56. package/text_engine_3d/index.js +11 -0
  57. package/text_engine_3d/instancedTextBuffers.cjs.js +1 -0
  58. package/text_engine_3d/instancedTextBuffers.d.ts +28 -0
  59. package/text_engine_3d/instancedTextBuffers.js +105 -0
  60. package/text_engine_3d/types.cjs.js +1 -0
  61. package/text_engine_3d/types.d.ts +37 -0
  62. package/text_engine_3d/types.js +8 -0
  63. package/text_engine_3d/useInstancedTextMaterial.cjs.js +1 -0
  64. package/text_engine_3d/useInstancedTextMaterial.d.ts +4 -0
  65. 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,3 @@
1
+ import { InstancedText } from './InstancedText';
2
+ import { InstancedTextProvider } from './InstancedTextProvider';
3
+ export { InstancedTextProvider, InstancedText };
@@ -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,8 @@
1
+ let TextAlignment = /*#__PURE__*/function (TextAlignment) {
2
+ TextAlignment["left"] = "left";
3
+ TextAlignment["right"] = "right";
4
+ TextAlignment["center"] = "center";
5
+ return TextAlignment;
6
+ }({});
7
+
8
+ export { TextAlignment };
@@ -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 };