1
- import React , { useMemo , useCallback } from 'react'
2
- import { useLoader , useThree } from 'react-three-fiber'
3
- import * as THREE from 'three'
1
+ import React , { useMemo , useRef } from 'react'
2
+ import { useLoader , useThree , useFrame } from 'react-three-fiber'
3
+ import {
4
+ Vector3 ,
5
+ Font ,
6
+ TextureLoader ,
7
+ Color ,
8
+ DoubleSide ,
9
+ Quaternion ,
10
+ } from 'three'
4
11
import TextGeometry from './TextGeometry'
5
12
import robotoFont from './fonts/roboto/Roboto-Regular.json'
6
13
import robotoTexture from './fonts/roboto/Roboto-Regular.png'
@@ -59,7 +66,7 @@ export const Text = ({
59
66
} ) => {
60
67
// Font Data
61
68
const font = useMemo ( ( ) => {
62
- return new THREE . Font ( fontData )
69
+ return new Font ( fontData )
63
70
} , [ fontData ] )
64
71
65
72
const hasBackground = useMemo ( ( ) => backgroundAlpha > 0 , [ backgroundAlpha ] )
@@ -69,15 +76,15 @@ export const Text = ({
69
76
] )
70
77
71
78
// Texture Data
72
- const texture = useLoader ( THREE . TextureLoader , textureData )
79
+ const texture = useLoader ( TextureLoader , textureData )
73
80
74
81
// Uniforms for shader
75
82
const uniforms = useMemo ( ( ) => {
76
- const textColorArray = new THREE . Color ( textColor ) . toArray ( )
83
+ const textColorArray = new Color ( textColor ) . toArray ( )
77
84
textColorArray . push ( textAlpha )
78
- const borderColorArray = new THREE . Color ( borderColor ) . toArray ( )
85
+ const borderColorArray = new Color ( borderColor ) . toArray ( )
79
86
borderColorArray . push ( borderAlpha )
80
- const backgroundColorArray = new THREE . Color ( backgroundColor ) . toArray ( )
87
+ const backgroundColorArray = new Color ( backgroundColor ) . toArray ( )
81
88
backgroundColorArray . push ( backgroundAlpha )
82
89
83
90
const uniforms = {
@@ -104,77 +111,94 @@ export const Text = ({
104
111
] )
105
112
106
113
// Retrieve the viewport from the rendering engine
107
- const { viewport } = useThree ( )
108
-
109
- // Calculate the scale of the font using the viewport factor
110
- const scale = useMemo ( ( ) => {
111
- const view = 1 / viewport . factor
112
- return ( view / font . data . info . size ) * fontSize
113
- } , [ font . data . info . size , fontSize , viewport . factor ] )
114
+ const { camera, size } = useThree ( )
114
115
115
116
// Calculate the desired width of the text (for wrapping) based on the "width" prop (percentage of the screen width)
116
117
const adjustedTextWidth = useMemo ( ( ) => {
117
- return ( ( viewport . width / scale ) * width ) / 100
118
- } , [ scale , viewport . width , width ] )
118
+ return ( size . width * font . data . info . size * ( 1 / fontSize ) * width ) / 100
119
+ } , [ size . width , font . data . info . size , fontSize , width ] )
119
120
120
121
// Create userData based on the text so that the screen will update if the text changes
121
122
const userData = useMemo ( ( ) => {
122
123
return { text }
123
124
} , [ text ] )
124
125
126
+ // Capture the camera postion so we can orient the txt towards it
127
+ const cameraPosition = useMemo ( ( ) => {
128
+ const vec = new Vector3 ( )
129
+ camera . getWorldPosition ( vec )
130
+ return vec
131
+ } , [ camera ] )
132
+
133
+ const worldQuaternion = useMemo ( ( ) => new Quaternion ( ) )
134
+
135
+ const meshRef = useRef ( )
136
+
125
137
// Called whenever the mesh updates. Here we calculate and set the postion of the text.
126
- const update = useCallback (
127
- ( self ) => {
128
- const box = self . geometry . boundingBox
129
- const sphere = self . geometry . boundingSphere
130
-
131
- const anchorOffset = {
132
- x :
133
- anchorHorz === LEFT
134
- ? - box . min . x
135
- : anchorHorz === CENTER
136
- ? - sphere . center . x
137
- : anchorHorz === RIGHT
138
- ? - box . max . x
139
- : 0 ,
140
- y :
141
- anchorVert === TOP
142
- ? box . min . y
143
- : anchorVert === CENTER
144
- ? sphere . center . y
145
- : anchorVert === BOTTOM
146
- ? box . max . y
147
- : 0 ,
148
- }
149
-
150
- const placementOffset = {
151
- x : ( viewport . width * positionHorz ) / 100 - viewport . width / 2 ,
152
- y : viewport . height / 2 - ( viewport . height * positionVert ) / 100 ,
153
- }
154
-
155
- const position = [
156
- anchorOffset . x * scale + placementOffset . x ,
157
- anchorOffset . y * scale + placementOffset . y ,
158
- 0 ,
159
- ]
160
-
161
- self . scale . set ( scale , scale , scale )
162
- self . position . set ( ...position )
163
- self . rotation . set ( Math . PI , 0 , 0 )
164
- } ,
165
- [
166
- anchorHorz ,
167
- anchorVert ,
168
- positionHorz ,
169
- positionVert ,
170
- scale ,
171
- viewport . height ,
172
- viewport . width ,
138
+ useFrame ( ( ) => {
139
+ const self = meshRef . current
140
+
141
+ const box = self . geometry . boundingBox
142
+ const sphere = self . geometry . boundingSphere
143
+
144
+ const anchorOffset = {
145
+ x :
146
+ anchorHorz === LEFT
147
+ ? - box . min . x
148
+ : anchorHorz === CENTER
149
+ ? - sphere . center . x
150
+ : anchorHorz === RIGHT
151
+ ? - box . max . x
152
+ : 0 ,
153
+ y :
154
+ anchorVert === TOP
155
+ ? box . min . y
156
+ : anchorVert === CENTER
157
+ ? sphere . center . y
158
+ : anchorVert === BOTTOM
159
+ ? box . max . y
160
+ : 0 ,
161
+ }
162
+
163
+ const worldPosition = self . getWorldPosition ( self . position )
164
+
165
+ const aspect = size . width / size . height
166
+ const distance = cameraPosition . distanceTo ( worldPosition )
167
+ const fov = ( camera . fov * Math . PI ) / 180 // convert vertical fov to radians
168
+ const viewHeight = 2 * Math . tan ( fov / 2 ) * distance // visible
169
+ const viewWidth = viewHeight * aspect
170
+ const factor = size . width / viewWidth
171
+ const scale = fontSize / ( factor * font . data . info . size )
172
+
173
+ const placementOffset = {
174
+ x : ( viewWidth * positionHorz ) / 100 - viewWidth / 2 ,
175
+ y : viewHeight / 2 - ( viewHeight * positionVert ) / 100 ,
176
+ }
177
+
178
+ const position = [
179
+ anchorOffset . x * scale + placementOffset . x ,
180
+ anchorOffset . y * scale + placementOffset . y ,
181
+ 0 ,
173
182
]
174
- )
183
+
184
+ const upright = new Quaternion ( ) . setFromAxisAngle (
185
+ new Vector3 ( 1 , 0 , 0 ) ,
186
+ Math . PI
187
+ )
188
+
189
+ const rotation = self . parent
190
+ . getWorldQuaternion ( worldQuaternion )
191
+ . conjugate ( )
192
+ . multiply ( camera . quaternion )
193
+ . multiply ( upright )
194
+
195
+ self . position . set ( ...position )
196
+ self . setRotationFromQuaternion ( rotation )
197
+ self . scale . set ( scale , scale , scale )
198
+ } )
175
199
176
200
return (
177
- < mesh name = 'Text' onUpdate = { update } userData = { userData } >
201
+ < mesh name = 'Text' ref = { meshRef } userData = { userData } >
178
202
< TextGeometry
179
203
attach = 'geometry'
180
204
text = { text }
@@ -190,6 +214,7 @@ export const Text = ({
190
214
< shaderMaterial
191
215
attach = 'material'
192
216
depthTest = { depthTest }
217
+ side = { DoubleSide }
193
218
args = { [
194
219
{
195
220
transparent : true ,
0 commit comments