@vyr/three 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +26 -0
- package/src/actor/ComposerServiceActor.ts +107 -0
- package/src/actor/GeometryActor.ts +13 -0
- package/src/actor/HTMLConvertActor.ts +55 -0
- package/src/actor/MaterialActor.ts +13 -0
- package/src/actor/NodeActor.ts +25 -0
- package/src/actor/OrbitControllerActor.ts +110 -0
- package/src/actor/PassActor.ts +24 -0
- package/src/actor/SceneServiceActor.ts +122 -0
- package/src/actor/TextureActor.ts +13 -0
- package/src/actor/TransformControllerActor.ts +23 -0
- package/src/actor/index.ts +10 -0
- package/src/asset/index.ts +187 -0
- package/src/controls/CameraControls.ts +2360 -0
- package/src/controls/TransformControls.ts +1747 -0
- package/src/controls/index.ts +2 -0
- package/src/descriptor/ComposerServiceDescriptor.ts +47 -0
- package/src/descriptor/GeoMapDescriptor.ts +24 -0
- package/src/descriptor/HTMLConvertDescriptor.ts +12 -0
- package/src/descriptor/InstancedMeshDescriptor.ts +21 -0
- package/src/descriptor/MeshDescriptor.ts +16 -0
- package/src/descriptor/ModelDescriptor.ts +15 -0
- package/src/descriptor/OrbitControllerDescriptor.ts +84 -0
- package/src/descriptor/OrthographicCameraDescriptor.ts +12 -0
- package/src/descriptor/ParticleDescriptor.ts +88 -0
- package/src/descriptor/PassDescriptor.ts +33 -0
- package/src/descriptor/PerspectiveCameraDescriptor.ts +15 -0
- package/src/descriptor/PointsDescriptor.ts +16 -0
- package/src/descriptor/SceneServiceDescriptor.ts +39 -0
- package/src/descriptor/SpriteDescriptor.ts +16 -0
- package/src/descriptor/TextDescriptor.ts +41 -0
- package/src/descriptor/TransformControllerDescriptor.ts +32 -0
- package/src/descriptor/animation/AnimationActionDescriptor.ts +52 -0
- package/src/descriptor/geometry/BoxGeometryDescriptor.ts +26 -0
- package/src/descriptor/geometry/BufferGeometryDescriptor.ts +15 -0
- package/src/descriptor/geometry/CircleGeometryDescriptor.ts +22 -0
- package/src/descriptor/geometry/CylinderGeometryDescriptor.ts +30 -0
- package/src/descriptor/geometry/ExtrudeGeometryDescriptor.ts +35 -0
- package/src/descriptor/geometry/GeometryDescriptor.ts +8 -0
- package/src/descriptor/geometry/PlaneGeometryDescriptor.ts +22 -0
- package/src/descriptor/geometry/RingGeometryDescriptor.ts +26 -0
- package/src/descriptor/geometry/SphereGeometryDescriptor.ts +27 -0
- package/src/descriptor/geometry/SurfaceGeometryDescriptor.ts +15 -0
- package/src/descriptor/geometry/TubeGeometryDescriptor.ts +25 -0
- package/src/descriptor/helper/AxesHelperDescriptor.ts +8 -0
- package/src/descriptor/index.ts +45 -0
- package/src/descriptor/light/AmbientLightDescriptor.ts +8 -0
- package/src/descriptor/light/DirectionalLightDescriptor.ts +33 -0
- package/src/descriptor/light/HemisphereLightDescriptor.ts +16 -0
- package/src/descriptor/light/LightDescriptor.ts +16 -0
- package/src/descriptor/light/PointLightDescriptor.ts +24 -0
- package/src/descriptor/light/RectAreaLightDescriptor.ts +20 -0
- package/src/descriptor/light/SpotLightDescriptor.ts +30 -0
- package/src/descriptor/material/MaterialDescriptor.ts +84 -0
- package/src/descriptor/material/MeshBasicMaterialDescriptor.ts +53 -0
- package/src/descriptor/material/MeshPhongMaterialDescriptor.ts +102 -0
- package/src/descriptor/material/MeshStandardMaterialDescriptor.ts +99 -0
- package/src/descriptor/material/PointsMaterialDescriptor.ts +31 -0
- package/src/descriptor/material/ShaderMaterialDescriptor.ts +35 -0
- package/src/descriptor/material/ShadowMaterialDescriptor.ts +19 -0
- package/src/descriptor/material/SpriteMaterialDescriptor.ts +31 -0
- package/src/descriptor/texture/TextureDescriptor.ts +110 -0
- package/src/index.ts +9 -0
- package/src/interpreter/ComposerServiceInterpreter.ts +25 -0
- package/src/interpreter/GeoMapInterpreter.ts +253 -0
- package/src/interpreter/HTMLConvertInterpreter.ts +31 -0
- package/src/interpreter/InstancedMeshInterpreter.ts +76 -0
- package/src/interpreter/MeshInterpreter.ts +25 -0
- package/src/interpreter/ModelInterpreter.ts +61 -0
- package/src/interpreter/NodeInterpreter.ts +65 -0
- package/src/interpreter/OrbitControllerInterpreter.ts +47 -0
- package/src/interpreter/OrthographicCameraInterpreter.ts +13 -0
- package/src/interpreter/ParticleInterpreter.ts +221 -0
- package/src/interpreter/PassInterpreter.ts +43 -0
- package/src/interpreter/PerspectiveCameraInterpreter.ts +33 -0
- package/src/interpreter/PointsInterpreter.ts +61 -0
- package/src/interpreter/SceneServiceInterpreter.ts +119 -0
- package/src/interpreter/ServiceSchedulerInterpreter.ts +23 -0
- package/src/interpreter/SpriteInterpreter.ts +45 -0
- package/src/interpreter/TextInterpreter.ts +76 -0
- package/src/interpreter/TransformControllerInterpreter.ts +44 -0
- package/src/interpreter/animation/AnimationActionInterpreter.ts +66 -0
- package/src/interpreter/geometry/BoxGeometryInterpreter.ts +34 -0
- package/src/interpreter/geometry/BufferGeometryInterpreter.ts +47 -0
- package/src/interpreter/geometry/CircleGeometryInterpreter.ts +34 -0
- package/src/interpreter/geometry/CylinderGeometryInterpreter.ts +34 -0
- package/src/interpreter/geometry/ExtrudeGeometryInterpreter.ts +55 -0
- package/src/interpreter/geometry/PlaneGeometryInterpreter.ts +34 -0
- package/src/interpreter/geometry/RingGeometryInterpreter.ts +34 -0
- package/src/interpreter/geometry/SphereGeometryInterpreter.ts +34 -0
- package/src/interpreter/geometry/SurfaceGeometryInterpreter.ts +39 -0
- package/src/interpreter/geometry/TubeGeometryInterpreter.ts +42 -0
- package/src/interpreter/helper/AxesHelperInterpreter.ts +38 -0
- package/src/interpreter/index.ts +45 -0
- package/src/interpreter/light/AmbientLightInterpreter.ts +30 -0
- package/src/interpreter/light/DirectionalLightInterpreter.ts +84 -0
- package/src/interpreter/light/HemisphereLightInterpreter.ts +32 -0
- package/src/interpreter/light/PointLightInterpreter.ts +46 -0
- package/src/interpreter/light/RectAreaLightInterpreter.ts +34 -0
- package/src/interpreter/light/SpotLightInterpreter.ts +68 -0
- package/src/interpreter/material/MaterialInterpreter.ts +34 -0
- package/src/interpreter/material/MeshBasicMaterialInterpreter.ts +43 -0
- package/src/interpreter/material/MeshPhongMaterialInterpreter.ts +63 -0
- package/src/interpreter/material/MeshStandardMaterialInterpreter.ts +58 -0
- package/src/interpreter/material/PointsMaterialInterpreter.ts +36 -0
- package/src/interpreter/material/ShaderMaterialInterpreter.ts +51 -0
- package/src/interpreter/material/ShadowMaterialInterpreter.ts +31 -0
- package/src/interpreter/material/SpriteMaterialInterpreter.ts +36 -0
- package/src/interpreter/texture/TextureInterpreter.ts +59 -0
- package/src/locale/Language.ts +10 -0
- package/src/locale/LanguageProvider.ts +16 -0
- package/src/locale/index.ts +2 -0
- package/src/preset/execute/GeoMap/drilldown.ts +61 -0
- package/src/preset/execute/GeoMap/index.ts +1 -0
- package/src/preset/execute/index.ts +1 -0
- package/src/preset/index.ts +7 -0
- package/src/preset/routine/GeoMap/drilldown.ts +26 -0
- package/src/preset/routine/GeoMap/index.ts +1 -0
- package/src/preset/routine/index.ts +1 -0
- package/src/utils/dispose/index.ts +23 -0
- package/src/utils/geometry/index.ts +82 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/material/index.ts +53 -0
- package/src/utils/pickup/index.ts +16 -0
- package/src/utils/random/index.ts +7 -0
- package/src/utils/text/index.ts +492 -0
- package/src/utils/texture/index.ts +19 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { Box3, BufferGeometry } from 'three';
|
|
2
|
+
import { Font } from 'three/examples/jsm/loaders/FontLoader.js';
|
|
3
|
+
import { TextGeometry } from 'three/examples/jsm/geometries/TextGeometry.js';
|
|
4
|
+
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
|
5
|
+
|
|
6
|
+
interface CharInfo {
|
|
7
|
+
geometry: BufferGeometry
|
|
8
|
+
width: number
|
|
9
|
+
height: number
|
|
10
|
+
depth: number
|
|
11
|
+
minX: number
|
|
12
|
+
minY: number
|
|
13
|
+
maxY: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface TextOptions {
|
|
17
|
+
size: number
|
|
18
|
+
letterSpacing: number
|
|
19
|
+
lineHeight: number
|
|
20
|
+
align: string
|
|
21
|
+
lineIndex: number
|
|
22
|
+
currentY: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface CharSizes {
|
|
26
|
+
[k: string]: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface TextLayout {
|
|
30
|
+
lines: string[]
|
|
31
|
+
charSizes: CharSizes
|
|
32
|
+
maxCharHeight: number
|
|
33
|
+
spaceWidth: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface CharDimensions {
|
|
37
|
+
width: number
|
|
38
|
+
height: number
|
|
39
|
+
depth: number
|
|
40
|
+
minX: number
|
|
41
|
+
minY: number
|
|
42
|
+
maxY: number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class TextUtils {
|
|
46
|
+
static charTemplateCache = new Map<string, CharInfo>()
|
|
47
|
+
static defaultOptions = {
|
|
48
|
+
size: 1,
|
|
49
|
+
depth: 1,
|
|
50
|
+
curveSegments: 12,
|
|
51
|
+
bevelEnabled: true,
|
|
52
|
+
bevelThickness: 0,
|
|
53
|
+
bevelSize: 0,
|
|
54
|
+
bevelOffset: 0,
|
|
55
|
+
bevelSegments: 5
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 创建字符几何体(使用缓存优化)
|
|
60
|
+
*/
|
|
61
|
+
static createCharGeometry(char: string, font: Font, options = {}) {
|
|
62
|
+
if (!char || char === '') {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// 检查缓存
|
|
68
|
+
if (!this.charTemplateCache.has(char)) {
|
|
69
|
+
const geometry = new TextGeometry(char, {
|
|
70
|
+
...TextUtils.defaultOptions,
|
|
71
|
+
font,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
geometry.computeBoundingBox();
|
|
75
|
+
const bbox = geometry.boundingBox as Box3
|
|
76
|
+
|
|
77
|
+
if (bbox.isEmpty()) return null
|
|
78
|
+
|
|
79
|
+
this.charTemplateCache.set(char, {
|
|
80
|
+
geometry: geometry,
|
|
81
|
+
width: bbox.max.x - bbox.min.x,
|
|
82
|
+
height: bbox.max.y - bbox.min.y,
|
|
83
|
+
depth: bbox.max.z - bbox.min.z,
|
|
84
|
+
minX: bbox.min.x,
|
|
85
|
+
minY: bbox.min.y,
|
|
86
|
+
maxY: bbox.max.y,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const template = this.charTemplateCache.get(char) as CharInfo
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
geometry: template.geometry.clone(),
|
|
94
|
+
width: template.width,
|
|
95
|
+
height: template.height,
|
|
96
|
+
depth: template.depth,
|
|
97
|
+
minX: template.minX,
|
|
98
|
+
minY: template.minY
|
|
99
|
+
};
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error(`Error creating geometry for character "${char}":`, error);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 获取字符尺寸信息(包含字间距)
|
|
108
|
+
*/
|
|
109
|
+
static getCharDimensions(char: string, font: Font, options: Partial<TextOptions> = {}): CharDimensions {
|
|
110
|
+
if (!char || char === '') {
|
|
111
|
+
return { width: 0, height: 0, depth: 0, minX: 0, minY: 0, maxY: 0 };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { letterSpacing = 0.1 } = options
|
|
115
|
+
|
|
116
|
+
if (!this.charTemplateCache.has(char)) {
|
|
117
|
+
this.createCharGeometry(char, font, options);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const template = this.charTemplateCache.get(char);
|
|
121
|
+
if (!template) {
|
|
122
|
+
return { width: 0, height: 0, depth: 0, minX: 0, minY: 0, maxY: 0 };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 在字符宽度基础上添加字间距
|
|
126
|
+
return {
|
|
127
|
+
width: template.width + letterSpacing,
|
|
128
|
+
height: template.height,
|
|
129
|
+
depth: template.depth,
|
|
130
|
+
minX: template.minX,
|
|
131
|
+
minY: template.minY,
|
|
132
|
+
maxY: template.maxY,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 计算文本行布局
|
|
138
|
+
*/
|
|
139
|
+
static calculateLineLayout(text: string, maxWidth: number, font: Font, options: Partial<TextOptions> = {}): TextLayout {
|
|
140
|
+
if (!text || maxWidth <= 0) {
|
|
141
|
+
return { lines: [], charSizes: {}, maxCharHeight: 0, spaceWidth: 0 };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const paragraphs = text.split('\n');
|
|
145
|
+
const lines = [];
|
|
146
|
+
const charSizes: CharSizes = {};
|
|
147
|
+
|
|
148
|
+
// 预计算空格宽度(包含字间距)
|
|
149
|
+
const spaceDimensions = this.getCharDimensions(' ', font, options);
|
|
150
|
+
const spaceWidth = spaceDimensions.width;
|
|
151
|
+
|
|
152
|
+
// 计算最大字符高度
|
|
153
|
+
let minY = Infinity, maxY = 0;
|
|
154
|
+
|
|
155
|
+
for (const paragraph of paragraphs) {
|
|
156
|
+
const words = paragraph.split(' ');
|
|
157
|
+
let currentLine = '';
|
|
158
|
+
let currentLineWidth = 0;
|
|
159
|
+
|
|
160
|
+
for (const word of words) {
|
|
161
|
+
if (!word) continue;
|
|
162
|
+
|
|
163
|
+
let wordWidth = 0;
|
|
164
|
+
const chars = word.split('');
|
|
165
|
+
|
|
166
|
+
// 预计算单词中所有字符的尺寸
|
|
167
|
+
for (const char of chars) {
|
|
168
|
+
if (!charSizes[char]) {
|
|
169
|
+
const dim = this.getCharDimensions(char, font, options);
|
|
170
|
+
charSizes[char] = dim.width;
|
|
171
|
+
minY = Math.min(minY, dim.minY)
|
|
172
|
+
maxY = Math.max(maxY, dim.maxY)
|
|
173
|
+
}
|
|
174
|
+
wordWidth += charSizes[char];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 处理超长单词
|
|
178
|
+
if (wordWidth > maxWidth && currentLine === '') {
|
|
179
|
+
this.handleLongWord(chars, maxWidth, charSizes, lines, font, options);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 计算当前行加上这个单词后的宽度
|
|
184
|
+
const spaceWidthForWord = currentLine ? spaceWidth : 0;
|
|
185
|
+
const potentialLineWidth = currentLineWidth + spaceWidthForWord + wordWidth;
|
|
186
|
+
|
|
187
|
+
if (potentialLineWidth > maxWidth) {
|
|
188
|
+
if (currentLine) {
|
|
189
|
+
lines.push(currentLine);
|
|
190
|
+
currentLine = word;
|
|
191
|
+
currentLineWidth = wordWidth;
|
|
192
|
+
} else {
|
|
193
|
+
lines.push(word);
|
|
194
|
+
currentLine = '';
|
|
195
|
+
currentLineWidth = 0;
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
if (currentLine) {
|
|
199
|
+
currentLine += ' ' + word;
|
|
200
|
+
currentLineWidth += spaceWidth + wordWidth;
|
|
201
|
+
} else {
|
|
202
|
+
currentLine = word;
|
|
203
|
+
currentLineWidth = wordWidth;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (currentLine) {
|
|
209
|
+
lines.push(currentLine);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (paragraphs.length > 1 && paragraph !== paragraphs[paragraphs.length - 1]) {
|
|
213
|
+
lines.push('');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const maxCharHeight = maxY - minY;
|
|
218
|
+
return {
|
|
219
|
+
lines: lines.filter(line => line !== undefined && line !== null),
|
|
220
|
+
charSizes,
|
|
221
|
+
maxCharHeight: maxCharHeight > 0 ? maxCharHeight : 0,
|
|
222
|
+
spaceWidth
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 处理超长单词
|
|
228
|
+
*/
|
|
229
|
+
static handleLongWord(chars: string[], maxWidth: number, charSizes: CharSizes, lines: string[], font: Font, options: Partial<TextOptions>) {
|
|
230
|
+
const { letterSpacing = 0.1 } = options;
|
|
231
|
+
let currentSegment = '';
|
|
232
|
+
let segmentWidth = 0;
|
|
233
|
+
|
|
234
|
+
for (const char of chars) {
|
|
235
|
+
if (!charSizes[char]) {
|
|
236
|
+
const dim = this.getCharDimensions(char, font, options);
|
|
237
|
+
charSizes[char] = dim.width; // 这里已经包含了letterSpacing
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const charWidth = charSizes[char];
|
|
241
|
+
|
|
242
|
+
if (charWidth > maxWidth && currentSegment === '') {
|
|
243
|
+
lines.push(char);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (segmentWidth + charWidth > maxWidth && currentSegment) {
|
|
248
|
+
lines.push(currentSegment);
|
|
249
|
+
currentSegment = char;
|
|
250
|
+
segmentWidth = charWidth;
|
|
251
|
+
} else {
|
|
252
|
+
currentSegment += char;
|
|
253
|
+
segmentWidth += charWidth;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (currentSegment) {
|
|
258
|
+
lines.push(currentSegment);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 创建单行文本几何体
|
|
264
|
+
*/
|
|
265
|
+
static createSingleLineGeometry(line: string, layout: TextLayout, font: Font, options: Partial<TextOptions>) {
|
|
266
|
+
const {
|
|
267
|
+
align = 'left',
|
|
268
|
+
currentY = 0,
|
|
269
|
+
} = options;
|
|
270
|
+
|
|
271
|
+
if (!line || line.trim() === '') {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
// 计算行总宽度(这里已经包含了字间距)
|
|
277
|
+
let lineWidth = 0;
|
|
278
|
+
for (const char of line) {
|
|
279
|
+
if (char === ' ') {
|
|
280
|
+
lineWidth += layout.spaceWidth;
|
|
281
|
+
} else {
|
|
282
|
+
lineWidth += layout.charSizes[char] || 0;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 根据对齐方式计算起始X坐标
|
|
287
|
+
let startX = 0;
|
|
288
|
+
switch (align) {
|
|
289
|
+
case 'center':
|
|
290
|
+
startX = -lineWidth / 2;
|
|
291
|
+
break;
|
|
292
|
+
case 'right':
|
|
293
|
+
startX = -lineWidth;
|
|
294
|
+
break;
|
|
295
|
+
default: // 'left'
|
|
296
|
+
startX = 0;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const charGeometries = [];
|
|
300
|
+
let currentX = startX;
|
|
301
|
+
|
|
302
|
+
// 计算这行字符的最大上升部和下降部
|
|
303
|
+
let maxAscent = 0;
|
|
304
|
+
let maxDescent = 0;
|
|
305
|
+
|
|
306
|
+
for (const char of line) {
|
|
307
|
+
if (char === ' ') continue;
|
|
308
|
+
|
|
309
|
+
const dim = this.getCharDimensions(char, font, options);
|
|
310
|
+
maxAscent = Math.max(maxAscent, dim.maxY);
|
|
311
|
+
maxDescent = Math.max(maxDescent, -dim.minY);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 统一的基线位置
|
|
315
|
+
const baselineY = currentY;
|
|
316
|
+
|
|
317
|
+
// 创建每个字符的几何体
|
|
318
|
+
for (const char of line) {
|
|
319
|
+
if (char === ' ') {
|
|
320
|
+
currentX += layout.spaceWidth;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const charGeo = this.createCharGeometry(char, font, options);
|
|
325
|
+
if (!charGeo || !charGeo.geometry) continue;
|
|
326
|
+
|
|
327
|
+
const geometry = charGeo.geometry;
|
|
328
|
+
|
|
329
|
+
// 字符位置计算
|
|
330
|
+
const offsetX = currentX - charGeo.minX;
|
|
331
|
+
const offsetY = baselineY;
|
|
332
|
+
|
|
333
|
+
geometry.translate(offsetX, offsetY, 0);
|
|
334
|
+
|
|
335
|
+
charGeometries.push(geometry);
|
|
336
|
+
|
|
337
|
+
// 移动到下一个字符位置(这里使用包含letterSpacing的宽度)
|
|
338
|
+
currentX += layout.charSizes[char] || 0;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return this.mergeGeometries(charGeometries);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
console.error('Error creating single line geometry:', error);
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 合并多个几何体
|
|
350
|
+
*/
|
|
351
|
+
static mergeGeometries(geometries: BufferGeometry[]) {
|
|
352
|
+
const validGeometries = geometries.filter(geo =>
|
|
353
|
+
geo && geo.isBufferGeometry && geo.attributes.position && geo.attributes.position.count > 0
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (validGeometries.length === 0) {
|
|
357
|
+
return new BufferGeometry();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (validGeometries.length === 1) {
|
|
361
|
+
return validGeometries[0];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
try {
|
|
365
|
+
const merged = mergeGeometries(validGeometries);
|
|
366
|
+
validGeometries.forEach(geo => geo.dispose());
|
|
367
|
+
return merged || new BufferGeometry();
|
|
368
|
+
} catch (error) {
|
|
369
|
+
console.error(error);
|
|
370
|
+
validGeometries.forEach(geo => geo.dispose());
|
|
371
|
+
return new BufferGeometry();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 清理缓存和释放内存
|
|
377
|
+
*/
|
|
378
|
+
static dispose() {
|
|
379
|
+
for (const [key, template] of this.charTemplateCache) {
|
|
380
|
+
if (template.geometry && template.geometry.dispose) {
|
|
381
|
+
template.geometry.dispose();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
this.charTemplateCache.clear();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* 获取文本尺寸信息
|
|
389
|
+
*/
|
|
390
|
+
static measureText(text: string, maxWidth: number, font: Font, options: Partial<TextOptions> = {}) {
|
|
391
|
+
if (!text || maxWidth <= 0) {
|
|
392
|
+
return { width: 0, height: 0, lineCount: 0, lines: [] };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const layout = this.calculateLineLayout(text, maxWidth, font, options);
|
|
396
|
+
|
|
397
|
+
if (layout.lines.length === 0) {
|
|
398
|
+
return { width: 0, height: 0, lineCount: 0, lines: [] };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let totalWidth = 0;
|
|
402
|
+
const nonEmptyLines = layout.lines.filter(line => line !== '');
|
|
403
|
+
|
|
404
|
+
for (const line of nonEmptyLines) {
|
|
405
|
+
let lineWidth = 0;
|
|
406
|
+
for (const char of line) {
|
|
407
|
+
if (char === ' ') {
|
|
408
|
+
lineWidth += layout.spaceWidth;
|
|
409
|
+
} else {
|
|
410
|
+
lineWidth += layout.charSizes[char] || 0;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
totalWidth = Math.max(totalWidth, lineWidth);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const totalHeight = nonEmptyLines.length * layout.maxCharHeight * (options.lineHeight || 1.4);
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
width: totalWidth,
|
|
420
|
+
height: totalHeight,
|
|
421
|
+
lineCount: nonEmptyLines.length,
|
|
422
|
+
lines: layout.lines
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* 创建换行文本的合并几何体
|
|
428
|
+
*/
|
|
429
|
+
static createTextGeometry(text: string, maxWidth: number, font: Font, options: Partial<TextOptions> = {}) {
|
|
430
|
+
if (!text || text.trim() === '' || maxWidth <= 0) {
|
|
431
|
+
return new BufferGeometry();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const {
|
|
435
|
+
size = 1,
|
|
436
|
+
lineHeight = 1.02,
|
|
437
|
+
align = 'left'
|
|
438
|
+
} = options;
|
|
439
|
+
|
|
440
|
+
const layout = this.calculateLineLayout(text, maxWidth, font, options);
|
|
441
|
+
|
|
442
|
+
if (layout.lines.length === 0) {
|
|
443
|
+
return new BufferGeometry();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const lineGeometries: BufferGeometry[] = [];
|
|
447
|
+
const maxCharHeight = layout.maxCharHeight;
|
|
448
|
+
|
|
449
|
+
// 计算起始Y位置(从顶部开始)
|
|
450
|
+
const nonEmptyLines = layout.lines.filter(line => line !== '');
|
|
451
|
+
const totalHeight = (nonEmptyLines.length - 1) * maxCharHeight * lineHeight;
|
|
452
|
+
let currentY = totalHeight / 2;
|
|
453
|
+
|
|
454
|
+
// 为每一行创建几何体
|
|
455
|
+
for (let i = 0; i < layout.lines.length; i++) {
|
|
456
|
+
const line = layout.lines[i];
|
|
457
|
+
if (line === '') {
|
|
458
|
+
currentY -= maxCharHeight * lineHeight;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const lineGeometry = this.createSingleLineGeometry(line, layout, font, {
|
|
463
|
+
...options,
|
|
464
|
+
align,
|
|
465
|
+
lineIndex: i,
|
|
466
|
+
currentY
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
if (lineGeometry) {
|
|
470
|
+
lineGeometries.push(lineGeometry);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
currentY -= maxCharHeight * lineHeight;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// 合并所有几何体
|
|
477
|
+
const mergedGeometry = this.mergeGeometries(lineGeometries);
|
|
478
|
+
mergedGeometry.scale(size, size, 1)
|
|
479
|
+
|
|
480
|
+
// 计算边界框和边界球
|
|
481
|
+
if (mergedGeometry.attributes.position && mergedGeometry.attributes.position.count > 0) {
|
|
482
|
+
mergedGeometry.computeBoundingBox();
|
|
483
|
+
mergedGeometry.computeBoundingSphere();
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return mergedGeometry;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export {
|
|
491
|
+
TextUtils
|
|
492
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Material } from 'three'
|
|
2
|
+
import { Asset, Descriptor, Graphics, UpdateArgs } from '@vyr/engine'
|
|
3
|
+
import { TextureDescriptor } from '../../descriptor'
|
|
4
|
+
import { TextureActor } from '../../actor'
|
|
5
|
+
|
|
6
|
+
const setMap = <T extends Descriptor = Descriptor>(material: Material, key: keyof T, descriptor: T, graphics: Graphics, args: UpdateArgs) => {
|
|
7
|
+
const value = descriptor[key] as string
|
|
8
|
+
const texture = Asset.get(value)
|
|
9
|
+
if (texture instanceof TextureDescriptor) {
|
|
10
|
+
const textureActor = graphics.getActor<TextureActor>(texture, args)
|
|
11
|
+
//@ts-ignore
|
|
12
|
+
material[key] = textureActor.object
|
|
13
|
+
} else {
|
|
14
|
+
//@ts-ignore
|
|
15
|
+
material[key] = null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { setMap }
|