Skip to content

Instantly share code, notes, and snippets.

@KeyMaster-
Last active Aug 29, 2015
Embed
What would you like to do?
Importer for PLY files from Blender into Luxe
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;
}
}
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