@shapediver/viewer.data-engine.geometry-engine 3.3.3 → 3.3.6

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.
@@ -1,823 +0,0 @@
1
- import { ITreeNode, TreeNode } from '@shapediver/viewer.shared.node-tree'
2
- import { Logger, ShapeDiverViewerDataProcessingError } from '@shapediver/viewer.shared.services'
3
- import {
4
- ACCESSORCOMPONENTTYPE_V1 as ACCESSOR_COMPONENTTYPE,
5
- ACCESSORTYPE_V1 as ACCESSORTYPE,
6
- ISDGTF_v1,
7
- } from '@shapediver/viewer.data-engine.shared-types'
8
- import {
9
- AttributeData,
10
- GeometryData,
11
- MaterialStandardData,
12
- PRIMITIVE_MODE,
13
- PrimitiveData,
14
- } from '@shapediver/viewer.shared.types'
15
- import { mat4, vec3, vec4 } from 'gl-matrix'
16
-
17
- export class SDGTFLoader {
18
- // #region Properties (5)
19
-
20
- private readonly BINARY_EXTENSION_HEADER_LENGTH = 20;
21
- private readonly _logger: Logger = Logger.instance;
22
-
23
- private _body!: ArrayBuffer;
24
- private _content!: ISDGTF_v1;
25
-
26
- // #endregion Properties (5)
27
-
28
- // #region Public Methods (1)
29
-
30
- public async load(binaryGeometry: ArrayBuffer, gltfLength: number): Promise<ITreeNode> {
31
- if (gltfLength < binaryGeometry.byteLength) {
32
- const headerDataView = new DataView(binaryGeometry, gltfLength, this.BINARY_EXTENSION_HEADER_LENGTH + 1);
33
- const header = {
34
- magic: String.fromCharCode(headerDataView.getUint8(0)) + String.fromCharCode(headerDataView.getUint8(1)) + String.fromCharCode(headerDataView.getUint8(2)) + String.fromCharCode(headerDataView.getUint8(3)) + String.fromCharCode(headerDataView.getUint8(4)),
35
- version: headerDataView.getUint32(5, true),
36
- length: headerDataView.getUint32(9, true),
37
- contentLength: headerDataView.getUint32(13, true),
38
- contentFormat: headerDataView.getUint32(17, true)
39
- }
40
- if (header.magic != 'sdgTF')
41
- throw new ShapeDiverViewerDataProcessingError('SDGTFLoader.load: Invalid data: sdgTF magic wrong.');
42
-
43
- // create content
44
- const contentDataView = new DataView(binaryGeometry, gltfLength + this.BINARY_EXTENSION_HEADER_LENGTH + 1, header.contentLength);
45
- const contentDecoded = new TextDecoder().decode(contentDataView);
46
- this._content = JSON.parse(contentDecoded);
47
- this._body = binaryGeometry.slice(gltfLength + this.BINARY_EXTENSION_HEADER_LENGTH + 1 + header.contentLength, gltfLength + header.length);
48
- } else {
49
- return new TreeNode();
50
- }
51
-
52
- return await this.loadScene();
53
- }
54
-
55
- // #endregion Public Methods (1)
56
-
57
- // #region Private Methods (6)
58
-
59
- private convertToIndicesArray(indices: number[]): Uint8Array | Uint16Array | Uint32Array {
60
- const max = Math.max(0, ...indices);
61
- if(max < (1 << 8) - 1) {
62
- return new Uint8Array(indices);
63
- } else if (max < (1 << 16) - 1) {
64
- return new Uint16Array(indices);
65
- } else {
66
- return new Uint32Array(indices);
67
- }
68
- }
69
-
70
- private async loadAccessor(accessorName: string): Promise<AttributeData> {
71
- if (!this._content.accessors![accessorName]) throw new Error('Accessor not available.')
72
- const accessor = this._content.accessors![accessorName];
73
- const bufferView = this._body;
74
-
75
- const itemSize = ACCESSORTYPE[<keyof typeof ACCESSORTYPE>accessor.type];
76
- const ArrayType = ACCESSOR_COMPONENTTYPE[<keyof typeof ACCESSOR_COMPONENTTYPE>accessor.componentType];
77
-
78
- const elementBytes = ArrayType.BYTES_PER_ELEMENT;
79
- const itemBytes = elementBytes * itemSize;
80
- const byteOffset = accessor.byteOffset || 0;
81
-
82
- return new AttributeData(new ArrayType(bufferView, byteOffset, itemSize * accessor.count), itemSize, itemBytes, byteOffset, elementBytes, false, accessor.count);
83
- }
84
-
85
- private async loadArcs(): Promise<ITreeNode> {
86
- if (!this._content.arcs) throw new Error('Arcs not available.')
87
- const arc = this._content.arcs;
88
- const arcNode = new TreeNode('arcs');
89
-
90
- const data = await this.loadAccessor(arc.attributes['ARCS']);
91
-
92
- // data with an absolute classic array of Vec12s ...
93
- // like you usually have it in any good program
94
- // not 4 Vec3s, no, that would be to logic, but a Vec12 instead
95
-
96
- const count = data.array.length / data.itemSize;
97
-
98
- for (let i = 0; i < count; ++i) {
99
- const singleArcNode = new TreeNode('arc_' + i);
100
-
101
- const index = i * 12;
102
- const arcCenter = vec3.fromValues(data.array[index + 0], data.array[index + 1], data.array[index + 2]);
103
- const arcXAxis = vec3.fromValues(data.array[index + 3], data.array[index + 4], data.array[index + 5]);
104
- const arcYAxis = vec3.fromValues(data.array[index + 6], data.array[index + 7], data.array[index + 8]);
105
- const arcRadius = data.array[index + 9];
106
- const arcMinAngle = data.array[index + 10];
107
- const arcMaxAngle = data.array[index + 11];
108
- const arcZAxis = vec3.cross(vec3.create(), arcXAxis, arcYAxis)
109
-
110
- if (arcRadius <= 0) {
111
- this._logger.warn('SDGTFLoader.loadArcs: Arc radius is <= 0.');
112
- continue;
113
- }
114
- const points: number[] = [];
115
- const getPointOnArc = (t: number): void => {
116
- const twoPi = Math.PI * 2;
117
- let deltaAngle = arcMaxAngle - arcMinAngle;
118
- const samePoints = Math.abs(deltaAngle) < Number.EPSILON;
119
- // ensures that deltaAngle is 0 .. 2 PI
120
- while (deltaAngle < 0) deltaAngle += twoPi;
121
- while (deltaAngle > twoPi) deltaAngle -= twoPi;
122
- deltaAngle = deltaAngle < Number.EPSILON ? samePoints ? 0 : twoPi : deltaAngle;
123
- const angle = arcMinAngle + t * deltaAngle;
124
- let x = arcRadius * Math.cos(angle);
125
- let y = arcRadius * Math.sin(angle);
126
- points.push(x, y, 0);
127
- }
128
-
129
- const numberOfPoints = Math.max(3, Math.round(50 * ((arcMaxAngle - arcMinAngle) / 2 * Math.PI)));
130
- for (let d = 0; d <= numberOfPoints; d++)
131
- getPointOnArc(d / numberOfPoints);
132
-
133
- const array = new Float32Array(points);
134
- const attributes: {
135
- [key: string]: AttributeData
136
- } = {};
137
- attributes['POSITION'] = new AttributeData(array, 3, 0, 0, 0, false, array.length / 3)
138
-
139
- const geometry = new GeometryData(new PrimitiveData(attributes, null), PRIMITIVE_MODE.LINE_STRIP);
140
- singleArcNode.data.push(geometry);
141
-
142
- singleArcNode.addTransformation({
143
- id: 'arc_' + i + '_translation',
144
- matrix: mat4.translate(mat4.create(), mat4.create(), vec3.fromValues(arcCenter[0], arcCenter[1], arcCenter[2]))
145
- });
146
-
147
- const arcRotationMatrix = mat4.transpose(mat4.create(), mat4.fromValues(
148
- arcXAxis[0], arcYAxis[0], arcZAxis[0], 0,
149
- arcXAxis[1], arcYAxis[1], arcZAxis[1], 0,
150
- arcXAxis[2], arcYAxis[2], arcZAxis[2], 0,
151
- 0, 0, 0, 1
152
- ));
153
- singleArcNode.addTransformation({
154
- id: 'arc_' + i + '_rotation',
155
- matrix: arcRotationMatrix
156
- });
157
-
158
- arcNode.addChild(singleArcNode);
159
- }
160
- return arcNode;
161
- }
162
-
163
- private async loadBeziercurve(beziercurveName: string): Promise<ITreeNode> {
164
- if (!this._content.beziercurves![beziercurveName]) throw new Error('Beziercurve not available.')
165
- const beziercurve = this._content.beziercurves![beziercurveName];
166
- const beziercurveNode = new TreeNode(beziercurveName);
167
-
168
- const controlPointsData = await this.loadAccessor(beziercurve.attributes['CONTROLPOINTS']); // vec3
169
- const controlPoints: vec4[] = [];
170
- for (let i = 0; i < controlPointsData.array.length; i += 3)
171
- controlPoints.push(vec4.fromValues(controlPointsData.array[i], controlPointsData.array[i + 1], controlPointsData.array[i + 2], 1));
172
-
173
- const knotsData = await this.loadAccessor(beziercurve.attributes['KNOTS']); // scalar
174
- const knots: number[] = [knotsData.array[0]];
175
- for (let i = 0; i < knotsData.array.length; i++)
176
- knots.push(knotsData.array[i]);
177
- knots.push(knotsData.array[knotsData.array.length - 1])
178
- const degree = beziercurve.degree;
179
-
180
- const findSpan = (u: number): number => {
181
- const n = knots.length - degree - 1;
182
- if (u >= knots[n])
183
- return n - 1;
184
- if (u <= knots[degree])
185
- return degree;
186
-
187
- let low = degree;
188
- let high = n;
189
- let mid = Math.floor((low + high) / 2);
190
-
191
- while (u < knots[mid] || u >= knots[mid + 1]) {
192
- if (u < knots[mid]) {
193
- high = mid;
194
- } else {
195
- low = mid;
196
- }
197
- mid = Math.floor((low + high) / 2);
198
- }
199
- return mid;
200
- }
201
-
202
- const calcBasisFunctions = (span: number, u: number) => {
203
- const N = [];
204
- const left = [];
205
- const right = [];
206
- N[0] = 1.0;
207
-
208
- for (let j = 1; j <= degree; ++j) {
209
- left[j] = u - knots[span + 1 - j];
210
- right[j] = knots[span + j] - u;
211
-
212
- let saved = 0.0;
213
- for (let r = 0; r < j; ++r) {
214
- const rv = right[r + 1];
215
- const lv = left[j - r];
216
- const temp: number = N[r] / (rv + lv);
217
- N[r] = saved + rv * temp;
218
- saved = lv * temp;
219
- }
220
- N[j] = saved;
221
- }
222
- return N;
223
- }
224
-
225
- const calcBSplinePoint = (u: number): vec4 => {
226
- const span = findSpan(u);
227
- const N = calcBasisFunctions(span, u);
228
- const C = vec4.create();
229
- for (let j = 0; j <= degree; ++j) {
230
- const point = controlPoints[span - degree + j];
231
- const Nj = N[j];
232
- const wNj = point[3] * Nj;
233
- vec4.add(C, C, vec4.fromValues(point[0] * wNj, point[1] * wNj, point[2] * wNj, point[3] * Nj))
234
- }
235
- return C;
236
- }
237
-
238
- const points: number[] = [];
239
- const getPointOnBezierCurve = (t: number): void => {
240
- const u = knots[0] + t * (knots[knots.length - 1] - knots[0]); // linear mapping t->u
241
- // following results in (wx, wy, wz, w) homogeneous point
242
- let hpoint = calcBSplinePoint(u);
243
- if (hpoint[3] !== 1.0) {
244
- // project to 3D space: (wx, wy, wz, w) -> (x, y, z, 1)
245
- hpoint = vec4.divide(vec4.create(), hpoint, vec4.fromValues(hpoint[3], hpoint[3], hpoint[3], hpoint[3]))
246
- }
247
- points.push(hpoint[0], hpoint[1], hpoint[2]);
248
- }
249
-
250
- // Number of points calculation
251
- // We go through the control points, measure the distance
252
- let distance = 0;
253
- for (let i = 1; i < controlPoints.length; i++)
254
- distance += vec3.distance(vec3.fromValues(controlPoints[i - 1][0], controlPoints[i - 1][1], controlPoints[i - 1][2]), vec3.fromValues(controlPoints[i][0], controlPoints[i][1], controlPoints[i][2]));
255
-
256
- const numberOfPoints = Math.min(100, Math.max(25, Math.floor(distance / 0.1)));
257
- for (let d = 0; d <= numberOfPoints; d++)
258
- getPointOnBezierCurve(d / numberOfPoints);
259
-
260
- const array = new Float32Array(points);
261
- const attributes: {
262
- [key: string]: AttributeData
263
- } = {};
264
- attributes['POSITION'] = new AttributeData(array, 3, 0, 0, 0, false, array.length / 3)
265
-
266
- const geometry = new GeometryData(new PrimitiveData(attributes, null), PRIMITIVE_MODE.LINE_STRIP);
267
- beziercurveNode.data.push(geometry);
268
-
269
- return beziercurveNode;
270
- }
271
-
272
- private async loadCircles(): Promise<ITreeNode> {
273
- if (!this._content.circles) throw new Error('Circles not available.')
274
- const circle = this._content.circles;
275
- const circleNode = new TreeNode('circles');
276
-
277
- const data = await this.loadAccessor(circle.attributes['CIRCLES']);
278
-
279
- const count = data.array.length / data.itemSize;
280
- for (let i = 0; i < count; i++) {
281
- const singleCircleNode = new TreeNode('circle_' + i);
282
-
283
- const index = i * 10;
284
- const circleCenter = vec3.fromValues(data.array[index + 0], data.array[index + 1], data.array[index + 2]);
285
- const circleXAxis = vec3.fromValues(data.array[index + 3], data.array[index + 4], data.array[index + 5]);
286
- const circleYAxis = vec3.fromValues(data.array[index + 6], data.array[index + 7], data.array[index + 8]);
287
- const circleRadius = data.array[index + 9];
288
- const circleZAxis = vec3.cross(vec3.create(), circleXAxis, circleYAxis)
289
-
290
- if (circleRadius <= 0) {
291
- this._logger.warn('SDGTFLoader.loadCircles: Circle radius is <= 0.');
292
- continue;
293
- }
294
-
295
- const points: number[] = [];
296
- const getPointOnArc = (t: number): void => {
297
- const twoPi = Math.PI * 2;
298
- let deltaAngle = 2.0 * Math.PI - 0;
299
- const samePoints = Math.abs(deltaAngle) < Number.EPSILON;
300
- // ensures that deltaAngle is 0 .. 2 PI
301
- while (deltaAngle < 0) deltaAngle += twoPi;
302
- while (deltaAngle > twoPi) deltaAngle -= twoPi;
303
- deltaAngle = deltaAngle < Number.EPSILON ? samePoints ? 0 : twoPi : deltaAngle;
304
- const angle = 0 + t * deltaAngle;
305
- let x = circleRadius * Math.cos(angle);
306
- let y = circleRadius * Math.sin(angle);
307
- points.push(x, y, 0);
308
- }
309
-
310
- const numberOfPoints = 50;
311
- for (let d = 0; d <= numberOfPoints; d++)
312
- getPointOnArc(d / numberOfPoints);
313
-
314
- const array = new Float32Array(points);
315
- const attributes: {
316
- [key: string]: AttributeData
317
- } = {};
318
- attributes['POSITION'] = new AttributeData(array, 3, 0, 0, 0, false, array.length / 3)
319
-
320
- const geometry = new GeometryData(new PrimitiveData(attributes, null), PRIMITIVE_MODE.LINE_STRIP);
321
- singleCircleNode.data.push(geometry);
322
-
323
- singleCircleNode.addTransformation({
324
- id: 'circle_' + i + '_translation',
325
- matrix: mat4.translate(mat4.create(), mat4.create(), vec3.fromValues(circleCenter[0], circleCenter[1], circleCenter[2]))
326
- });
327
-
328
- const circleRotationMatrix = mat4.transpose(mat4.create(), mat4.fromValues(
329
- circleXAxis[0], circleYAxis[0], circleZAxis[0], 0,
330
- circleXAxis[1], circleYAxis[1], circleZAxis[1], 0,
331
- circleXAxis[2], circleYAxis[2], circleZAxis[2], 0,
332
- 0, 0, 0, 1
333
- ));
334
- singleCircleNode.addTransformation({
335
- id: 'circle_' + i + '_rotation',
336
- matrix: circleRotationMatrix
337
- });
338
-
339
- circleNode.addChild(singleCircleNode);
340
-
341
- }
342
- return circleNode;
343
- }
344
-
345
- private async loadCylinders(): Promise<ITreeNode> {
346
- if (!this._content.cylinders) throw new Error('Cylinders not available.')
347
- const cylinder = this._content.cylinders;
348
- const cylinderNode = new TreeNode('cylinders');
349
-
350
- const data = await this.loadAccessor(cylinder.attributes['CYLINDERS']);
351
-
352
- const count = data.array.length / data.itemSize;
353
- for (let i = 0; i < count; i++) {
354
- const singleCylinderNode = new TreeNode('cylinder_' + i);
355
-
356
- const index = i * 7;
357
- const cylinderTop = vec3.fromValues(data.array[index + 0], data.array[index + 1], data.array[index + 2]);
358
- const cylinderBottom = vec3.fromValues(data.array[index + 3], data.array[index + 4], data.array[index + 5]);
359
- const cylinderRadius = data.array[index + 6];
360
- const cylinderAxis = vec3.sub(vec3.create(), cylinderTop, cylinderBottom)
361
- const dotX = Math.abs(vec3.dot(cylinderAxis, vec3.fromValues(1, 0, 0)));
362
- const dotY = Math.abs(vec3.dot(cylinderAxis, vec3.fromValues(0, 1, 0)));
363
-
364
- let cylinderXAxis: vec3;
365
- if (dotX < dotY) {
366
- cylinderXAxis = vec3.cross(vec3.create(), cylinderAxis, vec3.fromValues(1, 0, 0));
367
- } else {
368
- cylinderXAxis = vec3.cross(vec3.create(), cylinderAxis, vec3.fromValues(0, 1, 0));
369
-
370
- }
371
- const cylinderYAxis = vec3.cross(vec3.create(), cylinderAxis, cylinderXAxis);
372
-
373
- vec3.normalize(cylinderAxis, cylinderAxis);
374
- vec3.normalize(cylinderXAxis, cylinderXAxis);
375
- vec3.normalize(cylinderYAxis, cylinderYAxis);
376
-
377
- if (cylinderRadius <= 0) {
378
- this._logger.warn('SDGTFLoader.loadCylinders: Cylinder radius is <= 0.');
379
- continue;
380
- }
381
-
382
- const indices: number[] = [];
383
- const vertices: number[] = [];
384
- const normals: number[] = [];
385
- const uvs: number[] = [];
386
-
387
-
388
- const height = vec3.distance(cylinderTop, cylinderBottom);
389
- const halfHeight = height / 2;
390
- const thetaStart = 0, thetaLength = Math.PI * 2
391
- let indexCounter = 0;
392
- const indexArray: number[][] = [];
393
-
394
- const heightSegments = 1, radialSegments = 50;
395
-
396
- const normal = vec3.create();
397
- const vertex = vec3.create();
398
- let groupCount = 0;
399
- // this will be used to calculate the normal
400
- const slope = 0;
401
- // generate vertices, normals and uvs
402
- for (let y = 0; y <= heightSegments; y++) {
403
- const indexRow = [];
404
- const v = y / heightSegments;
405
- // calculate the radius of the current row
406
- const radius = cylinderRadius;
407
- for (let x = 0; x <= radialSegments; x++) {
408
- const u = x / radialSegments;
409
- const theta = u * thetaLength + thetaStart;
410
- const sinTheta = Math.sin(theta);
411
- const cosTheta = Math.cos(theta);
412
- // vertex
413
- vertex[0] = radius * sinTheta;
414
- vertex[1] = - v * height + halfHeight;
415
- vertex[2] = radius * cosTheta;
416
- vertices.push(vertex[0], vertex[1], vertex[2]);
417
- // normal
418
- vec3.normalize(normal, vec3.fromValues(sinTheta, slope, cosTheta))
419
- normals.push(normal[0], normal[1], normal[2]);
420
- // uv
421
- uvs.push(u, 1 - v);
422
- // save index of vertex in respective row
423
- indexRow.push(indexCounter++);
424
- }
425
- // now save vertices of the row in our index array
426
- indexArray.push(indexRow);
427
- }
428
-
429
- // generate indices
430
- for (let x = 0; x < radialSegments; x++) {
431
- for (let y = 0; y < heightSegments; y++) {
432
- // we use the index array to access the correct indices
433
- const a = indexArray[y][x];
434
- const b = indexArray[y + 1][x];
435
- const c = indexArray[y + 1][x + 1];
436
- const d = indexArray[y][x + 1];
437
- // faces
438
- indices.push(a, b, d);
439
- indices.push(b, c, d);
440
- // update group counter
441
- groupCount += 6;
442
- }
443
- }
444
-
445
- const attributes: {
446
- [key: string]: AttributeData
447
- } = {};
448
- attributes['POSITION'] = new AttributeData(new Float32Array(vertices), 3, 0, 0, 0, false, vertices.length / 3)
449
- attributes['NORMAL'] = new AttributeData(new Float32Array(normals), 3, 0, 0, 0, false, normals.length / 3)
450
- attributes['TEXCOORD_0'] = new AttributeData(new Float32Array(uvs), 2, 0, 0, 0, false, uvs.length / 2)
451
-
452
- const geometry = new GeometryData(new PrimitiveData(attributes, new AttributeData(this.convertToIndicesArray(indices), 1, 0, 0, 0, false, indices.length)), PRIMITIVE_MODE.TRIANGLES);
453
- singleCylinderNode.data.push(geometry);
454
-
455
- singleCylinderNode.addTransformation({
456
- id: 'cylinder_' + i + '_translation',
457
- matrix: mat4.translate(mat4.create(), mat4.create(), cylinderBottom)
458
- });
459
-
460
- const cylinderRotationMatrix = mat4.transpose(mat4.create(), mat4.fromValues(
461
- cylinderXAxis[0], cylinderYAxis[0], cylinderAxis[0], 0,
462
- cylinderXAxis[1], cylinderYAxis[1], cylinderAxis[1], 0,
463
- cylinderXAxis[2], cylinderYAxis[2], cylinderAxis[2], 0,
464
- 0, 0, 0, 1
465
- ));
466
- singleCylinderNode.addTransformation({
467
- id: 'cylinder_' + i + '_rotation',
468
- matrix: cylinderRotationMatrix
469
- });
470
-
471
-
472
- singleCylinderNode.addTransformation({
473
- id: 'cylinder_' + i + '_rotation2',
474
- matrix: mat4.rotateX(mat4.create(), mat4.create(), 0.5 * Math.PI)
475
- });
476
- singleCylinderNode.addTransformation({
477
- id: 'cylinder_' + i + '_translation2',
478
- matrix: mat4.translate(mat4.create(), mat4.create(), vec3.fromValues(0, 0, 0.5 * vec3.distance(cylinderTop, cylinderBottom)))
479
- });
480
- cylinderNode.addChild(singleCylinderNode);
481
-
482
- }
483
- return cylinderNode;
484
- }
485
-
486
- private async loadSpheres(): Promise<ITreeNode> {
487
- if (!this._content.spheres) throw new Error('Spheres not available.')
488
- const sphere = this._content.spheres;
489
- const sphereNode = new TreeNode('spheres');
490
-
491
- const data = await this.loadAccessor(sphere.attributes['SPHERES']);
492
-
493
- const count = data.array.length / data.itemSize;
494
- for (let i = 0; i < count; i++) {
495
- const singleSphereNode = new TreeNode('sphere_' + i);
496
-
497
- const index = i * 4;
498
- const sphereTranslation = vec3.fromValues(data.array[index + 0], data.array[index + 1], data.array[index + 2]);
499
- const sphereRadius = data.array[index + 3];
500
- if (sphereRadius <= 0) {
501
- this._logger.warn('SDGTFLoader.loadSpheres: Sphere radius is <= 0.');
502
- continue;
503
- }
504
-
505
- const indices: number[] = [];
506
- const vertices: number[] = [];
507
- const normals: number[] = [];
508
- const uvs: number[] = [];
509
- const grid: number[][] = [];
510
-
511
- // for some reason, this doesn't work with values > 15
512
- // let's not look into it, it's legacy stuff
513
- const heightSegments = 15, widthSegments = 15;
514
- const phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI;
515
- const thetaEnd = Math.min(thetaStart + thetaLength, Math.PI);
516
-
517
- let indexCounter = 0;
518
-
519
- // generate vertices, normals and uvs
520
-
521
- for (let iy = 0; iy <= heightSegments; iy++) {
522
- const verticesRow = [];
523
- const v = iy / heightSegments;
524
-
525
- // special case for the poles
526
- let uOffset = 0;
527
- if (iy == 0 && thetaStart == 0) {
528
- uOffset = 0.5 / widthSegments;
529
- } else if (iy == heightSegments && thetaEnd == Math.PI) {
530
- uOffset = - 0.5 / widthSegments;
531
- }
532
- for (let ix = 0; ix <= widthSegments; ix++) {
533
- const u = ix / widthSegments;
534
- // vertex
535
- const vertex = vec3.fromValues(
536
- - sphereRadius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength),
537
- sphereRadius * Math.cos(thetaStart + v * thetaLength),
538
- sphereRadius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength)
539
- );
540
- vertices.push(vertex[0], vertex[1], vertex[2]);
541
- // normal
542
- const normal = vec3.normalize(vec3.create(), vertex);
543
- normals.push(normal[0], normal[1], normal[2]);
544
- // uv
545
- uvs.push(u + uOffset, 1 - v);
546
- verticesRow.push(indexCounter++);
547
- }
548
- grid.push(verticesRow);
549
- }
550
-
551
- // indices
552
- for (let iy = 0; iy < heightSegments; iy++) {
553
- for (let ix = 0; ix < widthSegments; ix++) {
554
- const a = grid[iy][ix + 1];
555
- const b = grid[iy][ix];
556
- const c = grid[iy + 1][ix];
557
- const d = grid[iy + 1][ix + 1];
558
- if (iy !== 0 || thetaStart > 0) indices.push(a, b, d);
559
- if (iy !== heightSegments - 1 || thetaEnd < Math.PI) indices.push(b, c, d);
560
- }
561
- }
562
-
563
- const attributes: {
564
- [key: string]: AttributeData
565
- } = {};
566
- attributes['POSITION'] = new AttributeData(new Float32Array(vertices), 3, 0, 0, 0, false, vertices.length / 3)
567
- attributes['NORMAL'] = new AttributeData(new Float32Array(normals), 3, 0, 0, 0, false, normals.length / 3)
568
- attributes['TEXCOORD_0'] = new AttributeData(new Float32Array(uvs), 2, 0, 0, 0, false, uvs.length / 2)
569
-
570
- const geometry = new GeometryData(new PrimitiveData(attributes, new AttributeData(this.convertToIndicesArray(indices), 1, 0, 0, 0, false, indices.length)), PRIMITIVE_MODE.TRIANGLES);
571
- singleSphereNode.data.push(geometry);
572
-
573
- singleSphereNode.addTransformation({
574
- id: 'sphere_' + i + '_translation',
575
- matrix: mat4.translate(mat4.create(), mat4.create(), sphereTranslation)
576
- });
577
- sphereNode.addChild(singleSphereNode);
578
-
579
- }
580
- return sphereNode;
581
- }
582
-
583
- private async loadPoint(pointName: string): Promise<ITreeNode> {
584
- if (!this._content.points![pointName]) throw new Error('Point not available.')
585
- const point = this._content.points![pointName];
586
- const pointNode = new TreeNode(pointName);
587
-
588
- const attributes: {
589
- [key: string]: AttributeData
590
- } = {};
591
-
592
- const data = await this.loadAccessor(point.attributes['POINTS']);
593
- attributes['POSITION'] = new AttributeData(data.array, 3, data.itemBytes, data.byteOffset, data.elementBytes, data.normalized, data.count)
594
-
595
- const geometry = new GeometryData(new PrimitiveData(attributes, null), PRIMITIVE_MODE.POINTS);
596
- pointNode.data.push(geometry);
597
-
598
- return pointNode;
599
- }
600
-
601
- private async loadPolyline(polylineName: string): Promise<ITreeNode> {
602
- if (!this._content.polylines![polylineName]) throw new Error('Polyline not available.')
603
- const polyLine = this._content.polylines![polylineName];
604
- const polyLineNode = new TreeNode(polylineName);
605
-
606
- const attributes: {
607
- [key: string]: AttributeData
608
- } = {};
609
-
610
- const data = await this.loadAccessor(polyLine.attributes['VERTICES']);
611
- attributes['POSITION'] = new AttributeData(data.array, 3, data.itemBytes, data.byteOffset, data.elementBytes, data.normalized, data.count)
612
-
613
- const geometry = new GeometryData(new PrimitiveData(attributes, null), PRIMITIVE_MODE.LINE_STRIP);
614
- polyLineNode.data.push(geometry);
615
-
616
- return polyLineNode;
617
- }
618
-
619
- private async loadSurfacepatch(surfacepatchName: string): Promise<ITreeNode> {
620
- if (!this._content.surfacepatches![surfacepatchName]) throw new Error('Surfacepatch not available.')
621
- const surfacepatch = this._content.surfacepatches![surfacepatchName];
622
- const surfacepatchNode = new TreeNode(surfacepatchName);
623
-
624
- const controlPointCountU = surfacepatch.controlPointCountU;
625
- const controlPointCountV = surfacepatch.controlPointCountV;
626
-
627
- const controlPointsData = await this.loadAccessor(surfacepatch.attributes['CONTROLPOINTS']); // vec3
628
- const controlPoints: vec4[][] = [];
629
- let pointCount = 0;
630
- for (let u = 0; u < controlPointCountU; u++) {
631
- let innerArray = []
632
- for (let v = 0; v < controlPointCountV; v++) {
633
- innerArray.push(vec4.fromValues(controlPointsData.array[pointCount * 3], controlPointsData.array[pointCount * 3 + 1], controlPointsData.array[pointCount * 3 + 2], 1));
634
- pointCount++;
635
- }
636
- controlPoints.push(innerArray);
637
- }
638
-
639
- const knotsUData = await this.loadAccessor(surfacepatch.attributes['KNOTSU']); // scalar
640
- const knotsU: number[] = [knotsUData.array[0]];
641
- for (let i = 0; i < knotsUData.array.length; i++)
642
- knotsU.push(knotsUData.array[i]);
643
- knotsU.push(knotsUData.array[knotsUData.array.length - 1])
644
-
645
- const knotsVData = await this.loadAccessor(surfacepatch.attributes['KNOTSV']); // scalar
646
- const knotsV: number[] = [knotsVData.array[0]];
647
- for (let i = 0; i < knotsVData.array.length; i++)
648
- knotsV.push(knotsVData.array[i]);
649
- knotsV.push(knotsVData.array[knotsVData.array.length - 1])
650
-
651
- const degreeU = surfacepatch.degreeU;
652
- const degreeV = surfacepatch.degreeV;
653
-
654
- const findSpan = (knots: number[], degree: number, u: number): number => {
655
- const n = knots.length - degree - 1;
656
- if (u >= knots[n])
657
- return n - 1;
658
- if (u <= knots[degree])
659
- return degree;
660
-
661
- let low = degree;
662
- let high = n;
663
- let mid = Math.floor((low + high) / 2);
664
-
665
- while (u < knots[mid] || u >= knots[mid + 1]) {
666
- if (u < knots[mid]) {
667
- high = mid;
668
- } else {
669
- low = mid;
670
- }
671
- mid = Math.floor((low + high) / 2);
672
- }
673
- return mid;
674
- }
675
-
676
- const calcBasisFunctions = (knots: number[], degree: number, span: number, u: number) => {
677
- const N = [];
678
- const left = [];
679
- const right = [];
680
- N[0] = 1.0;
681
-
682
- for (let j = 1; j <= degree; ++j) {
683
- left[j] = u - knots[span + 1 - j];
684
- right[j] = knots[span + j] - u;
685
-
686
- let saved = 0.0;
687
- for (let r = 0; r < j; ++r) {
688
- const rv = right[r + 1];
689
- const lv = left[j - r];
690
- const temp: number = N[r] / (rv + lv);
691
- N[r] = saved + rv * temp;
692
- saved = lv * temp;
693
- }
694
- N[j] = saved;
695
- }
696
- return N;
697
- }
698
-
699
- const calcSurfacePoint = (u: number, v: number): vec3 => {
700
-
701
- const uspan = findSpan(knotsU, degreeU, u);
702
- const vspan = findSpan(knotsV, degreeV, v);
703
- const Nu = calcBasisFunctions(knotsU, degreeU, uspan, u);
704
- const Nv = calcBasisFunctions(knotsV, degreeV, vspan, v);
705
- const temp: vec4[] = [];
706
-
707
- for (let l = 0; l <= degreeV; ++l) {
708
-
709
- temp[l] = vec4.create();
710
- for (let k = 0; k <= degreeU; ++k) {
711
-
712
- const point = vec4.clone(controlPoints[uspan - degreeU + k][vspan - degreeV + l]);
713
- const w = point[3];
714
- point[0] *= w;
715
- point[1] *= w;
716
- point[2] *= w;
717
- vec4.add(temp[l], temp[l], vec4.multiply(vec4.create(), point, vec4.fromValues(Nu[k], Nu[k], Nu[k], Nu[k])))
718
- }
719
- }
720
-
721
- const Sw = vec4.create();
722
- for (let l = 0; l <= degreeV; ++l) {
723
- vec4.add(Sw, Sw, vec4.multiply(vec4.create(), temp[l], vec4.fromValues(Nv[l], Nv[l], Nv[l], Nv[l])))
724
- }
725
-
726
- vec4.divide(Sw, Sw, vec4.fromValues(Sw[3], Sw[3], Sw[3], Sw[3]))
727
- return vec3.fromValues(Sw[0], Sw[1], Sw[2]);
728
- }
729
-
730
- const getPointOnSurfacepatch = (t1: number, t2: number): vec3 => {
731
- const u = knotsU[0] + t1 * (knotsU[knotsU.length - 1] - knotsU[0]); // linear mapping t1->u
732
- const v = knotsV[0] + t2 * (knotsV[knotsV.length - 1] - knotsV[0]); // linear mapping t2->u
733
- return calcSurfacePoint(u, v);
734
- }
735
-
736
- const numberOfPoints = 15;
737
-
738
- const indices: number[] = [];
739
- const vertices = [];
740
-
741
- for (let d = 0; d <= numberOfPoints; d++) {
742
- const v = d / numberOfPoints;
743
- for (let f = 0; f <= numberOfPoints; f++) {
744
- const u = f / numberOfPoints;
745
- const vertex = getPointOnSurfacepatch(u, v);
746
- vertices.push(vertex[0], vertex[1], vertex[2]);
747
- }
748
- }
749
-
750
- for (let d = 0; d < numberOfPoints; d++) {
751
- for (let f = 0; f < numberOfPoints; f++) {
752
- const i1 = d * (numberOfPoints + 1) + f;
753
- const i2 = d * (numberOfPoints + 1) + f + 1;
754
- const i3 = (d+1) * (numberOfPoints + 1) + f;
755
- const i4 = (d+1) * (numberOfPoints + 1) + f + 1;
756
- // faces one and two
757
- indices.push(i3, i2, i1);
758
- indices.push(i2, i3, i4);
759
- }
760
- }
761
-
762
- const attributes: {
763
- [key: string]: AttributeData
764
- } = {};
765
- attributes['POSITION'] = new AttributeData(new Float32Array(vertices), 3, 0, 0, 0, false, vertices.length / 3);
766
- // to not compute normals ourselves, we just let three.js do it
767
- // in our geometry loader, this array will cause the computation of vertex normals
768
- attributes['NORMAL'] = new AttributeData(new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]), 3, 0, 0, 0, false, vertices.length / 3);
769
-
770
- const geometry = new GeometryData(new PrimitiveData(attributes, new AttributeData(this.convertToIndicesArray(indices), 1, 0, 0, 0, false, indices.length)), PRIMITIVE_MODE.TRIANGLES);
771
- surfacepatchNode.data.push(geometry);
772
-
773
- return surfacepatchNode;
774
- }
775
-
776
-
777
- private async loadScene(): Promise<ITreeNode> {
778
- const sceneNode = new TreeNode('sdgtf_content');
779
-
780
- // arcs
781
- if (this._content.arcs)
782
- sceneNode.addChild(await this.loadArcs());
783
-
784
- // beziercurves
785
- if (this._content.beziercurves) {
786
- for (let beziercurve in this._content.beziercurves)
787
- sceneNode.addChild(await this.loadBeziercurve(beziercurve));
788
- }
789
-
790
- // circles
791
- if (this._content.circles)
792
- sceneNode.addChild(await this.loadCircles());
793
-
794
- // cylinders
795
- if (this._content.cylinders)
796
- sceneNode.addChild(await this.loadCylinders());
797
-
798
- //points
799
- if (this._content.points) {
800
- for (let point in this._content.points)
801
- sceneNode.addChild(await this.loadPoint(point));
802
- }
803
-
804
- // polylines
805
- if (this._content.polylines) {
806
- for (let line in this._content.polylines)
807
- sceneNode.addChild(await this.loadPolyline(line));
808
- }
809
-
810
- // spheres
811
- if (this._content.spheres)
812
- sceneNode.addChild(await this.loadSpheres());
813
-
814
- // surfacepatches
815
- if (this._content.surfacepatches) {
816
- for (let surfacepatch in this._content.surfacepatches)
817
- sceneNode.addChild(await this.loadSurfacepatch(surfacepatch));
818
- }
819
- return sceneNode;
820
- }
821
-
822
- // #endregion Private Methods (6)
823
- }