void App::onRender() { // Show message message("Rendering..."); Stopwatch timer; rayTraceImage(1.0f, m_raysPerPixel); timer.after("Trace"); debugPrintf("%f s\n", timer.elapsedTime()); // m_result->toImage3uint8()->save("result.png"); }
void ArticulatedModel::load3DS(const Specification& specification) { // During loading, we make no attempt to optimize the mesh. We leave that until the // Parts have been created. The vertex arrays are therefore much larger than they // need to be. Stopwatch timer; Parse3DS parseData; { BinaryInput bi(specification.filename, G3D_LITTLE_ENDIAN); timer.after(" open file"); parseData.parse(bi); timer.after(" parse"); } name = FilePath::base(specification.filename); const std::string& path = FilePath::parent(specification.filename); /* if (specification.stripMaterials) { stripMaterials(parseData); } if (specification.mergeMeshesByMaterial) { mergeGroupsAndMeshesByMaterial(parseData); }*/ for (int p = 0; p < parseData.objectArray.size(); ++p) { Parse3DS::Object& object = parseData.objectArray[p]; // Create a unique name for this part std::string name = object.name; int count = 0; while (this->part(name) != NULL) { ++count; name = object.name + format("_#%d", count); } // Create the new part // All 3DS parts are promoted to the root in the current implementation. Part* part = addPart(name); // Process geometry part->cpuVertexArray.vertex.resize(object.vertexArray.size()); part->cframe = object.keyframe.approxCoordinateFrame(); debugAssert(isFinite(part->cframe.rotation.determinant())); debugAssert(part->cframe.rotation.isOrthonormal()); if (! part->cframe.rotation.isRightHanded()) { // TODO: how will this impact other code? I think we can't just force it like this -- Morgan part->cframe.rotation.setColumn(0, -part->cframe.rotation.column(0)); } debugAssert(part->cframe.rotation.isRightHanded()); //debugPrintf("%s %d %d\n", object.name.c_str(), object.hierarchyIndex, object.nodeID); if (part->cpuVertexArray.vertex.size() > 0) { // Convert vertices to object space (there is no surface normal data at this point) Matrix4 netXForm = part->cframe.inverse().toMatrix4(); debugAssertM(netXForm.row(3) == Vector4(0,0,0,1), "3DS file loading requires that the last row of the xform matrix be 0, 0, 0, 1"); if (object.texCoordArray.size() > 0) { part->m_hasTexCoord0 = true; part->cpuVertexArray.hasTexCoord0 = true; } const Matrix3& S = netXForm.upper3x3(); const Vector3& T = netXForm.column(3).xyz(); for (int v = 0; v < part->cpuVertexArray.vertex.size(); ++v) { # ifdef G3D_DEBUG { const Vector3& vec = object.vertexArray[v]; debugAssert(vec.isFinite()); } # endif CPUVertexArray::Vertex& vertex = part->cpuVertexArray.vertex[v]; vertex.position = S * object.vertexArray[v] + T; vertex.tangent = Vector4::nan(); vertex.normal = Vector3::nan(); if (part->m_hasTexCoord0) { vertex.texCoord0 = object.texCoordArray[v]; } # ifdef G3D_DEBUG { const Vector3& vec = vertex.position; debugAssert(vec.isFinite()); } # endif } if (object.faceMatArray.size() == 0) { // Merge all geometry into one mesh since there are no materials Mesh* mesh = addMesh("mesh", part); mesh->cpuIndexArray = object.indexArray; debugAssert(mesh->cpuIndexArray.size() % 3 == 0); } else { for (int m = 0; m < object.faceMatArray.size(); ++m) { const Parse3DS::FaceMat& faceMat = object.faceMatArray[m]; if (faceMat.faceIndexArray.size() > 0) { Material::Ref mat; bool twoSided = false; const std::string& materialName = faceMat.materialName; if (parseData.materialNameToIndex.containsKey(materialName)) { int i = parseData.materialNameToIndex[materialName]; const Parse3DS::Material& material = parseData.materialArray[i]; //if (! materialSubstitution.get(material.texture1.filename, mat)) { const Material::Specification& spec = compute3DSMaterial(&material, path, specification); mat = Material::create(spec); //} twoSided = material.twoSided || mat->hasAlphaMask(); } else { mat = Material::create(); logPrintf("Referenced unknown material '%s'\n", materialName.c_str()); } Mesh* mesh = addMesh(materialName, part); debugAssert(isValidHeapPointer(mesh)); mesh->material = mat; mesh->twoSided = twoSided; // Construct an index array for this part for (int i = 0; i < faceMat.faceIndexArray.size(); ++i) { // 3*f is an index into object.indexArray int f = faceMat.faceIndexArray[i]; debugAssert(f >= 0); for (int v = 0; v < 3; ++v) { mesh->cpuIndexArray.append(object.indexArray[3 * f + v]); } } debugAssert(mesh->cpuIndexArray.size() > 0); debugAssert(mesh->cpuIndexArray.size() % 3 == 0); } } // for m } // if has materials } } timer.after(" convert"); }
void ArticulatedModel::initOBJ(const std::string& filename, const Preprocess& preprocess) { Stopwatch loadTimer; TextInput::Settings set; set.cppBlockComments = false; set.cppLineComments = false; set.otherCommentCharacter = '#'; set.generateNewlineTokens = true; // Notes on OBJ file format. See also: // // - http://www.martinreddy.net/gfx/3d/OBJ.spec // - http://en.wikipedia.org/wiki/Obj // - http://www.royriggs.com/obj.html // // OBJ indexing is 1-based. // Line breaks are significant. // The first token on a line indicates the contents of the line. // // Faces contain separate indices for normals and texcoords. // We load the raw vertices and then form our own optimized // gl indices from them. // // Negative indices are relative to the last coordinate seen. // Raw arrays with independent indexing, as imported from the file Array<Vector3> rawVertex; Array<Vector3> rawNormal; Array<Vector2> rawTexCoord; // part.geometry.vertexArray[i] = rawVertex[cookVertex[i]]; Array<int> cookVertex; Array<int> cookNormal; Array<int> cookTexCoord; // Put everything into a single part // Convert to a Part Part& part = partArray.next(); part.cframe = CoordinateFrame(); part.name = "root"; part.parent = -1; // v,t,n repeated for each vertex Array<int> faceTempIndex; // Count of errors from mismatched texcoord and vertex int texCoordChanged = 0; int normalChanged = 0; Table<std::string, Material::Ref> materialLibrary; Table<std::string, TriListSpec*> groupTable; TriListSpec* currentTriList = NULL; int numTris = 0; const Matrix3 normalXform = preprocess.xform.upper3x3().transpose().inverse(); const std::string& basePath = FilePath::parent(FileSystem::resolve(filename)); { TextInput ti(filename, set); while (ti.hasMore()) { // Consume comments/newlines while (ti.hasMore() && (ti.peek().type() == Token::NEWLINE)) { // Consume the newline ti.read(); } if (! ti.hasMore()) { break; } // Process one line const std::string& cmd = ti.readSymbol(); if (cmd == "mtllib") { // Specify material library const std::string& mtlFilename = ti.readUntilNewlineAsString(); loadMTL(FilePath::concat(basePath, mtlFilename), materialLibrary, preprocess); } else if (cmd == "g") { // New trilist const std::string& name = ti.readUntilNewlineAsString(); if (! groupTable.containsKey(name)) { currentTriList = new TriListSpec(); currentTriList->name = name; groupTable.set(name, currentTriList); } else { currentTriList = groupTable[name]; } } else if (cmd == "usemtl") { if (currentTriList) { currentTriList->materialName = ti.readUntilNewlineAsString(); } } else if (cmd == "v") { rawVertex.append(readVertex(ti, preprocess.xform)); } else if (cmd == "vt") { // Texcoord Vector2& t = rawTexCoord.next(); t.x = ti.readNumber(); t.y = 1.0f - ti.readNumber(); } else if (cmd == "vn") { // Normal rawNormal.append(readNormal(ti, normalXform)); } else if ((cmd == "f") && currentTriList) { // Face // Read each vertex while (ti.hasMore() && (ti.peek().type() != Token::NEWLINE)) { // Read one 3-part index int v = ti.readNumber(); if (v < 0) { v = rawVertex.size() + 1 + v; } int n = 0; int t = 0; if (ti.peek().type() == Token::SYMBOL) { ti.readSymbol("/"); if (ti.peek().type() == Token::NUMBER) { t = ti.readNumber(); if (t < 0) { t = rawTexCoord.size() + 1 + t; } } if (ti.peek().type() == Token::SYMBOL) { ti.readSymbol("/"); if (ti.peek().type() == Token::NUMBER) { n = ti.readNumber(); if (n < 0) { n = rawNormal.size() + 1 + n; } } } } // Switch to zero-based indexing --v; --n; --t; faceTempIndex.append(v, t, n); } alwaysAssertM(faceTempIndex.size() >= 3*3, "Face with fewer than three vertices in model."); numTris += (faceTempIndex.size()/3) - 2; // The faceTempIndex is now a triangle fan. Convert it to a triangle list and use unique vertices for (int i = 2; i < faceTempIndex.size()/3; ++i) { // Always start with vertex 0 cookVertex.append(faceTempIndex[0]); cookTexCoord.append(faceTempIndex[1]); cookNormal.append(faceTempIndex[2]); // The vertex just before the one we're adding int j = (i - 1) * 3; cookVertex.append(faceTempIndex[j]); cookTexCoord.append(faceTempIndex[j+1]); cookNormal.append(faceTempIndex[j+2]); // The vertex we're adding j = i * 3; cookVertex.append(faceTempIndex[j]); cookTexCoord.append(faceTempIndex[j+1]); cookNormal.append(faceTempIndex[j+2]); // Update the index array to contain the three vertices we just added currentTriList->cpuIndex.append(cookVertex.size() - 3, cookVertex.size() - 2, cookVertex.size() - 1); } faceTempIndex.fastClear(); } // Read until the end of the line while (ti.hasMore() && (ti.read().type() != Token::NEWLINE)); } } debugPrintf("Creating TriLists\n"); // Copy geometry const int N = cookVertex.size(); part.geometry.vertexArray.resize(N); for (int i = 0; i < N; ++i) { part.geometry.vertexArray[i] = rawVertex[cookVertex[i]]; } // Optional normals if (rawNormal.size() > 0) { part.geometry.normalArray.resize(N); for (int i = 0; i < N; ++i) { part.geometry.normalArray[i] = rawNormal[cookNormal[i]]; } } // Optional texcoords if (rawTexCoord.size() > 0) { part.texCoordArray.resize(N); for (int i = 0; i < N; ++i) { part.texCoordArray[i] = rawTexCoord[cookTexCoord[i]]; } } // Create trilists for (Table<std::string, TriListSpec*>::Iterator it = groupTable.begin(); it.hasMore(); ++it) { TriListSpec* s = it->value; Material::Ref material; if (materialLibrary.containsKey(s->materialName)) { material = materialLibrary[s->materialName]; } else { material = Material::createDiffuse(Color3::white() * 0.8f); debugPrintf("Warning: unrecognized material: %s\n", s->materialName.c_str()); } Part::TriList::Ref triList = part.newTriList(material); triList->twoSided = false; triList->indexArray = s->cpuIndex; } groupTable.deleteValues(); groupTable.clear(); debugPrintf("Done loading. %d vertices, %d faces, %d frames\n\n", cookVertex.size(), numTris, N); loadTimer.after("Loading"); }