@liquidcars/atlas 0.1.37 → 0.1.39
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 +60 -33
- package/dist/liquidcars-atlas.js +447 -322
- package/dist/liquidcars-atlas.js.map +1 -1
- package/package.json +1 -1
- package/src/liquidcars-atlas.js +149 -21
package/package.json
CHANGED
package/src/liquidcars-atlas.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import * as THREE from "./three.module.js";
|
|
2
|
+
import atlasPackage from "../package.json" with { type: "json" };
|
|
3
|
+
|
|
4
|
+
// This value is bundled from the package manifest, so it always identifies
|
|
5
|
+
// the Atlas module that is actually executing rather than the host
|
|
6
|
+
// application's declared dependency.
|
|
7
|
+
export const ATLAS_VERSION = atlasPackage.version;
|
|
2
8
|
|
|
3
9
|
const DEFAULT_PALETTE = {
|
|
4
10
|
component: "#8ae4d0",
|
|
@@ -10,50 +16,156 @@ const RELATION_RENDER_ORDER = 15;
|
|
|
10
16
|
|
|
11
17
|
const GEOMETRY_DEFINITIONS = new Map();
|
|
12
18
|
const GEOMETRY_ALIASES = new Map();
|
|
19
|
+
const GEOMETRY_PACKS = new Map();
|
|
20
|
+
|
|
21
|
+
function cloneGeometryMetadata(value) {
|
|
22
|
+
if (Array.isArray(value)) return value.map(cloneGeometryMetadata);
|
|
23
|
+
if (!value || typeof value !== "object") return value;
|
|
24
|
+
return Object.fromEntries(
|
|
25
|
+
Object.entries(value)
|
|
26
|
+
.filter(([, item]) => typeof item !== "function" && item !== undefined)
|
|
27
|
+
.map(([key, item]) => [key, cloneGeometryMetadata(item)])
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeGeometryAuthors(value) {
|
|
32
|
+
const authors = value == null ? [] : Array.isArray(value) ? value : [value];
|
|
33
|
+
return authors
|
|
34
|
+
.map(author => typeof author === "string" ? { name: author } : cloneGeometryMetadata(author))
|
|
35
|
+
.filter(author => author && String(author.name || "").trim())
|
|
36
|
+
.map(author => ({ ...author, name: String(author.name).trim() }));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeGeometryStringList(value) {
|
|
40
|
+
const values = value == null ? [] : Array.isArray(value) ? value : [value];
|
|
41
|
+
return [...new Set(values.map(item => String(item || "").trim()).filter(Boolean))];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeGeometryLicense(value) {
|
|
45
|
+
if (!value) return null;
|
|
46
|
+
return typeof value === "string" ? { spdx: value } : cloneGeometryMetadata(value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeGeometrySource(source, index) {
|
|
50
|
+
const value = cloneGeometryMetadata(source || {});
|
|
51
|
+
const id = String(value.id || `source-${index + 1}`).trim();
|
|
52
|
+
if (!id) throw new TypeError("Geometry pack sources require an id.");
|
|
53
|
+
return {
|
|
54
|
+
...value,
|
|
55
|
+
id,
|
|
56
|
+
authors: normalizeGeometryAuthors(value.authors ?? value.author),
|
|
57
|
+
license: normalizeGeometryLicense(value.license)
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeGeometryResource(resource, index, sourceIds) {
|
|
62
|
+
const value = cloneGeometryMetadata(resource || {});
|
|
63
|
+
const id = String(value.id || "").trim();
|
|
64
|
+
if (!id) throw new TypeError(`Geometry pack resource at index ${index} requires an id.`);
|
|
65
|
+
const source = value.source == null ? null : String(value.source).trim();
|
|
66
|
+
if (source && !sourceIds.has(source)) {
|
|
67
|
+
throw new TypeError(`Geometry pack resource '${id}' references unknown source '${source}'.`);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
...value,
|
|
71
|
+
id,
|
|
72
|
+
kind: String(value.kind || value.type || "asset"),
|
|
73
|
+
source,
|
|
74
|
+
authors: normalizeGeometryAuthors(value.authors ?? value.author),
|
|
75
|
+
license: normalizeGeometryLicense(value.license)
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function normalizeGeometryPackMetadata(pack, namespace) {
|
|
80
|
+
const sources = (Array.isArray(pack.sources) ? pack.sources : []).map(normalizeGeometrySource);
|
|
81
|
+
const sourceIds = new Set(sources.map(source => source.id));
|
|
82
|
+
const resources = (Array.isArray(pack.resources) ? pack.resources : [])
|
|
83
|
+
.map((resource, index) => normalizeGeometryResource(resource, index, sourceIds));
|
|
84
|
+
if (new Set(resources.map(resource => resource.id)).size !== resources.length) {
|
|
85
|
+
throw new TypeError(`Geometry pack '${namespace}' contains duplicate resource ids.`);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
namespace,
|
|
89
|
+
name: String(pack.name || namespace),
|
|
90
|
+
version: String(pack.version || "0.0.0"),
|
|
91
|
+
description: String(pack.description || ""),
|
|
92
|
+
category: String(pack.category || pack.name || namespace),
|
|
93
|
+
homepage: pack.homepage ? String(pack.homepage) : "",
|
|
94
|
+
repository: cloneGeometryMetadata(pack.repository || null),
|
|
95
|
+
authors: normalizeGeometryAuthors(pack.authors ?? pack.author),
|
|
96
|
+
license: normalizeGeometryLicense(pack.license),
|
|
97
|
+
attribution: String(pack.attribution || ""),
|
|
98
|
+
collections: cloneGeometryMetadata(Array.isArray(pack.collections) ? pack.collections : []),
|
|
99
|
+
sources,
|
|
100
|
+
resources,
|
|
101
|
+
geometries: []
|
|
102
|
+
};
|
|
103
|
+
}
|
|
13
104
|
|
|
14
105
|
function publicGeometryDefinition(definition) {
|
|
15
|
-
const { create, ...metadata } = definition;
|
|
106
|
+
const { create, resourceMap, ...metadata } = definition;
|
|
16
107
|
return {
|
|
17
|
-
...metadata,
|
|
108
|
+
...cloneGeometryMetadata(metadata),
|
|
18
109
|
canonicalSize: [...definition.canonicalSize],
|
|
19
|
-
parameters:
|
|
20
|
-
palette:
|
|
21
|
-
anchors:
|
|
110
|
+
parameters: cloneGeometryMetadata(definition.parameters),
|
|
111
|
+
palette: cloneGeometryMetadata(definition.palette),
|
|
112
|
+
anchors: cloneGeometryMetadata(definition.anchors)
|
|
22
113
|
};
|
|
23
114
|
}
|
|
24
115
|
|
|
116
|
+
function publicGeometryPack(pack) {
|
|
117
|
+
return cloneGeometryMetadata(pack);
|
|
118
|
+
}
|
|
119
|
+
|
|
25
120
|
export function registerGeometryPack(pack) {
|
|
26
121
|
if (!pack || typeof pack !== "object") throw new TypeError("A geometry pack must be an object.");
|
|
27
122
|
const namespace = String(pack.namespace || "").trim();
|
|
28
|
-
if (!/^[a-z][a-z0-9-]*$/i.test(namespace)) throw new TypeError("A geometry pack requires a valid namespace.");
|
|
123
|
+
if (!/^[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*$/i.test(namespace)) throw new TypeError("A geometry pack requires a valid dot-separated namespace.");
|
|
29
124
|
if (!Array.isArray(pack.geometries) || !pack.geometries.length) throw new TypeError("A geometry pack requires at least one geometry.");
|
|
30
|
-
|
|
125
|
+
|
|
126
|
+
const metadata = normalizeGeometryPackMetadata(pack, namespace);
|
|
127
|
+
const resourceMap = Object.freeze(Object.fromEntries(
|
|
128
|
+
metadata.resources.map(resource => [resource.id, Object.freeze(cloneGeometryMetadata(resource))])
|
|
129
|
+
));
|
|
130
|
+
const definitions = [];
|
|
31
131
|
for (const source of pack.geometries) {
|
|
32
132
|
if (!source || typeof source !== "object" || !source.id) throw new TypeError(`Geometry pack '${namespace}' contains an invalid geometry.`);
|
|
33
133
|
const localId = String(source.id).includes(".") ? String(source.id).split(".").pop() : String(source.id);
|
|
34
134
|
const id = String(source.id).includes(".") ? String(source.id) : `${namespace}.${localId}`;
|
|
35
|
-
|
|
135
|
+
definitions.push({
|
|
36
136
|
...source,
|
|
37
137
|
id,
|
|
38
138
|
localId,
|
|
39
139
|
namespace,
|
|
40
|
-
pack:
|
|
41
|
-
packVersion:
|
|
42
|
-
category: source.category ||
|
|
140
|
+
pack: metadata.name,
|
|
141
|
+
packVersion: metadata.version,
|
|
142
|
+
category: source.category || metadata.category,
|
|
143
|
+
collection: source.collection == null ? "" : String(source.collection).trim(),
|
|
144
|
+
tags: normalizeGeometryStringList(source.tags),
|
|
145
|
+
keywords: normalizeGeometryStringList(source.keywords),
|
|
43
146
|
canonicalSize: Array.isArray(source.canonicalSize) && source.canonicalSize.length === 3 ? source.canonicalSize.map(Number) : [2.5, 1.5, 2.5],
|
|
44
147
|
parameters: source.parameters && typeof source.parameters === "object" ? source.parameters : {},
|
|
45
148
|
palette: { ...(pack.palette || {}), ...(source.palette || {}) },
|
|
46
|
-
anchors: { ...(pack.anchors || {}), ...(source.anchors || {}) }
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
GEOMETRY_ALIASES.set(id, id);
|
|
50
|
-
for (const alias of source.aliases || []) GEOMETRY_ALIASES.set(String(alias), id);
|
|
51
|
-
registered.push(id);
|
|
149
|
+
anchors: { ...(pack.anchors || {}), ...(source.anchors || {}) },
|
|
150
|
+
resourceMap
|
|
151
|
+
});
|
|
52
152
|
}
|
|
153
|
+
|
|
154
|
+
unregisterGeometryPack(namespace);
|
|
155
|
+
const registered = [];
|
|
156
|
+
for (const definition of definitions) {
|
|
157
|
+
GEOMETRY_DEFINITIONS.set(definition.id, definition);
|
|
158
|
+
GEOMETRY_ALIASES.set(definition.id, definition.id);
|
|
159
|
+
for (const alias of definition.aliases || []) GEOMETRY_ALIASES.set(String(alias), definition.id);
|
|
160
|
+
registered.push(definition.id);
|
|
161
|
+
}
|
|
162
|
+
metadata.geometries = [...registered];
|
|
163
|
+
GEOMETRY_PACKS.set(namespace, metadata);
|
|
164
|
+
const registeredPack = publicGeometryPack(metadata);
|
|
53
165
|
if (typeof globalThis.dispatchEvent === "function" && typeof CustomEvent === "function") {
|
|
54
|
-
globalThis.dispatchEvent(new CustomEvent("lc-geometry-registry-change", { detail: { namespace, geometries: registered } }));
|
|
166
|
+
globalThis.dispatchEvent(new CustomEvent("lc-geometry-registry-change", { detail: { namespace, geometries: registered, pack: registeredPack } }));
|
|
55
167
|
}
|
|
56
|
-
return
|
|
168
|
+
return registeredPack;
|
|
57
169
|
}
|
|
58
170
|
|
|
59
171
|
export function unregisterGeometryPack(namespace) {
|
|
@@ -64,6 +176,7 @@ export function unregisterGeometryPack(namespace) {
|
|
|
64
176
|
removed.push(id);
|
|
65
177
|
}
|
|
66
178
|
for (const [alias, id] of GEOMETRY_ALIASES) if (removed.includes(id)) GEOMETRY_ALIASES.delete(alias);
|
|
179
|
+
GEOMETRY_PACKS.delete(namespace);
|
|
67
180
|
return removed;
|
|
68
181
|
}
|
|
69
182
|
|
|
@@ -71,6 +184,10 @@ export function getGeometryCatalog() {
|
|
|
71
184
|
return [...GEOMETRY_DEFINITIONS.values()].map(publicGeometryDefinition);
|
|
72
185
|
}
|
|
73
186
|
|
|
187
|
+
export function getGeometryPackCatalog() {
|
|
188
|
+
return [...GEOMETRY_PACKS.values()].map(publicGeometryPack);
|
|
189
|
+
}
|
|
190
|
+
|
|
74
191
|
function geometryDefinition(kind) {
|
|
75
192
|
const id = GEOMETRY_ALIASES.get(kind) || kind;
|
|
76
193
|
return GEOMETRY_DEFINITIONS.get(id) || null;
|
|
@@ -116,7 +233,11 @@ registerGeometryPack({
|
|
|
116
233
|
namespace: "geo",
|
|
117
234
|
name: "Geometric primitives",
|
|
118
235
|
version: "1",
|
|
236
|
+
description: "Built-in Atlas geometric primitives.",
|
|
119
237
|
category: "Geometric primitives",
|
|
238
|
+
authors: [{ name: "LiquidCars S.L.", url: "https://liquidcars.net" }],
|
|
239
|
+
license: { spdx: "MIT", url: "https://opensource.org/license/mit" },
|
|
240
|
+
homepage: "https://github.com/gaelibus/atlas",
|
|
120
241
|
geometries: CORE_GEOMETRIES.map(id => ({
|
|
121
242
|
id,
|
|
122
243
|
primitive: id,
|
|
@@ -383,7 +504,7 @@ function createGeometryContent(definition, entity, modelPalette, invalidate, rep
|
|
|
383
504
|
...(entity.geometryPalette || entity.geometry_palette || {})
|
|
384
505
|
};
|
|
385
506
|
let descriptor = typeof definition.create === "function"
|
|
386
|
-
? definition.create({ parameters: { ...parameters }, colors: { ...localPalette }, entity: { ...entity } })
|
|
507
|
+
? definition.create({ parameters: { ...parameters }, colors: { ...localPalette }, entity: { ...entity }, resources: definition.resourceMap || {} })
|
|
387
508
|
: { parts: definition.parts || [] };
|
|
388
509
|
if (Array.isArray(descriptor)) descriptor = { parts: descriptor };
|
|
389
510
|
const parts = Array.isArray(descriptor?.parts) ? descriptor.parts : [];
|
|
@@ -678,11 +799,14 @@ export function captureRendererFrame(renderer, scene, camera, ownerDocument = gl
|
|
|
678
799
|
}
|
|
679
800
|
|
|
680
801
|
class LiquidCarsAtlas extends HTMLElement {
|
|
802
|
+
static version = ATLAS_VERSION;
|
|
803
|
+
|
|
681
804
|
static get observedAttributes() { return ["height", "relation-mode", "selection-mode", "selection-navigation", "item-facing", "shell-labels", "toolbar", "panel", "focus-mode", "focus-effect", "focus-layout"]; }
|
|
682
805
|
|
|
683
806
|
static registerGeometryPack(pack) { return registerGeometryPack(pack); }
|
|
684
807
|
static unregisterGeometryPack(namespace) { return unregisterGeometryPack(namespace); }
|
|
685
808
|
static getGeometryCatalog() { return getGeometryCatalog(); }
|
|
809
|
+
static getGeometryPackCatalog() { return getGeometryPackCatalog(); }
|
|
686
810
|
|
|
687
811
|
constructor() {
|
|
688
812
|
super();
|
|
@@ -1436,7 +1560,7 @@ class LiquidCarsAtlas extends HTMLElement {
|
|
|
1436
1560
|
return this.panBy(-dx * worldPerPixel, dy * worldPerPixel);
|
|
1437
1561
|
}
|
|
1438
1562
|
|
|
1439
|
-
fit() {
|
|
1563
|
+
fit(options = {}) {
|
|
1440
1564
|
const bounds = new THREE.Box3();
|
|
1441
1565
|
for (const node of this.nodes.values()) {
|
|
1442
1566
|
const center = this._worldPosition(node);
|
|
@@ -1452,6 +1576,10 @@ class LiquidCarsAtlas extends HTMLElement {
|
|
|
1452
1576
|
const baseDistance = this._baseCameraDistance();
|
|
1453
1577
|
this.targetZoom = Math.max(MIN_ZOOM, Math.min(2.8, baseDistance / requiredDistance));
|
|
1454
1578
|
this.targetFocus.copy(center);
|
|
1579
|
+
if (options.animate === false) {
|
|
1580
|
+
this.zoom = this.targetZoom;
|
|
1581
|
+
this.focus.copy(this.targetFocus);
|
|
1582
|
+
}
|
|
1455
1583
|
this.invalidate();
|
|
1456
1584
|
return this;
|
|
1457
1585
|
}
|