@gmvjs/torque3d 0.0.0-stage → 0.1.0
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 +21 -0
- package/README.md +55 -2
- package/dist/games/beamng.d.ts +7 -0
- package/dist/games/beamng.js +11 -0
- package/dist/games/beamng.js.map +1 -0
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.js +12 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/jbeam.d.ts +12 -0
- package/dist/jbeam.js +221 -0
- package/dist/jbeam.js.map +1 -0
- package/dist/vehicle.d.ts +65 -0
- package/dist/vehicle.js +276 -0
- package/dist/vehicle.js.map +1 -0
- package/package.json +54 -4
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,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @gmvjs/torque3d — Torque3D (BeamNG)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Torque3D — движок BeamNG.drive: конфигурации `.pc` → слоты jbeam → flexbody из `.dae` с материалами и покраской.
|
|
4
|
+
|
|
5
|
+
Плагин движка для [`@gmvjs/core`](../core/README.md): форматы, архивы и пресеты игр. Тяжёлые разборщики загружаются по требованию (`import()` внутри `load`), так что подключение пакета почти ничего не стоит, пока файл этого движка не открыт.
|
|
6
|
+
|
|
7
|
+
## Установка
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm i @gmvjs/core @gmvjs/torque3d @gmvjs/collada
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Подключение
|
|
14
|
+
|
|
15
|
+
Весь движок (все игры):
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { createRegistry, ViewerSession } from '@gmvjs/core';
|
|
19
|
+
import { torque3dEngine } from '@gmvjs/torque3d';
|
|
20
|
+
|
|
21
|
+
const session = new ViewerSession(createRegistry([torque3dEngine]));
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Одна игра — подпуть пакета (пресет игры и движок, ограниченный этой игрой):
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { createRegistry, ViewerSession } from '@gmvjs/core';
|
|
28
|
+
import beamng from '@gmvjs/torque3d/beamng';
|
|
29
|
+
|
|
30
|
+
const session = new ViewerSession(createRegistry([beamng.engine]), beamng.preset.id);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Игры (подпути)
|
|
34
|
+
|
|
35
|
+
| Импорт | Пресет | Игра | Статус | Особенности |
|
|
36
|
+
|---|---|---|---|---|
|
|
37
|
+
| `@gmvjs/torque3d/beamng` | `beamng` | BeamNG.drive | 🟡 бета | — |
|
|
38
|
+
|
|
39
|
+
## Форматы
|
|
40
|
+
|
|
41
|
+
| id | Что | Файлы | Статус |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| `torque3d-pc` | BeamNG: конфигурация машины (.pc) | `.pc` | 🟡 бета |
|
|
44
|
+
|
|
45
|
+
## Зависимости
|
|
46
|
+
|
|
47
|
+
- `@gmvjs/core` — API плагинов, типы, архивы ZIP/папка, zlib, DDS/BC, геометрия
|
|
48
|
+
- `@gmvjs/collada` — разбор `.dae` (flexbody)
|
|
49
|
+
|
|
50
|
+
## Не сделано
|
|
51
|
+
|
|
52
|
+
- Другие игры на Torque3D (DTS/DIF)
|
|
53
|
+
|
|
54
|
+
## Лицензия
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -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 — подпуть `@gmvjs/torque3d/beamng`: пресет игры и движок только с ней.\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
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
|
package/dist/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","sources":["../src/i18n.ts"],"sourcesContent":["// Английские тексты движка torque3d (русские — в самих плагинах).\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;"}
|
package/dist/index.d.ts
ADDED
|
@@ -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: машина собирается из .pc (конфигурация) + .jbeam (детали и слоты) + .dae (меши flexbody) + *.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;"}
|
package/dist/jbeam.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type JValue = string | number | boolean | null | JValue[] | {
|
|
2
|
+
[k: string]: JValue;
|
|
3
|
+
};
|
|
4
|
+
/** Разбор jbeam/pc/materials.json: комментарии // и /* *\/, запятые необязательны. */
|
|
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
|
+
/** Значение поля jbeam: число, "$var" или "$=выражение". */
|
|
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 с вольностями» — комментарии, висячие и пропущенные запятые. Плюс вычисление выражений \"$=…\".\n\nexport type JValue = string | number | boolean | null | JValue[] | { [k: string]: JValue };\n\n/** Разбор jbeam/pc/materials.json: комментарии // и /* *\\/, запятые необязательны. */\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// ---------- выражения \"$=…\" (подмножество Lua, как в BeamNG) ----------\nexport type Vars = Map<string, number | string | boolean | null>;\ntype V = number | string | boolean | null;\n\n/** Значение поля jbeam: число, \"$var\" или \"$=выражение\". */\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
|
+
/** Все детали из jbeam-файлов архивов (папка vehicles/ мода и, если открыта, игры). */
|
|
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
|
+
/** Обход дерева слотов от главной детали. pcParts: slotType -> имя детали ("" — пусто). */
|
|
40
|
+
export declare function assemble(parts: Map<string, JPart>, mainPart: string, pcParts: Record<string, string>, pcVars?: Record<string, JValue>): Assembly;
|
|
41
|
+
/** Копия меша с преобразованием flexbody и смещением узлов. */
|
|
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 — карта цвета (пусто, если только цвет-множитель или заглушка generic_*); factor — baseColorFactor слоя 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
|
+
/** Старый формат TorqueScript: singleton Material(name) { mapTo = "x"; diffuseMap[0] = "…"; } */
|
|
62
|
+
export declare function readMaterialsCs(text: string, out: Map<string, MaterialTex>): void;
|
|
63
|
+
/** Все материалы из открытых архивов (json важнее cs: читаем json первым). */
|
|
64
|
+
export declare function collectMaterials(archives: readonly Archive[]): Promise<Map<string, MaterialTex>>;
|
|
65
|
+
export { evalValue };
|
package/dist/vehicle.js
ADDED
|
@@ -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":["// Сборка машины BeamNG.drive по конфигурации .pc: дерево слотов jbeam -> выбранные детали -> flexbody-меши из .dae.\n// Положение мешей: pos/rot/scale flexbody + nodeOffset слота (смещение узлов детали; X зеркалится по стороне меша).\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: первая — заголовок, словари между строками — модификаторы (пропускаем). */\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/** Все детали из jbeam-файлов архивов (папка vehicles/ мода и, если открыта, игры). */\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/** Обход дерева слотов от главной детали. pcParts: slotType -> имя детали (\"\" — пусто). */\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 // деталь из .pc не найдена (общая деталь игры) — берём деталь слота по умолчанию из мода\n // а если и её нет — первую деталь мода с тем же slotType (колёса, шины из общих файлов игры)\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 // переменные: значения по умолчанию из выбранных деталей, затем из .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/** Поворот в градусах: сначала X, затем Y, затем 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, строки\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/** Копия меша с преобразованием flexbody и смещением узлов. */\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 // nodeOffset слотов: X — наружу по стороне вершины (узлы смещаются на x·sign(x узла)), Y/Z — как есть.\n // Меш ступиц часто один на обе стороны, поэтому знак берётся по каждой вершине.\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 // меши: .dae из тех же архивов, где встречаются нужные имена узлов\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)); // сначала папка машины\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 // без карты цвета: множитель материала, иначе цвет покраски из .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.json, materials.cs) ----------\n/** color — карта цвета (пусто, если только цвет-множитель или заглушка generic_*); factor — baseColorFactor слоя 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 стадии материала BeamNG (v1.5): отдельные одноканальные карты и множители. */\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/** Старый формат TorqueScript: 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/** Все материалы из открытых архивов (json важнее cs: читаем json первым). */\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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
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.0",
|
|
48
|
+
"@gmvjs/core": "^0.1.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@gmvjs/test-utils": "^0.1.0"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
}
|
|
56
|
+
}
|