@liquidcars/atlas 0.1.14 → 0.1.16
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/README.md +34 -3
- package/demo/index.html +2 -2
- package/dist/liquidcars-atlas.js +535 -14
- package/index.html +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,6 +65,21 @@ atlas.setTheme({
|
|
|
65
65
|
ui: { breadcrumbs: true, panel: true, toolbar: true, hint: false }
|
|
66
66
|
});
|
|
67
67
|
atlas.setPalette({ channels: '#58b6ec', offers: '#48c8b0', connection: '#f4edba' });
|
|
68
|
+
|
|
69
|
+
// El componente se puede modificar sin volver a crearlo.
|
|
70
|
+
atlas.addEntity({ id: 'service', name: 'Service', geometry: 'cone', p: [4, 0, 0], s: [2, 2, 2] });
|
|
71
|
+
atlas.updateEntity('service', { text: 'Servicio actualizado', c: '#ed8f98' });
|
|
72
|
+
atlas.addRelation({ from: 'service', to: 'offers', label: 'publica', mode: 'bidirectional' });
|
|
73
|
+
atlas.updateRelation(0, { mode: 'broken' });
|
|
74
|
+
atlas.removeRelation(0);
|
|
75
|
+
atlas.removeEntity('service');
|
|
76
|
+
|
|
77
|
+
atlas.addEventListener('lc-select', event => {
|
|
78
|
+
console.log('Entidad seleccionada:', event.detail.id, event.detail.entity);
|
|
79
|
+
});
|
|
80
|
+
atlas.addEventListener('lc-model-change', event => {
|
|
81
|
+
console.log('Modelo actualizado:', event.detail.kind);
|
|
82
|
+
});
|
|
68
83
|
```
|
|
69
84
|
|
|
70
85
|
El mismo bloque `theme` puede formar parte del JSON del modelo. Los estilos
|
|
@@ -73,7 +88,12 @@ principales también están expuestos como variables CSS (`--lc-bg`,
|
|
|
73
88
|
por lo que una aplicación puede adaptar la apariencia sin modificar el bundle.
|
|
74
89
|
Las propiedades `atlas.theme` y `atlas.styles` son alias prácticos de esta configuración.
|
|
75
90
|
|
|
76
|
-
Eventos disponibles: `lc-ready`, `lc-error`, `lc-select`, `lc-
|
|
91
|
+
Eventos disponibles: `lc-ready`, `lc-error`, `lc-select`, `lc-model-change`,
|
|
92
|
+
`lc-relation-mode` y `lc-disconnect`. `lc-select` incluye `{ id, entity,
|
|
93
|
+
source }` en `event.detail`; cuando se vuelve a la vista general emite `id:
|
|
94
|
+
null`. Los eventos de selección y cambios de modelo son `bubbles` y
|
|
95
|
+
`composed`, por lo que funcionan también al integrar el componente en otras
|
|
96
|
+
capas de Web Components.
|
|
77
97
|
|
|
78
98
|
## Contrato de datos
|
|
79
99
|
|
|
@@ -83,6 +103,10 @@ Eventos disponibles: `lc-ready`, `lc-error`, `lc-select`, `lc-relation-mode` y `
|
|
|
83
103
|
- `p` es la posición local respecto a `parent`; una entidad sin `parent` usa coordenadas de escena.
|
|
84
104
|
- `s` son dimensiones absolutas locales `[x, y, z]`.
|
|
85
105
|
- `relations` acepta objetos `{from, to, label}` o la forma corta `[from, to, label]`.
|
|
106
|
+
El campo visual opcional `mode` admite `directed` (por defecto),
|
|
107
|
+
`bidirectional` y `broken`. En modo bidireccional los indicadores animados
|
|
108
|
+
circulan en ambos sentidos por el mismo tubo; en modo broken el tubo aparece
|
|
109
|
+
interrumpido en su punto medio.
|
|
86
110
|
|
|
87
111
|
Geometrías incluidas en esta primera versión: `box`, `cylinder`, `sphere`, `cone`, `frustum-cone`, `frustum-pyramid`, `frustum-triangle`, `prism`, `pyramid-square`, `pyramid-triangle`, `octahedron`, `dodecahedron` e `icosahedron`. Las variantes `frustum-*` son sólidos truncados con dos caras finitas paralelas; las dimensiones `s` se aplican sobre su caja final normalizada.
|
|
88
112
|
|
|
@@ -90,6 +114,13 @@ La paleta puede cambiarse después de cargar el modelo mediante `atlas.setPalett
|
|
|
90
114
|
|
|
91
115
|
Las acciones de navegación y exploración de la toolbar también están disponibles para la página anfitriona: `zoomIn()`, `zoomOut()`, `fit()`, `frontView()`, `setShellLabels()`, `toggleShellLabels()`, `openAllContainers()` y `closeAllContainers()`. Así se puede construir una barra de controles propia sin depender de la toolbar interna.
|
|
92
116
|
|
|
117
|
+
La escena también se puede editar en caliente. `updateModel(patch)` reemplaza
|
|
118
|
+
las secciones indicadas; `addEntity`, `updateEntity` y `removeEntity` gestionan
|
|
119
|
+
entidades, mientras `addRelation`, `updateRelation` y `removeRelation`
|
|
120
|
+
gestionan relaciones. Cada operación reconstruye la geometría necesaria,
|
|
121
|
+
conserva la cámara y la selección cuando sigue siendo válida, y emite
|
|
122
|
+
`lc-model-change` con el tipo de operación y el modelo resultante.
|
|
123
|
+
|
|
93
124
|
## Distribución
|
|
94
125
|
|
|
95
126
|
El archivo `dist/liquidcars-atlas.js` es un módulo ESM autocontenido: Three.js se incluye dentro del bundle, por lo que el consumidor no necesita instalar otra dependencia para probarlo. El bundle no contiene ningún modelo de negocio ni datos de LiquidCars Atlas; los modelos se suministran aparte mediante `model`, `src` o un `<script type="application/json">`. Para una distribución privada se puede servir ese archivo desde un CDN propio o desde el mismo sitio. Para publicar en npm, después de revisar el API estable:
|
|
@@ -103,9 +134,9 @@ Una vez publicado, los consumidores podrán instalarlo con `npm install @liquidc
|
|
|
103
134
|
Para probar el paquete sin publicarlo, basta con instalar el tarball generado:
|
|
104
135
|
|
|
105
136
|
```bash
|
|
106
|
-
npm install ./liquidcars-atlas-0.1.
|
|
137
|
+
npm install ./liquidcars-atlas-0.1.15.tgz
|
|
107
138
|
```
|
|
108
139
|
|
|
109
140
|
## Estado
|
|
110
141
|
|
|
111
|
-
`0.1.
|
|
142
|
+
`0.1.16` mantiene la corrección del desplazamiento interno del texto y ancla las etiquetas a las dimensiones reales de las geometrías redondeadas. Además fija un margen superior absoluto para las etiquetas secundarias de contenedores abiertos, situado dentro del frontal del contenedor y sin depender de sus dimensiones. También corrige los sólidos truncados, normaliza sus dimensiones finales, refuerza la visibilidad de la toolbar con el atributo `toolbar="on"`/`"off"` y `setToolbarVisible()`, desacopla la paleta por defecto de nombres de dominio, añade actualización programática de la paleta con `setPalette`, expone las acciones de navegación para controles externos y mantiene siempre visibles las ayudas de interacción dentro del viewport. Esta versión añade edición en caliente de entidades y relaciones, eventos compuestos de selección y los modos de relación `directed`, `bidirectional` y `broken`.
|
package/demo/index.html
CHANGED
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
liquidcars-atlas { display: block; width: 100%; max-width: 1480px; margin: 0 auto; }
|
|
15
15
|
code { color: #85e5d3; }
|
|
16
16
|
</style>
|
|
17
|
-
<script type="module" src="../dist/liquidcars-atlas.js?v=0.1.
|
|
17
|
+
<script type="module" src="../dist/liquidcars-atlas.js?v=0.1.15"></script>
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
<header>
|
|
21
21
|
<h1>LiquidCars Atlas</h1>
|
|
22
|
-
<p>Prueba de distribución del componente <code><liquidcars-atlas></code> · v0.1.
|
|
22
|
+
<p>Prueba de distribución del componente <code><liquidcars-atlas></code> · v0.1.15</p>
|
|
23
23
|
</header>
|
|
24
24
|
<main>
|
|
25
25
|
<liquidcars-atlas id="atlas" height="720" relation-mode="selected" shell-labels="on"></liquidcars-atlas>
|
package/dist/liquidcars-atlas.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
/*! LiquidCars Atlas 0.1.
|
|
1
|
+
/*! LiquidCars Atlas 0.1.16 | MIT */
|
|
2
2
|
|
|
3
3
|
// src/three.core.js
|
|
4
|
+
/**
|
|
5
|
+
* @license
|
|
6
|
+
* Copyright 2010-2025 Three.js Authors
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
*/
|
|
4
9
|
var REVISION = "180";
|
|
5
10
|
var CullFaceNone = 0;
|
|
6
11
|
var CullFaceBack = 1;
|
|
@@ -221,6 +226,7 @@ var EventDispatcher = class {
|
|
|
221
226
|
}
|
|
222
227
|
};
|
|
223
228
|
var _lut = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df", "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff"];
|
|
229
|
+
var _seed = 1234567;
|
|
224
230
|
var DEG2RAD = Math.PI / 180;
|
|
225
231
|
var RAD2DEG = 180 / Math.PI;
|
|
226
232
|
function generateUUID() {
|
|
@@ -237,9 +243,102 @@ function clamp(value, min, max) {
|
|
|
237
243
|
function euclideanModulo(n, m) {
|
|
238
244
|
return (n % m + m) % m;
|
|
239
245
|
}
|
|
246
|
+
function mapLinear(x, a1, a2, b1, b2) {
|
|
247
|
+
return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
|
|
248
|
+
}
|
|
249
|
+
function inverseLerp(x, y, value) {
|
|
250
|
+
if (x !== y) {
|
|
251
|
+
return (value - x) / (y - x);
|
|
252
|
+
} else {
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
240
256
|
function lerp(x, y, t) {
|
|
241
257
|
return (1 - t) * x + t * y;
|
|
242
258
|
}
|
|
259
|
+
function damp(x, y, lambda, dt) {
|
|
260
|
+
return lerp(x, y, 1 - Math.exp(-lambda * dt));
|
|
261
|
+
}
|
|
262
|
+
function pingpong(x, length = 1) {
|
|
263
|
+
return length - Math.abs(euclideanModulo(x, length * 2) - length);
|
|
264
|
+
}
|
|
265
|
+
function smoothstep(x, min, max) {
|
|
266
|
+
if (x <= min) return 0;
|
|
267
|
+
if (x >= max) return 1;
|
|
268
|
+
x = (x - min) / (max - min);
|
|
269
|
+
return x * x * (3 - 2 * x);
|
|
270
|
+
}
|
|
271
|
+
function smootherstep(x, min, max) {
|
|
272
|
+
if (x <= min) return 0;
|
|
273
|
+
if (x >= max) return 1;
|
|
274
|
+
x = (x - min) / (max - min);
|
|
275
|
+
return x * x * x * (x * (x * 6 - 15) + 10);
|
|
276
|
+
}
|
|
277
|
+
function randInt(low, high) {
|
|
278
|
+
return low + Math.floor(Math.random() * (high - low + 1));
|
|
279
|
+
}
|
|
280
|
+
function randFloat(low, high) {
|
|
281
|
+
return low + Math.random() * (high - low);
|
|
282
|
+
}
|
|
283
|
+
function randFloatSpread(range) {
|
|
284
|
+
return range * (0.5 - Math.random());
|
|
285
|
+
}
|
|
286
|
+
function seededRandom(s) {
|
|
287
|
+
if (s !== void 0) _seed = s;
|
|
288
|
+
let t = _seed += 1831565813;
|
|
289
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
290
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
291
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
292
|
+
}
|
|
293
|
+
function degToRad(degrees) {
|
|
294
|
+
return degrees * DEG2RAD;
|
|
295
|
+
}
|
|
296
|
+
function radToDeg(radians) {
|
|
297
|
+
return radians * RAD2DEG;
|
|
298
|
+
}
|
|
299
|
+
function isPowerOfTwo(value) {
|
|
300
|
+
return (value & value - 1) === 0 && value !== 0;
|
|
301
|
+
}
|
|
302
|
+
function ceilPowerOfTwo(value) {
|
|
303
|
+
return Math.pow(2, Math.ceil(Math.log(value) / Math.LN2));
|
|
304
|
+
}
|
|
305
|
+
function floorPowerOfTwo(value) {
|
|
306
|
+
return Math.pow(2, Math.floor(Math.log(value) / Math.LN2));
|
|
307
|
+
}
|
|
308
|
+
function setQuaternionFromProperEuler(q, a, b, c, order) {
|
|
309
|
+
const cos = Math.cos;
|
|
310
|
+
const sin = Math.sin;
|
|
311
|
+
const c2 = cos(b / 2);
|
|
312
|
+
const s2 = sin(b / 2);
|
|
313
|
+
const c13 = cos((a + c) / 2);
|
|
314
|
+
const s13 = sin((a + c) / 2);
|
|
315
|
+
const c1_3 = cos((a - c) / 2);
|
|
316
|
+
const s1_3 = sin((a - c) / 2);
|
|
317
|
+
const c3_1 = cos((c - a) / 2);
|
|
318
|
+
const s3_1 = sin((c - a) / 2);
|
|
319
|
+
switch (order) {
|
|
320
|
+
case "XYX":
|
|
321
|
+
q.set(c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13);
|
|
322
|
+
break;
|
|
323
|
+
case "YZY":
|
|
324
|
+
q.set(s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13);
|
|
325
|
+
break;
|
|
326
|
+
case "ZXZ":
|
|
327
|
+
q.set(s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13);
|
|
328
|
+
break;
|
|
329
|
+
case "XZX":
|
|
330
|
+
q.set(c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13);
|
|
331
|
+
break;
|
|
332
|
+
case "YXY":
|
|
333
|
+
q.set(s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13);
|
|
334
|
+
break;
|
|
335
|
+
case "ZYZ":
|
|
336
|
+
q.set(s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13);
|
|
337
|
+
break;
|
|
338
|
+
default:
|
|
339
|
+
console.warn("THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: " + order);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
243
342
|
function denormalize(value, array) {
|
|
244
343
|
switch (array.constructor) {
|
|
245
344
|
case Float32Array:
|
|
@@ -280,6 +379,250 @@ function normalize(value, array) {
|
|
|
280
379
|
throw new Error("Invalid component type.");
|
|
281
380
|
}
|
|
282
381
|
}
|
|
382
|
+
var MathUtils = {
|
|
383
|
+
DEG2RAD,
|
|
384
|
+
RAD2DEG,
|
|
385
|
+
/**
|
|
386
|
+
* Generate a [UUID]{@link https://en.wikipedia.org/wiki/Universally_unique_identifier}
|
|
387
|
+
* (universally unique identifier).
|
|
388
|
+
*
|
|
389
|
+
* @static
|
|
390
|
+
* @method
|
|
391
|
+
* @return {string} The UUID.
|
|
392
|
+
*/
|
|
393
|
+
generateUUID,
|
|
394
|
+
/**
|
|
395
|
+
* Clamps the given value between min and max.
|
|
396
|
+
*
|
|
397
|
+
* @static
|
|
398
|
+
* @method
|
|
399
|
+
* @param {number} value - The value to clamp.
|
|
400
|
+
* @param {number} min - The min value.
|
|
401
|
+
* @param {number} max - The max value.
|
|
402
|
+
* @return {number} The clamped value.
|
|
403
|
+
*/
|
|
404
|
+
clamp,
|
|
405
|
+
/**
|
|
406
|
+
* Computes the Euclidean modulo of the given parameters that
|
|
407
|
+
* is `( ( n % m ) + m ) % m`.
|
|
408
|
+
*
|
|
409
|
+
* @static
|
|
410
|
+
* @method
|
|
411
|
+
* @param {number} n - The first parameter.
|
|
412
|
+
* @param {number} m - The second parameter.
|
|
413
|
+
* @return {number} The Euclidean modulo.
|
|
414
|
+
*/
|
|
415
|
+
euclideanModulo,
|
|
416
|
+
/**
|
|
417
|
+
* Performs a linear mapping from range `<a1, a2>` to range `<b1, b2>`
|
|
418
|
+
* for the given value.
|
|
419
|
+
*
|
|
420
|
+
* @static
|
|
421
|
+
* @method
|
|
422
|
+
* @param {number} x - The value to be mapped.
|
|
423
|
+
* @param {number} a1 - Minimum value for range A.
|
|
424
|
+
* @param {number} a2 - Maximum value for range A.
|
|
425
|
+
* @param {number} b1 - Minimum value for range B.
|
|
426
|
+
* @param {number} b2 - Maximum value for range B.
|
|
427
|
+
* @return {number} The mapped value.
|
|
428
|
+
*/
|
|
429
|
+
mapLinear,
|
|
430
|
+
/**
|
|
431
|
+
* Returns the percentage in the closed interval `[0, 1]` of the given value
|
|
432
|
+
* between the start and end point.
|
|
433
|
+
*
|
|
434
|
+
* @static
|
|
435
|
+
* @method
|
|
436
|
+
* @param {number} x - The start point
|
|
437
|
+
* @param {number} y - The end point.
|
|
438
|
+
* @param {number} value - A value between start and end.
|
|
439
|
+
* @return {number} The interpolation factor.
|
|
440
|
+
*/
|
|
441
|
+
inverseLerp,
|
|
442
|
+
/**
|
|
443
|
+
* Returns a value linearly interpolated from two known points based on the given interval -
|
|
444
|
+
* `t = 0` will return `x` and `t = 1` will return `y`.
|
|
445
|
+
*
|
|
446
|
+
* @static
|
|
447
|
+
* @method
|
|
448
|
+
* @param {number} x - The start point
|
|
449
|
+
* @param {number} y - The end point.
|
|
450
|
+
* @param {number} t - The interpolation factor in the closed interval `[0, 1]`.
|
|
451
|
+
* @return {number} The interpolated value.
|
|
452
|
+
*/
|
|
453
|
+
lerp,
|
|
454
|
+
/**
|
|
455
|
+
* Smoothly interpolate a number from `x` to `y` in a spring-like manner using a delta
|
|
456
|
+
* time to maintain frame rate independent movement. For details, see
|
|
457
|
+
* [Frame rate independent damping using lerp]{@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}.
|
|
458
|
+
*
|
|
459
|
+
* @static
|
|
460
|
+
* @method
|
|
461
|
+
* @param {number} x - The current point.
|
|
462
|
+
* @param {number} y - The target point.
|
|
463
|
+
* @param {number} lambda - A higher lambda value will make the movement more sudden,
|
|
464
|
+
* and a lower value will make the movement more gradual.
|
|
465
|
+
* @param {number} dt - Delta time in seconds.
|
|
466
|
+
* @return {number} The interpolated value.
|
|
467
|
+
*/
|
|
468
|
+
damp,
|
|
469
|
+
/**
|
|
470
|
+
* Returns a value that alternates between `0` and the given `length` parameter.
|
|
471
|
+
*
|
|
472
|
+
* @static
|
|
473
|
+
* @method
|
|
474
|
+
* @param {number} x - The value to pingpong.
|
|
475
|
+
* @param {number} [length=1] - The positive value the function will pingpong to.
|
|
476
|
+
* @return {number} The alternated value.
|
|
477
|
+
*/
|
|
478
|
+
pingpong,
|
|
479
|
+
/**
|
|
480
|
+
* Returns a value in the range `[0,1]` that represents the percentage that `x` has
|
|
481
|
+
* moved between `min` and `max`, but smoothed or slowed down the closer `x` is to
|
|
482
|
+
* the `min` and `max`.
|
|
483
|
+
*
|
|
484
|
+
* See [Smoothstep]{@link http://en.wikipedia.org/wiki/Smoothstep} for more details.
|
|
485
|
+
*
|
|
486
|
+
* @static
|
|
487
|
+
* @method
|
|
488
|
+
* @param {number} x - The value to evaluate based on its position between min and max.
|
|
489
|
+
* @param {number} min - The min value. Any x value below min will be `0`.
|
|
490
|
+
* @param {number} max - The max value. Any x value above max will be `1`.
|
|
491
|
+
* @return {number} The alternated value.
|
|
492
|
+
*/
|
|
493
|
+
smoothstep,
|
|
494
|
+
/**
|
|
495
|
+
* A [variation on smoothstep]{@link https://en.wikipedia.org/wiki/Smoothstep#Variations}
|
|
496
|
+
* that has zero 1st and 2nd order derivatives at x=0 and x=1.
|
|
497
|
+
*
|
|
498
|
+
* @static
|
|
499
|
+
* @method
|
|
500
|
+
* @param {number} x - The value to evaluate based on its position between min and max.
|
|
501
|
+
* @param {number} min - The min value. Any x value below min will be `0`.
|
|
502
|
+
* @param {number} max - The max value. Any x value above max will be `1`.
|
|
503
|
+
* @return {number} The alternated value.
|
|
504
|
+
*/
|
|
505
|
+
smootherstep,
|
|
506
|
+
/**
|
|
507
|
+
* Returns a random integer from `<low, high>` interval.
|
|
508
|
+
*
|
|
509
|
+
* @static
|
|
510
|
+
* @method
|
|
511
|
+
* @param {number} low - The lower value boundary.
|
|
512
|
+
* @param {number} high - The upper value boundary
|
|
513
|
+
* @return {number} A random integer.
|
|
514
|
+
*/
|
|
515
|
+
randInt,
|
|
516
|
+
/**
|
|
517
|
+
* Returns a random float from `<low, high>` interval.
|
|
518
|
+
*
|
|
519
|
+
* @static
|
|
520
|
+
* @method
|
|
521
|
+
* @param {number} low - The lower value boundary.
|
|
522
|
+
* @param {number} high - The upper value boundary
|
|
523
|
+
* @return {number} A random float.
|
|
524
|
+
*/
|
|
525
|
+
randFloat,
|
|
526
|
+
/**
|
|
527
|
+
* Returns a random integer from `<-range/2, range/2>` interval.
|
|
528
|
+
*
|
|
529
|
+
* @static
|
|
530
|
+
* @method
|
|
531
|
+
* @param {number} range - Defines the value range.
|
|
532
|
+
* @return {number} A random float.
|
|
533
|
+
*/
|
|
534
|
+
randFloatSpread,
|
|
535
|
+
/**
|
|
536
|
+
* Returns a deterministic pseudo-random float in the interval `[0, 1]`.
|
|
537
|
+
*
|
|
538
|
+
* @static
|
|
539
|
+
* @method
|
|
540
|
+
* @param {number} [s] - The integer seed.
|
|
541
|
+
* @return {number} A random float.
|
|
542
|
+
*/
|
|
543
|
+
seededRandom,
|
|
544
|
+
/**
|
|
545
|
+
* Converts degrees to radians.
|
|
546
|
+
*
|
|
547
|
+
* @static
|
|
548
|
+
* @method
|
|
549
|
+
* @param {number} degrees - A value in degrees.
|
|
550
|
+
* @return {number} The converted value in radians.
|
|
551
|
+
*/
|
|
552
|
+
degToRad,
|
|
553
|
+
/**
|
|
554
|
+
* Converts radians to degrees.
|
|
555
|
+
*
|
|
556
|
+
* @static
|
|
557
|
+
* @method
|
|
558
|
+
* @param {number} radians - A value in radians.
|
|
559
|
+
* @return {number} The converted value in degrees.
|
|
560
|
+
*/
|
|
561
|
+
radToDeg,
|
|
562
|
+
/**
|
|
563
|
+
* Returns `true` if the given number is a power of two.
|
|
564
|
+
*
|
|
565
|
+
* @static
|
|
566
|
+
* @method
|
|
567
|
+
* @param {number} value - The value to check.
|
|
568
|
+
* @return {boolean} Whether the given number is a power of two or not.
|
|
569
|
+
*/
|
|
570
|
+
isPowerOfTwo,
|
|
571
|
+
/**
|
|
572
|
+
* Returns the smallest power of two that is greater than or equal to the given number.
|
|
573
|
+
*
|
|
574
|
+
* @static
|
|
575
|
+
* @method
|
|
576
|
+
* @param {number} value - The value to find a POT for.
|
|
577
|
+
* @return {number} The smallest power of two that is greater than or equal to the given number.
|
|
578
|
+
*/
|
|
579
|
+
ceilPowerOfTwo,
|
|
580
|
+
/**
|
|
581
|
+
* Returns the largest power of two that is less than or equal to the given number.
|
|
582
|
+
*
|
|
583
|
+
* @static
|
|
584
|
+
* @method
|
|
585
|
+
* @param {number} value - The value to find a POT for.
|
|
586
|
+
* @return {number} The largest power of two that is less than or equal to the given number.
|
|
587
|
+
*/
|
|
588
|
+
floorPowerOfTwo,
|
|
589
|
+
/**
|
|
590
|
+
* Sets the given quaternion from the [Intrinsic Proper Euler Angles]{@link https://en.wikipedia.org/wiki/Euler_angles}
|
|
591
|
+
* defined by the given angles and order.
|
|
592
|
+
*
|
|
593
|
+
* Rotations are applied to the axes in the order specified by order:
|
|
594
|
+
* rotation by angle `a` is applied first, then by angle `b`, then by angle `c`.
|
|
595
|
+
*
|
|
596
|
+
* @static
|
|
597
|
+
* @method
|
|
598
|
+
* @param {Quaternion} q - The quaternion to set.
|
|
599
|
+
* @param {number} a - The rotation applied to the first axis, in radians.
|
|
600
|
+
* @param {number} b - The rotation applied to the second axis, in radians.
|
|
601
|
+
* @param {number} c - The rotation applied to the third axis, in radians.
|
|
602
|
+
* @param {('XYX'|'XZX'|'YXY'|'YZY'|'ZXZ'|'ZYZ')} order - A string specifying the axes order.
|
|
603
|
+
*/
|
|
604
|
+
setQuaternionFromProperEuler,
|
|
605
|
+
/**
|
|
606
|
+
* Normalizes the given value according to the given typed array.
|
|
607
|
+
*
|
|
608
|
+
* @static
|
|
609
|
+
* @method
|
|
610
|
+
* @param {number} value - The float value in the range `[0,1]` to normalize.
|
|
611
|
+
* @param {TypedArray} array - The typed array that defines the data type of the value.
|
|
612
|
+
* @return {number} The normalize value.
|
|
613
|
+
*/
|
|
614
|
+
normalize,
|
|
615
|
+
/**
|
|
616
|
+
* Denormalizes the given value according to the given typed array.
|
|
617
|
+
*
|
|
618
|
+
* @static
|
|
619
|
+
* @method
|
|
620
|
+
* @param {number} value - The value to denormalize.
|
|
621
|
+
* @param {TypedArray} array - The typed array that defines the data type of the value.
|
|
622
|
+
* @return {number} The denormalize (float) value in the range `[0,1]`.
|
|
623
|
+
*/
|
|
624
|
+
denormalize
|
|
625
|
+
};
|
|
283
626
|
var Vector2 = class _Vector2 {
|
|
284
627
|
/**
|
|
285
628
|
* Constructs a new 2D vector.
|
|
@@ -18886,6 +19229,11 @@ if (typeof window !== "undefined") {
|
|
|
18886
19229
|
}
|
|
18887
19230
|
|
|
18888
19231
|
// src/three.module.js
|
|
19232
|
+
/**
|
|
19233
|
+
* @license
|
|
19234
|
+
* Copyright 2010-2025 Three.js Authors
|
|
19235
|
+
* SPDX-License-Identifier: MIT
|
|
19236
|
+
*/
|
|
18889
19237
|
function WebGLAnimation() {
|
|
18890
19238
|
let context = null;
|
|
18891
19239
|
let isAnimating = false;
|
|
@@ -29021,7 +29369,7 @@ function normalizeTheme(value) {
|
|
|
29021
29369
|
}
|
|
29022
29370
|
var CSS = [
|
|
29023
29371
|
":host{display:block;position:relative;height:var(--lc-height,680px);min-height:420px;color:var(--lc-panel-title,#e9f0f5);font:14px Inter,system-ui,-apple-system,sans-serif;background:var(--lc-bg,#0a111a);border:1px solid #294252;border-radius:12px;overflow:hidden;contain:content}",
|
|
29024
|
-
"*{box-sizing:border-box}.atlas{display:grid;grid-template-columns:minmax(0,1fr) 300px;height:100%}.atlas[data-panel=hidden]{grid-template-columns:1fr}.viewport{position:relative;min-width:0;background:radial-gradient(ellipse at 50% 45%,var(--lc-bg-glow,#1c2b39),var(--lc-bg-fade,#0a111a) 82%)}canvas{display:block;width:100%;height:100%;cursor:grab;touch-action:none}.toolbar{position:absolute;z-index:10;bottom:
|
|
29372
|
+
"*{box-sizing:border-box}.atlas{display:grid;grid-template-columns:minmax(0,1fr) 300px;height:100%}.atlas[data-panel=hidden]{grid-template-columns:1fr}.viewport{position:relative;min-width:0;overflow:hidden;background:radial-gradient(ellipse at 50% 45%,var(--lc-bg-glow,#1c2b39),var(--lc-bg-fade,#0a111a) 82%)}canvas{display:block;width:100%;height:100%;cursor:grab;touch-action:none}.toolbar{position:absolute;z-index:10;bottom:28px;left:50%;transform:translateX(-50%);display:flex;gap:3px;padding:6px;background:var(--lc-toolbar-bg,#15232fe8);border:1px solid var(--lc-toolbar-border,#3a5062);border-radius:8px;white-space:nowrap}.toolbar[data-hidden=true],.toolbar[hidden],.panel[data-hidden=true],.panel[hidden],.crumbs[data-hidden=true],.crumbs[hidden],.hint[data-hidden=true],.hint[hidden]{display:none!important}.toolbar button{height:30px;padding:0 9px;border:0;border-radius:5px;background:none;color:var(--lc-toolbar-text,#dbe7ec);cursor:pointer}.toolbar button:hover,.toolbar button.active{background:var(--lc-toolbar-active,#1f3d4b);color:var(--lc-toolbar-active-text,#c5f2e9)}.toolbar i{display:block;width:1px;margin:2px 3px;background:#38515f}.hint{position:absolute;z-index:9;top:8px;bottom:auto;left:0;right:0;text-align:center;padding:2px 8px;font-size:11px;line-height:16px;color:var(--lc-hint,#8da6b8);pointer-events:none}.panel{overflow:auto;padding:24px 20px;background:var(--lc-panel-bg,linear-gradient(140deg,#182633,#0e1925 65%));border-left:1px solid var(--lc-panel-border,#2a3a48)}.panel h2{font-size:26px;line-height:1.12;font-weight:470;margin:12px 0;color:var(--lc-panel-title,#e9f0f5)}.panel h3{font-size:11px;letter-spacing:1.4px;color:#91aabd;margin:24px 0 10px}.panel p{font-size:14px;line-height:1.55;color:var(--lc-panel-text,#b5c6d2)}.panel .eyebrow{font-size:11px;letter-spacing:1.7px;color:#6ad7c0}.panel .sub{font-size:13px;color:#8fa8bd;margin-bottom:16px}.panel button{display:block;width:100%;padding:10px 0;text-align:left;background:none;border:0;border-bottom:1px solid var(--lc-panel-border,#2a3a48);color:#d9e7eb;cursor:pointer}.panel button:hover{color:#6ad7c0}.panel .relation{padding:10px 0;border-bottom:1px solid var(--lc-panel-border,#2a3a48)}.panel .relation strong{display:block;font-weight:550}.panel .relation small{display:block;color:#86a6b9;font-size:12px;margin-top:4px}.panel .back{color:#9db7c8;font-size:13px}.panel .crumbs{display:flex;flex-wrap:wrap;gap:5px;align-items:center;margin:0 0 12px}.panel .crumbs button{display:inline;width:auto;padding:3px 7px;border:0;border-radius:4px;background:var(--lc-crumb-bg,#142733);color:var(--lc-crumb-text,#91b6c7);font-size:12px}.panel .crumbs button.current{background:var(--lc-crumb-active,#1c4a4d);color:var(--lc-crumb-active-text,#c5f2e9)}.panel .note{border-top:1px solid var(--lc-panel-border,#2a3a48);padding-top:16px;margin-top:22px;font-size:12px;color:#7692a6}.error{position:absolute;inset:20%;padding:18px;background:#172b3b;border:1px solid #7896a9;line-height:1.5;z-index:4}@media(max-width:900px){.toolbar{gap:0}.toolbar button{padding:0 6px;font-size:12px}}@media(max-width:760px){:host{height:680px}.atlas{grid-template-columns:1fr;grid-template-rows:minmax(420px,1fr) auto}.panel{max-height:250px;border-left:0;border-top:1px solid var(--lc-panel-border,#2a3a48);padding:18px}.toolbar{bottom:24px}.hint{top:8px;bottom:auto;display:block}}"
|
|
29025
29373
|
].join("");
|
|
29026
29374
|
var TEMPLATE = '<div class="atlas"><section class="viewport"><canvas tabindex="0" aria-label="Modelo 3D interactivo"></canvas><div class="toolbar"><button data-action="minus" aria-label="Alejar">\u2212</button><button data-action="plus" aria-label="Acercar">+</button><i></i><button data-action="fit">\u2316 Encuadrar</button><button data-action="front">\u25B1 Frontal</button><button data-action="labels" class="active" aria-pressed="true">\u25A7 Etiquetas</button><i></i><button data-action="relations-all" aria-pressed="false">Todas</button><button data-action="relations-selected" class="active" aria-pressed="true">Selecci\xF3n</button><button data-action="relations-none" aria-pressed="false">Ocultar</button></div><div class="hint">Arrastra para girar \xB7 rueda para zoom \xB7 clic para seleccionar</div></section><aside class="panel" aria-live="polite"></aside></div>';
|
|
29027
29375
|
function escapeHtml(value) {
|
|
@@ -29033,6 +29381,13 @@ function normalizeModel(input) {
|
|
|
29033
29381
|
const relations = Array.isArray(source.relations) ? source.relations.map((relation, index) => Array.isArray(relation) ? { from: relation[0], to: relation[1], label: relation[2] || "", index } : { ...relation, from: relation.from, to: relation.to, label: relation.label || "", index }) : [];
|
|
29034
29382
|
return { ...source, palette: { ...DEFAULT_PALETTE, ...source.palette || {} }, theme: normalizeTheme(source.theme || source.styles), entities, relations };
|
|
29035
29383
|
}
|
|
29384
|
+
function relationVisualMode(relation) {
|
|
29385
|
+
if (!relation || typeof relation !== "object") return "directed";
|
|
29386
|
+
const raw = String(relation.mode || relation.visual || relation.render || relation.type || relation.direction || "").toLowerCase();
|
|
29387
|
+
if (relation.bidirectional === true || ["bidirectional", "both", "two-way", "two_way", "duplex"].includes(raw)) return "bidirectional";
|
|
29388
|
+
if (relation.broken === true || ["broken", "interrupted", "\u65AD"].includes(raw)) return "broken";
|
|
29389
|
+
return "directed";
|
|
29390
|
+
}
|
|
29036
29391
|
function roundedBox(radius, segments, size = [1, 1, 1]) {
|
|
29037
29392
|
const r = Math.min(Math.max(0, Number(radius) || 0), size[0] / 2, size[1] / 2, size[2] / 2);
|
|
29038
29393
|
if (!r) return new BoxGeometry(1, 1, 1);
|
|
@@ -29291,6 +29646,119 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29291
29646
|
set model(value) {
|
|
29292
29647
|
this.load(value);
|
|
29293
29648
|
}
|
|
29649
|
+
get selectedId() {
|
|
29650
|
+
return this.selected?.id || null;
|
|
29651
|
+
}
|
|
29652
|
+
_emit(type, detail = {}) {
|
|
29653
|
+
this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
|
|
29654
|
+
}
|
|
29655
|
+
_publicEntity(id) {
|
|
29656
|
+
const entity = this.modelData?.entities?.find((item) => item.id === id);
|
|
29657
|
+
return entity ? {
|
|
29658
|
+
...entity,
|
|
29659
|
+
p: Array.isArray(entity.p) ? [...entity.p] : [0, 0, 0],
|
|
29660
|
+
s: Array.isArray(entity.s) ? [...entity.s] : [1, 1, 1]
|
|
29661
|
+
} : null;
|
|
29662
|
+
}
|
|
29663
|
+
_commitModelChange(kind, detail = {}, options = {}) {
|
|
29664
|
+
const selectedId = options.preserveSelection === false ? null : this.selected?.id;
|
|
29665
|
+
this.modelData = normalizeModel(this.modelData);
|
|
29666
|
+
this._build({ preserveSelection: selectedId });
|
|
29667
|
+
this._emit("lc-model-change", {
|
|
29668
|
+
kind,
|
|
29669
|
+
model: this.modelData,
|
|
29670
|
+
...detail
|
|
29671
|
+
});
|
|
29672
|
+
return this;
|
|
29673
|
+
}
|
|
29674
|
+
/** Replace selected parts of the model and rebuild the scene in place. */
|
|
29675
|
+
updateModel(patch = {}, options = {}) {
|
|
29676
|
+
if (!patch || typeof patch !== "object" || Array.isArray(patch)) throw new TypeError("El parche del modelo debe ser un objeto.");
|
|
29677
|
+
const current = this.modelData || normalizeModel({});
|
|
29678
|
+
const next = {
|
|
29679
|
+
...current,
|
|
29680
|
+
...patch,
|
|
29681
|
+
palette: patch.palette ? { ...current.palette, ...patch.palette } : current.palette,
|
|
29682
|
+
theme: patch.theme || patch.styles ? patch.theme || patch.styles : current.theme,
|
|
29683
|
+
entities: Array.isArray(patch.entities) ? patch.entities : current.entities,
|
|
29684
|
+
relations: Array.isArray(patch.relations) ? patch.relations : current.relations
|
|
29685
|
+
};
|
|
29686
|
+
this.modelData = normalizeModel(next);
|
|
29687
|
+
this.theme = this.modelData.theme;
|
|
29688
|
+
return this._commitModelChange("model-update", { patch }, options);
|
|
29689
|
+
}
|
|
29690
|
+
addEntity(entity, options = {}) {
|
|
29691
|
+
if (!entity || typeof entity !== "object" || !entity.id) throw new TypeError("La entidad debe incluir un id.");
|
|
29692
|
+
if (!this.modelData) this.modelData = normalizeModel({});
|
|
29693
|
+
if (this.modelData.entities.some((item) => item.id === entity.id)) throw new Error(`Ya existe una entidad con id "${entity.id}".`);
|
|
29694
|
+
if (entity.parent && !this.modelData.entities.some((item) => item.id === entity.parent)) throw new Error(`No existe el contenedor padre "${entity.parent}".`);
|
|
29695
|
+
this.modelData.entities = [...this.modelData.entities, { ...entity }];
|
|
29696
|
+
return this._commitModelChange("entity-add", { entity: this._publicEntity(entity.id) }, options);
|
|
29697
|
+
}
|
|
29698
|
+
updateEntity(id, patch = {}, options = {}) {
|
|
29699
|
+
if (!this.modelData) throw new Error("No hay ning\xFAn modelo cargado.");
|
|
29700
|
+
const index = this.modelData.entities.findIndex((item) => item.id === id);
|
|
29701
|
+
if (index < 0) throw new Error(`No existe una entidad con id "${id}".`);
|
|
29702
|
+
if (!patch || typeof patch !== "object" || Array.isArray(patch)) throw new TypeError("Los cambios de una entidad deben ser un objeto.");
|
|
29703
|
+
if (patch.id && patch.id !== id) throw new Error("El id de una entidad no se puede modificar.");
|
|
29704
|
+
if (patch.parent && patch.parent !== id && !this.modelData.entities.some((item) => item.id === patch.parent)) throw new Error(`No existe el contenedor padre "${patch.parent}".`);
|
|
29705
|
+
this.modelData.entities = this.modelData.entities.map((item, itemIndex) => itemIndex === index ? { ...item, ...patch, id } : item);
|
|
29706
|
+
return this._commitModelChange("entity-update", { id, patch, entity: this._publicEntity(id) }, options);
|
|
29707
|
+
}
|
|
29708
|
+
removeEntity(id, options = {}) {
|
|
29709
|
+
if (!this.modelData) throw new Error("No hay ning\xFAn modelo cargado.");
|
|
29710
|
+
const cascade = options.cascade !== false;
|
|
29711
|
+
const removed = /* @__PURE__ */ new Set([id]);
|
|
29712
|
+
if (cascade) {
|
|
29713
|
+
let changed = true;
|
|
29714
|
+
while (changed) {
|
|
29715
|
+
changed = false;
|
|
29716
|
+
for (const entity of this.modelData.entities) {
|
|
29717
|
+
if (entity.parent && removed.has(entity.parent) && !removed.has(entity.id)) {
|
|
29718
|
+
removed.add(entity.id);
|
|
29719
|
+
changed = true;
|
|
29720
|
+
}
|
|
29721
|
+
}
|
|
29722
|
+
}
|
|
29723
|
+
}
|
|
29724
|
+
if (!this.modelData.entities.some((item) => item.id === id)) throw new Error(`No existe una entidad con id "${id}".`);
|
|
29725
|
+
this.modelData.entities = this.modelData.entities.filter((item) => !removed.has(item.id));
|
|
29726
|
+
this.modelData.relations = this.modelData.relations.filter((relation) => !removed.has(relation.from) && !removed.has(relation.to));
|
|
29727
|
+
for (const removedId of removed) this.open.delete(removedId);
|
|
29728
|
+
if (this.selected && removed.has(this.selected.id)) this.selected = null;
|
|
29729
|
+
return this._commitModelChange("entity-remove", { id, removed: [...removed] });
|
|
29730
|
+
}
|
|
29731
|
+
_relationIndex(reference) {
|
|
29732
|
+
if (Number.isInteger(reference)) return reference;
|
|
29733
|
+
if (typeof reference === "string") return this.modelData?.relations?.findIndex((relation) => relation.id === reference) ?? -1;
|
|
29734
|
+
return -1;
|
|
29735
|
+
}
|
|
29736
|
+
addRelation(relation, options = {}) {
|
|
29737
|
+
if (!relation || typeof relation !== "object" || !relation.from || !relation.to) throw new TypeError("La relaci\xF3n debe incluir from y to.");
|
|
29738
|
+
if (!this.modelData) throw new Error("No hay ning\xFAn modelo cargado.");
|
|
29739
|
+
const ids = new Set(this.modelData.entities.map((entity) => entity.id));
|
|
29740
|
+
if (!ids.has(relation.from) || !ids.has(relation.to)) throw new Error("Los extremos de la relaci\xF3n deben existir en el modelo.");
|
|
29741
|
+
this.modelData.relations = [...this.modelData.relations, { ...relation }];
|
|
29742
|
+
return this._commitModelChange("relation-add", { relation: { ...relation } }, options);
|
|
29743
|
+
}
|
|
29744
|
+
updateRelation(reference, patch = {}, options = {}) {
|
|
29745
|
+
if (!this.modelData) throw new Error("No hay ning\xFAn modelo cargado.");
|
|
29746
|
+
const index = this._relationIndex(reference);
|
|
29747
|
+
if (index < 0 || index >= this.modelData.relations.length) throw new Error("No existe la relaci\xF3n indicada.");
|
|
29748
|
+
if (!patch || typeof patch !== "object" || Array.isArray(patch)) throw new TypeError("Los cambios de una relaci\xF3n deben ser un objeto.");
|
|
29749
|
+
const next = { ...this.modelData.relations[index], ...patch };
|
|
29750
|
+
const ids = new Set(this.modelData.entities.map((entity) => entity.id));
|
|
29751
|
+
if (!ids.has(next.from) || !ids.has(next.to)) throw new Error("Los extremos de la relaci\xF3n deben existir en el modelo.");
|
|
29752
|
+
this.modelData.relations = this.modelData.relations.map((item, itemIndex) => itemIndex === index ? next : item);
|
|
29753
|
+
return this._commitModelChange("relation-update", { index, patch, relation: { ...next } }, options);
|
|
29754
|
+
}
|
|
29755
|
+
removeRelation(reference, options = {}) {
|
|
29756
|
+
if (!this.modelData) throw new Error("No hay ning\xFAn modelo cargado.");
|
|
29757
|
+
const index = this._relationIndex(reference);
|
|
29758
|
+
if (index < 0 || index >= this.modelData.relations.length) throw new Error("No existe la relaci\xF3n indicada.");
|
|
29759
|
+
const [relation] = this.modelData.relations.splice(index, 1);
|
|
29760
|
+
return this._commitModelChange("relation-remove", { index, relation: { ...relation } }, options);
|
|
29761
|
+
}
|
|
29294
29762
|
get palette() {
|
|
29295
29763
|
return this.modelData?.palette || { ...DEFAULT_PALETTE };
|
|
29296
29764
|
}
|
|
@@ -29329,8 +29797,21 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29329
29797
|
return this;
|
|
29330
29798
|
}
|
|
29331
29799
|
fit() {
|
|
29332
|
-
|
|
29333
|
-
this.
|
|
29800
|
+
const bounds = new Box3();
|
|
29801
|
+
for (const node of this.nodes.values()) {
|
|
29802
|
+
const center2 = this._worldPosition(node);
|
|
29803
|
+
const half = new Vector3(node.s[0] / 2, node.s[1] / 2, node.s[2] / 2);
|
|
29804
|
+
bounds.expandByPoint(center2.clone().sub(half));
|
|
29805
|
+
bounds.expandByPoint(center2.clone().add(half));
|
|
29806
|
+
}
|
|
29807
|
+
const center = bounds.isEmpty() ? new Vector3() : bounds.getCenter(new Vector3());
|
|
29808
|
+
const size = bounds.isEmpty() ? new Vector3(1, 1, 1) : bounds.getSize(new Vector3());
|
|
29809
|
+
const radius = Math.max(1, size.length() / 2);
|
|
29810
|
+
const fov2 = MathUtils.degToRad(this.camera.fov);
|
|
29811
|
+
const requiredDistance = radius / Math.sin(fov2 / 2) * 1.12;
|
|
29812
|
+
const baseDistance = Math.max(29, 39.5 / Math.max(this.camera.aspect, 0.5));
|
|
29813
|
+
this.targetZoom = Math.max(0.55, Math.min(2.8, baseDistance / requiredDistance));
|
|
29814
|
+
this.targetFocus.copy(center);
|
|
29334
29815
|
this.invalidate();
|
|
29335
29816
|
return this;
|
|
29336
29817
|
}
|
|
@@ -29451,6 +29932,8 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29451
29932
|
try {
|
|
29452
29933
|
const data = typeof source === "string" ? await fetch(source).then((response) => response.json()) : source;
|
|
29453
29934
|
this.modelData = normalizeModel(data);
|
|
29935
|
+
this.open.clear();
|
|
29936
|
+
this.selected = null;
|
|
29454
29937
|
if (this._pendingPalette) {
|
|
29455
29938
|
this.modelData.palette = { ...this.modelData.palette, ...this._pendingPalette };
|
|
29456
29939
|
this._pendingPalette = null;
|
|
@@ -29510,6 +29993,14 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29510
29993
|
this.scene.remove(node.group);
|
|
29511
29994
|
}
|
|
29512
29995
|
this.nodes.clear();
|
|
29996
|
+
this._clearRelationGroup();
|
|
29997
|
+
}
|
|
29998
|
+
_clearRelationGroup() {
|
|
29999
|
+
for (const object of this.relationGroup.children) {
|
|
30000
|
+
object.geometry?.dispose();
|
|
30001
|
+
if (Array.isArray(object.material)) object.material.forEach((material) => material.dispose());
|
|
30002
|
+
else object.material?.dispose();
|
|
30003
|
+
}
|
|
29513
30004
|
this.relationGroup.clear();
|
|
29514
30005
|
}
|
|
29515
30006
|
_recolorNodes() {
|
|
@@ -29520,10 +30011,12 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29520
30011
|
if (node.edge?.material) node.edge.material.color.set(color);
|
|
29521
30012
|
}
|
|
29522
30013
|
}
|
|
29523
|
-
_build() {
|
|
30014
|
+
_build(options = {}) {
|
|
29524
30015
|
if (!this._ensureRenderer()) return;
|
|
30016
|
+
const selectedId = options.preserveSelection || null;
|
|
29525
30017
|
this._clearObjects();
|
|
29526
30018
|
for (const entity of this.modelData.entities) this.nodes.set(entity.id, { ...entity });
|
|
30019
|
+
this.open = new Set([...this.open].filter((id) => this.nodes.has(id)));
|
|
29527
30020
|
for (const entity of this.modelData.entities) {
|
|
29528
30021
|
const node = this.nodes.get(entity.id);
|
|
29529
30022
|
node.group = new Group();
|
|
@@ -29555,6 +30048,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29555
30048
|
node.group.add(node.label);
|
|
29556
30049
|
this.scene.add(node.group);
|
|
29557
30050
|
}
|
|
30051
|
+
this.selected = selectedId && this.nodes.has(selectedId) ? this.nodes.get(selectedId) : null;
|
|
29558
30052
|
this.relations = this.modelData.relations;
|
|
29559
30053
|
this._renderPanel();
|
|
29560
30054
|
this._rebuildRelations();
|
|
@@ -29594,7 +30088,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29594
30088
|
return this.relations.map((relation, index) => relation.from === this.selected.id || relation.to === this.selected.id ? index : -1).filter((index) => index >= 0);
|
|
29595
30089
|
}
|
|
29596
30090
|
_rebuildRelations() {
|
|
29597
|
-
this.
|
|
30091
|
+
this._clearRelationGroup();
|
|
29598
30092
|
const active = this._activeRelationIndexes();
|
|
29599
30093
|
const color = new Color(this.modelData?.palette.connection || DEFAULT_PALETTE.connection);
|
|
29600
30094
|
for (const index of active) {
|
|
@@ -29606,14 +30100,28 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29606
30100
|
const end = this._endpoint(to, from, false);
|
|
29607
30101
|
const delta = end.clone().sub(start);
|
|
29608
30102
|
if (delta.lengthSq() < 0.01) continue;
|
|
29609
|
-
const
|
|
29610
|
-
|
|
30103
|
+
const visualMode = relationVisualMode(relation);
|
|
30104
|
+
const makeTube = (from2, to2, opacity = 0.82) => {
|
|
30105
|
+
const tube = new Mesh(new TubeGeometry(new LineCurve3(from2, to2), 10, 0.045, 6, false), new MeshBasicMaterial({ color, transparent: true, opacity }));
|
|
30106
|
+
tube.userData.relationIndex = index;
|
|
30107
|
+
this.relationGroup.add(tube);
|
|
30108
|
+
};
|
|
30109
|
+
if (visualMode === "broken") {
|
|
30110
|
+
const direction = delta.clone().normalize();
|
|
30111
|
+
const midpoint = start.clone().lerp(end, 0.5);
|
|
30112
|
+
const gap = Math.min(delta.length() * 0.16, 0.72);
|
|
30113
|
+
makeTube(start, midpoint.clone().sub(direction.clone().multiplyScalar(gap / 2)), 0.55);
|
|
30114
|
+
makeTube(midpoint.clone().add(direction.clone().multiplyScalar(gap / 2)), end, 0.55);
|
|
30115
|
+
continue;
|
|
30116
|
+
}
|
|
30117
|
+
makeTube(start, end);
|
|
29611
30118
|
for (let ringIndex = 0; ringIndex < 4; ringIndex++) {
|
|
29612
30119
|
const ring = new Mesh(new TorusGeometry(0.068, 0.018, 8, 20), new MeshBasicMaterial({ color, transparent: true, opacity: 0.9 }));
|
|
29613
30120
|
ring.userData.phase = ringIndex / 4;
|
|
30121
|
+
ring.userData.direction = visualMode === "bidirectional" && ringIndex % 2 ? -1 : 1;
|
|
29614
30122
|
ring.userData.start = start.clone();
|
|
29615
30123
|
ring.userData.end = end.clone();
|
|
29616
|
-
ring.quaternion.setFromUnitVectors(new Vector3(0, 0, 1), delta.normalize());
|
|
30124
|
+
ring.quaternion.setFromUnitVectors(new Vector3(0, 0, 1), delta.clone().normalize());
|
|
29617
30125
|
this.relationGroup.add(ring);
|
|
29618
30126
|
}
|
|
29619
30127
|
}
|
|
@@ -29661,7 +30169,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29661
30169
|
});
|
|
29662
30170
|
this._applyTheme();
|
|
29663
30171
|
}
|
|
29664
|
-
select(id) {
|
|
30172
|
+
select(id, options = {}) {
|
|
29665
30173
|
const node = this.nodes.get(id);
|
|
29666
30174
|
if (!node) return;
|
|
29667
30175
|
let parent = node.parent ? this.nodes.get(node.parent) : null;
|
|
@@ -29676,7 +30184,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29676
30184
|
this._renderPanel();
|
|
29677
30185
|
this._rebuildRelations();
|
|
29678
30186
|
this.invalidate();
|
|
29679
|
-
this.
|
|
30187
|
+
this._emit("lc-select", { id, entity: this._publicEntity(id), source: options.source || "api" });
|
|
29680
30188
|
}
|
|
29681
30189
|
setRelationMode(mode) {
|
|
29682
30190
|
this.relationMode = ["all", "selected", "none"].includes(mode) ? mode : "selected";
|
|
@@ -29694,6 +30202,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29694
30202
|
this.dispatchEvent(new CustomEvent("lc-relation-mode", { detail: { mode: this.relationMode } }));
|
|
29695
30203
|
}
|
|
29696
30204
|
reset() {
|
|
30205
|
+
const previousId = this.selected?.id || null;
|
|
29697
30206
|
this.selected = null;
|
|
29698
30207
|
this.open.clear();
|
|
29699
30208
|
this.relationMode = "selected";
|
|
@@ -29712,9 +30221,11 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29712
30221
|
this.targetZoom = 1;
|
|
29713
30222
|
this.focus.set(0, 0, 0);
|
|
29714
30223
|
this.targetFocus.set(0, 0, 0);
|
|
30224
|
+
this.fit();
|
|
29715
30225
|
this._renderPanel();
|
|
29716
30226
|
this._rebuildRelations();
|
|
29717
30227
|
this.invalidate();
|
|
30228
|
+
if (previousId) this._emit("lc-select", { id: null, entity: null, previousId, source: "reset" });
|
|
29718
30229
|
}
|
|
29719
30230
|
resize() {
|
|
29720
30231
|
if (!this.renderer) return;
|
|
@@ -29773,7 +30284,10 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29773
30284
|
top.visible = displayTransparentLabel && cameraAbove;
|
|
29774
30285
|
const scale = displayTransparentLabel ? 0.52 : 1;
|
|
29775
30286
|
front.scale.setScalar(scale);
|
|
29776
|
-
|
|
30287
|
+
back.scale.setScalar(scale);
|
|
30288
|
+
const openLabelX = displayTransparentLabel ? -labelSize.x / 2 + 0.18 + labelWidth * scale / 2 - textOffsetX * scale : 0;
|
|
30289
|
+
front.position.x = openLabelX;
|
|
30290
|
+
back.position.x = displayTransparentLabel ? -openLabelX : 0;
|
|
29777
30291
|
const shellFrontTopMargin = 0.18;
|
|
29778
30292
|
front.position.y = displayTransparentLabel ? labelSize.y / 2 - shellFrontTopMargin - labelHeight * scale / 2 : labelSize.y / 2 - 0.09 - labelHeight / 2;
|
|
29779
30293
|
front.position.z = displayTransparentLabel ? labelSize.z / 2 + 0.025 : labelSize.z / 2 + 0.012;
|
|
@@ -29793,16 +30307,23 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29793
30307
|
back.visible = !cameraOnFront && !opened;
|
|
29794
30308
|
top.visible = false;
|
|
29795
30309
|
front.scale.setScalar(1);
|
|
30310
|
+
back.scale.setScalar(1);
|
|
29796
30311
|
front.position.x = 0;
|
|
30312
|
+
back.position.x = 0;
|
|
29797
30313
|
front.position.y = labelSize.y / 2 - 0.09 - labelHeight / 2;
|
|
30314
|
+
back.position.y = front.position.y;
|
|
30315
|
+
back.position.z = -labelSize.z / 2 - 0.012;
|
|
29798
30316
|
front.material.color.set(15923964);
|
|
29799
30317
|
front.material.opacity = 1;
|
|
30318
|
+
back.material.color.set(15923964);
|
|
30319
|
+
back.material.opacity = 1;
|
|
29800
30320
|
}
|
|
29801
30321
|
}
|
|
29802
30322
|
for (const child of this.relationGroup.children) {
|
|
29803
30323
|
if (!child.userData.start) continue;
|
|
29804
30324
|
const phase = child.userData.phase || 0;
|
|
29805
|
-
const
|
|
30325
|
+
const direction = child.userData.direction || 1;
|
|
30326
|
+
const factor = ((time * 22e-5 * direction + phase) % 1 + 1) % 1;
|
|
29806
30327
|
child.position.lerpVectors(child.userData.start, child.userData.end, factor);
|
|
29807
30328
|
}
|
|
29808
30329
|
this.renderer.render(this.scene, this.camera);
|
|
@@ -29856,7 +30377,7 @@ var LiquidCarsAtlas = class extends HTMLElement {
|
|
|
29856
30377
|
this.canvas.style.cursor = "grab";
|
|
29857
30378
|
if (drag && !drag.moved) {
|
|
29858
30379
|
const node = this._pick(event);
|
|
29859
|
-
if (node) this.select(node.id);
|
|
30380
|
+
if (node) this.select(node.id, { source: "pointer" });
|
|
29860
30381
|
}
|
|
29861
30382
|
this.invalidate();
|
|
29862
30383
|
});
|
package/index.html
CHANGED
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
liquidcars-atlas { display: block; width: 100%; max-width: 1480px; margin: 0 auto; }
|
|
15
15
|
code { color: #85e5d3; }
|
|
16
16
|
</style>
|
|
17
|
-
<script type="module" src="./dist/liquidcars-atlas.js?v=0.1.
|
|
17
|
+
<script type="module" src="./dist/liquidcars-atlas.js?v=0.1.15"></script>
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
<header>
|
|
21
21
|
<h1>LiquidCars Atlas</h1>
|
|
22
|
-
<p>Prueba de distribución del componente <code><liquidcars-atlas></code> · v0.1.
|
|
22
|
+
<p>Prueba de distribución del componente <code><liquidcars-atlas></code> · v0.1.15</p>
|
|
23
23
|
</header>
|
|
24
24
|
<main>
|
|
25
25
|
<liquidcars-atlas id="atlas" height="720" relation-mode="selected" shell-labels="on"></liquidcars-atlas>
|