Last active
August 29, 2015 14:08
-
-
Save KeyMaster-/247fee525cf73d086dc3 to your computer and use it in GitHub Desktop.
Importer for PLY files from Blender into Luxe
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package; | |
import luxe.Mesh; | |
import haxe.io.StringInput; | |
import luxe.options.MeshOptions; | |
import luxe.Vector; | |
import phoenix.Batcher; | |
import phoenix.geometry.Geometry; | |
import PlyParser; | |
import phoenix.geometry.Vertex; | |
/** | |
* Imports a Blender-exported .ply file | |
*/ | |
class PlyImporter { | |
//Usage: var m:Mesh = PlyImporter.createMesh('path/to/file.ply', {...}); | |
public static function createMesh(id:String, meshOptions:MeshOptions):Mesh { | |
if (meshOptions == null) { | |
throw "The importer requires non-null options at the moment"; | |
} | |
var fileString:String = Luxe.loadText(id).text; | |
var input = new StringInput(fileString); | |
var parser = new PlyParser(input); | |
parser.read(); | |
var vertexElement:Element = parser.elements.get("vertex"); | |
var faceElement:Element = parser.elements.get("face"); | |
if (vertexElement == null) throw "The .ply file is missing a \'vertex\' element"; | |
if (faceElement == null) throw "The .ply file is missing a \'face\' element"; | |
//If the vertex element has a property named 'red', assume that the vertex includes vertex color data | |
var hasVertexColor:Bool = Lambda.has(vertexElement.orderedPropNames, "red"); | |
//If the vertex element has a property named 's', assume that it has the 's' and 't' properties for UV coordinates | |
var hasUVs:Bool = Lambda.has(vertexElement.orderedPropNames, "s"); | |
//Extract all vertices | |
var vertices:Array<Vertex> = new Array<Vertex>(); | |
var v:Vertex; | |
for (vertex in parser.data.get(vertexElement)) { | |
v = new Vertex(new Vector()); | |
v.pos.x = vertex.x; | |
v.pos.y = vertex.y; | |
v.pos.z = vertex.z; | |
v.normal.x = vertex.nx; | |
v.normal.y = vertex.ny; | |
v.normal.z = vertex.nz; | |
if (hasVertexColor) { | |
v.color.r = vertex.red / 255; | |
v.color.g = vertex.green / 255; | |
v.color.b = vertex.blue / 255; | |
} | |
if (hasUVs) { | |
v.uv.uv0.u = vertex.s; | |
v.uv.uv0.v = 1 - vertex.t; | |
} | |
vertices.push(v); | |
} | |
//Extract faces to lists of Ints | |
var faces = new Array<Array<Int>>(); | |
for (face in parser.data.get(faceElement)) { | |
var array = new Array<Int>(); | |
for (i in 0...face.vertex_indices.data.length) { | |
array.push(Std.int(face.vertex_indices.data[i])); | |
} | |
faces.push(array); | |
} | |
var batcher = meshOptions.batcher == null ? Luxe.renderer.batcher : meshOptions.batcher; | |
var geom:Geometry = new Geometry( { | |
batcher: batcher, | |
texture:meshOptions.texture, | |
primitive_type:PrimitiveType.triangles | |
} ); | |
for (face in faces) { | |
for (index in face) { | |
geom.add(vertices[index]); | |
} | |
} | |
meshOptions.geometry = geom; | |
var mesh = new Mesh(meshOptions); | |
return mesh; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import haxe.ds.ObjectMap; | |
import haxe.ds.StringMap; | |
import haxe.io.Input; | |
import luxe.Color; | |
import luxe.Mesh; | |
import luxe.Vector; | |
import luxe.Visual; | |
import phoenix.Batcher; | |
import phoenix.geometry.Geometry; | |
import phoenix.geometry.Vertex; | |
class PlyParser { | |
var i:Input; | |
//All the elements in the file | |
public var elements:StringMap<Element>; | |
//List of element names as they are listed in the file | |
public var orderedElementNames:Array<String>; | |
public var data:ObjectMap<Element, Array<Dynamic>>; | |
public function new(input:Input) { | |
i = input; | |
i.bigEndian = true; | |
} | |
public function read():Void { | |
elements = new StringMap<Element>(); | |
orderedElementNames = new Array<String>(); | |
data = new ObjectMap<Element, Array<Dynamic>>(); | |
var fileString:String = i.readAll().toString(); | |
var lines:Array<String> = fileString.split('\n'); | |
parse(lines); | |
} | |
function parse(lines:Array<String>):Void { | |
var headerEnd:Int = lines.indexOf("end_header"); | |
if (headerEnd != -1) { | |
parseHeader(lines.slice(0, headerEnd)); | |
} | |
else { | |
throw "\'end_header\' could not be found!"; | |
} | |
parseData(lines.slice(headerEnd + 1)); | |
} | |
/** | |
* Fills the 'elements' array with parsed element data | |
* @param lines An array of lines making up the header | |
*/ | |
function parseHeader(lines:Array<String>):Void { | |
var lineParts:Array<String>; | |
var type:String; | |
var curElement:Element = { | |
name:"", | |
count:0, | |
orderedPropNames:null, | |
propertyTypes:null | |
} | |
for (line in lines) { | |
line = StringTools.trim(line); | |
lineParts = line.split(' '); | |
type = lineParts[0]; | |
switch(type) { | |
case "element": | |
curElement = { | |
name:lineParts[1], | |
count:Std.parseInt(lineParts[2]), | |
orderedPropNames:new Array<String>(), | |
propertyTypes:new StringMap<PlyType>() | |
}; | |
elements.set(curElement.name, curElement); | |
orderedElementNames.push(curElement.name); | |
case "property": | |
//Type is scalar (any variation of ints, chars, floats) | |
if (isScalarType(lineParts[1])) { | |
curElement.propertyTypes.set(lineParts[2], parseScalarType(lineParts[1])); | |
curElement.orderedPropNames.push(lineParts[2]); | |
} | |
//Type is list, handle specifically | |
else { | |
curElement.propertyTypes.set(lineParts[4], PlyType.list(parseScalarType(lineParts[3]))); | |
curElement.orderedPropNames.push(lineParts[4]); | |
} | |
} | |
} | |
} | |
/** | |
* Parses the data in the file according to the elements parsed beforehands | |
* @param lines An array of lines making up the data | |
*/ | |
function parseData(lines:Array<String>):Void { | |
var elementLines:Array<String>; | |
var startLinePos:Int = 0; | |
var element:Element; | |
for (elementName in orderedElementNames) { | |
element = elements.get(elementName); | |
parseElementData(element, lines.slice(startLinePos, startLinePos + element.count)); | |
startLinePos += element.count; | |
} | |
} | |
/** | |
* Parses all the data of one element | |
* @param element The element the data belongs to | |
* @param lines An array of lines containing all the data belonging to this element | |
*/ | |
function parseElementData(element:Element, lines:Array<String>):Void { | |
var elementData:Array<Dynamic> = new Array<Dynamic>(); | |
data.set(element, elementData); | |
var valueIndex:Int = 0; | |
var lineParts:Array<String>; | |
for (line in lines) { | |
lineParts = line.split(' '); | |
var dataSet = { }; | |
valueIndex = 0; | |
var type:PlyType; | |
for (propertyName in element.orderedPropNames) { | |
type = element.propertyTypes.get(propertyName); | |
switch(type) { | |
case PlyType.list(valueType): | |
var listData:ListData = { | |
count:Std.parseInt(lineParts[valueIndex]), | |
data:new Array<Dynamic>() | |
}; | |
valueIndex++; | |
switch(valueType) { | |
case PlyType.int: | |
for (n in valueIndex...valueIndex + listData.count) listData.data.push(Std.parseInt(lineParts[n])); | |
case PlyType.float: | |
for (n in valueIndex...valueIndex + listData.count) listData.data.push(Std.parseFloat(lineParts[n])); | |
case PlyType.list: | |
throw "The value type of a list seems to be another list. This is not handled"; | |
} | |
valueIndex += listData.count; | |
Reflect.setField(dataSet, propertyName, listData); | |
case PlyType.int: | |
Reflect.setField(dataSet, propertyName, Std.parseInt(lineParts[valueIndex])); | |
valueIndex++; | |
case PlyType.float: | |
Reflect.setField(dataSet, propertyName, Std.parseFloat(lineParts[valueIndex])); | |
valueIndex++; | |
} | |
} | |
elementData.push(dataSet); | |
} | |
} | |
function isScalarType(s:String):Bool { | |
switch(s) { | |
case "int", "uint", "int8", "uint8", "int16", "int32", "uint32", | |
"char", "uchar", "short", "ushort", | |
"float", "float32", "float64", "double" : | |
return true; | |
case "list": | |
return false; | |
} | |
throw "Type " + s + " not recognized"; | |
} | |
function parseScalarType(s:String):PlyType { | |
var t:PlyType = PlyType.float; | |
switch(s) { | |
case "int", "uint", "int8", "uint8", "int16", "int32", "uint32", | |
"char", "uchar", "short", "ushort" : | |
t = PlyType.int; | |
case "float", "float32", "float64", "double": | |
t = PlyType.float; | |
} | |
return t; | |
} | |
} | |
typedef Element = { | |
var name:String; | |
var count:Int; //Number of lines specifying data of this element | |
var orderedPropNames:Array<String>; //The names of the properties as they appear in the file | |
var propertyTypes:StringMap<PlyType>; //What type of data the element properties are | |
} | |
typedef ListData = { | |
var count:Int; //Number of data elements | |
var data:Array<Dynamic>; | |
} | |
//Specifies what Haxe data type the PLY type should be parsed to | |
enum PlyType { | |
float; | |
int; | |
list(valueType:PlyType); //List also specifies the type of data of the elements of the list | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment