Git Product home page Git Product logo

troika's People

Contributors

alexpineda avatar codyjasonbennett avatar dependabot[bot] avatar drcmda avatar hawtinzeng avatar inokawa avatar jmatsushita avatar lojjic avatar renaudrohlinger avatar sebastianbaltes avatar stevenbirobs avatar tvler avatar wmcmurray avatar wulimao49 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

troika's Issues

Extract text rendering to a ThreeJS object

Text3DFacade's underlying implementation should be extracted to a subclass of THREE.Mesh (without a dependency on troika-3d if possible). This would allow it to be used by itself in other projects and frameworks (AFrame, react-three-fiber, etc.) that could benefit from its advantages over other text implementations.

Text SDF texture can exceed maximum dimension

With an average gl.MAX_TEXTURE_SIZE of 4096 (see https://webglstats.com/webgl/parameter/MAX_TEXTURE_SIZE), the current approach of a single-column SDF texture only allows up to 64 glyphs. Exceeding that count results in gl errors and broken rendering.

The single-column approach was nice because it made inserting new glyphs super simple, but that obviously doesn't work. Let's move to a texture with default width of 2048 and a single row, and grow its height by powers of 2 as necessary from there. That will allow for 2048 glyphs at default SDF size in essentially all cases. Inserting and reading will be slightly more complex but not very much so.

Shader compile error with troika-3d-text

When using the 3d-text module I get this error. It seems that something is wrong with DerivedShader:

THREE.WebGLShader: gl.getShaderInfoLog() vertex
ERROR: 0:390: 'uTroikaSDFTextureSize' : redefinition
ERROR: 0:391: 'uTroikaSDFGlyphSize' : redefinition
ERROR: 0:392: 'uTroikaTotalBounds' : redefinition
ERROR: 0:393: 'uTroikaClipRect' : redefinition
ERROR: 0:394: 'uTroikaOrient' : redefinition
ERROR: 0:395: 'uTroikaUseGlyphColors' : redefinition
ERROR: 0:396: 'aTroikaGlyphBounds' : redefinition
ERROR: 0:397: 'aTroikaGlyphIndex' : redefinition
ERROR: 0:398: 'aTroikaGlyphColor' : redefinition
ERROR: 0:399: 'vTroikaSDFTextureUV' : redefinition
ERROR: 0:400: 'vTroikaGlyphUV' : redefinition
ERROR: 0:401: 'vTroikaGlyphColor' : redefinition
�1: precision highp float;
2: precision highp int;
3: #define HIGH_PRECISION
4: #define SHADER_NAME MeshBasicMaterial
5: #define TROIKA_DERIVED_MATERIAL 3
6: #define VERTEX_TEXTURES
7: #define GAMMA_FACTOR 2
8: #define MAX_BONES 0
9: #define BONE_TEXTURE
10: #define DOUBLE_SIDED
11: #define USE_LOGDEPTHBUF
12: #define USE_LOGDEPTHBUF_EXT
13: uniform mat4 modelMatrix;
14: uniform mat4 modelViewMatrix;
15: uniform mat4 projectionMatrix;
16: uniform mat4 viewMatrix;
17: uniform mat3 normalMatrix;
18: uniform vec3 cameraPosition;
19: uniform bool isOrthographic;
20: #ifdef USE_INSTANCING
21:  attribute mat4 instanceMatrix;
22: #endif
23: attribute vec3 position;
24: attribute vec3 normal;
25: attribute vec2 uv;
26: #ifdef USE_TANGENT
27: 	attribute vec4 tangent;
28: #endif
29: #ifdef USE_COLOR
30: 	attribute vec3 color;
31: #endif
32: #ifdef USE_MORPHTARGETS
33: 	attribute vec3 morphTarget0;
34: 	attribute vec3 morphTarget1;
35: 	attribute vec3 morphTarget2;
36: 	attribute vec3 morphTarget3;
37: 	#ifdef USE_MORPHNORMALS
38: 		attribute vec3 morphNormal0;
39: 		attribute vec3 morphNormal1;
40: 		attribute vec3 morphNormal2;
41: 		attribute vec3 morphNormal3;
42: 	#else
43: 		attribute vec3 morphTarget4;
44: 		attribute vec3 morphTarget5;
45: 		attribute vec3 morphTarget6;
46: 		attribute vec3 morphTarget7;
47: 	#endif
48: #endif
49: #ifdef USE_SKINNING
50: 	attribute vec4 skinIndex;
51: 	attribute vec4 skinWeight;
52: #endif
53: 
54: #define PI 3.14159265359
55: #define PI2 6.28318530718
56: #define PI_HALF 1.5707963267949
57: #define RECIPROCAL_PI 0.31830988618
58: #define RECIPROCAL_PI2 0.15915494
59: #define LOG2 1.442695
60: #define EPSILON 1e-6
61: #ifndef saturate
62: #define saturate(a) clamp( a, 0.0, 1.0 )
63: #endif
64: #define whiteComplement(a) ( 1.0 - saturate( a ) )
65: float pow2( const in float x ) { return x*x; }
66: float pow3( const in float x ) { return x*x*x; }
67: float pow4( const in float x ) { float x2 = x*x; return x2*x2; }
68: float average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }
69: highp float rand( const in vec2 troika_uv_1 ) {
70: 	const highp float a = 12.9898, b = 78.233, c = 43758.5453;
71: 	highp float dt = dot( troika_uv_1.xy, vec2( a,b ) ), sn = mod( dt, PI );
72: 	return fract(sin(sn) * c);
73: }
74: #ifdef HIGH_PRECISION
75: 	float precisionSafeLength( vec3 v ) { return length( v ); }
76: #else
77: 	float max3( vec3 v ) { return max( max( v.x, v.y ), v.z ); }
78: 	float precisionSafeLength( vec3 v ) {
79: 		float maxComponent = max3( abs( v ) );
80: 		return length( v / maxComponent ) * maxComponent;
81: 	}
82: #endif
83: struct IncidentLight {
84: 	vec3 color;
85: 	vec3 direction;
86: 	bool visible;
87: };
88: struct ReflectedLight {
89: 	vec3 directDiffuse;
90: 	vec3 directSpecular;
91: 	vec3 indirectDiffuse;
92: 	vec3 indirectSpecular;
93: };
94: struct GeometricContext {
95: 	vec3 troika_position_1;
96: 	vec3 troika_normal_1;
97: 	vec3 viewDir;
98: #ifdef CLEARCOAT
99: 	vec3 clearcoatNormal;
100: #endif
101: };
102: vec3 transformDirection( in vec3 dir, in mat4 matrix ) {
103: 	return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );
104: }
105: vec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {
106: 	return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );
107: }
108: vec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {
109: 	float distance = dot( planeNormal, point - pointOnPlane );
110: 	return - distance * planeNormal + point;
111: }
112: float sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {
113: 	return sign( dot( point - pointOnPlane, planeNormal ) );
114: }
115: vec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {
116: 	return lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;
117: }
118: mat3 transposeMat3( const in mat3 m ) {
119: 	mat3 tmp;
120: 	tmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );
121: 	tmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );
122: 	tmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );
123: 	return tmp;
124: }
125: float linearToRelativeLuminance( const in vec3 color ) {
126: 	vec3 weights = vec3( 0.2126, 0.7152, 0.0722 );
127: 	return dot( weights, color.rgb );
128: }
129: bool isPerspectiveMatrix( mat4 m ) {
130:   return m[ 2 ][ 3 ] == - 1.0;
131: }
132: #ifdef USE_UV
133: 	#ifdef UVS_VERTEX_ONLY
134: 		vec2 vUv;
135: 	#else
136: 		varying vec2 vUv;
137: 	#endif
138: 	uniform mat3 uvTransform;
139: #endif
140: #if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )
141: 	attribute vec2 uv2;
142: 	varying vec2 vUv2;
143: 	uniform mat3 uv2Transform;
144: #endif
145: #ifdef USE_ENVMAP
146: 	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) ||defined( PHONG )
147: 		#define ENV_WORLDPOS
148: 	#endif
149: 	#ifdef ENV_WORLDPOS
150: 		
151: 		varying vec3 vWorldPosition;
152: 	#else
153: 		varying vec3 vReflect;
154: 		uniform float refractionRatio;
155: 	#endif
156: #endif
157: #ifdef USE_COLOR
158: 	varying vec3 vColor;
159: #endif
160: #ifdef USE_FOG
161: 	varying float fogDepth;
162: #endif
163: #ifdef USE_MORPHTARGETS
164: 	uniform float morphTargetBaseInfluence;
165: 	#ifndef USE_MORPHNORMALS
166: 	uniform float morphTargetInfluences[ 8 ];
167: 	#else
168: 	uniform float morphTargetInfluences[ 4 ];
169: 	#endif
170: #endif
171: #ifdef USE_SKINNING
172: 	uniform mat4 bindMatrix;
173: 	uniform mat4 bindMatrixInverse;
174: 	#ifdef BONE_TEXTURE
175: 		uniform highp sampler2D boneTexture;
176: 		uniform int boneTextureSize;
177: 		mat4 getBoneMatrix( const in float i ) {
178: 			float j = i * 4.0;
179: 			float x = mod( j, float( boneTextureSize ) );
180: 			float y = floor( j / float( boneTextureSize ) );
181: 			float dx = 1.0 / float( boneTextureSize );
182: 			float dy = 1.0 / float( boneTextureSize );
183: 			y = dy * ( y + 0.5 );
184: 			vec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );
185: 			vec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );
186: 			vec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );
187: 			vec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );
188: 			mat4 bone = mat4( v1, v2, v3, v4 );
189: 			return bone;
190: 		}
191: 	#else
192: 		uniform mat4 boneMatrices[ MAX_BONES ];
193: 		mat4 getBoneMatrix( const in float i ) {
194: 			mat4 bone = boneMatrices[ int(i) ];
195: 			return bone;
196: 		}
197: 	#endif
198: #endif
199: #ifdef USE_LOGDEPTHBUF
200: 	#ifdef USE_LOGDEPTHBUF_EXT
201: 		varying float vFragDepth;
202: 		varying float vIsPerspective;
203: 	#else
204: 		uniform float logDepthBufFC;
205: 	#endif
206: #endif
207: #if 0 > 0
208: 	varying vec3 vClipPosition;
209: #endif
210: uniform vec3 diffuse;
211: 
212: 
213: uniform vec2 uTroikaSDFTextureSize;
214: uniform float uTroikaSDFGlyphSize;
215: uniform vec4 uTroikaTotalBounds;
216: uniform vec4 uTroikaClipRect;
217: uniform mat3 uTroikaOrient;
218: uniform bool uTroikaUseGlyphColors;
219: attribute vec4 aTroikaGlyphBounds;
220: attribute float aTroikaGlyphIndex;
221: attribute vec3 aTroikaGlyphColor;
222: varying vec2 vTroikaSDFTextureUV;
223: varying vec2 vTroikaGlyphUV;
224: varying vec3 vTroikaGlyphColor;
225: 
226: vec3 troika_position_1;
227: vec3 troika_normal_1;
228: vec2 troika_uv_1;
229: void troikaVertexTransform1(inout vec3 troika_position_3, inout vec3 troika_normal_3, inout vec2 troika_uv_3) {
230:   
231: vec4 bounds = aTroikaGlyphBounds;
232: vec4 clippedBounds = vec4(
233:   clamp(bounds.xy, uTroikaClipRect.xy, uTroikaClipRect.zw),
234:   clamp(bounds.zw, uTroikaClipRect.xy, uTroikaClipRect.zw)
235: );
236: vec2 clippedXY = (mix(clippedBounds.xy, clippedBounds.zw, troika_position_3.xy) - bounds.xy) / (bounds.zw - bounds.xy);
237: vTroikaGlyphUV = clippedXY.xy;
238: 
239: float cols = uTroikaSDFTextureSize.x / uTroikaSDFGlyphSize;
240: vTroikaSDFTextureUV = vec2(
241:   mod(aTroikaGlyphIndex, cols) + clippedXY.x,
242:   floor(aTroikaGlyphIndex / cols) + clippedXY.y
243: ) * uTroikaSDFGlyphSize / uTroikaSDFTextureSize;
244: 
245: troika_position_3.xy = mix(bounds.xy, bounds.zw, clippedXY);
246: 
247: troika_uv_3 = vec2(
248:   (troika_position_3.x - uTroikaTotalBounds.x) / (uTroikaTotalBounds.z - uTroikaTotalBounds.x),
249:   (troika_position_3.y - uTroikaTotalBounds.y) / (uTroikaTotalBounds.w - uTroikaTotalBounds.y)
250: );
251: 
252: troika_position_3 = uTroikaOrient * troika_position_3;
253: troika_normal_3 = uTroikaOrient * troika_normal_3;
254: 
255: }
256: 
257: void troikaOrigMain1() {
258: vTroikaGlyphColor = uTroikaUseGlyphColors ? aTroikaGlyphColor / 255.0 : diffuse;
259: 
260: #ifdef USE_UV
261: 	vUv = ( uvTransform * vec3( troika_uv_1, 1 ) ).xy;
262: #endif
263: #if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )
264: 	vUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;
265: #endif
266: #ifdef USE_COLOR
267: 	vColor.xyz = color.xyz;
268: #endif
269: #ifdef USE_SKINNING
270: 	mat4 boneMatX = getBoneMatrix( skinIndex.x );
271: 	mat4 boneMatY = getBoneMatrix( skinIndex.y );
272: 	mat4 boneMatZ = getBoneMatrix( skinIndex.z );
273: 	mat4 boneMatW = getBoneMatrix( skinIndex.w );
274: #endif
275: 	#ifdef USE_ENVMAP
276: vec3 objectNormal = vec3( troika_normal_1 );
277: #ifdef USE_TANGENT
278: 	vec3 objectTangent = vec3( tangent.xyz );
279: #endif
280: #ifdef USE_MORPHNORMALS
281: 	objectNormal *= morphTargetBaseInfluence;
282: 	objectNormal += morphNormal0 * morphTargetInfluences[ 0 ];
283: 	objectNormal += morphNormal1 * morphTargetInfluences[ 1 ];
284: 	objectNormal += morphNormal2 * morphTargetInfluences[ 2 ];
285: 	objectNormal += morphNormal3 * morphTargetInfluences[ 3 ];
286: #endif
287: #ifdef USE_SKINNING
288: 	mat4 skinMatrix = mat4( 0.0 );
289: 	skinMatrix += skinWeight.x * boneMatX;
290: 	skinMatrix += skinWeight.y * boneMatY;
291: 	skinMatrix += skinWeight.z * boneMatZ;
292: 	skinMatrix += skinWeight.w * boneMatW;
293: 	skinMatrix  = bindMatrixInverse * skinMatrix * bindMatrix;
294: 	objectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;
295: 	#ifdef USE_TANGENT
296: 		objectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;
297: 	#endif
298: #endif
299: vec3 transformedNormal = objectNormal;
300: #ifdef USE_INSTANCING
301: 	mat3 m = mat3( instanceMatrix );
302: 	transformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );
303: 	transformedNormal = m * transformedNormal;
304: #endif
305: transformedNormal = normalMatrix * transformedNormal;
306: #ifdef FLIP_SIDED
307: 	transformedNormal = - transformedNormal;
308: #endif
309: #ifdef USE_TANGENT
310: 	vec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;
311: 	#ifdef FLIP_SIDED
312: 		transformedTangent = - transformedTangent;
313: 	#endif
314: #endif
315: 	#endif
316: vec3 transformed = vec3( troika_position_1 );
317: #ifdef USE_MORPHTARGETS
318: 	transformed *= morphTargetBaseInfluence;
319: 	transformed += morphTarget0 * morphTargetInfluences[ 0 ];
320: 	transformed += morphTarget1 * morphTargetInfluences[ 1 ];
321: 	transformed += morphTarget2 * morphTargetInfluences[ 2 ];
322: 	transformed += morphTarget3 * morphTargetInfluences[ 3 ];
323: 	#ifndef USE_MORPHNORMALS
324: 	transformed += morphTarget4 * morphTargetInfluences[ 4 ];
325: 	transformed += morphTarget5 * morphTargetInfluences[ 5 ];
326: 	transformed += morphTarget6 * morphTargetInfluences[ 6 ];
327: 	transformed += morphTarget7 * morphTargetInfluences[ 7 ];
328: 	#endif
329: #endif
330: #ifdef USE_SKINNING
331: 	vec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );
332: 	vec4 skinned = vec4( 0.0 );
333: 	skinned += boneMatX * skinVertex * skinWeight.x;
334: 	skinned += boneMatY * skinVertex * skinWeight.y;
335: 	skinned += boneMatZ * skinVertex * skinWeight.z;
336: 	skinned += boneMatW * skinVertex * skinWeight.w;
337: 	transformed = ( bindMatrixInverse * skinned ).xyz;
338: #endif
339: vec4 mvPosition = vec4( transformed, 1.0 );
340: #ifdef USE_INSTANCING
341: 	mvPosition = instanceMatrix * mvPosition;
342: #endif
343: mvPosition = modelViewMatrix * mvPosition;
344: gl_Position = projectionMatrix * mvPosition;
345: #ifdef USE_LOGDEPTHBUF
346: 	#ifdef USE_LOGDEPTHBUF_EXT
347: 		vFragDepth = 1.0 + gl_Position.w;
348: 		vIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );
349: 	#else
350: 		if ( isPerspectiveMatrix( projectionMatrix ) ) {
351: 			gl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;
352: 			gl_Position.z *= gl_Position.w;
353: 		}
354: 	#endif
355: #endif
356: #if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )
357: 	vec4 worldPosition = vec4( transformed, 1.0 );
358: 	#ifdef USE_INSTANCING
359: 		worldPosition = instanceMatrix * worldPosition;
360: 	#endif
361: 	worldPosition = modelMatrix * worldPosition;
362: #endif
363: #if 0 > 0
364: 	vClipPosition = - mvPosition.xyz;
365: #endif
366: #ifdef USE_ENVMAP
367: 	#ifdef ENV_WORLDPOS
368: 		vWorldPosition = worldPosition.xyz;
369: 	#else
370: 		vec3 cameraToVertex;
371: 		if ( isOrthographic ) { 
372: 			cameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );
373: 		} else {
374: 			cameraToVertex = normalize( worldPosition.xyz - cameraPosition );
375: 		}
376: 		vec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );
377: 		#ifdef ENVMAP_MODE_REFLECTION
378: 			vReflect = reflect( cameraToVertex, worldNormal );
379: 		#else
380: 			vReflect = refract( cameraToVertex, worldNormal, refractionRatio );
381: 		#endif
382: 	#endif
383: #endif
384: #ifdef USE_FOG
385: 	fogDepth = -mvPosition.z;
386: #endif
387: }
388: 
389: 
390: uniform vec2 uTroikaSDFTextureSize;
391: uniform float uTroikaSDFGlyphSize;
392: uniform vec4 uTroikaTotalBounds;
393: uniform vec4 uTroikaClipRect;
394: uniform mat3 uTroikaOrient;
395: uniform bool uTroikaUseGlyphColors;
396: attribute vec4 aTroikaGlyphBounds;
397: attribute float aTroikaGlyphIndex;
398: attribute vec3 aTroikaGlyphColor;
399: varying vec2 vTroikaSDFTextureUV;
400: varying vec2 vTroikaGlyphUV;
401: varying vec3 vTroikaGlyphColor;
402: 
403: vec3 troika_position_3;
404: vec3 troika_normal_3;
405: vec2 troika_uv_3;
406: void troikaVertexTransform3(inout vec3 position, inout vec3 normal, inout vec2 uv) {
407:   
408: vec4 bounds = aTroikaGlyphBounds;
409: vec4 clippedBounds = vec4(
410:   clamp(bounds.xy, uTroikaClipRect.xy, uTroikaClipRect.zw),
411:   clamp(bounds.zw, uTroikaClipRect.xy, uTroikaClipRect.zw)
412: );
413: vec2 clippedXY = (mix(clippedBounds.xy, clippedBounds.zw, position.xy) - bounds.xy) / (bounds.zw - bounds.xy);
414: vTroikaGlyphUV = clippedXY.xy;
415: 
416: float cols = uTroikaSDFTextureSize.x / uTroikaSDFGlyphSize;
417: vTroikaSDFTextureUV = vec2(
418:   mod(aTroikaGlyphIndex, cols) + clippedXY.x,
419:   floor(aTroikaGlyphIndex / cols) + clippedXY.y
420: ) * uTroikaSDFGlyphSize / uTroikaSDFTextureSize;
421: 
422: position.xy = mix(bounds.xy, bounds.zw, clippedXY);
423: 
424: uv = vec2(
425:   (position.x - uTroikaTotalBounds.x) / (uTroikaTotalBounds.z - uTroikaTotalBounds.x),
426:   (position.y - uTroikaTotalBounds.y) / (uTroikaTotalBounds.w - uTroikaTotalBounds.y)
427: );
428: 
429: position = uTroikaOrient * position;
430: normal = uTroikaOrient * normal;
431: 
432: }
433: 
434: void troikaOrigMain3() {
435:   
436: troika_position_1 = vec3(troika_position_3);
437: troika_normal_1 = vec3(troika_normal_3);
438: troika_uv_1 = vec2(troika_uv_3);
439: troikaVertexTransform1(troika_position_1, troika_normal_1, troika_uv_1);
440: 
441: 
442:   troikaOrigMain1();
443:   
444: }
445: void main() {
446:   
447: troika_position_3 = vec3(position);
448: troika_normal_3 = vec3(normal);
449: troika_uv_3 = vec2(uv);
450: troikaVertexTransform3(troika_position_3, troika_normal_3, troika_uv_3);
451: 
452: 
453:   troikaOrigMain3();
454:   
455: }

createDerivedShader: inject fragmentColorTransform before postprocessing chunks

The builtin ThreeJS shaders have a set of fragment shader chunks that perform what could be considered post-processing tasks such as tone mapping, encoding, dithering, etc. Currently the fragmentColorTransform is injected after these chunks, but it would be more appropriate to inject it before those chunks, but after any other gl_FragColor manipulation.

I took an initial swing at it here: 85c71d6 and it works ok in nominal cases but fails when double-deriving due to #include expansion.

Improve text anchoring

For TextMesh and Text3DFacade:

The current anchor property isn't very expressive, and having it be an array can sometimes be hard to work with and generally goes against Troika's convention of facades being simple flat structures.

Here's my initial proposal:

anchorX - can be:

  • a number, interpreted as absolute x units in local space
  • a string percentage, e.g. "75%", interpreted as relative to the total calculated width
  • one of the string keywords "left", "center", or "right"

anchorY - can be:

  • a number, interpreted as absolute y units in local space (negative = down)
  • a string percentage, e.g. "75%", interpreted as relative to the total calculated height
  • one of the string keywords "top", "top-baseline"[1], "middle", "bottom-baseline"[1], or "bottom"

[1] These baseline values could be useful e.g. for the MOZ_text glTF export from Blender.

Something like CSS calc() could also be useful for added expressiveness and control, but that's a bigger lift that could be done in the future.

Double import of three caused by GLTFLoader

We use the GLTFLoader from Three's packaged jsm examples for some of the VR controller models. The GLTFLoader module imports ../../../build/three.module.js which works fine if the application's bundler also resolves three to that file, however some bundlers (e.g. parcel) will choose to load the three.js UMD module file instead, which results in two copies of Three being loaded and the GLTFLoader using different classes than the rest of the app, and causes rendering glitches in the models (perhaps due to instanceof checks or id collisions, not sure exactly.)

What we ideally want is for GLTFLoader to import from the 'three' alias so it will be resolved the same way as all the other imports, whether that's the jsm or umd file. The https://github.com/johh/three-gltf-loader package comes close but uses require('three') instead of an es6 import or umd.

Feature: resolve x/y in text block to string char index

This is a necessary feature for eventually enabling text selection/highlighting/editing.

We need the ability to raycast a position in a rendered text block, and for that position determine the closest character boundary to it, returning an index in the original text string.

It's easy enough to resolve the glyph index, by searching through the glyph rects array, but there isn't a clean way to resolve the glyph index back to string char index. These aren't always 1-to-1 due to ligature substitution etc. This may require modifying Typr.js to persist char indexes with the glyphs.

Raycasting in a web worker

Look into offloading the BoundingSphereOctree into a web worker. This could help with frame rate by removing the raycasting operations from the main thread. It could also pose complications with events and their relation to native events due to being async.

Feature: positional audio

Add an ergonomic API for defining and controlling positional audio. See https://threejs.org/docs/#api/en/audio/PositionalAudio

  • Automatic setup of AudioListener at camera location
  • Attachment of PositionalAudio to Object3DFacade positions, without mucking up relationships with other children (matrix sync rather than adding as child?)
  • Declarative definition of audio file(s) for an object
  • Declarative control of play/pause state
  • Tie in to pointerStates to define sound effects for hover/active
  • Handle browser user-initiated interaction requirement
  • Also allow non-positional
  • Should also work for troika-2d (non-positional only?)

UI block layers: antialiasing creates gaps between sibling blocks

Two blocks adjacent to each other currently have a small visual gap in between them due to the fragment shader antialiasing into the blocks by half a fragment.

We need to be smarter and:

  • Only antialias when it's being clipped along that edge; if it's at the plane edge that will already be antialiased via native webgl multisampling so it's redundant there.
  • When clipped along an edge, antialias outward only (?)
  • Antialiasing on border-radius curves would need to handle both scenarios

XR: configuration of XRInputSourceFacade

The XRInputSourceFacade design is set up to allow overrides to configuration of its grip, targetRay, cursor, etc., but there's not currently an exposed way for authors to define those overrides.

Feature: troika-3d-text: text editing

Following on from cursor positioning and text selection (#27, #25), we need text editing.

The big unknown here is how to input text. On screen it should take input from the keyboard just like HTML inputs. (Maybe use a hidden input/textarea behind the scenes?)

In XR, we may need an in-world keyboard. We could also look into utilizing speech-to-text.

Limit depth of BoundingSphereOctree

Objects with origins extremely near each other currently result in the BoundingSphereOctree we use for raycasting optimization becoming excessively deep. We should implement a minimum leaf size below which no further subdivision happens. This would likely improve performance in many cases, and may avoid some possible issues that could crop up as the node size approaches epsilon.

troika-3d-text standalone with react-native

Hi there,

Great module! Have you considered allowing to use troika-3d-text standalone with react-native? I've tried to use your module via react-three-fiber and https://github.com/react-spring/drei` but I run into the following bundler error: pmndrs/drei#19

It works great with react-native-web, but I think the downloading and building code might be the culprit. Is it possible to generate the sdf assets offline and then pass the resulting asset to troika-3d-text or does this defeat the purpose of the module and I'm better off with three-bmfont-text?

Thanks,

Jun

VR controllers: allow changing the pointing controller

Currently if you have 2 tracked controllers, we naively choose one of them to be the "isPointing" controller that is given a pointer ray and cursor.

We should allow either controller to become the pointer, e.g. by switching isPointing to that controller that last had a button press.

how to position text, f.i upper/left?

demo: https://codesandbox.io/s/r3f-troika-text-l40jo?file=/src/index.js

it seems to behave very strange when i anchor it left/op, then move it via position to minus half the viewports width/height so that it should be exactly at the upper left border.

it makes no sense to me, but this setting makes the text disappear completely:

<Text position={[-viewport.width / 2, 0, 0]}

while adding a tiny digit shows it again:

<Text position={[-viewport.width / 2.1, 0, 0]}

same happens at the top, the offsets that are 100% correct but a clean width / 2 makes the text go away.

it looks to me like something in the shader is clipping it incorrectly.

Promote standalone TextMesh to its own package

Now that the standalone TextMesh is being used by several third party ThreeJS frameworks and projects, the fact that those users have to explicitly import from a secondary file in the dist directory rather than just the package name feels ... wrong.

I'm proposing to add a new package that will hold all the code currently used by the standalone build, and the existing troika-3d-text will reference that and just add the extra stuff related to usage in the Troika framework. This will be a breaking change for projects using it, but that will be the case at some point regardless so sooner is better IMO.

Possible names for the new package:

  • troika-three-text
  • troika-three-textmesh
  • ...?

I'm open to renaming any exports as well ... TextMesh -> Text perhaps?

Automatic injection of webxr-polyfill

Followup from #11 -- The initial WebXR implementation does not do any polyfilling on its own, requiring authors to load and initialize the webxr-polyfill. We need to decide if that's acceptable or if we want to try injecting it automatically when applicable. My hope is that the polyfill will become obsolete fairly soon anyway as true WebXR implementations roll out to greenfield browsers, but in the short term it's still a necessary tool.

Also should look into a custom webxr-polyfill build that excludes the Cardboard device emulation.

Feature: textOverflow and ellipsis support

When overflowing the maxWidth and wrapping is disabled, we should support truncation of the text with or without ellipsis. Ideally we'd support everything in the CSS text-overflow property. We may also want to expose ellipsis in the middle, though there's no CSS precedent to match for that.

Instancing the opacity uniform gives console warning

As of version 0.21.0, using Instanceable3DFacade with material.instanceUniforms = ['opacity'] and castShadow:true results in the following console warning:

three.module.js:18347 THREE.WebGLProgram: gl.getProgramInfoLog() WARNING: Output of vertex shader 'troika_vary_opacity' not read by fragment shader

This is because the DepthMaterial fragment shader declares the opacity uniform, so InstancingManager creates a varying for it in both the vertex and fragment shaders; however that uniform and its usages are within #if conditional directives that are compiled out of the fragment shader, leaving it in only the vertex shader and producing this warning.

This is a low impact edge case but it would be nice to find a way to eliminate the warning.

[request] number of segments

I would like to configure the number of segments of the surface plane, similar to

new PlaneBufferGeometry(1, 1, 32, 32) // 32 x 32 segments/vertices

so that we can warp the surface like so: https://codesandbox.io/s/r3f-drei-meshwobblematerial-g5373

currently if i pass that wobbly material to troika it behaves very rigid: https://codesandbox.io/s/r3f-drei-meshwobblematerial-rwmrx i guess that's because the surface behaves like a plane with 1x1 segments.

can this be done somehow?

VR controllers: expose individual buttons/axes

We currently map buttons to mouse-ish events, and the stick axis to wheel events. That's decent for simple cases but authors will want to be able to handle specific buttons/axes in custom ways. We need to design an interface for doing that.

Build error in FontProcessor

When I build with create-react-app I get the error ReferenceError: s is not defined with the FontProcessor file. I suspect something is breaking during minification?


EDIT: Looks like textmesh-standalone.esm.js gets this error, however, textmesh-standalone.umd.js works fine. Quite possibly the same issue with codesandbox?

Experiment: flattened ThreeJS scene graph

Currently the Object3DFacade tree is mirrored directly in a ThreeJS Object3D tree of the same structure. This isn't a necessity, however; it's possible that using a simpler flattened ThreeJS scene could bring some advantages, such as:

  • Simpler and less error-prone optimization of non-renderable objects (e.g. Group) out of the scene graph
  • Faster frustum culling by prefiltering using the BoundingSphereOctree
  • Faster traversal of the tree in Three's WebGLRenderer.render() pass

Potential gotchas:

  • Object3D's 'visible' property's deep behavior

UI block layers: renderOrder does not play well with rest of scene

The UIBlockLayer3DFacade impl for backgrounds and borders relies on renderOrder to ensure correct transparency blending in deep UI trees. This doesn't play well with other objects in the scene, however, requiring the author to manually set renderOrder for any other objects that may overlap in front of or behind the UI panes.

Let's try to find a better way to handle background/border layering that allows z-sorting to still work like normal.

Instanceables do not honor inherited visibility

If an Instanceable3DFacade is a descendant of an object which is set to visible=false, the instance is still rendered. We need to handle inherited visibility on instanceables just like normal meshes do.

WebXR support

We should convert all the existing WebVR code in the troika-xr module to use the WebXR API, now that it has reached a stable vr-complete milestone. This will help smooth out many of the rough edges in the current impl and give us a much better base on which to build.

In the short term we'll probably want to polyfill it, which will depend on the webxr-polyfill finishing its update to the milestone API. Watch immersive-web/webxr-polyfill#51 for that status.

Feature: troika-3d-text: support for WOFF2

Support for WOFF2 font files was initially omitted, due to the requirement of a JS-executable Brotli decompressor. If we were to add one, it should otherwise work.

The big issue with Brotli implementations is their relatively large file size. This appears to be due to the fact that the Brotli algorithm specifies a built-in dictionary of common terms which itself is most of the file size -- 9K+ items.

In some scenarios the extra size may be outweighed by the decreased size of the font files, especially if using several different fonts or if the surrounding page is already using WOFF2 fonts in its CSS. But I don't think I'd want to include it by default, so this might be an option users could set that would point to a file holding the Brotli code.


For reference, a webpacked build of decompress.js at https://github.com/foliojs/brotli.js weighs in at about 92K / 60K gzipped. This is the smallest impl I've seen so far due to its approach of brotli-compressing the dictionary itself.

Feature: preloader function for fonts and SDF generation

Font download and SDF generation is currently lazy, waiting until a TextMesh is first created and sync()-ed. This can cause a noticeable period of blankness when a scene is first rendered.

We should add a function that users can call to preload a font and optionally generate SDF for a set of initial glyphs. It would take a callback that is invoked when the font file is loaded, parsed, and the SDFs are ready. It would then be ready for TextMeshes to use without any noticeable delay.

Proposal:

import {preloadFont} from 'troika-3d-text'
showLoadingScreen()
preloadFont('path/to/font.woff', 'abcdefghijklmnopqrstuvwxyz', () => {
  showScene()
})

Text: allow configuring SDF resolution per object

Currently, the sdfGlyphSize can only be configured once globally. It would be useful to allow configuring this on a per-object basis. For example some fonts with very fine details need a larger SDF resolution than others, and some text objects that will be displayed very large could use sharper corners than those that will be displayed small.

troika-3d-text request: possibility to define the axis of the text

Hi!

We already used troika-3d-text in our latest demos (https://blog.mozvr.com) and it's amazing. Great job!

I'm currently working on a glTF extension to include text without exporting the mesh but only the metadata needed for recreating it in realtime. My specific goal is to create text in Blender, export the scene to glTF, and recreate the text in realtime in threejs with the same look as in Blender, using troika-3d-text.

But I have a problem: a text in Blender with zero rotation is laid down on the floor, whereas a text in troika-3d-text with zero rotation is standing up, facing Z.

I tried different solutions for this, but none works completely. The best option would be to ask troika-3d-text to create the text "laid down on the floor", in the XZ plane. Do you think it could be possible to add a parameter in the constructor, or a property of the text, to specify the desired plane where the text is created? something like text.axis or text.normal, with the possible string values +x -x +y -y +z -z, specifying where the text is facing on creation time.

I don't know if that would break things. I this case, maybe you could give me some hints about where to hack the library to achieve the desired result. I've been trying but honestly I'm a bit lost on how it could be the best way to do it.

This would be incredibly useful to match Blender and troika-3d-text. Thanks!

Extract FlexLayoutProcessor to a standalone module

Other folks are wanting to do flexbox layout using Troika's text implementation, but they run into the same problems I did for troika-3d-ui with the async nature of the text layout and Yoga's need for measureText to be synchronous.

It occured to me that FlexLayoutProcessor.js is very close to being standalone, and could be exposed on its own to let people just call requestFlexLayout() with their own style tree. It would run in the worker alongside the text processor, have access to text measurement, and users could apply the result to their main thread objects.

I'm thinking this has a dependency on #47, and we'd create a new package troika-flexbox-layout or similar that just has the flexbox processing code and no other troika framework deps.

new Function causes Content Security Policy failure

Use of new Function in troika/packages/troika-worker-utils/src/WorkerModules.js (v0.20.0) causes CSP failure:

"Uncaught EvalError: Refused to evaluate a string as JavaScript because
'unsafe-eval' is not an allowed source of script in the following
Content Security Policy directive"

It requires content security directive script-src 'unsafe-eval'.

Would there be a safer alternative for new Function so that there would be no need to allow this unsafe functionality in policy?

Feature: common scene tools for XR

Build some out-of-the-box facades/utilities for common needs in XR scenes. Some ideas:

  • Floor plane / ceiling plane
  • Utility for keeping objects at reachable height when sitting vs standing
  • Teleport locomotion
  • View angle snap-rotation
  • UI/text that follows user's view and stays angled appropriately
  • Callouts for controller buttons to describe their behavior
  • Standard controls for controlling immersive session from inside (volume/mute, exit VR, etc.)
  • Grab interactions: target-and-pull, collision-based

We can break these out to separate issues as needed.

troika seems to break SSR

pmndrs/drei#25

pmndrs/drei#23

it seems to rely on document, which does not exist in node.

i believe in order to make troika pseudo-isomorphic all it would have to do is:

const linkEl = typeof document !== 'undefined' && document.createElement('a')

this would allow ssr to function normally.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.