markuno_lib 1.1.21 → 1.1.22

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.
@@ -0,0 +1,1831 @@
1
+ import * as THREE from 'three';
2
+ import { PIF, clean, hash, getshape, raccordabezier } from '#cad/markcad.js';
3
+ export { clamp } from '#cad/markcad.js';
4
+ import earcut from 'earcut';
5
+
6
+ const SIDE = THREE.FrontSide; // default sides THREE.DoubleSide/THREE.FrontSide;
7
+ let materialline1 = new THREE.LineBasicMaterial({ color: 0x303030 });
8
+ let materialline2 = new THREE.LineBasicMaterial({ color: 0x505050 });
9
+
10
+
11
+ const mwhite$1 = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.5, metalness: 0.4, side: SIDE });
12
+ const mgray1 = new THREE.MeshStandardMaterial({ color: 0x808080, roughness: 0.5, metalness: 0.4, side: SIDE });
13
+ const mgray2 = new THREE.MeshStandardMaterial({ color: 0xb0b0b0, roughness: 0.5, metalness: 0.4, side: SIDE });
14
+ const mred = new THREE.MeshStandardMaterial({ color: 0xff0000, roughness: 0.5, metalness: 0.4, side: SIDE });
15
+ const mblue = new THREE.MeshStandardMaterial({ color: 0x1e40af, roughness: 0.5, metalness: 0.4, side: SIDE });
16
+ const mgreen = new THREE.MeshStandardMaterial({ color: 0x009000, roughness: 0.5, metalness: 0.4, side: SIDE });
17
+ const mblack = new THREE.MeshStandardMaterial({ color: 0x000000, roughness: 0.5, metalness: 0.4, side: SIDE });
18
+ const scaleunit = 1 / 1000;
19
+
20
+ function groupfromgeometry(geometry, material, x, y, z, name, layer) {
21
+ let child = new THREE.Group();
22
+ child.position.set(x, y, z);
23
+ child.name = name;
24
+
25
+ // Crea la mesh principale
26
+ const mesh = new THREE.Mesh(geometry, material);
27
+ mesh.name = name;
28
+ mesh.castShadow = true;
29
+ mesh.receiveShadow = true;
30
+ mesh.layers.set(layer);
31
+
32
+ // Aggiunge i bordi
33
+ const edges = new THREE.EdgesGeometry(geometry);
34
+ const lineSegments = new THREE.LineSegments(edges, materialline1);
35
+ lineSegments.layers.set(30);
36
+
37
+ child.add(mesh);
38
+ child.add(lineSegments);
39
+ return child;
40
+ }
41
+
42
+ function posiziona(grp, pos = {}) {
43
+ let tm = new THREE.Group();
44
+ if (grp) {
45
+ let g2 = grp.clone();
46
+ let tm2 = g2;
47
+ let { sl = 0, sa = 0, sp = 0, ax = 0, ay = 0, az = 0, ul = 0, ua = 0, up = 0, scale = 1, scx = scale, scy = scale, scz = scale } = pos;
48
+ tm.position.set(sl, sa, sp);
49
+ tm.rotation.set(ax * PIF, ay * PIF, az * PIF);
50
+ if (ul || ua || up) {
51
+ tm2 = new THREE.Group();
52
+ tm2.add(g2);
53
+ tm2.position.set(ul, ua, up);
54
+ }
55
+ tm.scale.set(scx, scy, scz);
56
+ tm.add(tm2);
57
+ }
58
+ return tm;
59
+ }
60
+
61
+ function edgesfromgeometry(g1, layer = 30) {
62
+ const edges = new THREE.EdgesGeometry(g1, 20);
63
+ return getlinesgeom(edges, layer);
64
+ }
65
+
66
+ function getlinesgeom(edges, layer = 30) {
67
+ const lineSegments = new THREE.LineSegments(edges, materialline1);
68
+ lineSegments.layers.set(layer);
69
+ return lineSegments;
70
+ }
71
+
72
+ function getmesh(geom, material, layer = 1, clone = false) {
73
+ let m = new THREE.Mesh(geom, clone ? material.clone() : material);
74
+ m.castShadow = true;
75
+ m.receiveShadow = true;
76
+ m.layers.set(layer);
77
+ return m;
78
+ }
79
+
80
+ function get3dshape(punti, material, layer) {
81
+ if (!punti || punti.length < 3) {
82
+ return new THREE.BufferGeometry();
83
+ }
84
+ const vertices = [];
85
+ for (let i = 0; i < punti.length; i++) {
86
+ const p1 = punti[i];
87
+ const p2 = punti[(i + 1) % punti.length];
88
+ vertices.push(p1.x, 0, p1.y);
89
+ vertices.push(p2.x, 0, p2.y);
90
+ }
91
+ const geometry = new THREE.BufferGeometry();
92
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
93
+ let m = new THREE.LineSegments(geometry, material);
94
+ m.layers.set(layer);
95
+ return m;
96
+ }
97
+
98
+ function creategroup(name) {
99
+ let g = new THREE.Group();
100
+ g.name = name || '$$';
101
+ return g
102
+ }
103
+
104
+ function svuotanodo(n) {
105
+ function _dispose(node) {
106
+ // Gestisce ricorsivamente tutti i figli
107
+ if (node.children.length) {
108
+ const children = [...node.children];
109
+ children.forEach(child => {
110
+ _dispose(child);
111
+ node.remove(child); // Usa node.remove invece di children.remove
112
+ });
113
+ }
114
+ // Dispone delle risorse del nodo corrente
115
+ if (node.geometry) {
116
+ node.geometry.dispose();
117
+ }
118
+ if (node.material) {
119
+ if (Array.isArray(node.material)) {
120
+ node.material.forEach(m => m.dispose());
121
+ } else {
122
+ node.material.dispose();
123
+ }
124
+ }
125
+ }
126
+ _dispose(n);
127
+ }
128
+
129
+ // cancella uno specifico nodo in una scena THREEJS
130
+ function deletegroup(grpbase, name) {
131
+ if (!grpbase?.children) return;
132
+ let gr = grpbase.children.find(e => e.name == name);
133
+ if (gr) {
134
+ // Rimuovi tutti i figli in modo ricorsivo
135
+ while (gr.children.length > 0) {
136
+ const child = gr.children[0];
137
+ /*
138
+ if (child.geometry) {
139
+ child.geometry.dispose();
140
+ }
141
+ if (child.material) {
142
+ if (Array.isArray(child.material)) {
143
+ child.material.forEach(m => m.dispose());
144
+ } else {
145
+ child.material.dispose();
146
+ }
147
+ }
148
+ */
149
+ gr.remove(child);
150
+ }
151
+ // Rimuovi il gruppo dal parent
152
+ if (gr.parent) {
153
+ gr.parent.remove(gr);
154
+ }
155
+ // Pulisci eventuali riferimenti
156
+ gr = null;
157
+ }
158
+ }
159
+
160
+ function randombasemat() {
161
+ const material = new THREE.LineBasicMaterial();
162
+ const color = new THREE.Color();
163
+ color.setHSL(Math.random(), 0.7, 0.4);
164
+ material.color = color;
165
+ return material;
166
+ }
167
+
168
+ /**
169
+ * Crea una linea 3D
170
+ * @param {Object} l - Oggetto contenente punti p1 e p2
171
+ * @param {string} id - Identificatore
172
+ * @param {THREE.Material} [mat=null] - Materiale da applicare
173
+ * @returns {THREE.Line} Linea 3D
174
+ */
175
+ function getline(l, id, mat = null) {
176
+ const lineGeometry = new THREE.BufferGeometry().setFromPoints([
177
+ new THREE.Vector3(l.p1.x, 0, l.p1.y),
178
+ new THREE.Vector3(l.p2.x, 0, l.p2.y)
179
+ ]);
180
+ let tm = new THREE.Line(lineGeometry, mat || randombasemat());
181
+ return tm;
182
+ }
183
+
184
+ /**
185
+ * Crea un punto 3D rappresentato da una sfera
186
+ * @param {Object} p - Coordinate del punto
187
+ * @param {string} id - Identificatore
188
+ * @param {THREE.Material} [mat=null] - Materiale da applicare
189
+ * @param {number} [size=5] - Dimensione della sfera
190
+ * @returns {THREE.Mesh} Punto 3D
191
+ */
192
+ function getpoint(p, id, mat = null, size = 5) {
193
+ const pointgeom = new THREE.SphereGeometry(size, 8, 8);
194
+ let tm = new THREE.Mesh(pointgeom, mat || randombasemat());
195
+ tm.position.set(p.x, 0, p.y);
196
+ return tm;
197
+ }
198
+
199
+
200
+ function meshrotate(orientamento, mesh, x = 0, y = 0, z = 0) {
201
+ switch (orientamento.trim().toUpperCase()) {
202
+ case 'LPA':
203
+ mesh.rotation.y = Math.PI / 2;
204
+ mesh.rotation.x = Math.PI;
205
+ mesh.position.set(0, z, 0);
206
+ break;
207
+ case 'ALP':
208
+ mesh.rotation.x = Math.PI / 2;
209
+ mesh.position.set(0, x, 0);
210
+ break;
211
+ case 'APL':
212
+ mesh.rotation.x = -Math.PI / 2;
213
+ mesh.rotation.z = -Math.PI / 2;
214
+ break;
215
+ case 'PLA':
216
+ break;
217
+ case 'PAL':
218
+ mesh.rotation.z = Math.PI / 2;
219
+ mesh.position.set(z, 0, 0);
220
+ break;
221
+ case 'LAP':
222
+ mesh.rotation.y = Math.PI / 2;
223
+ mesh.rotation.z = Math.PI / 2;
224
+ break;
225
+ }
226
+ return mesh;
227
+ }
228
+
229
+
230
+ /**
231
+ * Aggiunge un pivot a un oggetto 3D esistente, mantenendone posizione e orientamento globale.
232
+ * @param {THREE.Object3D} grp - Oggetto esistente già posizionato nella scena.
233
+ * @param {THREE.Vector3} pivotLocal - Punto (locale) attorno a cui ruotare (es: -x/2, -y/2, 0).
234
+ * @param {Object} movimento - Istanza della classe MovimentoBase o derivata.
235
+ * @returns {THREE.Group} - Nuovo gruppo contenitore con pivot.
236
+ */
237
+
238
+ function addmovpivot(gcad, grp, movimento, op = {}, x = 0, y = 0, z = 0) {
239
+ movimento = clean(movimento, true);
240
+ if (!gcad.movs[movimento]) return grp;
241
+ gcad.movs[movimento];
242
+ const pivotLocal = new THREE.Vector3(x, y, z);
243
+ const isZeroPivot = pivotLocal.lengthSq() === 0;
244
+
245
+ const pivotGroup = new THREE.Group(); // gruppopivot
246
+ pivotGroup.name = `pivot_${movimento}`;
247
+
248
+ const movimentoGroup = new THREE.Group(); // gruppomovimento
249
+ movimentoGroup.name = `mov_${movimento}`;
250
+
251
+ // Gestione posizione
252
+ if (isZeroPivot) {
253
+ pivotGroup.position.copy(grp.position);
254
+ pivotGroup.quaternion.copy(grp.quaternion);
255
+ grp.position.set(0, 0, 0);
256
+ grp.rotation.set(0, 0, 0);
257
+ } else {
258
+ const pivotWorld = pivotLocal.clone().applyMatrix4(grp.matrixWorld);
259
+ pivotGroup.position.copy(pivotWorld);
260
+
261
+ const offset = pivotLocal.clone().negate();
262
+ grp.position.add(offset);
263
+
264
+ pivotGroup.quaternion.copy(grp.getWorldQuaternion(new THREE.Quaternion()));
265
+ grp.rotation.set(0, 0, 0);
266
+ }
267
+
268
+ // Costruisci gerarchia: pivot -> movimento -> oggetto
269
+ movimentoGroup.add(grp);
270
+ pivotGroup.add(movimentoGroup);
271
+
272
+ // Imposta userData del gruppo MOVIMENTO, non del pivot!
273
+ if (!op) op = {};
274
+ op.inmov = false;
275
+ op.key = movimento;
276
+ op.dt = 0;
277
+ op.dtstart = false;
278
+
279
+ movimentoGroup.userData.mov = { ...op };
280
+
281
+ return pivotGroup;
282
+ }
283
+
284
+
285
+ /**
286
+ * Crea un gestore di movimento per animare oggetti 3D.
287
+ * @param {string} key - Chiave identificativa del movimento
288
+ * @param {Array<Object>} gtimeline - Timeline di passi di animazione. Ogni passo può contenere:
289
+ * @param {number} time - Durata del passo in millisecondi
290
+ * @param {(number|function)} [x] - Traslazione X (unità o funzione che restituisce unità)
291
+ * @param {(number|function)} [y] - Traslazione Y
292
+ * @param {(number|function)} [z] - Traslazione Z
293
+ * @param {(number|function)} [s] - Scala uniforme (moltiplicatore)
294
+ * @param {(number|function)} [sx] - Scala X
295
+ * @param {(number|function)} [sy] - Scala Y
296
+ * @param {(number|function)} [sz] - Scala Z
297
+ * @param {(number|function)} [ax] - Rotazione X (in giri)
298
+ * @param {(number|function)} [ay] - Rotazione Y
299
+ * @param {(number|function)} [az] - Rotazione Z
300
+ * @param {(number|function)} [t] - Trasparenza (0-1)
301
+ * @returns {Object} Oggetto gestore del movimento con metodi:
302
+ * @property {number} tline - Durata totale della timeline
303
+ * @property {function} clear - Pulisce la timeline
304
+ * @property {function} add - Aggiunge un passo alla timeline
305
+ * @property {string} key - Chiave del movimento
306
+ * @property {function} step - Esegue un passo dell'animazione
307
+ * @property {function} reset - Resetta l'oggetto alla posizione iniziale
308
+ */
309
+ function getmovimento(key, gtimeline = []) {
310
+ let totale = 0;
311
+ let timeline = [];
312
+ const _calcolatotale = () => {
313
+ totale = timeline.reduce((t, e) => t + (e.time || 0), 0);
314
+ };
315
+
316
+ const _cleartimeline = () => {
317
+ timeline = [];
318
+ totale = 0;
319
+ };
320
+
321
+ const _add = (t, op = {}) => {
322
+ if (t) {
323
+ timeline.push({ ...op, time: t });
324
+ }
325
+ _calcolatotale();
326
+ };
327
+
328
+ _cleartimeline();
329
+ if (gtimeline && gtimeline.length) {
330
+ gtimeline.forEach(e => _add(e.time, e));
331
+ }
332
+
333
+ const _resetmov = (grp) => {
334
+ const { mov } = grp?.userData || {};
335
+ if (mov) {
336
+ mov.inmov = false;
337
+ grp.position.set(0, 0, 0);
338
+ grp.scale.set(1, 1, 1);
339
+ grp.rotation.set(0, 0, 0);
340
+ }
341
+ };
342
+
343
+ const _step = (grp, callback) => {
344
+ if (!grp || !grp.userData?.mov || !totale) return;
345
+ const { mov } = grp.userData;
346
+ if (!mov.inmov) return;
347
+ let dt = mov.dt - mov.dtstart;
348
+ if (mov.ripeti) {
349
+ dt = dt % totale;
350
+ } else if (dt > totale) {
351
+ _resetmov(grp);
352
+ return;
353
+ }
354
+
355
+ let x = 0, y = 0, z = 0;
356
+ let ax = 0, ay = 0, az = 0;
357
+ let sx = 1, sy = 1, sz = 1;
358
+ let t = null;
359
+
360
+ let accumTime = 0;
361
+
362
+ for (let step of timeline) {
363
+ accumTime += step.time;
364
+ if (dt < accumTime) {
365
+ const c = (step.time > 0) ? (dt - (accumTime - step.time)) / step.time : 1;
366
+ const _calc = (f, def = 0) => typeof f === 'function' ? f(mov) * c : (f || def) * c;
367
+
368
+ x += _calc(step.x);
369
+ y += _calc(step.y);
370
+ z += _calc(step.z);
371
+
372
+ sx *= 1 + _calc(step.sx ?? step.s, 0);
373
+ sy *= 1 + _calc(step.sy ?? step.s, 0);
374
+ sz *= 1 + _calc(step.sz ?? step.s, 0);
375
+
376
+ ax += _calc(step.ax) * PIF;
377
+ ay += _calc(step.ay) * PIF;
378
+ az += _calc(step.az) * PIF;
379
+
380
+ if (typeof step.t !== 'undefined') {
381
+ t = typeof step.t === 'function' ? step.t(mov) * c : step.t * c;
382
+ }
383
+
384
+ break; // consider only the current step
385
+ } else {
386
+ // Accumula completamente il valore intero
387
+ const _calc = (f, def = 0) => typeof f === 'function' ? f(mov) : f || def;
388
+
389
+ x += _calc(step.x);
390
+ y += _calc(step.y);
391
+ z += _calc(step.z);
392
+
393
+ sx *= 1 + _calc(step.sx ?? step.s, 0);
394
+ sy *= 1 + _calc(step.sy ?? step.s, 0);
395
+ sz *= 1 + _calc(step.sz ?? step.s, 0);
396
+
397
+ ax += _calc(step.ax) * PIF;
398
+ ay += _calc(step.ay) * PIF;
399
+ az += _calc(step.az) * PIF;
400
+
401
+ if (typeof step.t !== 'undefined') {
402
+ t = typeof step.t === 'function' ? step.t(mov) : step.t;
403
+ }
404
+ }
405
+ }
406
+
407
+ grp.position.set(x, y, z);
408
+ grp.scale.set(sx, sy, sz);
409
+ grp.rotation.set(ax, ay, az);
410
+
411
+ if (t !== null) {
412
+ // Applica la trasparenza se esistono materiali con `transparent: true`
413
+ grp.traverse(obj => {
414
+ if (obj.material) {
415
+ const materials = Array.isArray(obj.material) ? obj.material : [obj.material];
416
+ materials.forEach(mat => {
417
+ if (mat.transparent) {
418
+ mat.opacity = 1 - t;
419
+ }
420
+ });
421
+ }
422
+ });
423
+ }
424
+
425
+ if (callback) callback(grp, dt);
426
+ };
427
+
428
+ return {
429
+ get tline() { return totale },
430
+ clear: _cleartimeline,
431
+ add: _add,
432
+ key,
433
+ step: _step,
434
+ reset: _resetmov,
435
+ };
436
+ }
437
+
438
+ // Canvas globale per il rendering delle targhette
439
+ let globalLabelCanvas = null;
440
+ let globalLabelContext = null;
441
+ function getGlobalCanvas() {
442
+ if (!globalLabelCanvas) {
443
+ globalLabelCanvas = document.createElement('canvas');
444
+ globalLabelContext = globalLabelCanvas.getContext('2d', { willReadFrequently: true });
445
+ }
446
+ return { canvas: globalLabelCanvas, context: globalLabelContext };
447
+ }
448
+
449
+
450
+ /**
451
+ * Crea un punto di riferimento invisibile nell'albero 3D
452
+ * @param {number} x - Coordinata X
453
+ * @param {number} y - Coordinata Y
454
+ * @param {number} z - Coordinata Z
455
+ * @param {Object} dati - Dati da archiviare nell'oggetto
456
+ * @param {string} [id=null] - Identificatore opzionale
457
+ * @returns {THREE.Object3D} Oggetto di riferimento invisibile
458
+ */
459
+ function getriferimento(dati, x = 0, y = 0, z = 0, id = 'rif') {
460
+ if (typeof dati == 'string') dati = { testo: dati };
461
+ let riferimento = new THREE.Object3D();
462
+ riferimento.position.set(x, y, z);
463
+
464
+ // Imposta il nome se fornito
465
+ if (id) riferimento.name = id;
466
+ riferimento.userData = { tipo: 'rif', ...dati };
467
+ return riferimento;
468
+ }
469
+
470
+
471
+ /**
472
+ * Crea una targhetta rettangolare con testo
473
+ * @param {Array|string} testo - Array di oggetti {testo, size, colore} o stringa
474
+ * @param {number} dim - Larghezza/altezza della targhetta
475
+ * @param {Object} options - Opzioni aggiuntive
476
+ * @returns {THREE.Mesh} Mesh con la targhetta
477
+ */
478
+ function gettarghetta(gcad, testo, dim = 100, options = {}) {
479
+ // Converti stringa in array di oggetti testo
480
+ const {
481
+ noSfondo = false,
482
+ forcey = false,
483
+ coloreSfondo = 'white',
484
+ coloreBordo = 'darkgray',
485
+ spessoreBordo = 3,
486
+ padding = 8,
487
+ layer = 21,
488
+ fontFamily = 'Arial',
489
+ raggioAngoli = 20, // Raggio degli angoli arrotondati
490
+ } = options;
491
+ let tt = hash(`T:${coloreSfondo}|${coloreBordo}|${padding}|${spessoreBordo}|${raggioAngoli}|${noSfondo}|${testo}`);
492
+
493
+
494
+
495
+ testo = testo.split('\n').map(e => {
496
+ let size = 12, color = 'black';
497
+ e = e.trim();
498
+ let rr = /^\s*\#(\w)(\d*)\,(.*)$/.exec(e);
499
+ if (rr) {
500
+ size = parseInt(rr[2] || 12);
501
+ e = rr[3];
502
+ switch (rr[1].toLowerCase()) {
503
+ case 'r': color = 'red'; break;
504
+ case 'g': color = 'green'; break;
505
+ case 'b': color = 'blue'; break;
506
+ case 'c': color = 'cyan'; break;
507
+ }
508
+ }
509
+ return { testo: e, size, color }
510
+ }).filter(e => e.testo);
511
+
512
+
513
+
514
+ // Ottieni il canvas globale
515
+ const { canvas, context } = getGlobalCanvas();
516
+
517
+ // Calcola le dimensioni necessarie per il testo
518
+ let maxWidth = 0;
519
+ let totalHeight = padding * 2; // Inizia con il padding superiore e inferiore
520
+ const scale = 2;
521
+ testo.forEach((riga, i) => {
522
+ const { testo: testoRiga, size = 12 } = riga;
523
+ const fontSize = size * scale;
524
+ context.font = `${fontSize}px ${fontFamily}`;
525
+
526
+ const metrics = context.measureText(testoRiga);
527
+ const textWidth = metrics.width;
528
+ const textHeight = fontSize * (i == testo.length - 1 ? 1 : 1.2); // Approssimazione dell'altezza del testo
529
+ maxWidth = Math.max(maxWidth, textWidth);
530
+ totalHeight += textHeight;
531
+ });
532
+
533
+ // Aggiungi padding laterale alla larghezza
534
+ maxWidth += padding * 2;
535
+
536
+ // Calcola il rapporto di aspetto del testo
537
+ const aspectRatio = totalHeight / maxWidth || 1;
538
+
539
+ // Calcola dimx e dimy in base all'opzione forcey
540
+ let dimx, dimy;
541
+ if (forcey) {
542
+ dimy = dim;
543
+ dimx = Math.ceil(dimy / aspectRatio);
544
+ } else {
545
+ dimx = dim;
546
+ dimy = Math.ceil(dimx * aspectRatio);
547
+ }
548
+ if (!gcad.textures[tt]) {
549
+
550
+ // Ridimensiona il canvas per adattarlo alle dimensioni specificate
551
+ // Aggiungi spazio extra per il bordo
552
+ const canvasWidth = maxWidth + spessoreBordo * 2;
553
+ const canvasHeight = totalHeight + spessoreBordo * 2;
554
+
555
+ // Ridimensiona il canvas globale se necessario
556
+ if (canvas.width < canvasWidth || canvas.height < canvasHeight) {
557
+ canvas.width = Math.max(canvas.width, canvasWidth);
558
+ canvas.height = Math.max(canvas.height, canvasHeight);
559
+ }
560
+
561
+ // Pulisci l'area del canvas che useremo
562
+ context.clearRect(0, 0, canvasWidth, canvasHeight);
563
+
564
+ // Converti il colore di sfondo da esadecimale a RGB
565
+ const bgColor = new THREE.Color(coloreSfondo);
566
+ const bgColorStyle = `rgb(${Math.floor(bgColor.r * 255)}, ${Math.floor(bgColor.g * 255)}, ${Math.floor(bgColor.b * 255)})`;
567
+
568
+ // Converti il colore del bordo da esadecimale a RGB
569
+ const borderColor = new THREE.Color(coloreBordo);
570
+ const borderColorStyle = `rgb(${Math.floor(borderColor.r * 255)}, ${Math.floor(borderColor.g * 255)}, ${Math.floor(borderColor.b * 255)})`;
571
+
572
+ // Calcola il raggio degli angoli
573
+ const radius = raggioAngoli;
574
+
575
+ // Disegna prima il bordo (rettangolo più grande)
576
+ context.beginPath();
577
+ if (!noSfondo) {
578
+ drawRoundedRect(context, 0, 0, canvasWidth, canvasHeight, radius + spessoreBordo / 2);
579
+ context.fillStyle = borderColorStyle;
580
+ context.fill();
581
+
582
+ // Poi disegna lo sfondo (rettangolo più piccolo)
583
+ context.beginPath();
584
+ drawRoundedRect(context, spessoreBordo, spessoreBordo,
585
+ canvasWidth - spessoreBordo * 2,
586
+ canvasHeight - spessoreBordo * 2,
587
+ radius - spessoreBordo / 2);
588
+ context.fillStyle = bgColorStyle;
589
+ context.fill();
590
+ }
591
+ // Disegna ogni riga di testo
592
+ let yPosition = padding + spessoreBordo;
593
+
594
+ // Disegna ogni riga di testo
595
+ testo.forEach((riga) => {
596
+ const { testo: testoRiga, size = 12, color = 'black' } = riga;
597
+
598
+ // Imposta lo stile del testo
599
+ const fontSize = size * scale;
600
+ context.font = `${fontSize}px ${fontFamily}`;
601
+ context.fillStyle = color;
602
+ context.textAlign = 'center';
603
+ context.textBaseline = 'top';
604
+
605
+ // Disegna il testo
606
+ context.fillText(testoRiga, canvasWidth / 2, yPosition);
607
+ yPosition += fontSize * 1.2; // Spazio tra le righe
608
+ });
609
+
610
+ // Ottieni i dati dell'immagine direttamente dal canvas globale
611
+ const imageData = context.getImageData(0, 0, canvasWidth, canvasHeight);
612
+
613
+ // Crea una texture direttamente dai dati dell'immagine
614
+ const texture = new THREE.DataTexture(
615
+ imageData.data,
616
+ imageData.width,
617
+ imageData.height,
618
+ THREE.RGBAFormat
619
+ );
620
+
621
+ // Imposta le proprietà della texture
622
+ texture.needsUpdate = true;
623
+ texture.flipY = true;
624
+ texture.minFilter = THREE.LinearFilter;
625
+ texture.magFilter = THREE.LinearFilter;
626
+
627
+ // Imposta l'area del canvas da utilizzare come texture
628
+ // Memorizza la texture nella cache
629
+ gcad.textures[tt] = texture;
630
+ }
631
+
632
+
633
+
634
+ // Crea un materiale con la texture
635
+ const materialeTesto = new THREE.MeshBasicMaterial({
636
+ map: gcad.textures[tt],
637
+ transparent: true,
638
+ side: SIDE
639
+ });
640
+
641
+ // Crea un piano per il testo con le dimensioni specificate
642
+ const geometriaTesto = new THREE.PlaneGeometry(dimx, dimy);
643
+ const meshTesto = new THREE.Mesh(geometriaTesto, materialeTesto);
644
+ meshTesto.layers.set(layer);
645
+ meshTesto.userData = {
646
+ dimx, dimy
647
+ };
648
+
649
+ return meshTesto;
650
+ }
651
+
652
+ // Funzione helper per disegnare un rettangolo con angoli arrotondati
653
+ function drawRoundedRect(ctx, x, y, width, height, radius) {
654
+ // Assicurati che il raggio non sia troppo grande
655
+ radius = Math.min(radius, Math.min(width / 2, height / 2));
656
+
657
+ ctx.moveTo(x + radius, y);
658
+ ctx.lineTo(x + width - radius, y);
659
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
660
+ ctx.lineTo(x + width, y + height - radius);
661
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
662
+ ctx.lineTo(x + radius, y + height);
663
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
664
+ ctx.lineTo(x, y + radius);
665
+ ctx.quadraticCurveTo(x, y, x + radius, y);
666
+ ctx.closePath();
667
+ }
668
+
669
+ /**
670
+ * Crea una quota tra due punti in 3D sul piano XY
671
+ * @param {string} testo - Testo da visualizzare nella quota
672
+ * @param {number} x1 - Coordinata X del primo punto
673
+ * @param {number} y1 - Coordinata Y del primo punto
674
+ * @param {number} x2 - Coordinata X del secondo punto
675
+ * @param {number} y2 - Coordinata Y del secondo punto
676
+ * @param {number} sizetesto - Dimensione del testo
677
+ * @param {Object} options - Opzioni aggiuntive
678
+ * @returns {THREE.Group} Gruppo contenente la quota
679
+ */
680
+ function getquota(gcad, testo, x1, y1, x2, y2, altezza = 30, offset = 0, options = {}) {
681
+ // Opzioni di default
682
+ const {
683
+ piano = 'xy',
684
+ layer = 22,
685
+ delta = 5, // Margine per decidere se la targhetta è troppo grande
686
+ spessoreLinea = 3, // Spessore della linea (diametro del cilindro)
687
+ } = options;
688
+ // pp point on plane. usare questa funzione per convertire i punti 2D rispetto al piano scelto
689
+ const pp = (x, y) => {
690
+ if (piano === 'xz') return [x, 0, y];
691
+ if (piano === 'yz') return [0, x, y]; // Corretto: x diventa y, y diventa z
692
+ return [x, y, 0]; // Default: piano xy
693
+ };
694
+
695
+ // Creo un gruppo per contenere tutti gli elementi della quota
696
+ const quotaGroup = new THREE.Group();
697
+ quotaGroup.name = 'quota';
698
+
699
+ // Calcolo la distanza tra i due punti
700
+ const dx = x2 - x1;
701
+ const dy = y2 - y1;
702
+ const distanza = Math.sqrt(dx * dx + dy * dy);
703
+
704
+ // Calcolo l'angolo di rotazione
705
+ const angolo = Math.atan2(dy, dx);
706
+
707
+ // Calcolo l'offset perpendicolare alla linea
708
+ const offsetX = -Math.sin(angolo) * offset;
709
+ const offsetY = Math.cos(angolo) * offset;
710
+ // Creo il testo della quota
711
+ const targhetta = gettarghetta(gcad,
712
+ testo || `${distanza.toFixed(1)}`, altezza * 2,
713
+ {
714
+ noSfondo: true,
715
+ layer,
716
+ forcey: true
717
+ }
718
+ );
719
+ const { dimx, dimy } = targhetta.userData;
720
+
721
+ // Determino se la targhetta è più grande della distanza disponibile
722
+ const targhettaTroppoGrande = dimx > (distanza - delta);
723
+
724
+ // Funzione per creare un cilindro tra due punti
725
+ function getCilindro(puntoInizio, puntoFine, spessore) {
726
+ const direzione = new THREE.Vector3().subVectors(puntoFine, puntoInizio);
727
+ const lunghezza = direzione.length();
728
+
729
+ // Creo la geometria del cilindro senza top e bottom
730
+ let ky = hash(`c:${spessore}|${lunghezza}`);
731
+ let geometria;
732
+ if (!gcad.geo[ky]) {
733
+ geometria = new THREE.CylinderGeometry(
734
+ spessore / 2, // raggio superiore
735
+ spessore / 2, // raggio inferiore
736
+ lunghezza, // altezza
737
+ 3, // numero di segmenti
738
+ 1, // segmenti di altezza
739
+ true // openEnded = true per rimuovere top e bottom
740
+ );
741
+ gcad.geo[ky] = geometria;
742
+ } else {
743
+ geometria = gcad.geo[ky];
744
+ }
745
+
746
+ // Creo la mesh
747
+ const cilindro = new THREE.Mesh(geometria, mblack);
748
+
749
+ // Posiziono il cilindro
750
+ cilindro.position.copy(puntoInizio).add(direzione.multiplyScalar(0.5));
751
+
752
+ // Calcolo la rotazione per allineare il cilindro con la direzione
753
+ const punto = new THREE.Vector3().copy(puntoFine);
754
+ cilindro.lookAt(punto);
755
+
756
+ // Ruoto di 90 gradi per allineare correttamente il cilindro
757
+ cilindro.rotateX(Math.PI / 2);
758
+
759
+ return cilindro;
760
+ }
761
+
762
+ // Gestisco il posizionamento della targhetta e delle linee
763
+ if (targhettaTroppoGrande) {
764
+ // Posiziono la targhetta sopra la linea
765
+ targhetta.position.set(distanza / 2, altezza, 0);
766
+
767
+ // Creo una singola linea orizzontale come cilindro
768
+ const puntoInizio = new THREE.Vector3(...pp(0, 0));
769
+ const puntoFine = new THREE.Vector3(...pp(distanza, 0));
770
+ const lineaOrizzontale = getCilindro(puntoInizio, puntoFine, spessoreLinea);
771
+ quotaGroup.add(lineaOrizzontale);
772
+ } else {
773
+ // Calcolo lo spazio necessario per la targhetta
774
+ const spazioTarghetta = dimx + delta;
775
+ const inizioTarghetta = (distanza - spazioTarghetta) / 2;
776
+ const fineTarghetta = inizioTarghetta + spazioTarghetta;
777
+
778
+ // Creo due linee orizzontali separate come cilindri
779
+ const puntoInizio1 = new THREE.Vector3(...pp(0, 0));
780
+ const puntoFine1 = new THREE.Vector3(...pp(inizioTarghetta, 0));
781
+ const lineaOrizzontale1 = getCilindro(puntoInizio1, puntoFine1, spessoreLinea);
782
+ quotaGroup.add(lineaOrizzontale1);
783
+
784
+ const puntoInizio2 = new THREE.Vector3(...pp(fineTarghetta, 0));
785
+ const puntoFine2 = new THREE.Vector3(...pp(distanza, 0));
786
+ const lineaOrizzontale2 = getCilindro(puntoInizio2, puntoFine2, spessoreLinea);
787
+ quotaGroup.add(lineaOrizzontale2);
788
+
789
+ // Posiziono la targhetta al centro
790
+ targhetta.position.set(...pp(distanza / 2, 0));
791
+ }
792
+
793
+ // Creo le linee verticali alle estremità come cilindri
794
+ // Calcolo l'altezza delle linee verticali in base all'offset
795
+ const h1 = offset > altezza / 2 ? -offset : -altezza / 2;
796
+ const h2 = -offset > altezza / 2 ? -offset : altezza / 2;
797
+
798
+ // Creo le linee verticali - semplificato per tutti i piani usando pp
799
+ const puntoInizioV1 = new THREE.Vector3(...pp(0, h1));
800
+ const puntoFineV1 = new THREE.Vector3(...pp(0, h2));
801
+ const puntoInizioV2 = new THREE.Vector3(...pp(distanza, h1));
802
+ const puntoFineV2 = new THREE.Vector3(...pp(distanza, h2));
803
+
804
+ const lineaVerticale1 = getCilindro(puntoInizioV1, puntoFineV1, spessoreLinea);
805
+ quotaGroup.add(lineaVerticale1);
806
+
807
+ const lineaVerticale2 = getCilindro(puntoInizioV2, puntoFineV2, spessoreLinea);
808
+ quotaGroup.add(lineaVerticale2);
809
+
810
+ // Creo una funzione di aggiornamento per far sì che la targhetta sia sempre rivolta verso la camera
811
+ targhetta.userData.updateOrientation = function (camera) {
812
+ if (camera) {
813
+ this.quaternion.copy(camera.quaternion);
814
+ }
815
+ };
816
+
817
+ // Aggiungo la targhetta al gruppo
818
+ quotaGroup.add(targhetta);
819
+
820
+ // Posiziono e ruoto il gruppo in base al piano scelto
821
+ if (piano === 'xy') {
822
+ quotaGroup.position.set(x1 + offsetX, y1 + offsetY, 0);
823
+ quotaGroup.rotation.z = angolo;
824
+ } else if (piano === 'xz') {
825
+ quotaGroup.position.set(x1 + offsetX, 0, y1 + offsetY);
826
+ quotaGroup.rotation.y = -angolo; // Rotazione attorno all'asse Y per il piano XZ
827
+ } else if (piano === 'yz') {
828
+ quotaGroup.position.set(0, x1 + offsetX, y1 + offsetY);
829
+ quotaGroup.rotation.x = angolo; // Rotazione attorno all'asse X per il piano YZ
830
+ }
831
+
832
+
833
+ quotaGroup.traverse(function (object) {
834
+ object.layers.set(layer);
835
+ });
836
+
837
+ return quotaGroup;
838
+ }
839
+
840
+ function cylgeometry(gcad, h, r, rtop, segments = 16) {
841
+ if (!r || !h) return null;
842
+ let ky = hash(`cy${h}|${r}|${rtop || r}|${segments}`);
843
+ if (!gcad.geo[ky]) {
844
+ const geometry = new THREE.CylinderGeometry(rtop || r, r, h, segments);
845
+ gcad.geo[ky] = geometry;
846
+ }
847
+ return gcad.geo[ky];
848
+ }
849
+
850
+
851
+ /**
852
+ * Crea un cilindro 3D con orientamento personalizzabile
853
+ * @param {string} ori - Orientamento (X/L, Y/A, Z/P)
854
+ * @param {number} h - Altezza
855
+ * @param {number} r1 - Raggio base
856
+ * @param {number} r2 - Raggio top
857
+ * @param {THREE.Material|Array} mats - Materiale/i da applicare
858
+ * @param {Object} options - Opzioni di configurazione
859
+ * @returns {Promise<THREE.Group>} Gruppo contenente il cilindro
860
+ */
861
+ async function getcilindro(gcad, ori, h, r1, r2, mats = mwhite$1, options) {
862
+ if (!options) options = {};
863
+ if (!Array.isArray(mats)) mats = [mats];
864
+
865
+ const cyl = cylgeometry(gcad, h, r1, r2, options.sides || 16);
866
+
867
+ let grp = new THREE.Group();
868
+
869
+ // Gestione orientamento
870
+ switch ((ori || '').trim().toUpperCase()) {
871
+ case 'X':
872
+ case 'L':
873
+ grp.position.set(h / 2, 0, 0);
874
+ grp.rotation.z = Math.PI / 2;
875
+ break;
876
+ case 'Z':
877
+ case 'P':
878
+ grp.position.set(0, 0, h / 2);
879
+ grp.rotation.x = Math.PI / 2;
880
+ break;
881
+ default:
882
+ grp.position.set(0, h / 2, 0);
883
+ }
884
+
885
+ if (cyl) {
886
+ if (!options.nolines) {
887
+ let segments = edgesfromgeometry(cyl);
888
+ grp.add(segments);
889
+ }
890
+
891
+ const matTop = mats[0] || mwhite$1;
892
+ const matBottom = mats[1] || matTop;
893
+ const matSides = mats[2] || matTop;
894
+ grp.add(getmesh(cyl, [matTop, matBottom, matSides]));
895
+ }
896
+ return grp;
897
+ }
898
+
899
+ /**
900
+ * Crea un box 3D con linee di bordo opzionali
901
+ * @param {number} x - Larghezza
902
+ * @param {number} y - Altezza
903
+ * @param {number} z - Profondità
904
+ * @param {THREE.Material} mat - Materiale da applicare
905
+ * @param {Object} options - Opzioni di configurazione
906
+ * @returns {Promise<THREE.Group>} Gruppo contenente il box
907
+ */
908
+
909
+
910
+ function boxgeometry(gcad, x, y, z) {
911
+ let ky = hash(`b:${x}|${y}|${x}`);
912
+ if (!gcad.geo[ky]) {
913
+ const bx = new THREE.BoxGeometry(x, y, z);
914
+ gcad.geo[ky] = bx;
915
+ }
916
+ return gcad.geo[ky];
917
+ }
918
+
919
+ function getface(gcad, x, y, mat, scaled = false) { // rimosso async
920
+ let ky = hash(`f:${x}|${y}|${scaled ? 1 : 0}`);
921
+ if (!gcad.geo[ky]) {
922
+ const geometry = new THREE.PlaneGeometry(x, y);
923
+ const uvs = geometry.attributes.uv;
924
+ const positions = geometry.attributes.position;
925
+ for (let i = 0; i < positions.count; i++) {
926
+ uvs.setXY(i,
927
+ scaled ? positions.getX(i) : positions.getX(i) / (x * scaleunit),
928
+ scaled ? positions.getY(i) : positions.getY(i) / (y * scaleunit)
929
+ );
930
+ }
931
+ gcad.geo[ky] = geometry;
932
+ }
933
+ const plane = new THREE.Mesh(gcad.geo[ky], mat);
934
+ plane.position.set(x / 2, y / 2, 0);
935
+ if (mat?.transparent) {
936
+ plane.layers.set(2);
937
+ }
938
+ return plane;
939
+ }
940
+
941
+ async function getbox(gcad, x, y, z, mat, options) {
942
+ if (!options) options = {};
943
+ let grp = new THREE.Group();
944
+ const box = boxgeometry(gcad, x, y, z);
945
+ grp.position.set(x / 2, y / 2, z / 2);
946
+ if (!options.nolines) {
947
+ let segments = edgesfromgeometry(box);
948
+ grp.add(segments);
949
+ }
950
+ grp.add(getmesh(box, mat || mwhite$1));
951
+ return grp;
952
+ }
953
+
954
+ /**
955
+ * @param {BufferGeometry} geometry
956
+ * @param {number} tolerance
957
+ * @return {BufferGeometry}
958
+ */
959
+ function mergeVertices( geometry, tolerance = 1e-4 ) {
960
+
961
+ tolerance = Math.max( tolerance, Number.EPSILON );
962
+
963
+ // Generate an index buffer if the geometry doesn't have one, or optimize it
964
+ // if it's already available.
965
+ const hashToIndex = {};
966
+ const indices = geometry.getIndex();
967
+ const positions = geometry.getAttribute( 'position' );
968
+ const vertexCount = indices ? indices.count : positions.count;
969
+
970
+ // next value for triangle indices
971
+ let nextIndex = 0;
972
+
973
+ // attributes and new attribute arrays
974
+ const attributeNames = Object.keys( geometry.attributes );
975
+ const tmpAttributes = {};
976
+ const tmpMorphAttributes = {};
977
+ const newIndices = [];
978
+ const getters = [ 'getX', 'getY', 'getZ', 'getW' ];
979
+ const setters = [ 'setX', 'setY', 'setZ', 'setW' ];
980
+
981
+ // Initialize the arrays, allocating space conservatively. Extra
982
+ // space will be trimmed in the last step.
983
+ for ( let i = 0, l = attributeNames.length; i < l; i ++ ) {
984
+
985
+ const name = attributeNames[ i ];
986
+ const attr = geometry.attributes[ name ];
987
+
988
+ tmpAttributes[ name ] = new attr.constructor(
989
+ new attr.array.constructor( attr.count * attr.itemSize ),
990
+ attr.itemSize,
991
+ attr.normalized
992
+ );
993
+
994
+ const morphAttributes = geometry.morphAttributes[ name ];
995
+ if ( morphAttributes ) {
996
+
997
+ if ( ! tmpMorphAttributes[ name ] ) tmpMorphAttributes[ name ] = [];
998
+ morphAttributes.forEach( ( morphAttr, i ) => {
999
+
1000
+ const array = new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize );
1001
+ tmpMorphAttributes[ name ][ i ] = new morphAttr.constructor( array, morphAttr.itemSize, morphAttr.normalized );
1002
+
1003
+ } );
1004
+
1005
+ }
1006
+
1007
+ }
1008
+
1009
+ // convert the error tolerance to an amount of decimal places to truncate to
1010
+ const halfTolerance = tolerance * 0.5;
1011
+ const exponent = Math.log10( 1 / tolerance );
1012
+ const hashMultiplier = Math.pow( 10, exponent );
1013
+ const hashAdditive = halfTolerance * hashMultiplier;
1014
+ for ( let i = 0; i < vertexCount; i ++ ) {
1015
+
1016
+ const index = indices ? indices.getX( i ) : i;
1017
+
1018
+ // Generate a hash for the vertex attributes at the current index 'i'
1019
+ let hash = '';
1020
+ for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
1021
+
1022
+ const name = attributeNames[ j ];
1023
+ const attribute = geometry.getAttribute( name );
1024
+ const itemSize = attribute.itemSize;
1025
+
1026
+ for ( let k = 0; k < itemSize; k ++ ) {
1027
+
1028
+ // double tilde truncates the decimal value
1029
+ hash += `${ ~ ~ ( attribute[ getters[ k ] ]( index ) * hashMultiplier + hashAdditive ) },`;
1030
+
1031
+ }
1032
+
1033
+ }
1034
+
1035
+ // Add another reference to the vertex if it's already
1036
+ // used by another index
1037
+ if ( hash in hashToIndex ) {
1038
+
1039
+ newIndices.push( hashToIndex[ hash ] );
1040
+
1041
+ } else {
1042
+
1043
+ // copy data to the new index in the temporary attributes
1044
+ for ( let j = 0, l = attributeNames.length; j < l; j ++ ) {
1045
+
1046
+ const name = attributeNames[ j ];
1047
+ const attribute = geometry.getAttribute( name );
1048
+ const morphAttributes = geometry.morphAttributes[ name ];
1049
+ const itemSize = attribute.itemSize;
1050
+ const newArray = tmpAttributes[ name ];
1051
+ const newMorphArrays = tmpMorphAttributes[ name ];
1052
+
1053
+ for ( let k = 0; k < itemSize; k ++ ) {
1054
+
1055
+ const getterFunc = getters[ k ];
1056
+ const setterFunc = setters[ k ];
1057
+ newArray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) );
1058
+
1059
+ if ( morphAttributes ) {
1060
+
1061
+ for ( let m = 0, ml = morphAttributes.length; m < ml; m ++ ) {
1062
+
1063
+ newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttributes[ m ][ getterFunc ]( index ) );
1064
+
1065
+ }
1066
+
1067
+ }
1068
+
1069
+ }
1070
+
1071
+ }
1072
+
1073
+ hashToIndex[ hash ] = nextIndex;
1074
+ newIndices.push( nextIndex );
1075
+ nextIndex ++;
1076
+
1077
+ }
1078
+
1079
+ }
1080
+
1081
+ // generate result BufferGeometry
1082
+ const result = geometry.clone();
1083
+ for ( const name in geometry.attributes ) {
1084
+
1085
+ const tmpAttribute = tmpAttributes[ name ];
1086
+
1087
+ result.setAttribute( name, new tmpAttribute.constructor(
1088
+ tmpAttribute.array.slice( 0, nextIndex * tmpAttribute.itemSize ),
1089
+ tmpAttribute.itemSize,
1090
+ tmpAttribute.normalized,
1091
+ ) );
1092
+
1093
+ if ( ! ( name in tmpMorphAttributes ) ) continue;
1094
+
1095
+ for ( let j = 0; j < tmpMorphAttributes[ name ].length; j ++ ) {
1096
+
1097
+ const tmpMorphAttribute = tmpMorphAttributes[ name ][ j ];
1098
+
1099
+ result.morphAttributes[ name ][ j ] = new tmpMorphAttribute.constructor(
1100
+ tmpMorphAttribute.array.slice( 0, nextIndex * tmpMorphAttribute.itemSize ),
1101
+ tmpMorphAttribute.itemSize,
1102
+ tmpMorphAttribute.normalized,
1103
+ );
1104
+
1105
+ }
1106
+
1107
+ }
1108
+
1109
+ // indices
1110
+
1111
+ result.setIndex( newIndices );
1112
+
1113
+ return result;
1114
+
1115
+ }
1116
+
1117
+ function pannellogeometry(gcad, l, a, p, r1 = 0, r2 = 0, r3 = 0, r4 = 0, b = 0, npt = 2) {
1118
+ let ky = hash(`pg--${l}|${a}|${p}|${r1}|${r2}|${r3}|${r4}|${b}|${npt}`);
1119
+ l -= 0.01;
1120
+ a -= 0.01;
1121
+ p -= 0.01;
1122
+
1123
+ if (!gcad.geo[ky]) {
1124
+ l -= b * 2;
1125
+ a -= b * 2;
1126
+ p -= b * 2;
1127
+ let tm = getshape();
1128
+
1129
+ const vv = [
1130
+ { x: r4, y: 0 }, // 0
1131
+ { x: a - r1, y: 0 }, //1
1132
+ { x: a, y: r1 }, //2
1133
+ { x: a, y: p - r2 }, //3
1134
+ { x: a - r2, y: p }, //4
1135
+ { x: r3, y: p }, //5
1136
+ { x: 0, y: p - r3 }, //6
1137
+ { x: 0, y: r4 }, //7
1138
+ ];
1139
+ tm.addpt(vv[0]);
1140
+ tm.addpt(vv[1]);
1141
+ if (r1) {
1142
+ tm.addpt(raccordabezier(vv[0], vv[1], vv[2], vv[3], npt));
1143
+ }
1144
+ tm.addpt(vv[2]);
1145
+ tm.addpt(vv[3]);
1146
+ if (r2) {
1147
+ tm.addpt(raccordabezier(vv[2], vv[3], vv[4], vv[5], npt));
1148
+ }
1149
+ tm.addpt(vv[4]);
1150
+ tm.addpt(vv[5]);
1151
+ if (r3) {
1152
+ tm.addpt(raccordabezier(vv[4], vv[5], vv[6], vv[7], npt));
1153
+ }
1154
+ tm.addpt(vv[6]);
1155
+ tm.addpt(vv[7]);
1156
+ if (r2) {
1157
+ tm.addpt(raccordabezier(vv[6], vv[7], vv[0], vv[1], npt));
1158
+ }
1159
+ tm.removeduplicate(0.01);
1160
+ let pts = tm.pt;
1161
+ //pts = tm.computenormals(0.01);
1162
+ //const pts = tm.computenormals(0);
1163
+ const shape = new THREE.Shape();
1164
+ shape.moveTo(pts[0].x, pts[0].y);
1165
+ for (let i = 1; i < pts.length; i++) {
1166
+ shape.lineTo(pts[i].x, pts[i].y);
1167
+ }
1168
+ shape.lineTo(pts[0].x, pts[0].y);
1169
+ const extrudeSettings = {
1170
+ depth: l, // Estrusione lungo l'asse Z
1171
+ bevelEnabled: b > 0, // Attiva il bevel se r è valido
1172
+ bevelThickness: b,
1173
+ bevelSize: b,
1174
+ bevelSegments: 1,
1175
+ };
1176
+ let xgeo = new THREE.ExtrudeGeometry(shape, extrudeSettings);
1177
+ xgeo = mergeVertices(xgeo);
1178
+ xgeo.computeVertexNormals();
1179
+ const uv = xgeo.attributes.uv;
1180
+ let x1 = Math.random() * l;
1181
+ let y1 = Math.random() * (a + p);
1182
+ for (let i = 0; i < uv.count; i++) {
1183
+ uv.setXY(i,
1184
+ uv.getX(i) + x1,
1185
+ uv.getY(i) + y1
1186
+ );
1187
+ }
1188
+ uv.needsUpdate = true;
1189
+ gcad.geo[ky] = xgeo;
1190
+
1191
+ }
1192
+ return gcad.geo[ky];
1193
+ }
1194
+
1195
+
1196
+
1197
+ async function getpannello(gcad, orientamento, x, y, z, mat1, mat2, options) {
1198
+ if (!options) options = {};
1199
+ let { r, r1, r2, r3, r4, b, npt } = options;
1200
+
1201
+ // Normalizza i raggi degli angoli
1202
+ r1 = r1 || r || 0;
1203
+ r2 = r2 || r || 0;
1204
+ r3 = r3 || r || 0;
1205
+ r4 = r4 || r || 0;
1206
+ npt = npt || 2;
1207
+ b = b || 0;
1208
+ if (b >= x / 2 || b >= y / 2 || b >= z / 2) b = 0;
1209
+
1210
+
1211
+ // Calcola dimensioni in base all'orientamento
1212
+ let l, a, p;
1213
+ orientamento = orientamento.trim().toUpperCase();
1214
+ l = orientamento[0] == 'L' ? x : orientamento[0] == 'A' ? y : z;
1215
+ a = orientamento[1] == 'L' ? x : orientamento[1] == 'A' ? y : z;
1216
+ p = orientamento[2] == 'L' ? x : orientamento[2] == 'A' ? y : z;
1217
+
1218
+ let grp = new THREE.Group();
1219
+
1220
+ // Aggiunge linee di bordo se richiesto
1221
+ if (!options.nolines) {
1222
+ let segments = edgesfromgeometry(new THREE.BoxGeometry(x, y, z));
1223
+ segments.position.set(x / 2, y / 2, z / 2);
1224
+ grp.add(segments);
1225
+ }
1226
+ let geometry = pannellogeometry(gcad, l, a, p, r1, r2, r3, r4, b, npt);
1227
+
1228
+ let mesh = getmesh(geometry, [mat2 || mgreen, mat1 || mwhite$1]);
1229
+ mesh.name = 'pannello';
1230
+ mesh.layers.set(1);
1231
+ let m2 = mesh;
1232
+ if (b) {
1233
+
1234
+ mesh.position.set(b, b, b);
1235
+ m2 = new THREE.Group();
1236
+ m2.add(mesh);
1237
+ }
1238
+
1239
+ mesh = meshrotate(orientamento, m2, l, a, p);
1240
+ grp.add(mesh);
1241
+ return grp;
1242
+ }
1243
+
1244
+ function infoestrudi(shape, hshape, pts, options) {
1245
+ if (!options) options = {};
1246
+ if (!pts) pts = [];
1247
+ // calcola le posizioni;
1248
+ let p0 = options.p0 || 0;
1249
+ let coeffbase1 = Math.tan(options.coeffbase1 * PIF || 0);
1250
+ let coeffbase2 = Math.tan(options.coeffbase2 * PIF || 0);
1251
+ let p1 = hshape || options.p1 || 20;
1252
+ let coefftop1 = Math.tan(options.coefftop1 * PIF || 0);
1253
+ let coefftop2 = Math.tan(options.coefftop2 * PIF || 0);
1254
+ // calcola x,y minimo e massimo della shape
1255
+ let { mi, ma } = shape.pt.reduce((t, e) => {
1256
+ // e.x,e.y punto
1257
+ t.mi.x = Math.min(t.mi.x, e.x);
1258
+ t.mi.y = Math.min(t.mi.y, e.y);
1259
+ t.ma.x = Math.max(t.ma.x, e.x);
1260
+ t.ma.y = Math.max(t.ma.y, e.y);
1261
+ return t;
1262
+ }, { mi: { x: 1e9, y: 1e9 }, ma: { x: -1e9, y: -1e9 } });
1263
+ // calcola lunghezza minima e massima, e calcola la z per ogni punto
1264
+ let lmax = 0;
1265
+ let lmin = 1e9;
1266
+ function getpars(p) {
1267
+ p.z1 = getzeta(p.x, p.y, p0, coeffbase1, coeffbase2);
1268
+ p.z2 = getzeta(p.x, p.y, p1, coefftop1, coefftop2);
1269
+ p.l = Math.abs(p.z2 - p.z1);
1270
+ }
1271
+ for (let p of pts) {
1272
+ getpars(p);
1273
+ }
1274
+ getpars(mi);
1275
+ getpars(ma);
1276
+ let rect = [{ x: mi.x, y: mi.y },
1277
+ { x: ma.x, y: mi.y },
1278
+ { x: ma.x, y: ma.y },
1279
+ { x: mi.x, y: ma.y }];
1280
+ for (let r of rect) {
1281
+ getpars(r);
1282
+ if (r.l > lmax) lmax = r.l;
1283
+ if (r.l < lmin) lmin = r.l;
1284
+ }
1285
+ return {
1286
+ aini: options.coeffbase1 || 0,
1287
+ aini2: options.coeffbase2 || 0,
1288
+ afin: options.coefftop1 || 0,
1289
+ afin2: options.coefftop2 || 0,
1290
+
1291
+ pts,
1292
+ mi,
1293
+ ma,
1294
+ rect,
1295
+ dimx: ma.x - mi.x,
1296
+ dimy: ma.y - mi.y,
1297
+ lnom: p1 - p0,
1298
+ lmax,
1299
+ lmin,
1300
+ lmed: (lmax + lmin) / 2
1301
+ }
1302
+ }
1303
+
1304
+
1305
+ function getzeta(x, y, zbase, cx, cy) {
1306
+ return x * cx + y * cy + zbase;
1307
+ }
1308
+
1309
+ function sidegeomfromshapes(gcad, ky, s1, s2, invert = false) {
1310
+ // la chiave per questa funzione è complessa e va ricavata prima!
1311
+
1312
+ if (!s1 || !s2) return;
1313
+ if (s1.length !== s2.length) throw new Error('shapes with different length');
1314
+ if (s1.length < 2) throw new Error('I percorsi devono contenere almeno due punti ciascuno.');
1315
+ if (!gcad.geo[ky]) {
1316
+ const positions = [];
1317
+ // const normals = [];
1318
+ const uvs = [];
1319
+ const indices = [];
1320
+ const np = s1.length;
1321
+ const addpts = (ss) => {
1322
+ for (const s of ss) {
1323
+ positions.push(s.x, s.y, s.z);
1324
+ // normals.push(s.nx, s.ny, s.nz);
1325
+ uvs.push(s.u, s.v);
1326
+ }
1327
+ };
1328
+
1329
+ // Funzione per verificare se due punti sono uguali
1330
+ const equalpos = (p1, p2) => p1.x === p2.x && p1.y === p2.y && p1.z === p2.z;
1331
+
1332
+ const addindexquad = (i1, i2, i3, i4) => {
1333
+ if (invert) {
1334
+ indices.push(i1, i3, i2, i3, i4, i2);
1335
+
1336
+ } else {
1337
+ indices.push(i1, i2, i3, i3, i2, i4);
1338
+ }
1339
+ };
1340
+
1341
+ // Aggiungi tutti i punti di s1 e s2
1342
+ addpts(s1);
1343
+ addpts(s2);
1344
+
1345
+ // Creazione degli indici per i triangoli
1346
+ for (let i = 0; i < np - 1; i++) {
1347
+ const j = (i + 1); // raccorda sempre 'ultimo punto con il primo!
1348
+
1349
+ // Verifica se i punti consecutivi di s1 sono duplicati
1350
+ if (equalpos(s1[i], s1[j])) {
1351
+ continue;
1352
+ }
1353
+
1354
+ // Aggiungi i triangoli del quadrilatero
1355
+ addindexquad(i, j, i + np, j + np);
1356
+ }
1357
+
1358
+ // Creazione della BufferGeometry
1359
+ const geometry = new THREE.BufferGeometry();
1360
+
1361
+ // Impostazione degli attributi
1362
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
1363
+ // geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
1364
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
1365
+ geometry.setIndex(indices);
1366
+
1367
+ //Computazione delle normali (opzionale, se non già calcolate)
1368
+ geometry.computeVertexNormals();
1369
+ gcad.geo[ky] = geometry;
1370
+ }
1371
+ return gcad.geo[ky];
1372
+
1373
+ }
1374
+ function bottomgeomfromshape(gcad, inverti, outer, holes, c = 0, a = 0, b = 0) {
1375
+ // determina chiave
1376
+ let ky = `bg:${c}|${a}${b}|${outer.key}|${inverti}`;
1377
+ for (var h of holes) ky = `${ky}|${h.key}`;
1378
+ ky = hash(ky);
1379
+ if (!gcad.geo[ky]) {
1380
+ let pos = [],
1381
+ hl = [];
1382
+ let triangles;
1383
+ let cc = outer.pt.length;
1384
+ pos = outer.vec;
1385
+
1386
+ if (Array.isArray(holes)) {
1387
+ for (var h of holes) {
1388
+ hl.push(cc);
1389
+ cc += h.pt.length;
1390
+ pos = [...pos, ...h.vec];
1391
+ }
1392
+ triangles = earcut(pos, hl);
1393
+ } else {
1394
+ triangles = earcut(pos);
1395
+ }
1396
+ const geometry = new THREE.BufferGeometry();
1397
+ const vertices = [];
1398
+ const uvs = [];
1399
+ const indices = [];
1400
+ // Aggiungi i vertici per ogni triangolo
1401
+ for (let i = 0; i < triangles.length; i++) {
1402
+ const index = triangles[i];
1403
+ const x = pos[index * 2];
1404
+ const y = -pos[index * 2 + 1];
1405
+ const z = getzeta(x, y, c, a, b);// a * x + b * y + c; // Calcolo della coordinata z
1406
+
1407
+ // Aggiungi il vertice 3D
1408
+ vertices.push(x, y, z);
1409
+
1410
+ // Imposta le coordinate UV (x, y)
1411
+ uvs.push(y, x);
1412
+ }
1413
+ for (let i = 0; i < triangles.length; i += 3) {
1414
+ if (inverti) {
1415
+ indices.push(i, i + 2, i + 1);
1416
+ } else {
1417
+ indices.push(i, i + 1, i + 2);
1418
+ }
1419
+
1420
+ }
1421
+
1422
+
1423
+ // Calcola la normale una sola volta
1424
+ //const v0 = new THREE.Vector3(pos[triangles[0] * 2], pos[triangles[0] * 2 + 1], a * pos[triangles[0] * 2] + b * pos[triangles[0] * 2 + 1] + c);
1425
+ //const v1 = new THREE.Vector3(pos[triangles[1] * 2], pos[triangles[1] * 2 + 1], a * pos[triangles[1] * 2] + b * pos[triangles[1] * 2 + 1] + c);
1426
+ //const v2 = new THREE.Vector3(pos[triangles[2] * 2], pos[triangles[2] * 2 + 1], a * pos[triangles[2] * 2] + b * pos[triangles[2] * 2 + 1] + c);
1427
+
1428
+ // Calcola i vettori del triangolo
1429
+ //const edge1 = new THREE.Vector3().subVectors(v1, v0);
1430
+ //const edge2 = new THREE.Vector3().subVectors(v2, v0);
1431
+
1432
+ // Calcola il prodotto vettoriale per ottenere la normale
1433
+ //const normal = new THREE.Vector3().crossVectors(edge1, edge2).normalize();
1434
+
1435
+ // Aggiungi la normale per ogni vertice del triangolo
1436
+ //const normals = [];
1437
+ //for (let i = 0; i < triangles.length; i++) {
1438
+ // normals.push(normal.x, normal.y, normal.z);
1439
+ //}
1440
+
1441
+ // Imposta i vertici nella geometria
1442
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
1443
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
1444
+ geometry.setIndex(indices);
1445
+
1446
+ //geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
1447
+ geometry.computeVertexNormals();
1448
+ gcad.geo[ky] = geometry;
1449
+ }
1450
+ return gcad.geo[ky];
1451
+ }
1452
+
1453
+
1454
+ function sidegeomfrompat(gcad, pbase, pat, invert) {
1455
+ let ky = `bsg:${pbase.key}|${pat.key}|${invert}`;
1456
+ ky = hash(ky);
1457
+ if (!gcad.geo[ky]) {
1458
+ const positions = [];
1459
+ const uvs = [];
1460
+ const indices = [];
1461
+ const np = pbase.length;
1462
+ const lp = pat.pt.length;
1463
+ // Funzione per verificare se due punti sono uguali
1464
+ const equalpos = (p1, p2) => p1.x === p2.x && p1.y === p2.y && p1.z === p2.z;
1465
+
1466
+ let l0 = 0;
1467
+ for (let ii = 0; ii < lp; ii++) {
1468
+ const addpts = (ss, mat, deltav) => {
1469
+ for (const s of ss) {
1470
+ let p = new THREE.Vector3(s.x, s.y, s.z);
1471
+ p.applyMatrix4(mat);
1472
+ positions.push(p.x, p.y, p.z);
1473
+ uvs.push(s.u, s.v + deltav);
1474
+ }
1475
+ };
1476
+ let seg1 = pat.infosegmento(ii, true);
1477
+ let obj = new THREE.Object3D();
1478
+ obj.position.set(seg1.x, 0, seg1.y);
1479
+ obj.rotation.set(0, (90 - seg1.ang) * PIF, 0);
1480
+
1481
+ //obj.position.set(seg1.y, -seg1.x, 0);
1482
+
1483
+ obj.updateMatrix();
1484
+ addpts(pbase, obj.matrix, l0);
1485
+ l0 += seg1.l;
1486
+ }
1487
+
1488
+ // Creazione degli indici per i triangoli
1489
+ for (let li = 0; li < lp - 1; li++) {
1490
+
1491
+ for (let i = 0; i < np - 1; i++) {
1492
+ const addindexquad = (i1, i2, i3, i4) => {
1493
+ if (invert) {
1494
+ indices.push(i1, i3, i2, i3, i4, i2);
1495
+ } else {
1496
+ indices.push(i1, i2, i3, i3, i2, i4);
1497
+ }
1498
+ };
1499
+ const j = (i + 1); // raccorda sempre 'ultimo punto con il primo!
1500
+ // Verifica se i punti consecutivi di s1 sono duplicati
1501
+ if (equalpos(pbase[i], pbase[j])) {
1502
+ continue;
1503
+ }
1504
+ addindexquad(
1505
+ i + li * np,
1506
+ j + li * np,
1507
+ i + (li + 1) * np,
1508
+ j + (li + 1) * np
1509
+ );
1510
+ }
1511
+ }
1512
+ const geometry = new THREE.BufferGeometry();
1513
+ geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
1514
+ // geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
1515
+ geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
1516
+ geometry.setIndex(indices);
1517
+ geometry.computeVertexNormals();
1518
+ gcad[ky] = geometry;
1519
+ }
1520
+ return gcad[ky];
1521
+ }
1522
+
1523
+ function estrusorotate(orientamento, mesh, z = 0) {
1524
+ switch (orientamento.trim().toUpperCase().slice(0, 1)) {
1525
+ case 'A':
1526
+ mesh.rotation.x = -Math.PI / 2;
1527
+ break;
1528
+ case 'L':
1529
+ mesh.rotation.y = Math.PI / 2;
1530
+ mesh.rotation.z = Math.PI;
1531
+ break;
1532
+ // mesh.position.set(0, z, 0);
1533
+ // break;
1534
+ // case 'ALP':
1535
+ // mesh.rotation.x = Math.PI / 2;
1536
+ // mesh.rotation.y = Math.PI / 2;
1537
+ // break;
1538
+ // case 'APL':
1539
+ // mesh.rotation.z = Math.PI / 2;
1540
+ // mesh.rotation.y = Math.PI;
1541
+ // mesh.position.set(0, 0, z);
1542
+ // break;
1543
+ // case 'PLA':
1544
+ // //mesh.rotation.y = -Math.PI / 2;
1545
+ // mesh.rotation.x = -Math.PI / 2;
1546
+ // mesh.rotation.z = - Math.PI / 2;
1547
+
1548
+ // break;
1549
+ // case 'PAL':
1550
+ // mesh.rotation.y = -Math.PI / 2;
1551
+ // mesh.position.set(z, 0, 0);
1552
+ // break;
1553
+ // case 'LAP':
1554
+ // break;
1555
+ case 'P':
1556
+ mesh.rotation.z = Math.PI / 2;
1557
+
1558
+ break;
1559
+ }
1560
+ return mesh;
1561
+ }
1562
+
1563
+
1564
+ /**
1565
+ * Crea una geometria estrusa con opzioni avanzate
1566
+ * @param {string} orient - Orientamento dell'estrusione
1567
+ * @param {number} hshape - Altezza dell'estrusione
1568
+ * @param {Object} shape - Forma base
1569
+ * @param {Array} holes - Array di fori
1570
+ * @param {Array} mats - Array di materiali
1571
+ * @param {Object} options - Opzioni di configurazione
1572
+ * @returns {THREE.Group} Gruppo contenente la geometria estrusa
1573
+ */
1574
+
1575
+
1576
+ function estruso(gcad, orient, hshape, shape, holes, mats, options) {
1577
+ if (!options) {
1578
+ options = {};
1579
+ }
1580
+ if (!mats) mats = [];
1581
+ // Estrae i parametri dalle opzioni
1582
+ let p0 = options.p0 || 0;
1583
+
1584
+ let coeffbase1 = Math.tan(options.coeffbase1 * PIF || 0);
1585
+ let coeffbase2 = Math.tan(options.coeffbase2 * PIF || 0);
1586
+ let p1 = hshape || options.p1 || 20;
1587
+ let coefftop1 = Math.tan(options.coefftop1 * PIF || 0);
1588
+ let coefftop2 = Math.tan(options.coefftop2 * PIF || 0);
1589
+ let open = options.open || false;
1590
+ let grp = new THREE.Group();
1591
+
1592
+ // Crea le geometrie di base
1593
+
1594
+
1595
+
1596
+ // Aggiunge facce base e top se richiesto
1597
+ if (!options.nobase) {
1598
+ let g1 = bottomgeomfromshape(gcad, false, shape, open ? [] : holes, p0, coeffbase1, coeffbase2);
1599
+ if (!options.nolines) grp.add(edgesfromgeometry(g1));
1600
+ grp.add(getmesh(g1, mats[0] || mwhite$1));
1601
+ }
1602
+ if (!options.notop) {
1603
+ let g3 = bottomgeomfromshape(gcad, true, shape, open ? [] : holes, p1, coefftop1, coefftop2);
1604
+ grp.add(getmesh(g3, mats[1] || mats[0] || mwhite$1));
1605
+ if (!options.nolines) grp.add(edgesfromgeometry(g3));
1606
+ }
1607
+
1608
+ // Gestione dei lati e dei fori
1609
+ if (!options.nosides) {
1610
+ let pat1 = shape.to3d(0, p0, coeffbase1, coeffbase2, open);
1611
+ let pat2 = shape.to3d(0, p1, coefftop1, coefftop2, open);
1612
+ let ky = `${shape.key}|${p0}|${p1}|${coeffbase1}|${coeffbase2}|${coefftop1}|${coefftop2}|${open}`;
1613
+ let geo1 = sidegeomfromshapes(gcad, ky, pat1, pat2, false);
1614
+
1615
+ if (!options.nolines && !options.nosidelines) {
1616
+ grp.add(edgesfromgeometry(geo1));
1617
+ }
1618
+ grp.add(getmesh(geo1, mats[2] || mats[0] || mwhite$1));
1619
+
1620
+ // Gestione dei fori
1621
+ if (holes && !open) {
1622
+ for (var h of holes) {
1623
+ pat1 = h.to3d(0, p0, coeffbase1, coeffbase2);
1624
+ pat2 = h.to3d(0, p1, coefftop1, coefftop2);
1625
+ ky = `${h.key}|${p0}|${p1}|${coeffbase1}|${coeffbase2}|${coefftop1}|${coefftop2}`;
1626
+ let geo2 = sidegeomfromshapes(gcad, ky, pat1, pat2, true);
1627
+ if (!options.nolines && !options.nosidelines) {
1628
+ grp.add(edgesfromgeometry(geo2));
1629
+ }
1630
+ grp.add(getmesh(geo2, mats[3] || mats[0] || mwhite$1));
1631
+ }
1632
+ }
1633
+ }
1634
+ return estrusorotate(orient, grp, hshape);
1635
+ }
1636
+
1637
+ function revolve(gcad, shape, orient, mat, options) {
1638
+ if (!options) {
1639
+ options = {};
1640
+ }
1641
+ let segmenti = options.segmenti ?? 12;
1642
+ let ky = hash(`rev|${shape.key}|${orient}|${segmenti}`);
1643
+ let geometria = gcad.geo[ky];
1644
+ let grp = new THREE.Group();
1645
+ if (!geometria) {
1646
+ const punti3D = shape.pt.map(p => new THREE.Vector3(p.x, p.y, 0));
1647
+ // Creiamo la geometria di rivoluzione
1648
+ geometria = new THREE.LatheGeometry(
1649
+ punti3D,
1650
+ segmenti,
1651
+ 0,
1652
+ Math.PI * 2
1653
+ );
1654
+ gcad.geo[ky] = geometria;
1655
+ }
1656
+ const mesh = new THREE.Mesh(geometria, mat);
1657
+ grp.add(mesh);
1658
+ if (!options.nolines && !options.nosidelines) {
1659
+ grp.add(edgesfromgeometry(geometria));
1660
+ }
1661
+ return grp;
1662
+ // todo: orienta il gruppo
1663
+ //return estrusorotate(orient, grp);
1664
+
1665
+ }
1666
+
1667
+
1668
+
1669
+ /**
1670
+ * Crea una geometria estrusa con opzioni avanzate
1671
+ * @param {string} orient - Orientamento dell'estrusione
1672
+ * @param {number} hshape - Altezza dell'estrusione
1673
+ * @param {Object} shape - Forma base
1674
+ * @param {Array} holes - Array di fori
1675
+ * @param {Array} mats - Array di materiali
1676
+ * @param {Object} options - Opzioni di configurazione
1677
+ * @returns {THREE.Group} Gruppo contenente la geometria estrusa
1678
+ */
1679
+
1680
+
1681
+ function estrusopat(gcad, orient, pat, shape, mats, options) {
1682
+ if (!options) {
1683
+ options = {};
1684
+ }
1685
+ let invert = options.invert;
1686
+ //
1687
+ if (!mats) mats = [];
1688
+ let open = options.open;
1689
+ // Estrae i parametri dalle opzioni
1690
+ if (!pat.pt?.length) return;
1691
+ let pbase = shape.to3d(0, 0, 0, 0, open);
1692
+ let grp = new THREE.Group();
1693
+
1694
+ if (!options.nobase) {
1695
+ let gb = new THREE.Group();
1696
+ let g1 = bottomgeomfromshape(gcad, false, shape, [], 0, 0, 0);
1697
+ gb.add(getmesh(g1, mats[0] || mwhite$1));
1698
+ gb.add(edgesfromgeometry(g1));
1699
+ let seg1 = pat.infosegmento(0, true);
1700
+ grp.add(posiziona(gb, { sl: seg1.x, sp: seg1.y, sa: 0, ay: (90 - seg1.ang) }));
1701
+ }
1702
+
1703
+ if (!options.notop) {
1704
+ let gt = new THREE.Group();
1705
+ let g2 = bottomgeomfromshape(gcad, true, shape, [], 0, 0, 0);
1706
+ gt.add(getmesh(g2, mats[1] || mats[0] || mwhite$1));
1707
+ gt.add(edgesfromgeometry(g2));
1708
+ let seg1 = pat.infosegmento(pat.pt.length - 1, true);
1709
+ grp.add(posiziona(gt, { sl: seg1.x, sp: seg1.y, sa: 0, ay: (90 - seg1.ang) }));
1710
+ }
1711
+
1712
+
1713
+
1714
+ const geo1 = sidegeomfrompat(gcad, pbase, pat, invert);
1715
+ if (!options.nolines && !options.nosidelines) {
1716
+ grp.add(edgesfromgeometry(geo1));
1717
+ }
1718
+ grp.add(getmesh(geo1, mats[2] || mats[0] || mwhite$1));
1719
+
1720
+
1721
+ // for (let ii = 1; ii < pat.pt.length - 1; ii++) {
1722
+
1723
+ // let seg1 = pat.infosegmento(ii, true);
1724
+
1725
+ // grp.add(posiziona(gb, { sp: seg1.y, sa: - seg1.x, ax: 90 - seg1.ang }));
1726
+ // }
1727
+
1728
+
1729
+ return estrusorotate(orient, grp, 0);
1730
+ }
1731
+
1732
+ async function createCircleTexture(gcad, size = 64, color = 'rgba(255,0,0,1)') {
1733
+ const ky = `ct|${size}|${color}`;
1734
+ if (!gcad.textures[ky]) {
1735
+ const canvas = document.createElement('canvas');
1736
+ canvas.width = canvas.height = size;
1737
+
1738
+ const ctx = canvas.getContext('2d');
1739
+ const radius = size / 2;
1740
+
1741
+ // Sfondo trasparente
1742
+ ctx.clearRect(0, 0, size, size);
1743
+
1744
+ // Cerchio pieno al centro
1745
+ ctx.fillStyle = color;
1746
+ ctx.beginPath();
1747
+ ctx.arc(radius, radius, radius, 0, Math.PI * 2);
1748
+ ctx.fill();
1749
+
1750
+ const texture = new THREE.CanvasTexture(canvas);
1751
+ texture.needsUpdate = true;
1752
+ gcad.textures[ky] = texture;
1753
+ }
1754
+
1755
+ return gcad.textures[ky];
1756
+ }
1757
+
1758
+
1759
+ async function spritemat(gcad, file) {
1760
+ // le varie opzioni per smat sono sx,sy,rot,rough,metal,env
1761
+ try {
1762
+ let tm = {
1763
+ transparent: true,
1764
+ depthTest: false,
1765
+ depthWrite: false,
1766
+ fog: false,
1767
+ toneMapped: false, // ❗ importante con HDR
1768
+ };
1769
+ if (file.startsWith("#")) {
1770
+ tm.color = file;
1771
+ } else {
1772
+ let tx = await gcad.tex(file, 1000, 1000);
1773
+ if (tx) {
1774
+ tm.map = tx;
1775
+ } else {
1776
+ tm.color = "black";
1777
+ }
1778
+ }
1779
+ return new THREE.SpriteMaterial(tm);
1780
+ } catch (error) {
1781
+ return mwhite;
1782
+ }
1783
+ }
1784
+
1785
+ function getsprite0(ispunto, x, y, z, mat, options = {}) {
1786
+ if (!options) options = {};
1787
+ let { size = 50, pick = false, screen = false, sizex, sizey } = options;
1788
+
1789
+ const sprite = new THREE.Sprite(mat);
1790
+ sprite.position.set(0, 0, 0);
1791
+ sprite.userData.issprite = true;
1792
+ sprite.userData.ispunto = ispunto;
1793
+ sprite.userData.ispick = pick;
1794
+ sprite.userData.isScreen = screen;
1795
+ sprite.renderOrder = 999;
1796
+ sprite.layers.set(29);
1797
+ sprite.name = ispunto ? '_punto' : '_sprite';
1798
+ let gr = new THREE.Group();
1799
+ gr.position.set(x, y, z);
1800
+ gr.scale.set(sizex || size, sizey || sizex || size, 1); // dimensione apparente (adatta al tuo uso)
1801
+ gr.add(sprite);
1802
+ gr.traverse(obj => {
1803
+ if (obj.material) {
1804
+ obj.material.depthTest = false;
1805
+ obj.material.depthWrite = false;
1806
+ }
1807
+ });
1808
+ gr.renderOrder = 999;
1809
+ return gr;
1810
+
1811
+ }
1812
+
1813
+ async function getpunto(gcad, x, y, z, color = 'yellow', options) {
1814
+ if (!options) options = { size: 20 };
1815
+ options.screen = true;
1816
+ const texture = await createCircleTexture(gcad, 64, color); // rosso sfumato
1817
+ const mat = new THREE.SpriteMaterial({
1818
+ map: texture,
1819
+ transparent: true,
1820
+
1821
+ fog: false,
1822
+ toneMapped: false, // ❗ importante con HDR
1823
+ });
1824
+ return getsprite0(true, x, y, z, mat, options)
1825
+ }
1826
+
1827
+ async function getsprite(gcad, x, y, z, mat, options = {}) {
1828
+ return getsprite0(false, x, y, z, mat, options);
1829
+ }
1830
+
1831
+ export { SIDE, addmovpivot, creategroup, deletegroup, edgesfromgeometry, estruso, estrusopat, get3dshape, getbox, getcilindro, getface, getline, getlinesgeom, getmesh, getmovimento, getpannello, getpoint, getpunto, getquota, getriferimento, getsprite, gettarghetta, groupfromgeometry, infoestrudi, materialline1, materialline2, mblack, mblue, mgray1, mgray2, mgreen, mred, mwhite$1 as mwhite, posiziona, randombasemat, revolve, scaleunit, spritemat, svuotanodo };