|
- /*
- ==============================================================================
-
- This file is part of the JUCE examples.
- Copyright (c) 2022 - Raw Material Software Limited
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. Permission
- To use, copy, modify, and/or distribute this software for any purpose with or
- without fee is hereby granted provided that the above copyright notice and
- this permission notice appear in all copies.
-
- THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
- WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
- PURPOSE, ARE DISCLAIMED.
-
- ==============================================================================
- */
-
- #pragma once
-
- #include <map>
-
- //==============================================================================
- /**
- This is a quick-and-dirty parser for the 3D OBJ file format.
-
- Just call load() and if there aren't any errors, the 'shapes' array should
- be filled with all the shape objects that were loaded from the file.
- */
- class WavefrontObjFile
- {
- public:
- WavefrontObjFile() {}
-
- Result load (const String& objFileContent)
- {
- shapes.clear();
- return parseObjFile (StringArray::fromLines (objFileContent));
- }
-
- Result load (const File& file)
- {
- sourceFile = file;
- return load (file.loadFileAsString());
- }
-
- //==============================================================================
- typedef juce::uint32 Index;
-
- struct Vertex { float x, y, z; };
- struct TextureCoord { float x, y; };
-
- struct Mesh
- {
- Array<Vertex> vertices, normals;
- Array<TextureCoord> textureCoords;
- Array<Index> indices;
- };
-
- struct Material
- {
- Material() noexcept
- {
- zerostruct (ambient);
- zerostruct (diffuse);
- zerostruct (specular);
- zerostruct (transmittance);
- zerostruct (emission);
- }
-
- String name;
-
- Vertex ambient, diffuse, specular, transmittance, emission;
- float shininess = 1.0f, refractiveIndex = 0.0f;
-
- String ambientTextureName, diffuseTextureName,
- specularTextureName, normalTextureName;
-
- StringPairArray parameters;
- };
-
- struct Shape
- {
- String name;
- Mesh mesh;
- Material material;
- };
-
- OwnedArray<Shape> shapes;
-
- private:
- //==============================================================================
- File sourceFile;
-
- struct TripleIndex
- {
- TripleIndex() noexcept {}
-
- bool operator< (const TripleIndex& other) const noexcept
- {
- if (this == &other)
- return false;
-
- if (vertexIndex != other.vertexIndex)
- return vertexIndex < other.vertexIndex;
-
- if (textureIndex != other.textureIndex)
- return textureIndex < other.textureIndex;
-
- return normalIndex < other.normalIndex;
- }
-
- int vertexIndex = -1, textureIndex = -1, normalIndex = -1;
- };
-
- struct IndexMap
- {
- std::map<TripleIndex, Index> map;
-
- Index getIndexFor (TripleIndex i, Mesh& newMesh, const Mesh& srcMesh)
- {
- const std::map<TripleIndex, Index>::iterator it (map.find (i));
-
- if (it != map.end())
- return it->second;
-
- auto index = (Index) newMesh.vertices.size();
-
- if (isPositiveAndBelow (i.vertexIndex, srcMesh.vertices.size()))
- newMesh.vertices.add (srcMesh.vertices.getReference (i.vertexIndex));
-
- if (isPositiveAndBelow (i.normalIndex, srcMesh.normals.size()))
- newMesh.normals.add (srcMesh.normals.getReference (i.normalIndex));
-
- if (isPositiveAndBelow (i.textureIndex, srcMesh.textureCoords.size()))
- newMesh.textureCoords.add (srcMesh.textureCoords.getReference (i.textureIndex));
-
- map[i] = index;
- return index;
- }
- };
-
- static float parseFloat (String::CharPointerType& t)
- {
- t.incrementToEndOfWhitespace();
- return (float) CharacterFunctions::readDoubleValue (t);
- }
-
- static Vertex parseVertex (String::CharPointerType t)
- {
- Vertex v;
- v.x = parseFloat (t);
- v.y = parseFloat (t);
- v.z = parseFloat (t);
- return v;
- }
-
- static TextureCoord parseTextureCoord (String::CharPointerType t)
- {
- TextureCoord tc;
- tc.x = parseFloat (t);
- tc.y = parseFloat (t);
- return tc;
- }
-
- static bool matchToken (String::CharPointerType& t, const char* token)
- {
- auto len = (int) strlen (token);
-
- if (CharacterFunctions::compareUpTo (CharPointer_ASCII (token), t, len) == 0)
- {
- auto end = t + len;
-
- if (end.isEmpty() || end.isWhitespace())
- {
- t = end.findEndOfWhitespace();
- return true;
- }
- }
-
- return false;
- }
-
- struct Face
- {
- Face (String::CharPointerType t)
- {
- while (! t.isEmpty())
- triples.add (parseTriple (t));
- }
-
- Array<TripleIndex> triples;
-
- void addIndices (Mesh& newMesh, const Mesh& srcMesh, IndexMap& indexMap)
- {
- TripleIndex i0 (triples[0]), i1, i2 (triples[1]);
-
- for (auto i = 2; i < triples.size(); ++i)
- {
- i1 = i2;
- i2 = triples.getReference (i);
-
- newMesh.indices.add (indexMap.getIndexFor (i0, newMesh, srcMesh));
- newMesh.indices.add (indexMap.getIndexFor (i1, newMesh, srcMesh));
- newMesh.indices.add (indexMap.getIndexFor (i2, newMesh, srcMesh));
- }
- }
-
- static TripleIndex parseTriple (String::CharPointerType& t)
- {
- TripleIndex i;
-
- t.incrementToEndOfWhitespace();
- i.vertexIndex = t.getIntValue32() - 1;
- t = findEndOfFaceToken (t);
-
- if (t.isEmpty() || t.getAndAdvance() != '/')
- return i;
-
- if (*t == '/')
- {
- ++t;
- }
- else
- {
- i.textureIndex = t.getIntValue32() - 1;
- t = findEndOfFaceToken (t);
-
- if (t.isEmpty() || t.getAndAdvance() != '/')
- return i;
- }
-
- i.normalIndex = t.getIntValue32() - 1;
- t = findEndOfFaceToken (t);
- return i;
- }
-
- static String::CharPointerType findEndOfFaceToken (String::CharPointerType t) noexcept
- {
- return CharacterFunctions::findEndOfToken (t, CharPointer_ASCII ("/ \t"), String().getCharPointer());
- }
- };
-
- static Shape* parseFaceGroup (const Mesh& srcMesh,
- Array<Face>& faceGroup,
- const Material& material,
- const String& name)
- {
- if (faceGroup.size() == 0)
- return nullptr;
-
- std::unique_ptr<Shape> shape (new Shape());
- shape->name = name;
- shape->material = material;
-
- IndexMap indexMap;
-
- for (auto& f : faceGroup)
- f.addIndices (shape->mesh, srcMesh, indexMap);
-
- return shape.release();
- }
-
- Result parseObjFile (const StringArray& lines)
- {
- Mesh mesh;
- Array<Face> faceGroup;
-
- Array<Material> knownMaterials;
- Material lastMaterial;
- String lastName;
-
- for (auto lineNum = 0; lineNum < lines.size(); ++lineNum)
- {
- auto l = lines[lineNum].getCharPointer().findEndOfWhitespace();
-
- if (matchToken (l, "v")) { mesh.vertices .add (parseVertex (l)); continue; }
- if (matchToken (l, "vn")) { mesh.normals .add (parseVertex (l)); continue; }
- if (matchToken (l, "vt")) { mesh.textureCoords.add (parseTextureCoord (l)); continue; }
- if (matchToken (l, "f")) { faceGroup .add (Face (l)); continue; }
-
- if (matchToken (l, "usemtl"))
- {
- auto name = String (l).trim();
-
- for (auto i = knownMaterials.size(); --i >= 0;)
- {
- if (knownMaterials.getReference (i).name == name)
- {
- lastMaterial = knownMaterials.getReference (i);
- break;
- }
- }
-
- continue;
- }
-
- if (matchToken (l, "mtllib"))
- {
- auto r = parseMaterial (knownMaterials, String (l).trim());
- continue;
- }
-
- if (matchToken (l, "g") || matchToken (l, "o"))
- {
- if (auto* shape = parseFaceGroup (mesh, faceGroup, lastMaterial, lastName))
- shapes.add (shape);
-
- faceGroup.clear();
- lastName = StringArray::fromTokens (l, " \t", "")[0];
- continue;
- }
- }
-
- if (auto* shape = parseFaceGroup (mesh, faceGroup, lastMaterial, lastName))
- shapes.add (shape);
-
- return Result::ok();
- }
-
- Result parseMaterial (Array<Material>& materials, const String& filename)
- {
- jassert (sourceFile.exists());
- auto f = sourceFile.getSiblingFile (filename);
-
- if (! f.exists())
- return Result::fail ("Cannot open file: " + filename);
-
- auto lines = StringArray::fromLines (f.loadFileAsString());
-
- materials.clear();
- Material material;
-
- for (auto line : lines)
- {
- auto l = line.getCharPointer().findEndOfWhitespace();
-
- if (matchToken (l, "newmtl")) { materials.add (material); material.name = String (l).trim(); continue; }
-
- if (matchToken (l, "Ka")) { material.ambient = parseVertex (l); continue; }
- if (matchToken (l, "Kd")) { material.diffuse = parseVertex (l); continue; }
- if (matchToken (l, "Ks")) { material.specular = parseVertex (l); continue; }
- if (matchToken (l, "Kt")) { material.transmittance = parseVertex (l); continue; }
- if (matchToken (l, "Ke")) { material.emission = parseVertex (l); continue; }
- if (matchToken (l, "Ni")) { material.refractiveIndex = parseFloat (l); continue; }
- if (matchToken (l, "Ns")) { material.shininess = parseFloat (l); continue; }
-
- if (matchToken (l, "map_Ka")) { material.ambientTextureName = String (l).trim(); continue; }
- if (matchToken (l, "map_Kd")) { material.diffuseTextureName = String (l).trim(); continue; }
- if (matchToken (l, "map_Ks")) { material.specularTextureName = String (l).trim(); continue; }
- if (matchToken (l, "map_Ns")) { material.normalTextureName = String (l).trim(); continue; }
-
- auto tokens = StringArray::fromTokens (l, " \t", "");
-
- if (tokens.size() >= 2)
- material.parameters.set (tokens[0].trim(), tokens[1].trim());
- }
-
- materials.add (material);
- return Result::ok();
- }
-
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavefrontObjFile)
- };
|