-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathDATParser.js
More file actions
executable file
·331 lines (321 loc) · 11.1 KB
/
Copy pathDATParser.js
File metadata and controls
executable file
·331 lines (321 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
/**
* Set up the .dat parser for turning .dat text into JS mesh object representations
*/
var DATParser = (function () {
"use strict";
// matfile data cache
var dataCache = {};
/**
* Get the data for a .dat file
*/
var getDATFile = function (filename) {
// cache file!
if (!dataCache[filename]) {
var xhr = new XMLHttpRequest();
xhr.open("GET", filename, false);
xhr.send(null);
if (xhr.responseText.trim() === "") {
// oh no! try it stripped!
filename = filename.replace(/\\/g, '/');
filename = filename.substring(filename.lastIndexOf('/') + 1);
xhr.open("GET", "./parts/" + filename, false);
xhr.send(null);
if (xhr.responseText.trim() === "") {
// oh crap oh crap oh crap
throw "ERROR: " + filename + " was empty!";
}
}
dataCache[filename] = xhr.responseText;
}
return dataCache[filename];
};
/**
* Cast a value to a floating point number.
*/
var castFloat = function (v) {
return parseFloat(v);
};
/**
* Build a new mesh object for building the .dat file
*/
var uid = 1;
var buildMesh = function () {
return {
// local ID
meshId: uid++,
// list of vertices used in this mesh
vertices: [],
// list of faces used in this mesh
faces: [],
/**
* Add a vertex to this mesh. Vertices
* have x/y/z values as well as a color.
*/
addVertex: function (x, y, z, c) {
var vertex = {x: x, y: y, z: z, color: c};
this.vertices.push(vertex);
},
/**
* Add a triangle to this mesh. [m] will contain
* three x/y/z coordinates.
*/
addFace: function (m, c, clockwise, inverted) {
// record the vertices
var v1 = this.vertices.length;
this.addVertex(m[0], m[1], m[2], c);
var v2 = this.vertices.length;
this.addVertex(m[3], m[4], m[5], c);
var v3 = this.vertices.length;
this.addVertex(m[6], m[7], m[8], c);
// Record the triangle as a face, corrected for
// CW/CCW direction and face inversion.
var normal = (clockwise && inverted) || (!clockwise && !inverted);
this.faces.push({v1: v1, v2: (normal ? v2 : v3), v3: (normal ? v3 : v2)});
},
/**
* Add a quad to this mesh. [m] will contain
* four x/y/z coordinates.
*/
addQuad: function (m, c, clockwise, inverted) {
// record the vertices
var v1 = this.vertices.length;
this.addVertex(m[0], m[1], m[2], c);
var v2 = this.vertices.length;
this.addVertex(m[3], m[4], m[5], c);
var v3 = this.vertices.length;
this.addVertex(m[6], m[7], m[8], c);
var v4 = this.vertices.length;
this.addVertex(m[9], m[10], m[11], c);
// Record the triangle as a face, corrected for
// CW/CCW direction and face inversion.
var normal = (clockwise && inverted) || (!clockwise && !inverted);
this.faces.push({v1: v1, v2: (normal ? v2 : v3), v3: (normal ? v3 : v2)});
this.faces.push({v1: v3, v2: (normal ? v4 : v1), v3: (normal ? v1 : v4)});
},
/**
* format: m=[x,y,z,a,b,c,d,e,f,g,h,i], color
*
* m defines the following 3D transform matrix:
*
* | a d g 0 |
* | b e h 0 |
* | c f i 0 |
* | X Y Z 1 |
*
* so that every point (x, y ,z) gets transformed to (x', y', z'):
*
* x' = a*x + b*y + c*z + X
* y' = d*x + e*y + f*z + Y
* z' = g*x + h*y + i*z + Z
*/
apply: function (c, m, color) {
this.vertices.forEach(function (v) {
var x = m[0] * v.x + m[1] * v.y + m[2] * v.z + c[0];
var y = m[3] * v.x + m[4] * v.y + m[5] * v.z + c[1];
var z = m[6] * v.x + m[7] * v.y + m[8] * v.z + c[2];
v.x = x;
v.y = y;
v.z = z;
v.color = color;
});
}
};
};
/**
* The parser object that will be returned, capable of reading
* in .dat data and producing a javascript mesh object, which
* can be turned into .obj and .mtl files using the toOBJ and toMTL
* functions providede outside of the DATParser context.
*/
var Parser = function Parser(inverted, consolePrefix) {
// default values
this.invertNext = false;
this.clockwise = false;
// is this (sub)mesh inverted?
this.invert = inverted;
this.consolePrefix = consolePrefix || "";
};
/**
* Most of the functions are prototype functions.
*/
Parser.prototype = {
/**
* Logging wrapper
*/
log: (function () {
if (console && console.log) {
return function (s) {
console.log(this.consolePrefix + s);
};
}
return function (s) {};
}()),
/**
* Backface culling instructions.
* See http://www.ldraw.org/article/415.html for details
*/
handleBFC: function (operands, line) {
var parser = this;
operands.forEach(function (op) {
if (op === "CW") {
parser.clockwise = true;
}
if (op === "CCW") {
parser.clockwise = false;
}
if (op === "INVERTNEXT") {
parser.invertNext = true;
}
});
},
/**
* We don't care about comments, but there is one type of
* instruction that takes the form of a comment that we
* care a lot about: the backface culling instructions.
*/
handleComment: function (operands, line) {
if (operands[0] === "BFC") {
operands.splice(0, 1);
this.handleBFC(operands, line);
}
},
/**
* import format: color, x,y,z, a,b,c,d,e,f,g,h,i, "part filename"
*
* Fields 'a' through 'i' are 3D transform matrix values.
* See the mesh.apply() function for more details.
*/
handleDependency: function (operands, meshes, lineInstruction) {
// handle the include
var dependency = operands.splice(operands.length - 1, 1)[0];
operands = operands.map(castFloat);
// figure out the determinant for this include matrix:
var det = (function (m) {
var t1 = m[0] * (m[4] * m[8] - m[7] * m[5]);
var t2 = m[3] * (m[1] * m[8] - m[7] * m[2]);
var t3 = m[6] * (m[1] * m[5] - m[4] * m[2]);
return t1 - t2 + t3;
}(operands.slice(4, 4 + 9)));
// Make sure to get the BFC inversion right.
var inverted = this.invert;
if (this.invertNext) {
inverted = !inverted;
}
if (det < 0) {
inverted = !inverted;
}
var dependencyParser = new Parser(inverted, this.consolePrefix);
this.invertNext = false;
var dependencyMeshes = dependencyParser.parseFile(dependency);
// Also make sure to apply RT matrices when appending the meshes:
dependencyMeshes.forEach(function (mesh) {
mesh.apply(operands.slice(1, 1 + 3), operands.slice(4, 4 + 9), operands[0]);
meshes.push(mesh);
});
},
/**
* triangle format: color, x1,y1,z1, x2,y2,z2, x3,y3,z3
*/
parseTriangle: function (arg, mesh) {
arg = arg.map(castFloat);
mesh.addFace(arg.slice(1), arg[0], this.clockwise, this.invert);
},
/**
* quad format: color, x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4
*/
parseQuad: function (arg, mesh) {
arg = arg.map(castFloat);
mesh.addQuad(arg.slice(1), arg[0], this.clockwise, this.invert);
},
/**
* parse a .dat line instruction. This builds out the
* javascript mesh file that was passed as [meshes]
*/
parseInstruction: function (line, meshes) {
var currentMesh = meshes[meshes.length - 1];
if (line.trim() === "" && currentMesh.vertices.length > 0) {
meshes.push(buildMesh());
return;
}
var operands = line.trim().split(/\s+/);
var operator = operands.splice(0, 1)[0];
// there are a number of operators that we don't care about
if (operator === "2") { /* outline information for the .dat renderer, which we won't use */ }
else if (operator === "5") { /* outline-if-visible information for the .dat renderer, which we won't use */ }
// and an operation that we might care about
else if (operator === "0") { this.handleComment(operands, line); }
// and a number of operators that are really important.
else if (operator === "1") { this.handleDependency(operands, meshes, line); }
else if (operator === "3") { this.parseTriangle(operands, currentMesh); }
else if (operator === "4") { this.parseQuad(operands, currentMesh); }
},
/**
* Parse a stretch of .dat data, generating a javascript mesh object
*/
parse: function (filename, data) {
this.log(" * parsing " + filename + " data");
var lines = data.split(/\r?\n/);
var meshes = [buildMesh()];
var parser = this;
lines.forEach(function (line) {
parser.parseInstruction(line, meshes);
});
if (meshes[meshes.length - 1].vertices.length === 0) {
meshes.splice(meshes.length - 1, 1);
}
return meshes;
},
/**
* Parse a .dat file, generating a javascript mesh object
*/
parseFile: function (filename) {
var data = getDATFile(filename);
return this.parse(filename, data);
},
/**
* Convert the abstracted mesh data into .obj data
*/
toOBJ: function (meshData, scale) {
// var administration
scale = scale || 0.05;
var nl = "\n";
var empty = [""];
var obj = ["# LDraw .dat conversion for superawesomeness on the web"];
var vertices = ["# vertices:"];
var faces = ["# faces:"];
var offset = 1;
var precision = 6;
// number formatting helper function
var nf = (function (precision) { return function (v) {
var p = Math.pow(10, precision);
return ((v * p) | 0) / p;
}; }(precision));
// conversion function
var convert = function (mesh) {
if (mesh.vertices.length === 0) {
return;
}
// push all vertices
vertices = vertices.concat(["", "# mesh " + mesh.meshId]);
mesh.vertices.forEach(function (v) {
vertices.push("v " + nf(v.x * scale) + " " + nf(v.y * scale) + " " + nf(v.z * scale));
});
// construct face list
faces.push("g mesh" + mesh.meshId);
faces.push("usemtl color" + mesh.vertices[0].color);
faces.push("s 1");
faces.push("");
mesh.faces.forEach(function (f) {
faces.push("f " + (f.v1 + offset) + "// " + (f.v2 + offset) + "// " + (f.v3 + offset) + "//");
});
offset += mesh.vertices.length;
faces.push("");
};
// convert all mes data.
meshData.forEach(convert);
// source format and return
return obj.concat(empty).concat(vertices).concat(empty).concat(faces).join(nl);
}
};
return new Parser();
}());