diff --git a/packages/troika-3d-text/src/facade/Text3DFacade.js b/packages/troika-3d-text/src/facade/Text3DFacade.js
index ad9e8a31..6c7e8e03 100644
--- a/packages/troika-3d-text/src/facade/Text3DFacade.js
+++ b/packages/troika-3d-text/src/facade/Text3DFacade.js
@@ -40,6 +40,7 @@ export const TEXT_MESH_PROPS = [
'sdfGlyphSize',
'unicodeFontsURL',
'gpuAccelerateSDF',
+ 'usePremultipliedAlpha',
'debugSDF'
]
diff --git a/packages/troika-3d/src/facade/World3DFacade.js b/packages/troika-3d/src/facade/World3DFacade.js
index b770eff0..a6d17a73 100644
--- a/packages/troika-3d/src/facade/World3DFacade.js
+++ b/packages/troika-3d/src/facade/World3DFacade.js
@@ -1,5 +1,5 @@
import { WorldBaseFacade, utils } from 'troika-core'
-import { WebGLRenderer, Raycaster, Color, Vector2, Vector3, LinearEncoding, NoToneMapping } from 'three'
+import { WebGLRenderer, Raycaster, Color, Vector2, Vector3, LinearSRGBColorSpace, NoToneMapping } from 'three'
import Scene3DFacade from './Scene3DFacade.js'
import {PerspectiveCamera3DFacade} from './Camera3DFacade.js'
import {BoundingSphereOctree} from '../BoundingSphereOctree.js'
@@ -53,7 +53,7 @@ class World3DFacade extends WorldBaseFacade {
this._bgColor = backgroundColor
}
- renderer.outputEncoding = this.outputEncoding || LinearEncoding
+ renderer.outputEncoding = this.outputEncoding || LinearSRGBColorSpace
renderer.toneMapping = this.toneMapping || NoToneMapping
// Update render canvas size
diff --git a/packages/troika-examples/Geist-Regular.ttf b/packages/troika-examples/Geist-Regular.ttf
new file mode 100644
index 00000000..e63cb785
Binary files /dev/null and b/packages/troika-examples/Geist-Regular.ttf differ
diff --git a/packages/troika-examples/package.json b/packages/troika-examples/package.json
index b0e7c625..af9652ba 100644
--- a/packages/troika-examples/package.json
+++ b/packages/troika-examples/package.json
@@ -21,7 +21,7 @@
"react": "^16.14.0",
"react-dat-gui": "^4.0.0",
"react-dom": "^16.14.0",
- "three": "^0.149.0",
+ "three": "^0.180.0",
"three-instanced-uniforms-mesh": "^0.52.4",
"three-line-2d": "^1.1.6",
"troika-2d": "^0.52.0",
diff --git a/packages/troika-examples/text-batched/BatchedTextExample.jsx b/packages/troika-examples/text-batched/BatchedTextExample.jsx
index f7797c57..21ce1814 100644
--- a/packages/troika-examples/text-batched/BatchedTextExample.jsx
+++ b/packages/troika-examples/text-batched/BatchedTextExample.jsx
@@ -1,88 +1,148 @@
import React from "react";
+import { BoxGeometry, Mesh, MeshBasicMaterial, MeshStandardMaterial } from "three";
import { Canvas3D } from "troika-3d";
import { Text3DFacade, BatchedText3DFacade } from "troika-3d-text";
-import { FONTS } from '../text/TextExample'
-import { Color } from "three/src/math/Color";
+import { Object3DFacade } from "../../troika-3d/src/index";
-export default function BatchedTextExample ({ stats, width, height }) {
- const [texts, setTexts] = React.useState(randomizeText());
+// Define the source text array once, so both 3D and SVG can use it
+const words = [
+ "One", "Two", "Three", "Four", "Five",
+ "Six", "Seven", "Eight", "Nine", "Ten",
+ "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen",
+ "Sixteen", "Seventeen", "Eighteen", "Nineteen", "Twenty"
+];
- function randomizeText() {
- const all = [
- "One", "Two", "Three", "Four", "Five",
- "Six", "Seven", "Eight", "Nine", "Ten",
- "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen",
- "Sixteen", "Seventeen", "Eighteen", "Nineteen", "Twenty"
- ]
- const subset = all.slice(0, Math.max(16, Math.floor(Math.random() * all.length)))
- return subset.map((text, i) => ({
- facade: Text3DFacade,
- text,
- font: Object.values(FONTS)[i % Object.values(FONTS).length],
- fontSize: randRange(0.05, 0.25),
- x: randRange(-1, 1),
- y: randRange(-1, 1),
- z: randRange(-1, 1),
- rotateZ: randRange(-0.01, 0.01),
- anchorX: "50%",
- anchorY: "50%",
- color: randColor(),
- // fillOpacity: Math.random() < 0.5 ? 0.1 : 1,
- // strokeWidth: Math.random() < 0.5 ? '3%' : 0,
- // strokeColor: randColor(),
- outlineWidth: Math.random() < 0.3 ? '10%' : 0,
- // outlineBlur: Math.random() < 0.3 ? '20%' : 0,
- // outlineColor: randColor(),
- // outlineOpacity: Math.random(),
- // clipRect: Math.random() < 0.5 ? [0, 0, 999, 999] : null,
- // curveRadius: Math.random() < 0.5 ? 0.1 : 0,
- animation: {
- from: { rotateY: 0 },
- to: { rotateY: Math.PI * 2 },
- duration: randRange(800, 3000),
- iterations: Infinity
- }
- }))
- }
+// This function generates the properties for the 3D text
+function generate3DTextLayout(premultiply) {
+ const startY = 1.5;
+ const yStep = 0.15;
+ const startFontSize = 0.3;
+ const fontSizeStep = 0.012;
- return
- {
- setTexts(randomizeText())
- }}
- camera={{
- fov: 75,
- aspect: width / height,
- x: 0,
- y: 0,
- z: 2.5
- }}
- objects={[
- {
- facade: BatchedText3DFacade,
- children: texts,
- animation: {
- from: { rotateY: 0 },
- to: { rotateY: Math.PI * 2 },
- duration: 10000,
- iterations: Infinity
- }
- }
- ]}
- />
-
;
+ return words.map((text, i) => ({
+ facade: Text3DFacade,
+ text,
+ font: '/Geist-Regular.ttf',
+ fontSize: Math.max(0.02, startFontSize - i * fontSizeStep),
+ anchorX: "50%",
+ anchorY: "50%",
+ x: premultiply ? 1 : -1,
+ y: startY - i * yStep,
+ z: 0,
+ // color: 0xc4c4c4,
+ // color: 0xd0d0d0,
+ color: 0x808080,
+ fillOpacity: 0.5,
+ // fillOpacity: 0.9375,
+ outlineWidth: '10%',
+ outlineColor: 0x121212,
+ outlineOpacity: 1,
+ usePremultipliedAlpha: !!premultiply,
+ }));
}
-function randRange (min, max) {
- return min + Math.random() * (max - min);
-}
-function randColor() {
- return new Color().setHSL(Math.random(), 1, 0.5)
+class BackgroundTest extends Object3DFacade {
+ initThreeObject() {
+ return new Mesh(
+ new BoxGeometry(10,1,1),
+ new MeshBasicMaterial({
+ color: 0xff0000,
+ })
+ )
+ }
}
-// function randFromArray(array) {
-// return array[Math.floor(Math.random() * array.length)];
-// }
+
+
+export default function BatchedTextExample({ stats, width, height }) {
+ const [texts3D,setTexts] = React.useState(generate3DTextLayout());
+ const [premultipliedTexts3D,setPremultipliedTexts] = React.useState(generate3DTextLayout(true));
+
+ console.log('texts3D', texts3D)
+
+ // --- SVG Text Logic ---
+ // Define layout parameters suitable for SVG pixels
+ const svgStartY = 60; // Starting Y position in pixels
+ const svgYStep = 20; // Gap between lines in pixels
+ const svgStartFontSize = 32; // Starting font size in pixels
+ const svgFontSizeStep = 1; // How much to decrease font size (px)
+
+ // Generate the properties for the SVG text elements
+ const svgTexts = words.map((text, i) => ({
+ text,
+ y: svgStartY + i * svgYStep,
+ fontSize: Math.max(8, svgStartFontSize - i * svgFontSizeStep),
+ }));
+
+ return (
+ // Main container using Flexbox for a side-by-side layout
+
+
+ {/* Add a style tag to load the custom font for the SVG */}
+
+
+ {/* 1. 3D Canvas Container (takes up the left 50%) */}
+
+ {
+ setTexts(generate3DTextLayout());
+ setPremultipliedTexts(generate3DTextLayout(true));
+ }}
+ camera={{ fov: 75, aspect: (width / 2) / height, x: 0, y: 0, z: 2.5 }}
+ objects={[
+ {
+ facade: BatchedText3DFacade,
+ children: texts3D,
+ },
+ {
+ facade: BatchedText3DFacade,
+ children: premultipliedTexts3D,
+ },
+ {
+ key: 'jnjn',
+ facade: BackgroundTest,
+ receiveShadow: true,
+ scale: 1,
+ z:-1,
+ y:-1.4,
+ }
+ ]}
+ />
+
+
+ {/* 2. SVG Container (takes up the right 50%) */}
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/packages/troika-three-text/src/BatchedText.js b/packages/troika-three-text/src/BatchedText.js
index f81c90f2..b6306b53 100644
--- a/packages/troika-three-text/src/BatchedText.js
+++ b/packages/troika-three-text/src/BatchedText.js
@@ -27,6 +27,7 @@ Data texture packing strategy:
30: uTroikaStrokeOpacity
# Outline:
+27: usePremultipliedAlpha (0/1)
28-29: uTroikaPositionOffset
30: uTroikaEdgeOffset
31: uTroikaBlurRadius
@@ -210,6 +211,7 @@ export class BatchedText extends Text {
uTroikaStrokeOpacity,
uTroikaFillOpacity,
uTroikaCurveRadius,
+ uUsePremultipliedAlpha
} = material.uniforms;
// Total bounds for uv
@@ -234,6 +236,7 @@ export class BatchedText extends Text {
// Curve radius
setTexData(startIndex + 26, uTroikaCurveRadius.value)
+ setTexData(startIndex + 27, uUsePremultipliedAlpha.value);
if (isOutline) {
// Outline properties
@@ -415,6 +418,7 @@ function createBatchedTextMaterial (baseMaterial) {
'uTroikaStrokeOpacity',
'uTroikaFillOpacity',
'uTroikaCurveRadius',
+ 'uUsePremultipliedAlpha',
'diffuse'
]
varyingUniforms.forEach(uniformName => {
@@ -444,6 +448,7 @@ function createBatchedTextMaterial (baseMaterial) {
diffuse = troikaFloatToColor(data.x);
uTroikaFillOpacity = data.y;
uTroikaCurveRadius = data.z;
+ uUsePremultipliedAlpha = data.w;
data = troikaBatchTexel(7.0);
if (uTroikaIsOutline) {
diff --git a/packages/troika-three-text/src/Text.js b/packages/troika-three-text/src/Text.js
index b4b9977d..112a2f35 100644
--- a/packages/troika-three-text/src/Text.js
+++ b/packages/troika-three-text/src/Text.js
@@ -7,6 +7,10 @@ import {
PlaneGeometry,
Vector3,
Vector2,
+ CustomBlending,
+ OneFactor,
+ OneMinusSrcAlphaFactor,
+ NormalBlending,
} from 'three'
import { GlyphsGeometry } from './GlyphsGeometry.js'
import { createTextDerivedMaterial } from './TextDerivedMaterial.js'
@@ -398,6 +402,12 @@ class Text extends Mesh {
*/
this.gpuAccelerateSDF = true
+ /**
+ * @member {boolean} usePremultipliedAlpha
+ * TODO
+ **/
+ this.usePremultipliedAlpha = true
+
this.debugSDF = false
}
@@ -638,7 +648,15 @@ class Text extends Mesh {
}
fillOpacity = this.fillOpacity
}
-
+ if(this.usePremultipliedAlpha){
+ material.premultipliedAlpha = true
+ material.blending = CustomBlending;
+ material.blendSrc = OneFactor; // Use the source color as-is
+ material.blendDst = OneMinusSrcAlphaFactor;
+ }else{
+ material.blending = NormalBlending
+ }
+
uniforms.uTroikaEdgeOffset.value = distanceOffset
uniforms.uTroikaPositionOffset.value.set(offsetX, offsetY)
uniforms.uTroikaBlurRadius.value = blurRadius
@@ -663,6 +681,7 @@ class Text extends Mesh {
this.geometry.applyClipRect(uniforms.uTroikaClipRect.value)
}
uniforms.uTroikaSDFDebug.value = !!this.debugSDF
+ uniforms.uUsePremultipliedAlpha.value = this.usePremultipliedAlpha ? 1 : 0
material.polygonOffset = !!this.depthOffset
material.polygonOffsetFactor = material.polygonOffsetUnits = this.depthOffset || 0
diff --git a/packages/troika-three-text/src/TextDerivedMaterial.js b/packages/troika-three-text/src/TextDerivedMaterial.js
index 576de108..73ed330c 100644
--- a/packages/troika-three-text/src/TextDerivedMaterial.js
+++ b/packages/troika-three-text/src/TextDerivedMaterial.js
@@ -84,6 +84,7 @@ uniform vec3 uTroikaStrokeColor;
uniform float uTroikaStrokeWidth;
uniform float uTroikaStrokeOpacity;
uniform bool uTroikaSDFDebug;
+uniform float uUsePremultipliedAlpha;
varying vec2 vTroikaGlyphUV;
varying vec4 vTroikaTextureUVBounds;
varying float vTroikaTextureChannel;
@@ -195,9 +196,11 @@ gl_FragColor = mix(fillRGBA, strokeRGBA, smoothstep(
));
gl_FragColor.a *= edgeAlpha;
#endif
-
+if(uUsePremultipliedAlpha == 1.0){
+ gl_FragColor.rgb *= gl_FragColor.a;
+}
if (edgeAlpha == 0.0) {
- discard;
+ discard;
}
`
@@ -228,7 +231,8 @@ export function createTextDerivedMaterial(baseMaterial) {
uTroikaStrokeOpacity: {value: 1},
uTroikaOrient: {value: new Matrix3()},
uTroikaUseGlyphColors: {value: true},
- uTroikaSDFDebug: {value: false}
+ uTroikaSDFDebug: {value: false},
+ uUsePremultipliedAlpha: {value: 0}
},
vertexDefs: VERTEX_DEFS,
vertexTransform: VERTEX_TRANSFORM,