@gmvjs/torque3d 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Shibisty
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,62 @@
1
- # Temporary Holding Version
1
+ # @gmvjs/torque3d — Torque3D (BeamNG)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/cw/shibisty)
4
+
5
+ Torque3D, the BeamNG.drive engine: `.pc` configurations → jbeam slots → flexbodies from `.dae` with materials and paint.
6
+
7
+ An engine plugin for [`@gmvjs/core`](../core/README.md): formats, archives and game presets. Heavy parsers are loaded on demand (`import()` inside `load`), so adding the package costs almost nothing until a file of this engine is opened.
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ npm i @gmvjs/core @gmvjs/torque3d @gmvjs/collada
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ The whole engine (all games):
18
+
19
+ ```ts
20
+ import { createRegistry, ViewerSession } from '@gmvjs/core';
21
+ import { torque3dEngine } from '@gmvjs/torque3d';
22
+
23
+ const session = new ViewerSession(createRegistry([torque3dEngine]));
24
+ ```
25
+
26
+ One game: a package subpath (the game preset and an engine limited to that game):
27
+
28
+ ```ts
29
+ import { createRegistry, ViewerSession } from '@gmvjs/core';
30
+ import beamng from '@gmvjs/torque3d/beamng';
31
+
32
+ const session = new ViewerSession(createRegistry([beamng.engine]), beamng.preset.id);
33
+ ```
34
+
35
+ ## Games (subpaths)
36
+
37
+ | Import | Preset | Game | Status | Notes |
38
+ |---|---|---|---|---|
39
+ | `@gmvjs/torque3d/beamng` | `beamng` | BeamNG.drive | 🟡 beta | — |
40
+
41
+ ## Formats
42
+
43
+ | id | What | Files | Status |
44
+ |---|---|---|---|
45
+ | `torque3d-pc` | BeamNG: vehicle configuration (.pc) | `.pc` | 🟡 beta |
46
+
47
+ ## Dependencies
48
+
49
+ - `@gmvjs/core` — plugin API, types, ZIP/folder archives, zlib, DDS/BC, geometry
50
+ - `@gmvjs/collada` — `.dae` parsing (flexbody)
51
+
52
+ ## Not done yet
53
+
54
+ - Other Torque3D games (DTS/DIF)
55
+
56
+ ## License
57
+
58
+ MIT
59
+
60
+ [![Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/cw/shibisty)
61
+
62
+ If this project helps you, consider supporting its development on Patreon ❤️
@@ -0,0 +1,7 @@
1
+ export declare const engine: import("@gmvjs/core/plugin").EnginePlugin;
2
+ export declare const preset: import("@gmvjs/core/plugin").GamePreset;
3
+ declare const _default: {
4
+ engine: import("@gmvjs/core/plugin").EnginePlugin;
5
+ preset: import("@gmvjs/core/plugin").GamePreset;
6
+ };
7
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import { pickGames } from "@gmvjs/core/plugin";
2
+ import { torque3dEngine } from "../index.js";
3
+ const engine = pickGames(torque3dEngine, "beamng");
4
+ const preset = engine.games[0];
5
+ const beamng = { engine, preset };
6
+ export {
7
+ beamng as default,
8
+ engine,
9
+ preset
10
+ };
11
+ //# sourceMappingURL=beamng.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beamng.js","sources":["../../src/games/beamng.ts"],"sourcesContent":["// BeamNG.drive — subpath `@gmvjs/torque3d/beamng`: the game preset and an engine limited to it.\nimport { pickGames } from '@gmvjs/core/plugin';\nimport { torque3dEngine } from '../index';\n\nexport const engine = pickGames(torque3dEngine, 'beamng');\nexport const preset = engine.games![0]!;\nexport default { engine, preset };\n"],"names":[],"mappings":";;AAIO,MAAM,SAAS,UAAU,gBAAgB,QAAQ;AACjD,MAAM,SAAS,OAAO,MAAO,CAAC;AACrC,MAAA,SAAe,EAAE,QAAQ,OAAA;"}
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ import type { EngineTexts } from '@gmvjs/core/plugin';
2
+ export declare const EN: EngineTexts;
package/dist/i18n.js ADDED
@@ -0,0 +1,12 @@
1
+ const EN = {
2
+ "presets": {
3
+ "beamng": {
4
+ "hint": `Open a mod (.zip) or the vehicles/<car> folder with "Game folder" and pick a .pc configuration. Parts missing from the mod (shared game parts) are found if the game's content/vehicles is open too.`,
5
+ "archiveLabel": "Open mod (.zip)…"
6
+ }
7
+ }
8
+ };
9
+ export {
10
+ EN
11
+ };
12
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","sources":["../src/i18n.ts"],"sourcesContent":["// English texts of the torque3d engine (Russian ones live in the plugins themselves).\nimport type { EngineTexts } from '@gmvjs/core/plugin';\n\nexport const EN: EngineTexts = {\n \"presets\": {\n \"beamng\": {\n \"hint\": \"Open a mod (.zip) or the vehicles/<car> folder with \\\"Game folder\\\" and pick a .pc configuration. Parts missing from the mod (shared game parts) are found if the game's content/vehicles is open too.\",\n \"archiveLabel\": \"Open mod (.zip)…\"\n }\n }\n};\n"],"names":[],"mappings":"AAGO,MAAM,KAAkB;AAAA,EAC7B,WAAW;AAAA,IACT,UAAU;AAAA,MACR,QAAQ;AAAA,MACR,gBAAgB;AAAA,IAAA;AAAA,EAClB;AAEJ;"}
@@ -0,0 +1 @@
1
+ export declare const torque3dEngine: import("@gmvjs/core/plugin").EnginePlugin;
package/dist/index.js ADDED
@@ -0,0 +1,50 @@
1
+ import { defineEngine } from "@gmvjs/core/plugin";
2
+ import { EN } from "./i18n.js";
3
+ const torque3dEngine = defineEngine({
4
+ id: "torque3d",
5
+ title: "Torque3D (BeamNG)",
6
+ i18n: { en: EN },
7
+ formats: [
8
+ {
9
+ id: "torque3d-pc",
10
+ title: "BeamNG: конфигурация машины (.pc)",
11
+ engine: "torque3d",
12
+ extensions: ["pc"],
13
+ status: "beta",
14
+ sniff: (head) => {
15
+ const t = new TextDecoder().decode(head);
16
+ return /^\s*\{/.test(t) && /"(parts|mainPartName|format|model)"\s*:/.test(t) ? 1 : 0;
17
+ },
18
+ load: async (main, ctx) => (await import("./vehicle.js")).loadVehicle(main, ctx)
19
+ }
20
+ ],
21
+ materials: [
22
+ {
23
+ id: "torque3d-materials",
24
+ async resolve(mesh, lib) {
25
+ if (!mesh.material) return null;
26
+ const { collectMaterials } = await import("./vehicle.js");
27
+ const mats = await lib.memo("torque3d-materials", () => collectMaterials(lib.archives));
28
+ const t = mats.get(mesh.material.toLowerCase());
29
+ return t?.color ? lib.find(t.color) : null;
30
+ }
31
+ }
32
+ ],
33
+ games: [
34
+ {
35
+ id: "beamng",
36
+ group: "Игры",
37
+ title: "BeamNG.drive",
38
+ engine: "torque3d",
39
+ formats: ["torque3d-pc"],
40
+ status: "beta",
41
+ ui: { zUp: true, archives: true, archiveExts: ["zip"], archiveLabel: "Открыть мод (.zip)…" },
42
+ markers: ["beamng.drive.exe"],
43
+ hint: "Откройте мод (.zip) или папку vehicles/<машина> через «Папку игры» и выберите конфигурацию .pc. Детали, которых нет в моде (общие из игры), подтянутся, если открыт и content/vehicles игры."
44
+ }
45
+ ]
46
+ });
47
+ export {
48
+ torque3dEngine
49
+ };
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// BeamNG.drive: a vehicle is assembled from .pc (configuration) + .jbeam (parts and slots) + .dae (flexbody meshes) + *.materials.json.\nimport { defineEngine } from '@gmvjs/core/plugin';\nimport { EN } from './i18n';\n\nexport const torque3dEngine = defineEngine({\n id: 'torque3d',\n title: 'Torque3D (BeamNG)',\n i18n: { en: EN },\n formats: [\n {\n id: 'torque3d-pc', title: 'BeamNG: конфигурация машины (.pc)', engine: 'torque3d', extensions: ['pc'], status: 'beta',\n sniff: (head) => {\n const t = new TextDecoder().decode(head);\n return /^\\s*\\{/.test(t) && /\"(parts|mainPartName|format|model)\"\\s*:/.test(t) ? 1 : 0;\n },\n load: async (main, ctx) => (await import('./vehicle')).loadVehicle(main, ctx),\n },\n ],\n materials: [\n {\n id: 'torque3d-materials',\n async resolve(mesh, lib) {\n if (!mesh.material) return null;\n const { collectMaterials } = await import('./vehicle');\n const mats = await lib.memo('torque3d-materials', () => collectMaterials(lib.archives));\n const t = mats.get(mesh.material.toLowerCase());\n return t?.color ? lib.find(t.color) : null;\n },\n },\n ],\n games: [\n {\n id: 'beamng', group: 'Игры', title: 'BeamNG.drive', engine: 'torque3d', formats: ['torque3d-pc'], status: 'beta', ui: { zUp: true, archives: true, archiveExts: ['zip'], archiveLabel: 'Открыть мод (.zip)…' }, markers: ['beamng.drive.exe'],\n hint: 'Откройте мод (.zip) или папку vehicles/<машина> через «Папку игры» и выберите конфигурацию .pc. Детали, которых нет в моде (общие из игры), подтянутся, если открыт и content/vehicles игры.',\n },\n ],\n});\n"],"names":[],"mappings":";;AAIO,MAAM,iBAAiB,aAAa;AAAA,EACzC,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,MAAM,EAAE,IAAI,GAAA;AAAA,EACZ,SAAS;AAAA,IACP;AAAA,MACE,IAAI;AAAA,MAAe,OAAO;AAAA,MAAqC,QAAQ;AAAA,MAAY,YAAY,CAAC,IAAI;AAAA,MAAG,QAAQ;AAAA,MAC/G,OAAO,CAAC,SAAS;AACf,cAAM,IAAI,IAAI,cAAc,OAAO,IAAI;AACvC,eAAO,SAAS,KAAK,CAAC,KAAK,0CAA0C,KAAK,CAAC,IAAI,IAAI;AAAA,MACrF;AAAA,MACA,MAAM,OAAO,MAAM,SAAS,MAAM,OAAO,cAAW,GAAG,YAAY,MAAM,GAAG;AAAA,IAAA;AAAA,EAC9E;AAAA,EAEF,WAAW;AAAA,IACT;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,QAAQ,MAAM,KAAK;AACvB,YAAI,CAAC,KAAK,SAAU,QAAO;AAC3B,cAAM,EAAE,iBAAA,IAAqB,MAAM,OAAO,cAAW;AACrD,cAAM,OAAO,MAAM,IAAI,KAAK,sBAAsB,MAAM,iBAAiB,IAAI,QAAQ,CAAC;AACtF,cAAM,IAAI,KAAK,IAAI,KAAK,SAAS,aAAa;AAC9C,eAAO,GAAG,QAAQ,IAAI,KAAK,EAAE,KAAK,IAAI;AAAA,MACxC;AAAA,IAAA;AAAA,EACF;AAAA,EAEF,OAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MAAU,OAAO;AAAA,MAAQ,OAAO;AAAA,MAAgB,QAAQ;AAAA,MAAY,SAAS,CAAC,aAAa;AAAA,MAAG,QAAQ;AAAA,MAAQ,IAAI,EAAE,KAAK,MAAM,UAAU,MAAM,aAAa,CAAC,KAAK,GAAG,cAAc,sBAAA;AAAA,MAAyB,SAAS,CAAC,kBAAkB;AAAA,MAC5O,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ,CAAC;"}
@@ -0,0 +1,12 @@
1
+ export type JValue = string | number | boolean | null | JValue[] | {
2
+ [k: string]: JValue;
3
+ };
4
+ /** Parsing jbeam/pc/materials.json: // and /* *\/ comments, commas are optional. */
5
+ export declare function parseJbeam(text: string): JValue;
6
+ export type Vars = Map<string, number | string | boolean | null>;
7
+ type V = number | string | boolean | null;
8
+ /** A jbeam field value: a number, "$var" or "$=expression". */
9
+ export declare function evalValue(v: JValue | undefined, vars: Vars): V;
10
+ export declare const evalNumber: (v: JValue | undefined, vars: Vars, def?: number) => number;
11
+ export declare function evalExpr(src: string, vars: Vars): V;
12
+ export {};
package/dist/jbeam.js ADDED
@@ -0,0 +1,221 @@
1
+ function parseJbeam(text) {
2
+ let i = 0;
3
+ const n = text.length;
4
+ const skip = () => {
5
+ for (; ; ) {
6
+ while (i < n && /[\s,]/.test(text[i])) i++;
7
+ if (text[i] === "/" && text[i + 1] === "/") {
8
+ while (i < n && text[i] !== "\n") i++;
9
+ continue;
10
+ }
11
+ if (text[i] === "/" && text[i + 1] === "*") {
12
+ const e = text.indexOf("*/", i + 2);
13
+ i = e < 0 ? n : e + 2;
14
+ continue;
15
+ }
16
+ break;
17
+ }
18
+ };
19
+ const err = (msg) => {
20
+ const line = text.slice(0, i).split("\n").length;
21
+ return new Error(`jbeam: ${msg} (строка ${line})`);
22
+ };
23
+ const value = () => {
24
+ skip();
25
+ const c = text[i];
26
+ if (c === "{") {
27
+ i++;
28
+ const o = {};
29
+ for (; ; ) {
30
+ skip();
31
+ if (text[i] === "}") {
32
+ i++;
33
+ return o;
34
+ }
35
+ if (i >= n) throw err("незакрытый объект");
36
+ const k = value();
37
+ skip();
38
+ if (text[i] !== ":") throw err("ожидалось «:»");
39
+ i++;
40
+ o[String(k)] = value();
41
+ }
42
+ }
43
+ if (c === "[") {
44
+ i++;
45
+ const a = [];
46
+ for (; ; ) {
47
+ skip();
48
+ if (text[i] === "]") {
49
+ i++;
50
+ return a;
51
+ }
52
+ if (i >= n) throw err("незакрытый массив");
53
+ a.push(value());
54
+ }
55
+ }
56
+ if (c === '"') {
57
+ let s = "";
58
+ i++;
59
+ while (i < n && text[i] !== '"') {
60
+ if (text[i] === "\\") {
61
+ const e = text[i + 1];
62
+ s += e === "n" ? "\n" : e === "t" ? " " : e;
63
+ i += 2;
64
+ } else s += text[i++];
65
+ }
66
+ i++;
67
+ return s;
68
+ }
69
+ const m = /^(-?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?|true|false|null|nil|-?inf|nan)/i.exec(text.slice(i, i + 40));
70
+ if (!m) throw err(`неожиданный символ «${c ?? "конец"}»`);
71
+ i += m[0].length;
72
+ const t = m[0].toLowerCase();
73
+ if (t === "true") return true;
74
+ if (t === "false") return false;
75
+ if (t === "null" || t === "nil") return null;
76
+ if (t === "inf") return Infinity;
77
+ if (t === "-inf") return -Infinity;
78
+ if (t === "nan") return NaN;
79
+ return +m[0];
80
+ };
81
+ const v = value();
82
+ return v;
83
+ }
84
+ function evalValue(v, vars) {
85
+ if (typeof v !== "string") return v ?? null;
86
+ if (v.startsWith("$=")) return evalExpr(v.slice(2), vars);
87
+ if (/^\$[A-Za-z_]\w*$/.test(v)) return vars.has(v) ? vars.get(v) : null;
88
+ return v;
89
+ }
90
+ const evalNumber = (v, vars, def = 0) => {
91
+ const r = evalValue(v, vars);
92
+ return typeof r === "number" && Number.isFinite(r) ? r : typeof r === "boolean" ? +r : def;
93
+ };
94
+ const FUNCS = {
95
+ case: (...a) => {
96
+ for (let k = 0; k + 1 < a.length; k += 2) if (a[k] !== null && a[k] !== false) return a[k + 1];
97
+ return a.length % 2 ? a[a.length - 1] : null;
98
+ },
99
+ min: (...a) => Math.min(...a.map(Number)),
100
+ max: (...a) => Math.max(...a.map(Number)),
101
+ abs: (a) => Math.abs(Number(a)),
102
+ clamp: (x, lo, hi) => Math.min(Number(hi), Math.max(Number(lo), Number(x))),
103
+ round: (x) => Math.round(Number(x)),
104
+ floor: (x) => Math.floor(Number(x)),
105
+ ceil: (x) => Math.ceil(Number(x)),
106
+ sqrt: (x) => Math.sqrt(Number(x)),
107
+ sin: (x) => Math.sin(Number(x)),
108
+ cos: (x) => Math.cos(Number(x)),
109
+ tan: (x) => Math.tan(Number(x)),
110
+ rad: (x) => Number(x) * Math.PI / 180,
111
+ deg: (x) => Number(x) * 180 / Math.PI
112
+ };
113
+ function evalExpr(src, vars) {
114
+ const toks = src.match(/\$?[A-Za-z_][\w.]*|\d+\.?\d*(?:[eE][-+]?\d+)?|\.\d+|==|~=|!=|<=|>=|&&|\|\||[-+*/%^()<>,!?:]|"[^"]*"/g) ?? [];
115
+ let p = 0;
116
+ const peek = () => toks[p], next = () => toks[p++];
117
+ const truthy = (v) => v !== null && v !== false;
118
+ const num = (v) => typeof v === "number" ? v : typeof v === "boolean" ? +v : Number(v) || 0;
119
+ const primary = () => {
120
+ const t = next();
121
+ if (t === void 0) return null;
122
+ if (t === "(") {
123
+ const v = ternary();
124
+ next();
125
+ return v;
126
+ }
127
+ if (t === "-") return -num(unary());
128
+ if (t === "not" || t === "!") return !truthy(unary());
129
+ if (t.startsWith('"')) return t.slice(1, -1);
130
+ if (/^[\d.]/.test(t)) return +t;
131
+ if (t.startsWith("$")) return vars.has(t) ? vars.get(t) : null;
132
+ if (t === "nil") return null;
133
+ if (t === "true") return true;
134
+ if (t === "false") return false;
135
+ if (peek() === "(") {
136
+ next();
137
+ const args = [];
138
+ while (peek() !== ")" && p < toks.length) {
139
+ args.push(ternary());
140
+ if (peek() === ",") next();
141
+ }
142
+ next();
143
+ const f = FUNCS[t.replace(/^math\./, "")];
144
+ return f ? f(...args) : null;
145
+ }
146
+ return null;
147
+ };
148
+ const unary = () => primary();
149
+ const power = () => {
150
+ let a = unary();
151
+ while (peek() === "^") {
152
+ next();
153
+ a = num(a) ** num(unary());
154
+ }
155
+ return a;
156
+ };
157
+ const mul = () => {
158
+ let a = power();
159
+ for (let t = peek(); t === "*" || t === "/" || t === "%"; t = peek()) {
160
+ next();
161
+ const b = num(power());
162
+ a = t === "*" ? num(a) * b : t === "/" ? num(a) / b : num(a) % b;
163
+ }
164
+ return a;
165
+ };
166
+ const add = () => {
167
+ let a = mul();
168
+ for (let t = peek(); t === "+" || t === "-"; t = peek()) {
169
+ next();
170
+ const b = num(mul());
171
+ a = t === "+" ? num(a) + b : num(a) - b;
172
+ }
173
+ return a;
174
+ };
175
+ const cmp = () => {
176
+ let a = add();
177
+ for (let t = peek(); t && ["==", "~=", "!=", "<", ">", "<=", ">="].includes(t); t = peek()) {
178
+ next();
179
+ const b = add();
180
+ a = t === "==" ? a === b : t === "~=" || t === "!=" ? a !== b : t === "<" ? num(a) < num(b) : t === ">" ? num(a) > num(b) : t === "<=" ? num(a) <= num(b) : num(a) >= num(b);
181
+ }
182
+ return a;
183
+ };
184
+ const and = () => {
185
+ let a = cmp();
186
+ while (peek() === "and" || peek() === "&&") {
187
+ next();
188
+ const b = cmp();
189
+ a = truthy(a) ? b : a;
190
+ }
191
+ return a;
192
+ };
193
+ const or = () => {
194
+ let a = and();
195
+ while (peek() === "or" || peek() === "||") {
196
+ next();
197
+ const b = and();
198
+ a = truthy(a) ? a : b;
199
+ }
200
+ return a;
201
+ };
202
+ const ternary = () => {
203
+ const c = or();
204
+ if (peek() === "?") {
205
+ next();
206
+ const a = ternary();
207
+ next();
208
+ const b = ternary();
209
+ return truthy(c) ? a : b;
210
+ }
211
+ return c;
212
+ };
213
+ return ternary();
214
+ }
215
+ export {
216
+ evalExpr,
217
+ evalNumber,
218
+ evalValue,
219
+ parseJbeam
220
+ };
221
+ //# sourceMappingURL=jbeam.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jbeam.js","sources":["../src/jbeam.ts"],"sourcesContent":["// JBeam (BeamNG.drive): \"JSON with liberties\" (comments, trailing and missing commas). Plus evaluation of \"$=…\" expressions.\n\nexport type JValue = string | number | boolean | null | JValue[] | { [k: string]: JValue };\n\n/** Parsing jbeam/pc/materials.json: // and /* *\\/ comments, commas are optional. */\nexport function parseJbeam(text: string): JValue {\n let i = 0;\n const n = text.length;\n const skip = () => {\n for (;;) {\n while (i < n && /[\\s,]/.test(text[i]!)) i++;\n if (text[i] === '/' && text[i + 1] === '/') { while (i < n && text[i] !== '\\n') i++; continue; }\n if (text[i] === '/' && text[i + 1] === '*') { const e = text.indexOf('*/', i + 2); i = e < 0 ? n : e + 2; continue; }\n break;\n }\n };\n const err = (msg: string) => { const line = text.slice(0, i).split('\\n').length; return new Error(`jbeam: ${msg} (строка ${line})`); };\n const value = (): JValue => {\n skip();\n const c = text[i];\n if (c === '{') {\n i++;\n const o: { [k: string]: JValue } = {};\n for (;;) {\n skip();\n if (text[i] === '}') { i++; return o; }\n if (i >= n) throw err('незакрытый объект');\n const k = value();\n skip();\n if (text[i] !== ':') throw err('ожидалось «:»');\n i++;\n o[String(k)] = value();\n }\n }\n if (c === '[') {\n i++;\n const a: JValue[] = [];\n for (;;) {\n skip();\n if (text[i] === ']') { i++; return a; }\n if (i >= n) throw err('незакрытый массив');\n a.push(value());\n }\n }\n if (c === '\"') {\n let s = '';\n i++;\n while (i < n && text[i] !== '\"') {\n if (text[i] === '\\\\') { const e = text[i + 1]!; s += e === 'n' ? '\\n' : e === 't' ? '\\t' : e; i += 2; }\n else s += text[i++];\n }\n i++;\n return s;\n }\n const m = /^(-?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][-+]?\\d+)?|true|false|null|nil|-?inf|nan)/i.exec(text.slice(i, i + 40));\n if (!m) throw err(`неожиданный символ «${c ?? 'конец'}»`);\n i += m[0].length;\n const t = m[0].toLowerCase();\n if (t === 'true') return true;\n if (t === 'false') return false;\n if (t === 'null' || t === 'nil') return null;\n if (t === 'inf') return Infinity;\n if (t === '-inf') return -Infinity;\n if (t === 'nan') return NaN;\n return +m[0];\n };\n const v = value();\n return v;\n}\n\n// ---------- \"$=…\" expressions (a Lua subset, as in BeamNG) ----------\nexport type Vars = Map<string, number | string | boolean | null>;\ntype V = number | string | boolean | null;\n\n/** A jbeam field value: a number, \"$var\" or \"$=expression\". */\nexport function evalValue(v: JValue | undefined, vars: Vars): V {\n if (typeof v !== 'string') return (v ?? null) as V;\n if (v.startsWith('$=')) return evalExpr(v.slice(2), vars);\n if (/^\\$[A-Za-z_]\\w*$/.test(v)) return vars.has(v) ? vars.get(v)! : null;\n return v;\n}\nexport const evalNumber = (v: JValue | undefined, vars: Vars, def = 0): number => {\n const r = evalValue(v, vars);\n return typeof r === 'number' && Number.isFinite(r) ? r : typeof r === 'boolean' ? +r : def;\n};\n\nconst FUNCS: Record<string, (...a: V[]) => V> = {\n case: (...a) => { for (let k = 0; k + 1 < a.length; k += 2) if (a[k] !== null && a[k] !== false) return a[k + 1]!; return a.length % 2 ? a[a.length - 1]! : null; },\n min: (...a) => Math.min(...a.map(Number)),\n max: (...a) => Math.max(...a.map(Number)),\n abs: (a) => Math.abs(Number(a)),\n clamp: (x, lo, hi) => Math.min(Number(hi), Math.max(Number(lo), Number(x))),\n round: (x) => Math.round(Number(x)),\n floor: (x) => Math.floor(Number(x)),\n ceil: (x) => Math.ceil(Number(x)),\n sqrt: (x) => Math.sqrt(Number(x)),\n sin: (x) => Math.sin(Number(x)),\n cos: (x) => Math.cos(Number(x)),\n tan: (x) => Math.tan(Number(x)),\n rad: (x) => (Number(x) * Math.PI) / 180,\n deg: (x) => (Number(x) * 180) / Math.PI,\n};\n\nexport function evalExpr(src: string, vars: Vars): V {\n const toks = src.match(/\\$?[A-Za-z_][\\w.]*|\\d+\\.?\\d*(?:[eE][-+]?\\d+)?|\\.\\d+|==|~=|!=|<=|>=|&&|\\|\\||[-+*/%^()<>,!?:]|\"[^\"]*\"/g) ?? [];\n let p = 0;\n const peek = () => toks[p], next = () => toks[p++];\n const truthy = (v: V) => v !== null && v !== false;\n const num = (v: V) => (typeof v === 'number' ? v : typeof v === 'boolean' ? +v : Number(v) || 0);\n const primary = (): V => {\n const t = next();\n if (t === undefined) return null;\n if (t === '(') { const v = ternary(); next(); return v; }\n if (t === '-') return -num(unary());\n if (t === 'not' || t === '!') return !truthy(unary());\n if (t.startsWith('\"')) return t.slice(1, -1);\n if (/^[\\d.]/.test(t)) return +t;\n if (t.startsWith('$')) return vars.has(t) ? vars.get(t)! : null;\n if (t === 'nil') return null;\n if (t === 'true') return true;\n if (t === 'false') return false;\n if (peek() === '(') {\n next();\n const args: V[] = [];\n while (peek() !== ')' && p < toks.length) { args.push(ternary()); if (peek() === ',') next(); }\n next();\n const f = FUNCS[t.replace(/^math\\./, '')];\n return f ? f(...args) : null;\n }\n return null;\n };\n const unary = (): V => primary();\n const power = (): V => { let a = unary(); while (peek() === '^') { next(); a = num(a) ** num(unary()); } return a; };\n const mul = (): V => {\n let a = power();\n for (let t = peek(); t === '*' || t === '/' || t === '%'; t = peek()) { next(); const b = num(power()); a = t === '*' ? num(a) * b : t === '/' ? num(a) / b : num(a) % b; }\n return a;\n };\n const add = (): V => { let a = mul(); for (let t = peek(); t === '+' || t === '-'; t = peek()) { next(); const b = num(mul()); a = t === '+' ? num(a) + b : num(a) - b; } return a; };\n const cmp = (): V => {\n let a = add();\n for (let t = peek(); t && ['==', '~=', '!=', '<', '>', '<=', '>='].includes(t); t = peek()) {\n next();\n const b = add();\n a = t === '==' ? a === b : t === '~=' || t === '!=' ? a !== b : t === '<' ? num(a) < num(b) : t === '>' ? num(a) > num(b) : t === '<=' ? num(a) <= num(b) : num(a) >= num(b);\n }\n return a;\n };\n const and = (): V => { let a = cmp(); while (peek() === 'and' || peek() === '&&') { next(); const b = cmp(); a = truthy(a) ? b : a; } return a; };\n const or = (): V => { let a = and(); while (peek() === 'or' || peek() === '||') { next(); const b = and(); a = truthy(a) ? a : b; } return a; };\n const ternary = (): V => { const c = or(); if (peek() === '?') { next(); const a = ternary(); next(); const b = ternary(); return truthy(c) ? a : b; } return c; };\n return ternary();\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import type { LoadContext } from '@gmvjs/core/plugin';
2
+ import type { Archive, Mesh, Model, SurfaceInfo, VFile } from '@gmvjs/core/types';
3
+ import { type ColladaMesh } from '@gmvjs/collada';
4
+ import { evalValue, type JValue, type Vars } from './jbeam';
5
+ type Obj = {
6
+ [k: string]: JValue;
7
+ };
8
+ export interface JPart {
9
+ name: string;
10
+ slotType: string;
11
+ data: Obj;
12
+ file: string;
13
+ }
14
+ interface Slot {
15
+ type: string;
16
+ def: string;
17
+ opts: Obj | null;
18
+ }
19
+ interface Chosen {
20
+ part: JPart;
21
+ offsets: Obj[];
22
+ }
23
+ export interface Flexbody {
24
+ mesh: string;
25
+ part: string;
26
+ opts: Obj | null;
27
+ offsets: Obj[];
28
+ }
29
+ export declare function slotsOf(part: JPart): Slot[];
30
+ /** All parts from jbeam files in the archives (the mod's vehicles/ folder and, if open, the game's). */
31
+ export declare function collectParts(archives: readonly Archive[]): Promise<Map<string, JPart>>;
32
+ export interface Assembly {
33
+ chosen: Chosen[];
34
+ flexbodies: Flexbody[];
35
+ missing: string[];
36
+ substituted: string[];
37
+ vars: Vars;
38
+ }
39
+ /** Walk the slot tree from the main part. pcParts: slotType -> part name ("" = empty). */
40
+ export declare function assemble(parts: Map<string, JPart>, mainPart: string, pcParts: Record<string, string>, pcVars?: Record<string, JValue>): Assembly;
41
+ /** Copy of a mesh with the flexbody transform and node offset applied. */
42
+ export declare function placeMesh(src: ColladaMesh, fb: Flexbody, vars: Vars): Mesh;
43
+ export interface PcFile {
44
+ parts: Record<string, string>;
45
+ vars: Record<string, JValue>;
46
+ mainPartName?: string;
47
+ model?: string;
48
+ paints?: Array<{
49
+ baseColor?: number[];
50
+ }>;
51
+ }
52
+ export declare function loadVehicle(main: VFile, ctx: LoadContext): Promise<Model>;
53
+ /** color = the color map (empty if only a color multiplier or a generic_* placeholder); factor = baseColorFactor of layer 0. */
54
+ export interface MaterialTex {
55
+ color: string;
56
+ factor?: number[];
57
+ normal?: string;
58
+ surface?: SurfaceInfo;
59
+ }
60
+ export declare function readMaterialsJson(text: string, out: Map<string, MaterialTex>): void;
61
+ /** The old TorqueScript format: singleton Material(name) { mapTo = "x"; diffuseMap[0] = "…"; } */
62
+ export declare function readMaterialsCs(text: string, out: Map<string, MaterialTex>): void;
63
+ /** All materials from the open archives (json takes precedence over cs: json is read first). */
64
+ export declare function collectMaterials(archives: readonly Archive[]): Promise<Map<string, MaterialTex>>;
65
+ export { evalValue };
@@ -0,0 +1,276 @@
1
+ import { decodeText } from "@gmvjs/core/binary";
2
+ import { sumVerts, sumTris, boundsOf } from "@gmvjs/core/geometry";
3
+ import { parseCollada } from "@gmvjs/collada";
4
+ import { parseJbeam, evalNumber } from "./jbeam.js";
5
+ import { evalValue } from "./jbeam.js";
6
+ const isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
7
+ function rows(v) {
8
+ if (!Array.isArray(v)) return [];
9
+ return v.slice(1).filter((r) => Array.isArray(r));
10
+ }
11
+ const lastObj = (r) => {
12
+ for (let k = r.length - 1; k >= 0; k--) if (isObj(r[k])) return r[k];
13
+ return null;
14
+ };
15
+ function slotsOf(part) {
16
+ const out = [];
17
+ for (const r of rows(part.data.slots)) out.push({ type: String(r[0] ?? ""), def: String(r[1] ?? ""), opts: lastObj(r) });
18
+ for (const r of rows(part.data.slots2)) {
19
+ const allow = Array.isArray(r[1]) ? r[1].map(String) : [String(r[0] ?? "")];
20
+ for (const t of allow.length ? allow : [String(r[0] ?? "")]) out.push({ type: String(r[0] ?? t), def: String(r[3] ?? ""), opts: lastObj(r) });
21
+ }
22
+ return out;
23
+ }
24
+ async function collectParts(archives) {
25
+ const parts = /* @__PURE__ */ new Map();
26
+ for (const a of archives)
27
+ for (const e of a.entries) {
28
+ if (!e.path.endsWith(".jbeam")) continue;
29
+ let data;
30
+ try {
31
+ data = parseJbeam(decodeText(await a.read(e)));
32
+ } catch {
33
+ continue;
34
+ }
35
+ if (!isObj(data)) continue;
36
+ for (const [name, v] of Object.entries(data)) if (isObj(v) && !parts.has(name)) parts.set(name, { name, slotType: String(v.slotType ?? ""), data: v, file: e.path });
37
+ }
38
+ return parts;
39
+ }
40
+ function assemble(parts, mainPart, pcParts, pcVars = {}) {
41
+ const chosen = [], missing = [], substituted = [], seen = /* @__PURE__ */ new Set();
42
+ const bySlot = /* @__PURE__ */ new Map();
43
+ for (const p of parts.values()) {
44
+ if (!bySlot.has(p.slotType)) bySlot.set(p.slotType, []);
45
+ bySlot.get(p.slotType).push(p.name);
46
+ }
47
+ const visit = (name, offsets) => {
48
+ if (!name || seen.has(name)) return;
49
+ const part = parts.get(name);
50
+ if (!part) {
51
+ missing.push(name);
52
+ return;
53
+ }
54
+ seen.add(name);
55
+ chosen.push({ part, offsets });
56
+ for (const s of slotsOf(part)) {
57
+ const pick = Object.prototype.hasOwnProperty.call(pcParts, s.type) ? pcParts[s.type] : s.def;
58
+ const off = s.opts && isObj(s.opts.nodeOffset) ? [...offsets, s.opts.nodeOffset] : offsets;
59
+ if (pick && !parts.has(pick)) {
60
+ const alt = parts.has(s.def) ? s.def : bySlot.get(s.type)?.[0];
61
+ if (alt) {
62
+ substituted.push(`${pick} → ${alt}`);
63
+ visit(alt, off);
64
+ continue;
65
+ }
66
+ }
67
+ visit(pick, off);
68
+ }
69
+ };
70
+ visit(mainPart, []);
71
+ const vars = /* @__PURE__ */ new Map();
72
+ for (const c of chosen) {
73
+ const tbl = c.part.data.variables;
74
+ if (!Array.isArray(tbl) || !Array.isArray(tbl[0])) continue;
75
+ const head = tbl[0].map(String), iName = head.indexOf("name"), iDef = head.indexOf("default");
76
+ for (const r of rows(tbl)) if (iName >= 0 && iDef >= 0) vars.set(String(r[iName]), r[iDef] ?? null);
77
+ }
78
+ for (const [k, v] of Object.entries(pcVars)) vars.set(k, v);
79
+ const flexbodies = [];
80
+ for (const c of chosen)
81
+ for (const r of rows(c.part.data.flexbodies)) {
82
+ if (typeof r[0] !== "string" || !r[0]) continue;
83
+ flexbodies.push({ mesh: r[0], part: c.part.name, opts: lastObj(r), offsets: c.offsets });
84
+ }
85
+ return { chosen, flexbodies, missing, substituted, vars };
86
+ }
87
+ const vec = (o, vars, def) => isObj(o) ? [evalNumber(o.x, vars, def), evalNumber(o.y, vars, def), evalNumber(o.z, vars, def)] : [def, def, def];
88
+ function rotMat([rx, ry, rz]) {
89
+ const [a, b, c] = [rx, ry, rz].map((d) => d * Math.PI / 180);
90
+ const ca = Math.cos(a), sa = Math.sin(a), cb = Math.cos(b), sb = Math.sin(b), cc = Math.cos(c), sc = Math.sin(c);
91
+ return [cc * cb, cc * sb * sa - sc * ca, cc * sb * ca + sc * sa, sc * cb, sc * sb * sa + cc * ca, sc * sb * ca - cc * sa, -sb, cb * sa, cb * ca];
92
+ }
93
+ function placeMesh(src, fb, vars) {
94
+ const o = fb.opts, pos = vec(o?.pos, vars, 0), rot = vec(o?.rot, vars, 0), scl = vec(o?.scale, vars, 1), R = rotMat(rot);
95
+ const n = src.vertexCount, P = new Float32Array(n * 3), N = new Float32Array(n * 3);
96
+ for (let v = 0; v < n; v++) {
97
+ const x = src.pos[v * 3] * scl[0], y = src.pos[v * 3 + 1] * scl[1], z = src.pos[v * 3 + 2] * scl[2];
98
+ P[v * 3] = R[0] * x + R[1] * y + R[2] * z + pos[0];
99
+ P[v * 3 + 1] = R[3] * x + R[4] * y + R[5] * z + pos[1];
100
+ P[v * 3 + 2] = R[6] * x + R[7] * y + R[8] * z + pos[2];
101
+ const a = src.nor[v * 3], b = src.nor[v * 3 + 1], c = src.nor[v * 3 + 2];
102
+ N[v * 3] = R[0] * a + R[1] * b + R[2] * c;
103
+ N[v * 3 + 1] = R[3] * a + R[4] * b + R[5] * c;
104
+ N[v * 3 + 2] = R[6] * a + R[7] * b + R[8] * c;
105
+ }
106
+ let ox = 0, oy = 0, oz = 0;
107
+ for (const off of fb.offsets) {
108
+ const [x, y, z] = vec(off, vars, 0);
109
+ ox += x;
110
+ oy += y;
111
+ oz += z;
112
+ }
113
+ if (ox || oy || oz) for (let v = 0; v < n; v++) {
114
+ const x = P[v * 3];
115
+ P[v * 3] = x + Math.sign(x) * ox;
116
+ P[v * 3 + 1] += oy;
117
+ P[v * 3 + 2] += oz;
118
+ }
119
+ return { ...src, name: `${fb.mesh} · ${fb.part}`, pos: P, nor: N };
120
+ }
121
+ async function loadVehicle(main, ctx) {
122
+ const pc = parseJbeam(decodeText(await main.arrayBuffer()));
123
+ if (!pc || typeof pc !== "object" || !pc.parts) throw new Error('Не конфигурация BeamNG (.pc): нет "parts"');
124
+ const archives = [...ctx.archives];
125
+ if (main.archive && !archives.includes(main.archive)) archives.push(main.archive);
126
+ const parts = await collectParts(archives);
127
+ if (!parts.size) throw new Error("Не найдено ни одного .jbeam — откройте мод целиком («Папка игры…» или zip)");
128
+ const model = pc.model ?? (main.path ? /vehicles\/([^/]+)\//.exec(main.path)?.[1] : void 0) ?? "";
129
+ let mainPart = pc.mainPartName ?? "";
130
+ if (!parts.has(mainPart)) mainPart = [...parts.values()].find((p) => p.slotType === "main" && p.file.includes(`/vehicles/${model.toLowerCase()}/`))?.name ?? [...parts.values()].find((p) => p.slotType === "main")?.name ?? "";
131
+ if (!mainPart) throw new Error('Не найдена главная деталь (slotType "main")');
132
+ const asm = assemble(parts, mainPart, pc.parts, pc.vars ?? {});
133
+ const wanted = new Set(asm.flexbodies.map((f) => f.mesh.toLowerCase()));
134
+ const byName = /* @__PURE__ */ new Map();
135
+ const daes = [];
136
+ for (const a of archives) for (const e of a.entries) if (e.path.endsWith(".dae")) daes.push([a, e]);
137
+ const vehDir = `/vehicles/${model.toLowerCase()}/`;
138
+ daes.sort((x, y) => +y[1].path.includes(vehDir) - +x[1].path.includes(vehDir));
139
+ for (const [a, e] of daes) {
140
+ if ([...wanted].every((w) => byName.has(w))) break;
141
+ const xml = decodeText(await a.read(e)), low = xml.toLowerCase();
142
+ const need = new Set([...wanted].filter((w) => !byName.has(w) && (low.includes(`name="${w}"`) || low.includes(`id="${w}"`))));
143
+ if (!need.size) continue;
144
+ for (const m of parseCollada(xml, { nodes: need }).meshes) {
145
+ const k = m.nodeName.toLowerCase();
146
+ if (!byName.has(k)) byName.set(k, []);
147
+ byName.get(k).push(m);
148
+ }
149
+ }
150
+ const paint = pc.paints?.[0]?.baseColor;
151
+ const mats = await collectMaterials(archives);
152
+ const meshes = [], noMesh = /* @__PURE__ */ new Set();
153
+ for (const fb of asm.flexbodies) {
154
+ const src = byName.get(fb.mesh.toLowerCase());
155
+ if (!src) {
156
+ noMesh.add(fb.mesh);
157
+ continue;
158
+ }
159
+ for (const m of src) {
160
+ const pm = placeMesh(m, fb, asm.vars), mt = mats.get((m.material ?? "").toLowerCase());
161
+ if (mt?.normal) pm.normalMap = mt.normal;
162
+ if (mt?.surface) pm.surface = mt.surface;
163
+ if (mt?.color) pm.texture = mt.color;
164
+ else pm.color = (mt?.factor ?? paint ?? [0.7, 0.7, 0.7]).slice(0, 3).map((x) => Math.min(1, x));
165
+ meshes.push(pm);
166
+ }
167
+ }
168
+ if (!meshes.length) throw new Error(`Ни один меш не найден в .dae (нужно ${wanted.size}: ${[...wanted].slice(0, 5).join(", ")}…)`);
169
+ const info = [
170
+ `BeamNG: ${model} (${main.name}), деталей ${asm.chosen.length}, мешей ${meshes.length}, вершин ${sumVerts(meshes)}, треугольников ${sumTris(meshes)}`,
171
+ "Пропсы (руль, приборы, фары-источники) и деформация пока не показываются."
172
+ ];
173
+ if (asm.missing.length) info.push(`Нет деталей (в моде не хватает файлов игры?): ${asm.missing.slice(0, 8).join(", ")}${asm.missing.length > 8 ? "…" : ""}`);
174
+ if (asm.substituted.length) info.push(`Заменены деталями слота по умолчанию: ${asm.substituted.slice(0, 6).join(", ")}${asm.substituted.length > 6 ? "…" : ""}`);
175
+ if (noMesh.size) info.push(`Нет мешей в .dae: ${[...noMesh].slice(0, 8).join(", ")}${noMesh.size > 8 ? "…" : ""}`);
176
+ return {
177
+ meshes,
178
+ skeleton: null,
179
+ motions: [],
180
+ bounds: boundsOf(meshes),
181
+ flipZ: false,
182
+ defaultZUp: true,
183
+ cameraYaw: 0,
184
+ info,
185
+ meta: { beamng: { model, parts: asm.chosen.map((c) => c.part.name) } }
186
+ };
187
+ }
188
+ const PLACEHOLDER = /(^|\/)(null|generic_[a-z]*|white)(\.[a-z]+)*\.(dds|png|jpe?g|tga)$/i;
189
+ const putMat = (out, names, t) => {
190
+ for (const name of names) if (name && !out.has(name.toLowerCase())) out.set(name.toLowerCase(), t);
191
+ };
192
+ function readMaterialsJson(text, out) {
193
+ let j;
194
+ try {
195
+ j = parseJbeam(text);
196
+ } catch {
197
+ return;
198
+ }
199
+ if (!isObj(j)) return;
200
+ for (const [key, v] of Object.entries(j)) {
201
+ if (!isObj(v)) continue;
202
+ let color = "", factor, normal = "", surface;
203
+ const stages = Array.isArray(v.Stages) ? v.Stages : [];
204
+ for (const s of stages) {
205
+ if (!isObj(s)) continue;
206
+ if (!surface) surface = pbrOf(s);
207
+ if (!normal && typeof s.normalMap === "string" && s.normalMap && !/generic_n|null\./i.test(s.normalMap)) normal = s.normalMap;
208
+ if (!factor && Array.isArray(s.baseColorFactor)) factor = s.baseColorFactor.slice(0, 3).map(Number);
209
+ for (const k of ["baseColorMap", "colorMap", "diffuseMap"]) {
210
+ const p = s[k];
211
+ if (typeof p === "string" && p && !PLACEHOLDER.test(p)) {
212
+ color = p;
213
+ break;
214
+ }
215
+ }
216
+ if (color) break;
217
+ }
218
+ if (!color && typeof v.diffuseMap === "string" && !PLACEHOLDER.test(v.diffuseMap)) color = v.diffuseMap;
219
+ if (!color && !factor) continue;
220
+ putMat(out, [key, typeof v.mapTo === "string" ? v.mapTo : "", typeof v.name === "string" ? v.name : ""], { color, factor, ...normal ? { normal } : {}, ...surface ? { surface } : {} });
221
+ }
222
+ }
223
+ function pbrOf(s) {
224
+ const map = (k) => typeof s[k] === "string" && s[k] && !/null\./i.test(s[k]) ? { map: s[k], channel: 0 } : void 0;
225
+ const num = (k) => typeof s[k] === "number" ? s[k] : void 0;
226
+ const out = { roughness: map("roughnessMap"), metallic: map("metallicMap"), ao: map("ambientOcclusionMap"), roughnessFactor: num("roughnessFactor"), metallicFactor: num("metallicFactor"), source: "BeamNG" };
227
+ return out.roughness || out.metallic || out.ao || out.roughnessFactor !== void 0 || out.metallicFactor !== void 0 ? out : void 0;
228
+ }
229
+ function readMaterialsCs(text, out) {
230
+ const re = /(?:singleton|new)\s+Material\s*\(\s*([\w.]+)\s*\)\s*\{([\s\S]*?)\n\s*\};?/g;
231
+ let m;
232
+ while (m = re.exec(text)) {
233
+ const body = m[2], mapTo = /mapTo\s*=\s*"([^"]*)"/.exec(body)?.[1];
234
+ const maps = [...body.matchAll(/(?:diffuseMap|colorMap|baseColorMap)\s*(?:\[\s*(\d+)\s*\])?\s*=\s*"([^"]*)"/g)].map((x) => ({ layer: +(x[1] ?? 0), path: x[2] })).filter((x) => x.path && !PLACEHOLDER.test(x.path)).sort((a, b) => a.layer - b.layer);
235
+ const f = /(?:baseColorFactor|diffuseColor)\s*(?:\[\s*0\s*\])?\s*=\s*"([^"]*)"/.exec(body)?.[1]?.trim().split(/\s+/).map(Number);
236
+ const factor = f && f.length >= 3 && f.every(Number.isFinite) ? f.slice(0, 3) : void 0;
237
+ if (!maps.length && !factor) continue;
238
+ const normal = [...body.matchAll(/normalMap\s*(?:\[\s*(\d+)\s*\])?\s*=\s*"([^"]*)"/g)].map((x) => x[2]).find((n) => n && !/generic_n|null\./i.test(n));
239
+ const csMap = (k) => {
240
+ const v = new RegExp(`${k}\\s*\\[\\s*0\\s*\\]\\s*=\\s*"([^"]*)"`).exec(body)?.[1];
241
+ return v && !/null\./i.test(v) ? { map: v, channel: 0 } : void 0;
242
+ };
243
+ const csNum = (k) => {
244
+ const v = new RegExp(`${k}\\s*\\[\\s*0\\s*\\]\\s*=\\s*"([\\d.]+)"`).exec(body)?.[1];
245
+ return v !== void 0 ? +v : void 0;
246
+ };
247
+ const sf = { roughness: csMap("roughnessMap"), metallic: csMap("metallicMap"), ao: csMap("ambientOcclusionMap"), roughnessFactor: csNum("roughnessFactor"), metallicFactor: csNum("metallicFactor"), source: "BeamNG" };
248
+ const surface = sf.roughness || sf.metallic || sf.ao || sf.roughnessFactor !== void 0 || sf.metallicFactor !== void 0 ? sf : void 0;
249
+ putMat(out, [m[1], mapTo], { color: maps[0]?.path ?? "", factor, ...normal ? { normal } : {}, ...surface ? { surface } : {} });
250
+ }
251
+ }
252
+ async function collectMaterials(archives) {
253
+ const out = /* @__PURE__ */ new Map();
254
+ for (const pass of ["json", "cs"])
255
+ for (const a of archives)
256
+ for (const e of a.entries) {
257
+ const ok = pass === "json" ? /materials\.json$/.test(e.path) : /(^|\/)materials\.cs$/.test(e.path);
258
+ if (!ok) continue;
259
+ const text = decodeText(await a.read(e));
260
+ if (pass === "json") readMaterialsJson(text, out);
261
+ else readMaterialsCs(text, out);
262
+ }
263
+ return out;
264
+ }
265
+ export {
266
+ assemble,
267
+ collectMaterials,
268
+ collectParts,
269
+ evalValue,
270
+ loadVehicle,
271
+ placeMesh,
272
+ readMaterialsCs,
273
+ readMaterialsJson,
274
+ slotsOf
275
+ };
276
+ //# sourceMappingURL=vehicle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vehicle.js","sources":["../src/vehicle.ts"],"sourcesContent":["// Assembling a BeamNG.drive vehicle from a .pc configuration: the jbeam slot tree -> chosen parts -> flexbody meshes from .dae.\n// Mesh placement: flexbody pos/rot/scale + the slot's nodeOffset (part node offset; X is mirrored by the mesh side).\nimport { decodeText } from '@gmvjs/core/binary';\nimport { boundsOf, sumTris, sumVerts } from '@gmvjs/core/geometry';\nimport type { LoadContext } from '@gmvjs/core/plugin';\nimport type { Archive, ArchiveEntry, Mesh, Model, SurfaceInfo, VFile } from '@gmvjs/core/types';\nimport { parseCollada, type ColladaMesh } from '@gmvjs/collada';\nimport { evalNumber, evalValue, parseJbeam, type JValue, type Vars } from './jbeam';\n\ntype Obj = { [k: string]: JValue };\nconst isObj = (v: JValue | undefined): v is Obj => !!v && typeof v === 'object' && !Array.isArray(v);\n\nexport interface JPart { name: string; slotType: string; data: Obj; file: string }\ninterface Slot { type: string; def: string; opts: Obj | null }\ninterface Chosen { part: JPart; offsets: Obj[] }\nexport interface Flexbody { mesh: string; part: string; opts: Obj | null; offsets: Obj[] }\n\n/** jbeam table rows: the first is the header, dictionaries between rows are modifiers (skipped). */\nfunction rows(v: JValue | undefined): JValue[][] {\n if (!Array.isArray(v)) return [];\n return v.slice(1).filter((r): r is JValue[] => Array.isArray(r));\n}\nconst lastObj = (r: JValue[]): Obj | null => { for (let k = r.length - 1; k >= 0; k--) if (isObj(r[k])) return r[k] as Obj; return null; };\n\nexport function slotsOf(part: JPart): Slot[] {\n const out: Slot[] = [];\n for (const r of rows(part.data.slots)) out.push({ type: String(r[0] ?? ''), def: String(r[1] ?? ''), opts: lastObj(r) });\n // slots2: [\"name\", \"allowTypes\", \"denyTypes\", \"default\", \"description\"]\n for (const r of rows(part.data.slots2)) {\n const allow = Array.isArray(r[1]) ? (r[1] as JValue[]).map(String) : [String(r[0] ?? '')];\n for (const t of allow.length ? allow : [String(r[0] ?? '')]) out.push({ type: String(r[0] ?? t), def: String(r[3] ?? ''), opts: lastObj(r) });\n }\n return out;\n}\n\n/** All parts from jbeam files in the archives (the mod's vehicles/ folder and, if open, the game's). */\nexport async function collectParts(archives: readonly Archive[]): Promise<Map<string, JPart>> {\n const parts = new Map<string, JPart>();\n for (const a of archives)\n for (const e of a.entries) {\n if (!e.path.endsWith('.jbeam')) continue;\n let data: JValue;\n try { data = parseJbeam(decodeText(await a.read(e))); } catch { continue; }\n if (!isObj(data)) continue;\n for (const [name, v] of Object.entries(data)) if (isObj(v) && !parts.has(name)) parts.set(name, { name, slotType: String(v.slotType ?? ''), data: v, file: e.path });\n }\n return parts;\n}\n\nexport interface Assembly { chosen: Chosen[]; flexbodies: Flexbody[]; missing: string[]; substituted: string[]; vars: Vars }\n\n/** Walk the slot tree from the main part. pcParts: slotType -> part name (\"\" = empty). */\nexport function assemble(parts: Map<string, JPart>, mainPart: string, pcParts: Record<string, string>, pcVars: Record<string, JValue> = {}): Assembly {\n const chosen: Chosen[] = [], missing: string[] = [], substituted: string[] = [], seen = new Set<string>();\n const bySlot = new Map<string, string[]>();\n for (const p of parts.values()) { if (!bySlot.has(p.slotType)) bySlot.set(p.slotType, []); bySlot.get(p.slotType)!.push(p.name); }\n const visit = (name: string, offsets: Obj[]) => {\n if (!name || seen.has(name)) return;\n const part = parts.get(name);\n if (!part) { missing.push(name); return; }\n seen.add(name);\n chosen.push({ part, offsets });\n for (const s of slotsOf(part)) {\n const pick = Object.prototype.hasOwnProperty.call(pcParts, s.type) ? pcParts[s.type]! : s.def;\n const off = s.opts && isObj(s.opts.nodeOffset) ? [...offsets, s.opts.nodeOffset as Obj] : offsets;\n // the part from .pc is not found (a shared game part): take the slot's default part from the mod\n // and if that is missing too, the mod's first part with the same slotType (wheels, tires from shared game files)\n if (pick && !parts.has(pick)) {\n const alt = parts.has(s.def) ? s.def : bySlot.get(s.type)?.[0];\n if (alt) { substituted.push(`${pick} → ${alt}`); visit(alt, off); continue; }\n }\n visit(pick, off);\n }\n };\n visit(mainPart, []);\n // variables: defaults from the chosen parts, then from .pc\n const vars: Vars = new Map();\n for (const c of chosen) {\n const tbl = c.part.data.variables;\n if (!Array.isArray(tbl) || !Array.isArray(tbl[0])) continue;\n const head = (tbl[0] as JValue[]).map(String), iName = head.indexOf('name'), iDef = head.indexOf('default');\n for (const r of rows(tbl)) if (iName >= 0 && iDef >= 0) vars.set(String(r[iName]), (r[iDef] ?? null) as never);\n }\n for (const [k, v] of Object.entries(pcVars)) vars.set(k, v as never);\n const flexbodies: Flexbody[] = [];\n for (const c of chosen)\n for (const r of rows(c.part.data.flexbodies)) {\n if (typeof r[0] !== 'string' || !r[0]) continue;\n flexbodies.push({ mesh: r[0], part: c.part.name, opts: lastObj(r), offsets: c.offsets });\n }\n return { chosen, flexbodies, missing, substituted, vars };\n}\n\nconst vec = (o: JValue | undefined, vars: Vars, def: number): [number, number, number] =>\n isObj(o) ? [evalNumber(o.x, vars, def), evalNumber(o.y, vars, def), evalNumber(o.z, vars, def)] : [def, def, def];\n\n/** Rotation in degrees: X first, then Y, then Z. */\nfunction rotMat([rx, ry, rz]: number[]): number[] {\n const [a, b, c] = [rx!, ry!, rz!].map((d) => (d * Math.PI) / 180) as [number, number, number];\n const ca = Math.cos(a), sa = Math.sin(a), cb = Math.cos(b), sb = Math.sin(b), cc = Math.cos(c), sc = Math.sin(c);\n // Rz·Ry·Rx, rows\n return [cc * cb, cc * sb * sa - sc * ca, cc * sb * ca + sc * sa, sc * cb, sc * sb * sa + cc * ca, sc * sb * ca - cc * sa, -sb, cb * sa, cb * ca];\n}\n\n/** Copy of a mesh with the flexbody transform and node offset applied. */\nexport function placeMesh(src: ColladaMesh, fb: Flexbody, vars: Vars): Mesh {\n const o = fb.opts, pos = vec(o?.pos, vars, 0), rot = vec(o?.rot, vars, 0), scl = vec(o?.scale, vars, 1), R = rotMat(rot);\n const n = src.vertexCount, P = new Float32Array(n * 3), N = new Float32Array(n * 3);\n for (let v = 0; v < n; v++) {\n const x = src.pos[v * 3]! * scl[0], y = src.pos[v * 3 + 1]! * scl[1], z = src.pos[v * 3 + 2]! * scl[2];\n P[v * 3] = R[0]! * x + R[1]! * y + R[2]! * z + pos[0];\n P[v * 3 + 1] = R[3]! * x + R[4]! * y + R[5]! * z + pos[1];\n P[v * 3 + 2] = R[6]! * x + R[7]! * y + R[8]! * z + pos[2];\n const a = src.nor[v * 3]!, b = src.nor[v * 3 + 1]!, c = src.nor[v * 3 + 2]!;\n N[v * 3] = R[0]! * a + R[1]! * b + R[2]! * c; N[v * 3 + 1] = R[3]! * a + R[4]! * b + R[5]! * c; N[v * 3 + 2] = R[6]! * a + R[7]! * b + R[8]! * c;\n }\n // slot nodeOffset: X points outward by the vertex side (nodes move by x·sign(node x)), Y/Z as is.\n // A hub mesh often serves both sides, so the sign is taken per vertex.\n let ox = 0, oy = 0, oz = 0;\n for (const off of fb.offsets) { const [x, y, z] = vec(off, vars, 0); ox += x; oy += y; oz += z; }\n if (ox || oy || oz) for (let v = 0; v < n; v++) { const x = P[v * 3]!; P[v * 3] = x + Math.sign(x) * ox; P[v * 3 + 1] += oy; P[v * 3 + 2] += oz; }\n return { ...src, name: `${fb.mesh} · ${fb.part}`, pos: P, nor: N };\n}\n\nexport interface PcFile { parts: Record<string, string>; vars: Record<string, JValue>; mainPartName?: string; model?: string; paints?: Array<{ baseColor?: number[] }> }\n\nexport async function loadVehicle(main: VFile, ctx: LoadContext): Promise<Model> {\n const pc = parseJbeam(decodeText(await main.arrayBuffer())) as unknown as PcFile;\n if (!pc || typeof pc !== 'object' || !pc.parts) throw new Error('Не конфигурация BeamNG (.pc): нет \"parts\"');\n const archives = [...ctx.archives];\n if (main.archive && !archives.includes(main.archive)) archives.push(main.archive);\n const parts = await collectParts(archives);\n if (!parts.size) throw new Error('Не найдено ни одного .jbeam — откройте мод целиком («Папка игры…» или zip)');\n const model = pc.model ?? (main.path ? /vehicles\\/([^/]+)\\//.exec(main.path)?.[1] : undefined) ?? '';\n let mainPart = pc.mainPartName ?? '';\n if (!parts.has(mainPart)) mainPart = [...parts.values()].find((p) => p.slotType === 'main' && p.file.includes(`/vehicles/${model.toLowerCase()}/`))?.name ?? [...parts.values()].find((p) => p.slotType === 'main')?.name ?? '';\n if (!mainPart) throw new Error('Не найдена главная деталь (slotType \"main\")');\n const asm = assemble(parts, mainPart, pc.parts, pc.vars ?? {});\n const wanted = new Set(asm.flexbodies.map((f) => f.mesh.toLowerCase()));\n\n // meshes: .dae files from the same archives where the needed node names occur\n const byName = new Map<string, ColladaMesh[]>();\n const daes: Array<[Archive, ArchiveEntry]> = [];\n for (const a of archives) for (const e of a.entries) if (e.path.endsWith('.dae')) daes.push([a, e]);\n const vehDir = `/vehicles/${model.toLowerCase()}/`;\n daes.sort((x, y) => +y[1].path.includes(vehDir) - +x[1].path.includes(vehDir)); // the vehicle folder first\n for (const [a, e] of daes) {\n if ([...wanted].every((w) => byName.has(w))) break;\n const xml = decodeText(await a.read(e)), low = xml.toLowerCase();\n const need = new Set([...wanted].filter((w) => !byName.has(w) && (low.includes(`name=\"${w}\"`) || low.includes(`id=\"${w}\"`))));\n if (!need.size) continue;\n for (const m of parseCollada(xml, { nodes: need }).meshes) {\n const k = m.nodeName.toLowerCase();\n if (!byName.has(k)) byName.set(k, []);\n byName.get(k)!.push(m);\n }\n }\n const paint = pc.paints?.[0]?.baseColor;\n const mats = await collectMaterials(archives);\n const meshes: Mesh[] = [], noMesh = new Set<string>();\n for (const fb of asm.flexbodies) {\n const src = byName.get(fb.mesh.toLowerCase());\n if (!src) { noMesh.add(fb.mesh); continue; }\n for (const m of src) {\n const pm = placeMesh(m, fb, asm.vars), mt = mats.get((m.material ?? '').toLowerCase());\n // without a color map: the material multiplier, otherwise the paint color from .pc\n if (mt?.normal) pm.normalMap = mt.normal;\n if (mt?.surface) pm.surface = mt.surface;\n if (mt?.color) pm.texture = mt.color;\n else pm.color = (mt?.factor ?? paint ?? [0.7, 0.7, 0.7]).slice(0, 3).map((x) => Math.min(1, x));\n meshes.push(pm);\n }\n }\n if (!meshes.length) throw new Error(`Ни один меш не найден в .dae (нужно ${wanted.size}: ${[...wanted].slice(0, 5).join(', ')}…)`);\n const info = [\n `BeamNG: ${model} (${main.name}), деталей ${asm.chosen.length}, мешей ${meshes.length}, вершин ${sumVerts(meshes)}, треугольников ${sumTris(meshes)}`,\n 'Пропсы (руль, приборы, фары-источники) и деформация пока не показываются.',\n ];\n if (asm.missing.length) info.push(`Нет деталей (в моде не хватает файлов игры?): ${asm.missing.slice(0, 8).join(', ')}${asm.missing.length > 8 ? '…' : ''}`);\n if (asm.substituted.length) info.push(`Заменены деталями слота по умолчанию: ${asm.substituted.slice(0, 6).join(', ')}${asm.substituted.length > 6 ? '…' : ''}`);\n if (noMesh.size) info.push(`Нет мешей в .dae: ${[...noMesh].slice(0, 8).join(', ')}${noMesh.size > 8 ? '…' : ''}`);\n return { meshes, skeleton: null, motions: [], bounds: boundsOf(meshes), flipZ: false, defaultZUp: true, cameraYaw: 0, info,\n meta: { beamng: { model, parts: asm.chosen.map((c) => c.part.name) } } };\n}\n\n// ---------- materials (*.materials.json, materials.cs) ----------\n/** color = the color map (empty if only a color multiplier or a generic_* placeholder); factor = baseColorFactor of layer 0. */\nexport interface MaterialTex { color: string; factor?: number[]; normal?: string; surface?: SurfaceInfo }\nconst PLACEHOLDER = /(^|\\/)(null|generic_[a-z]*|white)(\\.[a-z]+)*\\.(dds|png|jpe?g|tga)$/i;\nconst putMat = (out: Map<string, MaterialTex>, names: Array<string | undefined>, t: MaterialTex) => {\n for (const name of names) if (name && !out.has(name.toLowerCase())) out.set(name.toLowerCase(), t);\n};\nexport function readMaterialsJson(text: string, out: Map<string, MaterialTex>): void {\n let j: JValue;\n try { j = parseJbeam(text); } catch { return; }\n if (!isObj(j)) return;\n for (const [key, v] of Object.entries(j)) {\n if (!isObj(v)) continue;\n let color = '', factor: number[] | undefined, normal = '', surface: SurfaceInfo | undefined;\n const stages = Array.isArray(v.Stages) ? (v.Stages as JValue[]) : [];\n for (const s of stages) {\n if (!isObj(s)) continue;\n if (!surface) surface = pbrOf(s);\n if (!normal && typeof s.normalMap === 'string' && s.normalMap && !/generic_n|null\\./i.test(s.normalMap)) normal = s.normalMap;\n if (!factor && Array.isArray(s.baseColorFactor)) factor = (s.baseColorFactor as JValue[]).slice(0, 3).map(Number);\n for (const k of ['baseColorMap', 'colorMap', 'diffuseMap']) {\n const p = s[k];\n if (typeof p === 'string' && p && !PLACEHOLDER.test(p)) { color = p; break; }\n }\n if (color) break;\n }\n if (!color && typeof v.diffuseMap === 'string' && !PLACEHOLDER.test(v.diffuseMap)) color = v.diffuseMap;\n if (!color && !factor) continue;\n putMat(out, [key, typeof v.mapTo === 'string' ? v.mapTo : '', typeof v.name === 'string' ? v.name : ''], { color, factor, ...(normal ? { normal } : {}), ...(surface ? { surface } : {}) });\n }\n}\n/** PBR of a BeamNG material stage (v1.5): separate single-channel maps and multipliers. */\nfunction pbrOf(s: Obj): SurfaceInfo | undefined {\n const map = (k: string) => (typeof s[k] === 'string' && s[k] && !/null\\./i.test(s[k] as string) ? { map: s[k] as string, channel: 0 as const } : undefined);\n const num = (k: string) => (typeof s[k] === 'number' ? (s[k] as number) : undefined);\n const out: SurfaceInfo = { roughness: map('roughnessMap'), metallic: map('metallicMap'), ao: map('ambientOcclusionMap'), roughnessFactor: num('roughnessFactor'), metallicFactor: num('metallicFactor'), source: 'BeamNG' };\n return out.roughness || out.metallic || out.ao || out.roughnessFactor !== undefined || out.metallicFactor !== undefined ? out : undefined;\n}\n/** The old TorqueScript format: singleton Material(name) { mapTo = \"x\"; diffuseMap[0] = \"…\"; } */\nexport function readMaterialsCs(text: string, out: Map<string, MaterialTex>): void {\n const re = /(?:singleton|new)\\s+Material\\s*\\(\\s*([\\w.]+)\\s*\\)\\s*\\{([\\s\\S]*?)\\n\\s*\\};?/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text))) {\n const body = m[2]!, mapTo = /mapTo\\s*=\\s*\"([^\"]*)\"/.exec(body)?.[1];\n const maps = [...body.matchAll(/(?:diffuseMap|colorMap|baseColorMap)\\s*(?:\\[\\s*(\\d+)\\s*\\])?\\s*=\\s*\"([^\"]*)\"/g)]\n .map((x) => ({ layer: +(x[1] ?? 0), path: x[2]! })).filter((x) => x.path && !PLACEHOLDER.test(x.path)).sort((a, b) => a.layer - b.layer);\n const f = /(?:baseColorFactor|diffuseColor)\\s*(?:\\[\\s*0\\s*\\])?\\s*=\\s*\"([^\"]*)\"/.exec(body)?.[1]?.trim().split(/\\s+/).map(Number);\n const factor = f && f.length >= 3 && f.every(Number.isFinite) ? f.slice(0, 3) : undefined;\n if (!maps.length && !factor) continue;\n const normal = [...body.matchAll(/normalMap\\s*(?:\\[\\s*(\\d+)\\s*\\])?\\s*=\\s*\"([^\"]*)\"/g)].map((x) => x[2]!).find((n) => n && !/generic_n|null\\./i.test(n));\n const csMap = (k: string) => { const v = new RegExp(`${k}\\\\s*\\\\[\\\\s*0\\\\s*\\\\]\\\\s*=\\\\s*\"([^\"]*)\"`).exec(body)?.[1]; return v && !/null\\./i.test(v) ? { map: v, channel: 0 as const } : undefined; };\n const csNum = (k: string) => { const v = new RegExp(`${k}\\\\s*\\\\[\\\\s*0\\\\s*\\\\]\\\\s*=\\\\s*\"([\\\\d.]+)\"`).exec(body)?.[1]; return v !== undefined ? +v : undefined; };\n const sf: SurfaceInfo = { roughness: csMap('roughnessMap'), metallic: csMap('metallicMap'), ao: csMap('ambientOcclusionMap'), roughnessFactor: csNum('roughnessFactor'), metallicFactor: csNum('metallicFactor'), source: 'BeamNG' };\n const surface = sf.roughness || sf.metallic || sf.ao || sf.roughnessFactor !== undefined || sf.metallicFactor !== undefined ? sf : undefined;\n putMat(out, [m[1]!, mapTo], { color: maps[0]?.path ?? '', factor, ...(normal ? { normal } : {}), ...(surface ? { surface } : {}) });\n }\n}\n\n/** All materials from the open archives (json takes precedence over cs: json is read first). */\nexport async function collectMaterials(archives: readonly Archive[]): Promise<Map<string, MaterialTex>> {\n const out = new Map<string, MaterialTex>();\n for (const pass of ['json', 'cs'] as const)\n for (const a of archives)\n for (const e of a.entries) {\n const ok = pass === 'json' ? /materials\\.json$/.test(e.path) : /(^|\\/)materials\\.cs$/.test(e.path);\n if (!ok) continue;\n const text = decodeText(await a.read(e));\n if (pass === 'json') readMaterialsJson(text, out); else readMaterialsCs(text, out);\n }\n return out;\n}\nexport { evalValue };\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "@gmvjs/torque3d",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "gmv: Torque3D (BeamNG)",
5
+ "keywords": [
6
+ "gmv",
7
+ "torque3d",
8
+ "beamng",
9
+ "jbeam"
10
+ ],
11
+ "license": "MIT",
12
+ "author": "Alexander Shibisty",
13
+ "homepage": "https://github.com/shibisty/gmv/tree/main/packages/torque3d#readme",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/shibisty/gmv.git",
17
+ "directory": "packages/torque3d"
18
+ },
19
+ "bugs": {
20
+ "url": "https://github.com/shibisty/gmv/issues"
21
+ },
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "exports": {
25
+ ".": {
26
+ "gmv-source": "./src/index.ts",
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ },
30
+ "./beamng": {
31
+ "gmv-source": "./src/games/beamng.ts",
32
+ "types": "./dist/games/beamng.d.ts",
33
+ "import": "./dist/games/beamng.js"
34
+ }
35
+ },
36
+ "main": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "files": [
39
+ "dist",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "scripts": {
44
+ "build": "node ../../scripts/build-package.mjs"
45
+ },
46
+ "dependencies": {
47
+ "@gmvjs/collada": "^0.1.1",
48
+ "@gmvjs/core": "^0.1.1"
49
+ },
50
+ "devDependencies": {
51
+ "@gmvjs/test-utils": "^0.1.0"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ }
56
+ }