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.
- package/bin/markcad.js +1 -1
- package/bin/markcad3d.js +1831 -0
- package/package.json +2 -1
package/bin/markcad3d.js
ADDED
|
@@ -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 };
|