From 50a8bd02a32f36b09165de21fe26d4ff4795694c Mon Sep 17 00:00:00 2001 From: Christoph Lipka Date: Wed, 14 Sep 2016 01:12:15 +0200 Subject: [PATCH 1/3] Implemented basic Wavefront OBJ import. --- changes.txt | 9 + source/base/fileinputoutput.h | 1 + source/base/fileutil.cpp | 3 + source/base/version.h | 2 +- source/core/shape/mesh.cpp | 23 +- source/core/shape/mesh.h | 7 +- source/parser/parser.cpp | 123 ++++-- source/parser/parser.h | 12 + source/parser/parser_obj.cpp | 578 +++++++++++++++++++++++++ source/parser/parser_tokenizer.cpp | 15 +- source/parser/reservedwords.cpp | 3 + source/parser/reservedwords.h | 3 + unix/VERSION | 2 +- windows/vs10/povparser.vcxproj | 1 + windows/vs10/povparser.vcxproj.filters | 3 + 15 files changed, 728 insertions(+), 57 deletions(-) create mode 100644 source/parser/parser_obj.cpp diff --git a/changes.txt b/changes.txt index 4b0403271..3a4a3bd65 100644 --- a/changes.txt +++ b/changes.txt @@ -49,6 +49,9 @@ Prior to the release of 3.7.1, the following items still need urgent attention: New Features ------------ +- Basic support for importing Wavefront OBJ files has been added as an extension + to the `mesh` primitive. + - The `map_type` keyword now supports the Angular Map projection for light probes, popularized by Paul Devebec, as type 7. @@ -109,6 +112,12 @@ For more details on the above new features, see the documentation. Changed Behaviour ----------------- +- The `mesh2` syntax has been absorbed into that of `mesh` (i.e. you can now + replace any occurrences of the `mesh2` keyword with `mesh`), to reflect the + fact that they are just two syntax variants for one and the same primitive. + The old `mesh2` keyword is retained for backward compatibility and, in + acknowledgement of its widespread use, remains non-deprecated. + - The `version` pseudo-variable will now evaluate to the effective language version at the time the expression is parsed, _except_ when used in a `#version` directive, in which case the behaviour remains unchanged. diff --git a/source/base/fileinputoutput.h b/source/base/fileinputoutput.h index 2ea754718..d1df6306b 100644 --- a/source/base/fileinputoutput.h +++ b/source/base/fileinputoutput.h @@ -72,6 +72,7 @@ enum POV_File_Text_Macro = POV_File_Text_INC, POV_File_Text_INI, POV_File_Text_CSV, + POV_File_Text_OBJ, POV_File_Text_Stream, POV_File_Text_User, POV_File_Data_DF3, diff --git a/source/base/fileutil.cpp b/source/base/fileutil.cpp index 9f9d83ddb..4c3d4e734 100644 --- a/source/base/fileutil.cpp +++ b/source/base/fileutil.cpp @@ -83,6 +83,7 @@ POV_File_Restrictions gPOV_File_Restrictions[POV_File_Count] = { true, false, false, false }, // POV_File_Text_INC { true, false, false, false }, // POV_File_Text_INI { true, true, false, false }, // POV_File_Text_CSV + { true, true, false, false }, // POV_File_Text_OBJ { true, false, false, false }, // POV_File_Text_Stream { true, true, false, false }, // POV_File_Text_User { true, true, true, false }, // POV_File_Data_DF3 @@ -128,6 +129,7 @@ POV_File_Extensions gPOV_File_Extensions[POV_File_Count] = {{ ".inc", ".INC", "", "" }}, // POV_File_Text_INC {{ ".ini", ".INI", "", "" }}, // POV_File_Text_INI {{ ".csv", ".CSV", "", "" }}, // POV_File_Text_CSV + {{ ".obj", ".OBJ", "", "" }}, // POV_File_Text_OBJ {{ ".txt", ".TXT", "", "" }}, // POV_File_Text_Stream {{ "", "", "", "" }}, // POV_File_Text_User {{ ".df3", ".DF3", "", "" }}, // POV_File_Data_DF3 @@ -157,6 +159,7 @@ const int gFile_Type_To_Mask [POV_File_Count] = NO_FILE, // POV_File_Text_INC NO_FILE, // POV_File_Text_INI NO_FILE, // POV_File_Text_CSV + NO_FILE, // POV_File_Text_OBJ NO_FILE, // POV_File_Text_Stream NO_FILE, // POV_File_Text_User NO_FILE, // POV_File_Data_DF3 diff --git a/source/base/version.h b/source/base/version.h index 2a5109277..08d8ea853 100644 --- a/source/base/version.h +++ b/source/base/version.h @@ -45,7 +45,7 @@ #define OFFICIAL_VERSION_STRING "3.7.1" #define OFFICIAL_VERSION_NUMBER 371 -#define POV_RAY_PRERELEASE "alpha.8778710" +#define POV_RAY_PRERELEASE "x.obj.8785394" #if (POV_RAY_IS_AUTOBUILD == 1) && ((POV_RAY_IS_OFFICIAL == 1) || (POV_RAY_IS_SEMI_OFFICIAL == 1)) #ifdef POV_RAY_PRERELEASE diff --git a/source/core/shape/mesh.cpp b/source/core/shape/mesh.cpp index c158342c8..5b69c8af9 100644 --- a/source/core/shape/mesh.cpp +++ b/source/core/shape/mesh.cpp @@ -826,14 +826,17 @@ void Mesh::Compute_BBox() * ******************************************************************************/ -bool Mesh::Compute_Mesh_Triangle(MESH_TRIANGLE *Triangle, bool Smooth, Vector3d& P1, Vector3d& P2, Vector3d& P3, Vector3d& S_Normal) +bool Mesh::Compute_Mesh_Triangle(MESH_TRIANGLE *Triangle, bool Smooth, const Vector3d& P1, const Vector3d& P2, const Vector3d& P3, Vector3d& S_Normal) const { MeshIndex temp; bool swap; DBL x, y, z; - Vector3d V1, V2, T1; + Vector3d V1, V2; DBL Length; + const Vector3d *pP1; + const Vector3d *pP2; + V1 = P2 - P1; V2 = P3 - P1; @@ -914,9 +917,8 @@ bool Mesh::Compute_Mesh_Triangle(MESH_TRIANGLE *Triangle, bool Smooth, Vector3d& Triangle->Texture = temp; } - T1 = P1; - P1 = P2; - P2 = T1; + pP1 = &P2; + pP2 = &P1; if (Smooth) { @@ -925,14 +927,19 @@ bool Mesh::Compute_Mesh_Triangle(MESH_TRIANGLE *Triangle, bool Smooth, Vector3d& Triangle->N1 = temp; } } + else + { + pP1 = &P1; + pP2 = &P2; + } if (Smooth) { - // compute_smooth_triangle(Triangle, P1, P2, P3); + // compute_smooth_triangle(Triangle, *pP1, *pP2, P3); Triangle->Smooth = true; } - compute_smooth_triangle(Triangle, P1, P2, P3); + compute_smooth_triangle(Triangle, *pP1, *pP2, P3); return(true); } @@ -965,7 +972,7 @@ bool Mesh::Compute_Mesh_Triangle(MESH_TRIANGLE *Triangle, bool Smooth, Vector3d& * ******************************************************************************/ -void Mesh::compute_smooth_triangle(MESH_TRIANGLE *Triangle, const Vector3d& P1, const Vector3d& P2, const Vector3d& P3) +void Mesh::compute_smooth_triangle(MESH_TRIANGLE *Triangle, const Vector3d& P1, const Vector3d& P2, const Vector3d& P3) const { Vector3d P3MinusP2, VTemp1, VTemp2; DBL x, y, z, uDenominator, Proj; diff --git a/source/core/shape/mesh.h b/source/core/shape/mesh.h index db4609483..b35f9911c 100644 --- a/source/core/shape/mesh.h +++ b/source/core/shape/mesh.h @@ -142,7 +142,10 @@ class Mesh : public ObjectBase void Test_Mesh_Opacity(); void Create_Mesh_Hash_Tables(); - bool Compute_Mesh_Triangle(MESH_TRIANGLE *Triangle, bool Smooth, Vector3d& P1, Vector3d& P2, Vector3d& P3, Vector3d& S_Normal); + + /// @note The method may decide to re-order the vertices without notice. + bool Compute_Mesh_Triangle(MESH_TRIANGLE *Triangle, bool Smooth, const Vector3d& P1, const Vector3d& P2, const Vector3d& P3, Vector3d& S_Normal) const; + void Build_Mesh_BBox_Tree(); bool Degenerate(const Vector3d& P1, const Vector3d& P2, const Vector3d& P3); void Init_Mesh_Triangle(MESH_TRIANGLE *Triangle); @@ -158,7 +161,7 @@ class Mesh : public ObjectBase bool Intersect(const BasicRay& ray, IStack& Depth_Stack, TraceThreadData *Thread); void Compute_Mesh_BBox(); void MeshUV(const Vector3d& P, const MESH_TRIANGLE *Triangle, Vector2d& Result) const; - void compute_smooth_triangle(MESH_TRIANGLE *Triangle, const Vector3d& P1, const Vector3d& P2, const Vector3d& P3); + void compute_smooth_triangle(MESH_TRIANGLE *Triangle, const Vector3d& P1, const Vector3d& P2, const Vector3d& P3) const; bool intersect_mesh_triangle(const BasicRay& ray, const MESH_TRIANGLE *Triangle, DBL *Depth) const; bool test_hit(const MESH_TRIANGLE *Triangle, const BasicRay& OrigRay, DBL Depth, DBL len, IStack& Depth_Stack, TraceThreadData *Thread); void get_triangle_bbox(const MESH_TRIANGLE *Triangle, BoundingBox *BBox) const; diff --git a/source/parser/parser.cpp b/source/parser/parser.cpp index b19c267e8..4ea00f8de 100644 --- a/source/parser/parser.cpp +++ b/source/parser/parser.cpp @@ -3691,6 +3691,54 @@ ObjectPtr Parser::Parse_Light_Source () ******************************************************************************/ ObjectPtr Parser::Parse_Mesh() +{ + Mesh *Object; + + Parse_Begin(); + + if ((Object = reinterpret_cast(Parse_Object_Id())) != NULL) + return (reinterpret_cast(Object)); + + /* Create object. */ + + Object = new Mesh(); + + EXPECT_ONE + + CASE4 (VERTEX_VECTORS_TOKEN, NORMAL_VECTORS_TOKEN, UV_VECTORS_TOKEN, TEXTURE_LIST_TOKEN) + // the following are currently not expected by mesh2 right away, but that may change in the future + CASE3 (FACE_INDICES_TOKEN, UV_INDICES_TOKEN, NORMAL_INDICES_TOKEN) + UNGET + Parse_Mesh2 (Object); + END_CASE + + CASE (OBJ_TOKEN) + Parse_Obj (Object); + END_CASE + + OTHERWISE + UNGET + Parse_Mesh1 (Object); + END_CASE + + END_EXPECT + + // Create bounding box. + + Object->Compute_BBox(); + + // Parse object modifiers. + + Parse_Object_Mods (reinterpret_cast(Object)); + + // Create bounding box tree. + + Object->Build_Mesh_BBox_Tree(); + + return Object; +} + +void Parser::Parse_Mesh1 (Mesh* Object) { /* NK 1998 - added all sorts of uv variables*/ int i; @@ -3702,7 +3750,6 @@ ObjectPtr Parser::Parse_Mesh() MeshVector *Normals, *Vertices; TEXTURE **Textures; MeshUVVector *UVCoords; - Mesh *Object; MESH_TRIANGLE *Triangles; bool fully_textured=true; /* NK 1998 */ @@ -3712,15 +3759,6 @@ ObjectPtr Parser::Parse_Mesh() Inside_Vect = Vector3d(0.0, 0.0, 0.0); - Parse_Begin(); - - if ((Object = reinterpret_cast(Parse_Object_Id())) != NULL) - return (reinterpret_cast(Object)); - - /* Create object. */ - - Object = new Mesh(); - /* Allocate temporary normals, textures, triangles and vertices. */ max_normals = 256; @@ -4091,20 +4129,6 @@ ObjectPtr Parser::Parse_Mesh() Object->Data->Number_Of_Triangles, Object->Data->Number_Of_UVCoords); */ - - /* Create bounding box. */ - - Object->Compute_BBox(); - - /* Parse object modifiers. */ - - Parse_Object_Mods(reinterpret_cast(Object)); - - /* Create bounding box tree. */ - - Object->Build_Mesh_BBox_Tree(); - - return (reinterpret_cast(Object)); } /***************************************************************************** @@ -4134,7 +4158,38 @@ ObjectPtr Parser::Parse_Mesh() * Feb 1998 : Creation. * ******************************************************************************/ + ObjectPtr Parser::Parse_Mesh2() +{ + Mesh *Object; + + Parse_Begin(); + + if ((Object = reinterpret_cast(Parse_Object_Id())) != NULL) + return (reinterpret_cast(Object)); + + /* Create object. */ + + Object = new Mesh(); + + Parse_Mesh2 (Object); + + // Create bounding box. + + Object->Compute_BBox(); + + // Parse object modifiers. + + Parse_Object_Mods (reinterpret_cast(Object)); + + // Create bounding box tree. + + Object->Build_Mesh_BBox_Tree(); + + return Object; +} + +void Parser::Parse_Mesh2 (Mesh* Object) { int i; int number_of_normals, number_of_textures, number_of_triangles, number_of_vertices, number_of_uvcoords; @@ -4155,19 +4210,10 @@ ObjectPtr Parser::Parse_Mesh2() MeshVector *Vertices = NULL; TEXTURE **Textures = NULL; MeshUVVector *UVCoords = NULL; - Mesh *Object; MESH_TRIANGLE *Triangles; Inside_Vect = Vector3d(0.0, 0.0, 0.0); - Parse_Begin(); - - if ((Object = reinterpret_cast(Parse_Object_Id())) != NULL) - return(reinterpret_cast(Object)); - - /* Create object. */ - Object = new Mesh(); - /* normals, uvcoords, and textures are optional */ number_of_vertices = 0; number_of_uvcoords = 0; @@ -4688,15 +4734,6 @@ ObjectPtr Parser::Parse_Mesh2() Set_Flag(Object, MULTITEXTURE_FLAG); } - /* Create bounding box. */ - Object->Compute_BBox(); - - /* Parse object modifiers. */ - Parse_Object_Mods(reinterpret_cast(Object)); - - /* Create bounding box tree. */ - Object->Build_Mesh_BBox_Tree(); - /* Render_Info("Mesh2: %ld bytes: %ld vertices, %ld normals, %ld textures, %ld triangles, %ld uv-coords\n", Object->Data->Number_Of_Normals*sizeof(MeshVector)+ @@ -4710,8 +4747,6 @@ ObjectPtr Parser::Parse_Mesh2() Object->Data->Number_Of_Triangles, Object->Data->Number_Of_UVCoords); */ - - return(reinterpret_cast(Object)); } diff --git a/source/parser/parser.h b/source/parser/parser.h index 2e13a9122..1dfbb9b42 100644 --- a/source/parser/parser.h +++ b/source/parser/parser.h @@ -105,6 +105,7 @@ class ImageData; struct GenericSpline; struct ClassicTurbulence; // full declaration in core/material/warp.h struct BlackHoleWarp; // full declaration in core/material/warp.h +class Mesh; class SceneData; /***************************************************************************** @@ -315,7 +316,9 @@ class Parser : public SceneTask void Warn_Compat (bool definite, const char *sym); void Link_Textures (TEXTURE **Old_Texture, TEXTURE *New_Texture); + /// @note This method includes an implied `Parse_End()`. ObjectPtr Parse_Object_Mods (ObjectPtr Object); + ObjectPtr Parse_Object (void); void Parse_Bound_Clip (vector& objects, bool notexture = true); void Parse_Default (void); @@ -599,7 +602,10 @@ class Parser : public SceneTask ObjectPtr Parse_Lathe(void); ObjectPtr Parse_Lemon(); ObjectPtr Parse_Light_Source(); + + /// @note This method includes an implied `Parse_End()`. ObjectPtr Parse_Object_Id(); + ObjectPtr Parse_Ovus(); ObjectPtr Parse_Plane(); ObjectPtr Parse_Poly(int order); @@ -615,6 +621,11 @@ class Parser : public SceneTask ObjectPtr Parse_Triangle(); ObjectPtr Parse_Mesh(); ObjectPtr Parse_Mesh2(); + + void Parse_Obj (Mesh*); + void Parse_Mesh1 (Mesh*); + void Parse_Mesh2 (Mesh*); + TEXTURE *Parse_Mesh_Texture(TEXTURE **t2, TEXTURE **t3); ObjectPtr Parse_TrueType(void); void Parse_Blob_Element_Mods(Blob_Element *Element); @@ -659,6 +670,7 @@ class Parser : public SceneTask bool Read_Float (void); void Read_Symbol (void); SYM_ENTRY *Find_Symbol (int Index, const char *s); + SYM_ENTRY *Find_Symbol (const char *s); void Skip_Tokens (COND_TYPE cond); void Break (void); diff --git a/source/parser/parser_obj.cpp b/source/parser/parser_obj.cpp new file mode 100644 index 000000000..16e6c1848 --- /dev/null +++ b/source/parser/parser_obj.cpp @@ -0,0 +1,578 @@ +//****************************************************************************** +/// +/// @file parser/parser_obj.cpp +/// +/// This module implements import of Wavefront OBJ files. +/// +/// @copyright +/// @parblock +/// +/// Persistence of Vision Ray Tracer ('POV-Ray') version 3.7. +/// Copyright 1991-2016 Persistence of Vision Raytracer Pty. Ltd. +/// +/// POV-Ray is free software: you can redistribute it and/or modify +/// it under the terms of the GNU Affero General Public License as +/// published by the Free Software Foundation, either version 3 of the +/// License, or (at your option) any later version. +/// +/// POV-Ray is distributed in the hope that it will be useful, +/// but WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU Affero General Public License for more details. +/// +/// You should have received a copy of the GNU Affero General Public License +/// along with this program. If not, see . +/// +/// ---------------------------------------------------------------------------- +/// +/// POV-Ray is based on the popular DKB raytracer version 2.12. +/// DKBTrace was originally written by David K. Buck. +/// DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins. +/// +/// @endparblock +/// +//****************************************************************************** + +// Unit header file must be the first file included within POV-Ray *.cpp files (pulls in config) +#include "parser/parser.h" + +#include + +#include "core/material/interior.h" +#include "core/shape/mesh.h" + +// this must be the last file included +#include "base/povdebug.h" + +namespace pov +{ + +static const int kMaxObjBufferSize = 1014; + +#define PAIR(c1,c2) ( ((int)(c1)) + (((int)(c2))<<8) ) +#define TRIPLET(c1,c2,c3) ( ((int)(c1)) + (((int)(c2))<<8) + (((int)(c3))<<16) ) + +struct FaceVertex +{ + MeshIndex vertexId; + MeshIndex normalId; + MeshIndex uvId; +}; + +struct FaceData +{ + FaceVertex vertexList[3]; + MeshIndex materialId; +}; + +struct MaterialData +{ + std::string mtlName; + TEXTURE *texture; +}; + +/// Fills the buffer with the next word from the current line. +/// A trailing null character will be appended. +static bool ReadWord (char *buffer, pov_base::ITextStream *file) +{ + int i; + int c; + for (i = 0; i < kMaxObjBufferSize-1; ++i) + { + c = file->getchar(); + if ((c == EOF) || isspace (c)) + break; + buffer[i] = c; + } + buffer[i] = '\0'; + while ((c != '\n') && isspace (c)) + c = file->getchar(); + if (c != EOF) + file->ungetchar (c); + return (buffer[0] != '\0'); +} + +static bool HasMoreWords (pov_base::ITextStream *file) +{ + int c = file->getchar(); + file->ungetchar (c); + return ((c != '\n') && (c != EOF)); +} + +static void AdvanceLine (pov_base::ITextStream *file) +{ + int c; + do + c = file->getchar(); + while ((c != '\n') && (c != EOF)); +} + +inline static bool ReadFloat (DBL& result, char *buffer, pov_base::ITextStream *file) +{ + if (!ReadWord (buffer, file)) + return false; + if (sscanf (buffer, DBL_FORMAT_STRING, &result) == 0) + return false; + return true; +} + +inline static bool ReadFaceVertexField (MeshIndex& result, char **p) +{ + POV_LONG temp = 0; + do + { + if (!isdigit (**p)) + return false; + temp = (temp * 10) + (**p - '0'); + ++(*p); + } + while ((**p != '\0') && (**p != '/')); + if ((temp < 1) || (temp > std::numeric_limits::max())) + return false; + result = (MeshIndex)temp; + return true; +} + +inline static bool ReadFaceVertex (FaceVertex& result, char *buffer, pov_base::ITextStream *file) +{ + if (!ReadWord (buffer, file)) + return false; + + char *p = buffer; + // parse face id + result.vertexId = 0; + result.uvId = 0; + result.normalId = 0; + if (!ReadFaceVertexField (result.vertexId, &p)) + return false; + if (*p == '/') + { + ++p; + if (*p != '/') + { + if (!ReadFaceVertexField (result.uvId, &p)) + return false; + } + if (*p == '/') + { + ++p; + if (!ReadFaceVertexField (result.normalId, &p)) + return false; + } + } + return true; +} + + +void Parser::Parse_Obj (Mesh* mesh) +{ + UCS2 *fileName; + char *s; + UCS2String ign; + IStream *stream = NULL; + pov_base::ITextStream *textStream = NULL; + char wordBuffer [kMaxObjBufferSize]; + std::string materialPrefix; + std::string materialSuffix; + + Vector3d insideVector(0.0); + Vector3d v3; + Vector2d v2; + bool foundZeroNormal = false; + bool fullyTextured = true; + bool havePolygonFaces = false; + + FaceData face; + MaterialData material; + MATERIAL *povMaterial; + + vector vertexList; + vector normalList; + vector uvList; + vector faceList; + vector flatFaceList; + vector materialList; + size_t materialId = 0; + + fileName = Parse_String (true); + + EXPECT + CASE (TEXTURE_LIST_TOKEN) + Parse_Begin(); + EXPECT + CASE5 (STRING_LITERAL_TOKEN,CHR_TOKEN,SUBSTR_TOKEN,STR_TOKEN,VSTR_TOKEN) + CASE4 (CONCAT_TOKEN,STRUPR_TOKEN,STRLWR_TOKEN,DATETIME_TOKEN) + UNGET + s = Parse_C_String(); + material.mtlName = s; + POV_FREE (s); + + EXPECT_ONE + CASE (TEXTURE_TOKEN) + Parse_Begin (); + material.texture = Parse_Texture(); + Parse_End (); + END_CASE + CASE (MATERIAL_TOKEN) + povMaterial = Create_Material(); + Parse_Material (povMaterial); + material.texture = Copy_Textures (povMaterial->Texture); + Destroy_Material (povMaterial); + END_CASE + OTHERWISE + Expectation_Error ("texture or material"); + END_CASE + END_EXPECT + + Post_Textures(material.texture); + materialList.push_back (material); + END_CASE + CASE (PREFIX_TOKEN) + s = Parse_C_String(); + materialPrefix = s; + POV_FREE (s); + END_CASE + CASE (SUFFIX_TOKEN) + s = Parse_C_String(); + materialSuffix = s; + POV_FREE (s); + END_CASE + OTHERWISE + UNGET + EXIT + END_CASE + END_EXPECT + Parse_End(); + END_CASE + + CASE (INSIDE_VECTOR_TOKEN) + Parse_Vector (insideVector); + END_CASE + + OTHERWISE + UNGET + EXIT + END_CASE + END_EXPECT + + // open obj file + + stream = Locate_File (fileName, POV_File_Text_OBJ, ign, true); + if (stream) + { + textStream = new IBufferedTextStream (fileName, stream); + if (!textStream) + delete stream; + } + if (!textStream) + Error ("Cannot open obj file %s.", UCS2toASCIIString(fileName).c_str()); + + // mark end of buffer with a non-null character to identify situations where a word may not have fit entirely inside the buffer. + wordBuffer[kMaxObjBufferSize-1] = '*'; + + while (!textStream->eof()) + { + (void)ReadWord (wordBuffer, textStream); + if (wordBuffer[0] == '\0') + wordBuffer[1] = '\0'; + if (wordBuffer[1] == '\0') + wordBuffer[2] = '\0'; + + bool unsupportedCmd = false; + bool skipLine = false; + + switch (TRIPLET (wordBuffer[0], wordBuffer[1], wordBuffer[2])) + { + case '\0': // empty line + case '#': // comment + case 'g': // group ("g NAME") + case 'o': // object name ("o NAME") + skipLine = true; + break; + + case 'f': // face ("f VERTEXID VERTEXID VERTEXID ...") + { + face.materialId = materialId; + if (materialId == 0) + fullyTextured = false; + int haveVertices = 0; + int haveUV = 0; + int haveNormal = 0; + int vertex = 0; + while (HasMoreWords (textStream)) + { + if (!ReadFaceVertex (face.vertexList[vertex], wordBuffer, textStream)) + Error ("Invalid or unsupported face index data '%s' in obj file %s line %i", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + if (face.vertexList[vertex].vertexId > vertexList.size()) + Error ("Vertex index out of range in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + if (face.vertexList[vertex].uvId > uvList.size()) + Error ("UV index out of range in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + if (face.vertexList[vertex].normalId > normalList.size()) + Error ("Normal index out of range in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + ++haveVertices; + if (face.vertexList[vertex].uvId > 0) + ++haveUV; + if (face.vertexList[vertex].normalId > 0) + ++haveNormal; + + if (haveVertices >= 3) + { + if (haveNormal == haveVertices) + faceList.push_back (face); + else + flatFaceList.push_back (face); + face.vertexList[1] = face.vertexList[2]; + } + else + ++vertex; + } + if ((haveVertices > 3) && (havePolygonFaces)) + { + Warning ("Non-triangular faces found in obj file %s. Faces will only import properly if they are convex and planar.", UCS2toASCIIString(fileName).c_str()); + havePolygonFaces = true; + } + if (haveVertices < 3) + Error ("Insufficient number of vertices per face in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + if ((haveUV != 0) && (haveUV != haveVertices)) + Error ("Inconsistent use of UV indices in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + if ((haveNormal != 0) && (haveNormal != haveVertices)) + Error ("Inconsistent use of normal indices in obj file %s line %i", UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + if (haveNormal > 0) + faceList.push_back (face); + else + flatFaceList.push_back (face); + } + break; + + case TRIPLET('m','t','l'): // presumably material library ("mtllib FILE FILE ...") + if (strcmp (wordBuffer, "mtllib") == 0) + { + // TODO + unsupportedCmd = true; + } + else + unsupportedCmd = true; + break; + + case TRIPLET('u','s','e'): // presumably material selection ("usemtl NAME") + if (strcmp (wordBuffer, "usemtl") == 0) + { + if (!ReadWord (wordBuffer, textStream)) + Error ("Invalid material name '%s' in obj file %s line %i", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + for (materialId = 0; materialId < materialList.size(); ++materialId) + { + if (materialList[materialId].mtlName.compare (wordBuffer) == 0) + break; + } + if (materialId == materialList.size()) + { + material.mtlName = wordBuffer; + material.texture = NULL; + std::string identifier = materialPrefix + std::string(wordBuffer) + materialSuffix; + SYM_ENTRY *symbol = Find_Symbol (identifier.c_str()); + if (symbol == NULL) + Error ("No matching texture for obj file material '%s': Identifier '%s' not found.", wordBuffer, identifier.c_str()); + else if (symbol->Token_Number == TEXTURE_ID_TOKEN) + material.texture = Copy_Textures(reinterpret_cast(symbol->Data)); + else if (symbol->Token_Number == MATERIAL_ID_TOKEN) + material.texture = Copy_Textures(reinterpret_cast(symbol->Data)->Texture); + else + Error ("No matching texture for obj file material '%s': Identifier '%s' is not a texture or material.", wordBuffer, identifier.c_str()); + Post_Textures (material.texture); + materialList.push_back (material); + } + materialId ++; + } + else + unsupportedCmd = true; + break; + + case 'v': // vertex XYZ coordinates ("v FLOAT FLOAT FLOAT") + for (int dimension = X; dimension <= Z; ++dimension) + { + if (!ReadFloat (v3[dimension], wordBuffer, textStream)) + Error ("Invalid coordinate value '%s' in obj file %s line %i", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + } + vertexList.push_back (v3); + break; + + case PAIR('v','n'): // vertex normal vector ("vn FLOAT FLOAT FLOAT") + for (int dimension = X; dimension <= Z; ++dimension) + { + if (!ReadFloat (v3[dimension], wordBuffer, textStream)) + Error ("Invalid coordinate value '%s' in obj file %s line %i", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + } + normalList.push_back (v3); + break; + + case PAIR('v','t'): // vertex UV coordinates ("vt FLOAT FLOAT") + for (int dimension = X; dimension <= Y; ++dimension) + { + if (!ReadFloat (v2[dimension], wordBuffer, textStream)) + Error ("Invalid coordinate value '%s' in obj file %s line %i", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + } + uvList.push_back (v2); + break; + + default: + unsupportedCmd = true; + break; + } + + if (unsupportedCmd) + { + Warning("Unsupported command '%s' skipped in obj file %s line %i.", wordBuffer, UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + skipLine = true; + } + + if (!skipLine && HasMoreWords (textStream)) + PossibleError("Unexpected extra data skipped in obj file %s line %i.", UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + + // skip remainder of line + AdvanceLine (textStream); + + if (wordBuffer[kMaxObjBufferSize-1] == '\0') + PossibleError("Excessively long data in obj file %s line %i.", UCS2toASCIIString(fileName).c_str(), (int)textStream->line()); + } + + // close obj file + + if (textStream) + delete textStream; + + size_t smoothFaces = faceList.size(); + faceList.insert (faceList.end(), flatFaceList.begin(), flatFaceList.end()); + + MeshVector *vertexArray = NULL; + MeshVector *normalArray = NULL; + MeshUVVector *uvArray = NULL; + TEXTURE **textureArray = NULL; + MESH_TRIANGLE *triangleArray = NULL; + + if (vertexList.empty()) + Error ("No vertices in obj file."); + else if (vertexList.size() >= std::numeric_limits::max()) + Error ("Too many UV vectors in obj file."); + + vertexArray = reinterpret_cast(POV_MALLOC(vertexList.size()*sizeof(MeshVector), "triangle mesh data")); + for (size_t i = 0; i < vertexList.size(); ++i) + vertexArray[i] = MeshVector (vertexList[i]); + + if (!normalList.empty()) + { + if (normalList.size() >= std::numeric_limits::max()) + Error ("Too many normal vectors in obj file."); + + normalArray = reinterpret_cast(POV_MALLOC((normalList.size()+faceList.size())*sizeof(MeshVector), "triangle mesh data")); + for (size_t i = 0; i < normalList.size(); ++i) + { + Vector3d& n = normalList[i]; + if ((fabs(n.x()) < EPSILON) && (fabs(n.x()) < EPSILON) && (fabs(n.z()) < EPSILON)) + { + n.x() = 1.0; // make it nonzero + if (!foundZeroNormal) + Warning("Normal vector in mesh2 cannot be zero - changing it to <1,0,0>."); + foundZeroNormal = true; + } + normalArray[i] = MeshVector(n); + } + } + + // make sure we at least have one UV coordinate + if (uvList.empty()) + uvList.push_back (Vector2d(0.0, 0.0)); + else if (uvList.size() >= std::numeric_limits::max()) + Error ("Too many UV vectors in obj file."); + + uvArray = reinterpret_cast(POV_MALLOC(uvList.size() *sizeof(MeshUVVector), "triangle mesh data")); + for (size_t i = 0; i < uvList.size(); ++i) + uvArray[i] = MeshUVVector(uvList[i]); + + if (!materialList.empty()) + { + if (materialList.size() >= std::numeric_limits::max()) + Error ("Too many materials in obj file."); + + textureArray = reinterpret_cast(POV_MALLOC(materialList.size() *sizeof(TEXTURE*), "triangle mesh data")); + for (size_t i = 0; i < materialList.size(); ++i) + textureArray[i] = materialList[i].texture; + } + + if (faceList.empty()) + Error ("No faces in obj file."); + + triangleArray = reinterpret_cast(POV_MALLOC(faceList.size()*sizeof(MESH_TRIANGLE), "triangle mesh data")); + for (size_t i = 0, j = normalList.size(); i < faceList.size(); ++i, ++j) + { + const FaceData& objTriangle = faceList[i]; + MESH_TRIANGLE& triangle = triangleArray[i]; + mesh->Init_Mesh_Triangle (&triangle); + triangle.P1 = objTriangle.vertexList[0].vertexId - 1; + triangle.P2 = objTriangle.vertexList[1].vertexId - 1; + triangle.P3 = objTriangle.vertexList[2].vertexId - 1; + triangle.Texture = objTriangle.materialId - 1; + triangle.UV1 = max(1, objTriangle.vertexList[0].uvId) - 1; + triangle.UV2 = max(1, objTriangle.vertexList[1].uvId) - 1; + triangle.UV3 = max(1, objTriangle.vertexList[2].uvId) - 1; + triangle.Smooth = (i < smoothFaces); + const Vector3d& P1 = vertexList[triangle.P1]; + const Vector3d& P2 = vertexList[triangle.P2]; + const Vector3d& P3 = vertexList[triangle.P3]; + Vector3d N; + if (triangle.Smooth) + { + triangle.N1 = objTriangle.vertexList[0].normalId - 1; + triangle.N2 = objTriangle.vertexList[1].normalId - 1; + triangle.N3 = objTriangle.vertexList[2].normalId - 1; + Vector3d& N1 = normalList[triangle.N1]; + Vector3d& N2 = normalList[triangle.N2]; + Vector3d& N3 = normalList[triangle.N3]; + + // check for equal normals + Vector3d D1 = N1 - N2; + Vector3d D2 = N1 - N3; + double l1 = D1.lengthSqr(); + double l2 = D2.lengthSqr(); + triangle.Smooth = ((fabs(l1) > EPSILON) || (fabs(l2) > EPSILON)); + } + mesh->Compute_Mesh_Triangle (&triangle, triangle.Smooth, P1, P2, P3, N); + triangle.Normal_Ind = j; + normalArray[j] = MeshVector(N); + } + + if (fullyTextured) + mesh->Type |= TEXTURED_OBJECT; + + mesh->Data = reinterpret_cast(POV_MALLOC(sizeof(MESH_DATA), "triangle mesh data")); + mesh->Data->References = 1; + mesh->Data->Tree = NULL; + + mesh->has_inside_vector = insideVector.IsNearNull (EPSILON); + if (mesh->has_inside_vector) + { + mesh->Data->Inside_Vect = insideVector.normalized(); + mesh->Type &= ~PATCH_OBJECT; + } + else + { + mesh->Type |= PATCH_OBJECT; + } + + mesh->Data->Normals = normalArray; + mesh->Data->Triangles = triangleArray; + mesh->Data->Vertices = vertexArray; + mesh->Data->UVCoords = uvArray; + mesh->Textures = textureArray; + + /* copy number of for normals, textures, triangles and vertices. */ + mesh->Data->Number_Of_Normals = normalList.size() + faceList.size(); + mesh->Data->Number_Of_Triangles = faceList.size(); + mesh->Data->Number_Of_Vertices = vertexList.size(); + mesh->Data->Number_Of_UVCoords = uvList.size(); + mesh->Number_Of_Textures = materialList.size(); + + if (!materialList.empty()) + Set_Flag(mesh, MULTITEXTURE_FLAG); +} + +} diff --git a/source/parser/parser_tokenizer.cpp b/source/parser/parser_tokenizer.cpp index c7e49fa21..7d0ca01b1 100644 --- a/source/parser/parser_tokenizer.cpp +++ b/source/parser/parser_tokenizer.cpp @@ -2954,7 +2954,7 @@ SYM_ENTRY *Parser::Add_Symbol (int Index,const char *Name,TOKEN Number) } -SYM_ENTRY *Parser::Find_Symbol(int Index,const char *Name) +SYM_ENTRY *Parser::Find_Symbol (int Index, const char *Name) { SYM_ENTRY *Entry; @@ -2976,6 +2976,19 @@ SYM_ENTRY *Parser::Find_Symbol(int Index,const char *Name) } +SYM_ENTRY *Parser::Find_Symbol (const char *name) +{ + SYM_ENTRY *entry; + for (int index = Table_Index; index > 0; --index) + { + entry = Find_Symbol (index, name); + if (entry) + return entry; + } + return NULL; +} + + void Parser::Remove_Symbol (int Index, const char *Name, bool is_array_elem, void **DataPtr, int ttype) { if(is_array_elem == true) diff --git a/source/parser/reservedwords.cpp b/source/parser/reservedwords.cpp index ce9018f02..ed5c40d9a 100644 --- a/source/parser/reservedwords.cpp +++ b/source/parser/reservedwords.cpp @@ -354,6 +354,7 @@ const RESERVED_WORD Reserved_Words[] = { { NUMBER_OF_TILES_TOKEN, "number_of_tiles" }, { NUMBER_OF_WAVES_TOKEN, "number_of_waves" }, + { OBJ_TOKEN, "obj" }, { OBJECT_TOKEN, "object" }, { OCTAVES_TOKEN, "octaves" }, { OFF_TOKEN, "off" }, @@ -399,6 +400,7 @@ const RESERVED_WORD Reserved_Words[] = { { PPM_TOKEN, "ppm" }, { PRECISION_TOKEN, "precision" }, { PRECOMPUTE_TOKEN, "precompute" }, + { PREFIX_TOKEN, "prefix" }, { PREMULTIPLIED_TOKEN, "premultiplied" }, { PRETRACE_END_TOKEN, "pretrace_end" }, { PRETRACE_START_TOKEN, "pretrace_start" }, @@ -504,6 +506,7 @@ const RESERVED_WORD Reserved_Words[] = { { STURM_TOKEN, "sturm" }, { SUBSTR_TOKEN, "substr" }, { SUBSURFACE_TOKEN, "subsurface" }, + { SUFFIX_TOKEN, "suffix" }, { SUM_TOKEN, "sum" }, { SUPERELLIPSOID_TOKEN, "superellipsoid" }, { SWITCH_TOKEN, "switch" }, diff --git a/source/parser/reservedwords.h b/source/parser/reservedwords.h index 33dde611b..f94fcdeb0 100644 --- a/source/parser/reservedwords.h +++ b/source/parser/reservedwords.h @@ -488,6 +488,7 @@ enum TOKEN_IDS NUMBER_OF_TILES_TOKEN, NUMBER_OF_WAVES_TOKEN, + OBJ_TOKEN, OBJECT_TOKEN, OBJECT_ID_TOKEN, OCTAVES_TOKEN, @@ -536,6 +537,7 @@ enum TOKEN_IDS PPM_TOKEN, PRECISION_TOKEN, PRECOMPUTE_TOKEN, + PREFIX_TOKEN, PREMULTIPLIED_TOKEN, PRETRACE_END_TOKEN, PRETRACE_START_TOKEN, @@ -631,6 +633,7 @@ enum TOKEN_IDS STURM_TOKEN, SUBSTR_TOKEN, SUBSURFACE_TOKEN, + SUFFIX_TOKEN, SUPERELLIPSOID_TOKEN, SWITCH_TOKEN, SYS_TOKEN, diff --git a/unix/VERSION b/unix/VERSION index a8df8c127..38f8d10c0 100644 --- a/unix/VERSION +++ b/unix/VERSION @@ -1 +1 @@ -3.7.1-alpha.8778710 +3.7.1-x.obj.8785394 diff --git a/windows/vs10/povparser.vcxproj b/windows/vs10/povparser.vcxproj index 9e4219a1d..957a3067e 100644 --- a/windows/vs10/povparser.vcxproj +++ b/windows/vs10/povparser.vcxproj @@ -349,6 +349,7 @@ + diff --git a/windows/vs10/povparser.vcxproj.filters b/windows/vs10/povparser.vcxproj.filters index e08da7b25..d504d8cc2 100644 --- a/windows/vs10/povparser.vcxproj.filters +++ b/windows/vs10/povparser.vcxproj.filters @@ -63,5 +63,8 @@ Parser Source + + Parser Source + \ No newline at end of file From d8c4f868bfc54584aba511f7b5e318dc2ed65720 Mon Sep 17 00:00:00 2001 From: Christoph Lipka Date: Wed, 14 Sep 2016 20:54:46 +0200 Subject: [PATCH 2/3] Bugfix, plus minor attempts at improving performance, in bounding hierarchy creation. --- source/base/mathutil.h | 9 + source/base/version.h | 2 +- source/core/bounding/boundingbox.cpp | 328 ++++++++++++--------------- source/core/bounding/boundingbox.h | 23 +- source/parser/parser.cpp | 65 +++--- source/parser/parser.h | 2 + unix/VERSION | 2 +- 7 files changed, 209 insertions(+), 222 deletions(-) diff --git a/source/base/mathutil.h b/source/base/mathutil.h index 855de7bba..ea3b68d17 100644 --- a/source/base/mathutil.h +++ b/source/base/mathutil.h @@ -144,6 +144,15 @@ inline T1 RoundDownToMultiple(T1 x, T2 base) { return x - (x % base); } template inline T1 RoundUpToMultiple(T1 x, T2 base) { return RoundDownToMultiple (x + base - 1, base); } +/// Test whether a value is in a given range +/// +/// This function tests whether the specified value is within the specified interval. +/// The boundaries are considered part of the interval. +/// +template +inline bool IsInRange (T1 value, T2 min, T2 max) +{ + return (min <= value) && (value <= max); } #endif // POVRAY_BASE_MATHUTIL_H diff --git a/source/base/version.h b/source/base/version.h index 08d8ea853..a549d76a5 100644 --- a/source/base/version.h +++ b/source/base/version.h @@ -45,7 +45,7 @@ #define OFFICIAL_VERSION_STRING "3.7.1" #define OFFICIAL_VERSION_NUMBER 371 -#define POV_RAY_PRERELEASE "x.obj.8785394" +#define POV_RAY_PRERELEASE "x.obj.8787748" #if (POV_RAY_IS_AUTOBUILD == 1) && ((POV_RAY_IS_OFFICIAL == 1) || (POV_RAY_IS_SEMI_OFFICIAL == 1)) #ifdef POV_RAY_PRERELEASE diff --git a/source/core/bounding/boundingbox.cpp b/source/core/bounding/boundingbox.cpp index d285c3b42..a7bc0298e 100644 --- a/source/core/bounding/boundingbox.cpp +++ b/source/core/bounding/boundingbox.cpp @@ -62,8 +62,8 @@ BBOX_TREE *create_bbox_node(int size); int find_axis(BBOX_TREE **Finite, ptrdiff_t first, ptrdiff_t last); void calc_bbox(BoundingBox *BBox, BBOX_TREE **Finite, ptrdiff_t first, ptrdiff_t last); -void build_area_table(BBOX_TREE **Finite, ptrdiff_t a, ptrdiff_t b, DBL *areas); -int sort_and_split(BBOX_TREE **Root, BBOX_TREE **&Finite, size_t *numOfFiniteObjects, ptrdiff_t first, ptrdiff_t last, size_t& maxfinitecount); +void build_area_table(BBOX_TREE **Finite, ptrdiff_t a, ptrdiff_t b, BBoxScalar *areas); +bool sort_and_split(BBOX_TREE **Root, BBOX_TREE **&Finite, size_t *numOfFiniteObjects, ptrdiff_t first, ptrdiff_t last, size_t& maxfinitecount, BBoxScalar **areaCache); BBoxPriorityQueue::BBoxPriorityQueue() { @@ -263,12 +263,16 @@ void Build_BBox_Tree(BBOX_TREE **Root, size_t numOfFiniteObjects, BBOX_TREE **&F low = 0; high = numOfFiniteObjects; - while(sort_and_split(Root, Finite, &numOfFiniteObjects, low, high, maxfinitecount) == 0) + BBoxScalar *areaCache = new BBoxScalar[numOfFiniteObjects*2]; + + while (sort_and_split(Root, Finite, &numOfFiniteObjects, low, high, maxfinitecount, &areaCache)) { low = high; high = numOfFiniteObjects; } + delete[] areaCache; + // Move infinite objects in the first leaf of Root. if(numOfInfiniteObjects > 0) { @@ -524,142 +528,103 @@ bool Intersect_BBox_Tree(BBoxPriorityQueue& pqueue, const BBOX_TREE *Root, const void Check_And_Enqueue(BBoxPriorityQueue& Queue, const BBOX_TREE *Node, const BoundingBox *BBox, const Rayinfo *rayinfo, RenderStatistics& Stats) { - DBL tmin, tmax; DBL dmin, dmax; if(Node->Infinite == false) { Stats[nChecked]++; - if(rayinfo->nonzero[X]) - { - if (rayinfo->positive[X]) - { - dmin = (BBox->lowerLeft[X] - rayinfo->slab_num[X]) * rayinfo->slab_den[X]; - dmax = dmin + (BBox->size[X] * rayinfo->slab_den[X]); - if(dmax < EPSILON) - return; - } - else - { - dmax = (BBox->lowerLeft[X] - rayinfo->slab_num[X]) * rayinfo->slab_den[X]; - - if(dmax < EPSILON) - return; - - dmin = dmax + (BBox->size[X] * rayinfo->slab_den[X]); - } + // Test whether the bounding box is being hit. - if(dmin > dmax) - return; - } - else - { - if((rayinfo->slab_num[X] < BBox->lowerLeft[X]) || - (rayinfo->slab_num[X] > BBox->size[X] + BBox->lowerLeft[X])) - return; + // The bounding box can be thought of as an intersection of three "slabs", by which we mean slices of 3D space + // bounded by parallel axis-aligned planes; we have an "X slab" bounding the box in the X dimension, an + // "Y slab", and a "Z slab". - dmin = -BOUND_HUGE; - dmax = BOUND_HUGE; - } + // With a few exceptions that we need to test for, any ray will intersect all three slabs, defining an interval + // along the ray in which the ray is inside the slab. - if(rayinfo->nonzero[Y]) - { - if(rayinfo->positive[Y]) - { - tmin = (BBox->lowerLeft[Y] - rayinfo->slab_num[Y]) * rayinfo->slab_den[Y]; - tmax = tmin + (BBox->size[Y] * rayinfo->slab_den[Y]); - } - else - { - tmax = (BBox->lowerLeft[Y] - rayinfo->slab_num[Y]) * rayinfo->slab_den[Y]; - tmin = tmax + (BBox->size[Y] * rayinfo->slab_den[Y]); - } + // Where the intervals for the individual slabs overlap, the ray is inside bounding box. - // Unwrap the logic - do the dmin and dmax checks only when tmin and - // tmax actually affect anything, also try to escape ASAP. Better - // yet, fold the logic below into the two branches above so as to - // compute only what is needed. + // We proceed one dimension at a time. - // You might even try tmax < EPSILON first (instead of second) for an - // early quick out. + // These will keep track of the overlap between the intervals. + dmin = -BOUND_HUGE; + dmax = BOUND_HUGE; - if(tmax < dmax) + for (int dim = X; dim <= Z; ++dim) + { + if(rayinfo->nonzero[dim]) { - if(tmax < EPSILON) - return; + // These will hold the distance to the near and far plane, respectively, for this slab. + DBL tmin, tmax; - // check bbox only if tmax changes dmax - - if(tmin > dmin) + if (rayinfo->positive[dim]) { - if(tmin > tmax) + // Far plane is "upper" plane, near plane is the "lower" one. + tmax = (BBox->lowerLeft[dim] + BBox->size[dim] - rayinfo->origin[dim]) * rayinfo->invDirection[dim]; + if(tmax < EPSILON) + // The far plane is (at least almost) behind the observer, + // so the ray is heading away from the box and can't possibly intersect it. return; - - // do this last in case it's not needed! - dmin = tmin; + tmin = (BBox->lowerLeft[dim] - rayinfo->origin[dim]) * rayinfo->invDirection[dim]; } - else if(dmin > tmax) - return; - - // do this last in case it's not needed! - dmax = tmax; - } - else if(tmin > dmin) - { - if(tmin > dmax) - return; - - // do this last in case it's not needed! - dmin = tmin; - } - } - else if((rayinfo->slab_num[Y] < BBox->lowerLeft[Y]) || - (rayinfo->slab_num[Y] > BBox->size[Y] + BBox->lowerLeft[Y])) - return; - - if(rayinfo->nonzero[Z]) - { - if(rayinfo->positive[Z]) - { - tmin = (BBox->lowerLeft[Z] - rayinfo->slab_num[Z]) * rayinfo->slab_den[Z]; - tmax = tmin + (BBox->size[Z] * rayinfo->slab_den[Z]); - } - else - { - tmax = (BBox->lowerLeft[Z] - rayinfo->slab_num[Z]) * rayinfo->slab_den[Z]; - tmin = tmax + (BBox->size[Z] * rayinfo->slab_den[Z]); - } - - if(tmax < dmax) - { - if(tmax < EPSILON) - return; - - // check bbox only if tmax changes dmax - if(tmin > dmin) + else { - if(tmin > tmax) + // Far plane is "lower" plane, near plane is the "upper" one. + tmax = (BBox->lowerLeft[dim] - rayinfo->origin[dim]) * rayinfo->invDirection[dim]; + if(tmax < EPSILON) + // The far plane is (at least almost) behind the observer, + // so the ray is heading away from the box and can't possibly intersect it. return; + tmin = (BBox->lowerLeft[dim] + BBox->size[dim] - rayinfo->origin[dim]) * rayinfo->invDirection[dim]; + } - // do this last in case it's not needed! - dmin = tmin; + // The next portion of code is essentially the same as the following + // (presuming dmin <= dmax initially), with a lot of shortcuts to bail out early: + // + // if (tmax < dmax) dmax = tmax; // update the overlap lower bound + // if (tmin > dmin) dmin = tmin; // update the overlap upper bound + // if (dmin > dmax) return; // detect whether there is no overlap + + if (tmax < dmax) + { + if (tmin > dmin) + { + if(tmin > tmax) + return; + dmin = tmin; + } + else + { + if(dmin > tmax) + return; + } + dmax = tmax; + } + else + { + if(tmin > dmin) + { + if(tmin > dmax) + return; + dmin = tmin; + } } - else if(dmin > tmax) - return; } - else if(tmin > dmin) + else { - if(tmin > dmax) + // Special case: The ray runs parallel to this slab; there ray is either entirely inside the slab, + // or it is entirely outside; we can easily check this by testing the ray origin. + + if (!IsInRange (rayinfo->origin[dim], BBox->lowerLeft[dim], BBox->lowerLeft[dim] + BBox->size[dim])) + // The ray is entirely outside the slab, so it can't possibly hit the bounding box. return; - // do this last in case it's not needed! - dmin = tmin; + // The ray is entirely inside the slab, so this slab has no effect on the end result. } } - else - if((rayinfo->slab_num[Z] < BBox->lowerLeft[Z]) || (rayinfo->slab_num[Z] > BBox->size[Z] + BBox->lowerLeft[Z])) - return; + + // If we've made it through to here, the ray does hit the box. Stats[nEnqueued]++; } @@ -667,7 +632,7 @@ void Check_And_Enqueue(BBoxPriorityQueue& Queue, const BBOX_TREE *Node, const Bo // Set intersection depth to -Max_Distance. dmin = -MAX_DISTANCE; - Queue.Insert(dmin, Node); + Queue.Insert (dmin, Node); } BBOX_TREE *create_bbox_node(int size) @@ -717,12 +682,12 @@ int find_axis(BBOX_TREE **Finite, ptrdiff_t first, ptrdiff_t last) { int which = X; ptrdiff_t i; - DBL e, d = -BOUND_HUGE; - Vector3d mins, maxs; + SNGL e, d = -BOUND_HUGE; + BBoxVector3d mins, maxs; BoundingBox *bbox; - mins = Vector3d(BOUND_HUGE); - maxs = Vector3d(-BOUND_HUGE); + mins = BBoxVector3d(BOUND_HUGE); + maxs = BBoxVector3d(-BOUND_HUGE); for(i = first; i < last; i++) { @@ -732,19 +697,19 @@ int find_axis(BBOX_TREE **Finite, ptrdiff_t first, ptrdiff_t last) mins[X] = bbox->lowerLeft[X]; if(bbox->lowerLeft[X] + bbox->size[X] > maxs[X]) - maxs[X] = bbox->lowerLeft[X]; + maxs[X] = bbox->lowerLeft[X] + bbox->size[X]; if(bbox->lowerLeft[Y] < mins[Y]) mins[Y] = bbox->lowerLeft[Y]; if(bbox->lowerLeft[Y] + bbox->size[Y] > maxs[Y]) - maxs[Y] = bbox->lowerLeft[Y]; + maxs[Y] = bbox->lowerLeft[Y] + bbox->size[Y]; if(bbox->lowerLeft[Z] < mins[Z]) mins[Z] = bbox->lowerLeft[Z]; if(bbox->lowerLeft[Z] + bbox->size[Z] > maxs[Z]) - maxs[Z] = bbox->lowerLeft[Z]; + maxs[Z] = bbox->lowerLeft[Z] + bbox->size[Z]; } e = maxs[X] - mins[X]; @@ -807,11 +772,11 @@ void calc_bbox(BoundingBox *BBox, BBOX_TREE **Finite, ptrdiff_t first, ptrdiff_t Make_BBox_from_min_max(*BBox, bmin, bmax); } -void build_area_table(BBOX_TREE **Finite, ptrdiff_t a, ptrdiff_t b, DBL *areas) +void build_area_table(BBOX_TREE **Finite, ptrdiff_t a, ptrdiff_t b, BBoxScalar *areas) { ptrdiff_t i, imin, dir; - DBL tmin, tmax; - Vector3d bmin, bmax, len; + BBoxScalar tmin, tmax; + BBoxVector3d bmin, bmax, len; BoundingBox *bbox; if (a < b) @@ -823,8 +788,8 @@ void build_area_table(BBOX_TREE **Finite, ptrdiff_t a, ptrdiff_t b, DBL *areas) imin = b; dir = -1; } - bmin = Vector3d(BOUND_HUGE); - bmax = Vector3d(-BOUND_HUGE); + bmin = BBoxVector3d(BOUND_HUGE); + bmax = BBoxVector3d(-BOUND_HUGE); for(i = a; i != (b + dir); i += dir) { @@ -854,74 +819,69 @@ void build_area_table(BBOX_TREE **Finite, ptrdiff_t a, ptrdiff_t b, DBL *areas) } } -int sort_and_split(BBOX_TREE **Root, BBOX_TREE **&Finite, size_t *numOfFiniteObjects, ptrdiff_t first, ptrdiff_t last, size_t& maxfinitecount) +bool sort_and_split(BBOX_TREE **Root, BBOX_TREE **&Finite, size_t *numOfFiniteObjects, ptrdiff_t first, ptrdiff_t last, size_t& maxfinitecount, BBoxScalar **areaCache) { - BBOX_TREE *cd; - ptrdiff_t size, i, best_loc; - DBL *area_left, *area_right; - DBL best_index, new_index; + ptrdiff_t i, best_loc = -1; + ptrdiff_t size = last - first; - int Axis = find_axis(Finite, first, last); - size = last - first; if(size <= 0) - return (1); - - // Actually, we could do this faster in several ways. We could use a - // logn algorithm to find the median along the given axis, and then a - // linear algorithm to partition along the axis. Oh well. + return false; - switch(Axis) + // Don't bother to do any further examinations if the BUNCHING_FACTOR is reached. + if (size > BUNCHING_FACTOR) { - case X: - QSORT(reinterpret_cast(&Finite[first]), size, sizeof(BBOX_TREE *), compboxes); - break; - case Y: - QSORT(reinterpret_cast(&Finite[first]), size, sizeof(BBOX_TREE *), compboxes); - break; - case Z: - QSORT(reinterpret_cast(&Finite[first]), size, sizeof(BBOX_TREE *), compboxes); - break; - } + BBoxScalar *area_left, *area_right; + BBoxScalar best_index, new_index; - // area_left[] and area_right[] hold the surface areas of the bounding - // boxes to the left and right of any given point. E.g. area_left[i] holds - // the surface area of the bounding box containing Finite 0 through i and - // area_right[i] holds the surface area of the box containing Finite - // i through size-1. + int Axis = find_axis(Finite, first, last); - area_left = new DBL[size]; - area_right = new DBL[size]; + // Actually, we could do this faster in several ways. We could use a + // logn algorithm to find the median along the given axis, and then a + // linear algorithm to partition along the axis. Oh well. - // Precalculate the areas for speed. - build_area_table(Finite, first, last - 1, area_left); - build_area_table(Finite, last - 1, first, area_right); - best_index = area_right[0] * (size - 3.0); - best_loc = -1; + switch(Axis) + { + case X: + QSORT(reinterpret_cast(&Finite[first]), size, sizeof(BBOX_TREE *), compboxes); + break; + case Y: + QSORT(reinterpret_cast(&Finite[first]), size, sizeof(BBOX_TREE *), compboxes); + break; + case Z: + QSORT(reinterpret_cast(&Finite[first]), size, sizeof(BBOX_TREE *), compboxes); + break; + } - // Find the most effective point to split. The best location will be - // the one that minimizes the function N1*A1 + N2*A2 where N1 and N2 - // are the number of objects in the two groups and A1 and A2 are the - // surface areas of the bounding boxes of the two groups. + // area_left[] and area_right[] hold the surface areas of the bounding + // boxes to the left and right of any given point. E.g. area_left[i] holds + // the surface area of the bounding box containing Finite 0 through i and + // area_right[i] holds the surface area of the box containing Finite + // i through size-1. - for(i = 0; i < size - 1; i++) - { - new_index = (i + 1) * area_left[i] + (size - 1 - i) * area_right[i + 1]; + area_left = *areaCache; + area_right = area_left + size; - if(new_index < best_index) + // Precalculate the areas for speed. + build_area_table(Finite, first, last - 1, area_left); + build_area_table(Finite, last - 1, first, area_right); + best_index = area_right[0] * float(size-3); // estimated cost of _not_ subdividing + + for(i = 1; i < size; i++) { - best_index = new_index; - best_loc = i + first; + new_index = float(i) * area_left[i-1] + float(size-i) * area_right[i]; + + if(new_index < best_index) + { + best_index = new_index; + best_loc = i + first; + } } } - delete[] area_left; - delete[] area_right; - - // Stop splitting if the BUNCHING_FACTOR is reached or - // if splitting stops being effective. - if((size <= BUNCHING_FACTOR) || (best_loc < 0)) + // Stop splitting if splitting stops being effective. + if(best_loc < 0) { - cd = create_bbox_node(size); + BBOX_TREE *cd = create_bbox_node(size); for(i = 0; i < size; i++) cd->Node[i] = Finite[first+i]; @@ -936,18 +896,22 @@ int sort_and_split(BBOX_TREE **Root, BBOX_TREE **&Finite, size_t *numOfFiniteObj // For debugging only. // TODO MESSAGE Debug_Info("Reallocing Finite to %d\n", maxfinitecount); Finite = reinterpret_cast(POV_REALLOC(Finite, maxfinitecount * sizeof(BBOX_TREE *), "bounding boxes")); + delete[] areaCache; + *areaCache = new BBoxScalar[maxfinitecount]; } Finite[*numOfFiniteObjects] = cd; (*numOfFiniteObjects)++; - return (1); + return false; } + else + { + sort_and_split(Root, Finite, numOfFiniteObjects, first, best_loc, maxfinitecount, areaCache); + sort_and_split(Root, Finite, numOfFiniteObjects, best_loc, last, maxfinitecount, areaCache); - sort_and_split(Root, Finite, numOfFiniteObjects, first, best_loc + 1, maxfinitecount); - sort_and_split(Root, Finite, numOfFiniteObjects, best_loc + 1, last, maxfinitecount); - - return (0); + return true; + } } } diff --git a/source/core/bounding/boundingbox.h b/source/core/bounding/boundingbox.h index bd3806203..8cd04dce4 100644 --- a/source/core/bounding/boundingbox.h +++ b/source/core/bounding/boundingbox.h @@ -159,10 +159,10 @@ typedef const BBOX_TREE* ConstBBoxTreePtr; struct BBox_Tree_Struct { - short Infinite; // Flag if node is infinite - short Entries; // Number of sub-nodes in this node - BoundingBox BBox; // Bounding box of this node BBOX_TREE **Node; // If node: children; if leaf: element + BoundingBox BBox; // Bounding box of this node + short Entries; // Number of sub-nodes in this node + bool Infinite; // Flag if node is infinite }; typedef bool VECTORB[3]; @@ -170,8 +170,8 @@ typedef bool VECTORB[3]; class Rayinfo { public: - BBoxVector3d slab_num; - BBoxVector3d slab_den; + BBoxVector3d origin; ///< Ray's origin. + BBoxVector3d invDirection; ///< Per-dimension inverse of the ray's direction. VECTORB nonzero; VECTORB positive; @@ -179,25 +179,25 @@ class Rayinfo { DBL t; - slab_num[X] = ray.Origin[X]; - slab_num[Y] = ray.Origin[Y]; - slab_num[Z] = ray.Origin[Z]; + origin[X] = ray.Origin[X]; + origin[Y] = ray.Origin[Y]; + origin[Z] = ray.Origin[Z]; if((nonzero[X] = ((t = ray.Direction[X]) != 0.0)) != 0) { - slab_den[X] = 1.0 / t; + invDirection[X] = 1.0 / t; positive[X] = (ray.Direction[X] > 0.0); } if((nonzero[Y] = ((t = ray.Direction[Y]) != 0.0)) != 0) { - slab_den[Y] = 1.0 / t; + invDirection[Y] = 1.0 / t; positive[Y] = (ray.Direction[Y] > 0.0); } if((nonzero[Z] = ((t = ray.Direction[Z]) != 0.0)) != 0) { - slab_den[Z] = 1.0 / t; + invDirection[Z] = 1.0 / t; positive[Z] = (ray.Direction[Z] > 0.0); } } @@ -265,7 +265,6 @@ void Build_BBox_Tree(BBOX_TREE **Root, size_t numOfFiniteObjects, BBOX_TREE **&F void Build_Bounding_Slabs(BBOX_TREE **Root, vector& objects, unsigned int& numberOfFiniteObjects, unsigned int& numberOfInfiniteObjects, unsigned int& numberOfLightSources); void Recompute_BBox(BoundingBox *bbox, const TRANSFORM *trans); -void Recompute_Inverse_BBox(BoundingBox *bbox, const TRANSFORM *trans); bool Intersect_BBox_Tree(BBoxPriorityQueue& pqueue, const BBOX_TREE *Root, const Ray& ray, Intersection *Best_Intersection, TraceThreadData *Thread); bool Intersect_BBox_Tree(BBoxPriorityQueue& pqueue, const BBOX_TREE *Root, const Ray& ray, Intersection *Best_Intersection, const RayObjectCondition& precondition, const RayObjectCondition& postcondition, TraceThreadData *Thread); void Check_And_Enqueue(BBoxPriorityQueue& Queue, const BBOX_TREE *Node, const BoundingBox *BBox, const Rayinfo *rayinfo, RenderStatistics& Stats); diff --git a/source/parser/parser.cpp b/source/parser/parser.cpp index 4ea00f8de..0235da949 100644 --- a/source/parser/parser.cpp +++ b/source/parser/parser.cpp @@ -283,44 +283,53 @@ void Parser::Run() // Check for experimental features char str[512] = ""; - if(mExperimentalFlags.backsideIllumination) - strcat(str, str [0] ? ", backside illumination" : "backside illumination"); - if(mExperimentalFlags.functionHf) - strcat(str, str [0] ? ", function '.hf'" : "function '.hf'"); - if(mExperimentalFlags.meshCamera) - strcat(str, str [0] ? ", mesh camera" : "mesh camera"); - if(mExperimentalFlags.slopeAltitude) - strcat(str, str [0] ? ", slope pattern altitude" : "slope pattern altitude"); - if(mExperimentalFlags.spline) - strcat(str, str [0] ? ", spline" : "spline"); - if(mExperimentalFlags.subsurface) - strcat(str, str [0] ? ", subsurface light transport" : "subsurface light transport"); - if(mExperimentalFlags.tiff) - strcat(str, str [0] ? ", TIFF image support" : "TIFF image support"); - if(mExperimentalFlags.userDefinedCamera) - strcat(str, str [0] ? ", user-defined camera" : "user-defined camera"); - - if (str[0] != '\0') + vector featureList; + std::string featureString; + + if(mExperimentalFlags.backsideIllumination) featureList.push_back("backside illumination"); + if(mExperimentalFlags.functionHf) featureList.push_back("function '.hf'"); + if(mExperimentalFlags.meshCamera) featureList.push_back("mesh camera"); + if(mExperimentalFlags.objImport) featureList.push_back("wavefront obj import"); + if(mExperimentalFlags.slopeAltitude) featureList.push_back("slope pattern altitude"); + if(mExperimentalFlags.spline) featureList.push_back("spline"); + if(mExperimentalFlags.subsurface) featureList.push_back("subsurface light transport"); + if(mExperimentalFlags.tiff) featureList.push_back("TIFF image support"); + if(mExperimentalFlags.userDefinedCamera) featureList.push_back("user-defined camera"); + + for (vector::iterator i = featureList.begin(); i != featureList.end(); ++i) + { + if (!featureString.empty()) + featureString += ", "; + featureString += *i; + } + + if (!featureString.empty()) Warning("This rendering uses the following experimental feature(s): %s.\n" "The design and implementation of these features is likely to change in future\n" "versions of POV-Ray. Backward compatibility with the current implementation is\n" "not guaranteed.", - str); + featureString.c_str()); // Check for beta features - str[0] = '\0'; + featureList.clear(); + featureString.clear(); + + if(mBetaFeatureFlags.videoCapture) featureList.push_back("video capture"); + if(mBetaFeatureFlags.realTimeRaytracing) featureList.push_back("real-time raytracing render loop"); - if(mBetaFeatureFlags.videoCapture) - strcat(str, str [0] ? ", video capture" : "video capture"); - if(mBetaFeatureFlags.realTimeRaytracing) - strcat(str, str [0] ? ", real-time raytracing render loop" : "real-time raytracing render loop"); + for (vector::iterator i = featureList.begin(); i != featureList.end(); ++i) + { + if (!featureString.empty()) + featureString += ", "; + featureString += *i; + } - if (str[0] != '\0') + if (!featureString.empty()) Warning("This rendering uses the following beta-test feature(s): %s.\n" "The implementation of these features is likely to change or be completely\n" "removed in subsequent beta-test versions of POV-Ray. There is no guarantee\n" "that they will be available in the next full release version.\n", - str); + featureString.c_str()); if ((sceneData->bspMaxDepth != 0) || (sceneData->bspObjectIsectCost != 0.0f) || (sceneData->bspBaseAccessCost != 0.0f) || @@ -3713,6 +3722,7 @@ ObjectPtr Parser::Parse_Mesh() END_CASE CASE (OBJ_TOKEN) + mExperimentalFlags.objImport = true; Parse_Obj (Object); END_CASE @@ -8005,6 +8015,8 @@ ObjectPtr Parser::Parse_Object_Mods (ObjectPtr Object) /* Get bounding boxes' volumes. */ + // TODO - Area is probably a better measure to decide which box is better. + // TODO - Doesn't this mechanism prevent users from reliably overriding broken default boxes? BOUNDS_VOLUME(V1, BBox); BOUNDS_VOLUME(V2, Object->BBox); @@ -8043,6 +8055,7 @@ ObjectPtr Parser::Parse_Object_Mods (ObjectPtr Object) /* Get bounding boxes' volumes. */ + // TODO - Area is probably a better measure to decide which box is better. BOUNDS_VOLUME(V1, BBox); BOUNDS_VOLUME(V2, Object->BBox); diff --git a/source/parser/parser.h b/source/parser/parser.h index 1dfbb9b42..3fe6f4d36 100644 --- a/source/parser/parser.h +++ b/source/parser/parser.h @@ -159,6 +159,7 @@ struct ExperimentalFlags bool backsideIllumination : 1; bool functionHf : 1; bool meshCamera : 1; + bool objImport : 1; bool slopeAltitude : 1; bool spline : 1; bool subsurface : 1; @@ -169,6 +170,7 @@ struct ExperimentalFlags backsideIllumination(false), functionHf(false), meshCamera(false), + objImport(false), slopeAltitude(false), spline(false), subsurface(false), diff --git a/unix/VERSION b/unix/VERSION index 38f8d10c0..c47f0750e 100644 --- a/unix/VERSION +++ b/unix/VERSION @@ -1 +1 @@ -3.7.1-x.obj.8785394 +3.7.1-x.obj.8787748 From 0e7dec66ad1700ab6452256aa681c977701982ea Mon Sep 17 00:00:00 2001 From: Christoph Lipka Date: Thu, 15 Sep 2016 16:35:42 +0200 Subject: [PATCH 3/3] Fix stupid error in previous commit. --- source/base/mathutil.h | 2 ++ source/base/version.h | 2 +- unix/VERSION | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/source/base/mathutil.h b/source/base/mathutil.h index ea3b68d17..299a6dcd5 100644 --- a/source/base/mathutil.h +++ b/source/base/mathutil.h @@ -155,4 +155,6 @@ inline bool IsInRange (T1 value, T2 min, T2 max) return (min <= value) && (value <= max); } +} + #endif // POVRAY_BASE_MATHUTIL_H diff --git a/source/base/version.h b/source/base/version.h index a549d76a5..e1e8585e1 100644 --- a/source/base/version.h +++ b/source/base/version.h @@ -45,7 +45,7 @@ #define OFFICIAL_VERSION_STRING "3.7.1" #define OFFICIAL_VERSION_NUMBER 371 -#define POV_RAY_PRERELEASE "x.obj.8787748" +#define POV_RAY_PRERELEASE "x.obj.8787755" #if (POV_RAY_IS_AUTOBUILD == 1) && ((POV_RAY_IS_OFFICIAL == 1) || (POV_RAY_IS_SEMI_OFFICIAL == 1)) #ifdef POV_RAY_PRERELEASE diff --git a/unix/VERSION b/unix/VERSION index c47f0750e..8f5e8321a 100644 --- a/unix/VERSION +++ b/unix/VERSION @@ -1 +1 @@ -3.7.1-x.obj.8787748 +3.7.1-x.obj.8787755