@firecms/neat 0.8.0 → 0.9.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/src/math.ts CHANGED
@@ -8,6 +8,14 @@ export class Matrix4 {
8
8
  0, 0, 0, 1
9
9
  ]);
10
10
  }
11
+ identity() {
12
+ const e = this.elements;
13
+ e[0] = 1; e[1] = 0; e[2] = 0; e[3] = 0;
14
+ e[4] = 0; e[5] = 1; e[6] = 0; e[7] = 0;
15
+ e[8] = 0; e[9] = 0; e[10] = 1; e[11] = 0;
16
+ e[12] = 0; e[13] = 0; e[14] = 0; e[15] = 1;
17
+ return this;
18
+ }
11
19
  translate(tx: number, ty: number, tz: number) {
12
20
  this.elements[12] += this.elements[0] * tx + this.elements[4] * ty + this.elements[8] * tz;
13
21
  this.elements[13] += this.elements[1] * tx + this.elements[5] * ty + this.elements[9] * tz;
@@ -30,6 +38,36 @@ export class Matrix4 {
30
38
  this.elements[11] = c * m43 - s * m42;
31
39
  return this;
32
40
  }
41
+ rotateY(angle: number) {
42
+ const c = Math.cos(angle);
43
+ const s = Math.sin(angle);
44
+ const m11 = this.elements[0], m21 = this.elements[1], m31 = this.elements[2], m41 = this.elements[3];
45
+ const m13 = this.elements[8], m23 = this.elements[9], m33 = this.elements[10], m43 = this.elements[11];
46
+ this.elements[0] = c * m11 - s * m13;
47
+ this.elements[1] = c * m21 - s * m23;
48
+ this.elements[2] = c * m31 - s * m33;
49
+ this.elements[3] = c * m41 - s * m43;
50
+ this.elements[8] = s * m11 + c * m13;
51
+ this.elements[9] = s * m21 + c * m23;
52
+ this.elements[10] = s * m31 + c * m33;
53
+ this.elements[11] = s * m41 + c * m43;
54
+ return this;
55
+ }
56
+ rotateZ(angle: number) {
57
+ const c = Math.cos(angle);
58
+ const s = Math.sin(angle);
59
+ const m11 = this.elements[0], m21 = this.elements[1], m31 = this.elements[2], m41 = this.elements[3];
60
+ const m12 = this.elements[4], m22 = this.elements[5], m32 = this.elements[6], m42 = this.elements[7];
61
+ this.elements[0] = c * m11 + s * m12;
62
+ this.elements[1] = c * m21 + s * m22;
63
+ this.elements[2] = c * m31 + s * m32;
64
+ this.elements[3] = c * m41 + s * m42;
65
+ this.elements[4] = -s * m11 + c * m12;
66
+ this.elements[5] = -s * m21 + c * m22;
67
+ this.elements[6] = -s * m31 + c * m32;
68
+ this.elements[7] = -s * m41 + c * m42;
69
+ return this;
70
+ }
33
71
  }
34
72
 
35
73
  export class OrthographicCamera {
@@ -41,6 +79,7 @@ export class OrthographicCamera {
41
79
  far: number;
42
80
  position: [number, number, number];
43
81
  projectionMatrix: Matrix4;
82
+ zoom: number;
44
83
 
45
84
  constructor(left: number, right: number, top: number, bottom: number, near: number, far: number) {
46
85
  this.left = left;
@@ -50,6 +89,7 @@ export class OrthographicCamera {
50
89
  this.near = near;
51
90
  this.far = far;
52
91
  this.position = [0, 0, 0];
92
+ this.zoom = 1.0;
53
93
  this.projectionMatrix = new Matrix4();
54
94
  this.updateProjectionMatrix();
55
95
  }
@@ -70,35 +110,76 @@ export class OrthographicCamera {
70
110
  }
71
111
  }
72
112
 
73
- export function updateCamera(camera: OrthographicCamera, width: number, height: number, planeWidth: number = 50, planeHeight: number = 50) {
74
- const viewPortAreaRatio = 1000000;
75
- const areaViewPort = width * height;
76
- const targetPlaneArea = areaViewPort / viewPortAreaRatio * planeWidth * planeHeight / 1.5;
77
-
113
+ export function updateCamera(camera: OrthographicCamera, width: number, height: number, planeWidth: number = 50, planeHeight: number = 50, shapeType: string = "plane", zoom: number = 1.0) {
114
+ camera.zoom = zoom;
78
115
  const ratio = width / height;
79
- const targetWidth = Math.sqrt(targetPlaneArea * ratio);
80
- const targetHeight = targetPlaneArea / targetWidth;
81
-
82
- let left = -planeWidth / 2;
83
- let right = Math.min((left + targetWidth) / 1.5, planeWidth / 2);
84
- let top = planeHeight / 4;
85
- let bottom = Math.max((top - targetHeight) / 2, -planeHeight / 4);
86
-
87
- if (ratio < 1) {
88
- const horizontalScale = ratio;
89
- left = left * horizontalScale;
90
- right = right * horizontalScale;
91
- const mobileZoomFactor = 1.05;
92
- left = left * mobileZoomFactor;
93
- right = right * mobileZoomFactor;
94
- top = top * mobileZoomFactor;
95
- bottom = bottom * mobileZoomFactor;
96
- }
97
-
98
- camera.left = left;
99
- camera.right = right;
100
- camera.top = top;
101
- camera.bottom = bottom;
116
+
117
+ if (shapeType === "plane") {
118
+ const viewPortAreaRatio = 1000000;
119
+ const areaViewPort = width * height;
120
+ const targetPlaneArea = areaViewPort / viewPortAreaRatio * planeWidth * planeHeight / 1.5;
121
+
122
+ const targetWidth = Math.sqrt(targetPlaneArea * ratio);
123
+ const targetHeight = targetPlaneArea / targetWidth;
124
+
125
+ let left = -planeWidth / 2;
126
+ let right = Math.min((left + targetWidth) / 1.5, planeWidth / 2);
127
+ let top = planeHeight / 4;
128
+ let bottom = Math.max((top - targetHeight) / 2, -planeHeight / 4);
129
+
130
+ if (ratio < 1) {
131
+ const horizontalScale = ratio;
132
+ left = left * horizontalScale;
133
+ right = right * horizontalScale;
134
+ const mobileZoomFactor = 1.05;
135
+ left = left * mobileZoomFactor;
136
+ right = right * mobileZoomFactor;
137
+ top = top * mobileZoomFactor;
138
+ bottom = bottom * mobileZoomFactor;
139
+ }
140
+
141
+ camera.left = left;
142
+ camera.right = right;
143
+ camera.top = top;
144
+ camera.bottom = bottom;
145
+ } else {
146
+ // Localized 3D shapes: Sphere, Torus, Cylinder, Ribbon.
147
+ // Use a symmetrical, non-stretching camera frustum.
148
+ let halfSize = 25.0; // Default for Ribbon / others
149
+ if (shapeType === "sphere") {
150
+ halfSize = 30.0;
151
+ } else if (shapeType === "torus") {
152
+ halfSize = 35.0;
153
+ } else if (shapeType === "cylinder") {
154
+ halfSize = 30.0;
155
+ }
156
+
157
+ if (ratio >= 1.0) {
158
+ camera.left = -halfSize * ratio;
159
+ camera.right = halfSize * ratio;
160
+ camera.top = halfSize;
161
+ camera.bottom = -halfSize;
162
+ } else {
163
+ camera.left = -halfSize;
164
+ camera.right = halfSize;
165
+ camera.top = halfSize / ratio;
166
+ camera.bottom = -halfSize / ratio;
167
+
168
+ // Zoom out slightly on mobile (1.05 = 5% zoom out)
169
+ const mobileZoomFactor = 1.05;
170
+ camera.left *= mobileZoomFactor;
171
+ camera.right *= mobileZoomFactor;
172
+ camera.top *= mobileZoomFactor;
173
+ camera.bottom *= mobileZoomFactor;
174
+ }
175
+ }
176
+
177
+ // Apply camera zoom to the boundary coordinates
178
+ camera.left /= zoom;
179
+ camera.right /= zoom;
180
+ camera.top /= zoom;
181
+ camera.bottom /= zoom;
182
+
102
183
  camera.near = -100;
103
184
  camera.far = 1000;
104
185
  camera.updateProjectionMatrix();
@@ -160,3 +241,275 @@ export function generatePlaneGeometry(width: number, height: number, widthSegmen
160
241
  wireframeIndex: isLarge ? new Uint32Array(wireframeIndices) : new Uint16Array(wireframeIndices)
161
242
  };
162
243
  }
244
+
245
+ export function generateSphereGeometry(radius: number, widthSegments: number, heightSegments: number) {
246
+ const vertices = [];
247
+ const normals = [];
248
+ const uvs = [];
249
+ const indices = [];
250
+
251
+ const widthSegmentsFloor = Math.floor(widthSegments);
252
+ const heightSegmentsFloor = Math.floor(heightSegments);
253
+
254
+ for (let iy = 0; iy <= heightSegmentsFloor; iy++) {
255
+ const v = iy / heightSegmentsFloor;
256
+ const theta = v * Math.PI;
257
+
258
+ for (let ix = 0; ix <= widthSegmentsFloor; ix++) {
259
+ const u = ix / widthSegmentsFloor;
260
+ const phi = u * Math.PI * 2;
261
+
262
+ const x = -radius * Math.sin(theta) * Math.cos(phi);
263
+ const y = radius * Math.cos(theta);
264
+ const z = radius * Math.sin(theta) * Math.sin(phi);
265
+
266
+ vertices.push(x, y, z);
267
+
268
+ const len = Math.sqrt(x*x + y*y + z*z);
269
+ normals.push(x/len, y/len, z/len);
270
+
271
+ uvs.push(u, 1 - v);
272
+ }
273
+ }
274
+
275
+ for (let iy = 0; iy < heightSegmentsFloor; iy++) {
276
+ for (let ix = 0; ix < widthSegmentsFloor; ix++) {
277
+ const a = ix + (widthSegmentsFloor + 1) * iy;
278
+ const b = ix + (widthSegmentsFloor + 1) * (iy + 1);
279
+ const c = (ix + 1) + (widthSegmentsFloor + 1) * (iy + 1);
280
+ const d = (ix + 1) + (widthSegmentsFloor + 1) * iy;
281
+
282
+ indices.push(a, b, d);
283
+ indices.push(b, c, d);
284
+ }
285
+ }
286
+
287
+ const isLarge = vertices.length / 3 > 65535;
288
+ const wireframeIndices = [];
289
+ for (let i = 0; i < indices.length; i += 3) {
290
+ const a = indices[i];
291
+ const b = indices[i + 1];
292
+ const c = indices[i + 2];
293
+ wireframeIndices.push(a, b, b, c, c, a);
294
+ }
295
+
296
+ return {
297
+ position: new Float32Array(vertices),
298
+ normal: new Float32Array(normals),
299
+ uv: new Float32Array(uvs),
300
+ index: isLarge ? new Uint32Array(indices) : new Uint16Array(indices),
301
+ wireframeIndex: isLarge ? new Uint32Array(wireframeIndices) : new Uint16Array(wireframeIndices)
302
+ };
303
+ }
304
+
305
+ export function generateTorusGeometry(radius: number, tube: number, radialSegments: number, tubularSegments: number) {
306
+ const vertices = [];
307
+ const normals = [];
308
+ const uvs = [];
309
+ const indices = [];
310
+
311
+ const radialSegmentsFloor = Math.floor(radialSegments);
312
+ const tubularSegmentsFloor = Math.floor(tubularSegments);
313
+
314
+ for (let j = 0; j <= radialSegmentsFloor; j++) {
315
+ const v = j / radialSegmentsFloor * Math.PI * 2;
316
+
317
+ for (let i = 0; i <= tubularSegmentsFloor; i++) {
318
+ const u = i / tubularSegmentsFloor * Math.PI * 2;
319
+
320
+ const x = (radius + tube * Math.cos(v)) * Math.cos(u);
321
+ const y = (radius + tube * Math.cos(v)) * Math.sin(u);
322
+ const z = tube * Math.sin(v);
323
+
324
+ vertices.push(x, y, z);
325
+
326
+ const cx = radius * Math.cos(u);
327
+ const cy = radius * Math.sin(u);
328
+ const nx = x - cx;
329
+ const ny = y - cy;
330
+ const nz = z;
331
+ const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
332
+ normals.push(nx / len, ny / len, nz / len);
333
+
334
+ uvs.push(i / tubularSegmentsFloor, j / radialSegmentsFloor);
335
+ }
336
+ }
337
+
338
+ for (let j = 1; j <= radialSegmentsFloor; j++) {
339
+ for (let i = 1; i <= tubularSegmentsFloor; i++) {
340
+ const a = (tubularSegmentsFloor + 1) * j + i - 1;
341
+ const b = (tubularSegmentsFloor + 1) * (j - 1) + i - 1;
342
+ const c = (tubularSegmentsFloor + 1) * (j - 1) + i;
343
+ const d = (tubularSegmentsFloor + 1) * j + i;
344
+
345
+ indices.push(a, b, d);
346
+ indices.push(b, c, d);
347
+ }
348
+ }
349
+
350
+ const isLarge = vertices.length / 3 > 65535;
351
+ const wireframeIndices = [];
352
+ for (let i = 0; i < indices.length; i += 3) {
353
+ const a = indices[i];
354
+ const b = indices[i + 1];
355
+ const c = indices[i + 2];
356
+ wireframeIndices.push(a, b, b, c, c, a);
357
+ }
358
+
359
+ return {
360
+ position: new Float32Array(vertices),
361
+ normal: new Float32Array(normals),
362
+ uv: new Float32Array(uvs),
363
+ index: isLarge ? new Uint32Array(indices) : new Uint16Array(indices),
364
+ wireframeIndex: isLarge ? new Uint32Array(wireframeIndices) : new Uint16Array(wireframeIndices)
365
+ };
366
+ }
367
+
368
+ export function generateCylinderGeometry(radiusTop: number, radiusBottom: number, height: number, radialSegments: number, heightSegments: number) {
369
+ const vertices = [];
370
+ const normals = [];
371
+ const uvs = [];
372
+ const indices = [];
373
+
374
+ const radialSegmentsFloor = Math.floor(radialSegments);
375
+ const heightSegmentsFloor = Math.floor(heightSegments);
376
+
377
+ const halfHeight = height / 2;
378
+
379
+ for (let y = 0; y <= heightSegmentsFloor; y++) {
380
+ const v = y / heightSegmentsFloor;
381
+ const h = v * height - halfHeight;
382
+ const radius = v * (radiusBottom - radiusTop) + radiusTop;
383
+
384
+ for (let x = 0; x <= radialSegmentsFloor; x++) {
385
+ const u = x / radialSegmentsFloor;
386
+ const theta = u * Math.PI * 2;
387
+
388
+ const sinTheta = Math.sin(theta);
389
+ const cosTheta = Math.cos(theta);
390
+
391
+ vertices.push(radius * sinTheta, -h, radius * cosTheta);
392
+ normals.push(sinTheta, 0, cosTheta);
393
+ uvs.push(u, 1 - v);
394
+ }
395
+ }
396
+
397
+ for (let y = 0; y < heightSegmentsFloor; y++) {
398
+ for (let x = 0; x < radialSegmentsFloor; x++) {
399
+ const a = x + (radialSegmentsFloor + 1) * y;
400
+ const b = x + (radialSegmentsFloor + 1) * (y + 1);
401
+ const c = (x + 1) + (radialSegmentsFloor + 1) * (y + 1);
402
+ const d = (x + 1) + (radialSegmentsFloor + 1) * y;
403
+
404
+ indices.push(a, b, d);
405
+ indices.push(b, c, d);
406
+ }
407
+ }
408
+
409
+ const isLarge = vertices.length / 3 > 65535;
410
+ const wireframeIndices = [];
411
+ for (let i = 0; i < indices.length; i += 3) {
412
+ const a = indices[i];
413
+ const b = indices[i + 1];
414
+ const c = indices[i + 2];
415
+ wireframeIndices.push(a, b, b, c, c, a);
416
+ }
417
+
418
+ return {
419
+ position: new Float32Array(vertices),
420
+ normal: new Float32Array(normals),
421
+ uv: new Float32Array(uvs),
422
+ index: isLarge ? new Uint32Array(indices) : new Uint16Array(indices),
423
+ wireframeIndex: isLarge ? new Uint32Array(wireframeIndices) : new Uint16Array(wireframeIndices)
424
+ };
425
+ }
426
+
427
+ export function generateRibbonGeometry(width: number, height: number, widthSegments: number, heightSegments: number, bend: number, twist: number) {
428
+ const width_half = width / 2;
429
+ const height_half = height / 2;
430
+ const gridX = Math.floor(widthSegments);
431
+ const gridY = Math.floor(heightSegments);
432
+ const gridX1 = gridX + 1;
433
+ const gridY1 = gridY + 1;
434
+ const segment_width = width / gridX;
435
+ const segment_height = height / gridY;
436
+
437
+ const vertices = [];
438
+ const normals = [];
439
+ const uvs = [];
440
+ const indices = [];
441
+
442
+ for (let iy = 0; iy < gridY1; iy++) {
443
+ const y = iy * segment_height - height_half;
444
+ for (let ix = 0; ix < gridX1; ix++) {
445
+ const x = ix * segment_width - width_half;
446
+
447
+ let xp = x;
448
+ let yp = y;
449
+ let zp = 0;
450
+
451
+ let nx = 0;
452
+ let ny = 0;
453
+ let nz = 1;
454
+
455
+ if (Math.abs(bend) > 0.001) {
456
+ const r = width / bend;
457
+ const angle = x / r;
458
+ xp = r * Math.sin(angle);
459
+ zp = r * (1 - Math.cos(angle));
460
+
461
+ nx = Math.sin(angle);
462
+ nz = Math.cos(angle);
463
+ }
464
+
465
+ if (Math.abs(twist) > 0.001) {
466
+ const angle = (y / height) * twist;
467
+ const cosA = Math.cos(angle);
468
+ const sinA = Math.sin(angle);
469
+
470
+ const rx = xp * cosA - zp * sinA;
471
+ const rz = xp * sinA + zp * cosA;
472
+ xp = rx;
473
+ zp = rz;
474
+
475
+ const rnx = nx * cosA - nz * sinA;
476
+ const rnz = nx * sinA + nz * cosA;
477
+ nx = rnx;
478
+ nz = rnz;
479
+ }
480
+
481
+ vertices.push(xp, -yp, zp);
482
+ normals.push(nx, ny, nz);
483
+ uvs.push(ix / gridX);
484
+ uvs.push(1 - (iy / gridY));
485
+ }
486
+ }
487
+
488
+ for (let iy = 0; iy < gridY; iy++) {
489
+ for (let ix = 0; ix < gridX; ix++) {
490
+ const a = ix + gridX1 * iy;
491
+ const b = ix + gridX1 * (iy + 1);
492
+ const c = (ix + 1) + gridX1 * (iy + 1);
493
+ const d = (ix + 1) + gridX1 * iy;
494
+ indices.push(a, b, d);
495
+ indices.push(b, c, d);
496
+ }
497
+ }
498
+
499
+ const isLarge = vertices.length / 3 > 65535;
500
+ const wireframeIndices = [];
501
+ for (let i = 0; i < indices.length; i += 3) {
502
+ const a = indices[i];
503
+ const b = indices[i + 1];
504
+ const c = indices[i + 2];
505
+ wireframeIndices.push(a, b, b, c, c, a);
506
+ }
507
+
508
+ return {
509
+ position: new Float32Array(vertices),
510
+ normal: new Float32Array(normals),
511
+ uv: new Float32Array(uvs),
512
+ index: isLarge ? new Uint32Array(indices) : new Uint16Array(indices),
513
+ wireframeIndex: isLarge ? new Uint32Array(wireframeIndices) : new Uint16Array(wireframeIndices)
514
+ };
515
+ }