quake2ts 0.0.572 → 0.0.574

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.
Files changed (38) hide show
  1. package/package.json +1 -1
  2. package/packages/client/dist/browser/index.global.js.map +1 -1
  3. package/packages/client/dist/cjs/index.cjs +2 -2
  4. package/packages/client/dist/cjs/index.cjs.map +1 -1
  5. package/packages/client/dist/esm/index.js +2 -2
  6. package/packages/client/dist/esm/index.js.map +1 -1
  7. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  8. package/packages/engine/dist/browser/index.global.js +16 -16
  9. package/packages/engine/dist/browser/index.global.js.map +1 -1
  10. package/packages/engine/dist/cjs/index.cjs +123 -14
  11. package/packages/engine/dist/cjs/index.cjs.map +1 -1
  12. package/packages/engine/dist/esm/index.js +122 -14
  13. package/packages/engine/dist/esm/index.js.map +1 -1
  14. package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
  15. package/packages/engine/dist/types/index.d.ts +1 -0
  16. package/packages/engine/dist/types/index.d.ts.map +1 -1
  17. package/packages/engine/dist/types/render/instancing.d.ts +12 -0
  18. package/packages/engine/dist/types/render/instancing.d.ts.map +1 -0
  19. package/packages/engine/dist/types/render/renderer.d.ts +4 -0
  20. package/packages/engine/dist/types/render/renderer.d.ts.map +1 -1
  21. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
  22. package/packages/test-utils/dist/index.cjs +133 -0
  23. package/packages/test-utils/dist/index.cjs.map +1 -1
  24. package/packages/test-utils/dist/index.d.cts +84 -2
  25. package/packages/test-utils/dist/index.d.ts +84 -2
  26. package/packages/test-utils/dist/index.js +126 -0
  27. package/packages/test-utils/dist/index.js.map +1 -1
  28. package/packages/tools/dist/browser/index.global.js +2 -1
  29. package/packages/tools/dist/browser/index.global.js.map +1 -1
  30. package/packages/tools/dist/cjs/index.cjs +175 -2
  31. package/packages/tools/dist/cjs/index.cjs.map +1 -1
  32. package/packages/tools/dist/esm/index.js +170 -1
  33. package/packages/tools/dist/esm/index.js.map +1 -1
  34. package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
  35. package/packages/tools/dist/types/index.d.ts +1 -0
  36. package/packages/tools/dist/types/index.d.ts.map +1 -1
  37. package/packages/tools/dist/types/modelExport.d.ts +19 -0
  38. package/packages/tools/dist/types/modelExport.d.ts.map +1 -0
@@ -1,2 +1,3 @@
1
- "use strict";var Quake2Tools=(()=>{var n=Object.defineProperty;var i=Object.getOwnPropertyDescriptor;var o=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var c=(r,e)=>{for(var s in e)n(r,s,{get:e[s],enumerable:!0})},u=(r,e,s,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of o(e))!m.call(r,t)&&t!==s&&n(r,t,{get:()=>e[t],enumerable:!(a=i(e,t))||a.enumerable});return r};var y=r=>u(n({},"__esModule",{value:!0}),r);var p={};c(p,{describeAsset:()=>d});function d(r,e){return{name:r,origin:e}}return y(p);})();
1
+ "use strict";var Quake2Tools=(()=>{var y=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var U=(n,e)=>{for(var o in e)y(n,o,{get:e[o],enumerable:!0})},z=(n,e,o,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let f of C(e))!k.call(n,f)&&f!==o&&y(n,f,{get:()=>e[f],enumerable:!(r=O(e,f))||r.enumerable});return n};var E=n=>z(y({},"__esModule",{value:!0}),n);var T={};U(T,{describeAsset:()=>L,exportMd2ToObj:()=>v,exportMd3ToGltf:()=>$});function v(n,e){if(e<0||e>=n.frames.length)throw new Error(`Frame index ${e} out of bounds (0-${n.frames.length-1})`);let o=n.frames[e],r=[];r.push("# Quake 2 MD2 to OBJ Export"),r.push(`# Model: ${n.header.skinWidth}x${n.header.skinHeight}`),r.push(`# Frame: ${e} (${o.name})`),r.push(`o ${o.name}`);for(let t of o.vertices)r.push(`v ${t.position.x.toFixed(6)} ${t.position.y.toFixed(6)} ${t.position.z.toFixed(6)}`);let f=n.header.skinWidth,p=n.header.skinHeight;for(let t of n.texCoords){let i=t.s/f,h=1-t.t/p;r.push(`vt ${i.toFixed(6)} ${h.toFixed(6)}`)}for(let t of o.vertices)r.push(`vn ${t.normal.x.toFixed(6)} ${t.normal.y.toFixed(6)} ${t.normal.z.toFixed(6)}`);r.push("s off");for(let t of n.triangles){let i=t.vertexIndices[0]+1,h=t.vertexIndices[1]+1,d=t.vertexIndices[2]+1,g=t.texCoordIndices[0]+1,a=t.texCoordIndices[1]+1,c=t.texCoordIndices[2]+1,l=i,m=h,x=d;r.push(`f ${i}/${g}/${l} ${h}/${a}/${m} ${d}/${c}/${x}`)}return r.join(`
2
+ `)}function $(n){let e={asset:{version:"2.0",generator:"quake2ts-tools"},scenes:[{nodes:[0]}],nodes:[{name:n.header.name,mesh:0}],meshes:[{name:n.header.name,primitives:[]}],buffers:[{byteLength:0}],bufferViews:[],accessors:[]},o=[],r=(t,i)=>{let h=o.length;for(;o.length%4!==0;)o.push(0);let d=o.length;for(let c=0;c<t.length;c++)o.push(t[c]);let g=t.length,a=e.bufferViews.length;return e.bufferViews.push({buffer:0,byteOffset:d,byteLength:g,target:i}),a},f=(t,i,h,d,g,a)=>{let c=e.accessors.length,l={bufferView:t,componentType:i,count:h,type:d};return g&&(l.min=g),a&&(l.max=a),e.accessors.push(l),c},p=0;for(let t of n.surfaces){let i=t.vertices[p],h=new Float32Array(i.length*3),d=new Float32Array(i.length*3),g=new Float32Array(i.length*2),a=[1/0,1/0,1/0],c=[-1/0,-1/0,-1/0];for(let s=0;s<i.length;s++){let u=i[s];h[s*3]=u.position.x,h[s*3+1]=u.position.y,h[s*3+2]=u.position.z,a[0]=Math.min(a[0],u.position.x),a[1]=Math.min(a[1],u.position.y),a[2]=Math.min(a[2],u.position.z),c[0]=Math.max(c[0],u.position.x),c[1]=Math.max(c[1],u.position.y),c[2]=Math.max(c[2],u.position.z),d[s*3]=u.normal.x,d[s*3+1]=u.normal.y,d[s*3+2]=u.normal.z;let b=t.texCoords[s];g[s*2]=b.s,g[s*2+1]=1-b.t}let l=new Uint16Array(t.triangles.length*3);for(let s=0;s<t.triangles.length;s++)l[s*3]=t.triangles[s].indices[0],l[s*3+1]=t.triangles[s].indices[1],l[s*3+2]=t.triangles[s].indices[2];let m=r(new Uint8Array(h.buffer),34962),x=r(new Uint8Array(d.buffer),34962),w=r(new Uint8Array(g.buffer),34962),A=r(new Uint8Array(l.buffer),34963),M=f(m,5126,i.length,"VEC3",a,c),V=f(x,5126,i.length,"VEC3"),F=f(w,5126,i.length,"VEC2"),I=f(A,5123,l.length,"SCALAR");e.meshes[0].primitives.push({attributes:{POSITION:M,NORMAL:V,TEXCOORD_0:F},indices:I,material:void 0})}for(;o.length%4!==0;)o.push(0);return e.buffers[0].byteLength=o.length,{json:JSON.stringify(e,null,2),buffer:new Uint8Array(o).buffer}}function L(n,e){return{name:n,origin:e}}return E(T);})();
2
3
  //# sourceMappingURL=index.global.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts"],"sourcesContent":["import type { Vec3 } from '@quake2ts/shared';\n\nexport interface AssetSummary {\n readonly name: string;\n readonly origin?: Vec3;\n}\n\nexport function describeAsset(name: string, origin?: Vec3): AssetSummary {\n return { name, origin };\n}\n"],"mappings":"+bAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,mBAAAE,IAOO,SAASA,EAAcC,EAAcC,EAA6B,CACvE,MAAO,CAAE,KAAAD,EAAM,OAAAC,CAAO,CACxB","names":["src_exports","__export","describeAsset","name","origin"]}
1
+ {"version":3,"sources":["../../src/index.ts","../../src/modelExport.ts"],"sourcesContent":["import type { Vec3 } from '@quake2ts/shared';\n\nexport interface AssetSummary {\n readonly name: string;\n readonly origin?: Vec3;\n}\n\nexport function describeAsset(name: string, origin?: Vec3): AssetSummary {\n return { name, origin };\n}\n\nexport { exportMd2ToObj, exportMd3ToGltf } from './modelExport.js';\n","import { Md2Model, Md3Model } from '@quake2ts/engine';\n\n/**\n * Export MD2 model frame to OBJ format\n * @param model Parsed MD2 model\n * @param frameIndex Frame index to export\n * @returns OBJ file contents as string\n */\nexport function exportMd2ToObj(model: Md2Model, frameIndex: number): string {\n if (frameIndex < 0 || frameIndex >= model.frames.length) {\n throw new Error(`Frame index ${frameIndex} out of bounds (0-${model.frames.length - 1})`);\n }\n\n const frame = model.frames[frameIndex];\n const lines: string[] = [];\n\n lines.push(`# Quake 2 MD2 to OBJ Export`);\n lines.push(`# Model: ${model.header.skinWidth}x${model.header.skinHeight}`);\n lines.push(`# Frame: ${frameIndex} (${frame.name})`);\n lines.push(`o ${frame.name}`);\n\n // Write vertices\n // OBJ vertices are \"v x y z\"\n // Quake uses Z-up, Y-forward? No, Quake is Z-up. OBJ is typically Y-up?\n // Actually, standard OBJ is just points. Tools usually expect Y-up.\n // Quake coords: X=Forward, Y=Left, Z=Up.\n // Blender/Standard: X=Right, Y=Up, Z=Back.\n // We usually export as-is and let the user handle rotation, or swap Y/Z.\n // The request doesn't specify coordinate conversion. I will export as-is (Quake coordinates).\n // Note: MD2 vertices are in local model space.\n for (const v of frame.vertices) {\n lines.push(`v ${v.position.x.toFixed(6)} ${v.position.y.toFixed(6)} ${v.position.z.toFixed(6)}`);\n }\n\n // Write texture coordinates\n // OBJ UVs are \"vt u v\"\n // MD2 tex coords are integers, need to normalize by skin size.\n // Also MD2 (0,0) is top-left, OBJ (0,0) is usually bottom-left.\n // So v = 1 - (t / height).\n const width = model.header.skinWidth;\n const height = model.header.skinHeight;\n for (const tc of model.texCoords) {\n const u = tc.s / width;\n const v = 1.0 - (tc.t / height);\n lines.push(`vt ${u.toFixed(6)} ${v.toFixed(6)}`);\n }\n\n // Write normals\n // MD2 stores normals in frame.vertices[i].normal\n for (const v of frame.vertices) {\n lines.push(`vn ${v.normal.x.toFixed(6)} ${v.normal.y.toFixed(6)} ${v.normal.z.toFixed(6)}`);\n }\n\n // Write faces\n // f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3\n // Indices are 1-based in OBJ.\n lines.push(`s off`); // Smoothing groups off\n for (const tri of model.triangles) {\n const v1 = tri.vertexIndices[0] + 1;\n const v2 = tri.vertexIndices[1] + 1;\n const v3 = tri.vertexIndices[2] + 1;\n\n const vt1 = tri.texCoordIndices[0] + 1;\n const vt2 = tri.texCoordIndices[1] + 1;\n const vt3 = tri.texCoordIndices[2] + 1;\n\n // Normal indices match vertex indices in MD2 (per-vertex normals)\n const vn1 = v1;\n const vn2 = v2;\n const vn3 = v3;\n\n // Reverse winding? Quake is clockwise? OpenGL is CCW.\n // MD2 is usually Clockwise winding for front face?\n // Let's stick to the order in the file.\n lines.push(`f ${v1}/${vt1}/${vn1} ${v2}/${vt2}/${vn2} ${v3}/${vt3}/${vn3}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Export MD3 model to glTF 2.0 format\n * Currently exports the first frame as a static mesh.\n * @param model Parsed MD3 model\n * @returns glTF JSON and binary buffer\n */\nexport function exportMd3ToGltf(model: Md3Model): {\n json: string\n buffer: ArrayBuffer\n} {\n // Structure for GLTF\n const gltf: any = {\n asset: {\n version: \"2.0\",\n generator: \"quake2ts-tools\"\n },\n scenes: [\n {\n nodes: [0]\n }\n ],\n nodes: [\n {\n name: model.header.name,\n mesh: 0\n }\n ],\n meshes: [\n {\n name: model.header.name,\n primitives: []\n }\n ],\n buffers: [\n {\n byteLength: 0 // To be filled\n }\n ],\n bufferViews: [],\n accessors: []\n };\n\n const binaryData: number[] = [];\n\n // Helpers to append data and create views\n const addBufferView = (data: Uint8Array, target?: number) => {\n const byteOffset = binaryData.length;\n // Align to 4 bytes\n while (binaryData.length % 4 !== 0) {\n binaryData.push(0);\n }\n const alignedOffset = binaryData.length;\n for (let i = 0; i < data.length; i++) {\n binaryData.push(data[i]);\n }\n const byteLength = data.length;\n const viewIndex = gltf.bufferViews.length;\n gltf.bufferViews.push({\n buffer: 0,\n byteOffset: alignedOffset,\n byteLength: byteLength,\n target: target\n });\n return viewIndex;\n };\n\n const addAccessor = (bufferView: number, componentType: number, count: number, type: string, min?: number[], max?: number[]) => {\n const index = gltf.accessors.length;\n const acc: any = {\n bufferView,\n componentType, // 5126=FLOAT, 5123=USHORT, 5125=UINT\n count,\n type, // \"SCALAR\", \"VEC2\", \"VEC3\"\n };\n if (min) acc.min = min;\n if (max) acc.max = max;\n gltf.accessors.push(acc);\n return index;\n };\n\n // Process surfaces\n // For each surface, export frame 0 geometry\n // MD3 has separate surfaces which map to GLTF primitives\n\n // We use frame 0\n const frameIndex = 0;\n\n for (const surface of model.surfaces) {\n // Vertices for frame 0\n const frameVerts = surface.vertices[frameIndex];\n\n // Positions (Vec3)\n const positions = new Float32Array(frameVerts.length * 3);\n const normals = new Float32Array(frameVerts.length * 3);\n const texCoords = new Float32Array(frameVerts.length * 2);\n\n let minPos = [Infinity, Infinity, Infinity];\n let maxPos = [-Infinity, -Infinity, -Infinity];\n\n for (let i = 0; i < frameVerts.length; i++) {\n const v = frameVerts[i];\n positions[i * 3] = v.position.x;\n positions[i * 3 + 1] = v.position.y;\n positions[i * 3 + 2] = v.position.z;\n\n minPos[0] = Math.min(minPos[0], v.position.x);\n minPos[1] = Math.min(minPos[1], v.position.y);\n minPos[2] = Math.min(minPos[2], v.position.z);\n maxPos[0] = Math.max(maxPos[0], v.position.x);\n maxPos[1] = Math.max(maxPos[1], v.position.y);\n maxPos[2] = Math.max(maxPos[2], v.position.z);\n\n normals[i * 3] = v.normal.x;\n normals[i * 3 + 1] = v.normal.y;\n normals[i * 3 + 2] = v.normal.z;\n\n // TexCoords (shared across frames in MD3)\n const tc = surface.texCoords[i];\n texCoords[i * 2] = tc.s;\n texCoords[i * 2 + 1] = 1.0 - tc.t; // Flip V\n }\n\n // Indices (Triangles)\n // MD3 indices are per surface\n const indices = new Uint16Array(surface.triangles.length * 3);\n for (let i = 0; i < surface.triangles.length; i++) {\n indices[i * 3] = surface.triangles[i].indices[0];\n indices[i * 3 + 1] = surface.triangles[i].indices[1];\n indices[i * 3 + 2] = surface.triangles[i].indices[2];\n }\n\n // Create BufferViews\n const posView = addBufferView(new Uint8Array(positions.buffer), 34962); // ARRAY_BUFFER\n const normView = addBufferView(new Uint8Array(normals.buffer), 34962);\n const tcView = addBufferView(new Uint8Array(texCoords.buffer), 34962);\n const idxView = addBufferView(new Uint8Array(indices.buffer), 34963); // ELEMENT_ARRAY_BUFFER\n\n // Create Accessors\n const posAcc = addAccessor(posView, 5126, frameVerts.length, \"VEC3\", minPos, maxPos);\n const normAcc = addAccessor(normView, 5126, frameVerts.length, \"VEC3\");\n const tcAcc = addAccessor(tcView, 5126, frameVerts.length, \"VEC2\");\n const idxAcc = addAccessor(idxView, 5123, indices.length, \"SCALAR\");\n\n // Add Primitive\n gltf.meshes[0].primitives.push({\n attributes: {\n POSITION: posAcc,\n NORMAL: normAcc,\n TEXCOORD_0: tcAcc\n },\n indices: idxAcc,\n material: undefined // Could add material info if needed\n });\n }\n\n // Finalize buffer\n // Pad to 4 bytes\n while (binaryData.length % 4 !== 0) {\n binaryData.push(0);\n }\n gltf.buffers[0].byteLength = binaryData.length;\n\n return {\n json: JSON.stringify(gltf, null, 2),\n buffer: new Uint8Array(binaryData).buffer\n };\n}\n"],"mappings":"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","names":["src_exports","__export","describeAsset","exportMd2ToObj","exportMd3ToGltf","exportMd2ToObj","model","frameIndex","frame","lines","v","width","height","tc","u","tri","v1","v2","v3","vt1","vt2","vt3","vn1","vn2","vn3","exportMd3ToGltf","gltf","binaryData","addBufferView","data","target","byteOffset","alignedOffset","i","byteLength","viewIndex","addAccessor","bufferView","componentType","count","type","min","max","index","acc","surface","frameVerts","positions","normals","texCoords","minPos","maxPos","indices","posView","normView","tcView","idxView","posAcc","normAcc","tcAcc","idxAcc","describeAsset","name","origin"]}
@@ -20,14 +20,187 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- describeAsset: () => describeAsset
23
+ describeAsset: () => describeAsset,
24
+ exportMd2ToObj: () => exportMd2ToObj,
25
+ exportMd3ToGltf: () => exportMd3ToGltf
24
26
  });
25
27
  module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/modelExport.ts
30
+ function exportMd2ToObj(model, frameIndex) {
31
+ if (frameIndex < 0 || frameIndex >= model.frames.length) {
32
+ throw new Error(`Frame index ${frameIndex} out of bounds (0-${model.frames.length - 1})`);
33
+ }
34
+ const frame = model.frames[frameIndex];
35
+ const lines = [];
36
+ lines.push(`# Quake 2 MD2 to OBJ Export`);
37
+ lines.push(`# Model: ${model.header.skinWidth}x${model.header.skinHeight}`);
38
+ lines.push(`# Frame: ${frameIndex} (${frame.name})`);
39
+ lines.push(`o ${frame.name}`);
40
+ for (const v of frame.vertices) {
41
+ lines.push(`v ${v.position.x.toFixed(6)} ${v.position.y.toFixed(6)} ${v.position.z.toFixed(6)}`);
42
+ }
43
+ const width = model.header.skinWidth;
44
+ const height = model.header.skinHeight;
45
+ for (const tc of model.texCoords) {
46
+ const u = tc.s / width;
47
+ const v = 1 - tc.t / height;
48
+ lines.push(`vt ${u.toFixed(6)} ${v.toFixed(6)}`);
49
+ }
50
+ for (const v of frame.vertices) {
51
+ lines.push(`vn ${v.normal.x.toFixed(6)} ${v.normal.y.toFixed(6)} ${v.normal.z.toFixed(6)}`);
52
+ }
53
+ lines.push(`s off`);
54
+ for (const tri of model.triangles) {
55
+ const v1 = tri.vertexIndices[0] + 1;
56
+ const v2 = tri.vertexIndices[1] + 1;
57
+ const v3 = tri.vertexIndices[2] + 1;
58
+ const vt1 = tri.texCoordIndices[0] + 1;
59
+ const vt2 = tri.texCoordIndices[1] + 1;
60
+ const vt3 = tri.texCoordIndices[2] + 1;
61
+ const vn1 = v1;
62
+ const vn2 = v2;
63
+ const vn3 = v3;
64
+ lines.push(`f ${v1}/${vt1}/${vn1} ${v2}/${vt2}/${vn2} ${v3}/${vt3}/${vn3}`);
65
+ }
66
+ return lines.join("\n");
67
+ }
68
+ function exportMd3ToGltf(model) {
69
+ const gltf = {
70
+ asset: {
71
+ version: "2.0",
72
+ generator: "quake2ts-tools"
73
+ },
74
+ scenes: [
75
+ {
76
+ nodes: [0]
77
+ }
78
+ ],
79
+ nodes: [
80
+ {
81
+ name: model.header.name,
82
+ mesh: 0
83
+ }
84
+ ],
85
+ meshes: [
86
+ {
87
+ name: model.header.name,
88
+ primitives: []
89
+ }
90
+ ],
91
+ buffers: [
92
+ {
93
+ byteLength: 0
94
+ // To be filled
95
+ }
96
+ ],
97
+ bufferViews: [],
98
+ accessors: []
99
+ };
100
+ const binaryData = [];
101
+ const addBufferView = (data, target) => {
102
+ const byteOffset = binaryData.length;
103
+ while (binaryData.length % 4 !== 0) {
104
+ binaryData.push(0);
105
+ }
106
+ const alignedOffset = binaryData.length;
107
+ for (let i = 0; i < data.length; i++) {
108
+ binaryData.push(data[i]);
109
+ }
110
+ const byteLength = data.length;
111
+ const viewIndex = gltf.bufferViews.length;
112
+ gltf.bufferViews.push({
113
+ buffer: 0,
114
+ byteOffset: alignedOffset,
115
+ byteLength,
116
+ target
117
+ });
118
+ return viewIndex;
119
+ };
120
+ const addAccessor = (bufferView, componentType, count, type, min, max) => {
121
+ const index = gltf.accessors.length;
122
+ const acc = {
123
+ bufferView,
124
+ componentType,
125
+ // 5126=FLOAT, 5123=USHORT, 5125=UINT
126
+ count,
127
+ type
128
+ // "SCALAR", "VEC2", "VEC3"
129
+ };
130
+ if (min) acc.min = min;
131
+ if (max) acc.max = max;
132
+ gltf.accessors.push(acc);
133
+ return index;
134
+ };
135
+ const frameIndex = 0;
136
+ for (const surface of model.surfaces) {
137
+ const frameVerts = surface.vertices[frameIndex];
138
+ const positions = new Float32Array(frameVerts.length * 3);
139
+ const normals = new Float32Array(frameVerts.length * 3);
140
+ const texCoords = new Float32Array(frameVerts.length * 2);
141
+ let minPos = [Infinity, Infinity, Infinity];
142
+ let maxPos = [-Infinity, -Infinity, -Infinity];
143
+ for (let i = 0; i < frameVerts.length; i++) {
144
+ const v = frameVerts[i];
145
+ positions[i * 3] = v.position.x;
146
+ positions[i * 3 + 1] = v.position.y;
147
+ positions[i * 3 + 2] = v.position.z;
148
+ minPos[0] = Math.min(minPos[0], v.position.x);
149
+ minPos[1] = Math.min(minPos[1], v.position.y);
150
+ minPos[2] = Math.min(minPos[2], v.position.z);
151
+ maxPos[0] = Math.max(maxPos[0], v.position.x);
152
+ maxPos[1] = Math.max(maxPos[1], v.position.y);
153
+ maxPos[2] = Math.max(maxPos[2], v.position.z);
154
+ normals[i * 3] = v.normal.x;
155
+ normals[i * 3 + 1] = v.normal.y;
156
+ normals[i * 3 + 2] = v.normal.z;
157
+ const tc = surface.texCoords[i];
158
+ texCoords[i * 2] = tc.s;
159
+ texCoords[i * 2 + 1] = 1 - tc.t;
160
+ }
161
+ const indices = new Uint16Array(surface.triangles.length * 3);
162
+ for (let i = 0; i < surface.triangles.length; i++) {
163
+ indices[i * 3] = surface.triangles[i].indices[0];
164
+ indices[i * 3 + 1] = surface.triangles[i].indices[1];
165
+ indices[i * 3 + 2] = surface.triangles[i].indices[2];
166
+ }
167
+ const posView = addBufferView(new Uint8Array(positions.buffer), 34962);
168
+ const normView = addBufferView(new Uint8Array(normals.buffer), 34962);
169
+ const tcView = addBufferView(new Uint8Array(texCoords.buffer), 34962);
170
+ const idxView = addBufferView(new Uint8Array(indices.buffer), 34963);
171
+ const posAcc = addAccessor(posView, 5126, frameVerts.length, "VEC3", minPos, maxPos);
172
+ const normAcc = addAccessor(normView, 5126, frameVerts.length, "VEC3");
173
+ const tcAcc = addAccessor(tcView, 5126, frameVerts.length, "VEC2");
174
+ const idxAcc = addAccessor(idxView, 5123, indices.length, "SCALAR");
175
+ gltf.meshes[0].primitives.push({
176
+ attributes: {
177
+ POSITION: posAcc,
178
+ NORMAL: normAcc,
179
+ TEXCOORD_0: tcAcc
180
+ },
181
+ indices: idxAcc,
182
+ material: void 0
183
+ // Could add material info if needed
184
+ });
185
+ }
186
+ while (binaryData.length % 4 !== 0) {
187
+ binaryData.push(0);
188
+ }
189
+ gltf.buffers[0].byteLength = binaryData.length;
190
+ return {
191
+ json: JSON.stringify(gltf, null, 2),
192
+ buffer: new Uint8Array(binaryData).buffer
193
+ };
194
+ }
195
+
196
+ // src/index.ts
26
197
  function describeAsset(name, origin) {
27
198
  return { name, origin };
28
199
  }
29
200
  // Annotate the CommonJS export names for ESM import in node:
30
201
  0 && (module.exports = {
31
- describeAsset
202
+ describeAsset,
203
+ exportMd2ToObj,
204
+ exportMd3ToGltf
32
205
  });
33
206
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts"],"sourcesContent":["import type { Vec3 } from '@quake2ts/shared';\n\nexport interface AssetSummary {\n readonly name: string;\n readonly origin?: Vec3;\n}\n\nexport function describeAsset(name: string, origin?: Vec3): AssetSummary {\n return { name, origin };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOO,SAAS,cAAc,MAAc,QAA6B;AACvE,SAAO,EAAE,MAAM,OAAO;AACxB;","names":[]}
1
+ {"version":3,"sources":["../../src/index.ts","../../src/modelExport.ts"],"sourcesContent":["import type { Vec3 } from '@quake2ts/shared';\n\nexport interface AssetSummary {\n readonly name: string;\n readonly origin?: Vec3;\n}\n\nexport function describeAsset(name: string, origin?: Vec3): AssetSummary {\n return { name, origin };\n}\n\nexport { exportMd2ToObj, exportMd3ToGltf } from './modelExport.js';\n","import { Md2Model, Md3Model } from '@quake2ts/engine';\n\n/**\n * Export MD2 model frame to OBJ format\n * @param model Parsed MD2 model\n * @param frameIndex Frame index to export\n * @returns OBJ file contents as string\n */\nexport function exportMd2ToObj(model: Md2Model, frameIndex: number): string {\n if (frameIndex < 0 || frameIndex >= model.frames.length) {\n throw new Error(`Frame index ${frameIndex} out of bounds (0-${model.frames.length - 1})`);\n }\n\n const frame = model.frames[frameIndex];\n const lines: string[] = [];\n\n lines.push(`# Quake 2 MD2 to OBJ Export`);\n lines.push(`# Model: ${model.header.skinWidth}x${model.header.skinHeight}`);\n lines.push(`# Frame: ${frameIndex} (${frame.name})`);\n lines.push(`o ${frame.name}`);\n\n // Write vertices\n // OBJ vertices are \"v x y z\"\n // Quake uses Z-up, Y-forward? No, Quake is Z-up. OBJ is typically Y-up?\n // Actually, standard OBJ is just points. Tools usually expect Y-up.\n // Quake coords: X=Forward, Y=Left, Z=Up.\n // Blender/Standard: X=Right, Y=Up, Z=Back.\n // We usually export as-is and let the user handle rotation, or swap Y/Z.\n // The request doesn't specify coordinate conversion. I will export as-is (Quake coordinates).\n // Note: MD2 vertices are in local model space.\n for (const v of frame.vertices) {\n lines.push(`v ${v.position.x.toFixed(6)} ${v.position.y.toFixed(6)} ${v.position.z.toFixed(6)}`);\n }\n\n // Write texture coordinates\n // OBJ UVs are \"vt u v\"\n // MD2 tex coords are integers, need to normalize by skin size.\n // Also MD2 (0,0) is top-left, OBJ (0,0) is usually bottom-left.\n // So v = 1 - (t / height).\n const width = model.header.skinWidth;\n const height = model.header.skinHeight;\n for (const tc of model.texCoords) {\n const u = tc.s / width;\n const v = 1.0 - (tc.t / height);\n lines.push(`vt ${u.toFixed(6)} ${v.toFixed(6)}`);\n }\n\n // Write normals\n // MD2 stores normals in frame.vertices[i].normal\n for (const v of frame.vertices) {\n lines.push(`vn ${v.normal.x.toFixed(6)} ${v.normal.y.toFixed(6)} ${v.normal.z.toFixed(6)}`);\n }\n\n // Write faces\n // f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3\n // Indices are 1-based in OBJ.\n lines.push(`s off`); // Smoothing groups off\n for (const tri of model.triangles) {\n const v1 = tri.vertexIndices[0] + 1;\n const v2 = tri.vertexIndices[1] + 1;\n const v3 = tri.vertexIndices[2] + 1;\n\n const vt1 = tri.texCoordIndices[0] + 1;\n const vt2 = tri.texCoordIndices[1] + 1;\n const vt3 = tri.texCoordIndices[2] + 1;\n\n // Normal indices match vertex indices in MD2 (per-vertex normals)\n const vn1 = v1;\n const vn2 = v2;\n const vn3 = v3;\n\n // Reverse winding? Quake is clockwise? OpenGL is CCW.\n // MD2 is usually Clockwise winding for front face?\n // Let's stick to the order in the file.\n lines.push(`f ${v1}/${vt1}/${vn1} ${v2}/${vt2}/${vn2} ${v3}/${vt3}/${vn3}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Export MD3 model to glTF 2.0 format\n * Currently exports the first frame as a static mesh.\n * @param model Parsed MD3 model\n * @returns glTF JSON and binary buffer\n */\nexport function exportMd3ToGltf(model: Md3Model): {\n json: string\n buffer: ArrayBuffer\n} {\n // Structure for GLTF\n const gltf: any = {\n asset: {\n version: \"2.0\",\n generator: \"quake2ts-tools\"\n },\n scenes: [\n {\n nodes: [0]\n }\n ],\n nodes: [\n {\n name: model.header.name,\n mesh: 0\n }\n ],\n meshes: [\n {\n name: model.header.name,\n primitives: []\n }\n ],\n buffers: [\n {\n byteLength: 0 // To be filled\n }\n ],\n bufferViews: [],\n accessors: []\n };\n\n const binaryData: number[] = [];\n\n // Helpers to append data and create views\n const addBufferView = (data: Uint8Array, target?: number) => {\n const byteOffset = binaryData.length;\n // Align to 4 bytes\n while (binaryData.length % 4 !== 0) {\n binaryData.push(0);\n }\n const alignedOffset = binaryData.length;\n for (let i = 0; i < data.length; i++) {\n binaryData.push(data[i]);\n }\n const byteLength = data.length;\n const viewIndex = gltf.bufferViews.length;\n gltf.bufferViews.push({\n buffer: 0,\n byteOffset: alignedOffset,\n byteLength: byteLength,\n target: target\n });\n return viewIndex;\n };\n\n const addAccessor = (bufferView: number, componentType: number, count: number, type: string, min?: number[], max?: number[]) => {\n const index = gltf.accessors.length;\n const acc: any = {\n bufferView,\n componentType, // 5126=FLOAT, 5123=USHORT, 5125=UINT\n count,\n type, // \"SCALAR\", \"VEC2\", \"VEC3\"\n };\n if (min) acc.min = min;\n if (max) acc.max = max;\n gltf.accessors.push(acc);\n return index;\n };\n\n // Process surfaces\n // For each surface, export frame 0 geometry\n // MD3 has separate surfaces which map to GLTF primitives\n\n // We use frame 0\n const frameIndex = 0;\n\n for (const surface of model.surfaces) {\n // Vertices for frame 0\n const frameVerts = surface.vertices[frameIndex];\n\n // Positions (Vec3)\n const positions = new Float32Array(frameVerts.length * 3);\n const normals = new Float32Array(frameVerts.length * 3);\n const texCoords = new Float32Array(frameVerts.length * 2);\n\n let minPos = [Infinity, Infinity, Infinity];\n let maxPos = [-Infinity, -Infinity, -Infinity];\n\n for (let i = 0; i < frameVerts.length; i++) {\n const v = frameVerts[i];\n positions[i * 3] = v.position.x;\n positions[i * 3 + 1] = v.position.y;\n positions[i * 3 + 2] = v.position.z;\n\n minPos[0] = Math.min(minPos[0], v.position.x);\n minPos[1] = Math.min(minPos[1], v.position.y);\n minPos[2] = Math.min(minPos[2], v.position.z);\n maxPos[0] = Math.max(maxPos[0], v.position.x);\n maxPos[1] = Math.max(maxPos[1], v.position.y);\n maxPos[2] = Math.max(maxPos[2], v.position.z);\n\n normals[i * 3] = v.normal.x;\n normals[i * 3 + 1] = v.normal.y;\n normals[i * 3 + 2] = v.normal.z;\n\n // TexCoords (shared across frames in MD3)\n const tc = surface.texCoords[i];\n texCoords[i * 2] = tc.s;\n texCoords[i * 2 + 1] = 1.0 - tc.t; // Flip V\n }\n\n // Indices (Triangles)\n // MD3 indices are per surface\n const indices = new Uint16Array(surface.triangles.length * 3);\n for (let i = 0; i < surface.triangles.length; i++) {\n indices[i * 3] = surface.triangles[i].indices[0];\n indices[i * 3 + 1] = surface.triangles[i].indices[1];\n indices[i * 3 + 2] = surface.triangles[i].indices[2];\n }\n\n // Create BufferViews\n const posView = addBufferView(new Uint8Array(positions.buffer), 34962); // ARRAY_BUFFER\n const normView = addBufferView(new Uint8Array(normals.buffer), 34962);\n const tcView = addBufferView(new Uint8Array(texCoords.buffer), 34962);\n const idxView = addBufferView(new Uint8Array(indices.buffer), 34963); // ELEMENT_ARRAY_BUFFER\n\n // Create Accessors\n const posAcc = addAccessor(posView, 5126, frameVerts.length, \"VEC3\", minPos, maxPos);\n const normAcc = addAccessor(normView, 5126, frameVerts.length, \"VEC3\");\n const tcAcc = addAccessor(tcView, 5126, frameVerts.length, \"VEC2\");\n const idxAcc = addAccessor(idxView, 5123, indices.length, \"SCALAR\");\n\n // Add Primitive\n gltf.meshes[0].primitives.push({\n attributes: {\n POSITION: posAcc,\n NORMAL: normAcc,\n TEXCOORD_0: tcAcc\n },\n indices: idxAcc,\n material: undefined // Could add material info if needed\n });\n }\n\n // Finalize buffer\n // Pad to 4 bytes\n while (binaryData.length % 4 !== 0) {\n binaryData.push(0);\n }\n gltf.buffers[0].byteLength = binaryData.length;\n\n return {\n json: JSON.stringify(gltf, null, 2),\n buffer: new Uint8Array(binaryData).buffer\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,8 +1,177 @@
1
+ // src/modelExport.ts
2
+ function exportMd2ToObj(model, frameIndex) {
3
+ if (frameIndex < 0 || frameIndex >= model.frames.length) {
4
+ throw new Error(`Frame index ${frameIndex} out of bounds (0-${model.frames.length - 1})`);
5
+ }
6
+ const frame = model.frames[frameIndex];
7
+ const lines = [];
8
+ lines.push(`# Quake 2 MD2 to OBJ Export`);
9
+ lines.push(`# Model: ${model.header.skinWidth}x${model.header.skinHeight}`);
10
+ lines.push(`# Frame: ${frameIndex} (${frame.name})`);
11
+ lines.push(`o ${frame.name}`);
12
+ for (const v of frame.vertices) {
13
+ lines.push(`v ${v.position.x.toFixed(6)} ${v.position.y.toFixed(6)} ${v.position.z.toFixed(6)}`);
14
+ }
15
+ const width = model.header.skinWidth;
16
+ const height = model.header.skinHeight;
17
+ for (const tc of model.texCoords) {
18
+ const u = tc.s / width;
19
+ const v = 1 - tc.t / height;
20
+ lines.push(`vt ${u.toFixed(6)} ${v.toFixed(6)}`);
21
+ }
22
+ for (const v of frame.vertices) {
23
+ lines.push(`vn ${v.normal.x.toFixed(6)} ${v.normal.y.toFixed(6)} ${v.normal.z.toFixed(6)}`);
24
+ }
25
+ lines.push(`s off`);
26
+ for (const tri of model.triangles) {
27
+ const v1 = tri.vertexIndices[0] + 1;
28
+ const v2 = tri.vertexIndices[1] + 1;
29
+ const v3 = tri.vertexIndices[2] + 1;
30
+ const vt1 = tri.texCoordIndices[0] + 1;
31
+ const vt2 = tri.texCoordIndices[1] + 1;
32
+ const vt3 = tri.texCoordIndices[2] + 1;
33
+ const vn1 = v1;
34
+ const vn2 = v2;
35
+ const vn3 = v3;
36
+ lines.push(`f ${v1}/${vt1}/${vn1} ${v2}/${vt2}/${vn2} ${v3}/${vt3}/${vn3}`);
37
+ }
38
+ return lines.join("\n");
39
+ }
40
+ function exportMd3ToGltf(model) {
41
+ const gltf = {
42
+ asset: {
43
+ version: "2.0",
44
+ generator: "quake2ts-tools"
45
+ },
46
+ scenes: [
47
+ {
48
+ nodes: [0]
49
+ }
50
+ ],
51
+ nodes: [
52
+ {
53
+ name: model.header.name,
54
+ mesh: 0
55
+ }
56
+ ],
57
+ meshes: [
58
+ {
59
+ name: model.header.name,
60
+ primitives: []
61
+ }
62
+ ],
63
+ buffers: [
64
+ {
65
+ byteLength: 0
66
+ // To be filled
67
+ }
68
+ ],
69
+ bufferViews: [],
70
+ accessors: []
71
+ };
72
+ const binaryData = [];
73
+ const addBufferView = (data, target) => {
74
+ const byteOffset = binaryData.length;
75
+ while (binaryData.length % 4 !== 0) {
76
+ binaryData.push(0);
77
+ }
78
+ const alignedOffset = binaryData.length;
79
+ for (let i = 0; i < data.length; i++) {
80
+ binaryData.push(data[i]);
81
+ }
82
+ const byteLength = data.length;
83
+ const viewIndex = gltf.bufferViews.length;
84
+ gltf.bufferViews.push({
85
+ buffer: 0,
86
+ byteOffset: alignedOffset,
87
+ byteLength,
88
+ target
89
+ });
90
+ return viewIndex;
91
+ };
92
+ const addAccessor = (bufferView, componentType, count, type, min, max) => {
93
+ const index = gltf.accessors.length;
94
+ const acc = {
95
+ bufferView,
96
+ componentType,
97
+ // 5126=FLOAT, 5123=USHORT, 5125=UINT
98
+ count,
99
+ type
100
+ // "SCALAR", "VEC2", "VEC3"
101
+ };
102
+ if (min) acc.min = min;
103
+ if (max) acc.max = max;
104
+ gltf.accessors.push(acc);
105
+ return index;
106
+ };
107
+ const frameIndex = 0;
108
+ for (const surface of model.surfaces) {
109
+ const frameVerts = surface.vertices[frameIndex];
110
+ const positions = new Float32Array(frameVerts.length * 3);
111
+ const normals = new Float32Array(frameVerts.length * 3);
112
+ const texCoords = new Float32Array(frameVerts.length * 2);
113
+ let minPos = [Infinity, Infinity, Infinity];
114
+ let maxPos = [-Infinity, -Infinity, -Infinity];
115
+ for (let i = 0; i < frameVerts.length; i++) {
116
+ const v = frameVerts[i];
117
+ positions[i * 3] = v.position.x;
118
+ positions[i * 3 + 1] = v.position.y;
119
+ positions[i * 3 + 2] = v.position.z;
120
+ minPos[0] = Math.min(minPos[0], v.position.x);
121
+ minPos[1] = Math.min(minPos[1], v.position.y);
122
+ minPos[2] = Math.min(minPos[2], v.position.z);
123
+ maxPos[0] = Math.max(maxPos[0], v.position.x);
124
+ maxPos[1] = Math.max(maxPos[1], v.position.y);
125
+ maxPos[2] = Math.max(maxPos[2], v.position.z);
126
+ normals[i * 3] = v.normal.x;
127
+ normals[i * 3 + 1] = v.normal.y;
128
+ normals[i * 3 + 2] = v.normal.z;
129
+ const tc = surface.texCoords[i];
130
+ texCoords[i * 2] = tc.s;
131
+ texCoords[i * 2 + 1] = 1 - tc.t;
132
+ }
133
+ const indices = new Uint16Array(surface.triangles.length * 3);
134
+ for (let i = 0; i < surface.triangles.length; i++) {
135
+ indices[i * 3] = surface.triangles[i].indices[0];
136
+ indices[i * 3 + 1] = surface.triangles[i].indices[1];
137
+ indices[i * 3 + 2] = surface.triangles[i].indices[2];
138
+ }
139
+ const posView = addBufferView(new Uint8Array(positions.buffer), 34962);
140
+ const normView = addBufferView(new Uint8Array(normals.buffer), 34962);
141
+ const tcView = addBufferView(new Uint8Array(texCoords.buffer), 34962);
142
+ const idxView = addBufferView(new Uint8Array(indices.buffer), 34963);
143
+ const posAcc = addAccessor(posView, 5126, frameVerts.length, "VEC3", minPos, maxPos);
144
+ const normAcc = addAccessor(normView, 5126, frameVerts.length, "VEC3");
145
+ const tcAcc = addAccessor(tcView, 5126, frameVerts.length, "VEC2");
146
+ const idxAcc = addAccessor(idxView, 5123, indices.length, "SCALAR");
147
+ gltf.meshes[0].primitives.push({
148
+ attributes: {
149
+ POSITION: posAcc,
150
+ NORMAL: normAcc,
151
+ TEXCOORD_0: tcAcc
152
+ },
153
+ indices: idxAcc,
154
+ material: void 0
155
+ // Could add material info if needed
156
+ });
157
+ }
158
+ while (binaryData.length % 4 !== 0) {
159
+ binaryData.push(0);
160
+ }
161
+ gltf.buffers[0].byteLength = binaryData.length;
162
+ return {
163
+ json: JSON.stringify(gltf, null, 2),
164
+ buffer: new Uint8Array(binaryData).buffer
165
+ };
166
+ }
167
+
1
168
  // src/index.ts
2
169
  function describeAsset(name, origin) {
3
170
  return { name, origin };
4
171
  }
5
172
  export {
6
- describeAsset
173
+ describeAsset,
174
+ exportMd2ToObj,
175
+ exportMd3ToGltf
7
176
  };
8
177
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/index.ts"],"sourcesContent":["import type { Vec3 } from '@quake2ts/shared';\n\nexport interface AssetSummary {\n readonly name: string;\n readonly origin?: Vec3;\n}\n\nexport function describeAsset(name: string, origin?: Vec3): AssetSummary {\n return { name, origin };\n}\n"],"mappings":";AAOO,SAAS,cAAc,MAAc,QAA6B;AACvE,SAAO,EAAE,MAAM,OAAO;AACxB;","names":[]}
1
+ {"version":3,"sources":["../../src/modelExport.ts","../../src/index.ts"],"sourcesContent":["import { Md2Model, Md3Model } from '@quake2ts/engine';\n\n/**\n * Export MD2 model frame to OBJ format\n * @param model Parsed MD2 model\n * @param frameIndex Frame index to export\n * @returns OBJ file contents as string\n */\nexport function exportMd2ToObj(model: Md2Model, frameIndex: number): string {\n if (frameIndex < 0 || frameIndex >= model.frames.length) {\n throw new Error(`Frame index ${frameIndex} out of bounds (0-${model.frames.length - 1})`);\n }\n\n const frame = model.frames[frameIndex];\n const lines: string[] = [];\n\n lines.push(`# Quake 2 MD2 to OBJ Export`);\n lines.push(`# Model: ${model.header.skinWidth}x${model.header.skinHeight}`);\n lines.push(`# Frame: ${frameIndex} (${frame.name})`);\n lines.push(`o ${frame.name}`);\n\n // Write vertices\n // OBJ vertices are \"v x y z\"\n // Quake uses Z-up, Y-forward? No, Quake is Z-up. OBJ is typically Y-up?\n // Actually, standard OBJ is just points. Tools usually expect Y-up.\n // Quake coords: X=Forward, Y=Left, Z=Up.\n // Blender/Standard: X=Right, Y=Up, Z=Back.\n // We usually export as-is and let the user handle rotation, or swap Y/Z.\n // The request doesn't specify coordinate conversion. I will export as-is (Quake coordinates).\n // Note: MD2 vertices are in local model space.\n for (const v of frame.vertices) {\n lines.push(`v ${v.position.x.toFixed(6)} ${v.position.y.toFixed(6)} ${v.position.z.toFixed(6)}`);\n }\n\n // Write texture coordinates\n // OBJ UVs are \"vt u v\"\n // MD2 tex coords are integers, need to normalize by skin size.\n // Also MD2 (0,0) is top-left, OBJ (0,0) is usually bottom-left.\n // So v = 1 - (t / height).\n const width = model.header.skinWidth;\n const height = model.header.skinHeight;\n for (const tc of model.texCoords) {\n const u = tc.s / width;\n const v = 1.0 - (tc.t / height);\n lines.push(`vt ${u.toFixed(6)} ${v.toFixed(6)}`);\n }\n\n // Write normals\n // MD2 stores normals in frame.vertices[i].normal\n for (const v of frame.vertices) {\n lines.push(`vn ${v.normal.x.toFixed(6)} ${v.normal.y.toFixed(6)} ${v.normal.z.toFixed(6)}`);\n }\n\n // Write faces\n // f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3\n // Indices are 1-based in OBJ.\n lines.push(`s off`); // Smoothing groups off\n for (const tri of model.triangles) {\n const v1 = tri.vertexIndices[0] + 1;\n const v2 = tri.vertexIndices[1] + 1;\n const v3 = tri.vertexIndices[2] + 1;\n\n const vt1 = tri.texCoordIndices[0] + 1;\n const vt2 = tri.texCoordIndices[1] + 1;\n const vt3 = tri.texCoordIndices[2] + 1;\n\n // Normal indices match vertex indices in MD2 (per-vertex normals)\n const vn1 = v1;\n const vn2 = v2;\n const vn3 = v3;\n\n // Reverse winding? Quake is clockwise? OpenGL is CCW.\n // MD2 is usually Clockwise winding for front face?\n // Let's stick to the order in the file.\n lines.push(`f ${v1}/${vt1}/${vn1} ${v2}/${vt2}/${vn2} ${v3}/${vt3}/${vn3}`);\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Export MD3 model to glTF 2.0 format\n * Currently exports the first frame as a static mesh.\n * @param model Parsed MD3 model\n * @returns glTF JSON and binary buffer\n */\nexport function exportMd3ToGltf(model: Md3Model): {\n json: string\n buffer: ArrayBuffer\n} {\n // Structure for GLTF\n const gltf: any = {\n asset: {\n version: \"2.0\",\n generator: \"quake2ts-tools\"\n },\n scenes: [\n {\n nodes: [0]\n }\n ],\n nodes: [\n {\n name: model.header.name,\n mesh: 0\n }\n ],\n meshes: [\n {\n name: model.header.name,\n primitives: []\n }\n ],\n buffers: [\n {\n byteLength: 0 // To be filled\n }\n ],\n bufferViews: [],\n accessors: []\n };\n\n const binaryData: number[] = [];\n\n // Helpers to append data and create views\n const addBufferView = (data: Uint8Array, target?: number) => {\n const byteOffset = binaryData.length;\n // Align to 4 bytes\n while (binaryData.length % 4 !== 0) {\n binaryData.push(0);\n }\n const alignedOffset = binaryData.length;\n for (let i = 0; i < data.length; i++) {\n binaryData.push(data[i]);\n }\n const byteLength = data.length;\n const viewIndex = gltf.bufferViews.length;\n gltf.bufferViews.push({\n buffer: 0,\n byteOffset: alignedOffset,\n byteLength: byteLength,\n target: target\n });\n return viewIndex;\n };\n\n const addAccessor = (bufferView: number, componentType: number, count: number, type: string, min?: number[], max?: number[]) => {\n const index = gltf.accessors.length;\n const acc: any = {\n bufferView,\n componentType, // 5126=FLOAT, 5123=USHORT, 5125=UINT\n count,\n type, // \"SCALAR\", \"VEC2\", \"VEC3\"\n };\n if (min) acc.min = min;\n if (max) acc.max = max;\n gltf.accessors.push(acc);\n return index;\n };\n\n // Process surfaces\n // For each surface, export frame 0 geometry\n // MD3 has separate surfaces which map to GLTF primitives\n\n // We use frame 0\n const frameIndex = 0;\n\n for (const surface of model.surfaces) {\n // Vertices for frame 0\n const frameVerts = surface.vertices[frameIndex];\n\n // Positions (Vec3)\n const positions = new Float32Array(frameVerts.length * 3);\n const normals = new Float32Array(frameVerts.length * 3);\n const texCoords = new Float32Array(frameVerts.length * 2);\n\n let minPos = [Infinity, Infinity, Infinity];\n let maxPos = [-Infinity, -Infinity, -Infinity];\n\n for (let i = 0; i < frameVerts.length; i++) {\n const v = frameVerts[i];\n positions[i * 3] = v.position.x;\n positions[i * 3 + 1] = v.position.y;\n positions[i * 3 + 2] = v.position.z;\n\n minPos[0] = Math.min(minPos[0], v.position.x);\n minPos[1] = Math.min(minPos[1], v.position.y);\n minPos[2] = Math.min(minPos[2], v.position.z);\n maxPos[0] = Math.max(maxPos[0], v.position.x);\n maxPos[1] = Math.max(maxPos[1], v.position.y);\n maxPos[2] = Math.max(maxPos[2], v.position.z);\n\n normals[i * 3] = v.normal.x;\n normals[i * 3 + 1] = v.normal.y;\n normals[i * 3 + 2] = v.normal.z;\n\n // TexCoords (shared across frames in MD3)\n const tc = surface.texCoords[i];\n texCoords[i * 2] = tc.s;\n texCoords[i * 2 + 1] = 1.0 - tc.t; // Flip V\n }\n\n // Indices (Triangles)\n // MD3 indices are per surface\n const indices = new Uint16Array(surface.triangles.length * 3);\n for (let i = 0; i < surface.triangles.length; i++) {\n indices[i * 3] = surface.triangles[i].indices[0];\n indices[i * 3 + 1] = surface.triangles[i].indices[1];\n indices[i * 3 + 2] = surface.triangles[i].indices[2];\n }\n\n // Create BufferViews\n const posView = addBufferView(new Uint8Array(positions.buffer), 34962); // ARRAY_BUFFER\n const normView = addBufferView(new Uint8Array(normals.buffer), 34962);\n const tcView = addBufferView(new Uint8Array(texCoords.buffer), 34962);\n const idxView = addBufferView(new Uint8Array(indices.buffer), 34963); // ELEMENT_ARRAY_BUFFER\n\n // Create Accessors\n const posAcc = addAccessor(posView, 5126, frameVerts.length, \"VEC3\", minPos, maxPos);\n const normAcc = addAccessor(normView, 5126, frameVerts.length, \"VEC3\");\n const tcAcc = addAccessor(tcView, 5126, frameVerts.length, \"VEC2\");\n const idxAcc = addAccessor(idxView, 5123, indices.length, \"SCALAR\");\n\n // Add Primitive\n gltf.meshes[0].primitives.push({\n attributes: {\n POSITION: posAcc,\n NORMAL: normAcc,\n TEXCOORD_0: tcAcc\n },\n indices: idxAcc,\n material: undefined // Could add material info if needed\n });\n }\n\n // Finalize buffer\n // Pad to 4 bytes\n while (binaryData.length % 4 !== 0) {\n binaryData.push(0);\n }\n gltf.buffers[0].byteLength = binaryData.length;\n\n return {\n json: JSON.stringify(gltf, null, 2),\n buffer: new Uint8Array(binaryData).buffer\n };\n}\n","import type { Vec3 } from '@quake2ts/shared';\n\nexport interface AssetSummary {\n readonly name: string;\n readonly origin?: Vec3;\n}\n\nexport function describeAsset(name: string, origin?: Vec3): AssetSummary {\n return { name, origin };\n}\n\nexport { exportMd2ToObj, exportMd3ToGltf } from './modelExport.js';\n"],"mappings":"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","names":[]}