gltut / Meshes / ConvCollada.lua

  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
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
local realPrint = print;
--package.cpath = package.cpath..";./?.dll;./?.so;../lib/?.so;../lib/vc_dll/?.dll;../lib/bcc_dll/?.dll;../lib/mingw_dll/?.dll;"
require("wx")
print = realPrint;

require "XmlWriter"
require "vmath"
require "lfs"

--[[
Command line format:

lua ConvCollada.lua <inputFile> <meshName> [options]

* <inputFile>: the Collada file to process.
* <meshName>: the name of the mesh in the Collada file to process.

Options:

* -mapfile <mapFileName>		: Specifies how semantic names map to attribute indices
* -mapstd						: Equivalent to -mapfile <ConvCollada.lua dir>stdmap.txt
* -map <semanticName>=<number> ...	:Specifies how semantic names map to attribute indices.
* -vao <vaoName> <number> <number> ...	:Specifies a named VAO, with attribute indices.
* -o <outputFilename>

You must provide one of the -map fields. You do not have to provide a -vao or -o. The default
filename is the <inputFile>.xml.
]]

--Utility iterator over all child XML nodes. Nodes of ANY type.
local function ixmlnodes(parNode)
	local currNode = parNode:GetChildren();
	return function()
		local ret = currNode;
		if(currNode) then currNode = currNode:GetNext() end
		return ret;
	end
end

--Iterator over all child XML elements. ONLY elements.
local function ixmlelements(parNode, nodeName)
	local currNode = parNode:GetChildren();
	if(nodeName) then
		while(currNode and
			not (currNode:GetType() == wx.wxXML_ELEMENT_NODE and
			currNode:GetName() == nodeName)) do
			
			currNode = currNode:GetNext();
		end
		
		return function()
			local ret = currNode;
			if(currNode) then
				repeat
					currNode = currNode:GetNext();
				until((not currNode) or
					(currNode:GetType() == wx.wxXML_ELEMENT_NODE and
					currNode:GetName() == nodeName))
			end
			return ret;
		end
	else
		while(currNode and
			currNode:GetType() ~= wx.wxXML_ELEMENT_NODE) do
			
			currNode = currNode:GetNext();
		end
		
		return function()
			local ret = currNode;
			if(currNode) then
				repeat
					currNode = currNode:GetNext();
				until((not currNode) or currNode:GetType() == wx.wxXML_ELEMENT_NODE)
			end
			return ret;
		end
	end
end

local function findXmlChild(parNode, nodeName)
	for xChild in ixmlelements(parNode, nodeName) do
		return xChild;
	end
	
	return nil;
end

local function popOne(first, ...)
	return ...
end

local function popTwo(first, second, ...)
	return ...
end


local inputFile, meshName = ...;
assert(inputFile, "You must provide an input file.");
assert(meshName, "You must provide a mesh name to export.");

local optionProcs = {}

function optionProcs.mapfile(options, optionList, iCurrOption)
	assert(optionList[iCurrOption] and optionList[iCurrOption]:sub(1, 1) ~= "-",
		"You must specify a mapping file name.");
		
	local filename = optionList[iCurrOption];
		
	local hFile = assert(io.open(filename, "r"),
		"Could not open the mapping file \"" .. filename .. "\".");
	
	for line in hFile:lines() do
		local semantic, number = line:match("([^%=]+)%=(%d+)");
		if(semantic) then
			options.map[semantic] = number;
		end
	end
	
	hFile:close();
	
	return 1;
end

function optionProcs.mapstd(options, optionList, iCurrOption)
	local fnameLoc = arg[0]:match([[()[^%\%/]+$]]) - 1;
	local thisDir = arg[0]:sub(1, fnameLoc)
	optionProcs.mapfile(options, {thisDir .. "stdmap.txt"}, 1);
	return 0;
end

function optionProcs.map(options, optionList, iCurrOption)
	assert(optionList[iCurrOption] and optionList[iCurrOption]:sub(1, 1) ~= "-",
		"You must specify at least one mapping.");
		
	local iProcCount = 0;
	while(optionList[iCurrOption] and optionList[iCurrOption]:sub(1, 1) ~= "-") do
		local currOpt = optionList[iCurrOption];
		local semantic, number = currOpt:match("([^%=]+)%=(%d+)");
		
		assert(semantic, "Semantic mapping must be specified as SEMANTIC=attrib.");
		
		options.map[semantic] = number;
		iCurrOption = iCurrOption + 1;
		iProcCount = iProcCount + 1;
	end
	
	return iProcCount;
end

function optionProcs.vao(options, optionList, iCurrOption)
	assert(optionList[iCurrOption] and optionList[iCurrOption]:sub(1, 1) ~= "-",
		"You must specify a VAO name.");
	local vao = {};
	local vaoName = optionList[iCurrOption];
	assert(not options.vaos[vaoName], "The VAO named \"" .. vaoName .. "\" has already been specified.");
	iCurrOption = iCurrOption + 1;

	assert(optionList[iCurrOption] and optionList[iCurrOption]:sub(1, 1) ~= "-",
		"You must specify at least one VAO attribute.");

	local iProcCount = 1;
	while(optionList[iCurrOption] and optionList[iCurrOption]:sub(1, 1) ~= "-") do
		assert(tonumber(optionList[iCurrOption]),
			"The VAO attribute must be a valid attribute index.");
			
		vao[#vao + 1] = optionList[iCurrOption];
		iCurrOption = iCurrOption + 1;
		iProcCount = iProcCount + 1;
	end
	
	options.vaos[vaoName] = vao;
	
	return iProcCount;
end

function optionProcs.o(options, optionList, iCurrOption)
	options.outputFile = optionList[iCurrOption];
	return 1;
end

local function ParseOptions(optionList)
	local options =
	{
		map = {},
		outputFile = inputFile:gsub("%.[^%.]+$", ".xml"),
		vaos = {},
	}
	
	local iCurrOption = 1;
	while(iCurrOption <= #optionList) do
		local currOpt = optionList[iCurrOption];
		assert(currOpt:sub(1, 1) == "-",
			"Malformed option list starting at \"" .. currOpt .. "\"");
		
		currOpt = currOpt:sub(2, -1);
		assert(optionProcs[currOpt], "Unknown option called \"" .. currOpt .. "\"");
		
		local iNumSkip = optionProcs[currOpt](options, optionList, iCurrOption + 1);
		iCurrOption = iCurrOption + 1 + iNumSkip;
	end

	return options;
end


local options = ParseOptions({popTwo(...)});

local reverseMap = {}
local bFoundMap = false;
for semantic, number in pairs(options.map) do
	reverseMap[number] = semantic;
	bFoundMap = true;
end

assert(bFoundMap, "No -map, -mapstd, or -mapfile option was used. Cannot export a mesh.");

--Check to see if any VAOs talk about non-existent attributes.
for vaoName, vao in pairs(options.vaos) do
	for j, source in ipairs(vao) do
		assert(reverseMap[source],
			"The VAO \"" .. vaoName .. "\" revers to the attribute " .. source .. " which is not in the map.");
	end
end


local colladaDoc, test = wx.wxXmlDocument(inputFile);
assert(colladaDoc, "Could not load XML file \"" .. inputFile .. "\"");
print(inputFile);
assert(colladaDoc:IsOk(), "Could not load XML file \"" .. inputFile .. "\"");

local xColladaElem = colladaDoc:GetRoot();

--Find the library_geometries node.
local xGeometries = nil;
for xChild in ixmlelements(xColladaElem) do
	if(xChild:GetName() == "library_geometries") then
		xGeometries = xChild;
		break;
	end
end

assert(xGeometries, "There are no meshes in the COLLADA file.");

--Find the 'geometry' node with the name designated for export.
local xTargetGeom = nil;
for xChild in ixmlelements(xGeometries, "geometry") do
	if(xChild:GetPropVal("id", "") == meshName) then
		xTargetGeom = xChild;
		break;
	end
end

assert(xTargetGeom, "Could not find the geometry with id '" .. meshName .. "' in the document.");

local xMeshToWrite = nil;
for xChild in ixmlelements(xTargetGeom, "mesh") do
	assert(xMeshToWrite == nil, "Multiple meshes in a geometry are not yet supported.");
	xMeshToWrite = xChild;
end

assert(xMeshToWrite, "Could not find a mesh within the geometry with id '" .. meshName .. "'");

--Collate all of the sources in the mesh.
local sources = {};
local bFound = false;
for xChild in ixmlelements(xMeshToWrite, "source") do
	local bHasId, id = xChild:GetPropVal("id");
	assert(bHasId, "Malformed COLLADA. No 'id' attribute on 'source' element.");
	sources["#" .. id] = xChild;
	bFound = true;
end

assert(bFound, "Could not find any 'source' elements in the '" .. meshName .. "'.");

--Get any vertex remappings.
local vertexRemap = {};
local vertexRemapId = nil;
for xChild in ixmlelements(xMeshToWrite, "vertices") do
	local bHasId, id = xChild:GetPropVal("id");
	assert(bHasId, "Malformed COLLADA. No 'id' attribute on 'vertices' element.");
	vertexRemapId = "#" .. id;
	local bFound = false;
	for xInput in ixmlelements(xChild, "input") do
		assert(not bFound, "Cannot process multiple 'vertices' 'input' elements yet.");
		
		local bHasId, semantic = xInput:GetPropVal("semantic");
		assert(bHasId, "An 'input' element on 'vertices' doesn't have a semantic.");
		local bHasId, source = xInput:GetPropVal("source");
		assert(bHasId, "An 'input' element on 'vertices' doesn't have a source.");
		vertexRemap[semantic] = source;
		bFound = true;
	end
end

--Get the triangle data.
local triangleData = {};
for xChild in ixmlelements(xMeshToWrite, "polylist") do
	triangleData.xObj = xChild;
	triangleData.xIndices = findXmlChild(xChild, "p"):GetChildren();
	triangleData.inputs = {};
	triangleData.numInputs = 0;
	for xInput in ixmlelements(xChild, "input") do
		local bHasId, semantic = xInput:GetPropVal("semantic");
		assert(bHasId, "An 'input' element on 'polylist' doesn't have a semantic.");
		local bHasId, source = xInput:GetPropVal("source");
		assert(bHasId, "An 'input' element on 'polylist' doesn't have a source.");
		triangleData.inputs[semantic] = source;
		
		triangleData.numInputs = triangleData.numInputs + 1;
	end
end

--Our VAO mapping has semantics. Create a mapping between triangle data and 
-- actual attributes.
local outputMap = {};
local outputIndexOffset = {}
for semantic, source in pairs(triangleData.inputs) do
	if(options.map[semantic]) then
		outputMap[semantic] = source;
		if(source == vertexRemapId) then
			for vSemantic, vSource in pairs(vertexRemap) do
				outputMap[semantic] = vSource;
				break;
			end
		end

		for xInput in ixmlelements(triangleData.xObj, "input") do
			if(semantic == xInput:GetPropVal("semantic", "")) then
				outputIndexOffset[semantic] = tonumber(xInput:GetPropVal("offset", ""));
			end
		end
	else
		print("Warning: semantic '" .. semantic ..
			"' does not have a map entry. It will not be written to the file.");
	end
end

--[[
Retrieves the basic data type and size of an attribute from the COLLADA doc.
Returns, in order:
* the MeshFormat "type"
* the MeshFormat "size", as an integer
* a Lua pattern string that returns a value. Suitable for use with string.gmatch
* the XML text node containing the array.
]]
local function ParseDataType(xSource)
	local typeMap = 
	{
		float_array = "float",
	}

	local retType = nil;
	local valueElem = nil;
	for colladaType, meshType in pairs(typeMap) do
		valueElem = findXmlChild(xSource, colladaType);
		if(valueElem) then
			retType = meshType;
			break;
		end
	end
	
	assert(retType, "Could not find the type for the 'source' named '" ..
		xSource:GetPropVal("id", "") .. "'.");
	
	local xTech = assert(findXmlChild(xSource, "technique_common"),
		"Malformed COLLADA: Missing 'technique_common' descriptor on 'source'.");

	local xAccess = assert(findXmlChild(xTech, "accessor"),
		"Malformed COLLADA: Missing 'technique_common' descriptor on 'source'.");

	local bHasProp, stride = xAccess:GetPropVal("stride");
	assert(bHasProp, "Malformed COLLADA: missing 'stride' on 'accessor'.");
	
	stride = tonumber(stride);
	
	return retType, stride, "([%+%-]?[%d%.]+e?[%+%-]?[%d%.]*)", valueElem:GetChildren();
end

local function WriteTextForArray(writer, valueArray, stride, indexList, indexOffset)
	writer:AddText("\n");
	
	for i=1, #indexList, triangleData.numInputs do
		writer:AddText("\t\t")
		local indexIx = i + indexOffset;
		local index = indexList[indexIx];
		index = index * stride;
		for j=1, stride do
			index = index + 1;	--One-base index.
			writer:AddText(valueArray[index], " ");
		end
		writer:AddText("\n");
	end
end

--Load the indices into Lua
local indexList = {}
local revIndices = {}
for index in triangleData.xIndices:GetContent():gmatch("(%d+)") do
	revIndices[#revIndices + 1] = tonumber(index);	--zero-base index
	
	--Reverse the winding order.
	if(#revIndices == triangleData.numInputs * 3) then
		for vertex=3, 1, -1 do
			for input=1, triangleData.numInputs do
				local index = (vertex-1) * triangleData.numInputs + input
				indexList[#indexList + 1] = revIndices[index];
			end
		end
	
		revIndices = {};
	end
end

assert(#revIndices == 0);

--Write the mesh.
local writer = XmlWriter.XmlWriter(options.outputFile);
writer:AddPI("oxygen", [[RNGSchema="../../Documents/meshFormat.rnc" type="compact"]]);
writer:PushElement("mesh", "http://www.arcsynthesis.com/gltut/mesh");

--Write the attributes, in order of their indices.
for semantic, source in pairs(outputMap) do
	writer:PushElement("attribute");
		writer:AddAttribute("index", tostring(options.map[semantic]));
		
		local meshType, stride, pttrn, valueText = ParseDataType(sources[source]);
		
		writer:AddAttribute("type", meshType);
		writer:AddAttribute("size", tostring(stride));
		
		local valueArray = {};
		local numberArray = {};
		for value in valueText:GetContent():gmatch(pttrn) do
			valueArray[#valueArray + 1] = value;
			numberArray[#numberArray + 1] = tostring(tonumber(value));
		end

		WriteTextForArray(writer, valueArray, stride, indexList, outputIndexOffset[semantic]);
	writer:PopElement();
end

--Write the VAOs
for vaoName, vao in pairs(options.vaos) do
	writer:PushElement("vao");
		writer:AddAttribute("name", vaoName);
		for i, attrib in ipairs(vao) do
			writer:PushElement("source");
				writer:AddAttribute("attrib", tostring(attrib));
			writer:PopElement();
		end
	writer:PopElement();
end

--Write the rendering command(s)
	writer:PushElement("arrays");
		writer:AddAttribute("cmd", "triangles");
		writer:AddAttribute("start", "0");
		local numTris = tonumber(triangleData.xObj:GetPropVal("count", ""));
		writer:AddAttribute("count", tostring(numTris * 3));
	writer:PopElement();

writer:PopElement();
writer:Close();
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.