module yage.resource.objloader;
import std.string;
import std.file;
import std.path;
import std.stdio;
import std.string;
import std.conv;
import yage.core.matrix;
import yage.core.misc;
import yage.core.vector;
import yage.resource.model;
import yage.resource.material;
import yage.resource.mesh;
import yage.resource.resource;
import yage.system.log;
union point{
int[3] array;
struct{
int vertex;
int texcoord;
int normal;
}
Vec3i vec;
}
//alias char[] point
alias char[][] face;
//alias point[3] triangle;
struct MTL{}
struct group{
//MTL mtl;
point[char[]] points;//used to test if point already exists
//char[] == lazy man's point reference
face[] faces; //array of char[] is face, array of faces
//face[] faces;
void addPoint(char[] word){
if(!(word in points)){
point p;
foreach(int index, inout part; split(word, "/")){
if(part.length != 0)
p.array[index] = std.conv.toInt(part);
}
points[word] = p;
}
}
void addFace(char[][] words){
for(int i = 1; i < words.length; i++){
addPoint(words[i]);
}
faces ~= words[1..$];
}
}
struct OBJ{
group[] groups;
Vec3f[] vertices, normals;
Vec2f[] texcoords;
void load(char[] source){
char[] file = cast(char[])read(source);
char[][] lines = splitlines(file);
delete file;
group g;
groups ~= g;
foreach(inout line; lines){
line = strip(line);
handleLine(line);
}
}
//Do not want to make a template or making 2/3 an arg for only two functions
Vec3f fromWords3(char[][] words){
Vec3f vertex;
for(int i; i < 3; i++)
vertex[i] = std.conv.toFloat(words[i+1]);
return vertex;
}
Vec2f fromWords2(char[][] words){
Vec2f vertex;
for(int i; i < 2; i++)
vertex[i] = std.conv.toFloat(words[i+1]);
return vertex;
}
void handleLine(char[] line){
char[][] words = split(line, " ");
switch(words[0]){
//Vertex data
case "v":
//does not support weight
vertices ~= fromWords3(words);
break;
case "vt":
//does not support weight
texcoords ~= fromWords2(words);
break;
case "vn":
normals ~= fromWords3(words);
break;
case "vp":
break;
case "cstype":
break;
case "deg":
break;
case "bmat":
break;
case "step":
break;
//Elements
case "p":
break;
case "l":
break;
case "f":
groups[$-1].addFace(words);
break;
case "curv":
break;
case "curv2":
break;
case "surf":
break;
//Free-form curve/surface body statements
case "parm":
break;
case "trim":
break;
case "hole":
break;
case "scrv":
break;
case "sp":
break;
case "end":
break;
//Connectivity between free-form surfaces
case "con":
break;
//Grouping
case "g":
group g;
groups ~= g;
break;
case "s":
break;
case "mg":
break;
case "o":
break;
//Display/render attributes
case "bevel":
break;
case "c_interp":
break;
case "d_interp":
break;
case "lod":
break;
case "usemtl":
//mesh[$-1].material =
break;
case "mtllib":
//new Material(words[1]);
break;
case "shadow_obj":
break;
case "trace_obj":
break;
case "ctech":
break;
case "stech":
break;
default:
break;
}
}
}
/**
* Create Models from files and other sources.
* This is separated and used as a mixin to reduce the length of model.d.*/
template ObjLoader(){
/**
* Load a model from a Milkshape3D model file.
* All materials, etc. referenced by this model are loaded through the Resource
* manager to avoid duplicates. Meshes without a material are assigned a default material.
* Uploading vertex data to video memory must be done manually by calling Upload(). */
protected void loadObj(char[] filename){
Vec3f[] vertices, normals;
Vec2f[] texcoords;
source = Resource.resolvePath(filename);
char[] path = source[0 .. rfind(source, "/") + 1]; // path stripped of filename
OBJ obj;
obj.load(source);
//Vertices
//vertices.length = ms3d.vertices.length;
//texcoords.length = ms3d.vertices.length;
//normals.length = ms3d.vertices.length;
//for (int v; v<ms3d.vertices.length; v++)
//{ vertices[v].x = ms3d.vertices[v].vertex[0];
// vertices[v].y = ms3d.vertices[v].vertex[1];
// vertices[v].z = ms3d.vertices[v].vertex[2];
//}
foreach(inout g; obj.groups){ //makes new vertices for different normals or tex
foreach(inout p; g.points){
vertices ~= obj.vertices[p.vertex - 1];
p.vertex = vertices.length - 1;
if(p.normal == 0)
normals.length = normals.length + 1;
else{
normals ~= obj.normals[p.normal - 1];
p.normal = normals.length - 1;
}
if(p.texcoord == 0)
texcoords.length = texcoords.length + 1;
else{
texcoords ~= obj.texcoords[p.texcoord -1];
p.texcoord = texcoords.length - 1;
}
}
}
writefln("vertices length is ", vertices.length);
writefln("normals length is ", normals.length);
writefln("texcoords length is ", texcoords.length);
/*
// Meshes
meshes.length = obj.groups.length;
for (int m; m<meshes.length; m++)
*/
foreach(g; obj.groups[1..$]){
meshes ~= new Mesh();
int m = meshes.length - 1;
Vec3i[] triangles = meshes[m].getTriangles();
/*
//triangles.length = ms3d.groups[m].numtriangles;
// // Material
if (ms3d.groups[m].materialIndex != -1)
{ // The filename exists in the system?
char[] name = ms3d.materials[ms3d.groups[m].materialIndex].name;
if (exists(path~name))
meshes[m].setMaterial(path~name);
else // create new material
{ Material matl = new Material();
matl.addLayer(new Layer());
int midx = ms3d.groups[m].materialIndex;
memcpy(&matl.getLayers()[0].ambient, ms3d.materials[midx].ambient.ptr, 16);
memcpy(&matl.getLayers()[0].diffuse, ms3d.materials[midx].diffuse.ptr, 16);
memcpy(&matl.getLayers()[0].specular, ms3d.materials[midx].specular.ptr, 16);
memcpy(&matl.getLayers()[0].emissive, ms3d.materials[midx].emissive.ptr, 16);
matl.getLayers()[0].specularity = ms3d.materials[midx].shininess;
meshes[m].setMaterial(matl);
// Load textures
// Ms3d stores a texture map and an alpha map for every material
char[] texfile = .toString(ms3d.materials[midx].texture.ptr);
if (texfile.length)
{ if (icmp(texfile[0..2], ".\\")==0) // linux fails with .\ in a path.
texfile = texfile[2..length];
meshes[m].getMaterial().getLayers()[0].addTexture(Resource.texture(path ~ texfile));
}
if (ms3d.materials[midx].alphamap[0])
meshes[m].getMaterial().getLayers()[0].addTexture(Resource.texture(path ~ ms3d.materials[midx].alphamap));
}
}
*/
// Triangles
foreach(f; g.faces){
//f is char[][]
//change to use yage.core.array
//this will fan polygons
while(f.length != 2){
writefln(vertices[g.points[f[0]].vertex]);
writefln(vertices[g.points[f[1]].vertex]);
writefln(vertices[g.points[f[2]].vertex]);
writefln();
triangles ~= Vec3i(g.points[f[0]].vertex, g.points[f[1]].vertex, g.points[f[2]].vertex);
for (size_t i = 1; i<f.length - 1; i++)
f[i] = f[i+1];
f.length = f.length - 1;
}
}
writefln("triangles length is ", triangles.length);
/*
for (int t; t<triangles.length; t++)
{
//printf( "%d\n", ms3d.groups[m].triangleIndices[t]);
triangles[t].x = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexIndices[0];
triangles[t].y = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexIndices[1];
triangles[t].z = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexIndices[2];
// Tex coords
texcoords[triangles[t].x].x = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].s[0];
texcoords[triangles[t].y].x = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].s[1];
texcoords[triangles[t].z].x = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].s[2];
texcoords[triangles[t].x].y = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].t[0];
texcoords[triangles[t].y].y = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].t[1];
texcoords[triangles[t].z].y = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].t[2];
// Normals
normals[triangles[t].x].x = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexNormals[0];
normals[triangles[t].x].y = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexNormals[1];
normals[triangles[t].x].z = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexNormals[2];
normals[triangles[t].y].x = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexNormals[3];
normals[triangles[t].y].y = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexNormals[4];
normals[triangles[t].y].z = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexNormals[5];
normals[triangles[t].z].x = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexNormals[6];
normals[triangles[t].z].y = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexNormals[7];
normals[triangles[t].z].z = ms3d.triangles[ms3d.groups[m].triangleIndices[t]].vertexNormals[8];
}
// In the MS3D format, texture coordinate and normal data is stored per triangle, while the vertex
// position is stored per vertex. This allows two triangles that reference the same vertex to have
// different texture and normal coordinates, but is incompatible with the OpenGL vertex format.
// This loop finds such instances, and creates new vertices to correct the problem.
for (int t; t<triangles.length; t++)
{
// Index of the triangle we're dealing with.
int tindex = ms3d.groups[m].triangleIndices[t];
// Triangle index a
if ((texcoords[triangles[t].x].x != ms3d.triangles[tindex].s[0]) ||
(texcoords[triangles[t].x].y != ms3d.triangles[tindex].t[0]))
{
// Duplicate vertex from original and copy new texutre and normal coordinates into it.
vertices.length = texcoords.length = normals.length = vertices.length+1;
memcpy(&vertices[length-1], &vertices[ms3d.triangles[tindex].vertexIndices[0]], 12);
memcpy(&normals[length-1], &ms3d.triangles[tindex].vertexNormals[0], 12);
texcoords[length-1].x = ms3d.triangles[tindex].s[0];
texcoords[length-1].y = ms3d.triangles[tindex].t[0];
// Assign this new vertex to the triangle
triangles[t].x = vertices.length-1;
}
// Triangle index b
if ((texcoords[triangles[t].y].x != ms3d.triangles[tindex].s[1]) ||
(texcoords[triangles[t].y].y != ms3d.triangles[tindex].t[1]))
{
// Duplicate vertex from original and copy new texutre and normal coordinates into it.
vertices.length = texcoords.length = normals.length = vertices.length+1;
memcpy(&vertices[length-1], &vertices[ms3d.triangles[tindex].vertexIndices[1]], 12);
memcpy(&normals[length-1], &ms3d.triangles[tindex].vertexNormals[3], 12);
texcoords[length-1].x = ms3d.triangles[tindex].s[1];
texcoords[length-1].y = ms3d.triangles[tindex].t[1];
// Assign this new vertex to the triangle
triangles[t].y = vertices.length-1;
}
// Triangle index b
if ((texcoords[triangles[t].z].x != ms3d.triangles[tindex].s[2]) ||
(texcoords[triangles[t].z].y != ms3d.triangles[tindex].t[2]))
{
// Duplicate vertex from original and copy new texutre and normal coordinates into it.
vertices.length = texcoords.length = normals.length = vertices.length+1;
memcpy(&vertices[length-1], &vertices[ms3d.triangles[tindex].vertexIndices[2]], 12);
memcpy(&normals[length-1], &ms3d.triangles[tindex].vertexNormals[6], 12);
texcoords[length-1].x = ms3d.triangles[tindex].s[2];
texcoords[length-1].y = ms3d.triangles[tindex].t[2];
// Assign this new vertex to the triangle
triangles[t].z = vertices.length-1;
}
}
// Find meshes with material of -1 (no material) and assign them the default material
if (ms3d.groups[m].materialIndex==-1)
*/
//{ meshes[m].setMaterial(new Material());
// meshes[m].getMaterial().addLayer(new Layer()); // should be init'd to defaults
//}
meshes[m].setTriangles(triangles);
}
/*
delete ms3d.vertices;
delete ms3d.triangles;
delete ms3d.groups;
delete ms3d.materials;
*/
setAttribute("gl_Vertex", vertices);
setAttribute("gl_TexCoord", texcoords);
setAttribute("gl_Normal", normals);
}
}