From 1376af237bae818d85a988204fd89e4eb47cfe7e Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Tue, 8 May 2018 00:08:19 +0800 Subject: [PATCH] Add auto uv unwrapping. Use https://github.com/Thekla/thekla_atlas to generate the texture. This commit still have some issues, some faces not satisfy thekla_atlas in some cases. - Add texture preview widget. - Fix single node black color issue. - Export texture in .gltf file. - Fix node order random issue in mesh generation. --- ACKNOWLEDGEMENTS.html | 52 + docs/builds.rst | 2 +- docs/interface/menubar.rst | 10 +- dust3d.pro | 185 +- src/{util.cpp => dust3dutil.cpp} | 2 +- src/{util.h => dust3dutil.h} | 4 +- src/gltffile.cpp | 51 +- src/gltffile.h | 7 +- src/meshgenerator.cpp | 8 +- src/meshresultcontext.cpp | 231 +- src/meshresultcontext.h | 47 +- src/meshresultpostprocessor.cpp | 36 + src/meshresultpostprocessor.h | 21 + src/skeletondocument.cpp | 143 +- src/skeletondocument.h | 22 +- src/skeletondocumentwindow.cpp | 113 +- src/skeletondocumentwindow.h | 10 +- src/skeletongenerator.cpp | 10 - src/skeletongraphicswidget.cpp | 2 +- src/skeletongraphicswidget.h | 2 +- src/skeletonsnapshot.cpp | 2 +- src/texturegenerator.cpp | 141 + src/texturegenerator.h | 32 + src/textureguidewidget.cpp | 73 + src/textureguidewidget.h | 26 + src/uvunwrapper.cpp | 2 - src/uvunwrapper.h | 8 - .../meshlite_unstable_vc14_x64/meshlite.dll | Bin 495104 -> 512512 bytes .../meshlite.dll.lib | Bin 14702 -> 15548 bytes .../meshlite_unstable_vc14_x64/meshlite.h | 3 + .../meshlite_unstable_vc14_x86/meshlite.dll | Bin 410112 -> 421376 bytes .../meshlite.dll.lib | Bin 14952 -> 15810 bytes .../meshlite_unstable_vc14_x86/meshlite.h | 3 + thirdparty/thekla_atlas/.gitattributes | 10 + thirdparty/thekla_atlas/.gitignore | 16 + thirdparty/thekla_atlas/CMakeLists.txt | 25 + thirdparty/thekla_atlas/LICENSE | 8 + thirdparty/thekla_atlas/README.md | 20 + .../extern/poshlib/CMakeLists.txt | 7 + thirdparty/thekla_atlas/extern/poshlib/posh.c | 1006 ++++ thirdparty/thekla_atlas/extern/poshlib/posh.h | 1035 ++++ .../thekla_atlas/extern/stb/stb_image.h | 4954 +++++++++++++++++ .../extern/tinyobj/tiny_obj_loader.h | 1035 ++++ .../projects/vc9/nvcore/nvcore.vcproj | 431 ++ .../projects/vc9/nvimage/nvimage.vcproj | 311 ++ .../projects/vc9/nvmath/nvmath.vcproj | 448 ++ .../projects/vc9/nvmesh/nvmesh.vcproj | 626 +++ .../thekla_atlas/projects/vc9/thekla.sln | 79 + .../vc9/thekla_atlas/thekla_atlas.vcproj | 297 + thirdparty/thekla_atlas/src/nvconfig.h | 37 + thirdparty/thekla_atlas/src/nvconfig.h.in | 22 + thirdparty/thekla_atlas/src/nvcore/Array.h | 184 + thirdparty/thekla_atlas/src/nvcore/Array.inl | 452 ++ thirdparty/thekla_atlas/src/nvcore/BitArray.h | 250 + .../thekla_atlas/src/nvcore/CMakeLists.txt | 74 + thirdparty/thekla_atlas/src/nvcore/Debug.cpp | 1347 +++++ thirdparty/thekla_atlas/src/nvcore/Debug.h | 246 + .../thekla_atlas/src/nvcore/DefsGnucDarwin.h | 57 + .../thekla_atlas/src/nvcore/DefsGnucLinux.h | 59 + .../thekla_atlas/src/nvcore/DefsGnucWin32.h | 65 + .../thekla_atlas/src/nvcore/DefsVcWin32.h | 94 + .../thekla_atlas/src/nvcore/FileSystem.cpp | 75 + .../thekla_atlas/src/nvcore/FileSystem.h | 24 + thirdparty/thekla_atlas/src/nvcore/ForEach.h | 71 + thirdparty/thekla_atlas/src/nvcore/Hash.h | 83 + thirdparty/thekla_atlas/src/nvcore/HashMap.h | 174 + .../thekla_atlas/src/nvcore/HashMap.inl | 550 ++ thirdparty/thekla_atlas/src/nvcore/Memory.cpp | 149 + thirdparty/thekla_atlas/src/nvcore/Memory.h | 72 + thirdparty/thekla_atlas/src/nvcore/Ptr.h | 322 ++ .../thekla_atlas/src/nvcore/RadixSort.cpp | 285 + .../thekla_atlas/src/nvcore/RadixSort.h | 75 + .../thekla_atlas/src/nvcore/RefCounted.h | 149 + .../thekla_atlas/src/nvcore/StdStream.h | 474 ++ thirdparty/thekla_atlas/src/nvcore/StrLib.cpp | 796 +++ thirdparty/thekla_atlas/src/nvcore/StrLib.h | 433 ++ thirdparty/thekla_atlas/src/nvcore/Stream.h | 164 + thirdparty/thekla_atlas/src/nvcore/Utils.h | 315 ++ thirdparty/thekla_atlas/src/nvcore/nvcore.h | 357 ++ thirdparty/thekla_atlas/src/nvcore/scanf.c | 641 +++ .../thekla_atlas/src/nvimage/BitMap.cpp | 27 + thirdparty/thekla_atlas/src/nvimage/BitMap.h | 87 + .../thekla_atlas/src/nvimage/CMakeLists.txt | 51 + thirdparty/thekla_atlas/src/nvimage/Image.cpp | 210 + thirdparty/thekla_atlas/src/nvimage/Image.h | 89 + thirdparty/thekla_atlas/src/nvimage/nvimage.h | 48 + thirdparty/thekla_atlas/src/nvmath/Basis.cpp | 270 + thirdparty/thekla_atlas/src/nvmath/Basis.h | 82 + thirdparty/thekla_atlas/src/nvmath/Box.cpp | 119 + thirdparty/thekla_atlas/src/nvmath/Box.h | 105 + thirdparty/thekla_atlas/src/nvmath/Box.inl | 154 + .../thekla_atlas/src/nvmath/CMakeLists.txt | 38 + thirdparty/thekla_atlas/src/nvmath/Color.h | 150 + .../thekla_atlas/src/nvmath/ConvexHull.cpp | 120 + .../thekla_atlas/src/nvmath/ConvexHull.h | 17 + .../thekla_atlas/src/nvmath/Fitting.cpp | 1205 ++++ thirdparty/thekla_atlas/src/nvmath/Fitting.h | 50 + thirdparty/thekla_atlas/src/nvmath/KahanSum.h | 39 + thirdparty/thekla_atlas/src/nvmath/Matrix.cpp | 441 ++ thirdparty/thekla_atlas/src/nvmath/Matrix.h | 113 + thirdparty/thekla_atlas/src/nvmath/Matrix.inl | 1274 +++++ thirdparty/thekla_atlas/src/nvmath/Morton.h | 83 + thirdparty/thekla_atlas/src/nvmath/Plane.cpp | 27 + thirdparty/thekla_atlas/src/nvmath/Plane.h | 42 + thirdparty/thekla_atlas/src/nvmath/Plane.inl | 50 + .../thekla_atlas/src/nvmath/ProximityGrid.cpp | 158 + .../thekla_atlas/src/nvmath/ProximityGrid.h | 99 + .../thekla_atlas/src/nvmath/Quaternion.h | 213 + thirdparty/thekla_atlas/src/nvmath/Random.cpp | 54 + thirdparty/thekla_atlas/src/nvmath/Random.h | 376 ++ thirdparty/thekla_atlas/src/nvmath/Solver.cpp | 744 +++ thirdparty/thekla_atlas/src/nvmath/Solver.h | 24 + thirdparty/thekla_atlas/src/nvmath/Sparse.cpp | 889 +++ thirdparty/thekla_atlas/src/nvmath/Sparse.h | 204 + thirdparty/thekla_atlas/src/nvmath/Sphere.cpp | 431 ++ thirdparty/thekla_atlas/src/nvmath/Sphere.h | 43 + .../src/nvmath/TypeSerialization.cpp | 54 + .../src/nvmath/TypeSerialization.h | 35 + thirdparty/thekla_atlas/src/nvmath/Vector.cpp | 4 + thirdparty/thekla_atlas/src/nvmath/Vector.h | 151 + thirdparty/thekla_atlas/src/nvmath/Vector.inl | 919 +++ thirdparty/thekla_atlas/src/nvmath/ftoi.h | 253 + thirdparty/thekla_atlas/src/nvmath/nvmath.h | 334 ++ .../thekla_atlas/src/nvmesh/BaseMesh.cpp | 19 + thirdparty/thekla_atlas/src/nvmesh/BaseMesh.h | 72 + .../thekla_atlas/src/nvmesh/CMakeLists.txt | 73 + .../thekla_atlas/src/nvmesh/MeshBuilder.cpp | 1000 ++++ .../thekla_atlas/src/nvmesh/MeshBuilder.h | 119 + .../thekla_atlas/src/nvmesh/MeshTopology.cpp | 122 + .../thekla_atlas/src/nvmesh/MeshTopology.h | 66 + .../thekla_atlas/src/nvmesh/QuadTriMesh.cpp | 36 + .../thekla_atlas/src/nvmesh/QuadTriMesh.h | 60 + .../thekla_atlas/src/nvmesh/TriMesh.cpp | 25 + thirdparty/thekla_atlas/src/nvmesh/TriMesh.h | 51 + .../src/nvmesh/geometry/Bounds.cpp | 54 + .../thekla_atlas/src/nvmesh/geometry/Bounds.h | 28 + .../src/nvmesh/geometry/CMakeLists.txt | 2 + .../src/nvmesh/geometry/Measurements.cpp | 36 + .../src/nvmesh/geometry/Measurements.h | 18 + .../thekla_atlas/src/nvmesh/halfedge/Edge.cpp | 57 + .../thekla_atlas/src/nvmesh/halfedge/Edge.h | 70 + .../thekla_atlas/src/nvmesh/halfedge/Face.cpp | 268 + .../thekla_atlas/src/nvmesh/halfedge/Face.h | 106 + .../thekla_atlas/src/nvmesh/halfedge/Mesh.cpp | 1284 +++++ .../thekla_atlas/src/nvmesh/halfedge/Mesh.h | 274 + .../src/nvmesh/halfedge/Vertex.cpp | 94 + .../thekla_atlas/src/nvmesh/halfedge/Vertex.h | 221 + thirdparty/thekla_atlas/src/nvmesh/nvmesh.cpp | 2 + thirdparty/thekla_atlas/src/nvmesh/nvmesh.h | 34 + .../thekla_atlas/src/nvmesh/param/Atlas.cpp | 1515 +++++ .../thekla_atlas/src/nvmesh/param/Atlas.h | 183 + .../src/nvmesh/param/AtlasBuilder.cpp | 1320 +++++ .../src/nvmesh/param/AtlasBuilder.h | 111 + .../src/nvmesh/param/AtlasPacker.cpp | 1379 +++++ .../src/nvmesh/param/AtlasPacker.h | 63 + .../src/nvmesh/param/BoundaryMap.cpp | 50 + .../src/nvmesh/param/BoundaryMap.h | 15 + .../src/nvmesh/param/ConformalMap.cpp | 204 + .../src/nvmesh/param/ConformalMap.h | 16 + .../nvmesh/param/LeastSquaresConformalMap.cpp | 483 ++ .../nvmesh/param/LeastSquaresConformalMap.h | 15 + .../nvmesh/param/OrthogonalProjectionMap.cpp | 99 + .../nvmesh/param/OrthogonalProjectionMap.h | 15 + .../nvmesh/param/ParameterizationQuality.cpp | 323 ++ .../nvmesh/param/ParameterizationQuality.h | 56 + .../src/nvmesh/param/SingleFaceMap.cpp | 53 + .../src/nvmesh/param/SingleFaceMap.h | 18 + .../thekla_atlas/src/nvmesh/param/Util.cpp | 326 ++ .../thekla_atlas/src/nvmesh/param/Util.h | 18 + .../src/nvmesh/raster/ClippedTriangle.h | 159 + .../thekla_atlas/src/nvmesh/raster/Raster.cpp | 626 +++ .../thekla_atlas/src/nvmesh/raster/Raster.h | 49 + .../thekla_atlas/src/nvmesh/weld/Snap.cpp | 100 + .../thekla_atlas/src/nvmesh/weld/Snap.h | 18 + .../src/nvmesh/weld/VertexWeld.cpp | 205 + .../thekla_atlas/src/nvmesh/weld/VertexWeld.h | 19 + .../thekla_atlas/src/nvmesh/weld/Weld.h | 171 + .../thekla_atlas/src/thekla/thekla_atlas.cpp | 263 + .../thekla_atlas/src/thekla/thekla_atlas.h | 116 + .../src/thekla/thekla_atlas_test.cpp | 58 + .../src/thekla/thekla_mesh_load.h | 110 + 181 files changed, 41743 insertions(+), 132 deletions(-) rename src/{util.cpp => dust3dutil.cpp} (95%) rename src/{util.h => dust3dutil.h} (88%) create mode 100644 src/meshresultpostprocessor.cpp create mode 100644 src/meshresultpostprocessor.h create mode 100644 src/texturegenerator.cpp create mode 100644 src/texturegenerator.h create mode 100644 src/textureguidewidget.cpp create mode 100644 src/textureguidewidget.h delete mode 100644 src/uvunwrapper.cpp delete mode 100644 src/uvunwrapper.h create mode 100755 thirdparty/thekla_atlas/.gitattributes create mode 100755 thirdparty/thekla_atlas/.gitignore create mode 100755 thirdparty/thekla_atlas/CMakeLists.txt create mode 100755 thirdparty/thekla_atlas/LICENSE create mode 100755 thirdparty/thekla_atlas/README.md create mode 100755 thirdparty/thekla_atlas/extern/poshlib/CMakeLists.txt create mode 100755 thirdparty/thekla_atlas/extern/poshlib/posh.c create mode 100755 thirdparty/thekla_atlas/extern/poshlib/posh.h create mode 100755 thirdparty/thekla_atlas/extern/stb/stb_image.h create mode 100755 thirdparty/thekla_atlas/extern/tinyobj/tiny_obj_loader.h create mode 100755 thirdparty/thekla_atlas/projects/vc9/nvcore/nvcore.vcproj create mode 100755 thirdparty/thekla_atlas/projects/vc9/nvimage/nvimage.vcproj create mode 100755 thirdparty/thekla_atlas/projects/vc9/nvmath/nvmath.vcproj create mode 100755 thirdparty/thekla_atlas/projects/vc9/nvmesh/nvmesh.vcproj create mode 100755 thirdparty/thekla_atlas/projects/vc9/thekla.sln create mode 100755 thirdparty/thekla_atlas/projects/vc9/thekla_atlas/thekla_atlas.vcproj create mode 100755 thirdparty/thekla_atlas/src/nvconfig.h create mode 100755 thirdparty/thekla_atlas/src/nvconfig.h.in create mode 100755 thirdparty/thekla_atlas/src/nvcore/Array.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/Array.inl create mode 100755 thirdparty/thekla_atlas/src/nvcore/BitArray.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/CMakeLists.txt create mode 100755 thirdparty/thekla_atlas/src/nvcore/Debug.cpp create mode 100755 thirdparty/thekla_atlas/src/nvcore/Debug.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/DefsGnucDarwin.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/DefsGnucLinux.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/DefsGnucWin32.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/DefsVcWin32.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/FileSystem.cpp create mode 100755 thirdparty/thekla_atlas/src/nvcore/FileSystem.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/ForEach.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/Hash.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/HashMap.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/HashMap.inl create mode 100755 thirdparty/thekla_atlas/src/nvcore/Memory.cpp create mode 100755 thirdparty/thekla_atlas/src/nvcore/Memory.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/Ptr.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/RadixSort.cpp create mode 100755 thirdparty/thekla_atlas/src/nvcore/RadixSort.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/RefCounted.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/StdStream.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/StrLib.cpp create mode 100755 thirdparty/thekla_atlas/src/nvcore/StrLib.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/Stream.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/Utils.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/nvcore.h create mode 100755 thirdparty/thekla_atlas/src/nvcore/scanf.c create mode 100755 thirdparty/thekla_atlas/src/nvimage/BitMap.cpp create mode 100755 thirdparty/thekla_atlas/src/nvimage/BitMap.h create mode 100755 thirdparty/thekla_atlas/src/nvimage/CMakeLists.txt create mode 100755 thirdparty/thekla_atlas/src/nvimage/Image.cpp create mode 100755 thirdparty/thekla_atlas/src/nvimage/Image.h create mode 100755 thirdparty/thekla_atlas/src/nvimage/nvimage.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Basis.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/Basis.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Box.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/Box.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Box.inl create mode 100755 thirdparty/thekla_atlas/src/nvmath/CMakeLists.txt create mode 100755 thirdparty/thekla_atlas/src/nvmath/Color.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/ConvexHull.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/ConvexHull.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Fitting.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/Fitting.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/KahanSum.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Matrix.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/Matrix.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Matrix.inl create mode 100755 thirdparty/thekla_atlas/src/nvmath/Morton.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Plane.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/Plane.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Plane.inl create mode 100755 thirdparty/thekla_atlas/src/nvmath/ProximityGrid.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/ProximityGrid.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Quaternion.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Random.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/Random.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Solver.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/Solver.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Sparse.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/Sparse.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Sphere.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/Sphere.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/TypeSerialization.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/TypeSerialization.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Vector.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmath/Vector.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/Vector.inl create mode 100755 thirdparty/thekla_atlas/src/nvmath/ftoi.h create mode 100755 thirdparty/thekla_atlas/src/nvmath/nvmath.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/BaseMesh.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/BaseMesh.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/CMakeLists.txt create mode 100755 thirdparty/thekla_atlas/src/nvmesh/MeshBuilder.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/MeshBuilder.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/MeshTopology.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/MeshTopology.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/QuadTriMesh.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/QuadTriMesh.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/TriMesh.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/TriMesh.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/geometry/Bounds.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/geometry/Bounds.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/geometry/CMakeLists.txt create mode 100755 thirdparty/thekla_atlas/src/nvmesh/geometry/Measurements.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/geometry/Measurements.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/halfedge/Edge.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/halfedge/Edge.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/halfedge/Face.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/halfedge/Face.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/halfedge/Mesh.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/halfedge/Mesh.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/halfedge/Vertex.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/halfedge/Vertex.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/nvmesh.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/nvmesh.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/Atlas.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/Atlas.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/AtlasBuilder.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/AtlasBuilder.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/AtlasPacker.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/AtlasPacker.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/BoundaryMap.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/BoundaryMap.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/ConformalMap.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/ConformalMap.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/LeastSquaresConformalMap.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/LeastSquaresConformalMap.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/OrthogonalProjectionMap.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/OrthogonalProjectionMap.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/ParameterizationQuality.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/ParameterizationQuality.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/SingleFaceMap.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/SingleFaceMap.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/Util.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/param/Util.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/raster/ClippedTriangle.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/raster/Raster.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/raster/Raster.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/weld/Snap.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/weld/Snap.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/weld/VertexWeld.cpp create mode 100755 thirdparty/thekla_atlas/src/nvmesh/weld/VertexWeld.h create mode 100755 thirdparty/thekla_atlas/src/nvmesh/weld/Weld.h create mode 100755 thirdparty/thekla_atlas/src/thekla/thekla_atlas.cpp create mode 100755 thirdparty/thekla_atlas/src/thekla/thekla_atlas.h create mode 100755 thirdparty/thekla_atlas/src/thekla/thekla_atlas_test.cpp create mode 100755 thirdparty/thekla_atlas/src/thekla/thekla_mesh_load.h diff --git a/ACKNOWLEDGEMENTS.html b/ACKNOWLEDGEMENTS.html index 7e36530a..0ab511c2 100644 --- a/ACKNOWLEDGEMENTS.html +++ b/ACKNOWLEDGEMENTS.html @@ -446,3 +446,55 @@ https://www.reddit.com/r/gamedev/comments/5iuf3h/i_am_writting_a_3d_monster_mode OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +

thekla_atlas

+
+    Copyright (c) 2013 Thekla, Inc
+
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+    
+    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+    
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ +

nvmesh

+
+    public domain -- Ignacio CastaƱo 
+
+ +

poshlib

+
+    Copyright (c) 2004, Brian Hook
+    All rights reserved.
+    
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are
+    met:
+    
+        * Redistributions of source code must retain the above copyright
+            notice, this list of conditions and the following disclaimer.
+    
+        * Redistributions in binary form must reproduce the above
+            copyright notice, this list of conditions and the following
+            disclaimer in the documentation and/or other materials provided
+            with the distribution.
+    
+        * The names of this package'ss contributors contributors may not
+            be used to endorse or promote products derived from this
+            software without specific prior written permission.
+    
+    
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/docs/builds.rst b/docs/builds.rst index 6ab763d0..34667ddd 100644 --- a/docs/builds.rst +++ b/docs/builds.rst @@ -7,7 +7,7 @@ The core mesh algorithms of Dust3D written in Rust language, located in meshlite https://github.com/huxingyi/meshlite -The UI of Dust3D built in Qt5, the only thirdparty dependency is CGAL library, however, CGAL will introduce some new dependencies, such as boost and gmp library. +The UI of Dust3D built in Qt5, the only thirdparty dependency which should been compiled separately is the CGAL library, however, CGAL will introduce some new dependencies, such as boost and gmp library. Prerequisites =============== diff --git a/docs/interface/menubar.rst b/docs/interface/menubar.rst index aa9a948d..ba1bf514 100644 --- a/docs/interface/menubar.rst +++ b/docs/interface/menubar.rst @@ -31,9 +31,8 @@ Save all openned window. Export... ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you want mesh only result, export as Wavefront (.obj) format. -Choose GL Transmission Format (.gltf) to export all results in one gltf file, the results including autogenerated Meshes, Skeleton, and Materials. -Because .gltf is a very new format, not every 3D software officially supports it, and there are some plugins for blender to support gltf export and import, but mostly are buggy at current stage. -You can use Don McCurdy's online website, glTF Viewer https://gltf-viewer.donmccurdy.com/ to check your exported result. +Choose GL Transmission Format (.gltf) to export all results in one gltf file, the results including autogenerated Meshes, Skeleton, and Texture. +Because .gltf is a very new format, not every 3D software officially supports it, you can use Don McCurdy's online website, glTF Viewer https://gltf-viewer.donmccurdy.com/ to check your exported result, make sure you drag the folder which including the .gltf file and .png texture file into the website. Change Turnaround... ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -114,6 +113,11 @@ Toggle Bones The skeleton would also been autogenerated after meshes got generated. Toggle this item to show or hide the Rendered Skeleton from canvas. +Show Texture +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The texture would also been autogenerated after meshes got generated. +Click the recycle button in the up-left corner to regenerate if the texture is not good. + Show Parts List ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The Parts List Panel is a tool window, if you closed it by accident, you can show it back here. diff --git a/dust3d.pro b/dust3d.pro index 86aa3a80..4d881958 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -59,8 +59,8 @@ HEADERS += src/aboutwidget.h SOURCES += src/meshgenerator.cpp HEADERS += src/meshgenerator.h -SOURCES += src/util.cpp -HEADERS += src/util.h +SOURCES += src/dust3dutil.cpp +HEADERS += src/dust3dutil.h SOURCES += src/turnaroundloader.cpp HEADERS += src/turnaroundloader.h @@ -86,8 +86,8 @@ HEADERS += src/mesh.h SOURCES += src/meshutil.cpp HEADERS += src/meshutil.h -SOURCES += src/uvunwrapper.cpp -HEADERS += src/uvunwrapper.h +SOURCES += src/texturegenerator.cpp +HEADERS += src/texturegenerator.h SOURCES += src/skeletongenerator.cpp HEADERS += src/skeletongenerator.h @@ -95,6 +95,9 @@ HEADERS += src/skeletongenerator.h SOURCES += src/meshresultcontext.cpp HEADERS += src/meshresultcontext.h +SOURCES += src/meshresultpostprocessor.cpp +HEADERS += src/meshresultpostprocessor.h + SOURCES += src/positionmap.cpp HEADERS += src/positionmap.h @@ -107,6 +110,9 @@ HEADERS += src/logbrowserdialog.h SOURCES += src/floatnumberwidget.cpp HEADERS += src/floatnumberwidget.h +SOURCES += src/textureguidewidget.cpp +HEADERS += src/textureguidewidget.h + SOURCES += src/main.cpp HEADERS += src/version.h @@ -181,6 +187,177 @@ unix:!macx { INCLUDEPATH += thirdparty/json +INCLUDEPATH += thirdparty/thekla_atlas/src +INCLUDEPATH += thirdparty/thekla_atlas/extern/poshlib +INCLUDEPATH += thirdparty/thekla_atlas/src/nvmesh +INCLUDEPATH += thirdparty/thekla_atlas/src/nvmesh/param +INCLUDEPATH += thirdparty/thekla_atlas/src/nvcore +INCLUDEPATH += thirdparty/thekla_atlas/src/nvimage +INCLUDEPATH += thirdparty/thekla_atlas/src/nvmath + +SOURCES += thirdparty/thekla_atlas/extern/poshlib/posh.c +HEADERS += thirdparty/thekla_atlas/extern/poshlib/posh.h + +SOURCES += thirdparty/thekla_atlas/src/thekla/thekla_atlas.cpp +HEADERS += thirdparty/thekla_atlas/src/thekla/thekla_atlas.h + +HEADERS += thirdparty/thekla_atlas/src/nvcore/Stream.h + +SOURCES += thirdparty/thekla_atlas/src/nvcore/Debug.cpp +HEADERS += thirdparty/thekla_atlas/src/nvcore/Debug.h + +HEADERS += thirdparty/thekla_atlas/src/nvcore/StdStream.h + +SOURCES += thirdparty/thekla_atlas/src/nvcore/StrLib.cpp +HEADERS += thirdparty/thekla_atlas/src/nvcore/StrLib.h + +HEADERS += thirdparty/thekla_atlas/src/nvcore/Utils.h + +SOURCES += thirdparty/thekla_atlas/src/nvcore/Array.inl +HEADERS += thirdparty/thekla_atlas/src/nvcore/Array.h + +SOURCES += thirdparty/thekla_atlas/src/nvcore/Memory.cpp +HEADERS += thirdparty/thekla_atlas/src/nvcore/Memory.h + +SOURCES += thirdparty/thekla_atlas/src/nvcore/RadixSort.cpp +HEADERS += thirdparty/thekla_atlas/src/nvcore/RadixSort.h + +SOURCES += thirdparty/thekla_atlas/src/nvcore/HashMap.inl +HEADERS += thirdparty/thekla_atlas/src/nvcore/HashMap.h + +HEADERS += thirdparty/thekla_atlas/src/nvcore/Hash.h + +HEADERS += thirdparty/thekla_atlas/src/nvcore/ForEach.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/nvmesh.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/nvmesh.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/BaseMesh.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/BaseMesh.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/MeshBuilder.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/MeshBuilder.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/TriMesh.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/TriMesh.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/QuadTriMesh.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/QuadTriMesh.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/MeshTopology.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/MeshTopology.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/halfedge/Edge.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/halfedge/Edge.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/halfedge/Mesh.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/halfedge/Mesh.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/halfedge/Face.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/halfedge/Face.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/halfedge/Vertex.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/halfedge/Vertex.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/geometry/Bounds.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/geometry/Bounds.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/geometry/Measurements.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/geometry/Measurements.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/raster/Raster.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/raster/raster.h + +HEADERS += thirdparty/thekla_atlas/src/nvmesh/raster/ClippedTriangle.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/param/Atlas.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/param/Atlas.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/param/AtlasBuilder.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/param/AtlasBuilder.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/param/AtlasPacker.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/param/AtlasPacker.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/param/ConformalMap.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/param/ConformalMap.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/param/LeastSquaresConformalMap.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/param/LeastSquaresConformalMap.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/param/OrthogonalProjectionMap.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/param/OrthogonalProjectionMap.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/param/ParameterizationQuality.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/param/ParameterizationQuality.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/param/SingleFaceMap.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/param/SingleFaceMap.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/param/Util.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/param/Util.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/weld/VertexWeld.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/weld/VertexWeld.h + +HEADERS += thirdparty/thekla_atlas/src/nvmesh/weld/Weld.h + +SOURCES += thirdparty/thekla_atlas/src/nvmesh/weld/Snap.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmesh/weld/Snap.h + +SOURCES += thirdparty/thekla_atlas/src/nvimage/Image.cpp +HEADERS += thirdparty/thekla_atlas/src/nvimage/Image.h + +SOURCES += thirdparty/thekla_atlas/src/nvimage/BitMap.cpp +HEADERS += thirdparty/thekla_atlas/src/nvimage/BitMap.h + +HEADERS += thirdparty/thekla_atlas/src/nvimage/nvimage.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/Basis.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/Basis.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/Box.inl +SOURCES += thirdparty/thekla_atlas/src/nvmath/Box.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/Box.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/Color.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/ConvexHull.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/ConvexHull.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/Fitting.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/Fitting.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/KahanSum.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/Matrix.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/Plane.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/Plane.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/ProximityGrid.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/ProximityGrid.h + +HEADERS += thirdparty/thekla_atlas/src/nvmath/Quaternion.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/Random.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/Random.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/Solver.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/Solver.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/Sparse.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/Sparse.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/TypeSerialization.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/TypeSerialization.h + +SOURCES += thirdparty/thekla_atlas/src/nvmath/Vector.inl +SOURCES += thirdparty/thekla_atlas/src/nvmath/Vector.cpp +HEADERS += thirdparty/thekla_atlas/src/nvmath/Vector.h + +HEADERS += thirdparty/thekla_atlas/src/nvmath/ftoi.h + INCLUDEPATH += $$MESHLITE_DIR LIBS += -L$$MESHLITE_DIR -l$$MESHLITE_LIBNAME diff --git a/src/util.cpp b/src/dust3dutil.cpp similarity index 95% rename from src/util.cpp rename to src/dust3dutil.cpp index f5a40108..70bd8347 100644 --- a/src/util.cpp +++ b/src/dust3dutil.cpp @@ -1,5 +1,5 @@ #include -#include "util.h" +#include "dust3dutil.h" QString valueOfKeyInMapOrEmpty(const std::map &map, const QString &key) { diff --git a/src/util.h b/src/dust3dutil.h similarity index 88% rename from src/util.h rename to src/dust3dutil.h index 1c871ec9..208a6625 100644 --- a/src/util.h +++ b/src/dust3dutil.h @@ -1,5 +1,5 @@ -#ifndef UTIL_H -#define UTIL_H +#ifndef DUST3D_UTIL_H +#define DUST3D_UTIL_H #include #include #include diff --git a/src/gltffile.cpp b/src/gltffile.cpp index 3a858505..9cb4b979 100644 --- a/src/gltffile.cpp +++ b/src/gltffile.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include "gltffile.h" #include "version.h" #include "util.h" @@ -15,7 +17,8 @@ // http://quaternions.online/ // https://en.m.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions?wprov=sfla1 -GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext) +GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext, const QString &filename) : + m_filename(filename) { const BmeshNode *rootNode = resultContext.centerBmeshNode(); if (!rootNode) { @@ -23,6 +26,10 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext) return; } + QFileInfo nameInfo(filename); + QString textureFilenameWithoutPath = nameInfo.completeBaseName() + ".png"; + m_textureFilename = nameInfo.path() + QDir::separator() + textureFilenameWithoutPath; + JointInfo rootHandleJoint; { rootHandleJoint.jointIndex = m_tracedJoints.size(); @@ -117,23 +124,35 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext) m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViews0FromOffset; alignBinaries(); bufferViewIndex++; + + m_json["textures"][0]["sampler"] = 0; + m_json["textures"][0]["source"] = 0; + + m_json["images"][0]["uri"] = textureFilenameWithoutPath.toUtf8().constData(); + + m_json["samplers"][0]["magFilter"] = 9729; + m_json["samplers"][0]["minFilter"] = 9987; + m_json["samplers"][0]["wrapS"] = 33648; + m_json["samplers"][0]["wrapT"] = 33648; int primitiveIndex = 0; - for (const auto &part: resultContext.resultParts()) { + for (const auto &part: resultContext.parts()) { int bufferViewFromOffset; m_json["meshes"][0]["primitives"][primitiveIndex]["indices"] = bufferViewIndex; m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["POSITION"] = bufferViewIndex + 1; m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["JOINTS_0"] = bufferViewIndex + 2; m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["WEIGHTS_0"] = bufferViewIndex + 3; + m_json["meshes"][0]["primitives"][primitiveIndex]["attributes"]["TEXCOORD_0"] = bufferViewIndex + 4; m_json["meshes"][0]["primitives"][primitiveIndex]["material"] = primitiveIndex; - + /* m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorFactor"] = { part.second.color.redF(), part.second.color.greenF(), part.second.color.blueF(), 1.0 - }; + };*/ + m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["baseColorTexture"]["index"] = 0; m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["metallicFactor"] = 0.0; m_json["materials"][primitiveIndex]["pbrMetallicRoughness"]["roughnessFactor"] = 1.0; @@ -235,12 +254,32 @@ GLTFFileWriter::GLTFFileWriter(MeshResultContext &resultContext) m_json["accessors"][bufferViewIndex]["count"] = part.second.weights.size(); m_json["accessors"][bufferViewIndex]["type"] = "VEC4"; bufferViewIndex++; + + bufferViewFromOffset = (int)binaries.size(); + m_json["bufferViews"][bufferViewIndex]["buffer"] = 0; + m_json["bufferViews"][bufferViewIndex]["byteOffset"] = bufferViewFromOffset; + for (const auto &it: part.second.vertexUvs) { + stream << (float)it.uv[0] << (float)it.uv[1]; + } + m_json["bufferViews"][bufferViewIndex]["byteLength"] = binaries.size() - bufferViewFromOffset; + alignBinaries(); + m_json["accessors"][bufferViewIndex]["bufferView"] = bufferViewIndex; + m_json["accessors"][bufferViewIndex]["byteOffset"] = 0; + m_json["accessors"][bufferViewIndex]["componentType"] = 5126; + m_json["accessors"][bufferViewIndex]["count"] = part.second.vertexUvs.size(); + m_json["accessors"][bufferViewIndex]["type"] = "VEC2"; + bufferViewIndex++; } m_json["buffers"][0]["uri"] = QString("data:application/octet-stream;base64," + binaries.toBase64()).toUtf8().constData(); m_json["buffers"][0]["byteLength"] = binaries.size(); } +const QString &GLTFFileWriter::textureFilenameInGltf() +{ + return m_textureFilename; +} + void GLTFFileWriter::traceBoneFromJoint(MeshResultContext &resultContext, std::pair node, std::set> &visitedNodes, std::set, std::pair>> &connections, int parentIndex) { if (visitedNodes.find(node) != visitedNodes.end()) @@ -303,9 +342,9 @@ void GLTFFileWriter::traceBoneFromJoint(MeshResultContext &resultContext, std::p } } -bool GLTFFileWriter::save(const QString &filename) +bool GLTFFileWriter::save() { - QFile file(filename); + QFile file(m_filename); if (!file.open(QIODevice::WriteOnly)) { return false; } diff --git a/src/gltffile.h b/src/gltffile.h index cfa6fd2c..7d3b0e2d 100644 --- a/src/gltffile.h +++ b/src/gltffile.h @@ -24,15 +24,18 @@ class GLTFFileWriter : public QObject { Q_OBJECT public: - GLTFFileWriter(MeshResultContext &resultContext); - bool save(const QString &filename); + GLTFFileWriter(MeshResultContext &resultContext, const QString &filename); + bool save(); void traceBones(MeshResultContext &resultContext); void traceBoneFromJoint(MeshResultContext &resultContext, std::pair node, std::set> &visitedNodes, std::set, std::pair>> &connections, int parentIndex); + const QString &textureFilenameInGltf(); private: QByteArray m_data; std::vector m_tracedJoints; std::map, int> m_tracedNodeToJointIndexMap; QString getMatrixStringInColumnOrder(const QMatrix4x4 &mat); + QString m_filename; + QString m_textureFilename; private: nlohmann::json m_json; }; diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index 426ca66f..6d12c97d 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -1,7 +1,7 @@ #include #include #include "meshgenerator.h" -#include "util.h" +#include "dust3dutil.h" #include "skeletondocument.h" #include "meshlite.h" #include "modelofflinerender.h" @@ -120,8 +120,8 @@ void MeshGenerator::loadGeneratedPositionsToMeshResultContext(void *meshliteCont for (int i = 0, positionIndex = 0; i < positionCount; i++, positionIndex+=3) { ResultVertex vertex; vertex.position = QVector3D(positionBuffer[positionIndex + 0], positionBuffer[positionIndex + 1], positionBuffer[positionIndex + 2]); - verticesMap[i] = m_meshResultContext->resultVertices.size(); - m_meshResultContext->resultVertices.push_back(vertex); + verticesMap[i] = m_meshResultContext->vertices.size(); + m_meshResultContext->vertices.push_back(vertex); } int faceCount = meshlite_get_face_count(meshliteContext, triangulatedMeshId); int triangleIndexBufferLen = faceCount * 3; @@ -137,7 +137,7 @@ void MeshGenerator::loadGeneratedPositionsToMeshResultContext(void *meshliteCont triangle.indicies[1] = verticesMap[triangleIndexBuffer[triangleVertIndex + 1]]; triangle.indicies[2] = verticesMap[triangleIndexBuffer[triangleVertIndex + 2]]; triangle.normal = QVector3D(normalBuffer[normalIndex + 0], normalBuffer[normalIndex + 1], normalBuffer[normalIndex + 2]); - m_meshResultContext->resultTriangles.push_back(triangle); + m_meshResultContext->triangles.push_back(triangle); } delete[] positionBuffer; delete[] triangleIndexBuffer; diff --git a/src/meshresultcontext.cpp b/src/meshresultcontext.cpp index 6655905f..1eff6037 100644 --- a/src/meshresultcontext.cpp +++ b/src/meshresultcontext.cpp @@ -4,6 +4,8 @@ #include #include "theme.h" #include "meshresultcontext.h" +#include "thekla_atlas.h" +#include "positionmap.h" struct HalfColorEdge { @@ -29,9 +31,11 @@ MeshResultContext::MeshResultContext() : m_centerBmeshNodeResolved(false), m_bmeshEdgeDirectionsResolved(false), m_bmeshNodeNeighborsResolved(false), - m_vertexWeightResolved(false), + m_vertexWeightsResolved(false), m_centerBmeshNode(nullptr), - m_resultPartsResolved(false) + m_resultPartsResolved(false), + m_resultTriangleUvsResolved(false), + m_resultRearrangedVerticesResolved(false) { } @@ -97,12 +101,12 @@ void MeshResultContext::calculateTriangleSourceNodes(std::vector, int>> colorTypes; for (int i = 0; i < 3; i++) { int index = triangle->indicies[i]; - ResultVertex *resultVertex = &resultVertices[index]; + ResultVertex *resultVertex = &vertices[index]; std::pair source; if (positionMap.findPosition(resultVertex->position.x(), resultVertex->position.y(), resultVertex->position.z(), &source)) { bool colorExisted = false; @@ -149,7 +153,7 @@ void MeshResultContext::calculateTriangleSourceNodes(std::vector, int> brokenTriangleMapByEdge; std::vector candidateEdges; for (const auto &x: brokenTriangleSet) { - const auto triangle = &resultTriangles[x]; + const auto triangle = &triangles[x]; for (int i = 0; i < 3; i++) { int oppositeStartIndex = triangle->indicies[(i + 1) % 3]; int oppositeStopIndex = triangle->indicies[i]; @@ -160,11 +164,11 @@ void MeshResultContext::calculateTriangleSourceNodes(std::vectorindicies[i]].position, // A - resultVertices[triangle->indicies[(i + 1) % 3]].position, // B - resultVertices[triangle->indicies[(i + 2) % 3]].position // C + vertices[triangle->indicies[i]].position, // A + vertices[triangle->indicies[(i + 1) % 3]].position, // B + vertices[triangle->indicies[(i + 2) % 3]].position // C }; - QVector3D oppositeCornPosition = resultVertices[findOpposite->second.cornVertexIndex].position; // D + QVector3D oppositeCornPosition = vertices[findOpposite->second.cornVertexIndex].position; // D QVector3D AB = selfPositions[1] - selfPositions[0]; float length = AB.length(); QVector3D AC = selfPositions[2] - selfPositions[0]; @@ -213,7 +217,7 @@ void MeshResultContext::calculateTriangleSourceNodes(std::vectorindicies[(i + 1) % 3]; int oppositeStopIndex = triangle->indicies[i]; @@ -226,7 +230,6 @@ void MeshResultContext::calculateTriangleSourceNodes(std::vector &triangleColors) { - PositionMap positionColorMap; std::map, QColor> nodeColorMap; for (const auto &it: bmeshNodes) { nodeColorMap[std::make_pair(it.bmeshId, it.nodeId)] = it.color; @@ -298,8 +301,8 @@ void MeshResultContext::calculateBmeshConnectivity() void MeshResultContext::calculateTriangleEdgeSourceMap(std::map, std::pair> &triangleEdgeSourceMap) { const std::vector> sourceNodes = triangleSourceNodes(); - for (auto x = 0u; x < resultTriangles.size(); x++) { - const auto triangle = &resultTriangles[x]; + for (auto x = 0u; x < triangles.size(); x++) { + const auto triangle = &triangles[x]; for (int i = 0; i < 3; i++) { int startIndex = triangle->indicies[i]; int stopIndex = triangle->indicies[(i + 1) % 3]; @@ -400,11 +403,11 @@ void MeshResultContext::calculateBmeshEdgeDirections() bmeshEdges = rearrangedEdges; } -const std::vector> &MeshResultContext::resultVertexWeights() +const std::vector> &MeshResultContext::vertexWeights() { - if (!m_vertexWeightResolved) { + if (!m_vertexWeightsResolved) { calculateVertexWeights(m_resultVertexWeights); - m_vertexWeightResolved = true; + m_vertexWeightsResolved = true; } return m_resultVertexWeights; } @@ -412,11 +415,11 @@ const std::vector> &MeshResultContext::resultVer void MeshResultContext::calculateVertexWeights(std::vector> &vertexWeights) { vertexWeights.clear(); - vertexWeights.resize(resultVertices.size()); - for (auto i = 0u; i < resultTriangles.size(); i++) { + vertexWeights.resize(vertices.size()); + for (auto i = 0u; i < triangles.size(); i++) { std::pair sourceNode = triangleSourceNodes()[i]; for (int j = 0; j < 3; j++) { - int vertexIndex = resultTriangles[i].indicies[j]; + int vertexIndex = triangles[i].indicies[j]; Q_ASSERT(vertexIndex < (int)vertexWeights.size()); int foundSourceNodeIndex = -1; for (auto k = 0u; k < vertexWeights[vertexIndex].size(); k++) { @@ -445,7 +448,7 @@ void MeshResultContext::calculateVertexWeights(std::vector &MeshResultContext::resultParts() +const std::map &MeshResultContext::parts() { if (!m_resultPartsResolved) { calculateResultParts(m_resultParts); @@ -454,11 +457,20 @@ const std::map &MeshResultContext::resultParts() return m_resultParts; } +const std::vector &MeshResultContext::triangleUvs() +{ + if (!m_resultTriangleUvsResolved) { + calculateResultTriangleUvs(m_resultTriangleUvs, m_seamVertices); + m_resultTriangleUvsResolved = true; + } + return m_resultTriangleUvs; +} + void MeshResultContext::calculateResultParts(std::map &parts) { std::map, int> oldVertexToNewMap; - for (auto x = 0u; x < resultTriangles.size(); x++) { - const auto &triangle = resultTriangles[x]; + for (auto x = 0u; x < triangles.size(); x++) { + const auto &triangle = triangles[x]; const auto &sourceNode = triangleSourceNodes()[x]; auto it = parts.find(sourceNode.first); if (it == parts.end()) { @@ -472,16 +484,181 @@ void MeshResultContext::calculateResultParts(std::map &parts) for (auto i = 0u; i < 3; i++) { auto key = std::make_pair(sourceNode.first, triangle.indicies[i]); const auto &it = oldVertexToNewMap.find(key); - if (it == oldVertexToNewMap.end()) { + if (it == oldVertexToNewMap.end() || m_seamVertices.end() != m_seamVertices.find(triangle.indicies[i])) { int newIndex = resultPart.vertices.size(); - resultPart.vertices.push_back(resultVertices[triangle.indicies[i]]); - resultPart.weights.push_back(resultVertexWeights()[triangle.indicies[i]]); - oldVertexToNewMap.insert(std::make_pair(key, newIndex)); + resultPart.vertices.push_back(vertices[triangle.indicies[i]]); + ResultVertexUv vertexUv; + vertexUv.uv[0] = triangleUvs()[x].uv[i][0]; + vertexUv.uv[1] = triangleUvs()[x].uv[i][1]; + resultPart.vertexUvs.push_back(vertexUv); + resultPart.weights.push_back(vertexWeights()[triangle.indicies[i]]); + if (it == oldVertexToNewMap.end()) + oldVertexToNewMap.insert(std::make_pair(key, newIndex)); newTriangle.indicies[i] = newIndex; } else { newTriangle.indicies[i] = it->second; } } resultPart.triangles.push_back(newTriangle); + resultPart.uvs.push_back(triangleUvs()[x]); + } +} + +void MeshResultContext::calculateResultTriangleUvs(std::vector &uvs, std::set &seamVertices) +{ + using namespace Thekla; + + const std::vector &choosenVertices = rearrangedVertices(); + const std::vector &choosenTriangles = rearrangedTriangles(); + + Atlas_Input_Mesh inputMesh; + + inputMesh.vertex_count = choosenVertices.size(); + inputMesh.vertex_array = new Atlas_Input_Vertex[inputMesh.vertex_count]; + inputMesh.face_count = choosenTriangles.size(); + inputMesh.face_array = new Atlas_Input_Face[inputMesh.face_count]; + for (auto i = 0; i < inputMesh.vertex_count; i++) { + const ResultRearrangedVertex *src = &choosenVertices[i]; + Atlas_Input_Vertex *dest = &inputMesh.vertex_array[i]; + dest->position[0] = src->position.x(); + dest->position[1] = src->position.y(); + dest->position[2] = src->position.z(); + dest->normal[0] = 0; + dest->normal[1] = 0; + dest->normal[2] = 0; + dest->uv[0] = 0; + dest->uv[1] = 0; + dest->first_colocal = i; + } + std::map, int> edgeToFaceIndexMap; + for (auto i = 0; i < inputMesh.face_count; i++) { + const ResultRearrangedTriangle *src = &choosenTriangles[i]; + Atlas_Input_Face *dest = &inputMesh.face_array[i]; + dest->material_index = abs(triangleSourceNodes()[src->originalIndex].first); + dest->vertex_index[0] = src->indicies[0]; + dest->vertex_index[1] = src->indicies[1]; + dest->vertex_index[2] = src->indicies[2]; + edgeToFaceIndexMap[std::make_pair(src->indicies[0], src->indicies[1])] = src->originalIndex; + edgeToFaceIndexMap[std::make_pair(src->indicies[1], src->indicies[2])] = src->originalIndex; + edgeToFaceIndexMap[std::make_pair(src->indicies[2], src->indicies[0])] = src->originalIndex; + for (auto j = 0; j < 3; j++) { + Atlas_Input_Vertex *vertex = &inputMesh.vertex_array[src->indicies[j]]; + vertex->normal[0] += src->normal.x(); + vertex->normal[1] += src->normal.y(); + vertex->normal[2] += src->normal.z(); + } + } + for (auto i = 0; i < inputMesh.vertex_count; i++) { + Atlas_Input_Vertex *dest = &inputMesh.vertex_array[i]; + QVector3D normal(dest->normal[0], dest->normal[1], dest->normal[2]); + normal.normalize(); + dest->normal[0] = normal.x(); + dest->normal[1] = normal.y(); + dest->normal[2] = normal.z(); + } + + Atlas_Options atlasOptions; + atlas_set_default_options(&atlasOptions); + + atlasOptions.packer_options.witness.packing_quality = 4; + + Atlas_Error error = Atlas_Error_Success; + Atlas_Output_Mesh *outputMesh = atlas_generate(&inputMesh, &atlasOptions, &error); + + PositionMap uvPositionAndIndexMap; + + uvs.resize(triangles.size()); + std::set refs; + for (auto i = 0; i < outputMesh->index_count; i += 3) { + Atlas_Output_Vertex *outputVertices[] = { + &outputMesh->vertex_array[outputMesh->index_array[i + 0]], + &outputMesh->vertex_array[outputMesh->index_array[i + 1]], + &outputMesh->vertex_array[outputMesh->index_array[i + 2]] + }; + int faceIndex = edgeToFaceIndexMap[std::make_pair(outputVertices[0]->xref, outputVertices[1]->xref)]; + Q_ASSERT(faceIndex == edgeToFaceIndexMap[std::make_pair(outputVertices[1]->xref, outputVertices[2]->xref)]); + Q_ASSERT(faceIndex == edgeToFaceIndexMap[std::make_pair(outputVertices[2]->xref, outputVertices[0]->xref)]); + int firstIndex = 0; + for (auto j = 0; j < 3; j++) { + if (choosenVertices[outputVertices[0]->xref].originalIndex == triangles[faceIndex].indicies[j]) { + firstIndex = j; + break; + } + } + for (auto j = 0; j < 3; j++) { + Atlas_Output_Vertex *from = outputVertices[j]; + ResultTriangleUv *to = &uvs[faceIndex]; + to->resolved = true; + int toIndex = (firstIndex + j) % 3; + to->uv[toIndex][0] = (float)from->uv[0] / outputMesh->atlas_width; + to->uv[toIndex][1] = (float)from->uv[1] / outputMesh->atlas_height; + int originalRef = choosenVertices[from->xref].originalIndex; + if (refs.find(originalRef) == refs.end()) { + refs.insert(originalRef); + uvPositionAndIndexMap.addPosition(from->uv[0], from->uv[1], (float)originalRef, 0); + } else { + if (!uvPositionAndIndexMap.findPosition(from->uv[0], from->uv[1], (float)originalRef, nullptr)) { + seamVertices.insert(originalRef); + } + } + } + } + int unresolvedUvFaceCount = 0; + for (auto i = 0u; i < uvs.size(); i++) { + ResultTriangleUv *uv = &uvs[i]; + if (!uv->resolved) { + unresolvedUvFaceCount++; + } + } + qDebug() << "unresolvedUvFaceCount:" << unresolvedUvFaceCount; + + atlas_free(outputMesh); +} + +const std::vector &MeshResultContext::rearrangedVertices() +{ + if (!m_resultRearrangedVerticesResolved) { + calculateResultRearrangedVertices(m_rearrangedVertices, m_rearrangedTriangles); + m_resultRearrangedVerticesResolved = true; + } + return m_rearrangedVertices; +} + +const std::vector &MeshResultContext::rearrangedTriangles() +{ + if (!m_resultRearrangedVerticesResolved) { + calculateResultRearrangedVertices(m_rearrangedVertices, m_rearrangedTriangles); + m_resultRearrangedVerticesResolved = true; + } + return m_rearrangedTriangles; +} + +void MeshResultContext::calculateResultRearrangedVertices(std::vector &rearrangedVertices, std::vector &rearrangedTriangles) +{ + std::map, int> oldVertexToNewMap; + rearrangedVertices.clear(); + rearrangedTriangles.clear(); + for (auto x = 0u; x < triangles.size(); x++) { + const auto &triangle = triangles[x]; + const auto &sourceNode = triangleSourceNodes()[x]; + ResultRearrangedTriangle newTriangle; + newTriangle.normal = triangle.normal; + newTriangle.originalIndex = x; + for (auto i = 0u; i < 3; i++) { + auto key = std::make_pair(sourceNode.first, triangle.indicies[i]); + const auto &it = oldVertexToNewMap.find(key); + if (it == oldVertexToNewMap.end()) { + ResultRearrangedVertex rearrangedVertex; + rearrangedVertex.originalIndex = triangle.indicies[i]; + rearrangedVertex.position = vertices[triangle.indicies[i]].position; + int newIndex = rearrangedVertices.size(); + rearrangedVertices.push_back(rearrangedVertex); + oldVertexToNewMap.insert(std::make_pair(key, newIndex)); + newTriangle.indicies[i] = newIndex; + } else { + newTriangle.indicies[i] = it->second; + } + } + rearrangedTriangles.push_back(newTriangle); } } diff --git a/src/meshresultcontext.h b/src/meshresultcontext.h index 58a04444..64749866 100644 --- a/src/meshresultcontext.h +++ b/src/meshresultcontext.h @@ -51,12 +51,38 @@ struct ResultVertexWeight float weight; }; +struct ResultTriangleUv +{ + float uv[3][2]; + bool resolved; +}; + +struct ResultVertexUv +{ + float uv[2]; +}; + struct ResultPart { QColor color; std::vector vertices; std::vector> weights; std::vector triangles; + std::vector uvs; + std::vector vertexUvs; +}; + +struct ResultRearrangedVertex +{ + QVector3D position; + int originalIndex; +}; + +struct ResultRearrangedTriangle +{ + int indicies[3]; + QVector3D normal; + int originalIndex; }; class MeshResultContext @@ -65,8 +91,8 @@ public: std::vector bmeshNodes; std::vector bmeshVertices; std::vector bmeshEdges; - std::vector resultVertices; - std::vector resultTriangles; + std::vector vertices; + std::vector triangles; MeshResultContext(); public: const std::vector> &triangleSourceNodes(); @@ -77,8 +103,11 @@ public: void resolveBmeshConnectivity(); void resolveBmeshEdgeDirections(); const std::map, std::vector>> &nodeNeighbors(); - const std::vector> &resultVertexWeights(); - const std::map &resultParts(); + const std::vector> &vertexWeights(); + const std::map &parts(); + const std::vector &triangleUvs(); + const std::vector &rearrangedVertices(); + const std::vector &rearrangedTriangles(); private: bool m_triangleSourceResolved; bool m_triangleColorResolved; @@ -88,9 +117,11 @@ private: bool m_centerBmeshNodeResolved; bool m_bmeshEdgeDirectionsResolved; bool m_bmeshNodeNeighborsResolved; - bool m_vertexWeightResolved; + bool m_vertexWeightsResolved; BmeshNode *m_centerBmeshNode; bool m_resultPartsResolved; + bool m_resultTriangleUvsResolved; + bool m_resultRearrangedVerticesResolved; private: std::vector> m_triangleSourceNodes; std::vector m_triangleColors; @@ -99,6 +130,10 @@ private: std::map, std::vector>> m_nodeNeighbors; std::vector> m_resultVertexWeights; std::map m_resultParts; + std::vector m_resultTriangleUvs; + std::set m_seamVertices; + std::vector m_rearrangedVertices; + std::vector m_rearrangedTriangles; private: void calculateTriangleSourceNodes(std::vector> &triangleSourceNodes); void calculateTriangleColors(std::vector &triangleColors); @@ -112,6 +147,8 @@ private: void calculateBmeshNodeNeighbors(std::map, std::vector>> &nodeNeighbors); void calculateVertexWeights(std::vector> &vertexWeights); void calculateResultParts(std::map &parts); + void calculateResultTriangleUvs(std::vector &uvs, std::set &seamVertices); + void calculateResultRearrangedVertices(std::vector &rearrangedVertices, std::vector &rearrangedTriangles); }; #endif diff --git a/src/meshresultpostprocessor.cpp b/src/meshresultpostprocessor.cpp new file mode 100644 index 00000000..c4431fc0 --- /dev/null +++ b/src/meshresultpostprocessor.cpp @@ -0,0 +1,36 @@ +#include +#include "meshresultpostprocessor.h" + +MeshResultPostProcessor::MeshResultPostProcessor(const MeshResultContext &meshResultContext) +{ + m_meshResultContext = new MeshResultContext; + *m_meshResultContext = meshResultContext; +} + +MeshResultPostProcessor::~MeshResultPostProcessor() +{ + delete m_meshResultContext; +} + +MeshResultContext *MeshResultPostProcessor::takePostProcessedResultContext() +{ + MeshResultContext *resultContext = m_meshResultContext; + m_meshResultContext = nullptr; + return resultContext; +} + +void MeshResultPostProcessor::process() +{ + if (!m_meshResultContext->bmeshNodes.empty()) { + m_meshResultContext->resolveBmeshConnectivity(); + m_meshResultContext->resolveBmeshEdgeDirections(); + m_meshResultContext->rearrangedVertices(); + m_meshResultContext->rearrangedTriangles(); + m_meshResultContext->parts(); + } + + this->moveToThread(QGuiApplication::instance()->thread()); + emit finished(); +} + + diff --git a/src/meshresultpostprocessor.h b/src/meshresultpostprocessor.h new file mode 100644 index 00000000..973876fb --- /dev/null +++ b/src/meshresultpostprocessor.h @@ -0,0 +1,21 @@ +#ifndef MESH_RESULT_POST_PROCESSOR_H +#define MESH_RESULT_POST_PROCESSOR_H +#include +#include "meshresultcontext.h" + +class MeshResultPostProcessor : public QObject +{ + Q_OBJECT +public: + MeshResultPostProcessor(const MeshResultContext &meshResultContext); + ~MeshResultPostProcessor(); + MeshResultContext *takePostProcessedResultContext(); +signals: + void finished(); +public slots: + void process(); +private: + MeshResultContext *m_meshResultContext; +}; + +#endif diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index 5730a351..c850f3c1 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -7,7 +7,7 @@ #include #include #include "skeletondocument.h" -#include "util.h" +#include "dust3dutil.h" #include "skeletonxml.h" unsigned long SkeletonDocument::m_maxSnapshot = 1000; @@ -21,6 +21,9 @@ SkeletonDocument::SkeletonDocument() : xlocked(false), ylocked(false), zlocked(false), + textureGuideImage(nullptr), + textureImage(nullptr), + textureBorderImage(nullptr), // private m_resultMeshIsObsolete(false), m_meshGenerator(nullptr), @@ -30,7 +33,11 @@ SkeletonDocument::SkeletonDocument() : m_resultSkeletonIsObsolete(false), m_skeletonGenerator(nullptr), m_resultSkeletonMesh(nullptr), - m_currentSkeletonResultContext(new MeshResultContext) + m_textureIsObsolete(false), + m_textureGenerator(nullptr), + m_postProcessResultIsObsolete(false), + m_postProcessor(nullptr), + m_postProcessedResultContext(new MeshResultContext) { } @@ -38,7 +45,10 @@ SkeletonDocument::~SkeletonDocument() { delete m_resultMesh; delete m_resultSkeletonMesh; - delete m_currentSkeletonResultContext; + delete m_postProcessedResultContext; + delete textureGuideImage; + delete textureImage; + delete textureBorderImage; } void SkeletonDocument::uiReady() @@ -863,6 +873,52 @@ void SkeletonDocument::generateMesh() thread->start(); } +void SkeletonDocument::generateTexture() +{ + if (nullptr != m_textureGenerator) { + m_textureIsObsolete = true; + return; + } + + qDebug() << "Texture guide generating.."; + + m_textureIsObsolete = false; + + QThread *thread = new QThread; + m_textureGenerator = new TextureGenerator(*m_postProcessedResultContext); + m_textureGenerator->moveToThread(thread); + connect(thread, &QThread::started, m_textureGenerator, &TextureGenerator::process); + connect(m_textureGenerator, &TextureGenerator::finished, this, &SkeletonDocument::textureReady); + connect(m_textureGenerator, &TextureGenerator::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + +void SkeletonDocument::textureReady() +{ + delete textureGuideImage; + textureGuideImage = m_textureGenerator->takeResultTextureGuideImage(); + + delete textureImage; + textureImage = m_textureGenerator->takeResultTextureImage(); + + delete textureBorderImage; + textureBorderImage = m_textureGenerator->takeResultTextureBorderImage(); + + delete m_textureGenerator; + m_textureGenerator = nullptr; + + qDebug() << "Texture guide generation done"; + + emit resultTextureChanged(); + + if (m_textureIsObsolete) { + generateTexture(); + } else { + checkExportReadyState(); + } +} + void SkeletonDocument::generateSkeleton() { if (nullptr != m_skeletonGenerator) { @@ -874,13 +930,8 @@ void SkeletonDocument::generateSkeleton() m_resultSkeletonIsObsolete = false; - if (!m_currentMeshResultContext) { - qDebug() << "Skeleton is null"; - return; - } - QThread *thread = new QThread; - m_skeletonGenerator = new SkeletonGenerator(*m_currentMeshResultContext); + m_skeletonGenerator = new SkeletonGenerator(*m_postProcessedResultContext); m_skeletonGenerator->moveToThread(thread); connect(thread, &QThread::started, m_skeletonGenerator, &SkeletonGenerator::process); connect(m_skeletonGenerator, &SkeletonGenerator::finished, this, &SkeletonDocument::skeletonReady); @@ -896,11 +947,6 @@ void SkeletonDocument::skeletonReady() delete m_resultSkeletonMesh; m_resultSkeletonMesh = resultSkeletonMesh; - MeshResultContext *resultContext = m_skeletonGenerator->takeResultContext(); - - delete m_currentSkeletonResultContext; - m_currentSkeletonResultContext = resultContext; - delete m_skeletonGenerator; m_skeletonGenerator = nullptr; @@ -910,12 +956,57 @@ void SkeletonDocument::skeletonReady() if (m_resultSkeletonIsObsolete) { generateSkeleton(); + } else { + checkExportReadyState(); } } -MeshResultContext &SkeletonDocument::currentSkeletonResultContext() +void SkeletonDocument::postProcess() { - return *m_currentSkeletonResultContext; + if (nullptr != m_postProcessor) { + m_postProcessResultIsObsolete = true; + return; + } + + qDebug() << "Post processing.."; + + m_postProcessResultIsObsolete = false; + + if (!m_currentMeshResultContext) { + qDebug() << "Mesh is null"; + return; + } + + QThread *thread = new QThread; + m_postProcessor = new MeshResultPostProcessor(*m_currentMeshResultContext); + m_postProcessor->moveToThread(thread); + connect(thread, &QThread::started, m_postProcessor, &MeshResultPostProcessor::process); + connect(m_postProcessor, &MeshResultPostProcessor::finished, this, &SkeletonDocument::postProcessedMeshResultReady); + connect(m_postProcessor, &MeshResultPostProcessor::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} + +void SkeletonDocument::postProcessedMeshResultReady() +{ + delete m_postProcessedResultContext; + m_postProcessedResultContext = m_postProcessor->takePostProcessedResultContext(); + + delete m_postProcessor; + m_postProcessor = nullptr; + + qDebug() << "Post process done"; + + emit postProcessedResultChanged(); + + if (m_postProcessResultIsObsolete) { + postProcess(); + } +} + +MeshResultContext &SkeletonDocument::currentPostProcessedResultContext() +{ + return *m_postProcessedResultContext; } void SkeletonDocument::setPartLockState(QUuid partId, bool locked) @@ -1176,3 +1267,23 @@ void SkeletonDocument::setZlockState(bool locked) zlocked = locked; emit zlockStateChanged(); } + +bool SkeletonDocument::isExportReady() const +{ + if (m_resultMeshIsObsolete || + m_resultSkeletonIsObsolete || + m_textureIsObsolete || + m_postProcessResultIsObsolete || + m_meshGenerator || + m_skeletonGenerator || + m_textureGenerator || + m_postProcessor) + return false; + return true; +} + +void SkeletonDocument::checkExportReadyState() +{ + if (isExportReady()) + emit exportReady(); +} diff --git a/src/skeletondocument.h b/src/skeletondocument.h index ae084822..81d218e9 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -13,6 +13,8 @@ #include "meshgenerator.h" #include "skeletongenerator.h" #include "theme.h" +#include "texturegenerator.h" +#include "meshresultpostprocessor.h" class SkeletonNode { @@ -186,6 +188,8 @@ signals: void editModeChanged(); void skeletonChanged(); void resultSkeletonChanged(); + void resultTextureChanged(); + void postProcessedResultChanged(); void partLockStateChanged(QUuid partId); void partVisibleStateChanged(QUuid partId); void partSubdivStateChanged(QUuid partId); @@ -206,6 +210,7 @@ signals: void partUnchecked(QUuid partId); void enableBackgroundBlur(); void disableBackgroundBlur(); + void exportReady(); public: // need initialize float originX; float originY; @@ -214,6 +219,9 @@ public: // need initialize bool xlocked; bool ylocked; bool zlocked; + QImage *textureGuideImage; + QImage *textureImage; + QImage *textureBorderImage; public: SkeletonDocument(); ~SkeletonDocument(); @@ -239,7 +247,8 @@ public: bool isNodeEditable(QUuid nodeId) const; bool isEdgeEditable(QUuid edgeId) const; bool originSettled() const; - MeshResultContext ¤tSkeletonResultContext(); + MeshResultContext ¤tPostProcessedResultContext(); + bool isExportReady() const; public slots: void removeNode(QUuid nodeId); void removeEdge(QUuid edgeId); @@ -257,6 +266,10 @@ public slots: void meshReady(); void generateSkeleton(); void skeletonReady(); + void generateTexture(); + void textureReady(); + void postProcess(); + void postProcessedMeshResultReady(); void setPartLockState(QUuid partId, bool locked); void setPartVisibleState(QUuid partId, bool visible); void setPartSubdivState(QUuid partId, bool subdived); @@ -285,6 +298,7 @@ private: bool isPartReadonly(QUuid partId) const; QUuid createNode(float x, float y, float z, float radius, QUuid fromNodeId); void settleOrigin(); + void checkExportReadyState(); private: // need initialize bool m_resultMeshIsObsolete; MeshGenerator *m_meshGenerator; @@ -294,7 +308,11 @@ private: // need initialize bool m_resultSkeletonIsObsolete; SkeletonGenerator *m_skeletonGenerator; Mesh *m_resultSkeletonMesh; - MeshResultContext *m_currentSkeletonResultContext; + bool m_textureIsObsolete; + TextureGenerator *m_textureGenerator; + bool m_postProcessResultIsObsolete; + MeshResultPostProcessor *m_postProcessor; + MeshResultContext *m_postProcessedResultContext; private: static unsigned long m_maxSnapshot; std::deque m_undoItems; diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index 74797476..aa1e1c33 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -22,7 +22,7 @@ #include "skeletonsnapshot.h" #include "skeletonxml.h" #include "logbrowser.h" -#include "util.h" +#include "dust3dutil.h" #include "aboutwidget.h" #include "version.h" #include "gltffile.h" @@ -45,7 +45,7 @@ void outputMessage(QtMsgType type, const QMessageLogContext &context, const QStr g_logBrowser->outputMessage(type, msg); } -void SkeletonDocumentWindow::SkeletonDocumentWindow::showAcknowlegements() +void SkeletonDocumentWindow::showAcknowlegements() { if (!g_acknowlegementsWidget) { g_acknowlegementsWidget = new QTextBrowser; @@ -61,7 +61,7 @@ void SkeletonDocumentWindow::SkeletonDocumentWindow::showAcknowlegements() g_acknowlegementsWidget->raise(); } -void SkeletonDocumentWindow::SkeletonDocumentWindow::showAbout() +void SkeletonDocumentWindow::showAbout() { if (!g_aboutWidget) { g_aboutWidget = new AboutWidget; @@ -71,10 +71,28 @@ void SkeletonDocumentWindow::SkeletonDocumentWindow::showAbout() g_aboutWidget->raise(); } +void SkeletonDocumentWindow::showTextureGuidePreview() +{ + if (!m_textureGuideWidget) { + m_textureGuideWidget = new TextureGuideWidget; + connect(m_textureGuideWidget, &TextureGuideWidget::regenerate, m_document, &SkeletonDocument::generateMesh); + } + if (m_textureGuideWidget->isHidden()) { + if (m_document->textureGuideImage) { + m_textureGuideWidget->updateGuideImage(*m_document->textureGuideImage); + } + } + m_textureGuideWidget->show(); + m_textureGuideWidget->activateWindow(); + m_textureGuideWidget->raise(); +} + SkeletonDocumentWindow::SkeletonDocumentWindow() : m_document(nullptr), m_firstShow(true), - m_documentSaved(true) + m_documentSaved(true), + m_textureGuideWidget(nullptr), + m_exportRequired(false) { if (!g_logBrowser) { g_logBrowser = new LogBrowser; @@ -231,12 +249,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_exportModelAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportModelResult, Qt::QueuedConnection); m_exportMenu->addAction(m_exportModelAction); - m_exportModelAndMaterialsAction = new QAction(tr("Model and Materials(.obj)..."), this); - connect(m_exportModelAndMaterialsAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportModelAndMaterialResult, Qt::QueuedConnection); - //m_exportMenu->addAction(m_exportModelAndMaterialsAction); - m_exportSkeletonAction = new QAction(tr("GL Transmission Format (.gltf)..."), this); - connect(m_exportSkeletonAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportSkeletonResult, Qt::QueuedConnection); + connect(m_exportSkeletonAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportGltfResult, Qt::QueuedConnection); m_exportMenu->addAction(m_exportSkeletonAction); m_changeTurnaroundAction = new QAction(tr("Change Turnaround..."), this); @@ -245,7 +259,6 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_fileMenu, &QMenu::aboutToShow, [=]() { m_exportModelAction->setEnabled(m_graphicsWidget->hasItems()); - m_exportModelAndMaterialsAction->setEnabled(m_graphicsWidget->hasItems()); m_exportSkeletonAction->setEnabled(m_graphicsWidget->hasItems()); }); @@ -341,6 +354,14 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : }); m_viewMenu->addAction(m_resetModelWidgetPosAction); + m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this); + connect(m_toggleWireframeAction, &QAction::triggered, [=]() { + m_modelRenderWidget->toggleWireframe(); + }); + m_viewMenu->addAction(m_toggleWireframeAction); + + m_viewMenu->addSeparator(); + m_toggleSkeletonWidgetAction = new QAction(tr("Toggle Bones"), this); connect(m_toggleSkeletonWidgetAction, &QAction::triggered, [=]() { if (m_skeletonRenderWidget->isVisible()) { @@ -355,17 +376,21 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : }); m_viewMenu->addAction(m_toggleSkeletonWidgetAction); + m_viewMenu->addSeparator(); + + m_showTextureGuidePreviewAction = new QAction(tr("Show Texture"), this); + connect(m_showTextureGuidePreviewAction, &QAction::triggered, this, &SkeletonDocumentWindow::showTextureGuidePreview); + m_viewMenu->addAction(m_showTextureGuidePreviewAction); + + m_viewMenu->addSeparator(); + m_showPartsListAction = new QAction(tr("Show Parts List"), this); connect(m_showPartsListAction, &QAction::triggered, [=]() { partListWidget->show(); }); m_viewMenu->addAction(m_showPartsListAction); - m_toggleWireframeAction = new QAction(tr("Toggle Wireframe"), this); - connect(m_toggleWireframeAction, &QAction::triggered, [=]() { - m_modelRenderWidget->toggleWireframe(); - }); - m_viewMenu->addAction(m_toggleWireframeAction); + m_viewMenu->addSeparator(); m_showDebugDialogAction = new QAction(tr("Show Debug Dialog"), this); connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog); @@ -437,6 +462,12 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : m_document->setZlockState(!m_document->zlocked); }); + connect(m_document, &SkeletonDocument::resultTextureChanged, [=]() { + if (m_document->textureGuideImage && m_textureGuideWidget && m_textureGuideWidget->isVisible()) { + m_textureGuideWidget->updateGuideImage(*m_document->textureGuideImage); + } + }); + connect(m_document, &SkeletonDocument::editModeChanged, graphicsWidget, &SkeletonGraphicsWidget::editModeChanged); connect(graphicsWidget, &SkeletonGraphicsWidget::addNode, m_document, &SkeletonDocument::addNode); @@ -502,7 +533,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::partUnchecked, partListWidget, &SkeletonPartListWidget::partUnchecked); connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh); - connect(m_document, &SkeletonDocument::resultMeshChanged, m_document, &SkeletonDocument::generateSkeleton); + connect(m_document, &SkeletonDocument::resultMeshChanged, m_document, &SkeletonDocument::postProcess); + connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateSkeleton); + connect(m_document, &SkeletonDocument::postProcessedResultChanged, m_document, &SkeletonDocument::generateTexture); connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() { m_modelRenderWidget->updateMesh(m_document->takeResultMesh()); @@ -526,6 +559,8 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::xlockStateChanged, this, &SkeletonDocumentWindow::updateXlockButtonState); connect(m_document, &SkeletonDocument::ylockStateChanged, this, &SkeletonDocumentWindow::updateYlockButtonState); connect(m_document, &SkeletonDocument::zlockStateChanged, this, &SkeletonDocumentWindow::updateZlockButtonState); + + connect(m_document, &SkeletonDocument::exportReady, this, &SkeletonDocumentWindow::checkDelayedExport); connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady); } @@ -666,6 +701,10 @@ void SkeletonDocumentWindow::initLockButton(QPushButton *button) SkeletonDocumentWindow::~SkeletonDocumentWindow() { + if (m_textureGuideWidget) { + delete m_textureGuideWidget; + m_textureGuideWidget = nullptr; + } g_documentWindows.erase(this); } @@ -796,29 +835,30 @@ void SkeletonDocumentWindow::exportModelResult() QApplication::restoreOverrideCursor(); } -void SkeletonDocumentWindow::exportModelAndMaterialResult() +void SkeletonDocumentWindow::exportGltfResult() { + if (!m_document->isExportReady()) { + m_exportRequired = true; + return; + } + + m_exportRequired = false; + QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), - tr("Wavefront (*.obj)")); + tr("GL Transmission Format (.gltf)")); if (filename.isEmpty()) { return; } QApplication::setOverrideCursor(Qt::WaitCursor); - m_modelRenderWidget->exportMeshAsObjPlusMaterials(filename); - QApplication::restoreOverrideCursor(); -} - -void SkeletonDocumentWindow::exportSkeletonResult() -{ - QString filename = QFileDialog::getSaveFileName(this, QString(), QString(), - tr("glTF (*.gltf)")); - if (filename.isEmpty()) { - return; - } - QApplication::setOverrideCursor(Qt::WaitCursor); - MeshResultContext skeletonResult = m_document->currentSkeletonResultContext(); - GLTFFileWriter gltfFileWriter(skeletonResult); - gltfFileWriter.save(filename); + MeshResultContext skeletonResult = m_document->currentPostProcessedResultContext(); + GLTFFileWriter gltfFileWriter(skeletonResult, filename); + gltfFileWriter.save(); + if (m_document->textureImage) + m_document->textureImage->save(gltfFileWriter.textureFilenameInGltf()); + QFileInfo nameInfo(filename); + QString guideFilename = nameInfo.path() + QDir::separator() + nameInfo.completeBaseName() + "-BORDER.png"; + if (m_document->textureBorderImage) + m_document->textureBorderImage->save(guideFilename); QApplication::restoreOverrideCursor(); } @@ -845,3 +885,10 @@ void SkeletonDocumentWindow::updateZlockButtonState() else m_zlockButton->setStyleSheet("QPushButton {color: #aaebc4}"); } + +void SkeletonDocumentWindow::checkDelayedExport() +{ + if (!m_exportRequired) + return; + exportGltfResult(); +} diff --git a/src/skeletondocumentwindow.h b/src/skeletondocumentwindow.h index a394fcd1..386e716b 100644 --- a/src/skeletondocumentwindow.h +++ b/src/skeletondocumentwindow.h @@ -9,6 +9,7 @@ #include #include "skeletondocument.h" #include "modelwidget.h" +#include "textureguidewidget.h" class SkeletonGraphicsWidget; @@ -33,8 +34,7 @@ public slots: void saveTo(const QString &saveAsFilename); void open(); void exportModelResult(); - void exportModelAndMaterialResult(); - void exportSkeletonResult(); + void exportGltfResult(); void newWindow(); void newDocument(); void saveAs(); @@ -48,6 +48,8 @@ public slots: void updateXlockButtonState(); void updateYlockButtonState(); void updateZlockButtonState(); + void showTextureGuidePreview(); + void checkDelayedExport(); private: void initAwesomeButton(QPushButton *button); void initLockButton(QPushButton *button); @@ -57,6 +59,8 @@ private: SkeletonDocument *m_document; bool m_firstShow; bool m_documentSaved; + TextureGuideWidget *m_textureGuideWidget; + bool m_exportRequired; private: QString m_currentFilename; @@ -75,7 +79,6 @@ private: QAction *m_changeTurnaroundAction; QAction *m_exportModelAction; - QAction *m_exportModelAndMaterialsAction; QAction *m_exportSkeletonAction; QMenu *m_editMenu; @@ -101,6 +104,7 @@ private: QAction *m_showPartsListAction; QAction *m_showDebugDialogAction; QAction *m_toggleWireframeAction; + QAction *m_showTextureGuidePreviewAction; QMenu *m_helpMenu; QAction *m_viewSourceAction; diff --git a/src/skeletongenerator.cpp b/src/skeletongenerator.cpp index c24d5ac0..b448e65e 100644 --- a/src/skeletongenerator.cpp +++ b/src/skeletongenerator.cpp @@ -76,18 +76,8 @@ struct BmeshNodeDistWithWorldCenter float dist2; }; -void SkeletonGenerator::combineAllBmeshSkeletons() -{ - m_meshResultContext->resolveBmeshConnectivity(); - m_meshResultContext->resolveBmeshEdgeDirections(); - m_meshResultContext->resultParts(); -} - void SkeletonGenerator::process() { - if (!m_meshResultContext->bmeshNodes.empty()) - combineAllBmeshSkeletons(); - m_resultSkeletonMesh = createSkeletonMesh(); this->moveToThread(QGuiApplication::instance()->thread()); diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index c0fa2cb0..8178c9e3 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -11,7 +11,7 @@ #include #include "skeletongraphicswidget.h" #include "theme.h" -#include "util.h" +#include "dust3dutil.h" #include "skeletonxml.h" SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) : diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index 3108e4f2..8deae100 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -14,7 +14,7 @@ #include "skeletondocument.h" #include "turnaroundloader.h" #include "theme.h" -#include "util.h" +#include "dust3dutil.h" class SkeletonGraphicsOriginItem : public QGraphicsPolygonItem { diff --git a/src/skeletonsnapshot.cpp b/src/skeletonsnapshot.cpp index f476c2f8..8d046392 100644 --- a/src/skeletonsnapshot.cpp +++ b/src/skeletonsnapshot.cpp @@ -1,6 +1,6 @@ #include #include "skeletonsnapshot.h" -#include "util.h" +#include "dust3dutil.h" void SkeletonSnapshot::resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId) { diff --git a/src/texturegenerator.cpp b/src/texturegenerator.cpp new file mode 100644 index 00000000..20025969 --- /dev/null +++ b/src/texturegenerator.cpp @@ -0,0 +1,141 @@ +#include +#include +#include "texturegenerator.h" +#include "theme.h" + +int TextureGenerator::m_textureWidth = 256; +int TextureGenerator::m_textureHeight = 256; + +TextureGenerator::TextureGenerator(const MeshResultContext &meshResultContext) : + m_resultTextureGuideImage(nullptr), + m_resultTextureImage(nullptr), + m_resultTextureBorderImage(nullptr) +{ + m_resultContext = new MeshResultContext(); + *m_resultContext = meshResultContext; +} + +TextureGenerator::~TextureGenerator() +{ + delete m_resultContext; + delete m_resultTextureGuideImage; + delete m_resultTextureImage; + delete m_resultTextureBorderImage; +} + +QImage *TextureGenerator::takeResultTextureGuideImage() +{ + QImage *resultTextureGuideImage = m_resultTextureGuideImage; + m_resultTextureGuideImage = nullptr; + return resultTextureGuideImage; +} + +QImage *TextureGenerator::takeResultTextureImage() +{ + QImage *resultTextureImage = m_resultTextureImage; + m_resultTextureImage = nullptr; + return resultTextureImage; +} + +QImage *TextureGenerator::takeResultTextureBorderImage() +{ + QImage *resultTextureBorderImage = m_resultTextureBorderImage; + m_resultTextureBorderImage = nullptr; + return resultTextureBorderImage; +} + +MeshResultContext *TextureGenerator::takeResultContext() +{ + MeshResultContext *resultContext = m_resultContext; + m_resultTextureImage = nullptr; + return resultContext; +} + +void TextureGenerator::process() +{ + const std::vector &triangleColors = m_resultContext->triangleColors(); + const std::vector &triangleUvs = m_resultContext->triangleUvs(); + + m_resultTextureGuideImage = new QImage(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight, QImage::Format_ARGB32); + m_resultTextureGuideImage->fill(Theme::black); + + m_resultTextureImage = new QImage(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight, QImage::Format_ARGB32); + m_resultTextureImage->fill(Qt::transparent); + + m_resultTextureBorderImage = new QImage(TextureGenerator::m_textureWidth, TextureGenerator::m_textureHeight, QImage::Format_ARGB32); + m_resultTextureBorderImage->fill(Qt::transparent); + + QPainter guidePainter; + guidePainter.begin(m_resultTextureGuideImage); + guidePainter.setRenderHint(QPainter::Antialiasing); + guidePainter.setRenderHint(QPainter::HighQualityAntialiasing); + QColor borderColor = Qt::darkGray; + QPen pen(borderColor); + + QPainter texturePainter; + texturePainter.begin(m_resultTextureImage); + texturePainter.setRenderHint(QPainter::Antialiasing); + texturePainter.setRenderHint(QPainter::HighQualityAntialiasing); + + QPainter textureBorderPainter; + textureBorderPainter.begin(m_resultTextureBorderImage); + textureBorderPainter.setRenderHint(QPainter::Antialiasing); + textureBorderPainter.setRenderHint(QPainter::HighQualityAntialiasing); + + // round 1, paint background + for (auto i = 0u; i < triangleUvs.size(); i++) { + QPainterPath path; + const ResultTriangleUv *uv = &triangleUvs[i]; + for (auto j = 0; j < 3; j++) { + if (0 == j) { + path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); + } else { + path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); + } + } + QPen textureBorderPen(triangleColors[i]); + textureBorderPen.setWidth(10); + texturePainter.setPen(textureBorderPen); + texturePainter.setBrush(QBrush(triangleColors[i])); + texturePainter.drawPath(path); + } + // round 2, real paint + texturePainter.setPen(Qt::NoPen); + guidePainter.setPen(Qt::NoPen); + for (auto i = 0u; i < triangleUvs.size(); i++) { + QPainterPath path; + const ResultTriangleUv *uv = &triangleUvs[i]; + for (auto j = 0; j < 3; j++) { + if (0 == j) { + path.moveTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); + } else { + path.lineTo(uv->uv[j][0] * TextureGenerator::m_textureWidth, uv->uv[j][1] * TextureGenerator::m_textureHeight); + } + } + guidePainter.fillPath(path, QBrush(triangleColors[i])); + texturePainter.fillPath(path, QBrush(triangleColors[i])); + } + + pen.setWidth(0); + guidePainter.setPen(pen); + textureBorderPainter.setPen(pen); + for (auto i = 0u; i < triangleUvs.size(); i++) { + const ResultTriangleUv *uv = &triangleUvs[i]; + for (auto j = 0; j < 3; j++) { + int from = j; + int to = (j + 1) % 3; + guidePainter.drawLine(uv->uv[from][0] * TextureGenerator::m_textureWidth, uv->uv[from][1] * TextureGenerator::m_textureHeight, + uv->uv[to][0] * TextureGenerator::m_textureWidth, uv->uv[to][1] * TextureGenerator::m_textureHeight); + textureBorderPainter.drawLine(uv->uv[from][0] * TextureGenerator::m_textureWidth, uv->uv[from][1] * TextureGenerator::m_textureHeight, + uv->uv[to][0] * TextureGenerator::m_textureWidth, uv->uv[to][1] * TextureGenerator::m_textureHeight); + } + } + + texturePainter.end(); + guidePainter.end(); + textureBorderPainter.end(); + + this->moveToThread(QGuiApplication::instance()->thread()); + + emit finished(); +} diff --git a/src/texturegenerator.h b/src/texturegenerator.h new file mode 100644 index 00000000..92fa64f8 --- /dev/null +++ b/src/texturegenerator.h @@ -0,0 +1,32 @@ +#ifndef TEXTURE_GENERATOR_H +#define TEXTURE_GENERATOR_H +#include +#include +#include +#include "meshresultcontext.h" + +class TextureGenerator : public QObject +{ + Q_OBJECT +public: + TextureGenerator(const MeshResultContext &meshResultContext); + ~TextureGenerator(); + QImage *takeResultTextureGuideImage(); + QImage *takeResultTextureImage(); + QImage *takeResultTextureBorderImage(); + MeshResultContext *takeResultContext(); +signals: + void finished(); +public slots: + void process(); +public: + static int m_textureWidth; + static int m_textureHeight; +private: + MeshResultContext *m_resultContext; + QImage *m_resultTextureGuideImage; + QImage *m_resultTextureImage; + QImage *m_resultTextureBorderImage; +}; + +#endif diff --git a/src/textureguidewidget.cpp b/src/textureguidewidget.cpp new file mode 100644 index 00000000..19be4e34 --- /dev/null +++ b/src/textureguidewidget.cpp @@ -0,0 +1,73 @@ +#include +#include +#include +#include +#include "textureguidewidget.h" +#include "aboutwidget.h" +#include "version.h" +#include "theme.h" + +TextureGuideWidget::TextureGuideWidget() : + m_previewLabel(nullptr) +{ + QVBoxLayout *toolButtonLayout = new QVBoxLayout; + toolButtonLayout->setSpacing(0); + toolButtonLayout->setContentsMargins(5, 10, 4, 0); + + m_previewLabel = new QLabel; + m_previewLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + QPushButton *regenerateButton = new QPushButton(QChar(fa::recycle)); + initAwesomeButton(regenerateButton); + connect(regenerateButton, &QPushButton::clicked, this, &TextureGuideWidget::regenerate); + + toolButtonLayout->addWidget(regenerateButton); + toolButtonLayout->addStretch(); + + QGridLayout *containerLayout = new QGridLayout; + containerLayout->setSpacing(0); + containerLayout->setContentsMargins(0, 0, 0, 0); + containerLayout->addWidget(m_previewLabel); + containerLayout->setRowStretch(0, 1); + containerLayout->setColumnStretch(0, 1); + + QHBoxLayout *mainLayout = new QHBoxLayout; + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->addLayout(toolButtonLayout); + mainLayout->addLayout(containerLayout); + + setLayout(mainLayout); + setMinimumSize(128, 128); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + + setWindowTitle(APP_NAME); +} + +void TextureGuideWidget::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + resizeImage(); +} + +void TextureGuideWidget::resizeImage() +{ + QPixmap pixmap = QPixmap::fromImage(m_image); + m_previewLabel->setPixmap(pixmap.scaled(m_previewLabel->width(), m_previewLabel->height(), Qt::KeepAspectRatio)); +} + +void TextureGuideWidget::updateGuideImage(const QImage &image) +{ + m_image = image; + resizeImage(); +} + +void TextureGuideWidget::initAwesomeButton(QPushButton *button) +{ + button->setFont(Theme::awesome()->font(Theme::toolIconFontSize)); + button->setFixedSize(Theme::toolIconSize, Theme::toolIconSize); + button->setStyleSheet("QPushButton {color: #f7d9c8}"); + button->setFocusPolicy(Qt::NoFocus); +} + + diff --git a/src/textureguidewidget.h b/src/textureguidewidget.h new file mode 100644 index 00000000..065afc71 --- /dev/null +++ b/src/textureguidewidget.h @@ -0,0 +1,26 @@ +#ifndef TEXTURE_GUIDE_WIDGET_H +#define TEXTURE_GUIDE_WIDGET_H +#include +#include +#include +#include + +class TextureGuideWidget : public QWidget +{ + Q_OBJECT +signals: + void regenerate(); +public: + TextureGuideWidget(); + void updateGuideImage(const QImage &image); +protected: + void resizeEvent(QResizeEvent *event); +private: + void resizeImage(); + void initAwesomeButton(QPushButton *button); +private: + QLabel *m_previewLabel; + QImage m_image; +}; + +#endif diff --git a/src/uvunwrapper.cpp b/src/uvunwrapper.cpp deleted file mode 100644 index 4a222fbf..00000000 --- a/src/uvunwrapper.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#include "uvunwrapper.h" - diff --git a/src/uvunwrapper.h b/src/uvunwrapper.h deleted file mode 100644 index a756181b..00000000 --- a/src/uvunwrapper.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef UV_UNWRAPPER_H -#define UV_UNWRAPPER_H - -class UvUnwrapper -{ -}; - -#endif diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll index 4515193153963a555a8c7ba7579a031e3799f461..4b2b9387457c2da7bd3dbd52997cc66bab5eadd4 100644 GIT binary patch delta 170981 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

;io7cuTURT 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

aLU#=c9c5uU=(LK~3A?z1z{0%=6@tPpxt! zC8v7m$Fj5%$FP9Ex~00U+H7@h^=|cT4Q*}O%A}qeZ%uJcNlkf8dre18PHk>&acx#z zX8x?q=G1y>3u^tfCAFd2mfC1-N3E&Ls&m)n*ZJy-Ii<33_d3oAcpH2T zO$}`gF;2wUnA7OtWc-aKjiJVt#we#`d^EUPHIx-{hw?+dP;sa) zx5CS*1}n-dnkw2VVilbg&dQuhPh~-+zp|vVy|SaSoYU{vloiei=Y~7No#BklS(|e< z`>TRgkpL%~wZ*-qdP~a|!^!&Sk@o5=PA{}IwrF#>Lv=%YLvCY!qpz{JG1yq%X#9*Q&Z9Ww zs;I7Lsc5f=S7cNMoRwLXF6vudnHw$$7ln(%?bI=gekq|}$`d^kp>N`wi>k`2;#Dpx zTe78`3j3=4iOP0VXV5PhH3f+tD6I+Av@m$$^nt0(U;yRQ3q=g0l3EQKPkny9ufCc} zx*GBue1Qf-L$@({s9KN<47h0MAPpQ3HRdNk4yP4sC2{aH=BF+7cl5PLCkfGmb)NvNE`*%9hwXl5na zuB5V@_G_uk2)h`5o^XER8VrU*;ihm~xS%~8XG9vtqi1tt<8 delta 148198 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 dwybjLUs>nsSJ`4Yi`&WyxJI$Ke-vGthxkUf~ delta 2618 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 F{{}y>P7eS8 delta 140264 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 F-vJ8w>b3v? delta 2547 zcmaKtO=w(I6vt0KCf}1xGGEDL()q~DWM-0?dGnDslQd}(L`)ZAf}u#&8bL&;L@0ub zs!0}Vpdo~uQc^HMT+|W@tw}AlD4M$Iq7q7Xg=%mi1s8=PQBfNI_r5o8-g9B$$4Te> z&$;)WbMAe&&pvv0)nBKS)Ph>zV8AnQE-_kw6769K_s~g z3wpe_mTGzF6&iR#1uj1$UF~R4Y9Xp@+ zxVBQnSPlL4>(24OVO8Zf55Ey|*zo(wTl{4>!0Vw$X6L72hYY$Jjv7Hzkz}I?KrxB< zUfjvSNU|IRrwHH=BF|KQ^73m@r;I^Es9MIXMB`?`eO-V~QH>OQSku5wv2a<03(u1= zm&}3|QLPj$blCZ$Sja589dpU^B>44MCqJxd=BL&y$A+~mXfV|wi<|elTKI6-&X4nM zbEaTH6QaRX522T5YuotmKFe}SLR&7!_xd16sOAh6=QN@hY;OJ|VToQ&X-ioCU{~_2}p4lij>u*TjGJS27G}y~Jgr zR(^3{(11KLkCmOPBig3%s*sD_nS9wHqBc}5>Ij@} zUd$LL63+V>%^8H#!;dq?N@QiGy*!=u@OKS$d{!~GEXJ=C*N%-BfjXt=HzifhAOznJ zX#%_y)CGajP4kuU@m_Y+9EG&<%Iqm#-p|_j<(ws{dFcJTo3j+#-2EOneSFd1!tcc` z`~Iw;O+c0>a&BJrH}aIn4$=P(e1Jy_&zZN0EVAi%<|xSD7LJ>p;X%!b2bDrRHfZ!p zl*JqwhO?8$d)oM?!O9du|28+mU#phpHW!_8s_-$OD5h$$ z$IZuzM!eXGxm+>6Sgah-H12)eRk9@hxuhk=SB2t8JYje9W+_rWzu1^rl8_QFyM`)j zo6;xwogpK=J~eXRVru%uzFRx_skd=$xDt-7;~Gnqc(n#})uFwf-?z%};z$`jLX^^F zCy%t^d9#ef$Wcv(?-Qk^tdD=m8w-jfnai1hWVhALzaOkv$Q3$c(;;5DmUvbLw#%T0kFZX9< diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.h b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.h index 85aec43c..47ab475a 100644 --- a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.h +++ b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.h @@ -46,6 +46,9 @@ int meshlite_bmesh_generate_mesh(void *context, int bmesh_id); int meshlite_bmesh_get_node_base_norm(void *context, int bmesh_id, int node_id, float *norm_buffer); int meshlite_bmesh_destroy(void *context, int bmesh_id); int meshlite_bmesh_error_count(void *context, int bmesh_id); +int meshlite_bmesh_add_seam_requirement(void *context, int bmesh_id); +int meshlite_bmesh_get_seam_count(void *context, int bmesh_id); +int meshlite_bmesh_get_seam_index_array(void *context, int bmesh_id, int *buffer, int max_buffer_len); int meshlite_combine_adj_faces(void *context, int mesh_id); int meshlite_combine_coplanar_faces(void *context, int mesh_id); int meshlite_trim(void *context, int mesh_id, int normalize); diff --git a/thirdparty/thekla_atlas/.gitattributes b/thirdparty/thekla_atlas/.gitattributes new file mode 100755 index 00000000..beb3e665 --- /dev/null +++ b/thirdparty/thekla_atlas/.gitattributes @@ -0,0 +1,10 @@ +* text=auto + +*.c text +*.cpp text +*.h text +*.md text +*.txt text + +*.sln text eol=crlf +*.vcproj text eol=crlf diff --git a/thirdparty/thekla_atlas/.gitignore b/thirdparty/thekla_atlas/.gitignore new file mode 100755 index 00000000..1ee65769 --- /dev/null +++ b/thirdparty/thekla_atlas/.gitignore @@ -0,0 +1,16 @@ +# Compiled Object files +*.slo +*.lo +*.o + +# Compiled Dynamic libraries +*.so +*.dylib + +# Compiled Static libraries +*.lai +*.la +*.a + +# CMake generated project +build diff --git a/thirdparty/thekla_atlas/CMakeLists.txt b/thirdparty/thekla_atlas/CMakeLists.txt new file mode 100755 index 00000000..b74d29b5 --- /dev/null +++ b/thirdparty/thekla_atlas/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 2.8.8) +project(thekla_atlas) + +#add_definitions(-DNV_OS_DARWIN=1) + +include_directories( + src + src/nvcore + extern/tinyobj + extern/poshlib) + +add_subdirectory(extern/poshlib) +add_subdirectory(src/nvcore) +add_subdirectory(src/nvimage) +add_subdirectory(src/nvmath) +add_subdirectory(src/nvmesh) + +add_executable( + thekla_atlas_test + src/thekla/thekla_atlas_test.cpp + src/thekla/thekla_atlas.cpp) + +target_link_libraries( + thekla_atlas_test + nvmesh) diff --git a/thirdparty/thekla_atlas/LICENSE b/thirdparty/thekla_atlas/LICENSE new file mode 100755 index 00000000..164e7d3a --- /dev/null +++ b/thirdparty/thekla_atlas/LICENSE @@ -0,0 +1,8 @@ +Copyright (c) 2013 Thekla, Inc + + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/thirdparty/thekla_atlas/README.md b/thirdparty/thekla_atlas/README.md new file mode 100755 index 00000000..8a543f64 --- /dev/null +++ b/thirdparty/thekla_atlas/README.md @@ -0,0 +1,20 @@ +thekla_atlas +============ + +This tool performs mesh segmentation, surface parameterization, and chart packing. + +This is especially useful when generating light maps for meshes that do not have artist-supplied uv's. + +### How to build (Windows) + +Open `vc9/thekla.sln` using Visual Studio. + +### How to build (OS X) + +Use cmake. For example: + +```bash +$ brew install cmake # Install cmake +$ cmake -H. -Bbuild # Create makefiles +$ cmake --build build # Invoke the build +``` diff --git a/thirdparty/thekla_atlas/extern/poshlib/CMakeLists.txt b/thirdparty/thekla_atlas/extern/poshlib/CMakeLists.txt new file mode 100755 index 00000000..a0f639ef --- /dev/null +++ b/thirdparty/thekla_atlas/extern/poshlib/CMakeLists.txt @@ -0,0 +1,7 @@ + +SET(POSHLIB_SRCS + posh.c + posh.h) + +ADD_LIBRARY(posh STATIC ${POSHLIB_SRCS}) + diff --git a/thirdparty/thekla_atlas/extern/poshlib/posh.c b/thirdparty/thekla_atlas/extern/poshlib/posh.c new file mode 100755 index 00000000..bd3fcc66 --- /dev/null +++ b/thirdparty/thekla_atlas/extern/poshlib/posh.c @@ -0,0 +1,1006 @@ +/* +LICENSE: + +Copyright (c) 2004, Brian Hook +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of this package'ss contributors contributors may not + be used to endorse or promote products derived from this + software without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/** + @file posh.c + @author Brian Hook + @date 2002 + @brief Portable Open Source Harness primary source file +*/ +#include "posh.h" + +#if !defined FORCE_DOXYGEN + +#if !defined POSH_NO_FLOAT +# define POSH_FLOAT_STRING "enabled" +#else +# define POSH_FLOAT_STRING "disabled" +#endif + +#if defined POSH_64BIT_INTEGER +# define POSH_64BIT_INTEGER_STRING "yes" +#else +# define POSH_64BIT_INTEGER_STRING "no" +#endif + +#if defined POSH_64BIT_POINTER +# define POSH_POINTER_STRING "64-bits" +#else +# define POSH_POINTER_STRING "32-bits" +#endif + +#if defined POSH_LITTLE_ENDIAN +# define IS_BIG_ENDIAN 0 + +# define NATIVE16 POSH_LittleU16 +# define NATIVE32 POSH_LittleU32 +# define NATIVE64 POSH_LittleU64 +# define FOREIGN16 POSH_BigU16 +# define FOREIGN32 POSH_BigU32 +# define FOREIGN64 POSH_BigU64 +#else +# define IS_BIG_ENDIAN 1 + +# define NATIVE16 POSH_BigU16 +# define NATIVE32 POSH_BigU32 +# define NATIVE64 POSH_BigU64 +# define FOREIGN16 POSH_LittleU16 +# define FOREIGN32 POSH_LittleU32 +# define FOREIGN64 POSH_LittleU64 +#endif /* POSH_LITTLE_ENDIAN */ + +static +int +s_testBigEndian( void ) +{ + union + { + posh_byte_t c[ 4 ]; + posh_u32_t i; + } u; + + u.i= 1; + + if ( u.c[ 0 ] == 1 ) + { + return 0; + } + return 1; +} + +static +const char * +s_testSerialization( void ) +{ + posh_byte_t serbuf[ 8 ]; + posh_u16_t tmp16; + posh_u32_t tmp32; + + /* 16-bit serialization */ + POSH_WriteU16ToLittle( serbuf, 0xABCD ); + if ( ( tmp16 = POSH_ReadU16FromLittle( serbuf ) ) != 0xABCD ) + { + return "*ERROR: failed little-endian 16-bit serialization test"; + } + + POSH_WriteU16ToBig( serbuf, 0xABCD ); + if ( ( tmp16 = POSH_ReadU16FromBig( serbuf ) ) != 0xABCD ) + { + return "*ERROR: failed big-endian 16-bit serialization test"; + } + + /* 32-bit serialization */ + POSH_WriteU32ToLittle( serbuf, 0xABCD1234L ); + if ( ( tmp32 = POSH_ReadU32FromLittle( serbuf ) ) != 0xABCD1234 ) + { + return "*ERROR: failed little-endian 32-bit serialization test"; + } + + POSH_WriteU32ToBig( serbuf, 0xABCD1234L ); + if ( ( tmp32 = POSH_ReadU32FromBig( serbuf ) ) != 0xABCD1234 ) + { + return "*ERROR: failed big-endian 32-bit serialization test"; + } + +#if defined POSH_64BIT_INTEGER + { +#define REF64 POSH_U64(0xFEDCBA9876543210) + + posh_u64_t tmp64; + + POSH_WriteU64ToLittle( serbuf, REF64 ); + + if ( ( tmp64 = POSH_ReadU64FromLittle( serbuf ) ) != REF64 ) + { + return "*ERROR: failed little-endian 64-bit serialization test"; + } + + POSH_WriteU64ToBig( serbuf, REF64 ); + + if ( ( tmp64 = POSH_ReadU64FromBig( serbuf ) ) != REF64 ) + { + return "*ERROR: failed big-endian 64-bit serialization test"; + } + } +#endif + + return 0; +} + +#if !defined POSH_NO_FLOAT +static +const char * +s_testFloatingPoint( void ) +{ + float fRef = 10.0f/30.0f; + double dRef = 10.0/30.0; + posh_byte_t dbuf[ 8 ]; + float fTmp; + double dTmp; + + fTmp = POSH_FloatFromLittleBits( POSH_LittleFloatBits( fRef ) ); + + if ( fTmp != fRef ) + { + return "*ERROR: POSH little endian floating point conversion failed. Please report this to poshlib@poshlib.org!\n"; + } + + fTmp = POSH_FloatFromBigBits( POSH_BigFloatBits( fRef ) ); + if ( fTmp != fRef ) + { + return "*ERROR: POSH big endian floating point conversion failed. Please report this to poshlib@poshlib.org!\n"; + } + + POSH_DoubleBits( dRef, dbuf ); + + dTmp = POSH_DoubleFromBits( dbuf ); + + if ( dTmp != dRef ) + { + return "*ERROR: POSH double precision floating point serialization failed. Please report this to poshlib@poshlib.org!\n"; + } + + return 0; +} +#endif /* !defined POSH_NO_FLOAT */ + +static +const char * +s_testEndianess( void ) +{ + /* check endianess */ + if ( s_testBigEndian() != IS_BIG_ENDIAN ) + { + return "*ERROR: POSH compile time endianess does not match run-time endianess verification. Please report this to poshlib@poshlib.org!\n"; + } + + /* make sure our endian swap routines work */ + if ( ( NATIVE32( 0x11223344L ) != 0x11223344L ) || + ( FOREIGN32( 0x11223344L ) != 0x44332211L ) || + ( NATIVE16( 0x1234 ) != 0x1234 ) || + ( FOREIGN16( 0x1234 ) != 0x3412 ) ) + { + return "*ERROR: POSH endianess macro selection failed. Please report this to poshlib@poshlib.org!\n"; + } + + /* test serialization routines */ + + return 0; +} +#endif /* !defined FORCE_DOXYGEN */ + +/** + Returns a string describing this platform's basic attributes. + + POSH_GetArchString() reports on an architecture's statically determined + attributes. In addition, it will perform run-time verification checks + to make sure the various platform specific functions work. If an error + occurs, please contact me at poshlib@poshlib.org so we can try to resolve + what the specific failure case is. + @returns a string describing this platform on success, or a string in the + form "*ERROR: [text]" on failure. You can simply check to see if + the first character returned is '*' to verify an error condition. +*/ +const char * +POSH_GetArchString( void ) +{ + const char *err; + const char *s = "OS:.............."POSH_OS_STRING"\n" + "CPU:............."POSH_CPU_STRING"\n" + "endian:.........."POSH_ENDIAN_STRING"\n" + "ptr size:........"POSH_POINTER_STRING"\n" + "64-bit ints......"POSH_64BIT_INTEGER_STRING"\n" + "floating point..."POSH_FLOAT_STRING"\n" + "compiler........."POSH_COMPILER_STRING"\n"; + + /* test endianess */ + err = s_testEndianess(); + + if ( err != 0 ) + { + return err; + } + + /* test serialization */ + err = s_testSerialization(); + + if ( err != 0 ) + { + return err; + } + +#if !defined POSH_NO_FLOAT + /* check that our floating point support is correct */ + err = s_testFloatingPoint(); + + if ( err != 0 ) + { + return err; + } + +#endif + + return s; +} + +/* ---------------------------------------------------------------------------*/ +/* BYTE SWAPPING SUPPORT */ +/* ---------------------------------------------------------------------------*/ +/** + * Byte swaps a 16-bit unsigned value + * + @ingroup ByteSwapFunctions + @param v [in] unsigned 16-bit input value to swap + @returns a byte swapped version of v + */ +posh_u16_t +POSH_SwapU16( posh_u16_t v ) +{ + posh_u16_t swapped; + + swapped = v << 8; + swapped |= v >> 8; + + return swapped; +} + +/** + * Byte swaps a 16-bit signed value + * + @ingroup ByteSwapFunctions + @param v [in] signed 16-bit input value to swap + @returns a byte swapped version of v + @remarks This just calls back to the unsigned version, since byte swapping + is independent of sign. However, we still provide this function to + avoid signed/unsigned mismatch compiler warnings. + */ +posh_i16_t +POSH_SwapI16( posh_i16_t v ) +{ + return ( posh_i16_t ) POSH_SwapU16( v ); +} + +/** + * Byte swaps a 32-bit unsigned value + * + @ingroup ByteSwapFunctions + @param v [in] unsigned 32-bit input value to swap + @returns a byte swapped version of v + */ +posh_u32_t +POSH_SwapU32( posh_u32_t v ) +{ + posh_u32_t swapped; + + swapped = ( v & 0xFF ) << 24; + swapped |= ( v & 0xFF00 ) << 8; + swapped |= ( v >> 8 ) & 0xFF00; + swapped |= ( v >> 24 ); + + return swapped; +} + +/** + * Byte swaps a 32-bit signed value + * + @ingroup ByteSwapFunctions + @param v [in] signed 32-bit input value to swap + @returns a byte swapped version of v + @remarks This just calls back to the unsigned version, since byte swapping + is independent of sign. However, we still provide this function to + avoid signed/unsigned mismatch compiler warnings. + */ +posh_i32_t +POSH_SwapI32( posh_i32_t v ) +{ + return ( posh_i32_t ) POSH_SwapU32( ( posh_u32_t ) v ); +} + +#if defined POSH_64BIT_INTEGER +/** + * Byte swaps a 64-bit unsigned value + + @param v [in] a 64-bit input value to swap + @ingroup SixtyFourBit + @returns a byte swapped version of v +*/ +posh_u64_t +POSH_SwapU64( posh_u64_t v ) +{ + posh_byte_t tmp; + union { + posh_byte_t bytes[ 8 ]; + posh_u64_t u64; + } u; + + u.u64 = v; + + tmp = u.bytes[ 0 ]; u.bytes[ 0 ] = u.bytes[ 7 ]; u.bytes[ 7 ] = tmp; + tmp = u.bytes[ 1 ]; u.bytes[ 1 ] = u.bytes[ 6 ]; u.bytes[ 6 ] = tmp; + tmp = u.bytes[ 2 ]; u.bytes[ 2 ] = u.bytes[ 5 ]; u.bytes[ 5 ] = tmp; + tmp = u.bytes[ 3 ]; u.bytes[ 3 ] = u.bytes[ 4 ]; u.bytes[ 4 ] = tmp; + + return u.u64; +} + +/** + * Byte swaps a 64-bit signed value + + @param v [in] a 64-bit input value to swap + @ingroup SixtyFourBit + @returns a byte swapped version of v +*/ +posh_i64_t +POSH_SwapI64( posh_i64_t v ) +{ + return ( posh_i64_t ) POSH_SwapU64( ( posh_u64_t ) v ); +} + +#endif /* defined POSH_64BIT_INTEGER */ + +/* ---------------------------------------------------------------------------*/ +/* IN-MEMORY SERIALIZATION */ +/* ---------------------------------------------------------------------------*/ + +/** + * Writes an unsigned 16-bit value to a little endian buffer + + @ingroup MemoryBuffer + @param dst [out] pointer to the destination buffer, may not be NULL. Alignment doesn't matter. + @param value [in] host-endian unsigned 16-bit value + @returns a pointer to the location two bytes after dst + @remarks does no validation of the inputs +*/ +posh_u16_t * +POSH_WriteU16ToLittle( void *dst, posh_u16_t value ) +{ + posh_u16_t *p16 = ( posh_u16_t * ) dst; + posh_byte_t *p = ( posh_byte_t * ) dst; + + p[ 0 ] = value & 0xFF; + p[ 1 ] = ( value & 0xFF00) >> 8; + + return p16 + 1; +} + +/** + * Writes a signed 16-bit value to a little endian buffer + + @ingroup MemoryBuffer + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian signed 16-bit value + @returns a pointer to the location two bytes after dst + @remarks does no validation of the inputs. This simply calls + POSH_WriteU16ToLittle() with appropriate casting. +*/ +posh_i16_t * +POSH_WriteI16ToLittle( void *dst, posh_i16_t value ) +{ + return ( posh_i16_t * ) POSH_WriteU16ToLittle( dst, ( posh_u16_t ) value ); +} + +/** + * Writes an unsigned 32-bit value to a little endian buffer + + @ingroup MemoryBuffer + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian signed 32-bit value + @returns a pointer to the location four bytes after dst + @remarks does no validation of the inputs. +*/ +posh_u32_t * +POSH_WriteU32ToLittle( void *dst, posh_u32_t value ) +{ + posh_u32_t *p32 = ( posh_u32_t * ) dst; + posh_byte_t *p = ( posh_byte_t * ) dst; + + p[ 0 ] = ( value & 0xFF ); + p[ 1 ] = ( value & 0xFF00 ) >> 8; + p[ 2 ] = ( value & 0xFF0000 ) >> 16; + p[ 3 ] = ( value & 0xFF000000 ) >> 24; + + return p32 + 1; +} + +/** + * Writes a signed 32-bit value to a little endian buffer + + @ingroup MemoryBuffer + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian signed 32-bit value + @returns a pointer to the location four bytes after dst + @remarks does no validation of the inputs. This simply calls + POSH_WriteU32ToLittle() with appropriate casting. +*/ +posh_i32_t * +POSH_WriteI32ToLittle( void *dst, posh_i32_t value ) +{ + return ( posh_i32_t * ) POSH_WriteU32ToLittle( dst, ( posh_u32_t ) value ); +} + +/** + * Writes an unsigned 16-bit value to a big endian buffer + + @ingroup MemoryBuffer + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian unsigned 16-bit value + @returns a pointer to the location two bytes after dst + @remarks does no validation of the inputs +*/ +posh_u16_t * +POSH_WriteU16ToBig( void *dst, posh_u16_t value ) +{ + posh_u16_t *p16 = ( posh_u16_t * ) dst; + posh_byte_t *p = ( posh_byte_t * ) dst; + + p[ 1 ] = ( value & 0xFF ); + p[ 0 ] = ( value & 0xFF00 ) >> 8; + + return p16 + 1; +} + +/** + * Writes a signed 16-bit value to a big endian buffer + + @ingroup MemoryBuffer + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian signed 16-bit value + @returns a pointer to the location two bytes after dst + @remarks does no validation of the inputs. This simply calls + POSH_WriteU16ToLittle() with appropriate casting. +*/ +posh_i16_t * +POSH_WriteI16ToBig( void *dst, posh_i16_t value ) +{ + return ( posh_i16_t * ) POSH_WriteU16ToBig( dst, ( posh_u16_t ) value ); +} + +/** + * Writes an unsigned 32-bit value to a big endian buffer + + @ingroup MemoryBuffer + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian unsigned 32-bit value + @returns a pointer to the location four bytes after dst + @remarks does no validation of the inputs. +*/ +posh_u32_t * +POSH_WriteU32ToBig( void *dst, posh_u32_t value ) +{ + posh_u32_t *p32 = ( posh_u32_t * ) dst; + posh_byte_t *p = ( posh_byte_t * ) dst; + + p[ 3 ] = ( value & 0xFF ); + p[ 2 ] = ( value & 0xFF00 ) >> 8; + p[ 1 ] = ( value & 0xFF0000 ) >> 16; + p[ 0 ] = ( value & 0xFF000000 ) >> 24; + + return p32 + 1; +} + +/** + * Writes a signed 32-bit value to a big endian buffer + + @ingroup MemoryBuffer + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian signed 32-bit value + @returns a pointer to the location four bytes after dst + @remarks does no validation of the inputs. This simply calls + POSH_WriteU32ToBig() with appropriate casting. +*/ +posh_i32_t * +POSH_WriteI32ToBig( void *dst, posh_i32_t value ) +{ + return ( posh_i32_t * ) POSH_WriteU32ToBig( dst, ( posh_u32_t ) value ); +} + +#if defined POSH_64BIT_INTEGER +/** + * Writes an unsigned 64-bit value to a little-endian buffer + + @ingroup SixtyFourBit + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian unsigned 64-bit value + @returns a pointer to the location eight bytes after dst + @remarks does no validation of the inputs. +*/ +posh_u64_t * +POSH_WriteU64ToLittle( void *dst, posh_u64_t value ) +{ + posh_u64_t *p64 = ( posh_u64_t * ) dst; + posh_byte_t *p = ( posh_byte_t * ) dst; + int i; + + for ( i = 0; i < 8; i++, value >>= 8 ) + { + p[ i ] = ( posh_byte_t ) ( value & 0xFF ); + } + + return p64 + 1; +} + +/** + * Writes a signed 64-bit value to a little-endian buffer + + @ingroup SixtyFourBit + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian unsigned 64-bit value + @returns a pointer to the location eight bytes after dst + @remarks does no validation of the inputs. +*/ +posh_i64_t * +POSH_WriteI64ToLittle( void *dst, posh_i64_t value ) +{ + return ( posh_i64_t * ) POSH_WriteU64ToLittle( dst, ( posh_u64_t ) value ); +} + +/** + * Writes an unsigned 64-bit value to a big-endian buffer + + @ingroup SixtyFourBit + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian unsigned 64-bit value + @returns a pointer to the location eight bytes after dst + @remarks does no validation of the inputs. +*/ +posh_u64_t * +POSH_WriteU64ToBig( void *dst, posh_u64_t value ) +{ + posh_u64_t *p64 = ( posh_u64_t * ) dst; + posh_byte_t *p = ( posh_byte_t * ) dst; + int i; + + for ( i = 0; i < 8; i++, value >>= 8 ) + { + p[ 7-i ] = ( posh_byte_t ) ( value & 0xFF ); + } + + return p64 + 8; +} + +/** + * Writes a signed 64-bit value to a big-endian buffer + + @ingroup SixtyFourBit + @param dst [out] pointer to the destination buffer, may not be NULL + @param value [in] host-endian signed 64-bit value + @returns a pointer to the location eight bytes after dst + @remarks does no validation of the inputs. +*/ +posh_i64_t * +POSH_WriteI64ToBig( void *dst, posh_i64_t value ) +{ + return ( posh_i64_t * ) POSH_WriteU64ToBig( dst, ( posh_u64_t ) value ); +} + +#endif /* POSH_64BIT_INTEGER */ + +/* ---------------------------------------------------------------------------*/ +/* IN-MEMORY DESERIALIZATION */ +/* ---------------------------------------------------------------------------*/ + +/** + * Reads an unsigned 16-bit value from a little-endian buffer + @ingroup MemoryBuffer + @param src [in] source buffer + @returns host-endian unsigned 16-bit value +*/ +posh_u16_t +POSH_ReadU16FromLittle( const void *src ) +{ + posh_u16_t v = 0; + posh_byte_t *p = ( posh_byte_t * ) src; + + v |= p[ 0 ]; + v |= ( ( posh_u16_t ) p[ 1 ] ) << 8; + + return v; +} + +/** + * Reads a signed 16-bit value from a little-endian buffer + @ingroup MemoryBuffer + @param src [in] source buffer + @returns host-endian signed 16-bit value +*/ +posh_i16_t +POSH_ReadI16FromLittle( const void *src ) +{ + return ( posh_i16_t ) POSH_ReadU16FromLittle( src ); +} + +/** + * Reads an unsigned 32-bit value from a little-endian buffer + @ingroup MemoryBuffer + @param src [in] source buffer + @returns host-endian unsigned 32-bit value +*/ +posh_u32_t +POSH_ReadU32FromLittle( const void *src ) +{ + posh_u32_t v = 0; + posh_byte_t *p = ( posh_byte_t * ) src; + + v |= p[ 0 ]; + v |= ( ( posh_u32_t ) p[ 1 ] ) << 8; + v |= ( ( posh_u32_t ) p[ 2 ] ) << 16; + v |= ( ( posh_u32_t ) p[ 3 ] ) << 24; + + return v; +} + +/** + * Reads a signed 32-bit value from a little-endian buffer + @ingroup MemoryBuffer + @param src [in] source buffer + @returns host-endian signed 32-bit value +*/ +posh_i32_t +POSH_ReadI32FromLittle( const void *src ) +{ + return ( posh_i32_t ) POSH_ReadU32FromLittle( src ); +} + + +/** + * Reads an unsigned 16-bit value from a big-endian buffer + @ingroup MemoryBuffer + @param src [in] source buffer + @returns host-endian unsigned 16-bit value +*/ +posh_u16_t +POSH_ReadU16FromBig( const void *src ) +{ + posh_u16_t v = 0; + posh_byte_t *p = ( posh_byte_t * ) src; + + v |= p[ 1 ]; + v |= ( ( posh_u16_t ) p[ 0 ] ) << 8; + + return v; +} + +/** + * Reads a signed 16-bit value from a big-endian buffer + @ingroup MemoryBuffer + @param src [in] source buffer + @returns host-endian signed 16-bit value +*/ +posh_i16_t +POSH_ReadI16FromBig( const void *src ) +{ + return ( posh_i16_t ) POSH_ReadU16FromBig( src ); +} + +/** + * Reads an unsigned 32-bit value from a big-endian buffer + @ingroup MemoryBuffer + @param src [in] source buffer + @returns host-endian unsigned 32-bit value +*/ +posh_u32_t +POSH_ReadU32FromBig( const void *src ) +{ + posh_u32_t v = 0; + posh_byte_t *p = ( posh_byte_t * ) src; + + v |= p[ 3 ]; + v |= ( ( posh_u32_t ) p[ 2 ] ) << 8; + v |= ( ( posh_u32_t ) p[ 1 ] ) << 16; + v |= ( ( posh_u32_t ) p[ 0 ] ) << 24; + + return v; +} + +/** + * Reads a signed 32-bit value from a big-endian buffer + @ingroup MemoryBuffer + @param src [in] source buffer + @returns host-endian signed 32-bit value +*/ +posh_i32_t +POSH_ReadI32FromBig( const void *src ) +{ + return POSH_BigI32( (*(const posh_i32_t*)src ) ); +} + +#if defined POSH_64BIT_INTEGER + +/** + * Reads an unsigned 64-bit value from a little-endian buffer + @param src [in] source buffer + @returns host-endian unsigned 32-bit value +*/ +posh_u64_t +POSH_ReadU64FromLittle( const void *src ) +{ + posh_u64_t v = 0; + posh_byte_t *p = ( posh_byte_t * ) src; + int i; + + for ( i = 0; i < 8; i++ ) + { + v |= ( ( posh_u64_t ) p[ i ] ) << (i*8); + } + + return v; +} + +/** + * Reads a signed 64-bit value from a little-endian buffer + @param src [in] source buffer + @returns host-endian signed 32-bit value +*/ +posh_i64_t +POSH_ReadI64FromLittle( const void *src ) +{ + return ( posh_i64_t ) POSH_ReadU64FromLittle( src ); +} + +/** + * Reads an unsigned 64-bit value from a big-endian buffer + @param src [in] source buffer + @returns host-endian unsigned 32-bit value +*/ +posh_u64_t +POSH_ReadU64FromBig( const void *src ) +{ + posh_u64_t v = 0; + posh_byte_t *p = ( posh_byte_t * ) src; + int i; + + for ( i = 0; i < 8; i++ ) + { + v |= ( ( posh_u64_t ) p[ 7-i ] ) << (i*8); + } + + return v; +} + +/** + * Reads an signed 64-bit value from a big-endian buffer + @param src [in] source buffer + @returns host-endian signed 32-bit value +*/ +posh_i64_t +POSH_ReadI64FromBig( const void *src ) +{ + return ( posh_i64_t ) POSH_ReadU64FromBig( src ); +} + +#endif /* POSH_64BIT_INTEGER */ + +/* ---------------------------------------------------------------------------*/ +/* FLOATING POINT SUPPORT */ +/* ---------------------------------------------------------------------------*/ + +#if !defined POSH_NO_FLOAT + +/** @ingroup FloatingPoint + @param[in] f floating point value + @returns a little-endian bit representation of f + */ +posh_u32_t +POSH_LittleFloatBits( float f ) +{ + union + { + float f32; + posh_u32_t u32; + } u; + + u.f32 = f; + + return POSH_LittleU32( u.u32 ); +} + +/** + * Extracts raw big-endian bits from a 32-bit floating point value + * + @ingroup FloatingPoint + @param f [in] floating point value + @returns a big-endian bit representation of f + */ +posh_u32_t +POSH_BigFloatBits( float f ) +{ + union + { + float f32; + posh_u32_t u32; + } u; + + u.f32 = f; + + return POSH_BigU32( u.u32 ); +} + +/** + * Extracts raw, little-endian bit representation from a 64-bit double. + * + @param d [in] 64-bit double precision value + @param dst [out] 8-byte storage buffer + @ingroup FloatingPoint + @returns the raw bits used to represent the value 'd', in the form dst[0]=LSB + */ +void +POSH_DoubleBits( double d, posh_byte_t dst[ 8 ] ) +{ + union + { + double d64; + posh_byte_t bytes[ 8 ]; + } u; + + u.d64 = d; + +#if defined POSH_LITTLE_ENDIAN + dst[ 0 ] = u.bytes[ 0 ]; + dst[ 1 ] = u.bytes[ 1 ]; + dst[ 2 ] = u.bytes[ 2 ]; + dst[ 3 ] = u.bytes[ 3 ]; + dst[ 4 ] = u.bytes[ 4 ]; + dst[ 5 ] = u.bytes[ 5 ]; + dst[ 6 ] = u.bytes[ 6 ]; + dst[ 7 ] = u.bytes[ 7 ]; +#else + dst[ 0 ] = u.bytes[ 7 ]; + dst[ 1 ] = u.bytes[ 6 ]; + dst[ 2 ] = u.bytes[ 5 ]; + dst[ 3 ] = u.bytes[ 4 ]; + dst[ 4 ] = u.bytes[ 3 ]; + dst[ 5 ] = u.bytes[ 2 ]; + dst[ 6 ] = u.bytes[ 1 ]; + dst[ 7 ] = u.bytes[ 0 ]; +#endif +} + +/** + * Creates a double-precision, 64-bit floating point value from a set of raw, + * little-endian bits + + @ingroup FloatingPoint + @param src [in] little-endian byte representation of 64-bit double precision + floating point value + @returns double precision floating point representation of the raw bits + @remarks No error checking is performed, so there are no guarantees that the + result is a valid number, nor is there any check to ensure that src is + non-NULL. BE CAREFUL USING THIS. + */ +double +POSH_DoubleFromBits( const posh_byte_t src[ 8 ] ) +{ + union + { + double d64; + posh_byte_t bytes[ 8 ]; + } u; + +#if defined POSH_LITTLE_ENDIAN + u.bytes[ 0 ] = src[ 0 ]; + u.bytes[ 1 ] = src[ 1 ]; + u.bytes[ 2 ] = src[ 2 ]; + u.bytes[ 3 ] = src[ 3 ]; + u.bytes[ 4 ] = src[ 4 ]; + u.bytes[ 5 ] = src[ 5 ]; + u.bytes[ 6 ] = src[ 6 ]; + u.bytes[ 7 ] = src[ 7 ]; +#else + u.bytes[ 0 ] = src[ 7 ]; + u.bytes[ 1 ] = src[ 6 ]; + u.bytes[ 2 ] = src[ 5 ]; + u.bytes[ 3 ] = src[ 4 ]; + u.bytes[ 4 ] = src[ 3 ]; + u.bytes[ 5 ] = src[ 2 ]; + u.bytes[ 6 ] = src[ 1 ]; + u.bytes[ 7 ] = src[ 0 ]; +#endif + + return u.d64; +} + +/** + * Creates a floating point number from little endian bits + * + @ingroup FloatingPoint + @param bits [in] raw floating point bits in little-endian form + @returns a floating point number based on the given bit representation + @remarks No error checking is performed, so there are no guarantees that the + result is a valid number. BE CAREFUL USING THIS. + */ +float +POSH_FloatFromLittleBits( posh_u32_t bits ) +{ + union + { + float f32; + posh_u32_t u32; + } u; + + u.u32 = bits; +#if defined POSH_BIG_ENDIAN + u.u32 = POSH_SwapU32( u.u32 ); +#endif + + return u.f32; +} + +/** + * Creates a floating point number from big-endian bits + * + @ingroup FloatingPoint + @param bits [in] raw floating point bits in big-endian form + @returns a floating point number based on the given bit representation + @remarks No error checking is performed, so there are no guarantees that the + result is a valid number. BE CAREFUL USING THIS. + */ +float +POSH_FloatFromBigBits( posh_u32_t bits ) +{ + union + { + float f32; + posh_u32_t u32; + } u; + + u.u32 = bits; +#if defined POSH_LITTLE_ENDIAN + u.u32 = POSH_SwapU32( u.u32 ); +#endif + + return u.f32; +} + +#endif /* !defined POSH_NO_FLOAT */ diff --git a/thirdparty/thekla_atlas/extern/poshlib/posh.h b/thirdparty/thekla_atlas/extern/poshlib/posh.h new file mode 100755 index 00000000..3038297b --- /dev/null +++ b/thirdparty/thekla_atlas/extern/poshlib/posh.h @@ -0,0 +1,1035 @@ +/** +@file posh.h +@author Brian Hook +@version 1.3.001 + +Header file for POSH, the Portable Open Source Harness project. + +NOTE: Unlike most header files, this one is designed to be included +multiple times, which is why it does not have the @#ifndef/@#define +preamble. + +POSH relies on environment specified preprocessor symbols in order +to infer as much as possible about the target OS/architecture and +the host compiler capabilities. + +NOTE: POSH is simple and focused. It attempts to provide basic +functionality and information, but it does NOT attempt to emulate +missing functionality. I am also not willing to make POSH dirty +and hackish to support truly ancient and/or outmoded and/or bizarre +technologies such as non-ANSI compilers, systems with non-IEEE +floating point formats, segmented 16-bit operating systems, etc. + +Please refer to the accompanying HTML documentation or visit +http://www.poshlib.org for more information on how to use POSH. + +LICENSE: + +Copyright (c) 2004, Brian Hook +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of this package'ss contributors contributors may not + be used to endorse or promote products derived from this + software without specific prior written permission. + + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +REVISION: + +I've been lax about revision histories, so this starts at, um, 1.3.001. +Sorry for any inconveniences. + +1.3.001 - 2/23/2006 - Incorporated fix for bug reported by Bill Cary, + where I was not detecting Visual Studio + compilation on x86-64 systems. Added check for + _M_X64 which should fix that. + +*/ +/* +I have yet to find an authoritative reference on preprocessor +symbols, but so far this is what I've gleaned: + +GNU GCC/G++: + - __GNUC__: GNU C version + - __GNUG__: GNU C++ compiler + - __sun__ : on Sun platforms + - __svr4__: on Solaris and other SysV R4 platforms + - __mips__: on MIPS processor platforms + - __sparc_v9__: on Sparc 64-bit CPUs + - __sparcv9: 64-bit Solaris + - __MIPSEL__: mips processor, compiled for little endian + - __MIPSEB__: mips processor, compiled for big endian + - _R5900: MIPS/Sony/Toshiba R5900 (PS2) + - mc68000: 68K + - m68000: 68K + - m68k: 68K + - __palmos__: PalmOS + +Intel C/C++ Compiler: + - __ECC : compiler version, IA64 only + - __EDG__ + - __ELF__ + - __GXX_ABI_VERSION + - __i386 : IA-32 only + - __i386__ : IA-32 only + - i386 : IA-32 only + - __ia64 : IA-64 only + - __ia64__ : IA-64 only + - ia64 : IA-64 only + - __ICC : IA-32 only + - __INTEL_COMPILER : IA-32 or IA-64, newer versions only + +Apple's C/C++ Compiler for OS X: + - __APPLE_CC__ + - __APPLE__ + - __BIG_ENDIAN__ + - __APPLE__ + - __ppc__ + - __MACH__ + +DJGPP: + - __MSDOS__ + - __unix__ + - __unix + - __GNUC__ + - __GO32 + - DJGPP + - __i386, __i386, i386 + +Cray's C compiler: + - _ADDR64: if 64-bit pointers + - _UNICOS: + - __unix: + +SGI's CC compiler predefines the following (and more) with -ansi: + - __sgi + - __unix + - __host_mips + - _SYSTYPE_SVR4 + - __mips + - _MIPSEB + - anyone know if there is a predefined symbol for the compiler?! + +MinGW: + - as GnuC but also defines _WIN32, __WIN32, WIN32, _X86_, __i386, __i386__, and several others + - __MINGW32__ + +Cygwin: + - as Gnu C, but also + - __unix__ + - __CYGWIN32__ + +Microsoft Visual Studio predefines the following: + - _MSC_VER + - _WIN32: on Win32 + - _M_IX6 (on x86 systems) + - _M_X64: on x86-64 systems + - _M_ALPHA (on DEC AXP systems) + - _SH3: WinCE, Hitachi SH-3 + - _MIPS: WinCE, MIPS + - _ARM: WinCE, ARM + +Sun's C Compiler: + - sun and _sun + - unix and _unix + - sparc and _sparc (SPARC systems only) + - i386 and _i386 (x86 systems only) + - __SVR4 (Solaris only) + - __sparcv9: 64-bit solaris + - __SUNPRO_C + - _LP64: defined in 64-bit LP64 mode, but only if is included + +Borland C/C++ predefines the following: + - __BORLANDC__: + +DEC/Compaq C/C++ on Alpha: + - __alpha + - __arch64__ + - __unix__ (on Tru64 Unix) + - __osf__ + - __DECC + - __DECCXX (C++ compilation) + - __DECC_VER + - __DECCXX_VER + +IBM's AIX compiler: + - __64BIT__ if 64-bit mode + - _AIX + - __IBMC__: C compiler version + - __IBMCPP__: C++ compiler version + - _LONG_LONG: compiler allows long long + +Watcom: + - __WATCOMC__ + - __DOS__ : if targeting DOS + - __386__ : if 32-bit support + - __WIN32__ : if targetin 32-bit Windows + +HP-UX C/C++ Compiler: + - __hpux + - __unix + - __hppa (on PA-RISC) + - __LP64__: if compiled in 64-bit mode + +Metrowerks: + - __MWERKS__ + - __powerpc__ + - _powerc + - __MC68K__ + - macintosh when compiling for MacOS + - __INTEL__ for x86 targets + - __POWERPC__ + +*/ + +/* +** ---------------------------------------------------------------------------- +** Include optionally +** ---------------------------------------------------------------------------- +*/ +#ifdef POSH_USE_LIMITS_H +# include +#endif + +/* +** ---------------------------------------------------------------------------- +** Determine compilation environment +** ---------------------------------------------------------------------------- +*/ +#if defined __ECC || defined __ICC || defined __INTEL_COMPILER +# define POSH_COMPILER_STRING "Intel C/C++" +# define POSH_COMPILER_INTEL 1 +#endif + +#if ( defined __host_mips || defined __sgi ) && !defined __GNUC__ +# define POSH_COMPILER_STRING "MIPSpro C/C++" +# define POSH_COMPILER_MIPSPRO 1 +#endif + +#if defined __hpux && !defined __GNUC__ +# define POSH_COMPILER_STRING "HP-UX CC" +# define POSH_COMPILER_HPCC 1 +#endif + +#if defined __GNUC__ && !defined __clang__ +# define POSH_COMPILER_STRING "Gnu GCC" +# define POSH_COMPILER_GCC 1 +#endif + +#if defined __clang__ +# define POSH_COMPILER_STRING "Clang" +# define POSH_COMPILER_CLANG 1 +#endif + +#if defined __APPLE_CC__ + /* we don't define the compiler string here, let it be GNU */ +# define POSH_COMPILER_APPLECC 1 +#endif + +#if defined __IBMC__ || defined __IBMCPP__ +# define POSH_COMPILER_STRING "IBM C/C++" +# define POSH_COMPILER_IBM 1 +#endif + +#if defined _MSC_VER +# define POSH_COMPILER_STRING "Microsoft Visual C++" +# define POSH_COMPILER_MSVC 1 +#endif + +#if defined __SUNPRO_C +# define POSH_COMPILER_STRING "Sun Pro" +# define POSH_COMPILER_SUN 1 +#endif + +#if defined __BORLANDC__ +# define POSH_COMPILER_STRING "Borland C/C++" +# define POSH_COMPILER_BORLAND 1 +#endif + +#if defined __MWERKS__ +# define POSH_COMPILER_STRING "MetroWerks CodeWarrior" +# define POSH_COMPILER_METROWERKS 1 +#endif + +#if defined __DECC || defined __DECCXX +# define POSH_COMPILER_STRING "Compaq/DEC C/C++" +# define POSH_COMPILER_DEC 1 +#endif + +#if defined __WATCOMC__ +# define POSH_COMPILER_STRING "Watcom C/C++" +# define POSH_COMPILER_WATCOM 1 +#endif + +#if !defined POSH_COMPILER_STRING +# define POSH_COMPILER_STRING "Unknown compiler" +#endif + +/* +** ---------------------------------------------------------------------------- +** Determine target operating system +** ---------------------------------------------------------------------------- +*/ +#if defined linux || defined __linux__ +# define POSH_OS_LINUX 1 +# define POSH_OS_STRING "Linux" +#endif + +#if defined __FreeBSD__ +# define POSH_OS_FREEBSD 1 +# define POSH_OS_STRING "FreeBSD" +#endif + +#if defined __CYGWIN32__ +# define POSH_OS_CYGWIN32 1 +# define POSH_OS_STRING "Cygwin" +#endif + +#if defined GEKKO +# define POSH_OS_GAMECUBE +# define __powerpc__ +# define POSH_OS_STRING "GameCube" +#endif + +#if defined __MINGW32__ +# define POSH_OS_MINGW 1 +# define POSH_OS_STRING "MinGW" +#endif + +#if defined GO32 && defined DJGPP && defined __MSDOS__ +# define POSH_OS_GO32 1 +# define POSH_OS_STRING "GO32/MS-DOS" +#endif + +/* NOTE: make sure you use /bt=DOS if compiling for 32-bit DOS, + otherwise Watcom assumes host=target */ +#if defined __WATCOMC__ && defined __386__ && defined __DOS__ +# define POSH_OS_DOS32 1 +# define POSH_OS_STRING "DOS/32-bit" +#endif + +#if defined _UNICOS +# define POSH_OS_UNICOS 1 +# define POSH_OS_STRING "UNICOS" +#endif + +//ACS if we're in xcode, look at the target conditionals to figure out if this is ios or osx +#if defined __APPLE__ +# include "TargetConditionals.h" +#endif +#if TARGET_OS_IPHONE +# define POSH_OS_IOS 1 +# define POSH_OS_STRING "iOS" +#else +# if ( defined __MWERKS__ && defined __powerc && !defined macintosh ) || defined __APPLE_CC__ || defined macosx +# define POSH_OS_OSX 1 +# define POSH_OS_STRING "MacOS X" +# endif +#endif + +#if defined __sun__ || defined sun || defined __sun || defined __solaris__ +# if defined __SVR4 || defined __svr4__ || defined __solaris__ +# define POSH_OS_STRING "Solaris" +# define POSH_OS_SOLARIS 1 +# endif +# if !defined POSH_OS_STRING +# define POSH_OS_STRING "SunOS" +# define POSH_OS_SUNOS 1 +# endif +#endif + +#if defined __sgi__ || defined sgi || defined __sgi +# define POSH_OS_IRIX 1 +# define POSH_OS_STRING "Irix" +#endif + +#if defined __hpux__ || defined __hpux +# define POSH_OS_HPUX 1 +# define POSH_OS_STRING "HP-UX" +#endif + +#if defined _AIX +# define POSH_OS_AIX 1 +# define POSH_OS_STRING "AIX" +#endif + +#if ( defined __alpha && defined __osf__ ) +# define POSH_OS_TRU64 1 +# define POSH_OS_STRING "Tru64" +#endif + +#if defined __BEOS__ || defined __beos__ +# define POSH_OS_BEOS 1 +# define POSH_OS_STRING "BeOS" +#endif + +#if defined amiga || defined amigados || defined AMIGA || defined _AMIGA +# define POSH_OS_AMIGA 1 +# define POSH_OS_STRING "Amiga" +#endif + +#if defined __unix__ +# define POSH_OS_UNIX 1 +# if !defined POSH_OS_STRING +# define POSH_OS_STRING "Unix-like(generic)" +# endif +#endif + +#if defined _WIN32_WCE +# define POSH_OS_WINCE 1 +# define POSH_OS_STRING "Windows CE" +#endif + +#if defined _XBOX || defined _XBOX_VER +# define POSH_OS_XBOX 1 +# define POSH_OS_STRING "XBOX" +#endif + +#if defined __ORBIS__ +# define POSH_OS_ORBIS +#endif + +#if defined _WIN32 || defined WIN32 || defined __NT__ || defined __WIN32__ +# if !defined POSH_OS_XBOX +# define POSH_OS_WIN32 1 +# if defined _WIN64 +# define POSH_OS_WIN64 1 +# define POSH_OS_STRING "Win64" +# else +# if !defined POSH_OS_STRING +# define POSH_OS_STRING "Win32" +# endif +# endif +# endif +#endif + +#if defined __palmos__ +# define POSH_OS_PALM 1 +# define POSH_OS_STRING "PalmOS" +#endif + +#if defined THINK_C || defined macintosh +# define POSH_OS_MACOS 1 +# define POSH_OS_STRING "MacOS" +#endif + +/* +** ----------------------------------------------------------------------------- +** Determine target CPU +** ----------------------------------------------------------------------------- +*/ + +#if defined GEKKO +# define POSH_CPU_PPC750 1 +# define POSH_CPU_STRING "IBM PowerPC 750 (NGC)" +#endif + +#if defined mc68000 || defined m68k || defined __MC68K__ || defined m68000 +# define POSH_CPU_68K 1 +# define POSH_CPU_STRING "MC68000" +#endif + +#if defined __PPC__ || defined __POWERPC__ || defined powerpc || defined _POWER || defined __ppc__ || defined __powerpc__ || defined _M_PPC +# define POSH_CPU_PPC 1 +# if !defined POSH_CPU_STRING +# if defined __powerpc64__ +# define POSH_CPU_STRING "PowerPC64" +# else +# define POSH_CPU_STRING "PowerPC" +# endif +# endif +#endif + +#if defined _CRAYT3E || defined _CRAYMPP +# define POSH_CPU_CRAYT3E 1 /* target processor is a DEC Alpha 21164 used in a Cray T3E*/ +# define POSH_CPU_STRING "Cray T3E (Alpha 21164)" +#endif + +#if defined CRAY || defined _CRAY && !defined _CRAYT3E +# error Non-AXP Cray systems not supported +#endif + +#if defined _SH3 +# define POSH_CPU_SH3 1 +# define POSH_CPU_STRING "Hitachi SH-3" +#endif + +#if defined __sh4__ || defined __SH4__ +# define POSH_CPU_SH3 1 +# define POSH_CPU_SH4 1 +# define POSH_CPU_STRING "Hitachi SH-4" +#endif + +#if defined __sparc__ || defined __sparc +# if defined __arch64__ || defined __sparcv9 || defined __sparc_v9__ +# define POSH_CPU_SPARC64 1 +# define POSH_CPU_STRING "Sparc/64" +# else +# define POSH_CPU_STRING "Sparc/32" +# endif +# define POSH_CPU_SPARC 1 +#endif + +#if defined ARM || defined __arm__ || defined _ARM +# define POSH_CPU_STRONGARM 1 +# define POSH_CPU_STRING "ARM" +#endif + +#if defined AARCH64 || defined __aarch64__ || defined _AARCH64 +# define POSH_CPU_STRONGARM 1 +# define POSH_CPU_STRING "AARCH64" +#endif + +#if defined mips || defined __mips__ || defined __MIPS__ || defined _MIPS +# define POSH_CPU_MIPS 1 +# if defined _R5900 +# define POSH_CPU_STRING "MIPS R5900 (PS2)" +# else +# define POSH_CPU_STRING "MIPS" +# endif +#endif + +#if defined __ia64 || defined _M_IA64 || defined __ia64__ +# define POSH_CPU_IA64 1 +# define POSH_CPU_STRING "IA64" +#endif + +#if defined __X86__ || defined __i386__ || defined i386 || defined _M_IX86 || defined __386__ || defined __x86_64__ || defined _M_X64 +# define POSH_CPU_X86 1 +# if defined __x86_64__ || defined _M_X64 +# define POSH_CPU_X86_64 1 +# endif +# if defined POSH_CPU_X86_64 +# define POSH_CPU_STRING "AMD x86-64" +# else +# define POSH_CPU_STRING "Intel 386+" +# endif +#endif + +#if defined __alpha || defined alpha || defined _M_ALPHA || defined __alpha__ +# define POSH_CPU_AXP 1 +# define POSH_CPU_STRING "AXP" +#endif + +#if defined __hppa || defined hppa +# define POSH_CPU_HPPA 1 +# define POSH_CPU_STRING "PA-RISC" +#endif + +#if !defined POSH_CPU_STRING +# error POSH cannot determine target CPU +# define POSH_CPU_STRING "Unknown" /* this is here for Doxygen's benefit */ +#endif + +/* +** ----------------------------------------------------------------------------- +** Attempt to autodetect building for embedded on Sony PS2 +** ----------------------------------------------------------------------------- +*/ +#if !defined POSH_OS_STRING +# if !defined FORCE_DOXYGEN +# define POSH_OS_EMBEDDED 1 +# endif +# if defined _R5900 +# define POSH_OS_STRING "Sony PS2(embedded)" +# else +# define POSH_OS_STRING "Embedded/Unknown" +# endif +#endif + +/* +** --------------------------------------------------------------------------- +** Handle cdecl, stdcall, fastcall, etc. +** --------------------------------------------------------------------------- +*/ +#if defined POSH_CPU_X86 && !defined POSH_CPU_X86_64 +# if defined __GNUC__ +# define POSH_CDECL __attribute__((cdecl)) +# define POSH_STDCALL __attribute__((stdcall)) +# define POSH_FASTCALL __attribute__((fastcall)) +# elif ( defined _MSC_VER || defined __WATCOMC__ || defined __BORLANDC__ || defined __MWERKS__ ) +# define POSH_CDECL __cdecl +# define POSH_STDCALL __stdcall +# define POSH_FASTCALL __fastcall +# endif +#else +# define POSH_CDECL +# define POSH_STDCALL +# define POSH_FASTCALL +#endif + +/* +** --------------------------------------------------------------------------- +** Define POSH_IMPORTEXPORT signature based on POSH_DLL and POSH_BUILDING_LIB +** --------------------------------------------------------------------------- +*/ + +/* +** We undefine this so that multiple inclusions will work +*/ +#if defined POSH_IMPORTEXPORT +# undef POSH_IMPORTEXPORT +#endif + +#if defined POSH_DLL +# if defined POSH_OS_WIN32 +# if defined _MSC_VER +# if ( _MSC_VER >= 800 ) +# if defined POSH_BUILDING_LIB +# define POSH_IMPORTEXPORT __declspec( dllexport ) +# else +# define POSH_IMPORTEXPORT __declspec( dllimport ) +# endif +# else +# if defined POSH_BUILDING_LIB +# define POSH_IMPORTEXPORT __export +# else +# define POSH_IMPORTEXPORT +# endif +# endif +# endif /* defined _MSC_VER */ +# if defined __BORLANDC__ +# if ( __BORLANDC__ >= 0x500 ) +# if defined POSH_BUILDING_LIB +# define POSH_IMPORTEXPORT __declspec( dllexport ) +# else +# define POSH_IMPORTEXPORT __declspec( dllimport ) +# endif +# else +# if defined POSH_BUILDING_LIB +# define POSH_IMPORTEXPORT __export +# else +# define POSH_IMPORTEXPORT +# endif +# endif +# endif /* defined __BORLANDC__ */ + /* for all other compilers, we're just making a blanket assumption */ +# if defined __GNUC__ || defined __WATCOMC__ || defined __MWERKS__ +# if defined POSH_BUILDING_LIB +# define POSH_IMPORTEXPORT __declspec( dllexport ) +# else +# define POSH_IMPORTEXPORT __declspec( dllimport ) +# endif +# endif /* all other compilers */ +# if !defined POSH_IMPORTEXPORT +# error Building DLLs not supported on this compiler (poshlib@poshlib.org if you know how) +# endif +# endif /* defined POSH_OS_WIN32 */ +#endif + +/* On pretty much everything else, we can thankfully just ignore this */ +#if !defined POSH_IMPORTEXPORT +# define POSH_IMPORTEXPORT +#endif + +#if defined FORCE_DOXYGEN +# define POSH_DLL +# define POSH_BUILDING_LIB +# undef POSH_DLL +# undef POSH_BUILDING_LIB +#endif + +/* +** ---------------------------------------------------------------------------- +** (Re)define POSH_PUBLIC_API export signature +** ---------------------------------------------------------------------------- +*/ +#ifdef POSH_PUBLIC_API +# undef POSH_PUBLIC_API +#endif + +#if ( ( defined _MSC_VER ) && ( _MSC_VER < 800 ) ) || ( defined __BORLANDC__ && ( __BORLANDC__ < 0x500 ) ) +# define POSH_PUBLIC_API(rtype) extern rtype POSH_IMPORTEXPORT +#else +# define POSH_PUBLIC_API(rtype) extern POSH_IMPORTEXPORT rtype +#endif + +/* +** ---------------------------------------------------------------------------- +** Try to infer endianess. Basically we just go through the CPUs we know are +** little endian, and assume anything that isn't one of those is big endian. +** As a sanity check, we also do this with operating systems we know are +** little endian, such as Windows. Some processors are bi-endian, such as +** the MIPS series, so we have to be careful about those. +** ---------------------------------------------------------------------------- +*/ +#if defined POSH_CPU_X86 || defined POSH_CPU_AXP || defined POSH_CPU_STRONGARM || defined POSH_OS_WIN32 || defined POSH_OS_WINCE || defined __MIPSEL__ +# define POSH_ENDIAN_STRING "little" +# define POSH_LITTLE_ENDIAN 1 +#else +# define POSH_ENDIAN_STRING "big" +# define POSH_BIG_ENDIAN 1 +#endif + +#if defined FORCE_DOXYGEN +# define POSH_LITTLE_ENDIAN +#endif + +/* +** ---------------------------------------------------------------------------- +** Cross-platform compile time assertion macro +** ---------------------------------------------------------------------------- +*/ +#define POSH_COMPILE_TIME_ASSERT(name, x) typedef int _POSH_dummy_ ## name[(x) ? 1 : -1 ] + +/* +** ---------------------------------------------------------------------------- +** 64-bit Integer +** +** We don't require 64-bit support, nor do we emulate its functionality, we +** simply export it if it's available. Since we can't count on +** for 64-bit support, we ignore the POSH_USE_LIMITS_H directive. +** ---------------------------------------------------------------------------- +*/ +#if defined ( __LP64__ ) || defined ( __powerpc64__ ) || defined POSH_CPU_SPARC64 +# define POSH_64BIT_INTEGER 1 +typedef long posh_i64_t; +typedef unsigned long posh_u64_t; +# define POSH_I64( x ) ((posh_i64_t)x) +# define POSH_U64( x ) ((posh_u64_t)x) +# define POSH_I64_PRINTF_PREFIX "l" +#elif defined _MSC_VER || defined __BORLANDC__ || defined __WATCOMC__ || ( defined __alpha && defined __DECC ) +# define POSH_64BIT_INTEGER 1 +typedef __int64 posh_i64_t; +typedef unsigned __int64 posh_u64_t; +# define POSH_I64( x ) ((posh_i64_t)x) +# define POSH_U64( x ) ((posh_u64_t)x) +# define POSH_I64_PRINTF_PREFIX "I64" +#elif defined __GNUC__ || defined __MWERKS__ || defined __SUNPRO_C || defined __SUNPRO_CC || defined __APPLE_CC__ || defined POSH_OS_IRIX || defined _LONG_LONG || defined _CRAYC +# define POSH_64BIT_INTEGER 1 +typedef long long posh_i64_t; +typedef unsigned long long posh_u64_t; +# define POSH_U64( x ) ((posh_u64_t)(x##LL)) +# define POSH_I64( x ) ((posh_i64_t)(x##LL)) +# define POSH_I64_PRINTF_PREFIX "ll" +#endif + +/* hack */ +/*#ifdef __MINGW32__ +#undef POSH_I64 +#undef POSH_U64 +#undef POSH_I64_PRINTF_PREFIX +#define POSH_I64( x ) ((posh_i64_t)x) +#define POSH_U64( x ) ((posh_u64_t)x) +#define POSH_I64_PRINTF_PREFIX "I64" +#endif*/ + +#ifdef FORCE_DOXYGEN +typedef long long posh_i64_t; +typedef unsigned long posh_u64_t; +# define POSH_64BIT_INTEGER +# define POSH_I64_PRINTF_PREFIX +# define POSH_I64(x) +# define POSH_U64(x) +#endif + +/** Minimum value for a 64-bit signed integer */ +#define POSH_I64_MIN POSH_I64(0x8000000000000000) +/** Maximum value for a 64-bit signed integer */ +#define POSH_I64_MAX POSH_I64(0x7FFFFFFFFFFFFFFF) +/** Minimum value for a 64-bit unsigned integer */ +#define POSH_U64_MIN POSH_U64(0) +/** Maximum value for a 64-bit unsigned integer */ +#define POSH_U64_MAX POSH_U64(0xFFFFFFFFFFFFFFFF) + +/* ---------------------------------------------------------------------------- +** Basic Sized Types +** +** These types are expected to be EXACTLY sized so you can use them for +** serialization. +** ---------------------------------------------------------------------------- +*/ +#define POSH_FALSE 0 +#define POSH_TRUE 1 + +typedef int posh_bool_t; +typedef unsigned char posh_byte_t; + +/* NOTE: These assume that CHAR_BIT is 8!! */ +typedef unsigned char posh_u8_t; +typedef signed char posh_i8_t; + +#if defined POSH_USE_LIMITS_H +# if CHAR_BITS > 8 +# error This machine uses 9-bit characters. This is a warning, you can comment this out now. +# endif /* CHAR_BITS > 8 */ + +/* 16-bit */ +# if ( USHRT_MAX == 65535 ) + typedef unsigned short posh_u16_t; + typedef short posh_i16_t; +# else + /* Yes, in theory there could still be a 16-bit character type and shorts are + 32-bits in size...if you find such an architecture, let me know =P */ +# error No 16-bit type found +# endif + +/* 32-bit */ +# if ( INT_MAX == 2147483647 ) + typedef unsigned posh_u32_t; + typedef int posh_i32_t; +# elif ( LONG_MAX == 2147483647 ) + typedef unsigned long posh_u32_t; + typedef long posh_i32_t; +# else + error No 32-bit type found +# endif + +#else /* POSH_USE_LIMITS_H */ + + typedef unsigned short posh_u16_t; + typedef short posh_i16_t; + +# if !defined POSH_OS_PALM + typedef unsigned posh_u32_t; + typedef int posh_i32_t; +# else + typedef unsigned long posh_u32_t; + typedef long posh_i32_t; +# endif +#endif + +/** Minimum value for a byte */ +#define POSH_BYTE_MIN 0 +/** Maximum value for an 8-bit unsigned value */ +#define POSH_BYTE_MAX 255 +/** Minimum value for a byte */ +#define POSH_I16_MIN ( ( posh_i16_t ) 0x8000 ) +/** Maximum value for a 16-bit signed value */ +#define POSH_I16_MAX ( ( posh_i16_t ) 0x7FFF ) +/** Minimum value for a 16-bit unsigned value */ +#define POSH_U16_MIN 0 +/** Maximum value for a 16-bit unsigned value */ +#define POSH_U16_MAX ( ( posh_u16_t ) 0xFFFF ) +/** Minimum value for a 32-bit signed value */ +#define POSH_I32_MIN ( ( posh_i32_t ) 0x80000000 ) +/** Maximum value for a 32-bit signed value */ +#define POSH_I32_MAX ( ( posh_i32_t ) 0x7FFFFFFF ) +/** Minimum value for a 32-bit unsigned value */ +#define POSH_U32_MIN 0 +/** Maximum value for a 32-bit unsigned value */ +#define POSH_U32_MAX ( ( posh_u32_t ) 0xFFFFFFFF ) + +/* +** ---------------------------------------------------------------------------- +** Sanity checks on expected sizes +** ---------------------------------------------------------------------------- +*/ +#if !defined FORCE_DOXYGEN + +POSH_COMPILE_TIME_ASSERT(posh_byte_t, sizeof(posh_byte_t) == 1); +POSH_COMPILE_TIME_ASSERT(posh_u8_t, sizeof(posh_u8_t) == 1); +POSH_COMPILE_TIME_ASSERT(posh_i8_t, sizeof(posh_i8_t) == 1); +POSH_COMPILE_TIME_ASSERT(posh_u16_t, sizeof(posh_u16_t) == 2); +POSH_COMPILE_TIME_ASSERT(posh_i16_t, sizeof(posh_i16_t) == 2); +POSH_COMPILE_TIME_ASSERT(posh_u32_t, sizeof(posh_u32_t) == 4); +POSH_COMPILE_TIME_ASSERT(posh_i32_t, sizeof(posh_i32_t) == 4); + +#if !defined POSH_NO_FLOAT + POSH_COMPILE_TIME_ASSERT(posh_testfloat_t, sizeof(float)==4 ); + POSH_COMPILE_TIME_ASSERT(posh_testdouble_t, sizeof(double)==8); +#endif + +#if defined POSH_64BIT_INTEGER + POSH_COMPILE_TIME_ASSERT(posh_u64_t, sizeof(posh_u64_t) == 8); + POSH_COMPILE_TIME_ASSERT(posh_i64_t, sizeof(posh_i64_t) == 8); +#endif + +#endif + +/* +** ---------------------------------------------------------------------------- +** 64-bit pointer support +** ---------------------------------------------------------------------------- +*/ +#if defined POSH_CPU_AXP && ( defined POSH_OS_TRU64 || defined POSH_OS_LINUX ) +# define POSH_64BIT_POINTER 1 +#endif + +#if defined POSH_CPU_X86_64 && defined POSH_OS_LINUX +# define POSH_64BIT_POINTER 1 +#endif + +#if defined POSH_CPU_SPARC64 || defined POSH_OS_WIN64 || defined __64BIT__ || defined __LP64 || defined _LP64 || defined __LP64__ || defined _ADDR64 || defined _CRAYC +# define POSH_64BIT_POINTER 1 +#endif + +#if defined POSH_64BIT_POINTER + POSH_COMPILE_TIME_ASSERT( posh_64bit_pointer, sizeof( void * ) == 8 ); +#elif !defined FORCE_DOXYGEN +/* if this assertion is hit then you're on a system that either has 64-bit + addressing and we didn't catch it, or you're on a system with 16-bit + pointers. In the latter case, POSH doesn't actually care, we're just + triggering this assertion to make sure you're aware of the situation, + so feel free to delete it. + + If this assertion is triggered on a known 32 or 64-bit platform, + please let us know (poshlib@poshlib.org) */ + POSH_COMPILE_TIME_ASSERT( posh_32bit_pointer, sizeof( void * ) == 4 ); +#endif + +#if defined FORCE_DOXYGEN +# define POSH_64BIT_POINTER +#endif + +/* +** ---------------------------------------------------------------------------- +** POSH Utility Functions +** +** These are optional POSH utility functions that are not required if you don't +** need anything except static checking of your host and target environment. +** +** These functions are NOT wrapped with POSH_PUBLIC_API because I didn't want +** to enforce their export if your own library is only using them internally. +** ---------------------------------------------------------------------------- +*/ +#ifdef __cplusplus +extern "C" { +#endif + +const char *POSH_GetArchString( void ); + +#if !defined POSH_NO_FLOAT + +posh_u32_t POSH_LittleFloatBits( float f ); +posh_u32_t POSH_BigFloatBits( float f ); +float POSH_FloatFromLittleBits( posh_u32_t bits ); +float POSH_FloatFromBigBits( posh_u32_t bits ); + +void POSH_DoubleBits( double d, posh_byte_t dst[ 8 ] ); +double POSH_DoubleFromBits( const posh_byte_t src[ 8 ] ); + +/* unimplemented +float *POSH_WriteFloatToLittle( void *dst, float f ); +float *POSH_WriteFloatToBig( void *dst, float f ); +float POSH_ReadFloatFromLittle( const void *src ); +float POSH_ReadFloatFromBig( const void *src ); + +double *POSH_WriteDoubleToLittle( void *dst, double d ); +double *POSH_WriteDoubleToBig( void *dst, double d ); +double POSH_ReadDoubleFromLittle( const void *src ); +double POSH_ReadDoubleFromBig( const void *src ); +*/ +#endif /* !defined POSH_NO_FLOAT */ + +#if defined FORCE_DOXYGEN +# define POSH_NO_FLOAT +# undef POSH_NO_FLOAT +#endif + +extern posh_u16_t POSH_SwapU16( posh_u16_t u ); +extern posh_i16_t POSH_SwapI16( posh_i16_t u ); +extern posh_u32_t POSH_SwapU32( posh_u32_t u ); +extern posh_i32_t POSH_SwapI32( posh_i32_t u ); + +#if defined POSH_64BIT_INTEGER + +extern posh_u64_t POSH_SwapU64( posh_u64_t u ); +extern posh_i64_t POSH_SwapI64( posh_i64_t u ); + +#endif /*POSH_64BIT_INTEGER */ + +extern posh_u16_t *POSH_WriteU16ToLittle( void *dst, posh_u16_t value ); +extern posh_i16_t *POSH_WriteI16ToLittle( void *dst, posh_i16_t value ); +extern posh_u32_t *POSH_WriteU32ToLittle( void *dst, posh_u32_t value ); +extern posh_i32_t *POSH_WriteI32ToLittle( void *dst, posh_i32_t value ); + +extern posh_u16_t *POSH_WriteU16ToBig( void *dst, posh_u16_t value ); +extern posh_i16_t *POSH_WriteI16ToBig( void *dst, posh_i16_t value ); +extern posh_u32_t *POSH_WriteU32ToBig( void *dst, posh_u32_t value ); +extern posh_i32_t *POSH_WriteI32ToBig( void *dst, posh_i32_t value ); + +extern posh_u16_t POSH_ReadU16FromLittle( const void *src ); +extern posh_i16_t POSH_ReadI16FromLittle( const void *src ); +extern posh_u32_t POSH_ReadU32FromLittle( const void *src ); +extern posh_i32_t POSH_ReadI32FromLittle( const void *src ); + +extern posh_u16_t POSH_ReadU16FromBig( const void *src ); +extern posh_i16_t POSH_ReadI16FromBig( const void *src ); +extern posh_u32_t POSH_ReadU32FromBig( const void *src ); +extern posh_i32_t POSH_ReadI32FromBig( const void *src ); + +#if defined POSH_64BIT_INTEGER +extern posh_u64_t *POSH_WriteU64ToLittle( void *dst, posh_u64_t value ); +extern posh_i64_t *POSH_WriteI64ToLittle( void *dst, posh_i64_t value ); +extern posh_u64_t *POSH_WriteU64ToBig( void *dst, posh_u64_t value ); +extern posh_i64_t *POSH_WriteI64ToBig( void *dst, posh_i64_t value ); + +extern posh_u64_t POSH_ReadU64FromLittle( const void *src ); +extern posh_i64_t POSH_ReadI64FromLittle( const void *src ); +extern posh_u64_t POSH_ReadU64FromBig( const void *src ); +extern posh_i64_t POSH_ReadI64FromBig( const void *src ); +#endif /* POSH_64BIT_INTEGER */ + +#if defined POSH_LITTLE_ENDIAN + +# define POSH_LittleU16(x) (x) +# define POSH_LittleU32(x) (x) +# define POSH_LittleI16(x) (x) +# define POSH_LittleI32(x) (x) +# if defined POSH_64BIT_INTEGER +# define POSH_LittleU64(x) (x) +# define POSH_LittleI64(x) (x) +# endif /* defined POSH_64BIT_INTEGER */ + +# define POSH_BigU16(x) POSH_SwapU16(x) +# define POSH_BigU32(x) POSH_SwapU32(x) +# define POSH_BigI16(x) POSH_SwapI16(x) +# define POSH_BigI32(x) POSH_SwapI32(x) +# if defined POSH_64BIT_INTEGER +# define POSH_BigU64(x) POSH_SwapU64(x) +# define POSH_BigI64(x) POSH_SwapI64(x) +# endif /* defined POSH_64BIT_INTEGER */ + +#else + +# define POSH_BigU16(x) (x) +# define POSH_BigU32(x) (x) +# define POSH_BigI16(x) (x) +# define POSH_BigI32(x) (x) + +# if defined POSH_64BIT_INTEGER +# define POSH_BigU64(x) (x) +# define POSH_BigI64(x) (x) +# endif /* POSH_64BIT_INTEGER */ + +# define POSH_LittleU16(x) POSH_SwapU16(x) +# define POSH_LittleU32(x) POSH_SwapU32(x) +# define POSH_LittleI16(x) POSH_SwapI16(x) +# define POSH_LittleI32(x) POSH_SwapI32(x) + +# if defined POSH_64BIT_INTEGER +# define POSH_LittleU64(x) POSH_SwapU64(x) +# define POSH_LittleI64(x) POSH_SwapI64(x) +# endif /* POSH_64BIT_INTEGER */ + +#endif + +#ifdef __cplusplus +} +#endif + + diff --git a/thirdparty/thekla_atlas/extern/stb/stb_image.h b/thirdparty/thekla_atlas/extern/stb/stb_image.h new file mode 100755 index 00000000..540ad183 --- /dev/null +++ b/thirdparty/thekla_atlas/extern/stb/stb_image.h @@ -0,0 +1,4954 @@ +/* stbi-1.29 - public domain JPEG/PNG reader - http://nothings.org/stb_image.c + when you control the images you're loading + no warranty implied; use at your own risk + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline (no JPEG progressive) + PNG 8-bit only + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + + - decoded from memory or through stdio FILE (define STBI_NO_STDIO to remove code) + - supports installable dequantizing-IDCT, YCbCr-to-RGB conversion (define STBI_SIMD) + + Latest revisions: + 1.29 (2010-08-16) various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) cast-to-uint8 to fix warnings (Laurent Gomila) + allow trailing 0s at end of image data (Laurent Gomila) + 1.26 (2010-07-24) fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) refix trans_data warning (Won Chun) + 1.24 (2010-07-12) perf improvements reading from files + minor perf improvements for jpeg + deprecated type-specific functions in hope of feedback + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) removed image *writing* support to stb_image_write.h + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva + 1.21 fix use of 'uint8' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + + See end of file for full revision history. + + TODO: + stbi_info support for BMP,PSD,HDR,PIC + rewrite stbi_info and load_file variations to share file handling code + (current system allows individual functions to be called directly, + since each does all the work, but I doubt anyone uses this in practice) + + + ============================ Contributors ========================= + + Image formats Optimizations & bugfixes + Sean Barrett (jpeg, png, bmp) Fabian "ryg" Giesen + Nicolas Schulz (hdr, psd) + Jonathan Dummer (tga) Bug fixes & warning fixes + Jean-Marc Lienher (gif) Marc LeBlanc + Tom Seddon (pic) Christpher Lloyd + Thatcher Ulrich (psd) Dave Moore + Won Chun + the Horde3D community + Extensions, features Janez Zemva + Jetro Lauha (stbi_info) Jonathan Blow + James "moose2000" Brown (iPhone PNG) Laurent Gomila + Aruelien Pocheville + + If your name should be here but isn't, let Sean know. + +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// To get a header file for this, either cut and paste the header, +// or create stb_image.h, #define STBI_HEADER_FILE_ONLY, and +// then include stb_image.c from it. + +//// begin header file //////////////////////////////////////////////////// +// +// Limitations: +// - no jpeg progressive support +// - non-HDR formats support 8-bit samples only (jpeg, png) +// - no delayed line count (jpeg) -- IJG doesn't support either +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to easily see if it's opaque. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB; nominally they +// would silently load as BGR, except the existing code should have just +// failed on such iPhone PNGs. But you can disable this conversion by +// by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through. +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); + +#ifndef STBI_NO_STDIO +#include +#endif + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +// PRIMARY API - works on images of any type + +// load image by filename, open file, or memory buffer +extern stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_HDR + extern float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + + #ifndef STBI_NO_STDIO + extern float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + extern float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + #endif + + extern void stbi_hdr_to_ldr_gamma(float gamma); + extern void stbi_hdr_to_ldr_scale(float scale); + + extern void stbi_ldr_to_hdr_gamma(float gamma); + extern void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_HDR + +// get a VERY brief reason for failure +// NOT THREADSAFE +extern const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +extern void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +extern int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +extern int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); + +#ifndef STBI_NO_STDIO +extern int stbi_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +extern int stbi_is_hdr (char const *filename); +extern int stbi_is_hdr_from_file(FILE *f); +#endif + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +extern void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +extern void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + + +// ZLIB client - used by PNG, available for other purposes + +extern char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +extern char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +extern int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +extern char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +extern int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +// define new loaders +typedef struct +{ + int (*test_memory)(stbi_uc const *buffer, int len); + stbi_uc * (*load_from_memory)(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + #ifndef STBI_NO_STDIO + int (*test_file)(FILE *f); + stbi_uc * (*load_from_file)(FILE *f, int *x, int *y, int *comp, int req_comp); + #endif +} stbi_loader; + +// register a loader by filling out the above structure (you must define ALL functions) +// returns 1 if added or already added, 0 if not added (too many loaders) +// NOT THREADSAFE +extern int stbi_register_loader(stbi_loader *loader); + +// define faster low-level operations (typically SIMD support) +#ifdef STBI_SIMD +typedef void (*stbi_idct_8x8)(stbi_uc *out, int out_stride, short data[64], unsigned short *dequantize); +// compute an integer IDCT on "input" +// input[x] = data[x] * dequantize[x] +// write results to 'out': 64 samples, each run of 8 spaced by 'out_stride' +// CLAMP results to 0..255 +typedef void (*stbi_YCbCr_to_RGB_run)(stbi_uc *output, stbi_uc const *y, stbi_uc const *cb, stbi_uc const *cr, int count, int step); +// compute a conversion from YCbCr to RGB +// 'count' pixels +// write pixels to 'output'; each pixel is 'step' bytes (either 3 or 4; if 4, write '255' as 4th), order R,G,B +// y: Y input channel +// cb: Cb input channel; scale/biased to be 0..255 +// cr: Cr input channel; scale/biased to be 0..255 + +extern void stbi_install_idct(stbi_idct_8x8 func); +extern void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func); +#endif // STBI_SIMD + + + + +// TYPE-SPECIFIC ACCESS + +#ifdef STBI_TYPE_SPECIFIC_FUNCTIONS + +// is it a jpeg? +extern int stbi_jpeg_test_memory (stbi_uc const *buffer, int len); +extern stbi_uc *stbi_jpeg_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern int stbi_jpeg_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_jpeg_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern int stbi_jpeg_test_file (FILE *f); +extern stbi_uc *stbi_jpeg_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + +extern int stbi_jpeg_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_jpeg_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + +// is it a png? +extern int stbi_png_test_memory (stbi_uc const *buffer, int len); +extern stbi_uc *stbi_png_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_png_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_png_test_file (FILE *f); +extern stbi_uc *stbi_png_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + +// is it a bmp? +extern int stbi_bmp_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_bmp_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_bmp_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_bmp_test_file (FILE *f); +extern stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a tga? +extern int stbi_tga_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_tga_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_tga_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_tga_test_file (FILE *f); +extern stbi_uc *stbi_tga_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a psd? +extern int stbi_psd_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_psd_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_psd_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_psd_test_file (FILE *f); +extern stbi_uc *stbi_psd_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it an hdr? +extern int stbi_hdr_test_memory (stbi_uc const *buffer, int len); + +extern float * stbi_hdr_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern float * stbi_hdr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_hdr_test_file (FILE *f); +extern float * stbi_hdr_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a pic? +extern int stbi_pic_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_pic_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_pic_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_pic_test_file (FILE *f); +extern stbi_uc *stbi_pic_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a gif? +extern int stbi_gif_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_gif_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_gif_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern int stbi_gif_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern int stbi_gif_test_file (FILE *f); +extern stbi_uc *stbi_gif_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +extern int stbi_gif_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_gif_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + +#endif//STBI_TYPE_SPECIFIC_FUNCTIONS + + + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifndef STBI_HEADER_FILE_ONLY + +#ifndef STBI_NO_HDR +#include // ldexp +#include // strcmp +#endif + +#ifndef STBI_NO_STDIO +#include +#endif +#include +#include +#include +#include + +#ifndef _MSC_VER + #ifdef __cplusplus + #define __forceinline inline + #else + #define __forceinline + #endif +#endif + + +// implementation: +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef signed short int16; +typedef unsigned int uint32; +typedef signed int int32; +typedef unsigned int uint; + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(uint32)==4 ? 1 : -1]; + +#if defined(STBI_NO_STDIO) && !defined(STBI_NO_WRITE) +#define STBI_NO_WRITE +#endif + +#define STBI_NOTUSED(v) v=v + +#ifdef _MSC_VER +#define STBI_HAS_LRTOL +#endif + +#ifdef STBI_HAS_LRTOL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// Generic API that works on all image types +// + +// deprecated functions + +// is it a jpeg? +extern int stbi_jpeg_test_memory (stbi_uc const *buffer, int len); +extern stbi_uc *stbi_jpeg_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern int stbi_jpeg_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_jpeg_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern int stbi_jpeg_test_file (FILE *f); +extern stbi_uc *stbi_jpeg_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + +extern int stbi_jpeg_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_jpeg_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + +// is it a png? +extern int stbi_png_test_memory (stbi_uc const *buffer, int len); +extern stbi_uc *stbi_png_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern stbi_uc *stbi_png_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_png_test_file (FILE *f); +extern stbi_uc *stbi_png_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +extern int stbi_png_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + +// is it a bmp? +extern int stbi_bmp_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_bmp_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_bmp_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_bmp_test_file (FILE *f); +extern stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a tga? +extern int stbi_tga_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_tga_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_tga_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_tga_test_file (FILE *f); +extern stbi_uc *stbi_tga_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a psd? +extern int stbi_psd_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_psd_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_psd_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_psd_test_file (FILE *f); +extern stbi_uc *stbi_psd_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it an hdr? +extern int stbi_hdr_test_memory (stbi_uc const *buffer, int len); + +extern float * stbi_hdr_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern float * stbi_hdr_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_hdr_test_file (FILE *f); +extern float * stbi_hdr_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a pic? +extern int stbi_pic_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_pic_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_pic_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +#ifndef STBI_NO_STDIO +extern int stbi_pic_test_file (FILE *f); +extern stbi_uc *stbi_pic_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +#endif + +// is it a gif? +extern int stbi_gif_test_memory (stbi_uc const *buffer, int len); + +extern stbi_uc *stbi_gif_load (char const *filename, int *x, int *y, int *comp, int req_comp); +extern stbi_uc *stbi_gif_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); +extern int stbi_gif_info_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +extern int stbi_gif_test_file (FILE *f); +extern stbi_uc *stbi_gif_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +extern int stbi_gif_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_gif_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif + + +// this is not threadsafe +static const char *failure_reason; + +const char *stbi_failure_reason(void) +{ + return failure_reason; +} + +static int e(const char *str) +{ + failure_reason = str; + return 0; +} + +#ifdef STBI_NO_FAILURE_STRINGS + #define e(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define e(x,y) e(y) +#else + #define e(x,y) e(x) +#endif + +#define epf(x,y) ((float *) (e(x,y)?NULL:NULL)) +#define epuc(x,y) ((unsigned char *) (e(x,y)?NULL:NULL)) + +void stbi_image_free(void *retval_from_stbi_load) +{ + free(retval_from_stbi_load); +} + +#define MAX_LOADERS 32 +stbi_loader *loaders[MAX_LOADERS]; +static int max_loaders = 0; + +int stbi_register_loader(stbi_loader *loader) +{ + int i; + for (i=0; i < MAX_LOADERS; ++i) { + // already present? + if (loaders[i] == loader) + return 1; + // end of the list? + if (loaders[i] == NULL) { + loaders[i] = loader; + max_loaders = i+1; + return 1; + } + } + // no room for it + return 0; +} + +#ifndef STBI_NO_HDR +static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_STDIO +unsigned char *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = fopen(filename, "rb"); + unsigned char *result; + if (!f) return epuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +unsigned char *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + int i; + if (stbi_jpeg_test_file(f)) return stbi_jpeg_load_from_file(f,x,y,comp,req_comp); + if (stbi_png_test_file(f)) return stbi_png_load_from_file(f,x,y,comp,req_comp); + if (stbi_bmp_test_file(f)) return stbi_bmp_load_from_file(f,x,y,comp,req_comp); + if (stbi_gif_test_file(f)) return stbi_gif_load_from_file(f,x,y,comp,req_comp); + if (stbi_psd_test_file(f)) return stbi_psd_load_from_file(f,x,y,comp,req_comp); + if (stbi_pic_test_file(f)) return stbi_pic_load_from_file(f,x,y,comp,req_comp); + + #ifndef STBI_NO_HDR + if (stbi_hdr_test_file(f)) { + float *hdr = stbi_hdr_load_from_file(f, x,y,comp,req_comp); + return hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + for (i=0; i < max_loaders; ++i) + if (loaders[i]->test_file(f)) + return loaders[i]->load_from_file(f,x,y,comp,req_comp); + // test tga last because it's a crappy test! + if (stbi_tga_test_file(f)) + return stbi_tga_load_from_file(f,x,y,comp,req_comp); + return epuc("unknown image type", "Image not of any known type, or corrupt"); +} +#endif + +unsigned char *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + int i; + if (stbi_jpeg_test_memory(buffer,len)) return stbi_jpeg_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_png_test_memory(buffer,len)) return stbi_png_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_bmp_test_memory(buffer,len)) return stbi_bmp_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_gif_test_memory(buffer,len)) return stbi_gif_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_psd_test_memory(buffer,len)) return stbi_psd_load_from_memory(buffer,len,x,y,comp,req_comp); + if (stbi_pic_test_memory(buffer,len)) return stbi_pic_load_from_memory(buffer,len,x,y,comp,req_comp); + + #ifndef STBI_NO_HDR + if (stbi_hdr_test_memory(buffer, len)) { + float *hdr = stbi_hdr_load_from_memory(buffer, len,x,y,comp,req_comp); + return hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + for (i=0; i < max_loaders; ++i) + if (loaders[i]->test_memory(buffer,len)) + return loaders[i]->load_from_memory(buffer,len,x,y,comp,req_comp); + // test tga last because it's a crappy test! + if (stbi_tga_test_memory(buffer,len)) + return stbi_tga_load_from_memory(buffer,len,x,y,comp,req_comp); + return epuc("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_HDR + +#ifndef STBI_NO_STDIO +float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = fopen(filename, "rb"); + float *result; + if (!f) return epf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi_hdr_test_file(f)) + return stbi_hdr_load_from_file(f,x,y,comp,req_comp); + #endif + data = stbi_load_from_file(f, x, y, comp, req_comp); + if (data) + return ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return epf("unknown image type", "Image not of any known type, or corrupt"); +} +#endif + +float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + #ifndef STBI_NO_HDR + if (stbi_hdr_test_memory(buffer, len)) + return stbi_hdr_load_from_memory(buffer, len,x,y,comp,req_comp); + #endif + data = stbi_load_from_memory(buffer, len, x, y, comp, req_comp); + if (data) + return ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return epf("unknown image type", "Image not of any known type, or corrupt"); +} +#endif + +// these is-hdr-or-not is defined independent of whether STBI_NO_HDR is +// defined, for API simplicity; if STBI_NO_HDR is defined, it always +// reports false! + +int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + return stbi_hdr_test_memory(buffer, len); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +extern int stbi_is_hdr (char const *filename) +{ + FILE *f = fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +extern int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + return stbi_hdr_test_file(f); + #else + return 0; + #endif +} + +#endif + +#ifndef STBI_NO_HDR +static float h2l_gamma_i=1.0f/2.2f, h2l_scale_i=1.0f; +static float l2h_gamma=2.2f, l2h_scale=1.0f; + +void stbi_hdr_to_ldr_gamma(float gamma) { h2l_gamma_i = 1/gamma; } +void stbi_hdr_to_ldr_scale(float scale) { h2l_scale_i = 1/scale; } + +void stbi_ldr_to_hdr_gamma(float gamma) { l2h_gamma = gamma; } +void stbi_ldr_to_hdr_scale(float scale) { l2h_scale = scale; } +#endif + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + SCAN_load=0, + SCAN_type, + SCAN_header +}; + +typedef struct +{ + uint32 img_x, img_y; + int img_n, img_out_n; + + #ifndef STBI_NO_STDIO + FILE *img_file; + int buflen; + uint8 buffer_start[128]; + int from_file; + #endif + uint8 *img_buffer, *img_buffer_end; +} stbi; + +#ifndef STBI_NO_STDIO +static void start_file(stbi *s, FILE *f) +{ + s->img_file = f; + s->buflen = sizeof(s->buffer_start); + s->img_buffer_end = s->buffer_start + s->buflen; + s->img_buffer = s->img_buffer_end; + s->from_file = 1; +} +#endif + +static void start_mem(stbi *s, uint8 const *buffer, int len) +{ +#ifndef STBI_NO_STDIO + s->img_file = NULL; + s->from_file = 0; +#endif + s->img_buffer = (uint8 *) buffer; + s->img_buffer_end = (uint8 *) buffer+len; +} + +#ifndef STBI_NO_STDIO +static void refill_buffer(stbi *s) +{ + int n = fread(s->buffer_start, 1, s->buflen, s->img_file); + if (n == 0) { + s->from_file = 0; + s->img_buffer = s->img_buffer_end-1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} +#endif + +__forceinline static int get8(stbi *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; +#ifndef STBI_NO_STDIO + if (s->from_file) { + refill_buffer(s); + return *s->img_buffer++; + } +#endif + return 0; +} + +__forceinline static int at_eof(stbi *s) +{ +#ifndef STBI_NO_STDIO + if (s->img_file) { + if (!feof(s->img_file)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->from_file == 0) return 1; + } +#endif + return s->img_buffer >= s->img_buffer_end; +} + +__forceinline static uint8 get8u(stbi *s) +{ + return (uint8) get8(s); +} + +static void skip(stbi *s, int n) +{ +#ifndef STBI_NO_STDIO + if (s->img_file) { + int blen = s->img_buffer_end - s->img_buffer; + if (blen < n) { + s->img_buffer = s->img_buffer_end; + fseek(s->img_file, n - blen, SEEK_CUR); + return; + } + } +#endif + s->img_buffer += n; +} + +static int getn(stbi *s, stbi_uc *buffer, int n) +{ +#ifndef STBI_NO_STDIO + if (s->img_file) { + int blen = s->img_buffer_end - s->img_buffer; + if (blen < n) { + int res; + memcpy(buffer, s->img_buffer, blen); + res = ((int) fread(buffer + blen, 1, n - blen, s->img_file) == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } +#endif + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int get16(stbi *s) +{ + int z = get8(s); + return (z << 8) + get8(s); +} + +static uint32 get32(stbi *s) +{ + uint32 z = get16(s); + return (z << 16) + get16(s); +} + +static int get16le(stbi *s) +{ + int z = get8(s); + return z + (get8(s) << 8); +} + +static uint32 get32le(stbi *s) +{ + uint32 z = get16le(s); + return z + (get16le(s) << 16); +} + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static uint8 compute_y(int r, int g, int b) +{ + return (uint8) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *convert_format(unsigned char *data, int img_n, int req_comp, uint x, uint y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + assert(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) malloc(req_comp * x * y); + if (good == NULL) { + free(data); + return epuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: assert(0); + } + #undef CASE + } + + free(data); + return good; +} + +#ifndef STBI_NO_HDR +static float *ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output = (float *) malloc(x * y * comp * sizeof(float)); + if (output == NULL) { free(data); return epf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) pow(data[i*comp+k]/255.0f, l2h_gamma) * l2h_scale; + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + free(data); + return output; +} + +#define float2int(x) ((int) (x)) +static stbi_uc *hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output = (stbi_uc *) malloc(x * y * comp); + if (output == NULL) { free(data); return epuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*h2l_scale_i, h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (uint8) float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (uint8) float2int(z); + } + } + free(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder (not actually fully baseline implementation) +// +// simple implementation +// - channel subsampling of at most 2 in each dimension +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - uses a lot of intermediate memory, could cache poorly +// - load http://nothings.org/remote/anemones.jpg 3 times on 2.8Ghz P4 +// stb_jpeg: 1.34 seconds (MSVC6, default release build) +// stb_jpeg: 1.06 seconds (MSVC6, processor = Pentium Pro) +// IJL11.dll: 1.08 seconds (compiled by intel) +// IJG 1998: 0.98 seconds (MSVC6, makefile provided by IJG) +// IJG 1998: 0.95 seconds (MSVC6, makefile + proc=PPro) + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + uint8 fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + uint16 code[256]; + uint8 values[256]; + uint8 size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} huffman; + +typedef struct +{ + #ifdef STBI_SIMD + unsigned short dequant2[4][64]; + #endif + stbi s; + huffman huff_dc[4]; + huffman huff_ac[4]; + uint8 dequant[4][64]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + uint8 *data; + void *raw_data; + uint8 *linebuf; + } img_comp[4]; + + uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int scan_n, order[4]; + int restart_interval, todo; +} jpeg; + +static int build_huffman(huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (uint8) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (uint16) (code++); + if (code-1 >= (1 << j)) return e("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (uint8) i; + } + } + } + return 1; +} + +static void grow_buffer_unsafe(jpeg *j) +{ + do { + int b = j->nomore ? 0 : get8(&j->s); + if (b == 0xff) { + int c = get8(&j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static uint32 bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +__forceinline static int decode(jpeg *j, huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & bmask[k]) + h->delta[k]; + assert((((j->code_buffer) >> (32 - h->size[c])) & bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// combined JPEG 'receive' and JPEG 'extend', since baseline +// always extends everything it receives. +__forceinline static int extend_receive(jpeg *j, int n) +{ + unsigned int m = 1 << (n-1); + unsigned int k; + if (j->code_bits < n) grow_buffer_unsafe(j); + + #if 1 + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~bmask[n]; + k &= bmask[n]; + j->code_bits -= n; + #else + k = (j->code_buffer >> (32 - n)) & bmask[n]; + j->code_bits -= n; + j->code_buffer <<= n; + #endif + // the following test is probably a random branch that won't + // predict well. I tried to table accelerate it but failed. + // maybe it's compiling as a conditional move? + if (k < m) + return (-1 << n) + k + 1; + else + return k; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static uint8 dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int decode_block(jpeg *j, short data[64], huffman *hdc, huffman *hac, int b) +{ + int diff,dc,k; + int t = decode(j, hdc); + if (t < 0) return e("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) dc; + + // decode AC components, see JPEG spec + k = 1; + do { + int r,s; + int rs = decode(j, hac); + if (rs < 0) return e("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + data[dezigzag[k++]] = (short) extend_receive(j,s); + } + } while (k < 64); + return 1; +} + +// take a -128..127 value and clamp it and convert to 0..255 +__forceinline static uint8 clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (uint8) x; +} + +#define f2f(x) (int) (((x) * 4096 + 0.5)) +#define fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * f2f(0.5411961f); \ + t2 = p1 + p3*f2f(-1.847759065f); \ + t3 = p1 + p2*f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = fsh(p2+p3); \ + t1 = fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*f2f( 1.175875602f); \ + t0 = t0*f2f( 0.298631336f); \ + t1 = t1*f2f( 2.053119869f); \ + t2 = t2*f2f( 3.072711026f); \ + t3 = t3*f2f( 1.501321110f); \ + p1 = p5 + p1*f2f(-0.899976223f); \ + p2 = p5 + p2*f2f(-2.562915447f); \ + p3 = p3*f2f(-1.961570560f); \ + p4 = p4*f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +#ifdef STBI_SIMD +typedef unsigned short stbi_dequantize_t; +#else +typedef uint8 stbi_dequantize_t; +#endif + +// .344 seconds on 3*anemones.jpg +static void idct_block(uint8 *out, int out_stride, short data[64], stbi_dequantize_t *dequantize) +{ + int i,val[64],*v=val; + stbi_dequantize_t *dq = dequantize; + uint8 *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d,++dq, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] * dq[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + IDCT_1D(d[ 0]*dq[ 0],d[ 8]*dq[ 8],d[16]*dq[16],d[24]*dq[24], + d[32]*dq[32],d[40]*dq[40],d[48]*dq[48],d[56]*dq[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = clamp((x0+t3) >> 17); + o[7] = clamp((x0-t3) >> 17); + o[1] = clamp((x1+t2) >> 17); + o[6] = clamp((x1-t2) >> 17); + o[2] = clamp((x2+t1) >> 17); + o[5] = clamp((x2-t1) >> 17); + o[3] = clamp((x3+t0) >> 17); + o[4] = clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SIMD +static stbi_idct_8x8 stbi_idct_installed = idct_block; + +extern void stbi_install_idct(stbi_idct_8x8 func) +{ + stbi_idct_installed = func; +} +#endif + +#define MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static uint8 get_marker(jpeg *j) +{ + uint8 x; + if (j->marker != MARKER_none) { x = j->marker; j->marker = MARKER_none; return x; } + x = get8u(&j->s); + if (x != 0xff) return MARKER_none; + while (x == 0xff) + x = get8u(&j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, reset the entropy decoder and +// the dc prediction +static void reset(jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int parse_entropy_coded_data(jpeg *z) +{ + reset(z); + if (z->scan_n == 1) { + int i,j; + #ifdef STBI_SIMD + __declspec(align(16)) + #endif + short data[64]; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; + #ifdef STBI_SIMD + stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); + #else + idct_block(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); + #endif + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!RESTART(z->marker)) return 1; + reset(z); + } + } + } + } else { // interleaved! + int i,j,k,x,y; + short data[64]; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + if (!decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+z->img_comp[n].ha, n)) return 0; + #ifdef STBI_SIMD + stbi_idct_installed(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant2[z->img_comp[n].tq]); + #else + idct_block(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data, z->dequant[z->img_comp[n].tq]); + #endif + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!RESTART(z->marker)) return 1; + reset(z); + } + } + } + } + return 1; +} + +static int process_marker(jpeg *z, int m) +{ + int L; + switch (m) { + case MARKER_none: // no marker found + return e("expected marker","Corrupt JPEG"); + + case 0xC2: // SOF - progressive + return e("progressive jpeg","JPEG format not supported (progressive)"); + + case 0xDD: // DRI - specify restart interval + if (get16(&z->s) != 4) return e("bad DRI len","Corrupt JPEG"); + z->restart_interval = get16(&z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = get16(&z->s)-2; + while (L > 0) { + int q = get8(&z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return e("bad DQT type","Corrupt JPEG"); + if (t > 3) return e("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][dezigzag[i]] = get8u(&z->s); + #ifdef STBI_SIMD + for (i=0; i < 64; ++i) + z->dequant2[t][i] = z->dequant[t][i]; + #endif + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = get16(&z->s)-2; + while (L > 0) { + uint8 *v; + int sizes[16],i,m=0; + int q = get8(&z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return e("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = get8(&z->s); + m += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < m; ++i) + v[i] = get8u(&z->s); + L -= m; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + skip(&z->s, get16(&z->s)-2); + return 1; + } + return 0; +} + +// after we see SOS +static int process_scan_header(jpeg *z) +{ + int i; + int Ls = get16(&z->s); + z->scan_n = get8(&z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s.img_n) return e("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return e("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = get8(&z->s), which; + int q = get8(&z->s); + for (which = 0; which < z->s.img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s.img_n) return 0; + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return e("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return e("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + if (get8(&z->s) != 0) return e("bad SOS","Corrupt JPEG"); + get8(&z->s); // should be 63, but might be 0 + if (get8(&z->s) != 0) return e("bad SOS","Corrupt JPEG"); + + return 1; +} + +static int process_frame_header(jpeg *z, int scan) +{ + stbi *s = &z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = get16(s); if (Lf < 11) return e("bad SOF len","Corrupt JPEG"); // JPEG + p = get8(s); if (p != 8) return e("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = get16(s); if (s->img_y == 0) return e("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = get16(s); if (s->img_x == 0) return e("0 width","Corrupt JPEG"); // JPEG requires + c = get8(s); + if (c != 3 && c != 1) return e("bad component count","Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return e("bad SOF len","Corrupt JPEG"); + + for (i=0; i < s->img_n; ++i) { + z->img_comp[i].id = get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) // some version of jpegtran outputs non-JFIF-compliant files! + return e("bad component ID","Corrupt JPEG"); + q = get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return e("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return e("bad V","Corrupt JPEG"); + z->img_comp[i].tq = get8(s); if (z->img_comp[i].tq > 3) return e("bad TQ","Corrupt JPEG"); + } + + if (scan != SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].raw_data = malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + free(z->img_comp[i].raw_data); + z->img_comp[i].data = NULL; + } + return e("outofmem", "Out of memory"); + } + // align blocks for installable-idct using mmx/sse + z->img_comp[i].data = (uint8*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define DNL(x) ((x) == 0xdc) +#define SOI(x) ((x) == 0xd8) +#define EOI(x) ((x) == 0xd9) +#define SOF(x) ((x) == 0xc0 || (x) == 0xc1) +#define SOS(x) ((x) == 0xda) + +static int decode_jpeg_header(jpeg *z, int scan) +{ + int m; + z->marker = MARKER_none; // initialize cached marker to empty + m = get_marker(z); + if (!SOI(m)) return e("no SOI","Corrupt JPEG"); + if (scan == SCAN_type) return 1; + m = get_marker(z); + while (!SOF(m)) { + if (!process_marker(z,m)) return 0; + m = get_marker(z); + while (m == MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (at_eof(&z->s)) return e("no SOF", "Corrupt JPEG"); + m = get_marker(z); + } + } + if (!process_frame_header(z, scan)) return 0; + return 1; +} + +static int decode_jpeg_image(jpeg *j) +{ + int m; + j->restart_interval = 0; + if (!decode_jpeg_header(j, SCAN_load)) return 0; + m = get_marker(j); + while (!EOI(m)) { + if (SOS(m)) { + if (!process_scan_header(j)) return 0; + if (!parse_entropy_coded_data(j)) return 0; + if (j->marker == MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!at_eof(&j->s)) { + int x = get8(&j->s); + if (x == 255) { + j->marker = get8u(&j->s); + break; + } else if (x != 0) { + return 0; + } + } + // if we reach eof without hitting a marker, get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!process_marker(j, m)) return 0; + } + m = get_marker(j); + } + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef uint8 *(*resample_row_func)(uint8 *out, uint8 *in0, uint8 *in1, + int w, int hs); + +#define div4(x) ((uint8) ((x) >> 2)) + +static uint8 *resample_row_1(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static uint8* resample_row_v_2(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static uint8* resample_row_h_2(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + uint8 *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = div4(n+input[i-1]); + out[i*2+1] = div4(n+input[i+1]); + } + out[i*2+0] = div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define div16(x) ((uint8) ((x) >> 4)) + +static uint8 *resample_row_hv_2(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = div16(3*t0 + t1 + 8); + out[i*2 ] = div16(3*t1 + t0 + 8); + } + out[w*2-1] = div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +static uint8 *resample_row_generic(uint8 *out, uint8 *in_near, uint8 *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + in_far = in_far; + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) + +// 0.38 seconds on 3*anemones.jpg (0.25 with processor = Pro) +// VC6 without processor=Pro is generating multiple LEAs per multiply! +static void YCbCr_to_RGB_row(uint8 *out, const uint8 *y, const uint8 *pcb, const uint8 *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (uint8)r; + out[1] = (uint8)g; + out[2] = (uint8)b; + out[3] = 255; + out += step; + } +} + +#ifdef STBI_SIMD +static stbi_YCbCr_to_RGB_run stbi_YCbCr_installed = YCbCr_to_RGB_row; + +void stbi_install_YCbCr_to_RGB(stbi_YCbCr_to_RGB_run func) +{ + stbi_YCbCr_installed = func; +} +#endif + + +// clean up the temporary component buffers +static void cleanup_jpeg(jpeg *j) +{ + int i; + for (i=0; i < j->s.img_n; ++i) { + if (j->img_comp[i].data) { + free(j->img_comp[i].raw_data); + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].linebuf) { + free(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } +} + +typedef struct +{ + resample_row_func resample; + uint8 *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi_resample; + +static uint8 *load_jpeg_image(jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + // validate req_comp + if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error"); + z->s.img_n = 0; + + // load a jpeg image from whichever source + if (!decode_jpeg_image(z)) { cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s.img_n; + + if (z->s.img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s.img_n; + + // resample and color-convert + { + int k; + uint i,j; + uint8 *output; + uint8 *coutput[4]; + + stbi_resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi_resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (uint8 *) malloc(z->s.img_x + 3); + if (!z->img_comp[k].linebuf) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s.img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = resample_row_hv_2; + else r->resample = resample_row_generic; + } + + // can't error after this so, this is safe + output = (uint8 *) malloc(n * z->s.img_x * z->s.img_y + 1); + if (!output) { cleanup_jpeg(z); return epuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s.img_y; ++j) { + uint8 *out = output + n * z->s.img_x * j; + for (k=0; k < decode_n; ++k) { + stbi_resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + uint8 *y = coutput[0]; + if (z->s.img_n == 3) { + #ifdef STBI_SIMD + stbi_YCbCr_installed(out, y, coutput[1], coutput[2], z->s.img_x, n); + #else + YCbCr_to_RGB_row(out, y, coutput[1], coutput[2], z->s.img_x, n); + #endif + } else + for (i=0; i < z->s.img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + uint8 *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s.img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s.img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + cleanup_jpeg(z); + *out_x = z->s.img_x; + *out_y = z->s.img_y; + if (comp) *comp = z->s.img_n; // report original components, not output + return output; + } +} + +#ifndef STBI_NO_STDIO +unsigned char *stbi_jpeg_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + jpeg j; + start_file(&j.s, f); + return load_jpeg_image(&j, x,y,comp,req_comp); +} + +unsigned char *stbi_jpeg_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_jpeg_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return data; +} +#endif + +unsigned char *stbi_jpeg_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + #ifdef STBI_SMALL_STACK + unsigned char *result; + jpeg *j = (jpeg *) malloc(sizeof(*j)); + start_mem(&j->s, buffer, len); + result = load_jpeg_image(j,x,y,comp,req_comp); + free(j); + return result; + #else + jpeg j; + start_mem(&j.s, buffer,len); + return load_jpeg_image(&j, x,y,comp,req_comp); + #endif +} + +static int stbi_jpeg_info_raw(jpeg *j, int *x, int *y, int *comp) +{ + if (!decode_jpeg_header(j, SCAN_header)) + return 0; + if (x) *x = j->s.img_x; + if (y) *y = j->s.img_y; + if (comp) *comp = j->s.img_n; + return 1; +} + +#ifndef STBI_NO_STDIO +int stbi_jpeg_test_file(FILE *f) +{ + int n,r; + jpeg j; + n = ftell(f); + start_file(&j.s, f); + r = decode_jpeg_header(&j, SCAN_type); + fseek(f,n,SEEK_SET); + return r; +} + +int stbi_jpeg_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + jpeg j; + long n = ftell(f); + int res; + start_file(&j.s, f); + res = stbi_jpeg_info_raw(&j, x, y, comp); + fseek(f, n, SEEK_SET); + return res; +} + +int stbi_jpeg_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = fopen(filename, "rb"); + int result; + if (!f) return e("can't fopen", "Unable to open file"); + result = stbi_jpeg_info_from_file(f, x, y, comp); + fclose(f); + return result; +} +#endif + +int stbi_jpeg_test_memory(stbi_uc const *buffer, int len) +{ + jpeg j; + start_mem(&j.s, buffer,len); + return decode_jpeg_header(&j, SCAN_type); +} + +int stbi_jpeg_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + jpeg j; + start_mem(&j.s, buffer, len); + return stbi_jpeg_info_raw(&j, x, y, comp); +} + +#ifndef STBI_NO_STDIO +extern int stbi_jpeg_info (char const *filename, int *x, int *y, int *comp); +extern int stbi_jpeg_info_from_file (FILE *f, int *x, int *y, int *comp); +#endif +extern int stbi_jpeg_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define ZFAST_BITS 9 // accelerate all cases in default tables +#define ZFAST_MASK ((1 << ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + uint16 fast[1 << ZFAST_BITS]; + uint16 firstcode[16]; + int maxcode[17]; + uint16 firstsymbol[16]; + uint8 size[288]; + uint16 value[288]; +} zhuffman; + +__forceinline static int bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +__forceinline static int bit_reverse(int v, int bits) +{ + assert(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return bitreverse16(v) >> (16-bits); +} + +static int zbuild_huffman(zhuffman *z, uint8 *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 255, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + assert(sizes[i] <= (1 << i)); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (uint16) code; + z->firstsymbol[i] = (uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return e("bad codelengths","Corrupt JPEG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + z->size[c] = (uint8)s; + z->value[c] = (uint16)i; + if (s <= ZFAST_BITS) { + int k = bit_reverse(next_code[s],s); + while (k < (1 << ZFAST_BITS)) { + z->fast[k] = (uint16) c; + k += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + uint8 *zbuffer, *zbuffer_end; + int num_bits; + uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + zhuffman z_length, z_distance; +} zbuf; + +__forceinline static int zget8(zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void fill_bits(zbuf *z) +{ + do { + assert(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +__forceinline static unsigned int zreceive(zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +__forceinline static int zhuffman_decode(zbuf *a, zhuffman *z) +{ + int b,s,k; + if (a->num_bits < 16) fill_bits(a); + b = z->fast[a->code_buffer & ZFAST_MASK]; + if (b < 0xffff) { + s = z->size[b]; + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; + } + + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = bit_reverse(a->code_buffer, 16); + for (s=ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + assert(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +static int expand(zbuf *z, int n) // need to make room for n bytes +{ + char *q; + int cur, limit; + if (!z->z_expandable) return e("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) realloc(z->zout_start, limit); + if (q == NULL) return e("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int length_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int length_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int dist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int parse_huffman_block(zbuf *a) +{ + for(;;) { + int z = zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return e("bad huffman code","Corrupt PNG"); // error in huffman codes + if (a->zout >= a->zout_end) if (!expand(a, 1)) return 0; + *a->zout++ = (char) z; + } else { + uint8 *p; + int len,dist; + if (z == 256) return 1; + z -= 257; + len = length_base[z]; + if (length_extra[z]) len += zreceive(a, length_extra[z]); + z = zhuffman_decode(a, &a->z_distance); + if (z < 0) return e("bad huffman code","Corrupt PNG"); + dist = dist_base[z]; + if (dist_extra[z]) dist += zreceive(a, dist_extra[z]); + if (a->zout - a->zout_start < dist) return e("bad dist","Corrupt PNG"); + if (a->zout + len > a->zout_end) if (!expand(a, len)) return 0; + p = (uint8 *) (a->zout - dist); + while (len--) + *a->zout++ = *p++; + } + } +} + +static int compute_huffman_codes(zbuf *a) +{ + static uint8 length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + zhuffman z_codelength; + uint8 lencodes[286+32+137];//padding for maximum single op + uint8 codelength_sizes[19]; + int i,n; + + int hlit = zreceive(a,5) + 257; + int hdist = zreceive(a,5) + 1; + int hclen = zreceive(a,4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (uint8) s; + } + if (!zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = zhuffman_decode(a, &z_codelength); + assert(c >= 0 && c < 19); + if (c < 16) + lencodes[n++] = (uint8) c; + else if (c == 16) { + c = zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + assert(c == 18); + c = zreceive(a,7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return e("bad codelengths","Corrupt PNG"); + if (!zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int parse_uncompressed_block(zbuf *a) +{ + uint8 header[4]; + int len,nlen,k; + if (a->num_bits & 7) + zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (uint8) (a->code_buffer & 255); // wtf this warns? + a->code_buffer >>= 8; + a->num_bits -= 8; + } + assert(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = (uint8) zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return e("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return e("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!expand(a, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int parse_zlib_header(zbuf *a) +{ + int cmf = zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = zget8(a); + if ((cmf*256+flg) % 31 != 0) return e("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return e("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return e("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static uint8 default_length[288], default_distance[32]; +static void init_defaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) default_length[i] = 8; + for ( ; i <= 255; ++i) default_length[i] = 9; + for ( ; i <= 279; ++i) default_length[i] = 7; + for ( ; i <= 287; ++i) default_length[i] = 8; + + for (i=0; i <= 31; ++i) default_distance[i] = 5; +} + +int stbi_png_partial; // a quick hack to only allow decoding some of a PNG... I should implement real streaming support instead +static int parse_zlib(zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = zreceive(a,1); + type = zreceive(a,2); + if (type == 0) { + if (!parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!default_distance[31]) init_defaults(); + if (!zbuild_huffman(&a->z_length , default_length , 288)) return 0; + if (!zbuild_huffman(&a->z_distance, default_distance, 32)) return 0; + } else { + if (!compute_huffman_codes(a)) return 0; + } + if (!parse_huffman_block(a)) return 0; + } + if (stbi_png_partial && a->zout - a->zout_start > 65536) + break; + } while (!final); + return 1; +} + +static int do_zlib(zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return parse_zlib(a, parse_header); +} + +char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + zbuf a; + char *p = (char *) malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (uint8 *) buffer; + a.zbuffer_end = (uint8 *) buffer + len; + if (do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + zbuf a; + char *p = (char *) malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (uint8 *) buffer; + a.zbuffer_end = (uint8 *) buffer + len; + if (do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + zbuf a; + a.zbuffer = (uint8 *) ibuffer; + a.zbuffer_end = (uint8 *) ibuffer + ilen; + if (do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + zbuf a; + char *p = (char *) malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (uint8 *) buffer; + a.zbuffer_end = (uint8 *) buffer+len; + if (do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + free(a.zout_start); + return NULL; + } +} + +int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + zbuf a; + a.zbuffer = (uint8 *) ibuffer; + a.zbuffer_end = (uint8 *) ibuffer + ilen; + if (do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + + +typedef struct +{ + uint32 length; + uint32 type; +} chunk; + +#define PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static chunk get_chunk_header(stbi *s) +{ + chunk c; + c.length = get32(s); + c.type = get32(s); + return c; +} + +static int check_png_header(stbi *s) +{ + static uint8 png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (get8(s) != png_sig[i]) return e("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi s; + uint8 *idata, *expanded, *out; +} png; + + +enum { + F_none=0, F_sub=1, F_up=2, F_avg=3, F_paeth=4, + F_avg_first, F_paeth_first +}; + +static uint8 first_row_filter[5] = +{ + F_none, F_sub, F_none, F_avg_first, F_paeth_first +}; + +static int paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +// create the png data from post-deflated data +static int create_png_image_raw(png *a, uint8 *raw, uint32 raw_len, int out_n, uint32 x, uint32 y) +{ + stbi *s = &a->s; + uint32 i,j,stride = x*out_n; + int k; + int img_n = s->img_n; // copy it into a local for later + assert(out_n == s->img_n || out_n == s->img_n+1); + if (stbi_png_partial) y = 1; + a->out = (uint8 *) malloc(x * y * out_n); + if (!a->out) return e("outofmem", "Out of memory"); + if (!stbi_png_partial) { + if (s->img_x == x && s->img_y == y) { + if (raw_len != (img_n * x + 1) * y) return e("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < (img_n * x + 1) * y) return e("not enough pixels","Corrupt PNG"); + } + } + for (j=0; j < y; ++j) { + uint8 *cur = a->out + stride*j; + uint8 *prior = cur - stride; + int filter = *raw++; + if (filter > 4) return e("invalid filter","Corrupt PNG"); + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + // handle first pixel explicitly + for (k=0; k < img_n; ++k) { + switch (filter) { + case F_none : cur[k] = raw[k]; break; + case F_sub : cur[k] = raw[k]; break; + case F_up : cur[k] = raw[k] + prior[k]; break; + case F_avg : cur[k] = raw[k] + (prior[k]>>1); break; + case F_paeth : cur[k] = (uint8) (raw[k] + paeth(0,prior[k],0)); break; + case F_avg_first : cur[k] = raw[k]; break; + case F_paeth_first: cur[k] = raw[k]; break; + } + } + if (img_n != out_n) cur[img_n] = 255; + raw += img_n; + cur += out_n; + prior += out_n; + // this is a little gross, so that we don't switch per-pixel or per-component + if (img_n == out_n) { + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, raw+=img_n,cur+=img_n,prior+=img_n) \ + for (k=0; k < img_n; ++k) + switch (filter) { + CASE(F_none) cur[k] = raw[k]; break; + CASE(F_sub) cur[k] = raw[k] + cur[k-img_n]; break; + CASE(F_up) cur[k] = raw[k] + prior[k]; break; + CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-img_n])>>1); break; + CASE(F_paeth) cur[k] = (uint8) (raw[k] + paeth(cur[k-img_n],prior[k],prior[k-img_n])); break; + CASE(F_avg_first) cur[k] = raw[k] + (cur[k-img_n] >> 1); break; + CASE(F_paeth_first) cur[k] = (uint8) (raw[k] + paeth(cur[k-img_n],0,0)); break; + } + #undef CASE + } else { + assert(img_n+1 == out_n); + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[img_n]=255,raw+=img_n,cur+=out_n,prior+=out_n) \ + for (k=0; k < img_n; ++k) + switch (filter) { + CASE(F_none) cur[k] = raw[k]; break; + CASE(F_sub) cur[k] = raw[k] + cur[k-out_n]; break; + CASE(F_up) cur[k] = raw[k] + prior[k]; break; + CASE(F_avg) cur[k] = raw[k] + ((prior[k] + cur[k-out_n])>>1); break; + CASE(F_paeth) cur[k] = (uint8) (raw[k] + paeth(cur[k-out_n],prior[k],prior[k-out_n])); break; + CASE(F_avg_first) cur[k] = raw[k] + (cur[k-out_n] >> 1); break; + CASE(F_paeth_first) cur[k] = (uint8) (raw[k] + paeth(cur[k-out_n],0,0)); break; + } + #undef CASE + } + } + return 1; +} + +static int create_png_image(png *a, uint8 *raw, uint32 raw_len, int out_n, int interlaced) +{ + uint8 *final; + int p; + int save; + if (!interlaced) + return create_png_image_raw(a, raw, raw_len, out_n, a->s.img_x, a->s.img_y); + save = stbi_png_partial; + stbi_png_partial = 0; + + // de-interlacing + final = (uint8 *) malloc(a->s.img_x * a->s.img_y * out_n); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s.img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s.img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + if (!create_png_image_raw(a, raw, raw_len, out_n, x, y)) { + free(final); + return 0; + } + for (j=0; j < y; ++j) + for (i=0; i < x; ++i) + memcpy(final + (j*yspc[p]+yorig[p])*a->s.img_x*out_n + (i*xspc[p]+xorig[p])*out_n, + a->out + (j*x+i)*out_n, out_n); + free(a->out); + raw += (x*out_n+1)*y; + raw_len -= (x*out_n+1)*y; + } + } + a->out = final; + + stbi_png_partial = save; + return 1; +} + +static int compute_transparency(png *z, uint8 tc[3], int out_n) +{ + stbi *s = &z->s; + uint32 i, pixel_count = s->img_x * s->img_y; + uint8 *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + assert(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int expand_palette(png *a, uint8 *palette, int len, int pal_img_n) +{ + uint32 i, pixel_count = a->s.img_x * a->s.img_y; + uint8 *p, *temp_out, *orig = a->out; + + p = (uint8 *) malloc(pixel_count * pal_img_n); + if (p == NULL) return e("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + free(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi_unpremultiply_on_load = 0; +static int stbi_de_iphone_flag = 0; + +void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi_unpremultiply_on_load = flag_true_if_should_unpremultiply; +} +void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi_de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi_de_iphone(png *z) +{ + stbi *s = &z->s; + uint32 i, pixel_count = s->img_x * s->img_y; + uint8 *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + uint8 t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + assert(s->img_out_n == 4); + if (stbi_unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + uint8 a = p[3]; + uint8 t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + uint8 t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +static int parse_png_file(png *z, int scan, int req_comp) +{ + uint8 palette[1024], pal_img_n=0; + uint8 has_trans=0, tc[3]; + uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, iphone=0; + stbi *s = &z->s; + + if (!check_png_header(s)) return 0; + + if (scan == SCAN_type) return 1; + + for (;;) { + chunk c = get_chunk_header(s); + switch (c.type) { + case PNG_TYPE('C','g','B','I'): + iphone = stbi_de_iphone_flag; + skip(s, c.length); + break; + case PNG_TYPE('I','H','D','R'): { + int depth,color,comp,filter; + if (!first) return e("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return e("bad IHDR len","Corrupt PNG"); + s->img_x = get32(s); if (s->img_x > (1 << 24)) return e("too large","Very large image (corrupt?)"); + s->img_y = get32(s); if (s->img_y > (1 << 24)) return e("too large","Very large image (corrupt?)"); + depth = get8(s); if (depth != 8) return e("8bit only","PNG not supported: 8-bit only"); + color = get8(s); if (color > 6) return e("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return e("bad ctype","Corrupt PNG"); + comp = get8(s); if (comp) return e("bad comp method","Corrupt PNG"); + filter= get8(s); if (filter) return e("bad filter method","Corrupt PNG"); + interlace = get8(s); if (interlace>1) return e("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return e("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return e("too large", "Image too large to decode"); + if (scan == SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return e("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case PNG_TYPE('P','L','T','E'): { + if (first) return e("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return e("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return e("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = get8u(s); + palette[i*4+1] = get8u(s); + palette[i*4+2] = get8u(s); + palette[i*4+3] = 255; + } + break; + } + + case PNG_TYPE('t','R','N','S'): { + if (first) return e("first not IHDR", "Corrupt PNG"); + if (z->idata) return e("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return e("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return e("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = get8u(s); + } else { + if (!(s->img_n & 1)) return e("tRNS with alpha","Corrupt PNG"); + if (c.length != (uint32) s->img_n*2) return e("bad tRNS len","Corrupt PNG"); + has_trans = 1; + for (k=0; k < s->img_n; ++k) + tc[k] = (uint8) get16(s); // non 8-bit images will be larger + } + break; + } + + case PNG_TYPE('I','D','A','T'): { + if (first) return e("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return e("no PLTE","Corrupt PNG"); + if (scan == SCAN_header) { s->img_n = pal_img_n; return 1; } + if (ioff + c.length > idata_limit) { + uint8 *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + p = (uint8 *) realloc(z->idata, idata_limit); if (p == NULL) return e("outofmem", "Out of memory"); + z->idata = p; + } + if (!getn(s, z->idata+ioff,c.length)) return e("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case PNG_TYPE('I','E','N','D'): { + uint32 raw_len; + if (first) return e("first not IHDR", "Corrupt PNG"); + if (scan != SCAN_load) return 1; + if (z->idata == NULL) return e("no IDAT","Corrupt PNG"); + z->expanded = (uint8 *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, 16384, (int *) &raw_len, !iphone); + if (z->expanded == NULL) return 0; // zlib should set error + free(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!create_png_image(z, z->expanded, raw_len, s->img_out_n, interlace)) return 0; + if (has_trans) + if (!compute_transparency(z, tc, s->img_out_n)) return 0; + if (iphone && s->img_out_n > 2) + stbi_de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!expand_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + free(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return e("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX chunk not known"; + invalid_chunk[0] = (uint8) (c.type >> 24); + invalid_chunk[1] = (uint8) (c.type >> 16); + invalid_chunk[2] = (uint8) (c.type >> 8); + invalid_chunk[3] = (uint8) (c.type >> 0); + #endif + return e(invalid_chunk, "PNG not supported: unknown chunk type"); + } + skip(s, c.length); + break; + } + // end of chunk, read and skip CRC + get32(s); + } +} + +static unsigned char *do_png(png *p, int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + p->expanded = NULL; + p->idata = NULL; + p->out = NULL; + if (req_comp < 0 || req_comp > 4) return epuc("bad req_comp", "Internal error"); + if (parse_png_file(p, SCAN_load, req_comp)) { + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s.img_out_n) { + result = convert_format(result, p->s.img_out_n, req_comp, p->s.img_x, p->s.img_y); + p->s.img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s.img_x; + *y = p->s.img_y; + if (n) *n = p->s.img_n; + } + free(p->out); p->out = NULL; + free(p->expanded); p->expanded = NULL; + free(p->idata); p->idata = NULL; + + return result; +} + +#ifndef STBI_NO_STDIO +unsigned char *stbi_png_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + png p; + start_file(&p.s, f); + return do_png(&p, x,y,comp,req_comp); +} + +unsigned char *stbi_png_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_png_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return data; +} +#endif + +unsigned char *stbi_png_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + png p; + start_mem(&p.s, buffer,len); + return do_png(&p, x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +int stbi_png_test_file(FILE *f) +{ + png p; + int n,r; + n = ftell(f); + start_file(&p.s, f); + r = parse_png_file(&p, SCAN_type,STBI_default); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_png_test_memory(stbi_uc const *buffer, int len) +{ + png p; + start_mem(&p.s, buffer, len); + return parse_png_file(&p, SCAN_type,STBI_default); +} + +static int stbi_png_info_raw(png *p, int *x, int *y, int *comp) +{ + if (!parse_png_file(p, SCAN_header, 0)) + return 0; + if (x) *x = p->s.img_x; + if (y) *y = p->s.img_y; + if (comp) *comp = p->s.img_n; + return 1; +} + +#ifndef STBI_NO_STDIO +int stbi_png_info (char const *filename, int *x, int *y, int *comp) +{ + int res; + FILE *f = fopen(filename, "rb"); + if (!f) return 0; + res = stbi_png_info_from_file(f, x, y, comp); + fclose(f); + return res; +} + +int stbi_png_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + png p; + int res; + long n = ftell(f); + start_file(&p.s, f); + res = stbi_png_info_raw(&p, x, y, comp); + fseek(f, n, SEEK_SET); + return res; +} +#endif // !STBI_NO_STDIO + +int stbi_png_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + png p; + start_mem(&p.s, buffer, len); + return stbi_png_info_raw(&p, x, y, comp); +} + +// Microsoft/Windows BMP image + +static int bmp_test(stbi *s) +{ + int sz; + if (get8(s) != 'B') return 0; + if (get8(s) != 'M') return 0; + get32le(s); // discard filesize + get16le(s); // discard reserved + get16le(s); // discard reserved + get32le(s); // discard data offset + sz = get32le(s); + if (sz == 12 || sz == 40 || sz == 56 || sz == 108) return 1; + return 0; +} + +#ifndef STBI_NO_STDIO +int stbi_bmp_test_file (FILE *f) +{ + stbi s; + int r,n = ftell(f); + start_file(&s,f); + r = bmp_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_bmp_test_memory (stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s, buffer, len); + return bmp_test(&s); +} + +// returns 0..31 for the highest set bit +static int high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +static stbi_uc *bmp_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + uint8 *out; + unsigned int mr=0,mg=0,mb=0,ma=0, fake_a=0; + stbi_uc pal[256][4]; + int psize=0,i,j,compress=0,width; + int bpp, flip_vertically, pad, target, offset, hsz; + if (get8(s) != 'B' || get8(s) != 'M') return epuc("not BMP", "Corrupt BMP"); + get32le(s); // discard filesize + get16le(s); // discard reserved + get16le(s); // discard reserved + offset = get32le(s); + hsz = get32le(s); + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108) return epuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = get16le(s); + s->img_y = get16le(s); + } else { + s->img_x = get32le(s); + s->img_y = get32le(s); + } + if (get16le(s) != 1) return epuc("bad BMP", "bad BMP"); + bpp = get16le(s); + if (bpp == 1) return epuc("monochrome", "BMP type not supported: 1-bit"); + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + if (hsz == 12) { + if (bpp < 24) + psize = (offset - 14 - 24) / 3; + } else { + compress = get32le(s); + if (compress == 1 || compress == 2) return epuc("BMP RLE", "BMP type not supported: RLE"); + get32le(s); // discard sizeof + get32le(s); // discard hres + get32le(s); // discard vres + get32le(s); // discard colorsused + get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + get32le(s); + get32le(s); + get32le(s); + get32le(s); + } + if (bpp == 16 || bpp == 32) { + mr = mg = mb = 0; + if (compress == 0) { + if (bpp == 32) { + mr = 0xffu << 16; + mg = 0xffu << 8; + mb = 0xffu << 0; + ma = 0xffu << 24; + fake_a = 1; // @TODO: check for cases like alpha value is all 0 and switch it to 255 + } else { + mr = 31u << 10; + mg = 31u << 5; + mb = 31u << 0; + } + } else if (compress == 3) { + mr = get32le(s); + mg = get32le(s); + mb = get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (mr == mg && mg == mb) { + // ?!?!? + return epuc("bad BMP", "bad BMP"); + } + } else + return epuc("bad BMP", "bad BMP"); + } + } else { + assert(hsz == 108); + mr = get32le(s); + mg = get32le(s); + mb = get32le(s); + ma = get32le(s); + get32le(s); // discard color space + for (i=0; i < 12; ++i) + get32le(s); // discard color space parameters + } + if (bpp < 16) + psize = (offset - 14 - hsz) >> 2; + } + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + out = (stbi_uc *) malloc(target * s->img_x * s->img_y); + if (!out) return epuc("outofmem", "Out of memory"); + if (bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { free(out); return epuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = get8u(s); + pal[i][1] = get8u(s); + pal[i][0] = get8u(s); + if (hsz != 12) get8(s); + pal[i][3] = 255; + } + skip(s, offset - 14 - hsz - psize * (hsz == 12 ? 3 : 4)); + if (bpp == 4) width = (s->img_x + 1) >> 1; + else if (bpp == 8) width = s->img_x; + else { free(out); return epuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=get8(s),v2=0; + if (bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (bpp == 8) ? get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + skip(s, offset - 14 - hsz); + if (bpp == 24) width = 3 * s->img_x; + else if (bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (bpp == 24) { + easy = 1; + } else if (bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0xff000000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) return epuc("bad masks", "Corrupt BMP"); + // right shift amt to put high bit in position #7 + rshift = high_bit(mr)-7; rcount = bitcount(mr); + gshift = high_bit(mg)-7; gcount = bitcount(mr); + bshift = high_bit(mb)-7; bcount = bitcount(mr); + ashift = high_bit(ma)-7; acount = bitcount(mr); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + int a; + out[z+2] = get8u(s); + out[z+1] = get8u(s); + out[z+0] = get8u(s); + z += 3; + a = (easy == 2 ? get8(s) : 255); + if (target == 4) out[z++] = (uint8) a; + } + } else { + for (i=0; i < (int) s->img_x; ++i) { + uint32 v = (bpp == 16 ? get16le(s) : get32le(s)); + int a; + out[z++] = (uint8) shiftsigned(v & mr, rshift, rcount); + out[z++] = (uint8) shiftsigned(v & mg, gshift, gcount); + out[z++] = (uint8) shiftsigned(v & mb, bshift, bcount); + a = (ma ? shiftsigned(v & ma, ashift, acount) : 255); + if (target == 4) out[z++] = (uint8) a; + } + } + skip(s, pad); + } + } + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = target; + return out; +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_bmp_load (char const *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_bmp_load_from_file(f, x,y,comp,req_comp); + fclose(f); + return data; +} + +stbi_uc *stbi_bmp_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s, f); + return bmp_load(&s, x,y,comp,req_comp); +} +#endif + +stbi_uc *stbi_bmp_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s, buffer, len); + return bmp_load(&s, x,y,comp,req_comp); +} + +// Targa Truevision - TGA +// by Jonathan Dummer + +static int tga_info(stbi *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp; + int sz; + get8u(s); // discard Offset + sz = get8u(s); // color type + if( sz > 1 ) return 0; // only RGB or indexed allowed + sz = get8u(s); // image type + // only RGB or grey allowed, +/- RLE + if ((sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11)) return 0; + get16le(s); // discard palette start + get16le(s); // discard palette length + get8(s); // discard bits per palette color entry + get16le(s); // discard x origin + get16le(s); // discard y origin + tga_w = get16le(s); + if( tga_w < 1 ) return 0; // test width + tga_h = get16le(s); + if( tga_h < 1 ) return 0; // test height + sz = get8(s); // bits per pixel + // only RGB or RGBA or grey allowed + if ((sz != 8) && (sz != 16) && (sz != 24) && (sz != 32)) return 0; + tga_comp = sz; + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp / 8; + return 1; // seems to have passed everything +} + +#ifndef STBI_NO_STDIO +int stbi_tga_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + stbi s; + int r; + long n = ftell(f); + start_file(&s, f); + r = tga_info(&s, x, y, comp); + fseek(f, n, SEEK_SET); + return r; +} +#endif + +int stbi_tga_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi s; + start_mem(&s, buffer, len); + return tga_info(&s, x, y, comp); +} + +static int tga_test(stbi *s) +{ + int sz; + get8u(s); // discard Offset + sz = get8u(s); // color type + if ( sz > 1 ) return 0; // only RGB or indexed allowed + sz = get8u(s); // image type + if ( (sz != 1) && (sz != 2) && (sz != 3) && (sz != 9) && (sz != 10) && (sz != 11) ) return 0; // only RGB or grey allowed, +/- RLE + get16(s); // discard palette start + get16(s); // discard palette length + get8(s); // discard bits per palette color entry + get16(s); // discard x origin + get16(s); // discard y origin + if ( get16(s) < 1 ) return 0; // test width + if ( get16(s) < 1 ) return 0; // test height + sz = get8(s); // bits per pixel + if ( (sz != 8) && (sz != 16) && (sz != 24) && (sz != 32) ) return 0; // only RGB or RGBA or grey allowed + return 1; // seems to have passed everything +} + +#ifndef STBI_NO_STDIO +int stbi_tga_test_file (FILE *f) +{ + stbi s; + int r,n = ftell(f); + start_file(&s, f); + r = tga_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_tga_test_memory (stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s, buffer, len); + return tga_test(&s); +} + +static stbi_uc *tga_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = get8u(s); + int tga_indexed = get8u(s); + int tga_image_type = get8u(s); + int tga_is_RLE = 0; + int tga_palette_start = get16le(s); + int tga_palette_len = get16le(s); + int tga_palette_bits = get8u(s); + int tga_x_origin = get16le(s); + int tga_y_origin = get16le(s); + int tga_width = get16le(s); + int tga_height = get16le(s); + int tga_bits_per_pixel = get8u(s); + int tga_inverted = get8u(s); + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4]; + unsigned char trans_data[4]; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + /* int tga_alpha_bits = tga_inverted & 15; */ + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // error check + if ( //(tga_indexed) || + (tga_width < 1) || (tga_height < 1) || + (tga_image_type < 1) || (tga_image_type > 3) || + ((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16) && + (tga_bits_per_pixel != 24) && (tga_bits_per_pixel != 32)) + ) + { + return NULL; + } + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) + { + tga_bits_per_pixel = tga_palette_bits; + } + + // tga info + *x = tga_width; + *y = tga_height; + if ( (req_comp < 1) || (req_comp > 4) ) + { + // just use whatever the file was + req_comp = tga_bits_per_pixel / 8; + *comp = req_comp; + } else + { + // force a new number of components + *comp = tga_bits_per_pixel/8; + } + tga_data = (unsigned char*)malloc( tga_width * tga_height * req_comp ); + + // skip to the data's starting position (offset usually = 0) + skip(s, tga_offset ); + // do I need to load a palette? + if ( tga_indexed ) + { + // any data to skip? (offset usually = 0) + skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)malloc( tga_palette_len * tga_palette_bits / 8 ); + if (!getn(s, tga_palette, tga_palette_len * tga_palette_bits / 8 )) + return NULL; + } + // load the data + trans_data[0] = trans_data[1] = trans_data[2] = trans_data[3] = 0; + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE chunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = get8u(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in 1 byte, then perform the lookup + int pal_idx = get8u(s); + if ( pal_idx >= tga_palette_len ) + { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_bits_per_pixel / 8; + for (j = 0; j*8 < tga_bits_per_pixel; ++j) + { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else + { + // read in the data raw + for (j = 0; j*8 < tga_bits_per_pixel; ++j) + { + raw_data[j] = get8u(s); + } + } + // convert raw to the intermediate format + switch (tga_bits_per_pixel) + { + case 8: + // Luminous => RGBA + trans_data[0] = raw_data[0]; + trans_data[1] = raw_data[0]; + trans_data[2] = raw_data[0]; + trans_data[3] = 255; + break; + case 16: + // Luminous,Alpha => RGBA + trans_data[0] = raw_data[0]; + trans_data[1] = raw_data[0]; + trans_data[2] = raw_data[0]; + trans_data[3] = raw_data[1]; + break; + case 24: + // BGR => RGBA + trans_data[0] = raw_data[2]; + trans_data[1] = raw_data[1]; + trans_data[2] = raw_data[0]; + trans_data[3] = 255; + break; + case 32: + // BGRA => RGBA + trans_data[0] = raw_data[2]; + trans_data[1] = raw_data[1]; + trans_data[2] = raw_data[0]; + trans_data[3] = raw_data[3]; + break; + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + // convert to final format + switch (req_comp) + { + case 1: + // RGBA => Luminance + tga_data[i*req_comp+0] = compute_y(trans_data[0],trans_data[1],trans_data[2]); + break; + case 2: + // RGBA => Luminance,Alpha + tga_data[i*req_comp+0] = compute_y(trans_data[0],trans_data[1],trans_data[2]); + tga_data[i*req_comp+1] = trans_data[3]; + break; + case 3: + // RGBA => RGB + tga_data[i*req_comp+0] = trans_data[0]; + tga_data[i*req_comp+1] = trans_data[1]; + tga_data[i*req_comp+2] = trans_data[2]; + break; + case 4: + // RGBA => RGBA + tga_data[i*req_comp+0] = trans_data[0]; + tga_data[i*req_comp+1] = trans_data[1]; + tga_data[i*req_comp+2] = trans_data[2]; + tga_data[i*req_comp+3] = trans_data[3]; + break; + } + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * req_comp; + int index2 = (tga_height - 1 - j) * tga_width * req_comp; + for (i = tga_width * req_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + free( tga_palette ); + } + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_tga_load (char const *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_tga_load_from_file(f, x,y,comp,req_comp); + fclose(f); + return data; +} + +stbi_uc *stbi_tga_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s, f); + return tga_load(&s, x,y,comp,req_comp); +} +#endif + +stbi_uc *stbi_tga_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s, buffer, len); + return tga_load(&s, x,y,comp,req_comp); +} + + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +static int psd_test(stbi *s) +{ + if (get32(s) != 0x38425053) return 0; // "8BPS" + else return 1; +} + +#ifndef STBI_NO_STDIO +int stbi_psd_test_file(FILE *f) +{ + stbi s; + int r,n = ftell(f); + start_file(&s, f); + r = psd_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_psd_test_memory(stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s, buffer, len); + return psd_test(&s); +} + +static stbi_uc *psd_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int w,h; + uint8 *out; + + // Check identifier + if (get32(s) != 0x38425053) // "8BPS" + return epuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (get16(s) != 1) + return epuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = get16(s); + if (channelCount < 0 || channelCount > 16) + return epuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = get32(s); + w = get32(s); + + // Make sure the depth is 8 bits. + if (get16(s) != 8) + return epuc("unsupported bit depth", "PSD bit depth is not 8 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (get16(s) != 3) + return epuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + skip(s,get32(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + skip(s, get32(s) ); + + // Skip the reserved data. + skip(s, get32(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = get16(s); + if (compression > 1) + return epuc("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) malloc(4 * w*h); + if (!out) return epuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + uint8 *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++) *p = (channel == 3 ? 255 : 0), p += 4; + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = get8u(s); + p += 4; + len--; + } + } else if (len > 128) { + uint8 val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = get8u(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + uint8 *p; + + p = out + channel; + if (channel > channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++) *p = channel == 3 ? 255 : 0, p += 4; + } else { + // Read the data. + for (i = 0; i < pixelCount; i++) + *p = get8u(s), p += 4; + } + } + } + + if (req_comp && req_comp != 4) { + out = convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // convert_format frees input on failure + } + + if (comp) *comp = channelCount; + *y = h; + *x = w; + + return out; +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_psd_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_psd_load_from_file(f, x,y,comp,req_comp); + fclose(f); + return data; +} + +stbi_uc *stbi_psd_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s, f); + return psd_load(&s, x,y,comp,req_comp); +} +#endif + +stbi_uc *stbi_psd_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s, buffer, len); + return psd_load(&s, x,y,comp,req_comp); +} + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +static int pic_is4(stbi *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int pic_test(stbi *s) +{ + int i; + + if (!pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + get8(s); + + if (!pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} pic_packet_t; + +static stbi_uc *pic_readval(stbi *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (at_eof(s)) return epuc("bad file","PIC file too short"); + dest[i]=get8u(s); + } + } + + return dest; +} + +static void pic_copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *pic_load2(stbi *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + pic_packet_t packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + pic_packet_t *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return epuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = get8(s); + packet->size = get8u(s); + packet->type = get8u(s); + packet->channel = get8u(s); + + act_comp |= packet->channel; + + if (at_eof(s)) return epuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return epuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return epuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=get8u(s); + if (at_eof(s)) return epuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (uint8) left; + + if (!pic_readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = get8(s), i; + if (at_eof(s)) return epuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + int i; + + if (count==128) + count = get16(s); + else + count -= 127; + if (count > left) + return epuc("bad file","scanline overrun"); + + if (!pic_readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return epuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static stbi_uc *pic_load(stbi *s,int *px,int *py,int *comp,int req_comp) +{ + stbi_uc *result; + int i, x,y; + + for (i=0; i<92; ++i) + get8(s); + + x = get16(s); + y = get16(s); + if (at_eof(s)) return epuc("bad file","file too short (pic header)"); + if ((1 << 28) / x < y) return epuc("too large", "Image too large to decode"); + + get32(s); //skip `ratio' + get16(s); //skip `fields' + get16(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) malloc(x*y*4); + memset(result, 0xff, x*y*4); + + if (!pic_load2(s,x,y,comp, result)) { + free(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=convert_format(result,4,req_comp,x,y); + + return result; +} + +int stbi_pic_test_memory(stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s,buffer,len); + return pic_test(&s); +} + +stbi_uc *stbi_pic_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s,buffer,len); + return pic_load(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +int stbi_pic_test_file(FILE *f) +{ + int result; + long l = ftell(f); + stbi s; + start_file(&s,f); + result = pic_test(&s); + fseek(f,l,SEEK_SET); + return result; +} + +stbi_uc *stbi_pic_load(char const *filename,int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *result; + FILE *f=fopen(filename,"rb"); + if (!f) return 0; + result = stbi_pic_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +stbi_uc *stbi_pic_load_from_file(FILE *f,int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s,f); + return pic_load(&s,x,y,comp,req_comp); +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb +typedef struct stbi_gif_lzw_struct { + int16 prefix; + uint8 first; + uint8 suffix; +} stbi_gif_lzw; + +typedef struct stbi_gif_struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags; + uint8 pal[256][4]; + uint8 lpal[256][4]; + stbi_gif_lzw codes[4096]; + uint8 *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi_gif; + +static int gif_test(stbi *s) +{ + int sz; + if (get8(s) != 'G' || get8(s) != 'I' || get8(s) != 'F' || get8(s) != '8') return 0; + sz = get8(s); + if (sz != '9' && sz != '7') return 0; + if (get8(s) != 'a') return 0; + return 1; +} + +#ifndef STBI_NO_STDIO +int stbi_gif_test_file (FILE *f) +{ + stbi s; + int r,n = ftell(f); + start_file(&s,f); + r = gif_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +int stbi_gif_test_memory (stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s, buffer, len); + return gif_test(&s); +} + +static void stbi_gif_parse_colortable(stbi *s, uint8 pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = get8u(s); + pal[i][1] = get8u(s); + pal[i][0] = get8u(s); + pal[i][3] = transp ? 0 : 255; + } +} + +static int stbi_gif_header(stbi *s, stbi_gif *g, int *comp, int is_info) +{ + uint8 version; + if (get8(s) != 'G' || get8(s) != 'I' || get8(s) != 'F' || get8(s) != '8') + return e("not GIF", "Corrupt GIF"); + + version = get8u(s); + if (version != '7' && version != '9') return e("not GIF", "Corrupt GIF"); + if (get8(s) != 'a') return e("not GIF", "Corrupt GIF"); + + failure_reason = ""; + g->w = get16le(s); + g->h = get16le(s); + g->flags = get8(s); + g->bgindex = get8(s); + g->ratio = get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi_gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi_gif_info_raw(stbi *s, int *x, int *y, int *comp) +{ + stbi_gif g; + if (!stbi_gif_header(s, &g, comp, 1)) return 0; + if (x) *x = g.w; + if (y) *y = g.h; + return 1; +} + +static void stbi_out_gif_code(stbi_gif *g, uint16 code) +{ + uint8 *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi_out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static uint8 *stbi_process_gif_raster(stbi *s, stbi_gif *g) +{ + uint8 lzw_cs; + int32 len, code; + uint32 first; + int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi_gif_lzw *p; + + lzw_cs = get8u(s); + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (code = 0; code < clear; code++) { + g->codes[code].prefix = -1; + g->codes[code].first = (uint8) code; + g->codes[code].suffix = (uint8) code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (int32) get8(s) << valid_bits; + valid_bits += 8; + } else { + int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + skip(s, len); + while ((len = get8(s)) > 0) + skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return epuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return epuc("too many codes", "Corrupt GIF"); + p->prefix = (int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return epuc("illegal code in raster", "Corrupt GIF"); + + stbi_out_gif_code(g, (uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return epuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi_fill_gif_background(stbi_gif *g) +{ + int i; + uint8 *c = g->pal[g->bgindex]; + // @OPTIMIZE: write a dword at a time + for (i = 0; i < g->w * g->h * 4; i += 4) { + uint8 *p = &g->out[i]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static uint8 *stbi_gif_load_next(stbi *s, stbi_gif *g, int *comp, int req_comp) +{ + int i; + uint8 *old_out = 0; + + if (g->out == 0) { + if (!stbi_gif_header(s, g, comp,0)) return 0; // failure_reason set by stbi_gif_header + g->out = (uint8 *) malloc(4 * g->w * g->h); + if (g->out == 0) return epuc("outofmem", "Out of memory"); + stbi_fill_gif_background(g); + } else { + // animated-gif-only path + if (((g->eflags & 0x1C) >> 2) == 3) { + old_out = g->out; + g->out = (uint8 *) malloc(4 * g->w * g->h); + if (g->out == 0) return epuc("outofmem", "Out of memory"); + memcpy(g->out, old_out, g->w*g->h*4); + } + } + + for (;;) { + switch (get8(s)) { + case 0x2C: /* Image Descriptor */ + { + int32 x, y, w, h; + uint8 *o; + + x = get16le(s); + y = get16le(s); + w = get16le(s); + h = get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return epuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi_gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (uint8 *) g->lpal; + } else if (g->flags & 0x80) { + for (i=0; i < 256; ++i) // @OPTIMIZE: reset only the previous transparent + g->pal[i][3] = 255; + if (g->transparent >= 0 && (g->eflags & 0x01)) + g->pal[g->transparent][3] = 0; + g->color_table = (uint8 *) g->pal; + } else + return epuc("missing color table", "Corrupt GIF"); + + o = stbi_process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (req_comp && req_comp != 4) + o = convert_format(o, 4, req_comp, g->w, g->h); + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (get8(s) == 0xF9) { // Graphic Control Extension. + len = get8(s); + if (len == 4) { + g->eflags = get8(s); + get16le(s); // delay + g->transparent = get8(s); + } else { + skip(s, len); + break; + } + } + while ((len = get8(s)) != 0) + skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (uint8 *) 1; + + default: + return epuc("unknown code", "Corrupt GIF"); + } + } +} + +#ifndef STBI_NO_STDIO +stbi_uc *stbi_gif_load (char const *filename, int *x, int *y, int *comp, int req_comp) +{ + uint8 *data; + FILE *f = fopen(filename, "rb"); + if (!f) return NULL; + data = stbi_gif_load_from_file(f, x,y,comp,req_comp); + fclose(f); + return data; +} + +stbi_uc *stbi_gif_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp) +{ + uint8 *u = 0; + stbi s; + stbi_gif g={0}; + start_file(&s, f); + + u = stbi_gif_load_next(&s, &g, comp, req_comp); + if (u == (void *) 1) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + } + + return u; +} +#endif + +stbi_uc *stbi_gif_load_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + uint8 *u = 0; + stbi s; + stbi_gif g={0}; + start_mem(&s, buffer, len); + u = stbi_gif_load_next(&s, &g, comp, req_comp); + if (u == (void *) 1) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + } + return u; +} + +#ifndef STBI_NO_STDIO +int stbi_gif_info (char const *filename, int *x, int *y, int *comp) +{ + int res; + FILE *f = fopen(filename, "rb"); + if (!f) return 0; + res = stbi_gif_info_from_file(f, x, y, comp); + fclose(f); + return res; +} + +int stbi_gif_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + stbi s; + int res; + long n = ftell(f); + start_file(&s, f); + res = stbi_gif_info_raw(&s, x, y, comp); + fseek(f, n, SEEK_SET); + return res; +} +#endif // !STBI_NO_STDIO + +int stbi_gif_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi s; + start_mem(&s, buffer, len); + return stbi_gif_info_raw(&s, x, y, comp); +} + + + + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int hdr_test(stbi *s) +{ + const char *signature = "#?RADIANCE\n"; + int i; + for (i=0; signature[i]; ++i) + if (get8(s) != signature[i]) + return 0; + return 1; +} + +int stbi_hdr_test_memory(stbi_uc const *buffer, int len) +{ + stbi s; + start_mem(&s, buffer, len); + return hdr_test(&s); +} + +#ifndef STBI_NO_STDIO +int stbi_hdr_test_file(FILE *f) +{ + stbi s; + int r,n = ftell(f); + start_file(&s, f); + r = hdr_test(&s); + fseek(f,n,SEEK_SET); + return r; +} +#endif + +#define HDR_BUFLEN 1024 +static char *hdr_gettoken(stbi *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) get8(z); + + while (!at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == HDR_BUFLEN-1) { + // flush to end of line + while (!at_eof(z) && get8(z) != '\n') + ; + break; + } + c = (char) get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + + +static float *hdr_load(stbi *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + + + // Check identifier + if (strcmp(hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return epf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return epf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return epf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return epf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = strtol(token, NULL, 10); + + *x = width; + *y = height; + + *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) malloc(height * width * req_comp * sizeof(float)); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + getn(s, rgbe, 4); + hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = get8(s); + c2 = get8(s); + len = get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + uint8 rgbe[4]; + rgbe[0] = (uint8) c1; + rgbe[1] = (uint8) c2; + rgbe[2] = (uint8) len; + rgbe[3] = (uint8) get8u(s); + hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + free(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= get8(s); + if (len != width) { free(hdr_data); free(scanline); return epf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) malloc(width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = get8u(s); + if (count > 128) { + // Run + value = get8u(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = get8u(s); + } + } + } + for (i=0; i < width; ++i) + hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + free(scanline); + } + + return hdr_data; +} + +#ifndef STBI_NO_STDIO +float *stbi_hdr_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_file(&s,f); + return hdr_load(&s,x,y,comp,req_comp); +} +#endif + +float *stbi_hdr_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi s; + start_mem(&s,buffer, len); + return hdr_load(&s,x,y,comp,req_comp); +} + +#endif // STBI_NO_HDR + + +#ifndef STBI_NO_STDIO +int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = fopen(filename, "rb"); + int result; + if (!f) return e("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + if (stbi_jpeg_info_from_file(f, x, y, comp)) + return 1; + if (stbi_png_info_from_file(f, x, y, comp)) + return 1; + if (stbi_gif_info_from_file(f, x, y, comp)) + return 1; + // @TODO: stbi_bmp_info_from_file + // @TODO: stbi_psd_info_from_file + #ifndef STBI_NO_HDR + // @TODO: stbi_hdr_info_from_file + #endif + // test tga last because it's a crappy test! + if (stbi_tga_info_from_file(f, x, y, comp)) + return 1; + return e("unknown image type", "Image not of any known type, or corrupt"); +} +#endif // !STBI_NO_STDIO + +int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + if (stbi_jpeg_info_from_memory(buffer, len, x, y, comp)) + return 1; + if (stbi_png_info_from_memory(buffer, len, x, y, comp)) + return 1; + if (stbi_gif_info_from_memory(buffer, len, x, y, comp)) + return 1; + // @TODO: stbi_bmp_info_from_memory + // @TODO: stbi_psd_info_from_memory + #ifndef STBI_NO_HDR + // @TODO: stbi_hdr_info_from_memory + #endif + // test tga last because it's a crappy test! + if (stbi_tga_info_from_memory(buffer, len, x, y, comp)) + return 1; + return e("unknown image type", "Image not of any known type, or corrupt"); +} + +#endif // STBI_HEADER_FILE_ONLY + +/* + revision history: + 1.29 (2010-08-16) various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-uint8 to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.e. Janez (U+017D)emva) + 1.21 fix use of 'uint8' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 2008-08-02 + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi_bmp_load() and stbi_tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less + than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant +*/ diff --git a/thirdparty/thekla_atlas/extern/tinyobj/tiny_obj_loader.h b/thirdparty/thekla_atlas/extern/tinyobj/tiny_obj_loader.h new file mode 100755 index 00000000..2adb2bbb --- /dev/null +++ b/thirdparty/thekla_atlas/extern/tinyobj/tiny_obj_loader.h @@ -0,0 +1,1035 @@ +// +// Copyright 2012-2015, Syoyo Fujita. +// +// Licensed under 2-clause BSD liecense. +// + +// +// version 0.9.16: Make tinyobjloader header-only +// version 0.9.15: Change API to handle no mtl file case correctly(#58) +// version 0.9.14: Support specular highlight, bump, displacement and alpha map(#53) +// version 0.9.13: Report "Material file not found message" in `err`(#46) +// version 0.9.12: Fix groups being ignored if they have 'usemtl' just before 'g' (#44) +// version 0.9.11: Invert `Tr` parameter(#43) +// version 0.9.10: Fix seg fault on windows. +// version 0.9.9 : Replace atof() with custom parser. +// version 0.9.8 : Fix multi-materials(per-face material ID). +// version 0.9.7 : Support multi-materials(per-face material ID) per +// object/group. +// version 0.9.6 : Support Ni(index of refraction) mtl parameter. +// Parse transmittance material parameter correctly. +// version 0.9.5 : Parse multiple group name. +// Add support of specifying the base path to load material file. +// version 0.9.4 : Initial suupport of group tag(g) +// version 0.9.3 : Fix parsing triple 'x/y/z' +// version 0.9.2 : Add more .mtl load support +// version 0.9.1 : Add initial .mtl load support +// version 0.9.0 : Initial +// + +// +// Use this in *one* .cc +// #define TINYOBJLOADER_IMPLEMENTATION +// #include "tiny_obj_loader.h" +// + +#ifndef _TINY_OBJ_LOADER_H +#define _TINY_OBJ_LOADER_H + +#include +#include +#include + +namespace tinyobj { + +typedef struct { + std::string name; + + float ambient[3]; + float diffuse[3]; + float specular[3]; + float transmittance[3]; + float emission[3]; + float shininess; + float ior; // index of refraction + float dissolve; // 1 == opaque; 0 == fully transparent + // illumination model (see http://www.fileformat.info/format/material/) + int illum; + + std::string ambient_texname; // map_Ka + std::string diffuse_texname; // map_Kd + std::string specular_texname; // map_Ks + std::string specular_highlight_texname; // map_Ns + std::string bump_texname; // map_bump, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d + std::map unknown_parameter; +} material_t; + +typedef struct { + std::vector positions; + std::vector normals; + std::vector texcoords; + std::vector indices; + std::vector material_ids; // per-mesh material ID +} mesh_t; + +typedef struct { + std::string name; + mesh_t mesh; +} shape_t; + +class MaterialReader { +public: + MaterialReader() {} + virtual ~MaterialReader() {} + + virtual bool operator()(const std::string &matId, + std::vector &materials, + std::map &matMap, + std::string &err) = 0; +}; + +class MaterialFileReader : public MaterialReader { +public: + MaterialFileReader(const std::string &mtl_basepath) + : m_mtlBasePath(mtl_basepath) {} + virtual ~MaterialFileReader() {} + virtual bool operator()(const std::string &matId, + std::vector &materials, + std::map &matMap, + std::string &err); + +private: + std::string m_mtlBasePath; +}; + +/// Loads .obj from a file. +/// 'shapes' will be filled with parsed shape data +/// The function returns error string. +/// Returns true when loading .obj become success. +/// Returns warning and error message into `err` +/// 'mtl_basepath' is optional, and used for base path for .mtl file. +bool LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + std::string& err, // [output] + const char *filename, const char *mtl_basepath = NULL); + +/// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve +/// std::istream for materials. +/// Returns true when loading .obj become success. +/// Returns warning and error message into `err` +bool LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + std::string& err, // [output] + std::istream &inStream, MaterialReader &readMatFn); + +/// Loads materials into std::map +void LoadMtl(std::map &material_map, // [output] + std::vector &materials, // [output] + std::istream &inStream); +} + +#ifdef TINYOBJLOADER_IMPLEMENTATION +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "tiny_obj_loader.h" + +namespace tinyobj { + +#define TINYOBJ_SSCANF_BUFFER_SIZE (4096) + +struct vertex_index { + int v_idx, vt_idx, vn_idx; + vertex_index(){}; + vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx){}; + vertex_index(int vidx, int vtidx, int vnidx) + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx){}; +}; +// for std::map +static inline bool operator<(const vertex_index &a, const vertex_index &b) { + if (a.v_idx != b.v_idx) + return (a.v_idx < b.v_idx); + if (a.vn_idx != b.vn_idx) + return (a.vn_idx < b.vn_idx); + if (a.vt_idx != b.vt_idx) + return (a.vt_idx < b.vt_idx); + + return false; +} + +struct obj_shape { + std::vector v; + std::vector vn; + std::vector vt; +}; + +static inline bool isSpace(const char c) { return (c == ' ') || (c == '\t'); } + +static inline bool isNewLine(const char c) { + return (c == '\r') || (c == '\n') || (c == '\0'); +} + +// Make index zero-base, and also support relative index. +static inline int fixIndex(int idx, int n) { + if (idx > 0) return idx - 1; + if (idx == 0) return 0; + return n + idx; // negative value = relative +} + +static inline std::string parseString(const char *&token) { + std::string s; + token += strspn(token, " \t"); + size_t e = strcspn(token, " \t\r"); + s = std::string(token, &token[e]); + token += e; + return s; +} + +static inline int parseInt(const char *&token) { + token += strspn(token, " \t"); + int i = atoi(token); + token += strcspn(token, " \t\r"); + return i; +} + + +// Tries to parse a floating point number located at s. +// +// s_end should be a location in the string where reading should absolutely +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// Valid strings are for example: +// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 +// +// If the parsing is a success, result is set to the parsed value and true +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// The following situations triggers a failure: +// - s >= s_end. +// - parse failure. +// +static bool tryParseDouble(const char *s, const char *s_end, double *result) +{ + if (s >= s_end) + { + return false; + } + + double mantissa = 0.0; + // This exponent is base 2 rather than 10. + // However the exponent we parse is supposed to be one of ten, + // thus we must take care to convert the exponent/and or the + // mantissa to a * 2^E, where a is the mantissa and E is the + // exponent. + // To get the final double we will use ldexp, it requires the + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // How many characters were read in a loop. + int read = 0; + // Tells whether a loop terminated due to reaching s_end. + bool end_not_reached = false; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') + { + sign = *curr; + curr++; + } + else if (isdigit(*curr)) { /* Pass through. */ } + else + { + goto fail; + } + + // Read the integer part. + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; read++; + } + + // We must make sure we actually got something. + if (read == 0) + goto fail; + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) + goto assemble; + + // Read the decimal part. + if (*curr == '.') + { + curr++; + read = 1; + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + // NOTE: Don't use powf here, it will absolutely murder precision. + mantissa += static_cast(*curr - 0x30) * pow(10.0, -read); + read++; curr++; + } + } + else if (*curr == 'e' || *curr == 'E') {} + else + { + goto assemble; + } + + if (!end_not_reached) + goto assemble; + + // Read the exponent part. + if (*curr == 'e' || *curr == 'E') + { + curr++; + // Figure out if a sign is present and if it is. + if ((end_not_reached = (curr != s_end)) && (*curr == '+' || *curr == '-')) + { + exp_sign = *curr; + curr++; + } + else if (isdigit(*curr)) { /* Pass through. */ } + else + { + // Empty E is not allowed. + goto fail; + } + + read = 0; + while ((end_not_reached = (curr != s_end)) && isdigit(*curr)) + { + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; read++; + } + exponent *= (exp_sign == '+'? 1 : -1); + if (read == 0) + goto fail; + } + +assemble: + *result = (sign == '+'? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); + return true; +fail: + return false; +} +static inline float parseFloat(const char *&token) { + token += strspn(token, " \t"); +#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER + float f = (float)atof(token); + token += strcspn(token, " \t\r"); +#else + const char *end = token + strcspn(token, " \t\r"); + double val = 0.0; + tryParseDouble(token, end, &val); + float f = static_cast(val); + token = end; +#endif + return f; +} + + +static inline void parseFloat2(float &x, float &y, const char *&token) { + x = parseFloat(token); + y = parseFloat(token); +} + +static inline void parseFloat3(float &x, float &y, float &z, + const char *&token) { + x = parseFloat(token); + y = parseFloat(token); + z = parseFloat(token); +} + +// Parse triples: i, i/j/k, i//k, i/j +static vertex_index parseTriple(const char *&token, int vsize, int vnsize, + int vtsize) { + vertex_index vi(-1); + + vi.v_idx = fixIndex(atoi(token), vsize); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return vi; + } + token++; + + // i//k + if (token[0] == '/') { + token++; + vi.vn_idx = fixIndex(atoi(token), vnsize); + token += strcspn(token, "/ \t\r"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = fixIndex(atoi(token), vtsize); + token += strcspn(token, "/ \t\r"); + if (token[0] != '/') { + return vi; + } + + // i/j/k + token++; // skip '/' + vi.vn_idx = fixIndex(atoi(token), vnsize); + token += strcspn(token, "/ \t\r"); + return vi; +} + +static unsigned int +updateVertex(std::map &vertexCache, + std::vector &positions, std::vector &normals, + std::vector &texcoords, + const std::vector &in_positions, + const std::vector &in_normals, + const std::vector &in_texcoords, const vertex_index &i) { + const std::map::iterator it = vertexCache.find(i); + + if (it != vertexCache.end()) { + // found cache + return it->second; + } + + assert(in_positions.size() > (unsigned int)(3 * i.v_idx + 2)); + + positions.push_back(in_positions[3 * i.v_idx + 0]); + positions.push_back(in_positions[3 * i.v_idx + 1]); + positions.push_back(in_positions[3 * i.v_idx + 2]); + + if (i.vn_idx >= 0) { + normals.push_back(in_normals[3 * i.vn_idx + 0]); + normals.push_back(in_normals[3 * i.vn_idx + 1]); + normals.push_back(in_normals[3 * i.vn_idx + 2]); + } + + if (i.vt_idx >= 0) { + texcoords.push_back(in_texcoords[2 * i.vt_idx + 0]); + texcoords.push_back(in_texcoords[2 * i.vt_idx + 1]); + } + + unsigned int idx = static_cast(positions.size() / 3 - 1); + vertexCache[i] = idx; + + return idx; +} + +void InitMaterial(material_t &material) { + material.name = ""; + material.ambient_texname = ""; + material.diffuse_texname = ""; + material.specular_texname = ""; + material.specular_highlight_texname = ""; + material.bump_texname = ""; + material.displacement_texname = ""; + material.alpha_texname = ""; + for (int i = 0; i < 3; i++) { + material.ambient[i] = 0.f; + material.diffuse[i] = 0.f; + material.specular[i] = 0.f; + material.transmittance[i] = 0.f; + material.emission[i] = 0.f; + } + material.illum = 0; + material.dissolve = 1.f; + material.shininess = 1.f; + material.ior = 1.f; + material.unknown_parameter.clear(); +} + +static bool exportFaceGroupToShape( + shape_t &shape, std::map vertexCache, + const std::vector &in_positions, + const std::vector &in_normals, + const std::vector &in_texcoords, + const std::vector > &faceGroup, + const int material_id, const std::string &name, bool clearCache) { + if (faceGroup.empty()) { + return false; + } + + // Flatten vertices and indices + for (size_t i = 0; i < faceGroup.size(); i++) { + const std::vector &face = faceGroup[i]; + + vertex_index i0 = face[0]; + vertex_index i1(-1); + vertex_index i2 = face[1]; + + size_t npolys = face.size(); + + // Polygon -> triangle fan conversion + for (size_t k = 2; k < npolys; k++) { + i1 = i2; + i2 = face[k]; + + unsigned int v0 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i0); + unsigned int v1 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i1); + unsigned int v2 = updateVertex( + vertexCache, shape.mesh.positions, shape.mesh.normals, + shape.mesh.texcoords, in_positions, in_normals, in_texcoords, i2); + + shape.mesh.indices.push_back(v0); + shape.mesh.indices.push_back(v1); + shape.mesh.indices.push_back(v2); + + shape.mesh.material_ids.push_back(material_id); + } + } + + shape.name = name; + + if (clearCache) + vertexCache.clear(); + + return true; +} + +void LoadMtl(std::map &material_map, + std::vector &materials, + std::istream &inStream) { + + // Create a default material anyway. + material_t material; + InitMaterial(material); + + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (inStream.peek() != -1) { + inStream.getline(&buf[0], maxchars); + + std::string linebuf(&buf[0]); + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') + continue; // empty line + + if (token[0] == '#') + continue; // comment line + + // new mtl + if ((0 == strncmp(token, "newmtl", 6)) && isSpace((token[6]))) { + // flush previous material. + if (!material.name.empty()) { + material_map.insert( + std::pair(material.name, static_cast(materials.size()))); + materials.push_back(material); + } + + // initial temporary material + InitMaterial(material); + + // set new mtl name + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + material.name = namebuf; + continue; + } + + // ambient + if (token[0] == 'K' && token[1] == 'a' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.ambient[0] = r; + material.ambient[1] = g; + material.ambient[2] = b; + continue; + } + + // diffuse + if (token[0] == 'K' && token[1] == 'd' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + continue; + } + + // specular + if (token[0] == 'K' && token[1] == 's' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.specular[0] = r; + material.specular[1] = g; + material.specular[2] = b; + continue; + } + + // transmittance + if (token[0] == 'K' && token[1] == 't' && isSpace((token[2]))) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.transmittance[0] = r; + material.transmittance[1] = g; + material.transmittance[2] = b; + continue; + } + + // ior(index of refraction) + if (token[0] == 'N' && token[1] == 'i' && isSpace((token[2]))) { + token += 2; + material.ior = parseFloat(token); + continue; + } + + // emission + if (token[0] == 'K' && token[1] == 'e' && isSpace(token[2])) { + token += 2; + float r, g, b; + parseFloat3(r, g, b, token); + material.emission[0] = r; + material.emission[1] = g; + material.emission[2] = b; + continue; + } + + // shininess + if (token[0] == 'N' && token[1] == 's' && isSpace(token[2])) { + token += 2; + material.shininess = parseFloat(token); + continue; + } + + // illum model + if (0 == strncmp(token, "illum", 5) && isSpace(token[5])) { + token += 6; + material.illum = parseInt(token); + continue; + } + + // dissolve + if ((token[0] == 'd' && isSpace(token[1]))) { + token += 1; + material.dissolve = parseFloat(token); + continue; + } + if (token[0] == 'T' && token[1] == 'r' && isSpace(token[2])) { + token += 2; + // Invert value of Tr(assume Tr is in range [0, 1]) + material.dissolve = 1.0f - parseFloat(token); + continue; + } + + // ambient texture + if ((0 == strncmp(token, "map_Ka", 6)) && isSpace(token[6])) { + token += 7; + material.ambient_texname = token; + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && isSpace(token[6])) { + token += 7; + material.diffuse_texname = token; + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && isSpace(token[6])) { + token += 7; + material.specular_texname = token; + continue; + } + + // specular highlight texture + if ((0 == strncmp(token, "map_Ns", 6)) && isSpace(token[6])) { + token += 7; + material.specular_highlight_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_bump", 8)) && isSpace(token[8])) { + token += 9; + material.bump_texname = token; + continue; + } + + // alpha texture + if ((0 == strncmp(token, "map_d", 5)) && isSpace(token[5])) { + token += 6; + material.alpha_texname = token; + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && isSpace(token[4])) { + token += 5; + material.bump_texname = token; + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && isSpace(token[4])) { + token += 5; + material.displacement_texname = token; + continue; + } + + // unknown parameter + const char *_space = strchr(token, ' '); + if (!_space) { + _space = strchr(token, '\t'); + } + if (_space) { + std::ptrdiff_t len = _space - token; + std::string key(token, len); + std::string value = _space + 1; + material.unknown_parameter.insert( + std::pair(key, value)); + } + } + // flush last material. + material_map.insert( + std::pair(material.name, static_cast(materials.size()))); + materials.push_back(material); +} + +bool MaterialFileReader::operator()(const std::string &matId, + std::vector &materials, + std::map &matMap, + std::string& err) { + std::string filepath; + + if (!m_mtlBasePath.empty()) { + filepath = std::string(m_mtlBasePath) + matId; + } else { + filepath = matId; + } + + std::ifstream matIStream(filepath.c_str()); + LoadMtl(matMap, materials, matIStream); + if (!matIStream) { + std::stringstream ss; + ss << "WARN: Material file [ " << filepath << " ] not found. Created a default material."; + err += ss.str(); + } + return true; +} + +bool LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + std::string &err, + const char *filename, const char *mtl_basepath) { + + shapes.clear(); + + std::stringstream errss; + + std::ifstream ifs(filename); + if (!ifs) { + errss << "Cannot open file [" << filename << "]" << std::endl; + err = errss.str(); + return false; + } + + std::string basePath; + if (mtl_basepath) { + basePath = mtl_basepath; + } + MaterialFileReader matFileReader(basePath); + + return LoadObj(shapes, materials, err, ifs, matFileReader); +} + +bool LoadObj(std::vector &shapes, // [output] + std::vector &materials, // [output] + std::string& err, + std::istream &inStream, MaterialReader &readMatFn) { + std::stringstream errss; + + std::vector v; + std::vector vn; + std::vector vt; + std::vector > faceGroup; + std::string name; + + // material + std::map material_map; + std::map vertexCache; + int material = -1; + + shape_t shape; + + int maxchars = 8192; // Alloc enough size. + std::vector buf(maxchars); // Alloc enough size. + while (inStream.peek() != -1) { + inStream.getline(&buf[0], maxchars); + + std::string linebuf(&buf[0]); + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') + continue; // empty line + + if (token[0] == '#') + continue; // comment line + + // vertex + if (token[0] == 'v' && isSpace((token[1]))) { + token += 2; + float x, y, z; + parseFloat3(x, y, z, token); + v.push_back(x); + v.push_back(y); + v.push_back(z); + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && isSpace((token[2]))) { + token += 3; + float x, y, z; + parseFloat3(x, y, z, token); + vn.push_back(x); + vn.push_back(y); + vn.push_back(z); + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && isSpace((token[2]))) { + token += 3; + float x, y; + parseFloat2(x, y, token); + vt.push_back(x); + vt.push_back(y); + continue; + } + + // face + if (token[0] == 'f' && isSpace((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + std::vector face; + while (!isNewLine(token[0])) { + vertex_index vi = + parseTriple(token, static_cast(v.size() / 3), static_cast(vn.size() / 3), static_cast(vt.size() / 2)); + face.push_back(vi); + size_t n = strspn(token, " \t\r"); + token += n; + } + + faceGroup.push_back(face); + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && isSpace((token[6]))) { + + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + // Create face group per material. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + shapes.push_back(shape); + } + shape = shape_t(); + faceGroup.clear(); + + if (material_map.find(namebuf) != material_map.end()) { + material = material_map[namebuf]; + } else { + // { error!! material not found } + material = -1; + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && isSpace((token[6]))) { + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + + std::string err_mtl; + bool ok = readMatFn(namebuf, materials, material_map, err_mtl); + err += err_mtl; + + if (!ok) { + faceGroup.clear(); // for safety + return false; + } + + continue; + } + + // group name + if (token[0] == 'g' && isSpace((token[1]))) { + + // flush previous face group. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + shapes.push_back(shape); + } + + shape = shape_t(); + + // material = -1; + faceGroup.clear(); + + std::vector names; + while (!isNewLine(token[0])) { + std::string str = parseString(token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + assert(names.size() > 0); + + // names[0] must be 'g', so skip the 0th element. + if (names.size() > 1) { + name = names[1]; + } else { + name = ""; + } + + continue; + } + + // object name + if (token[0] == 'o' && isSpace((token[1]))) { + + // flush previous face group. + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, + faceGroup, material, name, true); + if (ret) { + shapes.push_back(shape); + } + + // material = -1; + faceGroup.clear(); + shape = shape_t(); + + // @todo { multiple object name? } + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + sscanf(token, "%s", namebuf); +#endif + name = std::string(namebuf); + + continue; + } + + // Ignore unknown command. + } + + bool ret = exportFaceGroupToShape(shape, vertexCache, v, vn, vt, faceGroup, + material, name, true); + if (ret) { + shapes.push_back(shape); + } + faceGroup.clear(); // for safety + + err += errss.str(); + return true; +} + +} // namespace + + +#endif + +#endif // _TINY_OBJ_LOADER_H \ No newline at end of file diff --git a/thirdparty/thekla_atlas/projects/vc9/nvcore/nvcore.vcproj b/thirdparty/thekla_atlas/projects/vc9/nvcore/nvcore.vcproj new file mode 100755 index 00000000..14f06972 --- /dev/null +++ b/thirdparty/thekla_atlas/projects/vc9/nvcore/nvcore.vcproj @@ -0,0 +1,431 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/thekla_atlas/projects/vc9/nvimage/nvimage.vcproj b/thirdparty/thekla_atlas/projects/vc9/nvimage/nvimage.vcproj new file mode 100755 index 00000000..f1510863 --- /dev/null +++ b/thirdparty/thekla_atlas/projects/vc9/nvimage/nvimage.vcproj @@ -0,0 +1,311 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/thekla_atlas/projects/vc9/nvmath/nvmath.vcproj b/thirdparty/thekla_atlas/projects/vc9/nvmath/nvmath.vcproj new file mode 100755 index 00000000..bf2d7186 --- /dev/null +++ b/thirdparty/thekla_atlas/projects/vc9/nvmath/nvmath.vcproj @@ -0,0 +1,448 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/thekla_atlas/projects/vc9/nvmesh/nvmesh.vcproj b/thirdparty/thekla_atlas/projects/vc9/nvmesh/nvmesh.vcproj new file mode 100755 index 00000000..947cdff2 --- /dev/null +++ b/thirdparty/thekla_atlas/projects/vc9/nvmesh/nvmesh.vcproj @@ -0,0 +1,626 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/thekla_atlas/projects/vc9/thekla.sln b/thirdparty/thekla_atlas/projects/vc9/thekla.sln new file mode 100755 index 00000000..5e73494d --- /dev/null +++ b/thirdparty/thekla_atlas/projects/vc9/thekla.sln @@ -0,0 +1,79 @@ +ļ»æ +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "thekla_atlas", "thekla_atlas\thekla_atlas.vcproj", "{EF943121-21F5-44B6-A8B5-C628F95CE070}" + ProjectSection(ProjectDependencies) = postProject + {42FA3213-9A92-455E-8F39-5500B2303CCD} = {42FA3213-9A92-455E-8F39-5500B2303CCD} + {3943D773-05FE-43E7-A122-F5E534F73D33} = {3943D773-05FE-43E7-A122-F5E534F73D33} + {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B} = {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B} + {AACBE57F-FF9D-4D15-86DD-A30232EB9102} = {AACBE57F-FF9D-4D15-86DD-A30232EB9102} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nv", "nv", "{382CF634-AF73-4943-83D6-C1D5BA499CC2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nvcore", "nvcore\nvcore.vcproj", "{42FA3213-9A92-455E-8F39-5500B2303CCD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nvmath", "nvmath\nvmath.vcproj", "{D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nvmesh", "nvmesh\nvmesh.vcproj", "{3943D773-05FE-43E7-A122-F5E534F73D33}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nvimage", "nvimage\nvimage.vcproj", "{AACBE57F-FF9D-4D15-86DD-A30232EB9102}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EF943121-21F5-44B6-A8B5-C628F95CE070}.Debug|Win32.ActiveCfg = Debug|Win32 + {EF943121-21F5-44B6-A8B5-C628F95CE070}.Debug|Win32.Build.0 = Debug|Win32 + {EF943121-21F5-44B6-A8B5-C628F95CE070}.Debug|x64.ActiveCfg = Debug|x64 + {EF943121-21F5-44B6-A8B5-C628F95CE070}.Release|Win32.ActiveCfg = Release|Win32 + {EF943121-21F5-44B6-A8B5-C628F95CE070}.Release|Win32.Build.0 = Release|Win32 + {EF943121-21F5-44B6-A8B5-C628F95CE070}.Release|x64.ActiveCfg = Release|x64 + {EF943121-21F5-44B6-A8B5-C628F95CE070}.Release|x64.Build.0 = Release|x64 + {42FA3213-9A92-455E-8F39-5500B2303CCD}.Debug|Win32.ActiveCfg = Debug|Win32 + {42FA3213-9A92-455E-8F39-5500B2303CCD}.Debug|Win32.Build.0 = Debug|Win32 + {42FA3213-9A92-455E-8F39-5500B2303CCD}.Debug|x64.ActiveCfg = Debug|x64 + {42FA3213-9A92-455E-8F39-5500B2303CCD}.Debug|x64.Build.0 = Debug|x64 + {42FA3213-9A92-455E-8F39-5500B2303CCD}.Release|Win32.ActiveCfg = Release|Win32 + {42FA3213-9A92-455E-8F39-5500B2303CCD}.Release|Win32.Build.0 = Release|Win32 + {42FA3213-9A92-455E-8F39-5500B2303CCD}.Release|x64.ActiveCfg = Release|x64 + {42FA3213-9A92-455E-8F39-5500B2303CCD}.Release|x64.Build.0 = Release|x64 + {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B}.Debug|Win32.ActiveCfg = Debug|Win32 + {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B}.Debug|Win32.Build.0 = Debug|Win32 + {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B}.Debug|x64.ActiveCfg = Debug|x64 + {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B}.Debug|x64.Build.0 = Debug|x64 + {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B}.Release|Win32.ActiveCfg = Release|Win32 + {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B}.Release|Win32.Build.0 = Release|Win32 + {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B}.Release|x64.ActiveCfg = Release|x64 + {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B}.Release|x64.Build.0 = Release|x64 + {3943D773-05FE-43E7-A122-F5E534F73D33}.Debug|Win32.ActiveCfg = Debug|Win32 + {3943D773-05FE-43E7-A122-F5E534F73D33}.Debug|Win32.Build.0 = Debug|Win32 + {3943D773-05FE-43E7-A122-F5E534F73D33}.Debug|x64.ActiveCfg = Debug|x64 + {3943D773-05FE-43E7-A122-F5E534F73D33}.Debug|x64.Build.0 = Debug|x64 + {3943D773-05FE-43E7-A122-F5E534F73D33}.Release|Win32.ActiveCfg = Release|Win32 + {3943D773-05FE-43E7-A122-F5E534F73D33}.Release|Win32.Build.0 = Release|Win32 + {3943D773-05FE-43E7-A122-F5E534F73D33}.Release|x64.ActiveCfg = Release|x64 + {3943D773-05FE-43E7-A122-F5E534F73D33}.Release|x64.Build.0 = Release|x64 + {AACBE57F-FF9D-4D15-86DD-A30232EB9102}.Debug|Win32.ActiveCfg = Debug|Win32 + {AACBE57F-FF9D-4D15-86DD-A30232EB9102}.Debug|Win32.Build.0 = Debug|Win32 + {AACBE57F-FF9D-4D15-86DD-A30232EB9102}.Debug|x64.ActiveCfg = Debug|x64 + {AACBE57F-FF9D-4D15-86DD-A30232EB9102}.Debug|x64.Build.0 = Debug|x64 + {AACBE57F-FF9D-4D15-86DD-A30232EB9102}.Release|Win32.ActiveCfg = Release|Win32 + {AACBE57F-FF9D-4D15-86DD-A30232EB9102}.Release|Win32.Build.0 = Release|Win32 + {AACBE57F-FF9D-4D15-86DD-A30232EB9102}.Release|x64.ActiveCfg = Release|x64 + {AACBE57F-FF9D-4D15-86DD-A30232EB9102}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {42FA3213-9A92-455E-8F39-5500B2303CCD} = {382CF634-AF73-4943-83D6-C1D5BA499CC2} + {D8B7F575-E8F5-4F1A-A4A4-EB9CC9A0766B} = {382CF634-AF73-4943-83D6-C1D5BA499CC2} + {3943D773-05FE-43E7-A122-F5E534F73D33} = {382CF634-AF73-4943-83D6-C1D5BA499CC2} + {AACBE57F-FF9D-4D15-86DD-A30232EB9102} = {382CF634-AF73-4943-83D6-C1D5BA499CC2} + EndGlobalSection +EndGlobal diff --git a/thirdparty/thekla_atlas/projects/vc9/thekla_atlas/thekla_atlas.vcproj b/thirdparty/thekla_atlas/projects/vc9/thekla_atlas/thekla_atlas.vcproj new file mode 100755 index 00000000..326e7129 --- /dev/null +++ b/thirdparty/thekla_atlas/projects/vc9/thekla_atlas/thekla_atlas.vcproj @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/thekla_atlas/src/nvconfig.h b/thirdparty/thekla_atlas/src/nvconfig.h new file mode 100755 index 00000000..815bc3ec --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvconfig.h @@ -0,0 +1,37 @@ +#ifndef NV_CONFIG +#define NV_CONFIG + +#if NV_OS_DARWIN + +// Hardcoded. + +#define NV_HAVE_UNISTD_H +#define NV_HAVE_STDARG_H +#define NV_HAVE_SIGNAL_H +#define NV_HAVE_EXECINFO_H +//#define NV_HAVE_MALLOC_H + +#else + +//#define HAVE_UNISTD_H +#define NV_HAVE_STDARG_H +//#define HAVE_SIGNAL_H +//#define HAVE_EXECINFO_H +//#define HAVE_MALLOC_H + +#endif + +//#define HAVE_OPENMP // Only in MSVC pro edition. + +//#cmakedefine HAVE_PNG +//#cmakedefine HAVE_JPEG +//#cmakedefine HAVE_TIFF +//#cmakedefine HAVE_OPENEXR +//#cmakedefine HAVE_FREEIMAGE +#if !NV_OS_IOS +#define NV_HAVE_STBIMAGE +#endif + +//#cmakedefine HAVE_MAYA + +#endif // NV_CONFIG diff --git a/thirdparty/thekla_atlas/src/nvconfig.h.in b/thirdparty/thekla_atlas/src/nvconfig.h.in new file mode 100755 index 00000000..5558fca5 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvconfig.h.in @@ -0,0 +1,22 @@ +#ifndef NV_CONFIG +#define NV_CONFIG + +#cmakedefine NV_HAVE_UNISTD_H +#cmakedefine NV_HAVE_STDARG_H +#cmakedefine NV_HAVE_SIGNAL_H +#cmakedefine NV_HAVE_EXECINFO_H +#cmakedefine NV_HAVE_MALLOC_H + +#cmakedefine NV_HAVE_OPENMP +#cmakedefine NV_HAVE_DISPATCH_H + +#define NV_HAVE_STBIMAGE +//#cmakedefine NV_HAVE_PNG +//#cmakedefine NV_HAVE_JPEG +//#cmakedefine NV_HAVE_TIFF +//#cmakedefine NV_HAVE_OPENEXR +//#cmakedefine NV_HAVE_FREEIMAGE + +#cmakedefine NV_HAVE_MAYA + +#endif // NV_CONFIG diff --git a/thirdparty/thekla_atlas/src/nvcore/Array.h b/thirdparty/thekla_atlas/src/nvcore/Array.h new file mode 100755 index 00000000..d3fab26a --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/Array.h @@ -0,0 +1,184 @@ +// This code is in the public domain -- Ignacio CastaƱo + +#pragma once +#ifndef NV_CORE_ARRAY_H +#define NV_CORE_ARRAY_H + +/* +This array class requires the elements to be relocable; it uses memmove and realloc. Ideally I should be +using swap, but I honestly don't care. The only thing that you should be aware of is that internal pointers +are not supported. + +Note also that push_back and resize does not support inserting arguments elements that are in the same +container. This is forbidden to prevent an extra copy. +*/ + + +#include "Memory.h" +#include "Debug.h" +#include "ForEach.h" // PseudoIndex + + +namespace nv +{ + class Stream; + + /** + * Replacement for std::vector that is easier to debug and provides + * some nice foreach enumerators. + */ + template + class NVCORE_CLASS Array { + public: + typedef uint size_type; + + // Default constructor. + NV_FORCEINLINE Array() : m_buffer(NULL), m_capacity(0), m_size(0) {} + + // Copy constructor. + NV_FORCEINLINE Array(const Array & a) : m_buffer(NULL), m_capacity(0), m_size(0) { + copy(a.m_buffer, a.m_size); + } + + // Constructor that initializes the vector with the given elements. + NV_FORCEINLINE Array(const T * ptr, uint num) : m_buffer(NULL), m_capacity(0), m_size(0) { + copy(ptr, num); + } + + // Allocate array. + NV_FORCEINLINE explicit Array(uint capacity) : m_buffer(NULL), m_capacity(0), m_size(0) { + setArrayCapacity(capacity); + } + + // Destructor. + NV_FORCEINLINE ~Array() { + clear(); + free(m_buffer); + } + + + /// Const element access. + NV_FORCEINLINE const T & operator[]( uint index ) const + { + nvDebugCheck(index < m_size); + return m_buffer[index]; + } + NV_FORCEINLINE const T & at( uint index ) const + { + nvDebugCheck(index < m_size); + return m_buffer[index]; + } + + /// Element access. + NV_FORCEINLINE T & operator[] ( uint index ) + { + nvDebugCheck(index < m_size); + return m_buffer[index]; + } + NV_FORCEINLINE T & at( uint index ) + { + nvDebugCheck(index < m_size); + return m_buffer[index]; + } + + /// Get vector size. + NV_FORCEINLINE uint size() const { return m_size; } + + /// Get vector size. + NV_FORCEINLINE uint count() const { return m_size; } + + /// Get vector capacity. + NV_FORCEINLINE uint capacity() const { return m_capacity; } + + /// Get const vector pointer. + NV_FORCEINLINE const T * buffer() const { return m_buffer; } + + /// Get vector pointer. + NV_FORCEINLINE T * buffer() { return m_buffer; } + + /// Provide begin/end pointers for C++11 range-based for loops. + NV_FORCEINLINE T * begin() { return m_buffer; } + NV_FORCEINLINE T * end() { return m_buffer + m_size; } + NV_FORCEINLINE const T * begin() const { return m_buffer; } + NV_FORCEINLINE const T * end() const { return m_buffer + m_size; } + + /// Is vector empty. + NV_FORCEINLINE bool isEmpty() const { return m_size == 0; } + + /// Is a null vector. + NV_FORCEINLINE bool isNull() const { return m_buffer == NULL; } + + + T & append(); + void push_back( const T & val ); + void pushBack( const T & val ); + Array & append( const T & val ); + Array & operator<< ( T & t ); + void pop_back(); + void popBack(uint count = 1); + void popFront(uint count = 1); + const T & back() const; + T & back(); + const T & front() const; + T & front(); + bool contains(const T & e) const; + bool find(const T & element, uint * indexPtr) const; + bool find(const T & element, uint begin, uint end, uint * indexPtr) const; + void removeAt(uint index); + bool remove(const T & element); + void insertAt(uint index, const T & val = T()); + void append(const Array & other); + void append(const T other[], uint count); + void replaceWithLast(uint index); + void resize(uint new_size); + void resize(uint new_size, const T & elem); + void fill(const T & elem); + void clear(); + void shrink(); + void reserve(uint desired_size); + void copy(const T * data, uint count); + Array & operator=( const Array & a ); + T * release(); + + + // Array enumerator. + typedef uint PseudoIndex; + + NV_FORCEINLINE PseudoIndex start() const { return 0; } + NV_FORCEINLINE bool isDone(const PseudoIndex & i) const { nvDebugCheck(i <= this->m_size); return i == this->m_size; } + NV_FORCEINLINE void advance(PseudoIndex & i) const { nvDebugCheck(i <= this->m_size); i++; } + +#if NV_NEED_PSEUDOINDEX_WRAPPER + NV_FORCEINLINE T & operator[]( const PseudoIndexWrapper & i ) { + return m_buffer[i(this)]; + } + NV_FORCEINLINE const T & operator[]( const PseudoIndexWrapper & i ) const { + return m_buffer[i(this)]; + } +#endif + + // Friends. + template + friend Stream & operator<< ( Stream & s, Array & p ); + + template + friend void swap(Array & a, Array & b); + + + protected: + + void setArraySize(uint new_size); + void setArrayCapacity(uint new_capacity); + + T * m_buffer; + uint m_capacity; + uint m_size; + + }; + + +} // nv namespace + +#include "Array.inl" + +#endif // NV_CORE_ARRAY_H diff --git a/thirdparty/thekla_atlas/src/nvcore/Array.inl b/thirdparty/thekla_atlas/src/nvcore/Array.inl new file mode 100755 index 00000000..0b4de28b --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/Array.inl @@ -0,0 +1,452 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_CORE_ARRAY_INL +#define NV_CORE_ARRAY_INL + +#include "Array.h" + +#include "Stream.h" +#include "Utils.h" // swap + +#include // memmove +#include // for placement new + + + +namespace nv +{ + template + NV_FORCEINLINE T & Array::append() + { + uint old_size = m_size; + uint new_size = m_size + 1; + + setArraySize(new_size); + + construct_range(m_buffer, new_size, old_size); + + return m_buffer[old_size]; // Return reference to last element. + } + + // Push an element at the end of the vector. + template + NV_FORCEINLINE void Array::push_back( const T & val ) + { +#if 1 + nvDebugCheck(&val < m_buffer || &val >= m_buffer+m_size); + + uint old_size = m_size; + uint new_size = m_size + 1; + + setArraySize(new_size); + + construct_range(m_buffer, new_size, old_size, val); +#else + uint new_size = m_size + 1; + + if (new_size > m_capacity) + { + // @@ Is there any way to avoid this copy? + // @@ Can we create a copy without side effects? Ie. without calls to constructor/destructor. Use alloca + memcpy? + // @@ Assert instead of copy? + const T copy(val); // create a copy in case value is inside of this array. + + setArraySize(new_size); + + new (m_buffer+new_size-1) T(copy); + } + else + { + m_size = new_size; + new(m_buffer+new_size-1) T(val); + } +#endif // 0/1 + } + template + NV_FORCEINLINE void Array::pushBack( const T & val ) + { + push_back(val); + } + template + NV_FORCEINLINE Array & Array::append( const T & val ) + { + push_back(val); + return *this; + } + + // Qt like push operator. + template + NV_FORCEINLINE Array & Array::operator<< ( T & t ) + { + push_back(t); + return *this; + } + + // Pop the element at the end of the vector. + template + NV_FORCEINLINE void Array::pop_back() + { + nvDebugCheck( m_size > 0 ); + resize( m_size - 1 ); + } + template + NV_FORCEINLINE void Array::popBack(uint count) + { + nvDebugCheck(m_size >= count); + resize(m_size - count); + } + + template + NV_FORCEINLINE void Array::popFront(uint count) + { + nvDebugCheck(m_size >= count); + //resize(m_size - count); + + if (m_size == count) { + clear(); + } + else { + destroy_range(m_buffer, 0, count); + + memmove(m_buffer, m_buffer + count, sizeof(T) * (m_size - count)); + + m_size -= count; + } + + } + + + // Get back element. + template + NV_FORCEINLINE const T & Array::back() const + { + nvDebugCheck( m_size > 0 ); + return m_buffer[m_size-1]; + } + + // Get back element. + template + NV_FORCEINLINE T & Array::back() + { + nvDebugCheck( m_size > 0 ); + return m_buffer[m_size-1]; + } + + // Get front element. + template + NV_FORCEINLINE const T & Array::front() const + { + nvDebugCheck( m_size > 0 ); + return m_buffer[0]; + } + + // Get front element. + template + NV_FORCEINLINE T & Array::front() + { + nvDebugCheck( m_size > 0 ); + return m_buffer[0]; + } + + // Check if the given element is contained in the array. + template + NV_FORCEINLINE bool Array::contains(const T & e) const + { + return find(e, NULL); + } + + // Return true if element found. + template + NV_FORCEINLINE bool Array::find(const T & element, uint * indexPtr) const + { + return find(element, 0, m_size, indexPtr); + } + + // Return true if element found within the given range. + template + NV_FORCEINLINE bool Array::find(const T & element, uint begin, uint end, uint * indexPtr) const + { + return ::nv::find(element, m_buffer, begin, end, indexPtr); + } + + + // Remove the element at the given index. This is an expensive operation! + template + void Array::removeAt(uint index) + { + nvDebugCheck(index >= 0 && index < m_size); + + if (m_size == 1) { + clear(); + } + else { + m_buffer[index].~T(); + + memmove(m_buffer+index, m_buffer+index+1, sizeof(T) * (m_size - 1 - index)); + m_size--; + } + } + + // Remove the first instance of the given element. + template + bool Array::remove(const T & element) + { + uint index; + if (find(element, &index)) { + removeAt(index); + return true; + } + return false; + } + + // Insert the given element at the given index shifting all the elements up. + template + void Array::insertAt(uint index, const T & val/*=T()*/) + { + nvDebugCheck( index >= 0 && index <= m_size ); + + setArraySize(m_size + 1); + + if (index < m_size - 1) { + memmove(m_buffer+index+1, m_buffer+index, sizeof(T) * (m_size - 1 - index)); + } + + // Copy-construct into the newly opened slot. + new(m_buffer+index) T(val); + } + + // Append the given data to our vector. + template + NV_FORCEINLINE void Array::append(const Array & other) + { + append(other.m_buffer, other.m_size); + } + + // Append the given data to our vector. + template + void Array::append(const T other[], uint count) + { + if (count > 0) { + const uint old_size = m_size; + + setArraySize(m_size + count); + + for (uint i = 0; i < count; i++ ) { + new(m_buffer + old_size + i) T(other[i]); + } + } + } + + + // Remove the given element by replacing it with the last one. + template + void Array::replaceWithLast(uint index) + { + nvDebugCheck( index < m_size ); + nv::swap(m_buffer[index], back()); // @@ Is this OK when index == size-1? + (m_buffer+m_size-1)->~T(); + m_size--; + } + + // Resize the vector preserving existing elements. + template + void Array::resize(uint new_size) + { + uint old_size = m_size; + + // Destruct old elements (if we're shrinking). + destroy_range(m_buffer, new_size, old_size); + + setArraySize(new_size); + + // Call default constructors + construct_range(m_buffer, new_size, old_size); + } + + + // Resize the vector preserving existing elements and initializing the + // new ones with the given value. + template + void Array::resize(uint new_size, const T & elem) + { + nvDebugCheck(&elem < m_buffer || &elem > m_buffer+m_size); + + uint old_size = m_size; + + // Destruct old elements (if we're shrinking). + destroy_range(m_buffer, new_size, old_size); + + setArraySize(new_size); + + // Call copy constructors + construct_range(m_buffer, new_size, old_size, elem); + } + + // Fill array with the given value. + template + void Array::fill(const T & elem) + { + fill(m_buffer, m_size, elem); + } + + // Clear the buffer. + template + NV_FORCEINLINE void Array::clear() + { + nvDebugCheck(isValidPtr(m_buffer)); + + // Destruct old elements + destroy_range(m_buffer, 0, m_size); + + m_size = 0; + } + + // Shrink the allocated vector. + template + NV_FORCEINLINE void Array::shrink() + { + if (m_size < m_capacity) { + setArrayCapacity(m_size); + } + } + + // Preallocate space. + template + NV_FORCEINLINE void Array::reserve(uint desired_size) + { + if (desired_size > m_capacity) { + setArrayCapacity(desired_size); + } + } + + // Copy elements to this array. Resizes it if needed. + template + NV_FORCEINLINE void Array::copy(const T * data, uint count) + { +#if 1 // More simple, but maybe not be as efficient? + destroy_range(m_buffer, 0, m_size); + + setArraySize(count); + + construct_range(m_buffer, count, 0, data); +#else + const uint old_size = m_size; + + destroy_range(m_buffer, count, old_size); + + setArraySize(count); + + copy_range(m_buffer, data, old_size); + + construct_range(m_buffer, count, old_size, data); +#endif + } + + // Assignment operator. + template + NV_FORCEINLINE Array & Array::operator=( const Array & a ) + { + copy(a.m_buffer, a.m_size); + return *this; + } + + // Release ownership of allocated memory and returns pointer to it. + template + T * Array::release() { + T * tmp = m_buffer; + m_buffer = NULL; + m_capacity = 0; + m_size = 0; + return tmp; + } + + + + // Change array size. + template + inline void Array::setArraySize(uint new_size) { + m_size = new_size; + + if (new_size > m_capacity) { + uint new_buffer_size; + if (m_capacity == 0) { + // first allocation is exact + new_buffer_size = new_size; + } + else { + // following allocations grow array by 25% + new_buffer_size = new_size + (new_size >> 2); + } + + setArrayCapacity( new_buffer_size ); + } + } + + // Change array capacity. + template + inline void Array::setArrayCapacity(uint new_capacity) { + nvDebugCheck(new_capacity >= m_size); + + if (new_capacity == 0) { + // free the buffer. + if (m_buffer != NULL) { + free(m_buffer); + m_buffer = NULL; + } + } + else { + // realloc the buffer + m_buffer = realloc(m_buffer, new_capacity); + } + + m_capacity = new_capacity; + } + + // Array serialization. + template + inline Stream & operator<< ( Stream & s, Array & p ) + { + if (s.isLoading()) { + uint size; + s << size; + p.resize( size ); + } + else { + s << p.m_size; + } + + for (uint i = 0; i < p.m_size; i++) { + s << p.m_buffer[i]; + } + + return s; + } + + // Swap the members of the two given vectors. + template + inline void swap(Array & a, Array & b) + { + nv::swap(a.m_buffer, b.m_buffer); + nv::swap(a.m_capacity, b.m_capacity); + nv::swap(a.m_size, b.m_size); + } + + +} // nv namespace + +// IC: These functions are for compatibility with the Foreach macro in The Witness. +template inline int item_count(const nv::Array & array) { return array.count(); } +template inline const T & item_at(const nv::Array & array, int i) { return array.at(i); } +template inline T & item_at(nv::Array & array, int i) { return array.at(i); } +template inline int item_advance(const nv::Array & array, int i) { return ++i; } +template inline int item_remove(nv::Array & array, int i) { array.replaceWithLast(i); return i - 1; } + +template inline int item_count(const nv::Array * array) { return array->count(); } +template inline const T & item_at(const nv::Array * array, int i) { return array->at(i); } +template inline T & item_at(nv::Array * array, int i) { return array->at(i); } +template inline int item_advance(const nv::Array * array, int i) { return ++i; } +template inline int item_remove(nv::Array * array, int i) { array->replaceWithLast(i); return i - 1; } + + +#endif // NV_CORE_ARRAY_INL diff --git a/thirdparty/thekla_atlas/src/nvcore/BitArray.h b/thirdparty/thekla_atlas/src/nvcore/BitArray.h new file mode 100755 index 00000000..23cf8806 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/BitArray.h @@ -0,0 +1,250 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_CORE_BITARRAY_H +#define NV_CORE_BITARRAY_H + +#include "nvcore.h" +#include "Array.inl" + +namespace nv +{ + + // @@ Uh, this could be much faster. + inline uint countSetBits(uint32 x) { + uint count = 0; + for(; x != 0; x >>= 1) { + count += (x & 1); + } + return count; + } + + // @@ This is even more lame. What was I thinking? + inline uint countSetBits(uint32 x, int bits) { + uint count = 0; + for(; x != 0 && bits != 0; x >>= 1, bits--) { + count += (x & 1); + } + return count; + } + + // See "Conditionally set or clear bits without branching" at http://graphics.stanford.edu/~seander/bithacks.html + inline uint setBits(uint w, uint m, bool b) { + return (w & ~m) | (-int(b) & m); + } + + + + // Simple bit array. + class BitArray + { + public: + + BitArray() {} + BitArray(uint sz) { + resize(sz); + } + + uint size() const { return m_size; } + void clear() { resize(0); } + + void resize(uint new_size) + { + m_size = new_size; + m_wordArray.resize( (m_size + 31) >> 5 ); + } + + void resize(uint new_size, bool init) + { + //if (new_size == m_size) return; + + uint old_size = m_size; + uint size_mod_32 = old_size & 31; + uint last_word_index = ((old_size + 31) >> 5) - 1; + uint mask = (1 << size_mod_32) - 1; + + uint init_dword; + if (init) { + if (size_mod_32) m_wordArray[last_word_index] |= ~mask; + init_dword = ~0; + } + else { + if (size_mod_32) m_wordArray[last_word_index] &= mask; + init_dword = 0; + } + + m_size = new_size; + m_wordArray.resize((new_size + 31) >> 5, init_dword); + + // Make sure new bits are initialized correctly. + /*for (uint i = old_size; i < new_size; i++) { + nvCheck(bitAt(i) == init); + }*/ + } + + + /// Get bit. + bool bitAt(uint b) const + { + nvDebugCheck( b < m_size ); + return (m_wordArray[b >> 5] & (1 << (b & 31))) != 0; + } + + // It may be useful to pack mulitple bit arrays together interleaving their bits. + uint bitsAt(uint idx, uint count) const + { + //nvDebugCheck(count == 2 || count == 4 || count == 8 || count == 16 || count == 32); + nvDebugCheck(count == 2); // @@ Hardcoded for two. + uint b = idx * count; + nvDebugCheck(b < m_size); + return (m_wordArray[b >> 5] & (0x3 << (b & 31))) >> (b & 31); + } + + // It would be useful to have a function to set two bits simultaneously. + /*void setBitsAt(uint idx, uint count, uint bits) const + { + //nvDebugCheck(count == 2 || count == 4 || count == 8 || count == 16 || count == 32); + nvDebugCheck(count == 2); // @@ Hardcoded for two. + uint b = idx * count; + nvDebugCheck(b < m_size); + return (m_wordArray[b >> 5] & (0x3 << (b & 31))) >> (b & 31); + }*/ + + + + // Set a bit. + void setBitAt(uint idx) + { + nvDebugCheck(idx < m_size); + m_wordArray[idx >> 5] |= (1 << (idx & 31)); + } + + // Clear a bit. + void clearBitAt(uint idx) + { + nvDebugCheck(idx < m_size); + m_wordArray[idx >> 5] &= ~(1 << (idx & 31)); + } + + // Toggle a bit. + void toggleBitAt(uint idx) + { + nvDebugCheck(idx < m_size); + m_wordArray[idx >> 5] ^= (1 << (idx & 31)); + } + + // Set a bit to the given value. @@ Rename modifyBitAt? + void setBitAt(uint idx, bool b) + { + nvDebugCheck(idx < m_size); + m_wordArray[idx >> 5] = setBits(m_wordArray[idx >> 5], 1 << (idx & 31), b); + nvDebugCheck(bitAt(idx) == b); + } + + void append(bool value) + { + resize(m_size + 1); + setBitAt(m_size - 1, value); + } + + + // Clear all the bits. + void clearAll() + { + memset(m_wordArray.buffer(), 0, m_wordArray.size() * sizeof(uint)); + } + + // Set all the bits. + void setAll() + { + memset(m_wordArray.buffer(), 0xFF, m_wordArray.size() * sizeof(uint)); + } + + // Toggle all the bits. + void toggleAll() + { + const uint wordCount = m_wordArray.count(); + for(uint b = 0; b < wordCount; b++) { + m_wordArray[b] ^= 0xFFFFFFFF; + } + } + + // Count the number of bits set. + uint countSetBits() const + { + const uint num = m_wordArray.size(); + if( num == 0 ) { + return 0; + } + + uint count = 0; + for(uint i = 0; i < num - 1; i++) { + count += nv::countSetBits(m_wordArray[i]); + } + count += nv::countSetBits(m_wordArray[num - 1], m_size & 31); + + //piDebugCheck(count + countClearBits() == m_size); + return count; + } + + // Count the number of bits clear. + uint countClearBits() const { + + const uint num = m_wordArray.size(); + if( num == 0 ) { + return 0; + } + + uint count = 0; + for(uint i = 0; i < num - 1; i++) { + count += nv::countSetBits(~m_wordArray[i]); + } + count += nv::countSetBits(~m_wordArray[num - 1], m_size & 31); + + //piDebugCheck(count + countSetBits() == m_size); + return count; + } + + friend void swap(BitArray & a, BitArray & b) + { + swap(a.m_size, b.m_size); + swap(a.m_wordArray, b.m_wordArray); + } + + void operator &= (const BitArray & other) { + if (other.m_size != m_size) { + resize(other.m_size); + } + + const uint wordCount = m_wordArray.count(); + for (uint i = 0; i < wordCount; i++) { + m_wordArray[i] &= other.m_wordArray[i]; + } + } + + void operator |= (const BitArray & other) { + if (other.m_size != m_size) { + resize(other.m_size); + } + + const uint wordCount = m_wordArray.count(); + for (uint i = 0; i < wordCount; i++) { + m_wordArray[i] |= other.m_wordArray[i]; + } + } + + + private: + + // Number of bits stored. + uint m_size; + + // Array of bits. + Array m_wordArray; + + }; + +} // nv namespace + +#endif // NV_CORE_BITARRAY_H + diff --git a/thirdparty/thekla_atlas/src/nvcore/CMakeLists.txt b/thirdparty/thekla_atlas/src/nvcore/CMakeLists.txt new file mode 100755 index 00000000..a84a9e99 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/CMakeLists.txt @@ -0,0 +1,74 @@ +PROJECT(nvcore) + +SET(CORE_SRCS + nvcore.h + DefsGnucDarwin.h + DefsGnucLinux.h + DefsGnucWin32.h + DefsVcWin32.h + Ptr.h + RefCounted.h + BitArray.h + Memory.h + Memory.cpp + Debug.h + Debug.cpp + StrLib.h + StrLib.cpp + Stream.h + StdStream.h + FileSystem.h + FileSystem.cpp + RadixSort.h + RadixSort.cpp) + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +# For Windows64 in MSVC we need to add the assembly version of vsscanf +IF(MSVC AND NV_SYSTEM_PROCESSOR STREQUAL "AMD64") + SET(VSSCANF_ASM_NAME "vsscanf_proxy_win64") + IF(MSVC_IDE) + # $(IntDir) is a macro expanded to the intermediate directory of the selected solution configuration + SET(VSSCANF_ASM_INTDIR "$(IntDir)") + ELSE(MSVC_IDE) + # For some reason the NMake generator doesn't work properly with the generated .obj source: + # it requires the absolute path. So this is a hack which worked as of cmake 2.6.0 patch 0 + GET_FILENAME_COMPONENT(VSSCANF_ASM_INTDIR + "${nvcore_BINARY_DIR}/CMakeFiles/nvcore.dir" ABSOLUTE) + ENDIF(MSVC_IDE) + + SET(VSSCANF_ASM_SRC "${CMAKE_CURRENT_SOURCE_DIR}/${VSSCANF_ASM_NAME}.masm") + SET(VSSCANF_ASM_OBJ "${VSSCANF_ASM_INTDIR}/${VSSCANF_ASM_NAME}.obj") + + # Adds the assembly output to the sources and adds the custom command to generate it + SET(CORE_SRCS + ${CORE_SRCS} + ${VSSCANF_ASM_OBJ} + ) + ADD_CUSTOM_COMMAND(OUTPUT ${VSSCANF_ASM_OBJ} + MAIN_DEPENDENCY ${VSSCANF_ASM_SRC} + COMMAND ml64 + ARGS /nologo /Fo ${VSSCANF_ASM_OBJ} /c /Cx ${VSSCANF_ASM_SRC} + ) +ENDIF(MSVC AND NV_SYSTEM_PROCESSOR STREQUAL "AMD64") + +# targets +ADD_DEFINITIONS(-DNVCORE_EXPORTS) + +IF(UNIX) + SET(LIBS ${LIBS} ${CMAKE_DL_LIBS}) +ENDIF(UNIX) + +IF(NVCORE_SHARED) + ADD_DEFINITIONS(-DNVCORE_SHARED=1) + ADD_LIBRARY(nvcore SHARED ${CORE_SRCS}) +ELSE(NVCORE_SHARED) + ADD_LIBRARY(nvcore ${CORE_SRCS}) +ENDIF(NVCORE_SHARED) + +TARGET_LINK_LIBRARIES(nvcore ${LIBS}) + +INSTALL(TARGETS nvcore + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib/static) diff --git a/thirdparty/thekla_atlas/src/nvcore/Debug.cpp b/thirdparty/thekla_atlas/src/nvcore/Debug.cpp new file mode 100755 index 00000000..75ac6beb --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/Debug.cpp @@ -0,0 +1,1347 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "Debug.h" +#include "Array.inl" +#include "StrLib.h" // StringBuilder + +#include "StdStream.h" // fileOpen + +#include + +// Extern +#if NV_OS_WIN32 //&& NV_CC_MSVC +# define WIN32_LEAN_AND_MEAN +# define VC_EXTRALEAN +# include +# include +# if NV_CC_MSVC +# include +# if _MSC_VER < 1300 +# define DECLSPEC_DEPRECATED +// VC6: change this path to your Platform SDK headers +# include // must be XP version of file +// include "M:\\dev7\\vs\\devtools\\common\\win32sdk\\include\\dbghelp.h" +# else +// VC7: ships with updated headers +# include +# endif +# endif +# pragma comment(lib,"dbghelp.lib") +#endif + +#if NV_OS_XBOX +# include +# ifdef _DEBUG +# include +# endif //_DEBUG +#endif //NV_OS_XBOX + +#if !NV_OS_WIN32 && defined(NV_HAVE_SIGNAL_H) +# include +#endif + +#if NV_OS_UNIX +# include // getpid +#endif + +#if NV_OS_LINUX && defined(NV_HAVE_EXECINFO_H) +# include // backtrace +# if NV_CC_GNUC // defined(NV_HAVE_CXXABI_H) +# include +# endif +#endif + +#if NV_OS_DARWIN || NV_OS_FREEBSD || NV_OS_OPENBSD +# include +# include +# include // sysctl +# if !defined(NV_OS_OPENBSD) +# include +# endif +# if defined(NV_HAVE_EXECINFO_H) // only after OSX 10.5 +# include // backtrace +# if NV_CC_GNUC // defined(NV_HAVE_CXXABI_H) +# include +# endif +# endif +#endif + +#if NV_OS_ORBIS +#include +#endif + +#if NV_OS_DURANGO +#include "Windows.h" +#include +#include +#include +#include +#define NV_USE_SEPARATE_THREAD 0 +#else +#define NV_USE_SEPARATE_THREAD 1 +#endif + + + +using namespace nv; + +namespace +{ + + static MessageHandler * s_message_handler = NULL; + static AssertHandler * s_assert_handler = NULL; + + static bool s_sig_handler_enabled = false; + static bool s_interactive = true; + +#if (NV_OS_WIN32 && NV_CC_MSVC) || NV_OS_DURANGO + + // Old exception filter. + static LPTOP_LEVEL_EXCEPTION_FILTER s_old_exception_filter = NULL; + +#elif !NV_OS_WIN32 && defined(NV_HAVE_SIGNAL_H) + + // Old signal handlers. + struct sigaction s_old_sigsegv; + struct sigaction s_old_sigtrap; + struct sigaction s_old_sigfpe; + struct sigaction s_old_sigbus; + +#endif + + +#if (NV_OS_WIN32 && NV_CC_MSVC) || NV_OS_DURANGO + + // We should try to simplify the top level filter as much as possible. + // http://www.nynaeve.net/?p=128 + + // The critical section enforcing the requirement that only one exception be + // handled by a handler at a time. + static CRITICAL_SECTION s_handler_critical_section; + +#if NV_USE_SEPARATE_THREAD + // Semaphores used to move exception handling between the exception thread + // and the handler thread. handler_start_semaphore_ is signalled by the + // exception thread to wake up the handler thread when an exception occurs. + // handler_finish_semaphore_ is signalled by the handler thread to wake up + // the exception thread when handling is complete. + static HANDLE s_handler_start_semaphore = NULL; + static HANDLE s_handler_finish_semaphore = NULL; + + // The exception handler thread. + static HANDLE s_handler_thread = NULL; + + static DWORD s_requesting_thread_id = 0; + static EXCEPTION_POINTERS * s_exception_info = NULL; + +#endif // NV_USE_SEPARATE_THREAD + + + struct MinidumpCallbackContext { + ULONG64 memory_base; + ULONG memory_size; + bool finished; + }; + +#if NV_OS_WIN32 + // static + static BOOL CALLBACK miniDumpWriteDumpCallback(PVOID context, const PMINIDUMP_CALLBACK_INPUT callback_input, PMINIDUMP_CALLBACK_OUTPUT callback_output) + { + switch (callback_input->CallbackType) + { + case MemoryCallback: { + MinidumpCallbackContext* callback_context = reinterpret_cast(context); + if (callback_context->finished) + return FALSE; + + // Include the specified memory region. + callback_output->MemoryBase = callback_context->memory_base; + callback_output->MemorySize = callback_context->memory_size; + callback_context->finished = true; + return TRUE; + } + + // Include all modules. + case IncludeModuleCallback: + case ModuleCallback: + return TRUE; + + // Include all threads. + case IncludeThreadCallback: + case ThreadCallback: + return TRUE; + + // Stop receiving cancel callbacks. + case CancelCallback: + callback_output->CheckCancel = FALSE; + callback_output->Cancel = FALSE; + return TRUE; + } + + // Ignore other callback types. + return FALSE; + } +#endif + + static bool writeMiniDump(EXCEPTION_POINTERS * pExceptionInfo) + { +#if NV_OS_DURANGO + // Get a handle to the minidump method. + typedef BOOL(WINAPI* MiniDumpWriteDumpPfn) ( + _In_ HANDLE hProcess, + _In_ DWORD ProcessId, + _In_ HANDLE hFile, + _In_ MINIDUMP_TYPE DumpType, + _In_opt_ PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + _In_opt_ PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + _Reserved_ PVOID CallbackParam + ); + MiniDumpWriteDumpPfn MiniDumpWriteDump = NULL; + HMODULE hToolHelpModule = ::LoadLibraryW(L"toolhelpx.dll"); + if (hToolHelpModule != INVALID_HANDLE_VALUE) { + MiniDumpWriteDump = reinterpret_cast(::GetProcAddress(hToolHelpModule, "MiniDumpWriteDump")); + if (!MiniDumpWriteDump) { + FreeLibrary(hToolHelpModule); + return false; + } + } + else + return false; + + // Generate a decent filename. + nv::Path application_path(256); + HINSTANCE hinstance = GetModuleHandle(NULL); + GetModuleFileName(hinstance, application_path.str(), 256); + application_path.stripExtension(); + const char * application_name = application_path.fileName(); + + SYSTEMTIME local_time; + GetLocalTime(&local_time); + + char dump_filename[MAX_PATH] = {}; + sprintf_s(dump_filename, "d:\\%s-%04d%02d%02d-%02d%02d%02d.dmp", + application_name, + local_time.wYear, local_time.wMonth, local_time.wDay, + local_time.wHour, local_time.wMinute, local_time.wSecond ); +#else + const char* dump_filename = "crash.dmp"; +#endif + + // create the file + HANDLE hFile = CreateFileA(dump_filename, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + //nvDebug("*** Failed to create dump file.\n"); +#if NV_OS_DURANGO + FreeLibrary(hToolHelpModule); +#endif + return false; + } + + MINIDUMP_EXCEPTION_INFORMATION * pExInfo = NULL; +#if NV_OS_WIN32 + MINIDUMP_CALLBACK_INFORMATION * pCallback = NULL; +#else + void * pCallback = NULL; +#endif + + MINIDUMP_EXCEPTION_INFORMATION ExInfo; + if (pExceptionInfo != NULL) { + ExInfo.ThreadId = ::GetCurrentThreadId(); + ExInfo.ExceptionPointers = pExceptionInfo; + ExInfo.ClientPointers = NULL; + pExInfo = &ExInfo; + +#if NV_OS_WIN32 + MINIDUMP_CALLBACK_INFORMATION callback; + MinidumpCallbackContext context; + + // Find a memory region of 256 bytes centered on the + // faulting instruction pointer. + const ULONG64 instruction_pointer = + #if defined(_M_IX86) + pExceptionInfo->ContextRecord->Eip; + #elif defined(_M_AMD64) + pExceptionInfo->ContextRecord->Rip; + #else + #error Unsupported platform + #endif + + MEMORY_BASIC_INFORMATION info; + + if (VirtualQuery(reinterpret_cast(instruction_pointer), &info, sizeof(MEMORY_BASIC_INFORMATION)) != 0 && info.State == MEM_COMMIT) + { + // Attempt to get 128 bytes before and after the instruction + // pointer, but settle for whatever's available up to the + // boundaries of the memory region. + const ULONG64 kIPMemorySize = 256; + context.memory_base = max(reinterpret_cast(info.BaseAddress), instruction_pointer - (kIPMemorySize / 2)); + ULONG64 end_of_range = min(instruction_pointer + (kIPMemorySize / 2), reinterpret_cast(info.BaseAddress) + info.RegionSize); + context.memory_size = static_cast(end_of_range - context.memory_base); + context.finished = false; + + callback.CallbackRoutine = miniDumpWriteDumpCallback; + callback.CallbackParam = reinterpret_cast(&context); + pCallback = &callback; + } +#endif + } + + MINIDUMP_TYPE miniDumpType = (MINIDUMP_TYPE)(MiniDumpNormal|MiniDumpWithHandleData|MiniDumpWithThreadInfo); + + // write the dump + BOOL ok = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, miniDumpType, pExInfo, NULL, pCallback) != 0; + CloseHandle(hFile); +#if NV_OS_DURANGO + FreeLibrary(hToolHelpModule); +#endif + + if (ok == FALSE) { + //nvDebug("*** Failed to save dump file.\n"); + return false; + } + + //nvDebug("\nDump file saved.\n"); + + return true; + } + +#if NV_USE_SEPARATE_THREAD + + static DWORD WINAPI ExceptionHandlerThreadMain(void* lpParameter) { + nvDebugCheck(s_handler_start_semaphore != NULL); + nvDebugCheck(s_handler_finish_semaphore != NULL); + + while (true) { + if (WaitForSingleObject(s_handler_start_semaphore, INFINITE) == WAIT_OBJECT_0) { + writeMiniDump(s_exception_info); + + // Allow the requesting thread to proceed. + ReleaseSemaphore(s_handler_finish_semaphore, 1, NULL); + } + } + + // This statement is not reached when the thread is unconditionally + // terminated by the ExceptionHandler destructor. + return 0; + } + +#endif // NV_USE_SEPARATE_THREAD + + static bool hasStackTrace() { + return true; + } + + /*static NV_NOINLINE int backtrace(void * trace[], int maxcount) { + + // In Windows XP and Windows Server 2003, the sum of the FramesToSkip and FramesToCapture parameters must be less than 63. + int xp_maxcount = min(63-1, maxcount); + + int count = RtlCaptureStackBackTrace(1, xp_maxcount, trace, NULL); + nvDebugCheck(count <= maxcount); + + return count; + }*/ + +#if NV_OS_WIN32 + static NV_NOINLINE int backtraceWithSymbols(CONTEXT * ctx, void * trace[], int maxcount, int skip = 0) { + + // Init the stack frame for this function + STACKFRAME64 stackFrame = { 0 }; + + #if NV_CPU_X86_64 + DWORD dwMachineType = IMAGE_FILE_MACHINE_AMD64; + stackFrame.AddrPC.Offset = ctx->Rip; + stackFrame.AddrFrame.Offset = ctx->Rbp; + stackFrame.AddrStack.Offset = ctx->Rsp; + #elif NV_CPU_X86 + DWORD dwMachineType = IMAGE_FILE_MACHINE_I386; + stackFrame.AddrPC.Offset = ctx->Eip; + stackFrame.AddrFrame.Offset = ctx->Ebp; + stackFrame.AddrStack.Offset = ctx->Esp; + #else + #error "Platform not supported!" + #endif + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrStack.Mode = AddrModeFlat; + + // Walk up the stack + const HANDLE hThread = GetCurrentThread(); + const HANDLE hProcess = GetCurrentProcess(); + int i; + for (i = 0; i < maxcount; i++) + { + // walking once first makes us skip self + if (!StackWalk64(dwMachineType, hProcess, hThread, &stackFrame, ctx, NULL, &SymFunctionTableAccess64, &SymGetModuleBase64, NULL)) { + break; + } + + /*if (stackFrame.AddrPC.Offset == stackFrame.AddrReturn.Offset || stackFrame.AddrPC.Offset == 0) { + break; + }*/ + + if (i >= skip) { + trace[i - skip] = (PVOID)stackFrame.AddrPC.Offset; + } + } + + return i - skip; + } + +#pragma warning(push) +#pragma warning(disable:4748) + static NV_NOINLINE int backtrace(void * trace[], int maxcount) { + CONTEXT ctx = { 0 }; +#if NV_CPU_X86 && !NV_CPU_X86_64 + ctx.ContextFlags = CONTEXT_CONTROL; + _asm { + call x + x: pop eax + mov ctx.Eip, eax + mov ctx.Ebp, ebp + mov ctx.Esp, esp + } +#else + RtlCaptureContext(&ctx); // Not implemented correctly in x86. +#endif + + return backtraceWithSymbols(&ctx, trace, maxcount, 1); + } +#pragma warning(pop) + + static NV_NOINLINE void writeStackTrace(void * trace[], int size, int start, Array & lines) + { + StringBuilder builder(512); + + HANDLE hProcess = GetCurrentProcess(); + + // Resolve PC to function names + for (int i = start; i < size; i++) + { + // Check for end of stack walk + DWORD64 ip = (DWORD64)trace[i]; + if (ip == NULL) + break; + + // Get function name + #define MAX_STRING_LEN (512) + unsigned char byBuffer[sizeof(IMAGEHLP_SYMBOL64) + MAX_STRING_LEN] = { 0 }; + IMAGEHLP_SYMBOL64 * pSymbol = (IMAGEHLP_SYMBOL64*)byBuffer; + pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); + pSymbol->MaxNameLength = MAX_STRING_LEN; + + DWORD64 dwDisplacement; + + if (SymGetSymFromAddr64(hProcess, ip, &dwDisplacement, pSymbol)) + { + pSymbol->Name[MAX_STRING_LEN-1] = 0; + + /* + // Make the symbol readable for humans + UnDecorateSymbolName( pSym->Name, lpszNonUnicodeUnDSymbol, BUFFERSIZE, + UNDNAME_COMPLETE | + UNDNAME_NO_THISTYPE | + UNDNAME_NO_SPECIAL_SYMS | + UNDNAME_NO_MEMBER_TYPE | + UNDNAME_NO_MS_KEYWORDS | + UNDNAME_NO_ACCESS_SPECIFIERS ); + */ + + // pSymbol->Name + const char * pFunc = pSymbol->Name; + + // Get file/line number + IMAGEHLP_LINE64 theLine = { 0 }; + theLine.SizeOfStruct = sizeof(theLine); + + DWORD dwDisplacement; + if (!SymGetLineFromAddr64(hProcess, ip, &dwDisplacement, &theLine)) + { + // Do not print unknown symbols anymore. + //break; + builder.format("unknown(%08X) : %s\n", (uint32)ip, pFunc); + } + else + { + /* + const char* pFile = strrchr(theLine.FileName, '\\'); + if ( pFile == NULL ) pFile = theLine.FileName; + else pFile++; + */ + const char * pFile = theLine.FileName; + + int line = theLine.LineNumber; + + builder.format("%s(%d) : %s\n", pFile, line, pFunc); + } + + lines.append(builder.release()); + + if (pFunc != NULL && strcmp(pFunc, "WinMain") == 0) { + break; + } + } + } + } +#endif + + // Write mini dump and print stack trace. + static LONG WINAPI handleException(EXCEPTION_POINTERS * pExceptionInfo) + { + EnterCriticalSection(&s_handler_critical_section); +#if NV_USE_SEPARATE_THREAD + s_requesting_thread_id = GetCurrentThreadId(); + s_exception_info = pExceptionInfo; + + // This causes the handler thread to call writeMiniDump. + ReleaseSemaphore(s_handler_start_semaphore, 1, NULL); + + // Wait until WriteMinidumpWithException is done and collect its return value. + WaitForSingleObject(s_handler_finish_semaphore, INFINITE); + //bool status = s_handler_return_value; + + // Clean up. + s_requesting_thread_id = 0; + s_exception_info = NULL; +#else + // First of all, write mini dump. + writeMiniDump(pExceptionInfo); +#endif + LeaveCriticalSection(&s_handler_critical_section); + + nvDebug("\nDump file saved.\n"); + + // Try to attach to debugger. + if (s_interactive && debug::attachToDebugger()) { + nvDebugBreak(); + return EXCEPTION_CONTINUE_EXECUTION; + } + +#if NV_OS_WIN32 + // If that fails, then try to pretty print a stack trace and terminate. + void * trace[64]; + + int size = backtraceWithSymbols(pExceptionInfo->ContextRecord, trace, 64); + + // @@ Use win32's CreateFile? + FILE * fp = fileOpen("crash.txt", "wb"); + if (fp != NULL) { + Array lines; + writeStackTrace(trace, size, 0, lines); + + for (uint i = 0; i < lines.count(); i++) { + fputs(lines[i], fp); + delete lines[i]; + } + + // @@ Add more info to crash.txt? + + fclose(fp); + } +#endif + + // This should terminate the process and set the error exit code. + TerminateProcess(GetCurrentProcess(), EXIT_FAILURE + 2); + + return EXCEPTION_EXECUTE_HANDLER; // Terminate app. In case terminate process did not succeed. + } + + static void handlePureVirtualCall() { + nvDebugBreak(); + TerminateProcess(GetCurrentProcess(), EXIT_FAILURE + 8); + } + + static void handleInvalidParameter(const wchar_t * wexpresion, const wchar_t * wfunction, const wchar_t * wfile, unsigned int line, uintptr_t reserved) { + + size_t convertedCharCount = 0; + + StringBuilder expresion; + if (wexpresion != NULL) { + uint size = U32(wcslen(wexpresion) + 1); + expresion.reserve(size); + wcstombs_s(&convertedCharCount, expresion.str(), size, wexpresion, _TRUNCATE); + } + + StringBuilder file; + if (wfile != NULL) { + uint size = U32(wcslen(wfile) + 1); + file.reserve(size); + wcstombs_s(&convertedCharCount, file.str(), size, wfile, _TRUNCATE); + } + + StringBuilder function; + if (wfunction != NULL) { + uint size = U32(wcslen(wfunction) + 1); + function.reserve(size); + wcstombs_s(&convertedCharCount, function.str(), size, wfunction, _TRUNCATE); + } + + int result = nvAbort(expresion.str(), file.str(), line, function.str()); + if (result == NV_ABORT_DEBUG) { + nvDebugBreak(); + } + } + +#elif !NV_OS_WIN32 && defined(NV_HAVE_SIGNAL_H) // NV_OS_LINUX || NV_OS_DARWIN + +#if defined(NV_HAVE_EXECINFO_H) + + static bool hasStackTrace() { + return true; + } + + + static void writeStackTrace(void * trace[], int size, int start, Array & lines) { + StringBuilder builder(512); + char ** string_array = backtrace_symbols(trace, size); + + for(int i = start; i < size-1; i++ ) { + // IC: Just in case. + if (string_array[i] == NULL || string_array[i][0] == '\0') break; + +# if NV_CC_GNUC // defined(NV_HAVE_CXXABI_H) + // @@ Write a better parser for the possible formats. + char * begin = strchr(string_array[i], '('); + char * end = strrchr(string_array[i], '+'); + char * module = string_array[i]; + + if (begin == 0 && end != 0) { + *(end - 1) = '\0'; + begin = strrchr(string_array[i], ' '); + module = NULL; // Ignore module. + } + + if (begin != 0 && begin < end) { + int stat; + *end = '\0'; + *begin = '\0'; + char * name = abi::__cxa_demangle(begin+1, 0, 0, &stat); + if (module == NULL) { + if (name == NULL || stat != 0) { + builder.format(" In: '%s'\n", begin+1); + } + else { + builder.format(" In: '%s'\n", name); + } + } + else { + if (name == NULL || stat != 0) { + builder.format(" In: [%s] '%s'\n", module, begin+1); + } + else { + builder.format(" In: [%s] '%s'\n", module, name); + } + } + free(name); + } + else { + builder.format(" In: '%s'\n", string_array[i]); + } +# else + builder.format(" In: '%s'\n", string_array[i]); +# endif + lines.append(builder.release()); + } + + free(string_array); + } + + static void printStackTrace(void * trace[], int size, int start=0) { + nvDebug( "\nDumping stacktrace:\n" ); + + Array lines; + writeStackTrace(trace, size, 1, lines); + + for (uint i = 0; i < lines.count(); i++) { + nvDebug("%s", lines[i]); + delete lines[i]; + } + + nvDebug("\n"); + } + +#endif // defined(NV_HAVE_EXECINFO_H) + + static void * callerAddress(void * secret) + { +#if NV_OS_DARWIN +# if defined(_STRUCT_MCONTEXT) +# if NV_CPU_PPC + ucontext_t * ucp = (ucontext_t *)secret; + return (void *) ucp->uc_mcontext->__ss.__srr0; +# elif NV_CPU_X86_64 + ucontext_t * ucp = (ucontext_t *)secret; + return (void *) ucp->uc_mcontext->__ss.__rip; +# elif NV_CPU_X86 + ucontext_t * ucp = (ucontext_t *)secret; + return (void *) ucp->uc_mcontext->__ss.__eip; +# elif NV_CPU_ARM + ucontext_t * ucp = (ucontext_t *)secret; + return (void *) ucp->uc_mcontext->__ss.__pc; +# else +# error "Unknown CPU" +# endif +# else +# if NV_CPU_PPC + ucontext_t * ucp = (ucontext_t *)secret; + return (void *) ucp->uc_mcontext->ss.srr0; +# elif NV_CPU_X86 + ucontext_t * ucp = (ucontext_t *)secret; + return (void *) ucp->uc_mcontext->ss.eip; +# else +# error "Unknown CPU" +# endif +# endif +#elif NV_OS_FREEBSD +# if NV_CPU_X86_64 + ucontext_t * ucp = (ucontext_t *)secret; + return (void *)ucp->uc_mcontext.mc_rip; +# elif NV_CPU_X86 + ucontext_t * ucp = (ucontext_t *)secret; + return (void *)ucp->uc_mcontext.mc_eip; +# else +# error "Unknown CPU" +# endif +#elif NV_OS_OPENBSD +# if NV_CPU_X86_64 + ucontext_t * ucp = (ucontext_t *)secret; + return (void *)ucp->sc_rip; +# elif NV_CPU_X86 + ucontext_t * ucp = (ucontext_t *)secret; + return (void *)ucp->sc_eip; +# else +# error "Unknown CPU" +# endif +#else +# if NV_CPU_X86_64 + // #define REG_RIP REG_INDEX(rip) // seems to be 16 + ucontext_t * ucp = (ucontext_t *)secret; + return (void *)ucp->uc_mcontext.gregs[REG_RIP]; +# elif NV_CPU_X86 + ucontext_t * ucp = (ucontext_t *)secret; + return (void *)ucp->uc_mcontext.gregs[14/*REG_EIP*/]; +# elif NV_CPU_PPC + ucontext_t * ucp = (ucontext_t *)secret; + return (void *) ucp->uc_mcontext.regs->nip; +# else +# error "Unknown CPU" +# endif +#endif + + // How to obtain the instruction pointers in different platforms, from mlton's source code. + // http://mlton.org/ + // OpenBSD && NetBSD + // ucp->sc_eip + // FreeBSD: + // ucp->uc_mcontext.mc_eip + // HPUX: + // ucp->uc_link + // Solaris: + // ucp->uc_mcontext.gregs[REG_PC] + // Linux hppa: + // uc->uc_mcontext.sc_iaoq[0] & ~0x3UL + // Linux sparc: + // ((struct sigcontext*) secret)->sigc_regs.tpc + // Linux sparc64: + // ((struct sigcontext*) secret)->si_regs.pc + + // potentially correct for other archs: + // Linux alpha: ucp->m_context.sc_pc + // Linux arm: ucp->m_context.ctx.arm_pc + // Linux ia64: ucp->m_context.sc_ip & ~0x3UL + // Linux mips: ucp->m_context.sc_pc + // Linux s390: ucp->m_context.sregs->regs.psw.addr + } + + static void nvSigHandler(int sig, siginfo_t *info, void *secret) + { + void * pnt = callerAddress(secret); + + // Do something useful with siginfo_t + if (sig == SIGSEGV) { + if (pnt != NULL) nvDebug("Got signal %d, faulty address is %p, from %p\n", sig, info->si_addr, pnt); + else nvDebug("Got signal %d, faulty address is %p\n", sig, info->si_addr); + } + else if(sig == SIGTRAP) { + nvDebug("Breakpoint hit.\n"); + } + else { + nvDebug("Got signal %d\n", sig); + } + +#if defined(NV_HAVE_EXECINFO_H) + if (hasStackTrace()) // in case of weak linking + { + void * trace[64]; + int size = backtrace(trace, 64); + + if (pnt != NULL) { + // Overwrite sigaction with caller's address. + trace[1] = pnt; + } + + printStackTrace(trace, size, 1); + } +#endif // defined(NV_HAVE_EXECINFO_H) + + exit(0); + } + +#endif // defined(NV_HAVE_SIGNAL_H) + + + +#if NV_OS_WIN32 //&& NV_CC_MSVC + + /** Win32 assert handler. */ + struct Win32AssertHandler : public AssertHandler + { + // Flush the message queue. This is necessary for the message box to show up. + static void flushMessageQueue() + { + MSG msg; + while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) { + //if( msg.message == WM_QUIT ) break; + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + } + + // Assert handler method. + virtual int assertion(const char * exp, const char * file, int line, const char * func, const char * msg, va_list arg) + { + int ret = NV_ABORT_EXIT; + + StringBuilder error_string; + error_string.format("*** Assertion failed: %s\n On file: %s\n On line: %d\n", exp, file, line ); + if (func != NULL) { + error_string.appendFormat(" On function: %s\n", func); + } + if (msg != NULL) { + error_string.append(" Message: "); + va_list tmp; + va_copy(tmp, arg); + error_string.appendFormatList(msg, tmp); + va_end(tmp); + error_string.append("\n"); + } + nvDebug( error_string.str() ); + + // Print stack trace: + debug::dumpInfo(); + + if (debug::isDebuggerPresent()) { + return NV_ABORT_DEBUG; + } + + if (s_interactive) { + flushMessageQueue(); + int action = MessageBoxA(NULL, error_string.str(), "Assertion failed", MB_ABORTRETRYIGNORE | MB_ICONERROR | MB_TOPMOST); + switch( action ) { + case IDRETRY: + ret = NV_ABORT_DEBUG; + break; + case IDIGNORE: + ret = NV_ABORT_IGNORE; + break; + case IDABORT: + default: + ret = NV_ABORT_EXIT; + break; + } + /*if( _CrtDbgReport( _CRT_ASSERT, file, line, module, exp ) == 1 ) { + return NV_ABORT_DEBUG; + }*/ + } + + if (ret == NV_ABORT_EXIT) { + // Exit cleanly. + exit(EXIT_FAILURE + 1); + } + + return ret; + } + }; +#elif NV_OS_XBOX + + /** Xbox360 assert handler. */ + struct Xbox360AssertHandler : public AssertHandler + { + // Assert handler method. + virtual int assertion(const char * exp, const char * file, int line, const char * func, const char * msg, va_list arg) + { + int ret = NV_ABORT_EXIT; + + StringBuilder error_string; + if( func != NULL ) { + error_string.format( "*** Assertion failed: %s\n On file: %s\n On function: %s\n On line: %d\n ", exp, file, func, line ); + nvDebug( error_string.str() ); + } + else { + error_string.format( "*** Assertion failed: %s\n On file: %s\n On line: %d\n ", exp, file, line ); + nvDebug( error_string.str() ); + } + + if (debug::isDebuggerPresent()) { + return NV_ABORT_DEBUG; + } + + if( ret == NV_ABORT_EXIT ) { + // Exit cleanly. + exit(EXIT_FAILURE + 1); + } + + return ret; + } + }; +#elif NV_OS_ORBIS || NV_OS_DURANGO + + /** Console assert handler. */ + struct ConsoleAssertHandler : public AssertHandler + { + // Assert handler method. + virtual int assertion(const char * exp, const char * file, int line, const char * func, const char * msg, va_list arg) + { + if( func != NULL ) { + nvDebug( "*** Assertion failed: %s\n On file: %s\n On function: %s\n On line: %d\n ", exp, file, func, line ); + } + else { + nvDebug( "*** Assertion failed: %s\n On file: %s\n On line: %d\n ", exp, file, line ); + } + + //SBtodoORBIS print stack trace + /*if (hasStackTrace()) + { + void * trace[64]; + int size = backtrace(trace, 64); + printStackTrace(trace, size, 2); + }*/ + + if (debug::isDebuggerPresent()) + return NV_ABORT_DEBUG; + + return NV_ABORT_IGNORE; + } + }; + +#else + + /** Unix assert handler. */ + struct UnixAssertHandler : public AssertHandler + { + // Assert handler method. + virtual int assertion(const char * exp, const char * file, int line, const char * func, const char * msg, va_list arg) + { + int ret = NV_ABORT_EXIT; + + if( func != NULL ) { + nvDebug( "*** Assertion failed: %s\n On file: %s\n On function: %s\n On line: %d\n ", exp, file, func, line ); + } + else { + nvDebug( "*** Assertion failed: %s\n On file: %s\n On line: %d\n ", exp, file, line ); + } + +#if _DEBUG + if (debug::isDebuggerPresent()) { + return NV_ABORT_DEBUG; + } +#endif + +#if defined(NV_HAVE_EXECINFO_H) + if (hasStackTrace()) + { + void * trace[64]; + int size = backtrace(trace, 64); + printStackTrace(trace, size, 2); + } +#endif + + if( ret == NV_ABORT_EXIT ) { + // Exit cleanly. + exit(EXIT_FAILURE + 1); + } + + return ret; + } + }; + +#endif + +} // namespace + + +/// Handle assertion through the assert handler. +int nvAbort(const char * exp, const char * file, int line, const char * func/*=NULL*/, const char * msg/*= NULL*/, ...) +{ +#if NV_OS_WIN32 //&& NV_CC_MSVC + static Win32AssertHandler s_default_assert_handler; +#elif NV_OS_XBOX + static Xbox360AssertHandler s_default_assert_handler; +#elif NV_OS_ORBIS || NV_OS_DURANGO + static ConsoleAssertHandler s_default_assert_handler; +#else + static UnixAssertHandler s_default_assert_handler; +#endif + + va_list arg; + va_start(arg,msg); + + AssertHandler * handler = s_assert_handler != NULL ? s_assert_handler : &s_default_assert_handler; + int result = handler->assertion(exp, file, line, func, msg, arg); + + va_end(arg); + + return result; +} + +// Abnormal termination. Create mini dump and output call stack. +void debug::terminate(int code) +{ +#if NV_OS_WIN32 || NV_OS_DURANGO + EnterCriticalSection(&s_handler_critical_section); + + writeMiniDump(NULL); + +#if NV_OS_WIN32 + const int max_stack_size = 64; + void * trace[max_stack_size]; + int size = backtrace(trace, max_stack_size); + + // @@ Use win32's CreateFile? + FILE * fp = fileOpen("crash.txt", "wb"); + if (fp != NULL) { + Array lines; + writeStackTrace(trace, size, 0, lines); + + for (uint i = 0; i < lines.count(); i++) { + fputs(lines[i], fp); + delete lines[i]; + } + + // @@ Add more info to crash.txt? + + fclose(fp); + } +#endif + + LeaveCriticalSection(&s_handler_critical_section); +#endif + + exit(code); +} + + +/// Shows a message through the message handler. +void NV_CDECL nvDebugPrint(const char *msg, ...) +{ + va_list arg; + va_start(arg,msg); + if (s_message_handler != NULL) { + s_message_handler->log( msg, arg ); + } + else { + vprintf(msg, arg); + } + va_end(arg); +} + + +/// Dump debug info. +void debug::dumpInfo() +{ +#if (NV_OS_WIN32 && NV_CC_MSVC) || (defined(NV_HAVE_SIGNAL_H) && defined(NV_HAVE_EXECINFO_H)) + if (hasStackTrace()) + { + void * trace[64]; + int size = backtrace(trace, 64); + + nvDebug( "\nDumping stacktrace:\n" ); + + Array lines; + writeStackTrace(trace, size, 1, lines); + + for (uint i = 0; i < lines.count(); i++) { + nvDebug("%s", lines[i]); + delete lines[i]; + } + } +#endif +} + +/// Dump callstack using the specified handler. +void debug::dumpCallstack(MessageHandler *messageHandler, int callstackLevelsToSkip /*= 0*/) +{ +#if (NV_OS_WIN32 && NV_CC_MSVC) || (defined(NV_HAVE_SIGNAL_H) && defined(NV_HAVE_EXECINFO_H)) + if (hasStackTrace()) + { + void * trace[64]; + int size = backtrace(trace, 64); + + Array lines; + writeStackTrace(trace, size, callstackLevelsToSkip + 1, lines); // + 1 to skip the call to dumpCallstack + + for (uint i = 0; i < lines.count(); i++) { + messageHandler->log(lines[i], NULL); + delete lines[i]; + } + } +#endif +} + + +/// Set the debug message handler. +void debug::setMessageHandler(MessageHandler * message_handler) +{ + s_message_handler = message_handler; +} + +/// Reset the debug message handler. +void debug::resetMessageHandler() +{ + s_message_handler = NULL; +} + +/// Set the assert handler. +void debug::setAssertHandler(AssertHandler * assert_handler) +{ + s_assert_handler = assert_handler; +} + +/// Reset the assert handler. +void debug::resetAssertHandler() +{ + s_assert_handler = NULL; +} + +#if NV_OS_WIN32 || NV_OS_DURANGO +#if NV_USE_SEPARATE_THREAD + +static void initHandlerThread() +{ + static const int kExceptionHandlerThreadInitialStackSize = 64 * 1024; + + // Set synchronization primitives and the handler thread. Each + // ExceptionHandler object gets its own handler thread because that's the + // only way to reliably guarantee sufficient stack space in an exception, + // and it allows an easy way to get a snapshot of the requesting thread's + // context outside of an exception. + InitializeCriticalSection(&s_handler_critical_section); + + s_handler_start_semaphore = CreateSemaphoreExW(NULL, 0, 1, NULL, 0, + SEMAPHORE_MODIFY_STATE | DELETE | SYNCHRONIZE); + nvDebugCheck(s_handler_start_semaphore != NULL); + + s_handler_finish_semaphore = CreateSemaphoreExW(NULL, 0, 1, NULL, 0, + SEMAPHORE_MODIFY_STATE | DELETE | SYNCHRONIZE); + nvDebugCheck(s_handler_finish_semaphore != NULL); + + // Don't attempt to create the thread if we could not create the semaphores. + if (s_handler_finish_semaphore != NULL && s_handler_start_semaphore != NULL) { + DWORD thread_id; + s_handler_thread = CreateThread(NULL, // lpThreadAttributes + kExceptionHandlerThreadInitialStackSize, + ExceptionHandlerThreadMain, + NULL, // lpParameter + 0, // dwCreationFlags + &thread_id); + nvDebugCheck(s_handler_thread != NULL); + } + + /* @@ We should avoid loading modules in the exception handler! + dbghelp_module_ = LoadLibrary(L"dbghelp.dll"); + if (dbghelp_module_) { + minidump_write_dump_ = reinterpret_cast(GetProcAddress(dbghelp_module_, "MiniDumpWriteDump")); + } + */ +} + +static void shutHandlerThread() { + // @@ Free stuff. Terminate thread. +} + +#endif // NV_USE_SEPARATE_THREAD +#endif // NV_OS_WIN32 + + +// Enable signal handler. +void debug::enableSigHandler(bool interactive) +{ + if (s_sig_handler_enabled) return; + + s_sig_handler_enabled = true; + s_interactive = interactive; + +#if (NV_OS_WIN32 && NV_CC_MSVC) || NV_OS_DURANGO + if (interactive) { +#if NV_OS_WIN32 + // Do not display message boxes on error. + // http://msdn.microsoft.com/en-us/library/windows/desktop/ms680621(v=vs.85).aspx + SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX|SEM_NOOPENFILEERRORBOX); +#endif + + // CRT reports errors to debug output only. + // http://msdn.microsoft.com/en-us/library/1y71x448(v=vs.80).aspx + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); + } + + +#if NV_USE_SEPARATE_THREAD + initHandlerThread(); +#else + InitializeCriticalSection(&s_handler_critical_section); +#endif + + s_old_exception_filter = ::SetUnhandledExceptionFilter( handleException ); + +#if _MSC_VER >= 1400 // MSVC 2005/8 + _set_invalid_parameter_handler(handleInvalidParameter); +#endif // _MSC_VER >= 1400 + + _set_purecall_handler(handlePureVirtualCall); + +#if NV_OS_WIN32 + // SYMOPT_DEFERRED_LOADS make us not take a ton of time unless we actual log traces + SymSetOptions(SYMOPT_DEFERRED_LOADS|SYMOPT_FAIL_CRITICAL_ERRORS|SYMOPT_LOAD_LINES|SYMOPT_UNDNAME); + + if (!SymInitialize(GetCurrentProcess(), NULL, TRUE)) { + DWORD error = GetLastError(); + nvDebug("SymInitialize returned error : %d\n", error); + } +#endif + +#elif !NV_OS_WIN32 && defined(NV_HAVE_SIGNAL_H) + + // Install our signal handler + struct sigaction sa; + sa.sa_sigaction = nvSigHandler; + sigemptyset (&sa.sa_mask); + sa.sa_flags = SA_ONSTACK | SA_RESTART | SA_SIGINFO; + + sigaction(SIGSEGV, &sa, &s_old_sigsegv); + sigaction(SIGTRAP, &sa, &s_old_sigtrap); + sigaction(SIGFPE, &sa, &s_old_sigfpe); + sigaction(SIGBUS, &sa, &s_old_sigbus); + +#endif +} + +/// Disable signal handler. +void debug::disableSigHandler() +{ + nvCheck(s_sig_handler_enabled == true); + s_sig_handler_enabled = false; + +#if (NV_OS_WIN32 && NV_CC_MSVC) || NV_OS_DURANGO + + ::SetUnhandledExceptionFilter( s_old_exception_filter ); + s_old_exception_filter = NULL; + +#if NV_OS_WIN32 + SymCleanup(GetCurrentProcess()); +#endif + +#elif !NV_OS_WIN32 && defined(NV_HAVE_SIGNAL_H) + + sigaction(SIGSEGV, &s_old_sigsegv, NULL); + sigaction(SIGTRAP, &s_old_sigtrap, NULL); + sigaction(SIGFPE, &s_old_sigfpe, NULL); + sigaction(SIGBUS, &s_old_sigbus, NULL); + +#endif +} + + +bool debug::isDebuggerPresent() +{ +#if NV_OS_WIN32 + HINSTANCE kernel32 = GetModuleHandleA("kernel32.dll"); + if (kernel32) { + FARPROC IsDebuggerPresent = GetProcAddress(kernel32, "IsDebuggerPresent"); + if (IsDebuggerPresent != NULL && IsDebuggerPresent()) { + return true; + } + } + return false; +#elif NV_OS_XBOX +#ifdef _DEBUG + return DmIsDebuggerPresent() == TRUE; +#else + return false; +#endif +#elif NV_OS_ORBIS + #if PS4_FINAL_REQUIREMENTS + return false; + #else + return sceDbgIsDebuggerAttached() == 1; + #endif +#elif NV_OS_DURANGO + #if XB1_FINAL_REQUIREMENTS + return false; + #else + return IsDebuggerPresent() == TRUE; + #endif +#elif NV_OS_DARWIN + int mib[4]; + struct kinfo_proc info; + size_t size; + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + size = sizeof(info); + info.kp_proc.p_flag = 0; + sysctl(mib,4,&info,&size,NULL,0); + return ((info.kp_proc.p_flag & P_TRACED) == P_TRACED); +#else + // if ppid != sid, some process spawned our app, probably a debugger. + return getsid(getpid()) != getppid(); +#endif +} + +bool debug::attachToDebugger() +{ +#if NV_OS_WIN32 + if (isDebuggerPresent() == FALSE) { + Path process(1024); + process.copy("\""); + GetSystemDirectoryA(process.str() + 1, 1024 - 1); + + process.appendSeparator(); + + process.appendFormat("VSJitDebugger.exe\" -p %lu", ::GetCurrentProcessId()); + + STARTUPINFOA sSi; + memset(&sSi, 0, sizeof(sSi)); + + PROCESS_INFORMATION sPi; + memset(&sPi, 0, sizeof(sPi)); + + BOOL b = CreateProcessA(NULL, process.str(), NULL, NULL, FALSE, 0, NULL, NULL, &sSi, &sPi); + if (b != FALSE) { + ::WaitForSingleObject(sPi.hProcess, INFINITE); + + DWORD dwExitCode; + ::GetExitCodeProcess(sPi.hProcess, &dwExitCode); + if (dwExitCode != 0) //if exit code is zero, a debugger was selected + b = FALSE; + } + + if (sPi.hThread != NULL) ::CloseHandle(sPi.hThread); + if (sPi.hProcess != NULL) ::CloseHandle(sPi.hProcess); + + if (b == FALSE) + return false; + + for (int i = 0; i < 5*60; i++) { + if (isDebuggerPresent()) + break; + ::Sleep(200); + } + } +#endif // NV_OS_WIN32 + + return true; +} diff --git a/thirdparty/thekla_atlas/src/nvcore/Debug.h b/thirdparty/thekla_atlas/src/nvcore/Debug.h new file mode 100755 index 00000000..3804ed47 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/Debug.h @@ -0,0 +1,246 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_CORE_DEBUG_H +#define NV_CORE_DEBUG_H + +#include "nvcore.h" + +#include // va_list + +#if NV_OS_IOS //ACS: maybe we want this for OSX too? +# ifdef __APPLE__ +# include +# include +# endif +#endif + +// Make sure we are using our assert. +#undef assert + +#define NV_ABORT_DEBUG 1 +#define NV_ABORT_IGNORE 2 +#define NV_ABORT_EXIT 3 + +#define nvNoAssert(exp) \ + NV_MULTI_LINE_MACRO_BEGIN \ + (void)sizeof(exp); \ + NV_MULTI_LINE_MACRO_END + +#if NV_NO_ASSERT + +# define nvAssert(exp) nvNoAssert(exp) +# define nvCheck(exp) nvNoAssert(exp) +# define nvDebugAssert(exp) nvNoAssert(exp) +# define nvDebugCheck(exp) nvNoAssert(exp) +# define nvDebugBreak() nvNoAssert(0) + +#else // NV_NO_ASSERT + +# if NV_CC_MSVC + // @@ Does this work in msvc-6 and earlier? +# define nvDebugBreak() __debugbreak() +//# define nvDebugBreak() __asm { int 3 } +# elif NV_OS_ORBIS +# define nvDebugBreak() __debugbreak() +# elif NV_OS_IOS && TARGET_OS_IPHONE +# define nvDebugBreak() raise(SIGINT) +# elif NV_CC_CLANG +# define nvDebugBreak() __builtin_debugtrap() +# elif NV_CC_GNUC +//# define nvDebugBreak() __builtin_debugtrap() // Does GCC have debugtrap? +# define nvDebugBreak() __builtin_trap() +/* +# elif NV_CC_GNUC && NV_CPU_PPC && NV_OS_DARWIN +// @@ Use __builtin_trap() on GCC +# define nvDebugBreak() __asm__ volatile ("trap") +# elif NV_CC_GNUC && NV_CPU_X86 && NV_OS_DARWIN +# define nvDebugBreak() __asm__ volatile ("int3") +# elif NV_CC_GNUC && NV_CPU_X86 +# define nvDebugBreak() __asm__ ( "int %0" : :"I"(3) ) +# elif NV_OS_ORBIS +# define nvDebugBreak() __asm volatile ("int $0x41") +# else +# include +# define nvDebugBreak() raise(SIGTRAP); +// define nvDebugBreak() *((int *)(0)) = 0 +*/ +# endif + +# if NV_CC_MSVC +# define nvExpect(expr) (expr) +#else +# define nvExpect(expr) __builtin_expect((expr) != 0, true) +#endif + +#if NV_CC_CLANG +# if __has_feature(attribute_analyzer_noreturn) +# define NV_ANALYZER_NORETURN __attribute__((analyzer_noreturn)) +# else +# define NV_ANALYZER_NORETURN +# endif +#else +# define NV_ANALYZER_NORETURN +#endif + +#define nvDebugBreakOnce() \ + NV_MULTI_LINE_MACRO_BEGIN \ + static bool firstTime = true; \ + if (firstTime) { firstTime = false; nvDebugBreak(); } \ + NV_MULTI_LINE_MACRO_END + +#define nvAssertMacro(exp) \ + NV_MULTI_LINE_MACRO_BEGIN \ + if (!nvExpect(exp)) { \ + if (nvAbort(#exp, __FILE__, __LINE__, __FUNC__) == NV_ABORT_DEBUG) { \ + nvDebugBreak(); \ + } \ + } \ + NV_MULTI_LINE_MACRO_END + +// GCC, LLVM need "##" before the __VA_ARGS__, MSVC doesn't care +#define nvAssertMacroWithIgnoreAll(exp,...) \ + NV_MULTI_LINE_MACRO_BEGIN \ + static bool ignoreAll = false; \ + if (!ignoreAll && !nvExpect(exp)) { \ + int _result = nvAbort(#exp, __FILE__, __LINE__, __FUNC__, ##__VA_ARGS__); \ + if (_result == NV_ABORT_DEBUG) { \ + nvDebugBreak(); \ + } else if (_result == NV_ABORT_IGNORE) { \ + ignoreAll = true; \ + } \ + } \ + NV_MULTI_LINE_MACRO_END + +// Interesting assert macro from Insomniac: +// http://www.gdcvault.com/play/1015319/Developing-Imperfect-Software-How-to +// Used as follows: +// if (nvCheck(i < count)) { +// normal path +// } else { +// fixup code. +// } +// This style of macro could be combined with __builtin_expect to let the compiler know failure is unlikely. +#define nvCheckMacro(exp) \ + (\ + (exp) ? true : ( \ + (nvAbort(#exp, __FILE__, __LINE__, __FUNC__) == NV_ABORT_DEBUG) ? (nvDebugBreak(), true) : ( false ) \ + ) \ + ) + + +#define nvAssert(exp) nvAssertMacro(exp) +#define nvCheck(exp) nvAssertMacro(exp) + +#if defined(_DEBUG) +# define nvDebugAssert(exp) nvAssertMacro(exp) +# define nvDebugCheck(exp) nvAssertMacro(exp) +#else // _DEBUG +# define nvDebugAssert(exp) nvNoAssert(exp) +# define nvDebugCheck(exp) nvNoAssert(exp) +#endif // _DEBUG + +#endif // NV_NO_ASSERT + +// Use nvAssume for very simple expresions only: nvAssume(0), nvAssume(value == true), etc. +/*#if !defined(_DEBUG) +# if NV_CC_MSVC +# define nvAssume(exp) __assume(exp) +# else +# define nvAssume(exp) nvCheck(exp) +# endif +#else +# define nvAssume(exp) nvCheck(exp) +#endif*/ + +#if defined(_DEBUG) +# if NV_CC_MSVC +# define nvUnreachable() nvAssert(0 && "unreachable"); __assume(0) +# else +# define nvUnreachable() nvAssert(0 && "unreachable"); __builtin_unreachable() +# endif +#else +# if NV_CC_MSVC +# define nvUnreachable() __assume(0) +# else +# define nvUnreachable() __builtin_unreachable() +# endif +#endif + +#define nvError(x) nvAbort(x, __FILE__, __LINE__, __FUNC__) +#define nvWarning(x) nvDebugPrint("*** Warning %s/%d: %s\n", __FILE__, __LINE__, (x)) + +#ifndef NV_DEBUG_PRINT +#define NV_DEBUG_PRINT 1 //defined(_DEBUG) +#endif + +#if NV_DEBUG_PRINT +#define nvDebug(...) nvDebugPrint(__VA_ARGS__) +#else +#if NV_CC_MSVC +#define nvDebug(...) __noop(__VA_ARGS__) +#else +#define nvDebug(...) ((void)0) // Non-msvc platforms do not evaluate arguments? +#endif +#endif + + +NVCORE_API int nvAbort(const char *exp, const char *file, int line, const char * func = NULL, const char * msg = NULL, ...) __attribute__((format (printf, 5, 6))) NV_ANALYZER_NORETURN; +NVCORE_API void NV_CDECL nvDebugPrint( const char *msg, ... ) __attribute__((format (printf, 1, 2))); + +namespace nv +{ + inline bool isValidPtr(const void * ptr) { + #if NV_OS_DARWIN + return true; // IC: Not sure what ranges are OK on OSX. + #endif + + #if NV_CPU_X86_64 + if (ptr == NULL) return true; + if (reinterpret_cast(ptr) < 0x10000ULL) return false; + if (reinterpret_cast(ptr) >= 0x000007FFFFFEFFFFULL) return false; + #else + if (reinterpret_cast(ptr) == 0xcccccccc) return false; + if (reinterpret_cast(ptr) == 0xcdcdcdcd) return false; + if (reinterpret_cast(ptr) == 0xdddddddd) return false; + if (reinterpret_cast(ptr) == 0xffffffff) return false; + #endif + return true; + } + + // Message handler interface. + struct MessageHandler { + virtual void log(const char * str, va_list arg) = 0; + virtual ~MessageHandler() {} + }; + + // Assert handler interface. + struct AssertHandler { + virtual int assertion(const char *exp, const char *file, int line, const char *func, const char *msg, va_list arg) = 0; + virtual ~AssertHandler() {} + }; + + + namespace debug + { + NVCORE_API void dumpInfo(); + NVCORE_API void dumpCallstack( MessageHandler *messageHandler, int callstackLevelsToSkip = 0 ); + + NVCORE_API void setMessageHandler( MessageHandler * messageHandler ); + NVCORE_API void resetMessageHandler(); + + NVCORE_API void setAssertHandler( AssertHandler * assertHanlder ); + NVCORE_API void resetAssertHandler(); + + NVCORE_API void enableSigHandler(bool interactive); + NVCORE_API void disableSigHandler(); + + NVCORE_API bool isDebuggerPresent(); + NVCORE_API bool attachToDebugger(); + + NVCORE_API void terminate(int code); + } + +} // nv namespace + +#endif // NV_CORE_DEBUG_H diff --git a/thirdparty/thekla_atlas/src/nvcore/DefsGnucDarwin.h b/thirdparty/thekla_atlas/src/nvcore/DefsGnucDarwin.h new file mode 100755 index 00000000..afb21c3d --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/DefsGnucDarwin.h @@ -0,0 +1,57 @@ +#ifndef NV_CORE_H +#error "Do not include this file directly." +#endif + +#include // uint8_t, int8_t, ... uintptr_t +#include // operator new, size_t, NULL + +// Function linkage +#define DLL_IMPORT +#if __GNUC__ >= 4 +# define DLL_EXPORT __attribute__((visibility("default"))) +# define DLL_EXPORT_CLASS DLL_EXPORT +#else +# define DLL_EXPORT +# define DLL_EXPORT_CLASS +#endif + +// Function calling modes +#if NV_CPU_X86 +# define NV_CDECL __attribute__((cdecl)) +# define NV_STDCALL __attribute__((stdcall)) +#else +# define NV_CDECL +# define NV_STDCALL +#endif + +#define NV_FASTCALL __attribute__((fastcall)) +#define NV_FORCEINLINE __attribute__((always_inline)) inline +#define NV_DEPRECATED __attribute__((deprecated)) +#if NV_OS_IOS +#define NV_THREAD_LOCAL // @@ IC: Looks like iOS does not have support for TLS declarations. +#else +#define NV_THREAD_LOCAL __thread +#endif + +#if __GNUC__ > 2 +#define NV_PURE __attribute__((pure)) +#define NV_CONST __attribute__((const)) +#else +#define NV_PURE +#define NV_CONST +#endif + +#define NV_NOINLINE __attribute__((noinline)) + +// Define __FUNC__ properly. +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __FUNC__ __PRETTY_FUNCTION__ // __FUNCTION__ +# else +# define __FUNC__ "" +# endif +#else +# define __FUNC__ __PRETTY_FUNCTION__ +#endif + +#define restrict __restrict__ diff --git a/thirdparty/thekla_atlas/src/nvcore/DefsGnucLinux.h b/thirdparty/thekla_atlas/src/nvcore/DefsGnucLinux.h new file mode 100755 index 00000000..2126d866 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/DefsGnucLinux.h @@ -0,0 +1,59 @@ +#ifndef NV_CORE_H +#error "Do not include this file directly." +#endif + +#include // uint8_t, int8_t, ... uintptr_t +#include // operator new, size_t, NULL + +// Function linkage +#define DLL_IMPORT +#if __GNUC__ >= 4 +# define DLL_EXPORT __attribute__((visibility("default"))) +# define DLL_EXPORT_CLASS DLL_EXPORT +#else +# define DLL_EXPORT +# define DLL_EXPORT_CLASS +#endif + +// Function calling modes +#if NV_CPU_X86 +# define NV_CDECL __attribute__((cdecl)) +# define NV_STDCALL __attribute__((stdcall)) +#else +# define NV_CDECL +# define NV_STDCALL +#endif + +#define NV_FASTCALL __attribute__((fastcall)) +//#if __GNUC__ > 3 +// It seems that GCC does not assume always_inline implies inline. I think this depends on the GCC version :( +#define NV_FORCEINLINE inline __attribute__((always_inline)) +//#else +// Some compilers complain that inline and always_inline are redundant. +//#define NV_FORCEINLINE __attribute__((always_inline)) +//#endif +#define NV_DEPRECATED __attribute__((deprecated)) +#define NV_THREAD_LOCAL __thread + +#if __GNUC__ > 2 +#define NV_PURE __attribute__((pure)) +#define NV_CONST __attribute__((const)) +#else +#define NV_PURE +#define NV_CONST +#endif + +#define NV_NOINLINE __attribute__((noinline)) + +// Define __FUNC__ properly. +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __FUNC__ __PRETTY_FUNCTION__ // __FUNCTION__ +# else +# define __FUNC__ "" +# endif +#else +# define __FUNC__ __PRETTY_FUNCTION__ +#endif + +#define restrict __restrict__ diff --git a/thirdparty/thekla_atlas/src/nvcore/DefsGnucWin32.h b/thirdparty/thekla_atlas/src/nvcore/DefsGnucWin32.h new file mode 100755 index 00000000..f35ed885 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/DefsGnucWin32.h @@ -0,0 +1,65 @@ +#ifndef NV_CORE_H +#error "Do not include this file directly." +#endif + +//#include // size_t, NULL + +// Function linkage +#define DLL_IMPORT __declspec(dllimport) +#define DLL_EXPORT __declspec(dllexport) +#define DLL_EXPORT_CLASS DLL_EXPORT + +// Function calling modes +#if NV_CPU_X86 +# define NV_CDECL __attribute__((cdecl)) +# define NV_STDCALL __attribute__((stdcall)) +#else +# define NV_CDECL +# define NV_STDCALL +#endif + +#define NV_FASTCALL __attribute__((fastcall)) +#define NV_FORCEINLINE __attribute__((always_inline)) +#define NV_DEPRECATED __attribute__((deprecated)) + +#if __GNUC__ > 2 +#define NV_PURE __attribute__((pure)) +#define NV_CONST __attribute__((const)) +#else +#define NV_PURE +#define NV_CONST +#endif + +#define NV_NOINLINE __attribute__((noinline)) + +// Define __FUNC__ properly. +#if __STDC_VERSION__ < 199901L +# if __GNUC__ >= 2 +# define __FUNC__ __PRETTY_FUNCTION__ // __FUNCTION__ +# else +# define __FUNC__ "" +# endif +#else +# define __FUNC__ __PRETTY_FUNCTION__ +#endif + +#define restrict __restrict__ + +/* +// Type definitions +typedef unsigned char uint8; +typedef signed char int8; + +typedef unsigned short uint16; +typedef signed short int16; + +typedef unsigned int uint32; +typedef signed int int32; + +typedef unsigned long long uint64; +typedef signed long long int64; + +// Aliases +typedef uint32 uint; +*/ + diff --git a/thirdparty/thekla_atlas/src/nvcore/DefsVcWin32.h b/thirdparty/thekla_atlas/src/nvcore/DefsVcWin32.h new file mode 100755 index 00000000..a915f379 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/DefsVcWin32.h @@ -0,0 +1,94 @@ +// This code is in the public domain -- Ignacio Castańo + +#ifndef NV_CORE_H +#error "Do not include this file directly." +#endif + +// Function linkage +#define DLL_IMPORT __declspec(dllimport) +#define DLL_EXPORT __declspec(dllexport) +#define DLL_EXPORT_CLASS DLL_EXPORT + +// Function calling modes +#define NV_CDECL __cdecl +#define NV_STDCALL __stdcall +#define NV_FASTCALL __fastcall +#define NV_DEPRECATED + +#define NV_PURE +#define NV_CONST + +// Set standard function names. +#if _MSC_VER < 1900 +# define snprintf _snprintf +#endif +#if _MSC_VER < 1500 +# define vsnprintf _vsnprintf +#endif +#if _MSC_VER < 1700 +# define strtoll _strtoi64 +# define strtoull _strtoui64 +#endif +//#define chdir _chdir +#define getcwd _getcwd + +#if _MSC_VER <= 1600 +#define va_copy(a, b) (a) = (b) +#endif + +#if !defined restrict +#define restrict +#endif + +// Ignore gcc attributes. +#define __attribute__(X) + +#if !defined __FUNC__ +#define __FUNC__ __FUNCTION__ +#endif + +#define NV_NOINLINE __declspec(noinline) +#define NV_FORCEINLINE __forceinline + +#define NV_THREAD_LOCAL __declspec(thread) + +/* +// Type definitions +typedef unsigned char uint8; +typedef signed char int8; + +typedef unsigned short uint16; +typedef signed short int16; + +typedef unsigned int uint32; +typedef signed int int32; + +typedef unsigned __int64 uint64; +typedef signed __int64 int64; + +// Aliases +typedef uint32 uint; +*/ + +// Unwanted VC++ warnings to disable. +/* +#pragma warning(disable : 4244) // conversion to float, possible loss of data +#pragma warning(disable : 4245) // conversion from 'enum ' to 'unsigned long', signed/unsigned mismatch +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4514) // unreferenced inline function has been removed +#pragma warning(disable : 4710) // inline function not expanded +#pragma warning(disable : 4127) // Conditional expression is constant +#pragma warning(disable : 4305) // truncation from 'const double' to 'float' +#pragma warning(disable : 4505) // unreferenced local function has been removed + +#pragma warning(disable : 4702) // unreachable code in inline expanded function +#pragma warning(disable : 4711) // function selected for automatic inlining +#pragma warning(disable : 4725) // Pentium fdiv bug + +#pragma warning(disable : 4786) // Identifier was truncated and cannot be debugged. + +#pragma warning(disable : 4675) // resolved overload was found by argument-dependent lookup +*/ + +#pragma warning(1 : 4705) // Report unused local variables. +#pragma warning(1 : 4555) // Expression has no effect. diff --git a/thirdparty/thekla_atlas/src/nvcore/FileSystem.cpp b/thirdparty/thekla_atlas/src/nvcore/FileSystem.cpp new file mode 100755 index 00000000..5ed0ca07 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/FileSystem.cpp @@ -0,0 +1,75 @@ +// This code is in the public domain -- castano@gmail.com + +#include "FileSystem.h" + +#if NV_OS_WIN32 +#define _CRT_NONSTDC_NO_WARNINGS // _chdir is defined deprecated, but that's a bug, chdir is deprecated, _chdir is *not*. +//#include // PathFileExists +#include // GetFileAttributes +#include // _mkdir +#elif NV_OS_XBOX +#include +#elif NV_OS_DURANGO +#include +#else +#include +#include +#include +#endif +#include // remove, unlink + +using namespace nv; + + +bool FileSystem::exists(const char * path) +{ +#if NV_OS_UNIX + return access(path, F_OK|R_OK) == 0; + //struct stat buf; + //return stat(path, &buf) == 0; +#elif NV_OS_WIN32 || NV_OS_XBOX || NV_OS_DURANGO + // PathFileExists requires linking to shlwapi.lib + //return PathFileExists(path) != 0; + return GetFileAttributesA(path) != INVALID_FILE_ATTRIBUTES; +#else + if (FILE * fp = fopen(path, "r")) + { + fclose(fp); + return true; + } + return false; +#endif +} + +bool FileSystem::createDirectory(const char * path) +{ +#if NV_OS_WIN32 || NV_OS_XBOX || NV_OS_DURANGO + return CreateDirectoryA(path, NULL) != 0; +#elif NV_OS_ORBIS + // not implemented + return false; +#else + return mkdir(path, 0777) != -1; +#endif +} + +bool FileSystem::changeDirectory(const char * path) +{ +#if NV_OS_WIN32 + return _chdir(path) != -1; +#elif NV_OS_XBOX || NV_OS_DURANGO + // Xbox doesn't support Current Working Directory! + return false; +#elif NV_OS_ORBIS + // Orbis doesn't support Current Working Directory! + return false; +#else + return chdir(path) != -1; +#endif +} + +bool FileSystem::removeFile(const char * path) +{ + // @@ Use unlink or remove? + return remove(path) == 0; +} diff --git a/thirdparty/thekla_atlas/src/nvcore/FileSystem.h b/thirdparty/thekla_atlas/src/nvcore/FileSystem.h new file mode 100755 index 00000000..afd0f449 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/FileSystem.h @@ -0,0 +1,24 @@ +// This code is in the public domain -- castano@gmail.com + +#pragma once +#ifndef NV_CORE_FILESYSTEM_H +#define NV_CORE_FILESYSTEM_H + +#include "nvcore.h" + +namespace nv +{ + + namespace FileSystem + { + NVCORE_API bool exists(const char * path); + NVCORE_API bool createDirectory(const char * path); + NVCORE_API bool changeDirectory(const char * path); + NVCORE_API bool removeFile(const char * path); + + } // FileSystem namespace + +} // nv namespace + + +#endif // NV_CORE_FILESYSTEM_H diff --git a/thirdparty/thekla_atlas/src/nvcore/ForEach.h b/thirdparty/thekla_atlas/src/nvcore/ForEach.h new file mode 100755 index 00000000..bc66f424 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/ForEach.h @@ -0,0 +1,71 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_CORE_FOREACH_H +#define NV_CORE_FOREACH_H + +/* +These foreach macros are very non-standard and somewhat confusing, but I like them. +*/ + +#include "nvcore.h" + + +#if NV_CC_CPP11 + +#define NV_FOREACH(i, container) \ + for (auto i = (container).start(); !(container).isDone(i); (container).advance(i)) + +#elif NV_CC_GNUC // If typeof is available: + +/* +Ideally we would like to write this: + +#define NV_FOREACH(i, container) \ + for(decltype(container)::PseudoIndex i((container).start()); !(container).isDone(i); (container).advance(i)) + +But gcc versions prior to 4.7 required an intermediate type. See: +https://gcc.gnu.org/bugzilla/show_bug.cgi?id=6709 +*/ + +#define NV_FOREACH(i, container) \ + typedef typeof(container) NV_STRING_JOIN2(cont,__LINE__); \ + for(NV_STRING_JOIN2(cont,__LINE__)::PseudoIndex i((container).start()); !(container).isDone(i); (container).advance(i)) + +#else // If typeof not available: + +#define NV_NEED_PSEUDOINDEX_WRAPPER 1 + +#include // placement new + +struct PseudoIndexWrapper { + template + PseudoIndexWrapper(const T & container) { + nvStaticCheck(sizeof(typename T::PseudoIndex) <= sizeof(memory)); + new (memory) typename T::PseudoIndex(container.start()); + } + // PseudoIndex cannot have a dtor! + + template typename T::PseudoIndex & operator()(const T * /*container*/) { + return *reinterpret_cast(memory); + } + template const typename T::PseudoIndex & operator()(const T * /*container*/) const { + return *reinterpret_cast(memory); + } + + uint8 memory[4]; // Increase the size if we have bigger enumerators. +}; + +#define NV_FOREACH(i, container) \ + for(PseudoIndexWrapper i(container); !(container).isDone(i(&(container))); (container).advance(i(&(container)))) + +#endif + +// Declare foreach keyword. +#if !defined NV_NO_USE_KEYWORDS +# define foreach NV_FOREACH +# define foreach_index NV_FOREACH +#endif + + +#endif // NV_CORE_FOREACH_H diff --git a/thirdparty/thekla_atlas/src/nvcore/Hash.h b/thirdparty/thekla_atlas/src/nvcore/Hash.h new file mode 100755 index 00000000..a8b0b2c6 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/Hash.h @@ -0,0 +1,83 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_CORE_HASH_H +#define NV_CORE_HASH_H + +#include "nvcore.h" + +namespace nv +{ + inline uint sdbmHash(const void * data_in, uint size, uint h = 5381) + { + const uint8 * data = (const uint8 *) data_in; + uint i = 0; + while (i < size) { + h = (h << 16) + (h << 6) - h + (uint) data[i++]; + } + return h; + } + + // Note that this hash does not handle NaN properly. + inline uint sdbmFloatHash(const float * f, uint count, uint h = 5381) + { + for (uint i = 0; i < count; i++) { + //nvDebugCheck(nv::isFinite(*f)); + union { float f; uint32 i; } x = { f[i] }; + if (x.i == 0x80000000) x.i = 0; + h = sdbmHash(&x, 4, h); + } + return h; + } + + + template + inline uint hash(const T & t, uint h = 5381) + { + return sdbmHash(&t, sizeof(T), h); + } + + template <> + inline uint hash(const float & f, uint h) + { + return sdbmFloatHash(&f, 1, h); + } + + + // Functors for hash table: + template struct Hash + { + uint operator()(const Key & k) const { + return hash(k); + } + }; + + template struct Equal + { + bool operator()(const Key & k0, const Key & k1) const { + return k0 == k1; + } + }; + + + // @@ Move to Utils.h? + template + struct Pair { + T1 first; + T2 second; + }; + + template + bool operator==(const Pair & p0, const Pair & p1) { + return p0.first == p1.first && p0.second == p1.second; + } + + template + uint hash(const Pair & p, uint h = 5381) { + return hash(p.second, hash(p.first)); + } + + +} // nv namespace + +#endif // NV_CORE_HASH_H diff --git a/thirdparty/thekla_atlas/src/nvcore/HashMap.h b/thirdparty/thekla_atlas/src/nvcore/HashMap.h new file mode 100755 index 00000000..7856d6a8 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/HashMap.h @@ -0,0 +1,174 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_CORE_HASHMAP_H +#define NV_CORE_HASHMAP_H + +/* +HashMap based on Thatcher Ulrich container, donated to the Public Domain. + +I'd like to do something to reduce the amount of code generated with this template. The type of +U is largely irrelevant to the generated code, except for calls to constructors and destructors, +but the combination of all T and U pairs, generate a large amounts of code. + +HashMap is not used in NVTT, so it could be removed from the repository. +*/ + + +#include "Memory.h" +#include "Debug.h" +#include "ForEach.h" +#include "Hash.h" + +namespace nv +{ + class Stream; + + /** Thatcher Ulrich's hash table. + * + * Hash table, linear probing, internal chaining. One + * interesting/nice thing about this implementation is that the table + * itself is a flat chunk of memory containing no pointers, only + * relative indices. If the key and value types of the hash contain + * no pointers, then the hash can be serialized using raw IO. Could + * come in handy. + * + * Never shrinks, unless you explicitly clear() it. Expands on + * demand, though. For best results, if you know roughly how big your + * table will be, default it to that size when you create it. + */ + template, typename E = Equal > + class NVCORE_CLASS HashMap + { + NV_FORBID_COPY(HashMap); + public: + + /// Default ctor. + HashMap() : entry_count(0), size_mask(-1), table(NULL) { } + + /// Ctor with size hint. + explicit HashMap(int size_hint) : entry_count(0), size_mask(-1), table(NULL) { setCapacity(size_hint); } + + /// Dtor. + ~HashMap() { clear(); } + + + void set(const T& key, const U& value); + void add(const T& key, const U& value); + bool remove(const T& key); + void clear(); + bool isEmpty() const; + bool get(const T& key, U* value = NULL, T* other_key = NULL) const; + bool contains(const T & key) const; + int size() const; + int count() const; + int capacity() const; + void checkExpand(); + void resize(int n); + + void setCapacity(int new_size); + + // Behaves much like std::pair. + struct Entry + { + int next_in_chain; // internal chaining for collisions + uint hash_value; // avoids recomputing. Worthwhile? + T key; + U value; + + Entry() : next_in_chain(-2) {} + Entry(const Entry& e) : next_in_chain(e.next_in_chain), hash_value(e.hash_value), key(e.key), value(e.value) {} + Entry(const T& k, const U& v, int next, int hash) : next_in_chain(next), hash_value(hash), key(k), value(v) {} + + bool isEmpty() const { return next_in_chain == -2; } + bool isEndOfChain() const { return next_in_chain == -1; } + bool isTombstone() const { return hash_value == TOMBSTONE_HASH; } + + void clear() { + key.~T(); // placement delete + value.~U(); // placement delete + next_in_chain = -2; + hash_value = ~TOMBSTONE_HASH; + } + + void makeTombstone() { + key.~T(); + value.~U(); + hash_value = TOMBSTONE_HASH; + } + }; + + + // HashMap enumerator. + typedef int PseudoIndex; + PseudoIndex start() const { PseudoIndex i = 0; findNext(i); return i; } + bool isDone(const PseudoIndex & i) const { nvDebugCheck(i <= size_mask+1); return i == size_mask+1; }; + void advance(PseudoIndex & i) const { nvDebugCheck(i <= size_mask+1); i++; findNext(i); } + +#if NV_NEED_PSEUDOINDEX_WRAPPER + Entry & operator[]( const PseudoIndexWrapper & i ) { + Entry & e = entry(i(this)); + nvDebugCheck(e.isTombstone() == false); + return e; + } + const Entry & operator[]( const PseudoIndexWrapper & i ) const { + const Entry & e = entry(i(this)); + nvDebugCheck(e.isTombstone() == false); + return e; + } +#else + Entry & operator[](const PseudoIndex & i) { + Entry & e = entry(i); + nvDebugCheck(e.isTombstone() == false); + return e; + } + const Entry & operator[](const PseudoIndex & i) const { + const Entry & e = entry(i); + nvDebugCheck(e.isTombstone() == false); + return e; + } +#endif + + + // By default we serialize the key-value pairs compactl y. + template + friend Stream & operator<< (Stream & s, HashMap<_T, _U, _H, _E> & map); + + // This requires more storage, but saves us from rehashing the elements. + template + friend Stream & rawSerialize(Stream & s, HashMap<_T, _U, _H, _E> & map); + + /// Swap the members of this vector and the given vector. + template + friend void swap(HashMap<_T, _U, _H, _E> & a, HashMap<_T, _U, _H, _E> & b); + + private: + static const uint TOMBSTONE_HASH = (uint) -1; + + uint compute_hash(const T& key) const; + + // Find the index of the matching entry. If no match, then return -1. + int findIndex(const T& key) const; + + // Return the index of the newly cleared element. + int removeTombstone(int index); + + // Helpers. + Entry & entry(int index); + const Entry & entry(int index) const; + + void setRawCapacity(int new_size); + + // Move the enumerator to the next valid element. + void findNext(PseudoIndex & i) const; + + + int entry_count; + int size_mask; + Entry * table; + + }; + +} // nv namespace + +#endif // NV_CORE_HASHMAP_H diff --git a/thirdparty/thekla_atlas/src/nvcore/HashMap.inl b/thirdparty/thekla_atlas/src/nvcore/HashMap.inl new file mode 100755 index 00000000..f0b6bfea --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/HashMap.inl @@ -0,0 +1,550 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_CORE_HASHMAP_INL +#define NV_CORE_HASHMAP_INL + +#include "HashMap.h" + +#include "Stream.h" +#include "Utils.h" // swap + +#include // for placement new + + +namespace nv +{ + + // Set a new or existing value under the key, to the value. + template + void HashMap::set(const T& key, const U& value) + { + int index = findIndex(key); + if (index >= 0) + { + entry(index).value = value; + return; + } + + // Entry under key doesn't exist. + add(key, value); + } + + + // Add a new value to the hash table, under the specified key. + template + void HashMap::add(const T& key, const U& value) + { + nvCheck(findIndex(key) == -1); + + checkExpand(); + nvCheck(table != NULL); + entry_count++; + + const uint hash_value = compute_hash(key); + const int index = hash_value & size_mask; + + Entry * natural_entry = &(entry(index)); + + if (natural_entry->isEmpty()) + { + // Put the new entry in. + new (natural_entry) Entry(key, value, -1, hash_value); + } + else if (natural_entry->isTombstone()) { + // Put the new entry in, without disturbing the rest of the chain. + int next_in_chain = natural_entry->next_in_chain; + new (natural_entry) Entry(key, value, next_in_chain, hash_value); + } + else + { + // Find a blank spot. + int blank_index = index; + for (int search_count = 0; ; search_count++) + { + blank_index = (blank_index + 1) & size_mask; + if (entry(blank_index).isEmpty()) break; // found it + if (entry(blank_index).isTombstone()) { + blank_index = removeTombstone(blank_index); + break; + } + nvCheck(search_count < this->size_mask); + } + Entry * blank_entry = &entry(blank_index); + + if (int(natural_entry->hash_value & size_mask) == index) + { + // Collision. Link into this chain. + + // Move existing list head. + new (blank_entry) Entry(*natural_entry); // placement new, copy ctor + + // Put the new info in the natural entry. + natural_entry->key = key; + natural_entry->value = value; + natural_entry->next_in_chain = blank_index; + natural_entry->hash_value = hash_value; + } + else + { + // Existing entry does not naturally + // belong in this slot. Existing + // entry must be moved. + + // Find natural location of collided element (i.e. root of chain) + int collided_index = natural_entry->hash_value & size_mask; + for (int search_count = 0; ; search_count++) + { + Entry * e = &entry(collided_index); + if (e->next_in_chain == index) + { + // Here's where we need to splice. + new (blank_entry) Entry(*natural_entry); + e->next_in_chain = blank_index; + break; + } + collided_index = e->next_in_chain; + nvCheck(collided_index >= 0 && collided_index <= size_mask); + nvCheck(search_count <= size_mask); + } + + // Put the new data in the natural entry. + natural_entry->key = key; + natural_entry->value = value; + natural_entry->hash_value = hash_value; + natural_entry->next_in_chain = -1; + } + } + } + + + // Remove the first value under the specified key. + template + bool HashMap::remove(const T& key) + { + if (table == NULL) + { + return false; + } + + int index = findIndex(key); + if (index < 0) + { + return false; + } + + Entry * pos = &entry(index); + + int natural_index = (int) (pos->hash_value & size_mask); + + if (index != natural_index) { + // We're not the head of our chain, so we can + // be spliced out of it. + + // Iterate up the chain, and splice out when + // we get to m_index. + Entry* e = &entry(natural_index); + while (e->next_in_chain != index) { + nvDebugCheck(e->isEndOfChain() == false); + e = &entry(e->next_in_chain); + } + + if (e->isTombstone() && pos->isEndOfChain()) { + // Tombstone has nothing else to point + // to, so mark it empty. + e->next_in_chain = -2; + } else { + e->next_in_chain = pos->next_in_chain; + } + + pos->clear(); + } + else if (pos->isEndOfChain() == false) { + // We're the head of our chain, and there are + // additional elements. + // + // We need to put a tombstone here. + // + // We can't clear the element, because the + // rest of the elements in the chain must be + // linked to this position. + // + // We can't move any of the succeeding + // elements in the chain (i.e. to fill this + // entry), because we don't want to invalidate + // any other existing iterators. + pos->makeTombstone(); + } else { + // We're the head of the chain, but we're the + // only member of the chain. + pos->clear(); + } + + entry_count--; + + return true; + } + + + // Remove all entries from the hash table. + template + void HashMap::clear() + { + if (table != NULL) + { + // Delete the entries. + for (int i = 0, n = size_mask; i <= n; i++) + { + Entry * e = &entry(i); + if (e->isEmpty() == false && e->isTombstone() == false) + { + e->clear(); + } + } + free(table); + table = NULL; + entry_count = 0; + size_mask = -1; + } + } + + + // Returns true if the hash is empty. + template + bool HashMap::isEmpty() const + { + return table == NULL || entry_count == 0; + } + + + // Retrieve the value under the given key. + // - If there's no value under the key, then return false and leave *value alone. + // - If there is a value, return true, and set *value to the entry's value. + // - If value == NULL, return true or false according to the presence of the key, but don't touch *value. + template + bool HashMap::get(const T& key, U* value/*= NULL*/, T* other_key/*= NULL*/) const + { + int index = findIndex(key); + if (index >= 0) + { + if (value != NULL) { + *value = entry(index).value; // take care with side-effects! + } + if (other_key != NULL) { + *other_key = entry(index).key; + } + return true; + } + return false; + } + + // Determine if the given key is contained in the hash. + template + bool HashMap::contains(const T & key) const + { + return get(key); + } + + // Number of entries in the hash. + template + int HashMap::size() const + { + return entry_count; + } + + // Number of entries in the hash. + template + int HashMap::count() const + { + return size(); + } + + template + int HashMap::capacity() const + { + return size_mask+1; + } + + + // Resize the hash table to fit one more entry. Often this doesn't involve any action. + template + void HashMap::checkExpand() + { + if (table == NULL) { + // Initial creation of table. Make a minimum-sized table. + setRawCapacity(16); + } + else if (entry_count * 3 > (size_mask + 1) * 2) { + // Table is more than 2/3rds full. Expand. + setRawCapacity(entry_count * 2); + } + } + + + // Hint the bucket count to >= n. + template + void HashMap::resize(int n) + { + // Not really sure what this means in relation to + // STLport's hash_map... they say they "increase the + // bucket count to at least n" -- but does that mean + // their real capacity after resize(n) is more like + // n*2 (since they do linked-list chaining within + // buckets?). + setCapacity(n); + } + + + // Size the hash so that it can comfortably contain the given number of elements. If the hash already contains more + // elements than new_size, then this may be a no-op. + template + void HashMap::setCapacity(int new_size) + { + int new_raw_size = (new_size * 3) / 2; + if (new_raw_size < size()) { return; } + + setRawCapacity(new_raw_size); + } + + + // By default we serialize the key-value pairs compactly. + template + Stream & operator<< (Stream & s, HashMap<_T, _U, _H, _E> & map) + { + typedef typename HashMap<_T, _U, _H, _E>::Entry HashMapEntry; + + int entry_count = map.entry_count; + s << entry_count; + + if (s.isLoading()) { + map.clear(); + if(entry_count == 0) { + return s; + } + map.entry_count = entry_count; + map.size_mask = nextPowerOfTwo(U32(entry_count)) - 1; + map.table = malloc(map.size_mask + 1); + + for (int i = 0; i <= map.size_mask; i++) { + map.table[i].next_in_chain = -2; // mark empty + } + + _T key; + _U value; + for (int i = 0; i < entry_count; i++) { + s << key << value; + map.add(key, value); + } + } + else { + int i = 0; + map.findNext(i); + while (i != map.size_mask+1) { + HashMapEntry & e = map.entry(i); + + s << e.key << e.value; + + i++; + map.findNext(i); + } + //for(HashMap<_T, _U, _H, _E>::PseudoIndex i((map).start()); !(map).isDone(i); (map).advance(i)) { + //foreach(i, map) { + // s << map[i].key << map[i].value; + //} + } + + return s; + } + + // This requires more storage, but saves us from rehashing the elements. + template + Stream & rawSerialize(Stream & s, HashMap<_T, _U, _H, _E> & map) + { + typedef typename HashMap<_T, _U, _H, _E>::Entry HashMapEntry; + + if (s.isLoading()) { + map.clear(); + } + + s << map.size_mask; + + if (map.size_mask != -1) { + s << map.entry_count; + + if (s.isLoading()) { + map.table = new HashMapEntry[map.size_mask+1]; + } + + for (int i = 0; i <= map.size_mask; i++) { + HashMapEntry & e = map.table[i]; + s << e.next_in_chain << e.hash_value; + s << e.key; + s << e.value; + } + } + + return s; + } + + // Swap the members of this vector and the given vector. + template + void swap(HashMap<_T, _U, _H, _E> & a, HashMap<_T, _U, _H, _E> & b) + { + swap(a.entry_count, b.entry_count); + swap(a.size_mask, b.size_mask); + swap(a.table, b.table); + } + + + template + uint HashMap::compute_hash(const T& key) const + { + H hash; + uint hash_value = hash(key); + if (hash_value == TOMBSTONE_HASH) { + hash_value ^= 0x8000; + } + return hash_value; + } + + // Find the index of the matching entry. If no match, then return -1. + template + int HashMap::findIndex(const T& key) const + { + if (table == NULL) return -1; + + E equal; + + uint hash_value = compute_hash(key); + int index = hash_value & size_mask; + + const Entry * e = &entry(index); + if (e->isEmpty()) return -1; + if (e->isTombstone() == false && int(e->hash_value & size_mask) != index) { + // occupied by a collider + return -1; + } + + for (;;) + { + nvCheck(e->isTombstone() || (e->hash_value & size_mask) == (hash_value & size_mask)); + + if (e->hash_value == hash_value && equal(e->key, key)) + { + // Found it. + return index; + } + nvDebugCheck(e->isTombstone() || !equal(e->key, key)); // keys are equal, but hash differs! + + // Keep looking through the chain. + index = e->next_in_chain; + if (index == -1) break; // end of chain + + nvCheck(index >= 0 && index <= size_mask); + e = &entry(index); + + nvCheck(e->isEmpty() == false || e->isTombstone()); + } + return -1; + } + + // Return the index of the newly cleared element. + template + int HashMap::removeTombstone(int index) { + Entry* e = &entry(index); + nvCheck(e->isTombstone()); + nvCheck(!e->isEndOfChain()); + + // Move the next element of the chain into the + // tombstone slot, and return the vacated element. + int new_blank_index = e->next_in_chain; + Entry* new_blank = &entry(new_blank_index); + new (e) Entry(*new_blank); + new_blank->clear(); + return new_blank_index; + } + + // Helpers. + template + typename HashMap::Entry & HashMap::entry(int index) + { + nvDebugCheck(table != NULL); + nvDebugCheck(index >= 0 && index <= size_mask); + return table[index]; + } + template + const typename HashMap::Entry & HashMap::entry(int index) const + { + nvDebugCheck(table != NULL); + nvDebugCheck(index >= 0 && index <= size_mask); + return table[index]; + } + + + // Resize the hash table to the given size (Rehash the contents of the current table). The arg is the number of + // hash table entries, not the number of elements we should actually contain (which will be less than this). + template + void HashMap::setRawCapacity(int new_size) + { + if (new_size <= 0) { + // Special case. + clear(); + return; + } + + // Force new_size to be a power of two. + new_size = nextPowerOfTwo(U32(new_size)); + + HashMap new_hash; + new_hash.table = malloc(new_size); + nvDebugCheck(new_hash.table != NULL); + + new_hash.entry_count = 0; + new_hash.size_mask = new_size - 1; + for (int i = 0; i < new_size; i++) + { + new_hash.entry(i).next_in_chain = -2; // mark empty + } + + // Copy stuff to new_hash + if (table != NULL) + { + for (int i = 0, n = size_mask; i <= n; i++) + { + Entry * e = &entry(i); + if (e->isEmpty() == false && e->isTombstone() == false) + { + // Insert old entry into new hash. + new_hash.add(e->key, e->value); + e->clear(); // placement delete of old element + } + } + + // Delete our old data buffer. + free(table); + } + + // Steal new_hash's data. + entry_count = new_hash.entry_count; + size_mask = new_hash.size_mask; + table = new_hash.table; + new_hash.entry_count = 0; + new_hash.size_mask = -1; + new_hash.table = NULL; + } + + // Move the enumerator to the next valid element. + template + void HashMap::findNext(PseudoIndex & i) const { + while (i <= size_mask) { + const Entry & e = entry(i); + if (e.isEmpty() == false && e.isTombstone() == false) { + break; + } + i++; + } + } + +} // nv namespace + +#endif // NV_CORE_HASHMAP_INL diff --git a/thirdparty/thekla_atlas/src/nvcore/Memory.cpp b/thirdparty/thekla_atlas/src/nvcore/Memory.cpp new file mode 100755 index 00000000..0ebd009d --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/Memory.cpp @@ -0,0 +1,149 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "Memory.h" +#include "Debug.h" +#include "Utils.h" + +#include + +#define USE_EFENCE 0 + +#if USE_EFENCE +extern "C" void *EF_malloc(size_t size); +extern "C" void *EF_realloc(void * oldBuffer, size_t newSize); +extern "C" void EF_free(void * address); +#endif + +using namespace nv; + +#if NV_OVERRIDE_ALLOC + +void * malloc(size_t size) +{ +#if USE_EFENCE + return EF_malloc(size); +#else + return ::malloc(size); +#endif +} + +void * debug_malloc(size_t size, const char * file, int line) +{ + NV_UNUSED(file); + NV_UNUSED(line); +#if USE_EFENCE + return EF_malloc(size); +#else + return ::malloc(size); +#endif +} + +void free(void * ptr) +{ +#if USE_EFENCE + return EF_free(const_cast(ptr)); +#else + ::free(const_cast(ptr)); +#endif +} + +void * realloc(void * ptr, size_t size) +{ + nvDebugCheck(ptr != NULL || size != 0); // undefined realloc behavior. +#if USE_EFENCE + return EF_realloc(ptr, size); +#else + return ::realloc(ptr, size); +#endif +} + + +/* No need to override this unless we want line info. +void * operator new (size_t size) throw() +{ + return malloc(size); +} + +void operator delete (void *p) throw() +{ + free(p); +} + +void * operator new [] (size_t size) throw() +{ + return malloc(size); +} + +void operator delete [] (void * p) throw() +{ + free(p); +} +*/ + +#if 0 // Code from Apple: +void* operator new(std::size_t sz) throw (std::bad_alloc) +{ + void *result = std::malloc (sz == 0 ? 1 : sz); + if (result == NULL) + throw std::bad_alloc(); + gNewCounter++; + return result; +} +void operator delete(void* p) throw() +{ + if (p == NULL) + return; + std::free (p); + gDeleteCounter++; +} + +/* These are the 'nothrow' versions of the above operators. + The system version will try to call a std::new_handler if they + fail, but your overriding versions are not required to do this. */ +void* operator new(std::size_t sz, const std::nothrow_t&) throw() +{ + try { + void * result = ::operator new (sz); // calls our overridden operator new + return result; + } catch (std::bad_alloc &) { + return NULL; + } +} +void operator delete(void* p, const std::nothrow_t&) throw() +{ + ::operator delete (p); +} + +#endif // 0 + +#endif // NV_OVERRIDE_ALLOC + +void * nv::aligned_malloc(size_t size, size_t alignment) +{ + // alignment must be a power of two, multiple of sizeof(void*) + nvDebugCheck(isPowerOfTwo(alignment)); + nvDebugCheck((alignment & (sizeof(void*) - 1)) == 0); + +#if NV_OS_WIN32 || NV_OS_DURANGO + return _aligned_malloc(size, alignment); +#elif NV_OS_DARWIN && !NV_OS_IOS + void * ptr = NULL; + posix_memalign(&ptr, alignment, size); + return ptr; +#elif NV_OS_LINUX + return memalign(alignment, size); +#else // NV_OS_ORBIS || NV_OS_IOS + // @@ IC: iOS appears to be 16 byte aligned, should we check alignment and assert if we request a higher alignment factor? + return ::malloc(size); +#endif +} + +void nv::aligned_free(void * ptr) +{ +#if NV_OS_WIN32 || NV_OS_DURANGO + _aligned_free(ptr); +#else + ::free(ptr); +#endif +} + diff --git a/thirdparty/thekla_atlas/src/nvcore/Memory.h b/thirdparty/thekla_atlas/src/nvcore/Memory.h new file mode 100755 index 00000000..1f71b609 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/Memory.h @@ -0,0 +1,72 @@ +// This code is in the public domain -- Ignacio CastaƱo + +#pragma once +#ifndef NV_CORE_MEMORY_H +#define NV_CORE_MEMORY_H + +#include "nvcore.h" + +#include // malloc(), realloc() and free() +#include // memset +//#include // size_t + +//#include // new and delete + +#define TRACK_MEMORY_LEAKS 0 +#if TRACK_MEMORY_LEAKS +#include +#endif + + +#if NV_CC_GNUC +# define NV_ALIGN_16 __attribute__ ((__aligned__ (16))) +#else +# define NV_ALIGN_16 __declspec(align(16)) +#endif + + +#define NV_OVERRIDE_ALLOC 0 + +#if NV_OVERRIDE_ALLOC + +// Custom memory allocator +extern "C" { + NVCORE_API void * malloc(size_t size); + NVCORE_API void * debug_malloc(size_t size, const char * file, int line); + NVCORE_API void free(void * ptr); + NVCORE_API void * realloc(void * ptr, size_t size); +} + +/* +#ifdef _DEBUG +#define new new(__FILE__, __LINE__) +#define malloc(i) debug_malloc(i, __FILE__, __LINE__) +#endif +*/ + +#endif + +namespace nv { + NVCORE_API void * aligned_malloc(size_t size, size_t alignment); + NVCORE_API void aligned_free(void * ); + + // C++ helpers. + template NV_FORCEINLINE T * malloc(size_t count) { + return (T *)::malloc(sizeof(T) * count); + } + + template NV_FORCEINLINE T * realloc(T * ptr, size_t count) { + return (T *)::realloc(ptr, sizeof(T) * count); + } + + template NV_FORCEINLINE void free(const T * ptr) { + ::free((void *)ptr); + } + + template NV_FORCEINLINE void zero(T & data) { + memset(&data, 0, sizeof(T)); + } + +} // nv namespace + +#endif // NV_CORE_MEMORY_H diff --git a/thirdparty/thekla_atlas/src/nvcore/Ptr.h b/thirdparty/thekla_atlas/src/nvcore/Ptr.h new file mode 100755 index 00000000..b4303927 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/Ptr.h @@ -0,0 +1,322 @@ +// This code is in the public domain -- Ignacio Castańo + +#ifndef NV_CORE_PTR_H +#define NV_CORE_PTR_H + +#include "nvcore.h" +#include "Debug.h" + +#include "RefCounted.h" + +namespace nv +{ + class WeakProxy; + + /** Simple auto pointer template class. + * + * This is very similar to the standard auto_ptr class, but with some + * additional limitations to make its use less error prone: + * - Copy constructor and assignment operator are disabled. + * - reset method is removed. + * + * The semantics of the standard auto_ptr are not clear and change depending + * on the std implementation. For a discussion of the problems of auto_ptr read: + * http://www.awprofessional.com/content/images/020163371X/autoptrupdate\auto_ptr_update.html + */ + template + class AutoPtr + { + NV_FORBID_COPY(AutoPtr); + NV_FORBID_HEAPALLOC(); + public: + + /// Ctor. + AutoPtr(T * p = NULL) : m_ptr(p) { } + + template + AutoPtr(Q * p) : m_ptr(static_cast(p)) { } + + /// Dtor. Deletes owned pointer. + ~AutoPtr() { + delete m_ptr; + m_ptr = NULL; + } + + /// Delete owned pointer and assign new one. + void operator=( T * p ) { + if (p != m_ptr) + { + delete m_ptr; + m_ptr = p; + } + } + + template + void operator=( Q * p ) { + if (p != m_ptr) + { + delete m_ptr; + m_ptr = static_cast(p); + } + } + + /// Member access. + T * operator -> () const { + nvDebugCheck(m_ptr != NULL); + return m_ptr; + } + + /// Get reference. + T & operator*() const { + nvDebugCheck(m_ptr != NULL); + return *m_ptr; + } + + /// Get pointer. + T * ptr() const { return m_ptr; } + + /// Relinquish ownership of the underlying pointer and returns that pointer. + T * release() { + T * tmp = m_ptr; + m_ptr = NULL; + return tmp; + } + + /// Const pointer equal comparation. + friend bool operator == (const AutoPtr & ap, const T * const p) { + return (ap.ptr() == p); + } + + /// Const pointer nequal comparation. + friend bool operator != (const AutoPtr & ap, const T * const p) { + return (ap.ptr() != p); + } + + /// Const pointer equal comparation. + friend bool operator == (const T * const p, const AutoPtr & ap) { + return (ap.ptr() == p); + } + + /// Const pointer nequal comparation. + friend bool operator != (const T * const p, const AutoPtr & ap) { + return (ap.ptr() != p); + } + + private: + T * m_ptr; + }; + + + /// Smart pointer template class. + template + class SmartPtr { + public: + + // BaseClass must implement addRef() and release(). + typedef SmartPtr ThisType; + + /// Default ctor. + SmartPtr() : m_ptr(NULL) + { + } + + /// Other type assignment. + template + SmartPtr( const SmartPtr & tc ) + { + m_ptr = static_cast( tc.ptr() ); + if (m_ptr) { + m_ptr->addRef(); + } + } + + /// Copy ctor. + SmartPtr( const ThisType & bc ) + { + m_ptr = bc.ptr(); + if (m_ptr) { + m_ptr->addRef(); + } + } + + /// Copy cast ctor. SmartPtr(NULL) is valid. + explicit SmartPtr( BaseClass * bc ) + { + m_ptr = bc; + if (m_ptr) { + m_ptr->addRef(); + } + } + + /// Dtor. + ~SmartPtr() + { + set(NULL); + } + + + /// -> operator. + BaseClass * operator -> () const + { + nvCheck( m_ptr != NULL ); + return m_ptr; + } + + /// * operator. + BaseClass & operator*() const + { + nvCheck( m_ptr != NULL ); + return *m_ptr; + } + + /// Get pointer. + BaseClass * ptr() const + { + return m_ptr; + } + + /// Other type assignment. + template + void operator = ( const SmartPtr & tc ) + { + set( static_cast(tc.ptr()) ); + } + + /// This type assignment. + void operator = ( const ThisType & bc ) + { + set( bc.ptr() ); + } + + /// Pointer assignment. + void operator = ( BaseClass * bc ) + { + set( bc ); + } + + + /// Other type equal comparation. + template + bool operator == ( const SmartPtr & other ) const + { + return m_ptr == other.ptr(); + } + + /// This type equal comparation. + bool operator == ( const ThisType & bc ) const + { + return m_ptr == bc.ptr(); + } + + /// Const pointer equal comparation. + bool operator == ( const BaseClass * const bc ) const + { + return m_ptr == bc; + } + + /// Other type not equal comparation. + template + bool operator != ( const SmartPtr & other ) const + { + return m_ptr != other.ptr(); + } + + /// Other type not equal comparation. + bool operator != ( const ThisType & bc ) const + { + return m_ptr != bc.ptr(); + } + + /// Const pointer not equal comparation. + bool operator != (const BaseClass * const bc) const + { + return m_ptr != bc; + } + + /// This type lower than comparation. + bool operator < (const ThisType & p) const + { + return m_ptr < p.ptr(); + } + + bool isValid() const { + return isValidPtr(m_ptr); + } + + private: + + // Set this pointer. + void set( BaseClass * p ) + { + if (p) p->addRef(); + if (m_ptr) m_ptr->release(); + m_ptr = p; + } + + private: + + BaseClass * m_ptr; + + }; + + + /// Smart pointer template class. + template + class WeakPtr { + public: + + WeakPtr() {} + + WeakPtr(T * p) { operator=(p); } + WeakPtr(const SmartPtr & p) { operator=(p.ptr()); } + + // Default constructor and assignment from weak_ptr are OK. + + void operator=(T * p) + { + if (p) { + m_proxy = p->getWeakProxy(); + nvDebugCheck(m_proxy != NULL); + nvDebugCheck(m_proxy->ptr() == p); + } + else { + m_proxy = NULL; + } + } + + void operator=(const SmartPtr & ptr) { operator=(ptr.ptr()); } + + bool operator==(const SmartPtr & p) const { return ptr() == p.ptr(); } + bool operator!=(const SmartPtr & p) const { return ptr() != p.ptr(); } + + bool operator==(const WeakPtr & p) const { return ptr() == p.ptr(); } + bool operator!=(const WeakPtr & p) const { return ptr() != p.ptr(); } + + bool operator==(T * p) const { return ptr() == p; } + bool operator!=(T * p) const { return ptr() != p; } + + T * operator->() const + { + T * p = ptr(); + nvDebugCheck(p != NULL); + return p; + } + + T * ptr() const + { + if (m_proxy != NULL) { + return static_cast(m_proxy->ptr()); + } + return NULL; + } + + private: + + mutable SmartPtr m_proxy; + + }; + + +} // nv namespace + +#endif // NV_CORE_PTR_H diff --git a/thirdparty/thekla_atlas/src/nvcore/RadixSort.cpp b/thirdparty/thekla_atlas/src/nvcore/RadixSort.cpp new file mode 100755 index 00000000..3f44620c --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/RadixSort.cpp @@ -0,0 +1,285 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "RadixSort.h" + +#include "Utils.h" + +#include // memset + +using namespace nv; + +static inline void FloatFlip(uint32 & f) { + //uint32 mask = -int32(f >> 31) | 0x80000000; // Michael Herf. + int32 mask = (int32(f) >> 31) | 0x80000000; // Warren Hunt, Manchor Ko. + f ^= mask; +} + +static inline void IFloatFlip(uint32 & f) { + uint32 mask = ((f >> 31) - 1) | 0x80000000; // Michael Herf. + //uint32 mask = (int32(f ^ 0x80000000) >> 31) | 0x80000000; // Warren Hunt, Manchor Ko. @@ Correct, but fails in release on gcc-4.2.1 + f ^= mask; +} + + +template +void createHistograms(const T * buffer, uint count, uint * histogram) +{ + const uint bucketCount = sizeof(T); // (8 * sizeof(T)) / log2(radix) + + // Init bucket pointers. + uint * h[bucketCount]; + for (uint i = 0; i < bucketCount; i++) { +#if NV_BIG_ENDIAN + h[sizeof(T)-1-i] = histogram + 256 * i; +#else + h[i] = histogram + 256 * i; +#endif + } + + // Clear histograms. + memset(histogram, 0, 256 * bucketCount * sizeof(uint)); + + // @@ Add support for signed integers. + + // Build histograms. + const uint8 * p = (const uint8 *)buffer; // @@ Does this break aliasing rules? + const uint8 * pe = p + count * sizeof(T); + + while (p != pe) { + h[0][*p++]++, h[1][*p++]++, h[2][*p++]++, h[3][*p++]++; + if (bucketCount == 8) h[4][*p++]++, h[5][*p++]++, h[6][*p++]++, h[7][*p++]++; + } +} + +/* +template <> +void createHistograms(const float * buffer, uint count, uint * histogram) +{ + // Init bucket pointers. + uint32 * h[4]; + for (uint i = 0; i < 4; i++) { +#if NV_BIG_ENDIAN + h[3-i] = histogram + 256 * i; +#else + h[i] = histogram + 256 * i; +#endif + } + + // Clear histograms. + memset(histogram, 0, 256 * 4 * sizeof(uint32)); + + // Build histograms. + for (uint i = 0; i < count; i++) { + uint32 fi = FloatFlip(buffer[i]); + + h[0][fi & 0xFF]++; + h[1][(fi >> 8) & 0xFF]++; + h[2][(fi >> 16) & 0xFF]++; + h[3][fi >> 24]++; + } +} +*/ + +RadixSort::RadixSort() : m_size(0), m_ranks(NULL), m_ranks2(NULL), m_validRanks(false) +{ +} + +RadixSort::RadixSort(uint reserve_count) : m_size(0), m_ranks(NULL), m_ranks2(NULL), m_validRanks(false) +{ + checkResize(reserve_count); +} + +RadixSort::~RadixSort() +{ + // Release everything + free(m_ranks2); + free(m_ranks); +} + + +void RadixSort::resize(uint count) +{ + m_ranks2 = realloc(m_ranks2, count); + m_ranks = realloc(m_ranks, count); +} + +inline void RadixSort::checkResize(uint count) +{ + if (count != m_size) + { + if (count > m_size) resize(count); + m_size = count; + m_validRanks = false; + } +} + +template inline void RadixSort::insertionSort(const T * input, uint count) +{ + if (!m_validRanks) { + /*for (uint i = 0; i < count; i++) { + m_ranks[i] = i; + }*/ + + m_ranks[0] = 0; + for (uint i = 1; i != count; ++i) + { + int rank = m_ranks[i] = i; + + uint j = i; + while (j != 0 && input[rank] < input[m_ranks[j-1]]) + { + m_ranks[j] = m_ranks[j-1]; + --j; + } + if (i != j) + { + m_ranks[j] = rank; + } + } + + m_validRanks = true; + } + else { + for (uint i = 1; i != count; ++i) + { + int rank = m_ranks[i]; + + uint j = i; + while (j != 0 && input[rank] < input[m_ranks[j-1]]) + { + m_ranks[j] = m_ranks[j-1]; + --j; + } + if (i != j) + { + m_ranks[j] = rank; + } + } + } +} + +template inline void RadixSort::radixSort(const T * input, uint count) +{ + const uint P = sizeof(T); // pass count + + // Allocate histograms & offsets on the stack + uint histogram[256 * P]; + uint * link[256]; + + createHistograms(input, count, histogram); + + // Radix sort, j is the pass number (0=LSB, P=MSB) + for (uint j = 0; j < P; j++) + { + // Pointer to this bucket. + const uint * h = &histogram[j * 256]; + + const uint8 * inputBytes = (const uint8*)input; // @@ Is this aliasing legal? + +#if NV_BIG_ENDIAN + inputBytes += P - 1 - j; +#else + inputBytes += j; +#endif + + if (h[inputBytes[0]] == count) { + // Skip this pass, all values are the same. + continue; + } + + // Create offsets + link[0] = m_ranks2; + for (uint i = 1; i < 256; i++) link[i] = link[i-1] + h[i-1]; + + // Perform Radix Sort + if (!m_validRanks) + { + for (uint i = 0; i < count; i++) + { + *link[inputBytes[i*P]]++ = i; + } + m_validRanks = true; + } + else + { + for (uint i = 0; i < count; i++) + { + const uint idx = m_ranks[i]; + *link[inputBytes[idx*P]]++ = idx; + } + } + + // Swap pointers for next pass. Valid indices - the most recent ones - are in m_ranks after the swap. + swap(m_ranks, m_ranks2); + } + + // All values were equal, generate linear ranks. + if (!m_validRanks) + { + for (uint i = 0; i < count; i++) + { + m_ranks[i] = i; + } + m_validRanks = true; + } +} + + +RadixSort & RadixSort::sort(const uint32 * input, uint count) +{ + if (input == NULL || count == 0) return *this; + + // Resize lists if needed + checkResize(count); + + if (count < 32) { + insertionSort(input, count); + } + else { + radixSort(input, count); + } + return *this; +} + + +RadixSort & RadixSort::sort(const uint64 * input, uint count) +{ + if (input == NULL || count == 0) return *this; + + // Resize lists if needed + checkResize(count); + + if (count < 64) { + insertionSort(input, count); + } + else { + radixSort(input, count); + } + return *this; +} + +RadixSort& RadixSort::sort(const float * input, uint count) +{ + if (input == NULL || count == 0) return *this; + + // Resize lists if needed + checkResize(count); + + if (count < 32) { + insertionSort(input, count); + } + else { + // @@ Avoid touching the input multiple times. + for (uint i = 0; i < count; i++) { + FloatFlip((uint32 &)input[i]); + } + + radixSort((const uint32 *)input, count); + + for (uint i = 0; i < count; i++) { + IFloatFlip((uint32 &)input[i]); + } + } + + return *this; +} diff --git a/thirdparty/thekla_atlas/src/nvcore/RadixSort.h b/thirdparty/thekla_atlas/src/nvcore/RadixSort.h new file mode 100755 index 00000000..82325ebb --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/RadixSort.h @@ -0,0 +1,75 @@ +#pragma once +#ifndef NV_CORE_RADIXSORT_H +#define NV_CORE_RADIXSORT_H + +// Based on Pierre Terdiman's and Michael Herf's source code. +// http://www.codercorner.com/RadixSortRevisited.htm +// http://www.stereopsis.com/radix.html + +#include "nvcore.h" +#include "Array.h" + +namespace nv +{ + + class NVCORE_CLASS RadixSort + { + NV_FORBID_COPY(RadixSort); + public: + // Constructor/Destructor + RadixSort(); + RadixSort(uint reserve_count); + ~RadixSort(); + + // Invalidate ranks. + RadixSort & reset() { m_validRanks = false; return *this; } + + // Sorting methods. + RadixSort & sort(const uint32 * input, uint count); + RadixSort & sort(const uint64 * input, uint count); + RadixSort & sort(const float * input, uint count); + + // Helpers. + RadixSort & sort(const Array & input); + RadixSort & sort(const Array & input); + RadixSort & sort(const Array & input); + + // Access to results. m_ranks is a list of indices in sorted order, i.e. in the order you may further process your data + inline const uint * ranks() const { nvDebugCheck(m_validRanks); return m_ranks; } + inline uint * ranks() { nvDebugCheck(m_validRanks); return m_ranks; } + inline uint rank(uint i) const { nvDebugCheck(m_validRanks); return m_ranks[i]; } + + // query whether the sort has been performed + inline bool valid() const { return m_validRanks; } + + private: + uint m_size; + uint * m_ranks; + uint * m_ranks2; + bool m_validRanks; + + // Internal methods + template void insertionSort(const T * input, uint count); + template void radixSort(const T * input, uint count); + + void checkResize(uint nb); + void resize(uint nb); + }; + + inline RadixSort & RadixSort::sort(const Array & input) { + return sort(input.buffer(), input.count()); + } + + inline RadixSort & RadixSort::sort(const Array & input) { + return sort(input.buffer(), input.count()); + } + + inline RadixSort & RadixSort::sort(const Array & input) { + return sort(input.buffer(), input.count()); + } + +} // nv namespace + + + +#endif // NV_CORE_RADIXSORT_H diff --git a/thirdparty/thekla_atlas/src/nvcore/RefCounted.h b/thirdparty/thekla_atlas/src/nvcore/RefCounted.h new file mode 100755 index 00000000..b8d68ede --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/RefCounted.h @@ -0,0 +1,149 @@ +// This code is in the public domain -- Ignacio Castańo + +#ifndef NV_CORE_REFCOUNTED_H +#define NV_CORE_REFCOUNTED_H + +#include "nvcore.h" +#include "Debug.h" + +#define NV_DECLARE_PTR(Class) \ + template class SmartPtr; \ + typedef SmartPtr Class ## Ptr; \ + typedef SmartPtr Class ## ConstPtr + + +namespace nv +{ + /// Weak proxy. + class WeakProxy + { + NV_FORBID_COPY(WeakProxy); + public: + /// Ctor. + WeakProxy(void * ptr) : m_count(0), m_ptr(ptr) { } + + /// Dtor. + ~WeakProxy() + { + nvCheck( m_count == 0 ); + } + + /// Increase reference count. + uint addRef() const + { + m_count++; + return m_count; + } + + /// Decrease reference count and remove when 0. + uint release() const + { + nvCheck( m_count > 0 ); + + m_count--; + if( m_count == 0 ) { + delete this; + return 0; + } + return m_count; + } + + /// WeakPtr's call this to determine if their pointer is valid or not. + bool isAlive() const { + return m_ptr != NULL; + } + + /// Only the actual object should call this. + void notifyObjectDied() { + m_ptr = NULL; + } + + /// Return proxy pointer. + void * ptr() const { + return m_ptr; + } + + private: + mutable int m_count; + void * m_ptr; + }; + + + /// Reference counted base class to be used with SmartPtr and WeakPtr. + class RefCounted + { + NV_FORBID_COPY(RefCounted); + public: + + /// Ctor. + RefCounted() : m_count(0), m_weak_proxy(NULL) + { + } + + /// Virtual dtor. + virtual ~RefCounted() + { + nvCheck( m_count == 0 ); + releaseWeakProxy(); + } + + + /// Increase reference count. + uint addRef() const + { + m_count++; + return m_count; + } + + + /// Decrease reference count and remove when 0. + uint release() const + { + nvCheck( m_count > 0 ); + + m_count--; + if( m_count == 0 ) { + delete this; + return 0; + } + return m_count; + } + + /// Get weak proxy. + WeakProxy * getWeakProxy() const + { + if (m_weak_proxy == NULL) { + m_weak_proxy = new WeakProxy((void *)this); + m_weak_proxy->addRef(); + } + return m_weak_proxy; + } + + /// Release the weak proxy. + void releaseWeakProxy() const + { + if (m_weak_proxy != NULL) { + m_weak_proxy->notifyObjectDied(); + m_weak_proxy->release(); + m_weak_proxy = NULL; + } + } + + /// Get reference count. + int refCount() const + { + return m_count; + } + + + private: + + mutable int m_count; + mutable WeakProxy * m_weak_proxy; + + }; + +} // nv namespace + + +#endif // NV_CORE_REFCOUNTED_H diff --git a/thirdparty/thekla_atlas/src/nvcore/StdStream.h b/thirdparty/thekla_atlas/src/nvcore/StdStream.h new file mode 100755 index 00000000..f65d6dab --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/StdStream.h @@ -0,0 +1,474 @@ +// This code is in the public domain -- Ignacio Castańo + +//#pragma once +//#ifndef NV_CORE_STDSTREAM_H +//#define NV_CORE_STDSTREAM_H + +#include "nvcore.h" +#include "Stream.h" +#include "Array.h" + +#include // fopen +#include // memcpy + +namespace nv +{ + + // Portable version of fopen. + inline FILE * fileOpen(const char * fileName, const char * mode) + { + nvCheck(fileName != NULL); +#if NV_CC_MSVC && _MSC_VER >= 1400 + FILE * fp; + if (fopen_s(&fp, fileName, mode) == 0) { + return fp; + } + return NULL; +#else + return fopen(fileName, mode); +#endif + } + + + /// Base stdio stream. + class NVCORE_CLASS StdStream : public Stream + { + NV_FORBID_COPY(StdStream); + public: + + /// Ctor. + StdStream( FILE * fp, bool autoclose ) : m_fp(fp), m_autoclose(autoclose) { } + + /// Dtor. + virtual ~StdStream() + { + if( m_fp != NULL && m_autoclose ) { +#if NV_OS_WIN32 + _fclose_nolock( m_fp ); +#else + fclose( m_fp ); +#endif + } + } + + + /** @name Stream implementation. */ + //@{ + virtual void seek( uint pos ) + { + nvDebugCheck(m_fp != NULL); + nvDebugCheck(pos <= size()); +#if NV_OS_WIN32 + _fseek_nolock(m_fp, pos, SEEK_SET); +#else + fseek(m_fp, pos, SEEK_SET); +#endif + } + + virtual uint tell() const + { + nvDebugCheck(m_fp != NULL); +#if NV_OS_WIN32 + return _ftell_nolock(m_fp); +#else + return (uint)ftell(m_fp); +#endif + } + + virtual uint size() const + { + nvDebugCheck(m_fp != NULL); +#if NV_OS_WIN32 + uint pos = _ftell_nolock(m_fp); + _fseek_nolock(m_fp, 0, SEEK_END); + uint end = _ftell_nolock(m_fp); + _fseek_nolock(m_fp, pos, SEEK_SET); +#else + uint pos = (uint)ftell(m_fp); + fseek(m_fp, 0, SEEK_END); + uint end = (uint)ftell(m_fp); + fseek(m_fp, pos, SEEK_SET); +#endif + return end; + } + + virtual bool isError() const + { + return m_fp == NULL || ferror( m_fp ) != 0; + } + + virtual void clearError() + { + nvDebugCheck(m_fp != NULL); + clearerr(m_fp); + } + + // @@ The original implementation uses feof, which only returns true when we attempt to read *past* the end of the stream. + // That is, if we read the last byte of a file, then isAtEnd would still return false, even though the stream pointer is at the file end. This is not the intent and was inconsistent with the implementation of the MemoryStream, a better + // implementation uses use ftell and fseek to determine our location within the file. + virtual bool isAtEnd() const + { + if (m_fp == NULL) return true; + //nvDebugCheck(m_fp != NULL); + //return feof( m_fp ) != 0; +#if NV_OS_WIN32 + uint pos = _ftell_nolock(m_fp); + _fseek_nolock(m_fp, 0, SEEK_END); + uint end = _ftell_nolock(m_fp); + _fseek_nolock(m_fp, pos, SEEK_SET); +#else + uint pos = (uint)ftell(m_fp); + fseek(m_fp, 0, SEEK_END); + uint end = (uint)ftell(m_fp); + fseek(m_fp, pos, SEEK_SET); +#endif + return pos == end; + } + + /// Always true. + virtual bool isSeekable() const { return true; } + //@} + + protected: + + FILE * m_fp; + bool m_autoclose; + + }; + + + /// Standard output stream. + class NVCORE_CLASS StdOutputStream : public StdStream + { + NV_FORBID_COPY(StdOutputStream); + public: + + /// Construct stream by file name. + StdOutputStream( const char * name ) : StdStream(fileOpen(name, "wb"), /*autoclose=*/true) { } + + /// Construct stream by file handle. + StdOutputStream( FILE * fp, bool autoclose ) : StdStream(fp, autoclose) + { + } + + /** @name Stream implementation. */ + //@{ + /// Write data. + virtual uint serialize( void * data, uint len ) + { + nvDebugCheck(data != NULL); + nvDebugCheck(m_fp != NULL); +#if NV_OS_WIN32 + return (uint)_fwrite_nolock(data, 1, len, m_fp); +#elif NV_OS_LINUX + return (uint)fwrite_unlocked(data, 1, len, m_fp); +#elif NV_OS_DARWIN + // @@ No error checking, always returns len. + for (uint i = 0; i < len; i++) { + putc_unlocked(((char *)data)[i], m_fp); + } + return len; +#else + return (uint)fwrite(data, 1, len, m_fp); +#endif + } + + virtual bool isLoading() const + { + return false; + } + + virtual bool isSaving() const + { + return true; + } + //@} + + }; + + + /// Standard input stream. + class NVCORE_CLASS StdInputStream : public StdStream + { + NV_FORBID_COPY(StdInputStream); + public: + + /// Construct stream by file name. + StdInputStream( const char * name ) : StdStream(fileOpen(name, "rb"), /*autoclose=*/true) { } + + /// Construct stream by file handle. + StdInputStream( FILE * fp, bool autoclose=true ) : StdStream(fp, autoclose) + { + } + + /** @name Stream implementation. */ + //@{ + /// Read data. + virtual uint serialize( void * data, uint len ) + { + nvDebugCheck(data != NULL); + nvDebugCheck(m_fp != NULL); +#if NV_OS_WIN32 + return (uint)_fread_nolock(data, 1, len, m_fp); +#elif NV_OS_LINUX + return (uint)fread_unlocked(data, 1, len, m_fp); +#elif NV_OS_DARWIN + // This is rather lame. Not sure if it's faster than the locked version. + for (uint i = 0; i < len; i++) { + ((char *)data)[i] = getc_unlocked(m_fp); + if (feof_unlocked(m_fp) != 0) { + return i; + } + } + return len; +#else + return (uint)fread(data, 1, len, m_fp); +#endif + + } + + virtual bool isLoading() const + { + return true; + } + + virtual bool isSaving() const + { + return false; + } + //@} + }; + + + + /// Memory input stream. + class NVCORE_CLASS MemoryInputStream : public Stream + { + NV_FORBID_COPY(MemoryInputStream); + public: + + /// Ctor. + MemoryInputStream( const uint8 * mem, uint size ) : m_mem(mem), m_ptr(mem), m_size(size) { } + + /** @name Stream implementation. */ + //@{ + /// Read data. + virtual uint serialize( void * data, uint len ) + { + nvDebugCheck(data != NULL); + nvDebugCheck(!isError()); + + uint left = m_size - tell(); + if (len > left) len = left; + + memcpy( data, m_ptr, len ); + m_ptr += len; + + return len; + } + + virtual void seek( uint pos ) + { + nvDebugCheck(!isError()); + m_ptr = m_mem + pos; + nvDebugCheck(!isError()); + } + + virtual uint tell() const + { + nvDebugCheck(m_ptr >= m_mem); + return uint(m_ptr - m_mem); + } + + virtual uint size() const + { + return m_size; + } + + virtual bool isError() const + { + return m_mem == NULL || m_ptr > m_mem + m_size || m_ptr < m_mem; + } + + virtual void clearError() + { + // Nothing to do. + } + + virtual bool isAtEnd() const + { + return m_ptr == m_mem + m_size; + } + + /// Always true. + virtual bool isSeekable() const + { + return true; + } + + virtual bool isLoading() const + { + return true; + } + + virtual bool isSaving() const + { + return false; + } + //@} + + const uint8 * ptr() const { return m_ptr; } + + + private: + + const uint8 * m_mem; + const uint8 * m_ptr; + uint m_size; + + }; + + + /// Buffer output stream. + class NVCORE_CLASS BufferOutputStream : public Stream + { + NV_FORBID_COPY(BufferOutputStream); + public: + + BufferOutputStream(Array & buffer) : m_buffer(buffer) { } + + virtual uint serialize( void * data, uint len ) + { + nvDebugCheck(data != NULL); + m_buffer.append((uint8 *)data, len); + return len; + } + + virtual void seek( uint /*pos*/ ) { /*Not implemented*/ } + virtual uint tell() const { return m_buffer.size(); } + virtual uint size() const { return m_buffer.size(); } + + virtual bool isError() const { return false; } + virtual void clearError() {} + + virtual bool isAtEnd() const { return true; } + virtual bool isSeekable() const { return false; } + virtual bool isLoading() const { return false; } + virtual bool isSaving() const { return true; } + + private: + Array & m_buffer; + }; + + + /// Protected input stream. + class NVCORE_CLASS ProtectedStream : public Stream + { + NV_FORBID_COPY(ProtectedStream); + public: + + /// Ctor. + ProtectedStream( Stream & s ) : m_s(&s), m_autodelete(false) + { + } + + /// Ctor. + ProtectedStream( Stream * s, bool autodelete = true ) : + m_s(s), m_autodelete(autodelete) + { + nvDebugCheck(m_s != NULL); + } + + /// Dtor. + virtual ~ProtectedStream() + { + if( m_autodelete ) { + delete m_s; + } + } + + /** @name Stream implementation. */ + //@{ + /// Read data. + virtual uint serialize( void * data, uint len ) + { + nvDebugCheck(data != NULL); + len = m_s->serialize( data, len ); + + if( m_s->isError() ) { +#if NV_OS_ORBIS + //SBtodoORBIS disabled (no exceptions) +#else + throw; +#endif + } + + return len; + } + + virtual void seek( uint pos ) + { + m_s->seek( pos ); + + if( m_s->isError() ) { +#if NV_OS_ORBIS + //SBtodoORBIS disabled (no exceptions) +#else + throw; +#endif + } + } + + virtual uint tell() const + { + return m_s->tell(); + } + + virtual uint size() const + { + return m_s->size(); + } + + virtual bool isError() const + { + return m_s->isError(); + } + + virtual void clearError() + { + m_s->clearError(); + } + + virtual bool isAtEnd() const + { + return m_s->isAtEnd(); + } + + virtual bool isSeekable() const + { + return m_s->isSeekable(); + } + + virtual bool isLoading() const + { + return m_s->isLoading(); + } + + virtual bool isSaving() const + { + return m_s->isSaving(); + } + //@} + + + private: + + Stream * const m_s; + bool const m_autodelete; + + }; + +} // nv namespace + + +//#endif // NV_CORE_STDSTREAM_H diff --git a/thirdparty/thekla_atlas/src/nvcore/StrLib.cpp b/thirdparty/thekla_atlas/src/nvcore/StrLib.cpp new file mode 100755 index 00000000..7ec6c701 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/StrLib.cpp @@ -0,0 +1,796 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "StrLib.h" + +#include "Memory.h" +#include "Utils.h" // swap + +#include // log +#include // vsnprintf +#include // strlen, strcmp, etc. + +#if NV_CC_MSVC +#include // vsnprintf +#endif + +using namespace nv; + +namespace +{ + static char * strAlloc(uint size) + { + return malloc(size); + } + + static char * strReAlloc(char * str, uint size) + { + return realloc(str, size); + } + + static void strFree(const char * str) + { + return free(str); + } + + /*static char * strDup( const char * str ) + { + nvDebugCheck( str != NULL ); + uint len = uint(strlen( str ) + 1); + char * dup = strAlloc( len ); + memcpy( dup, str, len ); + return dup; + }*/ + + // helper function for integer to string conversion. + static char * i2a( uint i, char *a, uint r ) + { + if( i / r > 0 ) { + a = i2a( i / r, a, r ); + } + *a = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i % r]; + return a + 1; + } + + // Locale independent functions. + static inline char toUpper( char c ) { + return (c<'a' || c>'z') ? (c) : (c+'A'-'a'); + } + static inline char toLower( char c ) { + return (c<'A' || c>'Z') ? (c) : (c+'a'-'A'); + } + static inline bool isAlpha( char c ) { + return (c>='a' && c<='z') || (c>='A' && c<='Z'); + } + static inline bool isDigit( char c ) { + return c>='0' && c<='9'; + } + static inline bool isAlnum( char c ) { + return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9'); + } + +} + +uint nv::strLen(const char * str) +{ + nvDebugCheck(str != NULL); + return U32(strlen(str)); +} + +int nv::strDiff(const char * s1, const char * s2) +{ + nvDebugCheck(s1 != NULL); + nvDebugCheck(s2 != NULL); + return strcmp(s1, s2); +} + +int nv::strCaseDiff(const char * s1, const char * s2) +{ + nvDebugCheck(s1 != NULL); + nvDebugCheck(s1 != NULL); +#if NV_CC_MSVC + return _stricmp(s1, s2); +#else + return strcasecmp(s1, s2); +#endif +} + +bool nv::strEqual(const char * s1, const char * s2) +{ + if (s1 == s2) return true; + if (s1 == NULL || s2 == NULL) return false; + return strcmp(s1, s2) == 0; +} + +bool nv::strCaseEqual(const char * s1, const char * s2) +{ + if (s1 == s2) return true; + if (s1 == NULL || s2 == NULL) return false; + return strCaseDiff(s1, s2) == 0; +} + +bool nv::strBeginsWith(const char * str, const char * prefix) +{ + //return strstr(str, prefix) == dst; + return strncmp(str, prefix, strlen(prefix)) == 0; +} + +bool nv::strEndsWith(const char * str, const char * suffix) +{ + uint ml = strLen(str); + uint sl = strLen(suffix); + if (ml < sl) return false; + return strncmp(str + ml - sl, suffix, sl) == 0; +} + +// @@ Add asserts to detect overlap between dst and src? +void nv::strCpy(char * dst, uint size, const char * src) +{ + nvDebugCheck(dst != NULL); + nvDebugCheck(src != NULL); +#if NV_CC_MSVC && _MSC_VER >= 1400 + strcpy_s(dst, size, src); +#else + NV_UNUSED(size); + strcpy(dst, src); +#endif +} + +void nv::strCpy(char * dst, uint size, const char * src, uint len) +{ + nvDebugCheck(dst != NULL); + nvDebugCheck(src != NULL); +#if NV_CC_MSVC && _MSC_VER >= 1400 + strncpy_s(dst, size, src, len); +#else + int n = min(len+1, size); + strncpy(dst, src, n); + dst[n-1] = '\0'; +#endif +} + +void nv::strCat(char * dst, uint size, const char * src) +{ + nvDebugCheck(dst != NULL); + nvDebugCheck(src != NULL); +#if NV_CC_MSVC && _MSC_VER >= 1400 + strcat_s(dst, size, src); +#else + NV_UNUSED(size); + strcat(dst, src); +#endif +} + +NVCORE_API const char * nv::strSkipWhiteSpace(const char * str) +{ + nvDebugCheck(str != NULL); + while (*str == ' ') str++; + return str; +} + +NVCORE_API char * nv::strSkipWhiteSpace(char * str) +{ + nvDebugCheck(str != NULL); + while (*str == ' ') str++; + return str; +} + + +/** Pattern matching routine. I don't remember where did I get this. */ +bool nv::strMatch(const char * str, const char * pat) +{ + nvDebugCheck(str != NULL); + nvDebugCheck(pat != NULL); + + char c2; + + while (true) { + if (*pat==0) { + if (*str==0) return true; + else return false; + } + if ((*str==0) && (*pat!='*')) return false; + if (*pat=='*') { + pat++; + if (*pat==0) return true; + while (true) { + if (strMatch(str, pat)) return true; + if (*str==0) return false; + str++; + } + } + if (*pat=='?') goto match; + if (*pat=='[') { + pat++; + while (true) { + if ((*pat==']') || (*pat==0)) return false; + if (*pat==*str) break; + if (pat[1] == '-') { + c2 = pat[2]; + if (c2==0) return false; + if ((*pat<=*str) && (c2>=*str)) break; + if ((*pat>=*str) && (c2<=*str)) break; + pat+=2; + } + pat++; + } + while (*pat!=']') { + if (*pat==0) { + pat--; + break; + } + pat++; + } + goto match; + } + + if (*pat == NV_PATH_SEPARATOR) { + pat++; + if (*pat==0) return false; + } + if (*pat!=*str) return false; + +match: + pat++; + str++; + } +} + +bool nv::isNumber(const char * str) { + while(*str != '\0') { + if (!isDigit(*str)) return false; + str++; + } + return true; +} + + +/** Empty string. */ +StringBuilder::StringBuilder() : m_size(0), m_str(NULL) +{ +} + +/** Preallocate space. */ +StringBuilder::StringBuilder( uint size_hint ) : m_size(size_hint) +{ + nvDebugCheck(m_size > 0); + m_str = strAlloc(m_size); + *m_str = '\0'; +} + +/** Copy ctor. */ +StringBuilder::StringBuilder( const StringBuilder & s ) : m_size(0), m_str(NULL) +{ + copy(s); +} + +/** Copy string. */ +StringBuilder::StringBuilder(const char * s) : m_size(0), m_str(NULL) +{ + if (s != NULL) { + copy(s); + } +} + +/** Copy string. */ +StringBuilder::StringBuilder(const char * s, uint len) : m_size(0), m_str(NULL) +{ + copy(s, len); +} + +/** Delete the string. */ +StringBuilder::~StringBuilder() +{ + strFree(m_str); +} + + +/** Format a string safely. */ +StringBuilder & StringBuilder::format( const char * fmt, ... ) +{ + nvDebugCheck(fmt != NULL); + va_list arg; + va_start( arg, fmt ); + + formatList( fmt, arg ); + + va_end( arg ); + + return *this; +} + + +/** Format a string safely. */ +StringBuilder & StringBuilder::formatList( const char * fmt, va_list arg ) +{ + nvDebugCheck(fmt != NULL); + + if (m_size == 0) { + m_size = 64; + m_str = strAlloc( m_size ); + } + + va_list tmp; + va_copy(tmp, arg); +#if NV_CC_MSVC && _MSC_VER >= 1400 + int n = vsnprintf_s(m_str, m_size, _TRUNCATE, fmt, tmp); +#else + int n = vsnprintf(m_str, m_size, fmt, tmp); +#endif + va_end(tmp); + + while( n < 0 || n >= int(m_size) ) { + if( n > -1 ) { + m_size = n + 1; + } + else { + m_size *= 2; + } + + m_str = strReAlloc(m_str, m_size); + + va_copy(tmp, arg); +#if NV_CC_MSVC && _MSC_VER >= 1400 + n = vsnprintf_s(m_str, m_size, _TRUNCATE, fmt, tmp); +#else + n = vsnprintf(m_str, m_size, fmt, tmp); +#endif + va_end(tmp); + } + + nvDebugCheck(n < int(m_size)); + + // Make sure it's null terminated. + nvDebugCheck(m_str[n] == '\0'); + //str[n] = '\0'; + + return *this; +} + + +// Append a character. +StringBuilder & StringBuilder::append( char c ) +{ + return append(&c, 1); +} + +// Append a string. +StringBuilder & StringBuilder::append( const char * s ) +{ + return append(s, U32(strlen( s ))); +} + +// Append a string. +StringBuilder & StringBuilder::append(const char * s, uint len) +{ + nvDebugCheck(s != NULL); + + uint offset = length(); + const uint size = offset + len + 1; + reserve(size); + strCpy(m_str + offset, len + 1, s, len); + + return *this; +} + +StringBuilder & StringBuilder::append(const StringBuilder & str) +{ + return append(str.m_str, str.length()); +} + + +/** Append a formatted string. */ +StringBuilder & StringBuilder::appendFormat( const char * fmt, ... ) +{ + nvDebugCheck( fmt != NULL ); + + va_list arg; + va_start( arg, fmt ); + + appendFormatList( fmt, arg ); + + va_end( arg ); + + return *this; +} + + +/** Append a formatted string. */ +StringBuilder & StringBuilder::appendFormatList( const char * fmt, va_list arg ) +{ + nvDebugCheck( fmt != NULL ); + + va_list tmp; + va_copy(tmp, arg); + + if (m_size == 0) { + formatList(fmt, arg); + } + else { + StringBuilder tmp_str; + tmp_str.formatList( fmt, tmp ); + append( tmp_str.str() ); + } + + va_end(tmp); + + return *this; +} + +// Append n spaces. +StringBuilder & StringBuilder::appendSpace(uint n) +{ + if (m_str == NULL) { + m_size = n + 1; + m_str = strAlloc(m_size); + memset(m_str, ' ', m_size); + m_str[n] = '\0'; + } + else { + const uint len = strLen(m_str); + if (m_size < len + n + 1) { + m_size = len + n + 1; + m_str = strReAlloc(m_str, m_size); + } + memset(m_str + len, ' ', n); + m_str[len+n] = '\0'; + } + + return *this; +} + + +/** Convert number to string in the given base. */ +StringBuilder & StringBuilder::number( int i, int base ) +{ + nvCheck( base >= 2 ); + nvCheck( base <= 36 ); + + // @@ This needs to be done correctly. + // length = floor(log(i, base)); + uint len = uint(log(float(i)) / log(float(base)) + 2); // one more if negative + reserve(len); + + if( i < 0 ) { + *m_str = '-'; + *i2a(uint(-i), m_str+1, base) = 0; + } + else { + *i2a(i, m_str, base) = 0; + } + + return *this; +} + + +/** Convert number to string in the given base. */ +StringBuilder & StringBuilder::number( uint i, int base ) +{ + nvCheck( base >= 2 ); + nvCheck( base <= 36 ); + + // @@ This needs to be done correctly. + // length = floor(log(i, base)); + uint len = uint(log(float(i)) / log(float(base)) - 0.5f + 1); + reserve(len); + + *i2a(i, m_str, base) = 0; + + return *this; +} + + +/** Resize the string preserving the contents. */ +StringBuilder & StringBuilder::reserve( uint size_hint ) +{ + nvCheck(size_hint != 0); + if (size_hint > m_size) { + m_str = strReAlloc(m_str, size_hint); + m_size = size_hint; + } + return *this; +} + + +/** Copy a string safely. */ +StringBuilder & StringBuilder::copy(const char * s) +{ + nvCheck( s != NULL ); + const uint str_size = uint(strlen( s )) + 1; + reserve(str_size); + memcpy(m_str, s, str_size); + return *this; +} + +/** Copy a string safely. */ +StringBuilder & StringBuilder::copy(const char * s, uint len) +{ + nvCheck( s != NULL ); + const uint str_size = len + 1; + reserve(str_size); + strCpy(m_str, str_size, s, len); + return *this; +} + + +/** Copy an StringBuilder. */ +StringBuilder & StringBuilder::copy( const StringBuilder & s ) +{ + if (s.m_str == NULL) { + nvCheck( s.m_size == 0 ); + reset(); + } + else { + reserve( s.m_size ); + strCpy( m_str, s.m_size, s.m_str ); + } + return *this; +} + +bool StringBuilder::endsWith(const char * str) const +{ + uint l = uint(strlen(str)); + uint ml = uint(strlen(m_str)); + if (ml < l) return false; + return strncmp(m_str + ml - l, str, l) == 0; +} + +bool StringBuilder::beginsWith(const char * str) const +{ + size_t l = strlen(str); + return strncmp(m_str, str, l) == 0; +} + +// Find given char starting from the end. +char * StringBuilder::reverseFind(char c) +{ + int length = (int)strlen(m_str) - 1; + while (length >= 0 && m_str[length] != c) { + length--; + } + if (length >= 0) { + return m_str + length; + } + else { + return NULL; + } +} + + +/** Reset the string. */ +void StringBuilder::reset() +{ + m_size = 0; + strFree( m_str ); + m_str = NULL; +} + +/** Release the allocated string. */ +char * StringBuilder::release() +{ + char * str = m_str; + m_size = 0; + m_str = NULL; + return str; +} + +// Take ownership of string. +void StringBuilder::acquire(char * str) +{ + if (str) { + m_size = strLen(str) + 1; + m_str = str; + } + else { + m_size = 0; + m_str = NULL; + } +} + +// Swap strings. +void nv::swap(StringBuilder & a, StringBuilder & b) { + swap(a.m_size, b.m_size); + swap(a.m_str, b.m_str); +} + + +/// Get the file name from a path. +const char * Path::fileName() const +{ + return fileName(m_str); +} + + +/// Get the extension from a file path. +const char * Path::extension() const +{ + return extension(m_str); +} + + +/*static */void Path::translatePath(char * path, char pathSeparator/*= NV_PATH_SEPARATOR*/) { + if (path != NULL) { + for (int i = 0;; i++) { + if (path[i] == '\0') break; + if (path[i] == '\\' || path[i] == '/') path[i] = pathSeparator; + } + } +} + +/// Toggles path separators (ie. \\ into /). +void Path::translatePath(char pathSeparator/*=NV_PATH_SEPARATOR*/) +{ + if (!isNull()) { + translatePath(m_str, pathSeparator); + } +} + +void Path::appendSeparator(char pathSeparator/*=NV_PATH_SEPARATOR*/) +{ + nvCheck(!isNull()); + + const uint l = length(); + + if (m_str[l] != '\\' && m_str[l] != '/') { + char separatorString[] = { pathSeparator, '\0' }; + append(separatorString); + } +} + + +/** +* Strip the file name from a path. +* @warning path cannot end with '/' o '\\', can't it? +*/ +void Path::stripFileName() +{ + nvCheck( m_str != NULL ); + + int length = (int)strlen(m_str) - 1; + while (length > 0 && m_str[length] != '/' && m_str[length] != '\\'){ + length--; + } + if( length ) { + m_str[length+1] = 0; + } + else { + m_str[0] = 0; + } +} + + +/// Strip the extension from a path name. +void Path::stripExtension() +{ + nvCheck( m_str != NULL ); + + int length = (int)strlen(m_str) - 1; + while (length > 0 && m_str[length] != '.') { + length--; + if( m_str[length] == NV_PATH_SEPARATOR ) { + return; // no extension + } + } + if (length > 0) { + m_str[length] = 0; + } +} + + +/// Get the path separator. +// static +char Path::separator() +{ + return NV_PATH_SEPARATOR; +} + +// static +const char * Path::fileName(const char * str) +{ + nvCheck( str != NULL ); + + int length = (int)strlen(str) - 1; + while (length >= 0 && str[length] != '\\' && str[length] != '/') { + length--; + } + + return &str[length+1]; +} + +// static +const char * Path::extension(const char * str) +{ + nvCheck( str != NULL ); + + int length, l; + l = length = (int)strlen( str ); + while (length > 0 && str[length] != '.') { + length--; + if (str[length] == '\\' || str[length] == '/') { + return &str[l]; // no extension + } + } + if (length == 0) { + return &str[l]; + } + return &str[length]; +} + + + +/// Clone this string +String String::clone() const +{ + String str(data); + return str; +} + +void String::setString(const char * str) +{ + if (str == NULL) { + data = NULL; + } + else { + allocString( str ); + addRef(); + } +} + +void String::setString(const char * str, uint length) +{ + nvDebugCheck(str != NULL); + + allocString(str, length); + addRef(); +} + +void String::setString(const StringBuilder & str) +{ + if (str.str() == NULL) { + data = NULL; + } + else { + allocString(str.str()); + addRef(); + } +} + +// Add reference count. +void String::addRef() +{ + if (data != NULL) + { + setRefCount(getRefCount() + 1); + } +} + +// Decrease reference count. +void String::release() +{ + if (data != NULL) + { + const uint16 count = getRefCount(); + setRefCount(count - 1); + if (count - 1 == 0) { + free(data - 2); + data = NULL; + } + } +} + +void String::allocString(const char * str, uint len) +{ + const char * ptr = malloc(2 + len + 1); + + setData( ptr ); + setRefCount( 0 ); + + // Copy string. + strCpy(const_cast(data), len+1, str, len); + + // Add terminating character. + const_cast(data)[len] = '\0'; +} + +void nv::swap(String & a, String & b) { + swap(a.data, b.data); +} diff --git a/thirdparty/thekla_atlas/src/nvcore/StrLib.h b/thirdparty/thekla_atlas/src/nvcore/StrLib.h new file mode 100755 index 00000000..ae4b5d12 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/StrLib.h @@ -0,0 +1,433 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_CORE_STRING_H +#define NV_CORE_STRING_H + +#include "Debug.h" +#include "Hash.h" // hash + +//#include // strlen, etc. + +#if NV_OS_WIN32 +#define NV_PATH_SEPARATOR '\\' +#else +#define NV_PATH_SEPARATOR '/' +#endif + +namespace nv +{ + + NVCORE_API uint strHash(const char * str, uint h) NV_PURE; + + /// String hash based on Bernstein's hash. + inline uint strHash(const char * data, uint h = 5381) + { + uint i = 0; + while(data[i] != 0) { + h = (33 * h) ^ uint(data[i]); + i++; + } + return h; + } + + template <> struct Hash { + uint operator()(const char * str) const { return strHash(str); } + }; + + NVCORE_API uint strLen(const char * str) NV_PURE; // Asserts on NULL strings. + + NVCORE_API int strDiff(const char * s1, const char * s2) NV_PURE; // Asserts on NULL strings. + NVCORE_API int strCaseDiff(const char * s1, const char * s2) NV_PURE; // Asserts on NULL strings. + NVCORE_API bool strEqual(const char * s1, const char * s2) NV_PURE; // Accepts NULL strings. + NVCORE_API bool strCaseEqual(const char * s1, const char * s2) NV_PURE; // Accepts NULL strings. + + template <> struct Equal { + bool operator()(const char * a, const char * b) const { return strEqual(a, b); } + }; + + NVCORE_API bool strBeginsWith(const char * dst, const char * prefix) NV_PURE; + NVCORE_API bool strEndsWith(const char * dst, const char * suffix) NV_PURE; + + + NVCORE_API void strCpy(char * dst, uint size, const char * src); + NVCORE_API void strCpy(char * dst, uint size, const char * src, uint len); + NVCORE_API void strCat(char * dst, uint size, const char * src); + + NVCORE_API const char * strSkipWhiteSpace(const char * str); + NVCORE_API char * strSkipWhiteSpace(char * str); + + NVCORE_API bool strMatch(const char * str, const char * pat) NV_PURE; + + NVCORE_API bool isNumber(const char * str) NV_PURE; + + /* @@ Implement these two functions and modify StringBuilder to use them? + NVCORE_API void strFormat(const char * dst, const char * fmt, ...); + NVCORE_API void strFormatList(const char * dst, const char * fmt, va_list arg); + + template void strFormatSafe(char (&buffer)[count], const char *fmt, ...) __attribute__((format (printf, 2, 3))); + template void strFormatSafe(char (&buffer)[count], const char *fmt, ...) { + va_list args; + va_start(args, fmt); + strFormatList(buffer, count, fmt, args); + va_end(args); + } + template void strFormatListSafe(char (&buffer)[count], const char *fmt, va_list arg) { + va_list tmp; + va_copy(tmp, args); + strFormatList(buffer, count, fmt, tmp); + va_end(tmp); + }*/ + + template void strCpySafe(char (&buffer)[count], const char *src) { + strCpy(buffer, count, src); + } + + template void strCatSafe(char (&buffer)[count], const char * src) { + strCat(buffer, count, src); + } + + + + /// String builder. + class NVCORE_CLASS StringBuilder + { + public: + + StringBuilder(); + explicit StringBuilder( uint size_hint ); + StringBuilder(const char * str); + StringBuilder(const char * str, uint len); + StringBuilder(const StringBuilder & other); + + ~StringBuilder(); + + StringBuilder & format( const char * format, ... ) __attribute__((format (printf, 2, 3))); + StringBuilder & formatList( const char * format, va_list arg ); + + StringBuilder & append(char c); + StringBuilder & append(const char * str); + StringBuilder & append(const char * str, uint len); + StringBuilder & append(const StringBuilder & str); + StringBuilder & appendFormat(const char * format, ...) __attribute__((format (printf, 2, 3))); + StringBuilder & appendFormatList(const char * format, va_list arg); + + StringBuilder & appendSpace(uint n); + + StringBuilder & number( int i, int base = 10 ); + StringBuilder & number( uint i, int base = 10 ); + + StringBuilder & reserve(uint size_hint); + StringBuilder & copy(const char * str); + StringBuilder & copy(const char * str, uint len); + StringBuilder & copy(const StringBuilder & str); + + StringBuilder & toLower(); + StringBuilder & toUpper(); + + bool endsWith(const char * str) const; + bool beginsWith(const char * str) const; + + char * reverseFind(char c); + + void reset(); + bool isNull() const { return m_size == 0; } + + // const char * accessors + //operator const char * () const { return m_str; } + //operator char * () { return m_str; } + const char * str() const { return m_str; } + char * str() { return m_str; } + + char * release(); // Release ownership of string. + void acquire(char *); // Take ownership of string. + + /// Implement value semantics. + StringBuilder & operator=( const StringBuilder & s ) { + return copy(s); + } + + /// Implement value semantics. + StringBuilder & operator=( const char * s ) { + return copy(s); + } + + /// Equal operator. + bool operator==( const StringBuilder & s ) const { + return strMatch(s.m_str, m_str); + } + + /// Return the exact length. + uint length() const { return isNull() ? 0 : strLen(m_str); } + + /// Return the size of the string container. + uint capacity() const { return m_size; } + + /// Return the hash of the string. + uint hash() const { return isNull() ? 0 : strHash(m_str); } + + // Swap strings. + friend void swap(StringBuilder & a, StringBuilder & b); + + protected: + + /// Size of the string container. + uint m_size; + + /// String. + char * m_str; + + }; + + + /// Path string. @@ This should be called PathBuilder. + class NVCORE_CLASS Path : public StringBuilder + { + public: + Path() : StringBuilder() {} + explicit Path(int size_hint) : StringBuilder(size_hint) {} + Path(const char * str) : StringBuilder(str) {} + Path(const Path & path) : StringBuilder(path) {} + + const char * fileName() const; + const char * extension() const; + + void translatePath(char pathSeparator = NV_PATH_SEPARATOR); + + void appendSeparator(char pathSeparator = NV_PATH_SEPARATOR); + + void stripFileName(); + void stripExtension(); + + // statics + NVCORE_API static char separator(); + NVCORE_API static const char * fileName(const char *); + NVCORE_API static const char * extension(const char *); + + NVCORE_API static void translatePath(char * path, char pathSeparator = NV_PATH_SEPARATOR); + }; + + + /// String class. + class NVCORE_CLASS String + { + public: + + /// Constructs a null string. @sa isNull() + String() + { + data = NULL; + } + + /// Constructs a shared copy of str. + String(const String & str) + { + data = str.data; + if (data != NULL) addRef(); + } + + /// Constructs a shared string from a standard string. + String(const char * str) + { + setString(str); + } + + /// Constructs a shared string from a standard string. + String(const char * str, int length) + { + setString(str, length); + } + + /// Constructs a shared string from a StringBuilder. + String(const StringBuilder & str) + { + setString(str); + } + + /// Dtor. + ~String() + { + release(); + } + + String clone() const; + + /// Release the current string and allocate a new one. + const String & operator=( const char * str ) + { + release(); + setString( str ); + return *this; + } + + /// Release the current string and allocate a new one. + const String & operator=( const StringBuilder & str ) + { + release(); + setString( str ); + return *this; + } + + /// Implement value semantics. + String & operator=( const String & str ) + { + if (str.data != data) + { + release(); + data = str.data; + addRef(); + } + return *this; + } + + /// Equal operator. + bool operator==( const String & str ) const + { + return strMatch(str.data, data); + } + + /// Equal operator. + bool operator==( const char * str ) const + { + return strMatch(str, data); + } + + /// Not equal operator. + bool operator!=( const String & str ) const + { + return !strMatch(str.data, data); + } + + /// Not equal operator. + bool operator!=( const char * str ) const + { + return !strMatch(str, data); + } + + /// Returns true if this string is the null string. + bool isNull() const { return data == NULL; } + + /// Return the exact length. + uint length() const { nvDebugCheck(data != NULL); return strLen(data); } + + /// Return the hash of the string. + uint hash() const { nvDebugCheck(data != NULL); return strHash(data); } + + /// const char * cast operator. + operator const char * () const { return data; } + + /// Get string pointer. + const char * str() const { return data; } + + + private: + + // Add reference count. + void addRef(); + + // Decrease reference count. + void release(); + + uint16 getRefCount() const + { + nvDebugCheck(data != NULL); + return *reinterpret_cast(data - 2); + } + + void setRefCount(uint16 count) { + nvDebugCheck(data != NULL); + nvCheck(count < 0xFFFF); + *reinterpret_cast(const_cast(data - 2)) = uint16(count); + } + + void setData(const char * str) { + data = str + 2; + } + + void allocString(const char * str) + { + allocString(str, strLen(str)); + } + + void allocString(const char * str, uint length); + + void setString(const char * str); + void setString(const char * str, uint length); + void setString(const StringBuilder & str); + + // Swap strings. + friend void swap(String & a, String & b); + + private: + + const char * data; + + }; + + template <> struct Hash { + uint operator()(const String & str) const { return str.hash(); } + }; + + + // Like AutoPtr, but for const char strings. + class AutoString + { + NV_FORBID_COPY(AutoString); + NV_FORBID_HEAPALLOC(); + public: + + // Ctor. + AutoString(const char * p = NULL) : m_ptr(p) { } + +#if NV_CC_CPP11 + // Move ctor. + AutoString(AutoString && ap) : m_ptr(ap.m_ptr) { ap.m_ptr = NULL; } +#endif + + // Dtor. Deletes owned pointer. + ~AutoString() { + delete [] m_ptr; + m_ptr = NULL; + } + + // Delete owned pointer and assign new one. + void operator=(const char * p) { + if (p != m_ptr) + { + delete [] m_ptr; + m_ptr = p; + } + } + + // Get pointer. + const char * ptr() const { return m_ptr; } + operator const char *() const { return m_ptr; } + + // Relinquish ownership of the underlying pointer and returns that pointer. + const char * release() { + const char * tmp = m_ptr; + m_ptr = NULL; + return tmp; + } + + // comparison operators. + friend bool operator == (const AutoString & ap, const char * const p) { + return (ap.ptr() == p); + } + friend bool operator != (const AutoString & ap, const char * const p) { + return (ap.ptr() != p); + } + friend bool operator == (const char * const p, const AutoString & ap) { + return (ap.ptr() == p); + } + friend bool operator != (const char * const p, const AutoString & ap) { + return (ap.ptr() != p); + } + + private: + const char * m_ptr; + }; + +} // nv namespace + +#endif // NV_CORE_STRING_H diff --git a/thirdparty/thekla_atlas/src/nvcore/Stream.h b/thirdparty/thekla_atlas/src/nvcore/Stream.h new file mode 100755 index 00000000..c35c0d0c --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/Stream.h @@ -0,0 +1,164 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_CORE_STREAM_H +#define NV_CORE_STREAM_H + +#include "nvcore.h" +#include "Debug.h" + +namespace nv +{ + + /// Base stream class. + class NVCORE_CLASS Stream { + public: + + enum ByteOrder { + LittleEndian = false, + BigEndian = true, + }; + + /// Get the byte order of the system. + static ByteOrder getSystemByteOrder() { +#if NV_LITTLE_ENDIAN + return LittleEndian; +#else + return BigEndian; +#endif + } + + + /// Ctor. + Stream() : m_byteOrder(LittleEndian) { } + + /// Virtual destructor. + virtual ~Stream() {} + + /// Set byte order. + void setByteOrder(ByteOrder bo) { m_byteOrder = bo; } + + /// Get byte order. + ByteOrder byteOrder() const { return m_byteOrder; } + + + /// Serialize the given data. + virtual uint serialize( void * data, uint len ) = 0; + + /// Move to the given position in the archive. + virtual void seek( uint pos ) = 0; + + /// Return the current position in the archive. + virtual uint tell() const = 0; + + /// Return the current size of the archive. + virtual uint size() const = 0; + + /// Determine if there has been any error. + virtual bool isError() const = 0; + + /// Clear errors. + virtual void clearError() = 0; + + /// Return true if the stream is at the end. + virtual bool isAtEnd() const = 0; + + /// Return true if the stream is seekable. + virtual bool isSeekable() const = 0; + + /// Return true if this is an input stream. + virtual bool isLoading() const = 0; + + /// Return true if this is an output stream. + virtual bool isSaving() const = 0; + + + void advance(uint offset) { seek(tell() + offset); } + + + // friends + friend Stream & operator<<( Stream & s, bool & c ) { +#if NV_OS_DARWIN && !NV_CC_CPP11 + nvStaticCheck(sizeof(bool) == 4); + uint8 b = c ? 1 : 0; + s.serialize( &b, 1 ); + c = (b != 0); +#else + nvStaticCheck(sizeof(bool) == 1); + s.serialize( &c, 1 ); +#endif + return s; + } + friend Stream & operator<<( Stream & s, char & c ) { + nvStaticCheck(sizeof(char) == 1); + s.serialize( &c, 1 ); + return s; + } + friend Stream & operator<<( Stream & s, uint8 & c ) { + nvStaticCheck(sizeof(uint8) == 1); + s.serialize( &c, 1 ); + return s; + } + friend Stream & operator<<( Stream & s, int8 & c ) { + nvStaticCheck(sizeof(int8) == 1); + s.serialize( &c, 1 ); + return s; + } + friend Stream & operator<<( Stream & s, uint16 & c ) { + nvStaticCheck(sizeof(uint16) == 2); + return s.byteOrderSerialize( &c, 2 ); + } + friend Stream & operator<<( Stream & s, int16 & c ) { + nvStaticCheck(sizeof(int16) == 2); + return s.byteOrderSerialize( &c, 2 ); + } + friend Stream & operator<<( Stream & s, uint32 & c ) { + nvStaticCheck(sizeof(uint32) == 4); + return s.byteOrderSerialize( &c, 4 ); + } + friend Stream & operator<<( Stream & s, int32 & c ) { + nvStaticCheck(sizeof(int32) == 4); + return s.byteOrderSerialize( &c, 4 ); + } + friend Stream & operator<<( Stream & s, uint64 & c ) { + nvStaticCheck(sizeof(uint64) == 8); + return s.byteOrderSerialize( &c, 8 ); + } + friend Stream & operator<<( Stream & s, int64 & c ) { + nvStaticCheck(sizeof(int64) == 8); + return s.byteOrderSerialize( &c, 8 ); + } + friend Stream & operator<<( Stream & s, float & c ) { + nvStaticCheck(sizeof(float) == 4); + return s.byteOrderSerialize( &c, 4 ); + } + friend Stream & operator<<( Stream & s, double & c ) { + nvStaticCheck(sizeof(double) == 8); + return s.byteOrderSerialize( &c, 8 ); + } + + protected: + + /// Serialize in the stream byte order. + Stream & byteOrderSerialize( void * v, uint len ) { + if( m_byteOrder == getSystemByteOrder() ) { + serialize( v, len ); + } + else { + for( uint i = len; i > 0; i-- ) { + serialize( (uint8 *)v + i - 1, 1 ); + } + } + return *this; + } + + + private: + + ByteOrder m_byteOrder; + + }; + +} // nv namespace + +#endif // NV_CORE_STREAM_H diff --git a/thirdparty/thekla_atlas/src/nvcore/Utils.h b/thirdparty/thekla_atlas/src/nvcore/Utils.h new file mode 100755 index 00000000..f20e42cd --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/Utils.h @@ -0,0 +1,315 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_CORE_UTILS_H +#define NV_CORE_UTILS_H + +#include "Debug.h" // nvDebugCheck + +#include // for placement new + + +// Just in case. Grrr. +#undef min +#undef max + +#define NV_INT8_MIN (-128) +#define NV_INT8_MAX 127 +#define NV_UINT8_MAX 255 +#define NV_INT16_MIN (-32767-1) +#define NV_INT16_MAX 32767 +#define NV_UINT16_MAX 0xffff +#define NV_INT32_MIN (-2147483647-1) +#define NV_INT32_MAX 2147483647 +#define NV_UINT32_MAX 0xffffffff +#define NV_INT64_MAX POSH_I64(9223372036854775807) +#define NV_INT64_MIN (-POSH_I64(9223372036854775807)-1) +#define NV_UINT64_MAX POSH_U64(0xffffffffffffffff) + +#define NV_HALF_MAX 65504.0F +#define NV_FLOAT_MAX 3.402823466e+38F + +#define NV_INTEGER_TO_FLOAT_MAX 16777217 // Largest integer such that it and all smaller integers can be stored in a 32bit float. + + +namespace nv +{ + // Less error prone than casting. From CB: + // http://cbloomrants.blogspot.com/2011/06/06-17-11-c-casting-is-devil.html + + // These intentionally look like casts. + + // uint64 casts: + template inline uint64 U64(T x) { return x; } + //template <> inline uint64 U64(uint64 x) { return x; } + template <> inline uint64 U64(int64 x) { nvDebugCheck(x >= 0); return (uint64)x; } + //template <> inline uint64 U32(uint32 x) { return x; } + template <> inline uint64 U64(int32 x) { nvDebugCheck(x >= 0); return (uint64)x; } + //template <> inline uint64 U64(uint16 x) { return x; } + template <> inline uint64 U64(int16 x) { nvDebugCheck(x >= 0); return (uint64)x; } + //template <> inline uint64 U64(uint8 x) { return x; } + template <> inline uint64 U64(int8 x) { nvDebugCheck(x >= 0); return (uint64)x; } + + // int64 casts: + template inline int64 I64(T x) { return x; } + template <> inline int64 I64(uint64 x) { nvDebugCheck(x <= NV_INT64_MAX); return (int64)x; } + //template <> inline uint64 U64(int64 x) { return x; } + //template <> inline uint64 U32(uint32 x) { return x; } + //template <> inline uint64 U64(int32 x) { return x; } + //template <> inline uint64 U64(uint16 x) { return x; } + //template <> inline uint64 U64(int16 x) { return x; } + //template <> inline uint64 U64(uint8 x) { return x; } + //template <> inline uint64 U64(int8 x) { return x; } + + // uint32 casts: + template inline uint32 U32(T x) { return x; } + template <> inline uint32 U32(uint64 x) { nvDebugCheck(x <= NV_UINT32_MAX); return (uint32)x; } + template <> inline uint32 U32(int64 x) { nvDebugCheck(x >= 0 && x <= NV_UINT32_MAX); return (uint32)x; } + //template <> inline uint32 U32(uint32 x) { return x; } + template <> inline uint32 U32(int32 x) { nvDebugCheck(x >= 0); return (uint32)x; } + //template <> inline uint32 U32(uint16 x) { return x; } + template <> inline uint32 U32(int16 x) { nvDebugCheck(x >= 0); return (uint32)x; } + //template <> inline uint32 U32(uint8 x) { return x; } + template <> inline uint32 U32(int8 x) { nvDebugCheck(x >= 0); return (uint32)x; } + + // int32 casts: + template inline int32 I32(T x) { return x; } + template <> inline int32 I32(uint64 x) { nvDebugCheck(x <= NV_INT32_MAX); return (int32)x; } + template <> inline int32 I32(int64 x) { nvDebugCheck(x >= NV_INT32_MIN && x <= NV_UINT32_MAX); return (int32)x; } + template <> inline int32 I32(uint32 x) { nvDebugCheck(x <= NV_INT32_MAX); return (int32)x; } + //template <> inline int32 I32(int32 x) { return x; } + //template <> inline int32 I32(uint16 x) { return x; } + //template <> inline int32 I32(int16 x) { return x; } + //template <> inline int32 I32(uint8 x) { return x; } + //template <> inline int32 I32(int8 x) { return x; } + + // uint16 casts: + template inline uint16 U16(T x) { return x; } + template <> inline uint16 U16(uint64 x) { nvDebugCheck(x <= NV_UINT16_MAX); return (uint16)x; } + template <> inline uint16 U16(int64 x) { nvDebugCheck(x >= 0 && x <= NV_UINT16_MAX); return (uint16)x; } + template <> inline uint16 U16(uint32 x) { nvDebugCheck(x <= NV_UINT16_MAX); return (uint16)x; } + template <> inline uint16 U16(int32 x) { nvDebugCheck(x >= 0 && x <= NV_UINT16_MAX); return (uint16)x; } + //template <> inline uint16 U16(uint16 x) { return x; } + template <> inline uint16 U16(int16 x) { nvDebugCheck(x >= 0); return (uint16)x; } + //template <> inline uint16 U16(uint8 x) { return x; } + template <> inline uint16 U16(int8 x) { nvDebugCheck(x >= 0); return (uint16)x; } + + // int16 casts: + template inline int16 I16(T x) { return x; } + template <> inline int16 I16(uint64 x) { nvDebugCheck(x <= NV_INT16_MAX); return (int16)x; } + template <> inline int16 I16(int64 x) { nvDebugCheck(x >= NV_INT16_MIN && x <= NV_UINT16_MAX); return (int16)x; } + template <> inline int16 I16(uint32 x) { nvDebugCheck(x <= NV_INT16_MAX); return (int16)x; } + template <> inline int16 I16(int32 x) { nvDebugCheck(x >= NV_INT16_MIN && x <= NV_UINT16_MAX); return (int16)x; } + template <> inline int16 I16(uint16 x) { nvDebugCheck(x <= NV_INT16_MAX); return (int16)x; } + //template <> inline int16 I16(int16 x) { return x; } + //template <> inline int16 I16(uint8 x) { return x; } + //template <> inline int16 I16(int8 x) { return x; } + + // uint8 casts: + template inline uint8 U8(T x) { return x; } + template <> inline uint8 U8(uint64 x) { nvDebugCheck(x <= NV_UINT8_MAX); return (uint8)x; } + template <> inline uint8 U8(int64 x) { nvDebugCheck(x >= 0 && x <= NV_UINT8_MAX); return (uint8)x; } + template <> inline uint8 U8(uint32 x) { nvDebugCheck(x <= NV_UINT8_MAX); return (uint8)x; } + template <> inline uint8 U8(int32 x) { nvDebugCheck(x >= 0 && x <= NV_UINT8_MAX); return (uint8)x; } + template <> inline uint8 U8(uint16 x) { nvDebugCheck(x <= NV_UINT8_MAX); return (uint8)x; } + template <> inline uint8 U8(int16 x) { nvDebugCheck(x >= 0 && x <= NV_UINT8_MAX); return (uint8)x; } + //template <> inline uint8 U8(uint8 x) { return x; } + template <> inline uint8 U8(int8 x) { nvDebugCheck(x >= 0); return (uint8)x; } + //template <> inline uint8 U8(int8 x) { nvDebugCheck(x >= 0.0f && x <= 255.0f); return (uint8)x; } + + // int8 casts: + template inline int8 I8(T x) { return x; } + template <> inline int8 I8(uint64 x) { nvDebugCheck(x <= NV_INT8_MAX); return (int8)x; } + template <> inline int8 I8(int64 x) { nvDebugCheck(x >= NV_INT8_MIN && x <= NV_UINT8_MAX); return (int8)x; } + template <> inline int8 I8(uint32 x) { nvDebugCheck(x <= NV_INT8_MAX); return (int8)x; } + template <> inline int8 I8(int32 x) { nvDebugCheck(x >= NV_INT8_MIN && x <= NV_UINT8_MAX); return (int8)x; } + template <> inline int8 I8(uint16 x) { nvDebugCheck(x <= NV_INT8_MAX); return (int8)x; } + template <> inline int8 I8(int16 x) { nvDebugCheck(x >= NV_INT8_MIN && x <= NV_UINT8_MAX); return (int8)x; } + template <> inline int8 I8(uint8 x) { nvDebugCheck(x <= NV_INT8_MAX); return (int8)x; } + //template <> inline int8 I8(int8 x) { return x; } + + // float casts: + template inline float F32(T x) { return x; } + template <> inline float F32(uint64 x) { nvDebugCheck(x <= NV_INTEGER_TO_FLOAT_MAX); return (float)x; } + template <> inline float F32(int64 x) { nvDebugCheck(x >= -NV_INTEGER_TO_FLOAT_MAX && x <= NV_INTEGER_TO_FLOAT_MAX); return (float)x; } + template <> inline float F32(uint32 x) { nvDebugCheck(x <= NV_INTEGER_TO_FLOAT_MAX); return (float)x; } + template <> inline float F32(int32 x) { nvDebugCheck(x >= -NV_INTEGER_TO_FLOAT_MAX && x <= NV_INTEGER_TO_FLOAT_MAX); return (float)x; } + // The compiler should not complain about these conversions: + //template <> inline float F32(uint16 x) { nvDebugCheck(return (float)x; } + //template <> inline float F32(int16 x) { nvDebugCheck(return (float)x; } + //template <> inline float F32(uint8 x) { nvDebugCheck(return (float)x; } + //template <> inline float F32(int8 x) { nvDebugCheck(return (float)x; } + + + /// Swap two values. + template + inline void swap(T & a, T & b) + { + T temp(a); + a = b; + b = temp; + } + + /// Return the maximum of the two arguments. For floating point values, it returns the second value if the first is NaN. + template + //inline const T & max(const T & a, const T & b) + inline T max(const T & a, const T & b) + { + return (b < a) ? a : b; + } + + /// Return the maximum of the four arguments. + template + //inline const T & max4(const T & a, const T & b, const T & c) + inline T max4(const T & a, const T & b, const T & c, const T & d) + { + return max(max(a, b), max(c, d)); + } + + /// Return the maximum of the three arguments. + template + //inline const T & max3(const T & a, const T & b, const T & c) + inline T max3(const T & a, const T & b, const T & c) + { + return max(a, max(b, c)); + } + + /// Return the minimum of two values. + template + //inline const T & min(const T & a, const T & b) + inline T min(const T & a, const T & b) + { + return (a < b) ? a : b; + } + + /// Return the maximum of the three arguments. + template + //inline const T & min3(const T & a, const T & b, const T & c) + inline T min3(const T & a, const T & b, const T & c) + { + return min(a, min(b, c)); + } + + /// Clamp between two values. + template + //inline const T & clamp(const T & x, const T & a, const T & b) + inline T clamp(const T & x, const T & a, const T & b) + { + return min(max(x, a), b); + } + + /** Return the next power of two. + * @see http://graphics.stanford.edu/~seander/bithacks.html + * @warning Behaviour for 0 is undefined. + * @note isPowerOfTwo(x) == true -> nextPowerOfTwo(x) == x + * @note nextPowerOfTwo(x) = 2 << log2(x-1) + */ + inline uint32 nextPowerOfTwo(uint32 x) + { + nvDebugCheck( x != 0 ); +#if 1 // On modern CPUs this is supposed to be as fast as using the bsr instruction. + x--; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x+1; +#else + uint p = 1; + while( x > p ) { + p += p; + } + return p; +#endif + } + + inline uint64 nextPowerOfTwo(uint64 x) + { + nvDebugCheck(x != 0); + uint p = 1; + while (x > p) { + p += p; + } + return p; + } + + // @@ Should I just use a macro instead? + template + inline bool isPowerOfTwo(T n) + { + return (n & (n-1)) == 0; + } + + + // @@ Move this to utils? + /// Delete all the elements of a container. + template + void deleteAll(T & container) + { + for (typename T::PseudoIndex i = container.start(); !container.isDone(i); container.advance(i)) + { + delete container[i]; + } + } + + + + // @@ Specialize these methods for numeric, pointer, and pod types. + + template + void construct_range(T * restrict ptr, uint new_size, uint old_size) { + for (uint i = old_size; i < new_size; i++) { + new(ptr+i) T; // placement new + } + } + + template + void construct_range(T * restrict ptr, uint new_size, uint old_size, const T & elem) { + for (uint i = old_size; i < new_size; i++) { + new(ptr+i) T(elem); // placement new + } + } + + template + void construct_range(T * restrict ptr, uint new_size, uint old_size, const T * src) { + for (uint i = old_size; i < new_size; i++) { + new(ptr+i) T(src[i]); // placement new + } + } + + template + void destroy_range(T * restrict ptr, uint new_size, uint old_size) { + for (uint i = new_size; i < old_size; i++) { + (ptr+i)->~T(); // Explicit call to the destructor + } + } + + template + void fill(T * restrict dst, uint count, const T & value) { + for (uint i = 0; i < count; i++) { + dst[i] = value; + } + } + + template + void copy_range(T * restrict dst, const T * restrict src, uint count) { + for (uint i = 0; i < count; i++) { + dst[i] = src[i]; + } + } + + template + bool find(const T & element, const T * restrict ptr, uint begin, uint end, uint * index) { + for (uint i = begin; i < end; i++) { + if (ptr[i] == element) { + if (index != NULL) *index = i; + return true; + } + } + return false; + } + +} // nv namespace + +#endif // NV_CORE_UTILS_H diff --git a/thirdparty/thekla_atlas/src/nvcore/nvcore.h b/thirdparty/thekla_atlas/src/nvcore/nvcore.h new file mode 100755 index 00000000..a3deb66b --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/nvcore.h @@ -0,0 +1,357 @@ +// This code is in the public domain -- Ignacio CastaƱo + +#pragma once +#ifndef NV_CORE_H +#define NV_CORE_H + +// Function linkage +#if NVCORE_SHARED +#ifdef NVCORE_EXPORTS +#define NVCORE_API DLL_EXPORT +#define NVCORE_CLASS DLL_EXPORT_CLASS +#else +#define NVCORE_API DLL_IMPORT +#define NVCORE_CLASS DLL_IMPORT +#endif +#else // NVCORE_SHARED +#define NVCORE_API +#define NVCORE_CLASS +#endif // NVCORE_SHARED + + +// Platform definitions +#include + +// OS: +// NV_OS_WIN32 +// NV_OS_WIN64 +// NV_OS_MINGW +// NV_OS_CYGWIN +// NV_OS_LINUX +// NV_OS_UNIX +// NV_OS_DARWIN +// NV_OS_XBOX +// NV_OS_ORBIS +// NV_OS_IOS + +#define NV_OS_STRING POSH_OS_STRING + +#if defined POSH_OS_LINUX +# define NV_OS_LINUX 1 +# define NV_OS_UNIX 1 +#elif defined POSH_OS_ORBIS +# define NV_OS_ORBIS 1 +#elif defined POSH_OS_FREEBSD +# define NV_OS_FREEBSD 1 +# define NV_OS_UNIX 1 +#elif defined POSH_OS_OPENBSD +# define NV_OS_OPENBSD 1 +# define NV_OS_UNIX 1 +#elif defined POSH_OS_CYGWIN32 +# define NV_OS_CYGWIN 1 +#elif defined POSH_OS_MINGW +# define NV_OS_MINGW 1 +# define NV_OS_WIN32 1 +#elif defined POSH_OS_OSX +# define NV_OS_OSX 1 // IC: Adding this, because iOS defines NV_OS_DARWIN too. +# define NV_OS_DARWIN 1 +# define NV_OS_UNIX 1 +#elif defined POSH_OS_IOS +# define NV_OS_DARWIN 1 //ACS should we keep this on IOS? +# define NV_OS_UNIX 1 +# define NV_OS_IOS 1 +#elif defined POSH_OS_UNIX +# define NV_OS_UNIX 1 +#elif defined POSH_OS_WIN64 +# define NV_OS_WIN32 1 +# define NV_OS_WIN64 1 +#elif defined POSH_OS_WIN32 +# define NV_OS_WIN32 1 +#elif defined POSH_OS_XBOX +# define NV_OS_XBOX 1 +#elif defined POSH_OS_DURANGO +# define NV_OS_DURANGO 1 +#else +# error "Unsupported OS" +#endif + + +// Is this a console OS? (i.e. connected to a TV) +#if NV_OS_ORBIS || NV_OS_XBOX || NV_OS_DURANGO +# define NV_OS_CONSOLE 1 +#endif + + +// Threading: +// some platforms don't implement __thread or similar for thread-local-storage +#if NV_OS_UNIX || NV_OS_ORBIS || NV_OS_IOS //ACStodoIOS darwin instead of ios? +# define NV_OS_USE_PTHREAD 1 +# if NV_OS_IOS +# define NV_OS_HAS_TLS_QUALIFIER 0 +# else +# define NV_OS_HAS_TLS_QUALIFIER 1 +# endif +#else +# define NV_OS_USE_PTHREAD 0 +# define NV_OS_HAS_TLS_QUALIFIER 1 +#endif + + +// CPUs: +// NV_CPU_X86 +// NV_CPU_X86_64 +// NV_CPU_PPC +// NV_CPU_ARM + +#define NV_CPU_STRING POSH_CPU_STRING + +#if defined POSH_CPU_X86_64 +//# define NV_CPU_X86 1 +# define NV_CPU_X86_64 1 +#elif defined POSH_CPU_X86 +# define NV_CPU_X86 1 +#elif defined POSH_CPU_PPC +# define NV_CPU_PPC 1 +#elif defined POSH_CPU_STRONGARM +# define NV_CPU_ARM 1 +#else +# error "Unsupported CPU" +#endif + + +// Compiler: +// NV_CC_GNUC +// NV_CC_MSVC +// NV_CC_CLANG + +#if defined POSH_COMPILER_CLANG +# define NV_CC_CLANG 1 +# define NV_CC_GNUC 1 // Clang is compatible with GCC. +# define NV_CC_STRING "clang" +#elif defined POSH_COMPILER_GCC +# define NV_CC_GNUC 1 +# define NV_CC_STRING "gcc" +#elif defined POSH_COMPILER_MSVC +# define NV_CC_MSVC 1 +# define NV_CC_STRING "msvc" +#else +# error "Unsupported compiler" +#endif + +#if NV_CC_MSVC +#define NV_CC_CPP11 (__cplusplus > 199711L || _MSC_VER >= 1800) // Visual Studio 2013 has all the features we use, but doesn't advertise full C++11 support yet. +#else +// @@ IC: This works in CLANG, about GCC? +// @@ ES: Doesn't work in gcc. These 3 features are available in GCC >= 4.4. +#ifdef __clang__ +#define NV_CC_CPP11 (__has_feature(cxx_deleted_functions) && __has_feature(cxx_rvalue_references) && __has_feature(cxx_static_assert)) +#elif defined __GNUC__ +#define NV_CC_CPP11 ( __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) +#endif +#endif + +// Endiannes: +#define NV_LITTLE_ENDIAN POSH_LITTLE_ENDIAN +#define NV_BIG_ENDIAN POSH_BIG_ENDIAN +#define NV_ENDIAN_STRING POSH_ENDIAN_STRING + + +// Define the right printf prefix for size_t arguments: +#if POSH_64BIT_POINTER +# define NV_SIZET_PRINTF_PREFIX POSH_I64_PRINTF_PREFIX +#else +# define NV_SIZET_PRINTF_PREFIX +#endif + + +// cmake config +#include "nvconfig.h" + +#if NV_OS_DARWIN +#include +//#include + +// Type definitions: +typedef uint8_t uint8; +typedef int8_t int8; + +typedef uint16_t uint16; +typedef int16_t int16; + +typedef uint32_t uint32; +typedef int32_t int32; + +typedef uint64_t uint64; +typedef int64_t int64; + +// POSH gets this wrong due to __LP64__ +#undef POSH_I64_PRINTF_PREFIX +#define POSH_I64_PRINTF_PREFIX "ll" + +#else + +// Type definitions: +typedef posh_u8_t uint8; +typedef posh_i8_t int8; + +typedef posh_u16_t uint16; +typedef posh_i16_t int16; + +typedef posh_u32_t uint32; +typedef posh_i32_t int32; + +//#if NV_OS_DARWIN +// OSX-64 is supposed to be LP64 (longs and pointers are 64 bits), thus uint64 is defined as +// unsigned long. However, some OSX headers define it as unsigned long long, producing errors, +// even though both types are 64 bit. Ideally posh should handle that, but it has not been +// updated in ages, so here I'm just falling back to the standard C99 types defined in inttypes.h +//#include +//typedef posh_u64_t uint64_t; +//typedef posh_i64_t int64_t; +//#else +typedef posh_u64_t uint64; +typedef posh_i64_t int64; +//#endif +#if NV_OS_DARWIN +// To avoid duplicate definitions. +#define _UINT64 +#endif +#endif + +// Aliases +typedef uint32 uint; + + +// Version string: +#define NV_VERSION_STRING \ + NV_OS_STRING "/" NV_CC_STRING "/" NV_CPU_STRING"/" \ + NV_ENDIAN_STRING"-endian - " __DATE__ "-" __TIME__ + + +// Disable copy constructor and assignment operator. +#if NV_CC_CPP11 +#define NV_FORBID_COPY(C) \ + C( const C & ) = delete; \ + C &operator=( const C & ) = delete +#else +#define NV_FORBID_COPY(C) \ + private: \ + C( const C & ); \ + C &operator=( const C & ) +#endif + +// Disable dynamic allocation on the heap. +// See Prohibiting Heap-Based Objects in More Effective C++. +#define NV_FORBID_HEAPALLOC() \ + private: \ + void *operator new(size_t size); \ + void *operator new[](size_t size) + //static void *operator new(size_t size); \ + //static void *operator new[](size_t size); + +// String concatenation macros. +#define NV_STRING_JOIN2(arg1, arg2) NV_DO_STRING_JOIN2(arg1, arg2) +#define NV_DO_STRING_JOIN2(arg1, arg2) arg1 ## arg2 +#define NV_STRING_JOIN3(arg1, arg2, arg3) NV_DO_STRING_JOIN3(arg1, arg2, arg3) +#define NV_DO_STRING_JOIN3(arg1, arg2, arg3) arg1 ## arg2 ## arg3 +#define NV_STRING2(x) #x +#define NV_STRING(x) NV_STRING2(x) + +#if NV_CC_MSVC +#define NV_MULTI_LINE_MACRO_BEGIN do { +#define NV_MULTI_LINE_MACRO_END \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) \ + } while(false) \ + __pragma(warning(pop)) +#else +#define NV_MULTI_LINE_MACRO_BEGIN do { +#define NV_MULTI_LINE_MACRO_END } while(false) +#endif + +#if NV_CC_CPP11 +#define nvStaticCheck(x) static_assert((x), "Static assert "#x" failed") +#else +#define nvStaticCheck(x) typedef char NV_STRING_JOIN2(__static_assert_,__LINE__)[(x)] +#endif +#define NV_COMPILER_CHECK(x) nvStaticCheck(x) // I like this name best. + +// Make sure type definitions are fine. +NV_COMPILER_CHECK(sizeof(int8) == 1); +NV_COMPILER_CHECK(sizeof(uint8) == 1); +NV_COMPILER_CHECK(sizeof(int16) == 2); +NV_COMPILER_CHECK(sizeof(uint16) == 2); +NV_COMPILER_CHECK(sizeof(int32) == 4); +NV_COMPILER_CHECK(sizeof(uint32) == 4); +NV_COMPILER_CHECK(sizeof(int32) == 4); +NV_COMPILER_CHECK(sizeof(uint32) == 4); + +#include // for size_t +template char (&ArraySizeHelper(T (&array)[N]))[N]; +#define NV_ARRAY_SIZE(x) sizeof(ArraySizeHelper(x)) +//#define NV_ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) + +#if 0 // Disabled in The Witness. +#if NV_CC_MSVC +#define NV_MESSAGE(x) message(__FILE__ "(" NV_STRING(__LINE__) ") : " x) +#else +#define NV_MESSAGE(x) message(x) +#endif +#else +#define NV_MESSAGE(x) +#endif + + +// Startup initialization macro. +#define NV_AT_STARTUP(some_code) \ + namespace { \ + static struct NV_STRING_JOIN2(AtStartup_, __LINE__) { \ + NV_STRING_JOIN2(AtStartup_, __LINE__)() { some_code; } \ + } \ + NV_STRING_JOIN3(AtStartup_, __LINE__, Instance); \ + } + +// Indicate the compiler that the parameter is not used to suppress compier warnings. +#if NV_CC_MSVC +#define NV_UNUSED(a) ((a)=(a)) +#else +#define NV_UNUSED(a) _Pragma(NV_STRING(unused(a))) +#endif + +// Null index. @@ Move this somewhere else... it's only used by nvmesh. +//const unsigned int NIL = unsigned int(~0); +#define NIL uint(~0) + +// Null pointer. +#ifndef NULL +#define NULL 0 +#endif + +// Platform includes +#if NV_CC_MSVC +# if NV_OS_WIN32 +# include "DefsVcWin32.h" +# elif NV_OS_XBOX +# include "DefsVcXBox.h" +# elif NV_OS_DURANGO +# include "DefsVcDurango.h" +# else +# error "MSVC: Platform not supported" +# endif +#elif NV_CC_GNUC +# if NV_OS_LINUX +# include "DefsGnucLinux.h" +# elif NV_OS_DARWIN || NV_OS_FREEBSD || NV_OS_OPENBSD +# include "DefsGnucDarwin.h" +# elif NV_OS_ORBIS +# include "DefsOrbis.h" +# elif NV_OS_MINGW +# include "DefsGnucWin32.h" +# elif NV_OS_CYGWIN +# error "GCC: Cygwin not supported" +# else +# error "GCC: Platform not supported" +# endif +#endif + +#endif // NV_CORE_H diff --git a/thirdparty/thekla_atlas/src/nvcore/scanf.c b/thirdparty/thekla_atlas/src/nvcore/scanf.c new file mode 100755 index 00000000..bf9d2931 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvcore/scanf.c @@ -0,0 +1,641 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * From: Id: vfscanf.c,v 1.13 1998/09/25 12:20:27 obrien Exp + * From: static char sccsid[] = "@(#)strtol.c 8.1 (Berkeley) 6/4/93"; + * From: static char sccsid[] = "@(#)strtoul.c 8.1 (Berkeley) 6/4/93"; + */ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#pragma warning(disable : 4244) // conversion from '*' to '*', possible loss of data +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4267) // '=' : conversion from 'size_t' to 'int', possible loss of data + +#define strtoq _strtoi64 +#define strtouq _strtoui64 +#define bcopy(b1,b2,len) (memmove((b2), (b1), (len)), (void) 0) + +typedef int long long quad_t; +typedef unsigned long long u_quad_t; +typedef unsigned char u_char; + +#define BUF 32 /* Maximum length of numeric string. */ + +/* + * Flags used during conversion. + */ +#define LONG 0x01 /* l: long or double */ +#define SHORT 0x04 /* h: short */ +#define SUPPRESS 0x08 /* suppress assignment */ +#define POINTER 0x10 /* weird %p pointer (`fake hex') */ +#define NOSKIP 0x20 /* do not skip blanks */ +#define QUAD 0x400 + +/* + * The following are used in numeric conversions only: + * SIGNOK, NDIGITS, DPTOK, and EXPOK are for floating point; + * SIGNOK, NDIGITS, PFXOK, and NZDIGITS are for integral. + */ +#define SIGNOK 0x40 /* +/- is (still) legal */ +#define NDIGITS 0x80 /* no digits detected */ + +#define DPTOK 0x100 /* (float) decimal point is still legal */ +#define EXPOK 0x200 /* (float) exponent (e+3, etc) still legal */ + +#define PFXOK 0x100 /* 0x prefix is (still) legal */ +#define NZDIGITS 0x200 /* no zero digits detected */ + +/* + * Conversion types. + */ +#define CT_CHAR 0 /* %c conversion */ +#define CT_CCL 1 /* %[...] conversion */ +#define CT_STRING 2 /* %s conversion */ +#define CT_INT 3 /* integer, i.e., strtoq or strtouq */ +typedef u_quad_t (*ccfntype)(const char *, char **, int); + +static const u_char *__sccl(char *, const u_char *); + +int +vsscanf(const char *inp, char const *fmt0, va_list ap) +{ + int inr; + const u_char *fmt = (const u_char *)fmt0; + int c; /* character from format, or conversion */ + size_t width; /* field width, or 0 */ + char *p; /* points into all kinds of strings */ + int n; /* handy integer */ + int flags; /* flags as defined above */ + char *p0; /* saves original value of p when necessary */ + int nassigned; /* number of fields assigned */ + int nconversions; /* number of conversions */ + int nread; /* number of characters consumed from fp */ + int base; /* base argument to strtoq/strtouq */ + ccfntype ccfn; /* conversion function (strtoq/strtouq) */ + char ccltab[256]; /* character class table for %[...] */ + char buf[BUF]; /* buffer for numeric conversions */ + + /* `basefix' is used to avoid `if' tests in the integer scanner */ + static short basefix[17] = + { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + + inr = strlen(inp); + + nassigned = 0; + nconversions = 0; + nread = 0; + base = 0; /* XXX just to keep gcc happy */ + ccfn = NULL; /* XXX just to keep gcc happy */ + for (;;) { + c = *fmt++; + if (c == 0) + return (nassigned); + if (isspace(c)) { + while (inr > 0 && isspace(*inp)) + nread++, inr--, inp++; + continue; + } + if (c != '%') + goto literal; + width = 0; + flags = 0; + /* + * switch on the format. continue if done; + * break once format type is derived. + */ +again: c = *fmt++; + switch (c) { + case '%': +literal: + if (inr <= 0) + goto input_failure; + if (*inp != c) + goto match_failure; + inr--, inp++; + nread++; + continue; + + case '*': + flags |= SUPPRESS; + goto again; + case 'l': + flags |= LONG; + goto again; + case 'q': + flags |= QUAD; + goto again; + case 'h': + flags |= SHORT; + goto again; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + width = width * 10 + c - '0'; + goto again; + + /* + * Conversions. + * + */ + case 'd': + c = CT_INT; + ccfn = (ccfntype)strtoq; + base = 10; + break; + + case 'i': + c = CT_INT; + ccfn = (ccfntype)strtoq; + base = 0; + break; + + case 'o': + c = CT_INT; + ccfn = strtouq; + base = 8; + break; + + case 'u': + c = CT_INT; + ccfn = strtouq; + base = 10; + break; + + case 'x': + flags |= PFXOK; /* enable 0x prefixing */ + c = CT_INT; + ccfn = strtouq; + base = 16; + break; + + case 's': + c = CT_STRING; + break; + + case '[': + fmt = __sccl(ccltab, fmt); + flags |= NOSKIP; + c = CT_CCL; + break; + + case 'c': + flags |= NOSKIP; + c = CT_CHAR; + break; + + case 'p': /* pointer format is like hex */ + flags |= POINTER | PFXOK; + c = CT_INT; + ccfn = strtouq; + base = 16; + break; + + case 'n': + nconversions++; + if (flags & SUPPRESS) /* ??? */ + continue; + if (flags & SHORT) + *va_arg(ap, short *) = nread; + else if (flags & LONG) + *va_arg(ap, long *) = nread; + else if (flags & QUAD) + *va_arg(ap, quad_t *) = nread; + else + *va_arg(ap, int *) = nread; + continue; + } + + /* + * We have a conversion that requires input. + */ + if (inr <= 0) + goto input_failure; + + /* + * Consume leading white space, except for formats + * that suppress this. + */ + if ((flags & NOSKIP) == 0) { + while (isspace(*inp)) { + nread++; + if (--inr > 0) + inp++; + else + goto input_failure; + } + /* + * Note that there is at least one character in + * the buffer, so conversions that do not set NOSKIP + * can no longer result in an input failure. + */ + } + + /* + * Do the conversion. + */ + switch (c) { + + case CT_CHAR: + /* scan arbitrary characters (sets NOSKIP) */ + if (width == 0) + width = 1; + if (flags & SUPPRESS) { + size_t sum = 0; + for (;;) { + if ((n = inr) < width) { + sum += n; + width -= n; + inp += n; + if (sum == 0) + goto input_failure; + break; + } else { + sum += width; + inr -= width; + inp += width; + break; + } + } + nread += sum; + } else { + bcopy(inp, va_arg(ap, char *), width); + inr -= width; + inp += width; + nread += width; + nassigned++; + } + nconversions++; + break; + + case CT_CCL: + /* scan a (nonempty) character class (sets NOSKIP) */ + if (width == 0) + width = (size_t)~0; /* `infinity' */ + /* take only those things in the class */ + if (flags & SUPPRESS) { + n = 0; + while (ccltab[(unsigned char)*inp]) { + n++, inr--, inp++; + if (--width == 0) + break; + if (inr <= 0) { + if (n == 0) + goto input_failure; + break; + } + } + if (n == 0) + goto match_failure; + } else { + p0 = p = va_arg(ap, char *); + while (ccltab[(unsigned char)*inp]) { + inr--; + *p++ = *inp++; + if (--width == 0) + break; + if (inr <= 0) { + if (p == p0) + goto input_failure; + break; + } + } + n = p - p0; + if (n == 0) + goto match_failure; + *p = 0; + nassigned++; + } + nread += n; + nconversions++; + break; + + case CT_STRING: + /* like CCL, but zero-length string OK, & no NOSKIP */ + if (width == 0) + width = (size_t)~0; + if (flags & SUPPRESS) { + n = 0; + while (!isspace(*inp)) { + n++, inr--, inp++; + if (--width == 0) + break; + if (inr <= 0) + break; + } + nread += n; + } else { + p0 = p = va_arg(ap, char *); + while (!isspace(*inp)) { + inr--; + *p++ = *inp++; + if (--width == 0) + break; + if (inr <= 0) + break; + } + *p = 0; + nread += p - p0; + nassigned++; + } + nconversions++; + continue; + + case CT_INT: + /* scan an integer as if by strtoq/strtouq */ +#ifdef hardway + if (width == 0 || width > sizeof(buf) - 1) + width = sizeof(buf) - 1; +#else + /* size_t is unsigned, hence this optimisation */ + if (--width > sizeof(buf) - 2) + width = sizeof(buf) - 2; + width++; +#endif + flags |= SIGNOK | NDIGITS | NZDIGITS; + for (p = buf; width; width--) { + c = *inp; + /* + * Switch on the character; `goto ok' + * if we accept it as a part of number. + */ + switch (c) { + + /* + * The digit 0 is always legal, but is + * special. For %i conversions, if no + * digits (zero or nonzero) have been + * scanned (only signs), we will have + * base==0. In that case, we should set + * it to 8 and enable 0x prefixing. + * Also, if we have not scanned zero digits + * before this, do not turn off prefixing + * (someone else will turn it off if we + * have scanned any nonzero digits). + */ + case '0': + if (base == 0) { + base = 8; + flags |= PFXOK; + } + if (flags & NZDIGITS) + flags &= ~(SIGNOK|NZDIGITS|NDIGITS); + else + flags &= ~(SIGNOK|PFXOK|NDIGITS); + goto ok; + + /* 1 through 7 always legal */ + case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + base = basefix[base]; + flags &= ~(SIGNOK | PFXOK | NDIGITS); + goto ok; + + /* digits 8 and 9 ok iff decimal or hex */ + case '8': case '9': + base = basefix[base]; + if (base <= 8) + break; /* not legal here */ + flags &= ~(SIGNOK | PFXOK | NDIGITS); + goto ok; + + /* letters ok iff hex */ + case 'A': case 'B': case 'C': + case 'D': case 'E': case 'F': + case 'a': case 'b': case 'c': + case 'd': case 'e': case 'f': + /* no need to fix base here */ + if (base <= 10) + break; /* not legal here */ + flags &= ~(SIGNOK | PFXOK | NDIGITS); + goto ok; + + /* sign ok only as first character */ + case '+': case '-': + if (flags & SIGNOK) { + flags &= ~SIGNOK; + goto ok; + } + break; + + /* x ok iff flag still set & 2nd char */ + case 'x': case 'X': + if (flags & PFXOK && p == buf + 1) { + base = 16; /* if %i */ + flags &= ~PFXOK; + goto ok; + } + break; + } + + /* + * If we got here, c is not a legal character + * for a number. Stop accumulating digits. + */ + break; + ok: + /* + * c is legal: store it and look at the next. + */ + *p++ = c; + if (--inr > 0) + inp++; + else + break; /* end of input */ + } + /* + * If we had only a sign, it is no good; push + * back the sign. If the number ends in `x', + * it was [sign] '0' 'x', so push back the x + * and treat it as [sign] '0'. + */ + if (flags & NDIGITS) { + if (p > buf) { + inp--; + inr++; + } + goto match_failure; + } + c = ((u_char *)p)[-1]; + if (c == 'x' || c == 'X') { + --p; + inp--; + inr++; + } + if ((flags & SUPPRESS) == 0) { + u_quad_t res; + + *p = 0; + res = (*ccfn)(buf, (char **)NULL, base); + if (flags & POINTER) + *va_arg(ap, void **) = + (void *)(uintptr_t)res; + else if (flags & SHORT) + *va_arg(ap, short *) = res; + else if (flags & LONG) + *va_arg(ap, long *) = res; + else if (flags & QUAD) + *va_arg(ap, quad_t *) = res; + else + *va_arg(ap, int *) = res; + nassigned++; + } + nread += p - buf; + nconversions++; + break; + + } + } +input_failure: + return (nconversions != 0 ? nassigned : -1); +match_failure: + return (nassigned); +} + + +/* + * Fill in the given table from the scanset at the given format + * (just after `['). Return a pointer to the character past the + * closing `]'. The table has a 1 wherever characters should be + * considered part of the scanset. + */ +static const u_char * +__sccl(char *tab, const u_char *fmt) +{ + int c, n, v; + + /* first `clear' the whole table */ + c = *fmt++; /* first char hat => negated scanset */ + if (c == '^') { + v = 1; /* default => accept */ + c = *fmt++; /* get new first char */ + } else + v = 0; /* default => reject */ + + /* XXX: Will not work if sizeof(tab*) > sizeof(char) */ + for (n = 0; n < 256; n++) + tab[n] = v; /* memset(tab, v, 256) */ + + if (c == 0) + return (fmt - 1);/* format ended before closing ] */ + + /* + * Now set the entries corresponding to the actual scanset + * to the opposite of the above. + * + * The first character may be ']' (or '-') without being special; + * the last character may be '-'. + */ + v = 1 - v; + for (;;) { + tab[c] = v; /* take character c */ +doswitch: + n = *fmt++; /* and examine the next */ + switch (n) { + + case 0: /* format ended too soon */ + return (fmt - 1); + + case '-': + /* + * A scanset of the form + * [01+-] + * is defined as `the digit 0, the digit 1, + * the character +, the character -', but + * the effect of a scanset such as + * [a-zA-Z0-9] + * is implementation defined. The V7 Unix + * scanf treats `a-z' as `the letters a through + * z', but treats `a-a' as `the letter a, the + * character -, and the letter a'. + * + * For compatibility, the `-' is not considerd + * to define a range if the character following + * it is either a close bracket (required by ANSI) + * or is not numerically greater than the character + * we just stored in the table (c). + */ + n = *fmt; + if (n == ']' || n < c) { + c = '-'; + break; /* resume the for(;;) */ + } + fmt++; + /* fill in the range */ + do { + tab[++c] = v; + } while (c < n); + c = n; + /* + * Alas, the V7 Unix scanf also treats formats + * such as [a-c-e] as `the letters a through e'. + * This too is permitted by the standard.... + */ + goto doswitch; + break; + + case ']': /* end of scanset */ + return (fmt); + + default: /* just another character */ + c = n; + break; + } + } + /* NOTREACHED */ +} + +/* +int +sscanf(const char *ibuf, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = vsscanf(ibuf, fmt, ap); + va_end(ap); + + return(ret); +} +*/ + +#ifdef __cplusplus +} +#endif diff --git a/thirdparty/thekla_atlas/src/nvimage/BitMap.cpp b/thirdparty/thekla_atlas/src/nvimage/BitMap.cpp new file mode 100755 index 00000000..8cc49644 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvimage/BitMap.cpp @@ -0,0 +1,27 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "BitMap.h" + +using namespace nv; + +void BitMap::resize(uint w, uint h, bool initValue) +{ + BitArray tmp(w*h); + + if (initValue) tmp.setAll(); + else tmp.clearAll(); + + // @@ Copying one bit at a time. This could be much faster. + for (uint y = 0; y < m_height; y++) + { + for (uint x = 0; x < m_width; x++) + { + //tmp.setBitAt(y*w + x, bitAt(x, y)); + if (bitAt(x, y) != initValue) tmp.toggleBitAt(y*w + x); + } + } + + swap(m_bitArray, tmp); + m_width = w; + m_height = h; +} diff --git a/thirdparty/thekla_atlas/src/nvimage/BitMap.h b/thirdparty/thekla_atlas/src/nvimage/BitMap.h new file mode 100755 index 00000000..a2853211 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvimage/BitMap.h @@ -0,0 +1,87 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_IMAGE_BITMAP_H +#define NV_IMAGE_BITMAP_H + +#include "nvimage.h" + +#include "nvcore/BitArray.h" + +namespace nv +{ + /// Bit map. This should probably be called BitImage. + class NVIMAGE_CLASS BitMap + { + public: + BitMap() : m_width(0), m_height(0) {} + BitMap(uint w, uint h) : m_width(w), m_height(h), m_bitArray(w*h) {} + + uint width() const { return m_width; } + uint height() const { return m_height; } + + void resize(uint w, uint h, bool initValue); + + bool bitAt(uint x, uint y) const + { + nvDebugCheck(x < m_width && y < m_height); + return m_bitArray.bitAt(y * m_width + x); + } + bool bitAt(uint idx) const + { + return m_bitArray.bitAt(idx); + } + + void setBitAt(uint x, uint y) + { + nvDebugCheck(x < m_width && y < m_height); + m_bitArray.setBitAt(y * m_width + x); + } + void setBitAt(uint idx) + { + m_bitArray.setBitAt(idx); + } + + void clearBitAt(uint x, uint y) + { + nvDebugCheck(x < m_width && y < m_height); + m_bitArray.clearBitAt(y * m_width + x); + } + void clearBitAt(uint idx) + { + m_bitArray.clearBitAt(idx); + } + + void clearAll() + { + m_bitArray.clearAll(); + } + + void setAll() + { + m_bitArray.setAll(); + } + + void toggleAll() + { + m_bitArray.toggleAll(); + } + + friend void swap(BitMap & a, BitMap & b) + { + nvCheck(a.m_width == b.m_width); + nvCheck(a.m_height == b.m_height); + swap(a.m_bitArray, b.m_bitArray); + } + + private: + + uint m_width; + uint m_height; + BitArray m_bitArray; + + }; + +} // nv namespace + +#endif // NV_IMAGE_BITMAP_H diff --git a/thirdparty/thekla_atlas/src/nvimage/CMakeLists.txt b/thirdparty/thekla_atlas/src/nvimage/CMakeLists.txt new file mode 100755 index 00000000..5bc3cf43 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvimage/CMakeLists.txt @@ -0,0 +1,51 @@ +PROJECT(nvimage) + +SET(IMAGE_SRCS + nvimage.h + BitMap.h BitMap.cpp + Image.h Image.cpp) + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +IF(PNG_FOUND) + SET(LIBS ${LIBS} ${PNG_LIBRARIES}) + INCLUDE_DIRECTORIES(${PNG_INCLUDE_DIR}) +ENDIF(PNG_FOUND) + +IF(JPEG_FOUND) + SET(LIBS ${LIBS} ${JPEG_LIBRARIES}) + INCLUDE_DIRECTORIES(${JPEG_INCLUDE_DIR}) +ENDIF(JPEG_FOUND) + +IF(TIFF_FOUND) + SET(LIBS ${LIBS} ${TIFF_LIBRARIES}) + INCLUDE_DIRECTORIES(${TIFF_INCLUDE_DIR}) +ENDIF(TIFF_FOUND) + +IF(OPENEXR_FOUND) + SET(LIBS ${LIBS} ${OPENEXR_LIBRARIES}) + INCLUDE_DIRECTORIES(${OPENEXR_INCLUDE_PATHS}) +ENDIF(OPENEXR_FOUND) + +IF(FREEIMAGE_FOUND) + SET(LIBS ${LIBS} ${FREEIMAGE_LIBRARIES}) + INCLUDE_DIRECTORIES(${FREEIMAGE_INCLUDE_PATH}) +ENDIF(FREEIMAGE_FOUND) + +# targets +ADD_DEFINITIONS(-DNVIMAGE_EXPORTS) + +IF(NVIMAGE_SHARED) + ADD_DEFINITIONS(-DNVIMAGE_SHARED=1) + ADD_LIBRARY(nvimage SHARED ${IMAGE_SRCS}) +ELSE(NVIMAGE_SHARED) + ADD_LIBRARY(nvimage ${IMAGE_SRCS}) +ENDIF(NVIMAGE_SHARED) + +TARGET_LINK_LIBRARIES(nvimage ${LIBS} nvcore nvmath posh) + +INSTALL(TARGETS nvimage + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib/static) + diff --git a/thirdparty/thekla_atlas/src/nvimage/Image.cpp b/thirdparty/thekla_atlas/src/nvimage/Image.cpp new file mode 100755 index 00000000..8c0cbcf4 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvimage/Image.cpp @@ -0,0 +1,210 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "Image.h" +//#include "ImageIO.h" + +#include "nvmath/Color.h" + +#include "nvcore/Debug.h" +#include "nvcore/Ptr.h" +#include "nvcore/Utils.h" // swap +#include "nvcore/Memory.h" // realloc, free + +#include // memcpy + + +using namespace nv; + +Image::Image() : m_width(0), m_height(0), m_format(Format_RGB), m_data(NULL) +{ +} + +Image::Image(const Image & img) : m_data(NULL) +{ + allocate(img.m_width, img.m_height, img.m_depth); + m_format = img.m_format; + memcpy(m_data, img.m_data, sizeof(Color32) * m_width * m_height * m_depth); +} + +Image::~Image() +{ + free(); +} + +const Image & Image::operator=(const Image & img) +{ + allocate(img.m_width, img.m_height, m_depth); + m_format = img.m_format; + memcpy(m_data, img.m_data, sizeof(Color32) * m_width * m_height * m_depth); + return *this; +} + + +void Image::allocate(uint w, uint h, uint d/*= 1*/) +{ + m_width = w; + m_height = h; + m_depth = d; + m_data = realloc(m_data, w * h * d); +} + +void Image::acquire(Color32 * data, uint w, uint h, uint d/*= 1*/) +{ + free(); + m_width = w; + m_height = h; + m_depth = d; + m_data = data; +} + +void Image::resize(uint w, uint h, uint d/*= 1*/) { + + Image img; + img.allocate(w, h, d); + + Color32 background(0,0,0,0); + + // Copy image. + uint x, y, z; + for(z = 0; z < min(d, m_depth); z++) { + for(y = 0; y < min(h, m_height); y++) { + for(x = 0; x < min(w, m_width); x++) { + img.pixel(x, y, z) = pixel(x, y, z); + } + for(; x < w; x++) { + img.pixel(x, y, z) = background; + } + } + for(; y < h; y++) { + for(x = 0; x < w; x++) { + img.pixel(x, y, z) = background; + } + } + } + for(; z < d; z++) { + for(y = 0; y < h; y++) { + for(x = 0; x < w; x++) { + img.pixel(x, y, z) = background; + } + } + } + + swap(m_width, img.m_width); + swap(m_height, img.m_height); + swap(m_depth, img.m_depth); + swap(m_format, img.m_format); + swap(m_data, img.m_data); +} + +/*bool Image::load(const char * name) +{ + free(); + + AutoPtr img(ImageIO::load(name)); + if (img == NULL) { + return false; + } + + swap(m_width, img->m_width); + swap(m_height, img->m_height); + swap(m_depth, img->m_depth); + swap(m_format, img->m_format); + swap(m_data, img->m_data); + + return true; +}*/ + +void Image::wrap(void * data, uint w, uint h, uint d) +{ + free(); + m_data = (Color32 *)data; + m_width = w; + m_height = h; + m_depth = d; +} + +void Image::unwrap() +{ + m_data = NULL; + m_width = 0; + m_height = 0; + m_depth = 0; +} + + +void Image::free() +{ + ::free(m_data); + m_data = NULL; +} + + +uint Image::width() const +{ + return m_width; +} + +uint Image::height() const +{ + return m_height; +} + +uint Image::depth() const +{ + return m_depth; +} + +const Color32 * Image::scanline(uint h) const +{ + nvDebugCheck(h < m_height); + return m_data + h * m_width; +} + +Color32 * Image::scanline(uint h) +{ + nvDebugCheck(h < m_height); + return m_data + h * m_width; +} + +const Color32 * Image::pixels() const +{ + return m_data; +} + +Color32 * Image::pixels() +{ + return m_data; +} + +const Color32 & Image::pixel(uint idx) const +{ + nvDebugCheck(idx < m_width * m_height * m_depth); + return m_data[idx]; +} + +Color32 & Image::pixel(uint idx) +{ + nvDebugCheck(idx < m_width * m_height * m_depth); + return m_data[idx]; +} + + +Image::Format Image::format() const +{ + return m_format; +} + +void Image::setFormat(Image::Format f) +{ + m_format = f; +} + +void Image::fill(Color32 c) +{ + const uint size = m_width * m_height * m_depth; + for (uint i = 0; i < size; ++i) + { + m_data[i] = c; + } +} + diff --git a/thirdparty/thekla_atlas/src/nvimage/Image.h b/thirdparty/thekla_atlas/src/nvimage/Image.h new file mode 100755 index 00000000..4c5748cb --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvimage/Image.h @@ -0,0 +1,89 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_IMAGE_IMAGE_H +#define NV_IMAGE_IMAGE_H + +#include "nvimage.h" +#include "nvcore/Debug.h" + +namespace nv +{ + class Color32; + + /// 32 bit RGBA image. + class NVIMAGE_CLASS Image + { + public: + + enum Format + { + Format_RGB, + Format_ARGB, + }; + + Image(); + Image(const Image & img); + ~Image(); + + const Image & operator=(const Image & img); + + + void allocate(uint w, uint h, uint d = 1); + void acquire(Color32 * data, uint w, uint h, uint d = 1); + //bool load(const char * name); + + void resize(uint w, uint h, uint d = 1); + + void wrap(void * data, uint w, uint h, uint d = 1); + void unwrap(); + + uint width() const; + uint height() const; + uint depth() const; + + const Color32 * scanline(uint h) const; + Color32 * scanline(uint h); + + const Color32 * pixels() const; + Color32 * pixels(); + + const Color32 & pixel(uint idx) const; + Color32 & pixel(uint idx); + + const Color32 & pixel(uint x, uint y, uint z = 0) const; + Color32 & pixel(uint x, uint y, uint z = 0); + + Format format() const; + void setFormat(Format f); + + void fill(Color32 c); + + private: + void free(); + + private: + uint m_width; + uint m_height; + uint m_depth; + Format m_format; + Color32 * m_data; + }; + + + inline const Color32 & Image::pixel(uint x, uint y, uint z) const + { + nvDebugCheck(x < m_width && y < m_height && z < m_depth); + return pixel((z * m_height + y) * m_width + x); + } + + inline Color32 & Image::pixel(uint x, uint y, uint z) + { + nvDebugCheck(x < m_width && y < m_height && z < m_depth); + return pixel((z * m_height + y) * m_width + x); + } + +} // nv namespace + + +#endif // NV_IMAGE_IMAGE_H diff --git a/thirdparty/thekla_atlas/src/nvimage/nvimage.h b/thirdparty/thekla_atlas/src/nvimage/nvimage.h new file mode 100755 index 00000000..5c89bd47 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvimage/nvimage.h @@ -0,0 +1,48 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_IMAGE_H +#define NV_IMAGE_H + +#include "nvcore/nvcore.h" +#include "nvcore/Debug.h" // nvDebugCheck +#include "nvcore/Utils.h" // isPowerOfTwo + +// Function linkage +#if NVIMAGE_SHARED +#ifdef NVIMAGE_EXPORTS +#define NVIMAGE_API DLL_EXPORT +#define NVIMAGE_CLASS DLL_EXPORT_CLASS +#else +#define NVIMAGE_API DLL_IMPORT +#define NVIMAGE_CLASS DLL_IMPORT +#endif +#else +#define NVIMAGE_API +#define NVIMAGE_CLASS +#endif + + +namespace nv { + + // Some utility functions: + + inline uint computeBitPitch(uint w, uint bitsize, uint alignmentInBits) + { + nvDebugCheck(isPowerOfTwo(alignmentInBits)); + + return ((w * bitsize + alignmentInBits - 1) / alignmentInBits) * alignmentInBits; + } + + inline uint computeBytePitch(uint w, uint bitsize, uint alignmentInBytes) + { + uint pitch = computeBitPitch(w, bitsize, 8*alignmentInBytes); + nvDebugCheck((pitch & 7) == 0); + + return (pitch + 7) / 8; + } + + +} // nv namespace + +#endif // NV_IMAGE_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Basis.cpp b/thirdparty/thekla_atlas/src/nvmath/Basis.cpp new file mode 100755 index 00000000..08241796 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Basis.cpp @@ -0,0 +1,270 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "Basis.h" + +using namespace nv; + + +/// Normalize basis vectors. +void Basis::normalize(float epsilon /*= NV_EPSILON*/) +{ + normal = ::normalizeSafe(normal, Vector3(0.0f), epsilon); + tangent = ::normalizeSafe(tangent, Vector3(0.0f), epsilon); + bitangent = ::normalizeSafe(bitangent, Vector3(0.0f), epsilon); +} + + +/// Gram-Schmidt orthogonalization. +/// @note Works only if the vectors are close to orthogonal. +void Basis::orthonormalize(float epsilon /*= NV_EPSILON*/) +{ + // N' = |N| + // T' = |T - (N' dot T) N'| + // B' = |B - (N' dot B) N' - (T' dot B) T'| + + normal = ::normalize(normal, epsilon); + + tangent -= normal * dot(normal, tangent); + tangent = ::normalize(tangent, epsilon); + + bitangent -= normal * dot(normal, bitangent); + bitangent -= tangent * dot(tangent, bitangent); + bitangent = ::normalize(bitangent, epsilon); +} + + + + +/// Robust orthonormalization. +/// Returns an orthonormal basis even when the original is degenerate. +void Basis::robustOrthonormalize(float epsilon /*= NV_EPSILON*/) +{ + // Normalize all vectors. + normalize(epsilon); + + if (lengthSquared(normal) < epsilon*epsilon) + { + // Build normal from tangent and bitangent. + normal = cross(tangent, bitangent); + + if (lengthSquared(normal) < epsilon*epsilon) + { + // Arbitrary basis. + tangent = Vector3(1, 0, 0); + bitangent = Vector3(0, 1, 0); + normal = Vector3(0, 0, 1); + return; + } + + normal = nv::normalize(normal, epsilon); + } + + // Project tangents to normal plane. + tangent -= normal * dot(normal, tangent); + bitangent -= normal * dot(normal, bitangent); + + if (lengthSquared(tangent) < epsilon*epsilon) + { + if (lengthSquared(bitangent) < epsilon*epsilon) + { + // Arbitrary basis. + buildFrameForDirection(normal); + } + else + { + // Build tangent from bitangent. + bitangent = nv::normalize(bitangent, epsilon); + + tangent = cross(bitangent, normal); + nvDebugCheck(isNormalized(tangent, epsilon)); + } + } + else + { + tangent = nv::normalize(tangent, epsilon); +#if 0 + bitangent -= tangent * dot(tangent, bitangent); + + if (lengthSquared(bitangent) < epsilon*epsilon) + { + bitangent = cross(tangent, normal); + nvDebugCheck(isNormalized(bitangent, epsilon)); + } + else + { + bitangent = nv::normalize(bitangent, epsilon); + } +#else + if (lengthSquared(bitangent) < epsilon*epsilon) + { + // Build bitangent from tangent. + bitangent = cross(tangent, normal); + nvDebugCheck(isNormalized(bitangent, epsilon)); + } + else + { + bitangent = nv::normalize(bitangent, epsilon); + + // At this point tangent and bitangent are orthogonal to normal, but we don't know whether their orientation. + + Vector3 bisector; + if (lengthSquared(tangent + bitangent) < epsilon*epsilon) + { + bisector = tangent; + } + else + { + bisector = nv::normalize(tangent + bitangent); + } + Vector3 axis = nv::normalize(cross(bisector, normal)); + + //nvDebugCheck(isNormalized(axis, epsilon)); + nvDebugCheck(equal(dot(axis, tangent), -dot(axis, bitangent), epsilon)); + + if (dot(axis, tangent) > 0) + { + tangent = bisector + axis; + bitangent = bisector - axis; + } + else + { + tangent = bisector - axis; + bitangent = bisector + axis; + } + + // Make sure the resulting tangents are still perpendicular to the normal. + tangent -= normal * dot(normal, tangent); + bitangent -= normal * dot(normal, bitangent); + + // Double check. + nvDebugCheck(equal(dot(normal, tangent), 0.0f, epsilon)); + nvDebugCheck(equal(dot(normal, bitangent), 0.0f, epsilon)); + + // Normalize. + tangent = nv::normalize(tangent); + bitangent = nv::normalize(bitangent); + + // If tangent and bitangent are not orthogonal, then derive bitangent from tangent, just in case... + if (!equal(dot(tangent, bitangent), 0.0f, epsilon)) { + bitangent = cross(tangent, normal); + bitangent = nv::normalize(bitangent); + } + } +#endif + } + + /*// Check vector lengths. + if (!isNormalized(normal, epsilon)) + { + nvDebug("%f %f %f\n", normal.x, normal.y, normal.z); + nvDebug("%f %f %f\n", tangent.x, tangent.y, tangent.z); + nvDebug("%f %f %f\n", bitangent.x, bitangent.y, bitangent.z); + }*/ + + nvDebugCheck(isNormalized(normal, epsilon)); + nvDebugCheck(isNormalized(tangent, epsilon)); + nvDebugCheck(isNormalized(bitangent, epsilon)); + + // Check vector angles. + nvDebugCheck(equal(dot(normal, tangent), 0.0f, epsilon)); + nvDebugCheck(equal(dot(normal, bitangent), 0.0f, epsilon)); + nvDebugCheck(equal(dot(tangent, bitangent), 0.0f, epsilon)); + + // Check vector orientation. + const float det = dot(cross(normal, tangent), bitangent); + nvDebugCheck(equal(det, 1.0f, epsilon) || equal(det, -1.0f, epsilon)); +} + + +/// Build an arbitrary frame for the given direction. +void Basis::buildFrameForDirection(Vector3::Arg d, float angle/*= 0*/) +{ + nvCheck(isNormalized(d)); + normal = d; + + // Choose minimum axis. + if (fabsf(normal.x) < fabsf(normal.y) && fabsf(normal.x) < fabsf(normal.z)) + { + tangent = Vector3(1, 0, 0); + } + else if (fabsf(normal.y) < fabsf(normal.z)) + { + tangent = Vector3(0, 1, 0); + } + else + { + tangent = Vector3(0, 0, 1); + } + + // Ortogonalize + tangent -= normal * dot(normal, tangent); + tangent = ::normalize(tangent); + + bitangent = cross(normal, tangent); + + // Rotate frame around normal according to angle. + if (angle != 0.0f) { + float c = cosf(angle); + float s = sinf(angle); + Vector3 tmp = c * tangent - s * bitangent; + bitangent = s * tangent + c * bitangent; + tangent = tmp; + } +} + +bool Basis::isValid() const +{ + if (equal(normal, Vector3(0.0f))) return false; + if (equal(tangent, Vector3(0.0f))) return false; + if (equal(bitangent, Vector3(0.0f))) return false; + + if (equal(determinant(), 0.0f)) return false; + + return true; +} + + +/// Transform by this basis. (From this basis to object space). +Vector3 Basis::transform(Vector3::Arg v) const +{ + Vector3 o = tangent * v.x; + o += bitangent * v.y; + o += normal * v.z; + return o; +} + +/// Transform by the transpose. (From object space to this basis). +Vector3 Basis::transformT(Vector3::Arg v) +{ + return Vector3(dot(tangent, v), dot(bitangent, v), dot(normal, v)); +} + +/// Transform by the inverse. (From object space to this basis). +/// @note Uses Cramer's rule so the inverse is not accurate if the basis is ill-conditioned. +Vector3 Basis::transformI(Vector3::Arg v) const +{ + const float det = determinant(); + nvDebugCheck(!equal(det, 0.0f, 0.0f)); + + const float idet = 1.0f / det; + + // Rows of the inverse matrix. + Vector3 r0( + (bitangent.y * normal.z - bitangent.z * normal.y), + -(bitangent.x * normal.z - bitangent.z * normal.x), + (bitangent.x * normal.y - bitangent.y * normal.x)); + + Vector3 r1( + -(tangent.y * normal.z - tangent.z * normal.y), + (tangent.x * normal.z - tangent.z * normal.x), + -(tangent.x * normal.y - tangent.y * normal.x)); + + Vector3 r2( + (tangent.y * bitangent.z - tangent.z * bitangent.y), + -(tangent.x * bitangent.z - tangent.z * bitangent.x), + (tangent.x * bitangent.y - tangent.y * bitangent.x)); + + return Vector3(dot(v, r0), dot(v, r1), dot(v, r2)) * idet; +} + + diff --git a/thirdparty/thekla_atlas/src/nvmath/Basis.h b/thirdparty/thekla_atlas/src/nvmath/Basis.h new file mode 100755 index 00000000..e8146afd --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Basis.h @@ -0,0 +1,82 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_MATH_BASIS_H +#define NV_MATH_BASIS_H + +#include "nvmath.h" +#include "Vector.inl" +#include "Matrix.h" + +namespace nv +{ + + /// Basis class to compute tangent space basis, ortogonalizations and to + /// transform vectors from one space to another. + class Basis + { + public: + + /// Create a null basis. + Basis() : tangent(0, 0, 0), bitangent(0, 0, 0), normal(0, 0, 0) {} + + /// Create a basis given three vectors. + Basis(Vector3::Arg n, Vector3::Arg t, Vector3::Arg b) : tangent(t), bitangent(b), normal(n) {} + + /// Create a basis with the given tangent vectors and the handness. + Basis(Vector3::Arg n, Vector3::Arg t, float sign) + { + build(n, t, sign); + } + + NVMATH_API void normalize(float epsilon = NV_EPSILON); + NVMATH_API void orthonormalize(float epsilon = NV_EPSILON); + NVMATH_API void robustOrthonormalize(float epsilon = NV_EPSILON); + NVMATH_API void buildFrameForDirection(Vector3::Arg d, float angle = 0); + + /// Calculate the determinant [ F G N ] to obtain the handness of the basis. + float handness() const + { + return determinant() > 0.0f ? 1.0f : -1.0f; + } + + /// Build a basis from 2 vectors and a handness flag. + void build(Vector3::Arg n, Vector3::Arg t, float sign) + { + normal = n; + tangent = t; + bitangent = sign * cross(t, n); + } + + /// Compute the determinant of this basis. + float determinant() const + { + return + tangent.x * bitangent.y * normal.z - tangent.z * bitangent.y * normal.x + + tangent.y * bitangent.z * normal.x - tangent.y * bitangent.x * normal.z + + tangent.z * bitangent.x * normal.y - tangent.x * bitangent.z * normal.y; + } + + bool isValid() const; + + // Get transform matrix for this basis. + NVMATH_API Matrix matrix() const; + + // Transform by this basis. (From this basis to object space). + NVMATH_API Vector3 transform(Vector3::Arg v) const; + + // Transform by the transpose. (From object space to this basis). + NVMATH_API Vector3 transformT(Vector3::Arg v); + + // Transform by the inverse. (From object space to this basis). + NVMATH_API Vector3 transformI(Vector3::Arg v) const; + + + Vector3 tangent; + Vector3 bitangent; + Vector3 normal; + }; + +} // nv namespace + +#endif // NV_MATH_BASIS_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Box.cpp b/thirdparty/thekla_atlas/src/nvmath/Box.cpp new file mode 100755 index 00000000..8f2014a0 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Box.cpp @@ -0,0 +1,119 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "Box.h" +#include "Box.inl" +#include "Sphere.h" + +using namespace nv; + + + + +// Clip the given segment against this box. +bool Box::clipSegment(const Vector3 & origin, const Vector3 & dir, float * t_near, float * t_far) const { + + // Avoid aliasing. + float tnear = *t_near; + float tfar = *t_far; + + // clip ray segment to box + for (int i = 0; i < 3; i++) + { + const float pos = origin.component[i] + tfar * dir.component[i]; + const float dt = tfar - tnear; + + if (dir.component[i] < 0) { + + // clip end point + if (pos < minCorner.component[i]) { + tfar = tnear + dt * (origin.component[i] - minCorner.component[i]) / (origin.component[i] - pos); + } + + // clip start point + if (origin.component[i] > maxCorner.component[i]) { + tnear = tnear + dt * (origin.component[i] - maxCorner.component[i]) / (tfar * dir.component[i]); + } + } + else { + + // clip end point + if (pos > maxCorner.component[i]) { + tfar = tnear + dt * (maxCorner.component[i] - origin.component[i]) / (pos - origin.component[i]); + } + + // clip start point + if (origin.component[i] < minCorner.component[i]) { + tnear = tnear + dt * (minCorner.component[i] - origin.component[i]) / (tfar * dir.component[i]); + } + } + + if (tnear > tfar) { + // Clipped away. + return false; + } + } + + // Return result. + *t_near = tnear; + *t_far = tfar; + return true; +} + + +float nv::distanceSquared(const Box &box, const Vector3 &point) { + Vector3 closest; + + if (point.x < box.minCorner.x) closest.x = box.minCorner.x; + else if (point.x > box.maxCorner.x) closest.x = box.maxCorner.x; + else closest.x = point.x; + + if (point.y < box.minCorner.y) closest.y = box.minCorner.y; + else if (point.y > box.maxCorner.y) closest.y = box.maxCorner.y; + else closest.y = point.y; + + if (point.z < box.minCorner.z) closest.z = box.minCorner.z; + else if (point.z > box.maxCorner.z) closest.z = box.maxCorner.z; + else closest.z = point.z; + + return lengthSquared(point - closest); +} + +bool nv::overlap(const Box &box, const Sphere &sphere) { + return distanceSquared(box, sphere.center) < sphere.radius * sphere.radius; +} + + +bool nv::intersect(const Box & box, const Vector3 & p, const Vector3 & id, float * t /*= NULL*/) { + // Precompute these in ray structure? + int sdx = (id.x < 0); + int sdy = (id.y < 0); + int sdz = (id.z < 0); + + float tmin = (box.corner( sdx).x - p.x) * id.x; + float tmax = (box.corner(1-sdx).x - p.x) * id.x; + float tymin = (box.corner( sdy).y - p.y) * id.y; + float tymax = (box.corner(1-sdy).y - p.y) * id.y; + + if ((tmin > tymax) || (tymin > tmax)) + return false; + + if (tymin > tmin) tmin = tymin; + if (tymax < tmax) tmax = tymax; + + float tzmin = (box.corner( sdz).z - p.z) * id.z; + float tzmax = (box.corner(1-sdz).z - p.z) * id.z; + + if ((tmin > tzmax) || (tzmin > tmax)) + return false; + + if (tzmin > tmin) tmin = tzmin; + if (tzmax < tmax) tmax = tzmax; + + if (tmax < 0) + return false; + + if (t != NULL) *t = tmin; + + return true; +} + diff --git a/thirdparty/thekla_atlas/src/nvmath/Box.h b/thirdparty/thekla_atlas/src/nvmath/Box.h new file mode 100755 index 00000000..a7c8b15e --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Box.h @@ -0,0 +1,105 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MATH_BOX_H +#define NV_MATH_BOX_H + +#include "Vector.h" + +#include // FLT_MAX + +namespace nv +{ + class Vector; + class Stream; + class Sphere; + + // Axis Aligned Bounding Box. + class Box + { + public: + + inline Box() {} + inline Box(const Box & b) : minCorner(b.minCorner), maxCorner(b.maxCorner) {} + inline Box(const Vector3 & mins, const Vector3 & maxs) : minCorner(mins), maxCorner(maxs) {} + + Box & operator=(const Box & b); + + operator const float * () const { return reinterpret_cast(this); } + + // Clear the bounds. + void clearBounds(); + + // min < max + bool isValid() const; + + // Build a cube centered on center and with edge = 2*dist + void cube(const Vector3 & center, float dist); + + // Build a box, given center and extents. + void setCenterExtents(const Vector3 & center, const Vector3 & extents); + + // Get box center. + Vector3 center() const; + + // Return extents of the box. + Vector3 extents() const; + + // Return extents of the box. + float extents(uint axis) const; + + // Add a point to this box. + void addPointToBounds(const Vector3 & p); + + // Add a box to this box. + void addBoxToBounds(const Box & b); + + // Add sphere to this box. + void addSphereToBounds(const Vector3 & p, float r); + + // Translate box. + void translate(const Vector3 & v); + + // Scale the box. + void scale(float s); + + // Expand the box by a fixed amount. + void expand(float r); + + // Get the area of the box. + float area() const; + + // Get the volume of the box. + float volume() const; + + // Return true if the box contains the given point. + bool contains(const Vector3 & p) const; + + // Split the given box in 8 octants and assign the ith one to this box. + void setOctant(const Box & box, const Vector3 & center, int i); + + + // Clip the given segment against this box. + bool clipSegment(const Vector3 & origin, const Vector3 & dir, float * t_near, float * t_far) const; + + + friend Stream & operator<< (Stream & s, Box & box); + + const Vector3 & corner(int i) const { return (&minCorner)[i]; } + + Vector3 minCorner; + Vector3 maxCorner; + }; + + float distanceSquared(const Box &box, const Vector3 &point); + bool overlap(const Box &box, const Sphere &sphere); + + // p is ray origin, id is inverse ray direction. + bool intersect(const Box & box, const Vector3 & p, const Vector3 & id, float * t); + +} // nv namespace + +#include "box.inl" + + +#endif // NV_MATH_BOX_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Box.inl b/thirdparty/thekla_atlas/src/nvmath/Box.inl new file mode 100755 index 00000000..dcfa70ff --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Box.inl @@ -0,0 +1,154 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MATH_BOX_INL +#define NV_MATH_BOX_INL + +#include "Box.h" +#include "Vector.inl" + +#include // FLT_MAX + +namespace nv +{ + // Default ctor. + //inline Box::Box() { }; + + // Copy ctor. + //inline Box::Box(const Box & b) : minCorner(b.minCorner), maxCorner(b.maxCorner) { } + + // Init ctor. + //inline Box::Box(const Vector3 & mins, const Vector3 & maxs) : minCorner(mins), maxCorner(maxs) { } + + // Assignment operator. + inline Box & Box::operator=(const Box & b) { minCorner = b.minCorner; maxCorner = b.maxCorner; return *this; } + + // Clear the bounds. + inline void Box::clearBounds() + { + minCorner.set(FLT_MAX, FLT_MAX, FLT_MAX); + maxCorner.set(-FLT_MAX, -FLT_MAX, -FLT_MAX); + } + + // min < max + inline bool Box::isValid() const + { + return minCorner.x <= maxCorner.x && minCorner.y <= maxCorner.y && minCorner.z <= maxCorner.z; + } + + // Build a cube centered on center and with edge = 2*dist + inline void Box::cube(const Vector3 & center, float dist) + { + setCenterExtents(center, Vector3(dist)); + } + + // Build a box, given center and extents. + inline void Box::setCenterExtents(const Vector3 & center, const Vector3 & extents) + { + minCorner = center - extents; + maxCorner = center + extents; + } + + // Get box center. + inline Vector3 Box::center() const + { + return (minCorner + maxCorner) * 0.5f; + } + + // Return extents of the box. + inline Vector3 Box::extents() const + { + return (maxCorner - minCorner) * 0.5f; + } + + // Return extents of the box. + inline float Box::extents(uint axis) const + { + nvDebugCheck(axis < 3); + if (axis == 0) return (maxCorner.x - minCorner.x) * 0.5f; + if (axis == 1) return (maxCorner.y - minCorner.y) * 0.5f; + if (axis == 2) return (maxCorner.z - minCorner.z) * 0.5f; + nvUnreachable(); + return 0.0f; + } + + // Add a point to this box. + inline void Box::addPointToBounds(const Vector3 & p) + { + minCorner = min(minCorner, p); + maxCorner = max(maxCorner, p); + } + + // Add a box to this box. + inline void Box::addBoxToBounds(const Box & b) + { + minCorner = min(minCorner, b.minCorner); + maxCorner = max(maxCorner, b.maxCorner); + } + + // Add sphere to this box. + inline void Box::addSphereToBounds(const Vector3 & p, float r) { + minCorner = min(minCorner, p - Vector3(r)); + maxCorner = min(maxCorner, p + Vector3(r)); + } + + // Translate box. + inline void Box::translate(const Vector3 & v) + { + minCorner += v; + maxCorner += v; + } + + // Scale the box. + inline void Box::scale(float s) + { + minCorner *= s; + maxCorner *= s; + } + + // Expand the box by a fixed amount. + inline void Box::expand(float r) { + minCorner -= Vector3(r,r,r); + maxCorner += Vector3(r,r,r); + } + + // Get the area of the box. + inline float Box::area() const + { + const Vector3 d = extents(); + return 8.0f * (d.x*d.y + d.x*d.z + d.y*d.z); + } + + // Get the volume of the box. + inline float Box::volume() const + { + Vector3 d = extents(); + return 8.0f * (d.x * d.y * d.z); + } + + // Return true if the box contains the given point. + inline bool Box::contains(const Vector3 & p) const + { + return + minCorner.x < p.x && minCorner.y < p.y && minCorner.z < p.z && + maxCorner.x > p.x && maxCorner.y > p.y && maxCorner.z > p.z; + } + + // Split the given box in 8 octants and assign the ith one to this box. + inline void Box::setOctant(const Box & box, const Vector3 & center, int i) + { + minCorner = box.minCorner; + maxCorner = box.maxCorner; + + if (i & 4) minCorner.x = center.x; + else maxCorner.x = center.x; + if (i & 2) minCorner.y = center.y; + else maxCorner.y = center.y; + if (i & 1) minCorner.z = center.z; + else maxCorner.z = center.z; + } + +} // nv namespace + + +#endif // NV_MATH_BOX_INL diff --git a/thirdparty/thekla_atlas/src/nvmath/CMakeLists.txt b/thirdparty/thekla_atlas/src/nvmath/CMakeLists.txt new file mode 100755 index 00000000..49f50496 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/CMakeLists.txt @@ -0,0 +1,38 @@ +PROJECT(nvmath) + +SET(MATH_SRCS + nvmath.h + Basis.h Basis.cpp + Box.h + Color.h + ConvexHull.h ConvexHull.cpp + Fitting.h Fitting.cpp + KahanSum.h + Matrix.h + Plane.h Plane.cpp + ProximityGrid.h ProximityGrid.cpp + Quaternion.h + Random.h Random.cpp + Solver.h Solver.cpp + Sparse.h Sparse.cpp + TypeSerialization.h TypeSerialization.cpp + Vector.h) + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +# targets +ADD_DEFINITIONS(-DNVMATH_EXPORTS) + +IF(NVMATH_SHARED) + ADD_DEFINITIONS(-DNVMATH_SHARED=1) + ADD_LIBRARY(nvmath SHARED ${MATH_SRCS}) +ELSE(NVMATH_SHARED) + ADD_LIBRARY(nvmath ${MATH_SRCS}) +ENDIF(NVMATH_SHARED) + +TARGET_LINK_LIBRARIES(nvmath ${LIBS} nvcore) + +INSTALL(TARGETS nvmath + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib/static) diff --git a/thirdparty/thekla_atlas/src/nvmath/Color.h b/thirdparty/thekla_atlas/src/nvmath/Color.h new file mode 100755 index 00000000..5cdc374b --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Color.h @@ -0,0 +1,150 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MATH_COLOR_H +#define NV_MATH_COLOR_H + +#include "nvmath.h" + +namespace nv +{ + + /// 64 bit color stored as BGRA. + class NVMATH_CLASS Color64 + { + public: + Color64() { } + Color64(const Color64 & c) : u(c.u) { } + Color64(uint16 R, uint16 G, uint16 B, uint16 A) { setRGBA(R, G, B, A); } + explicit Color64(uint64 U) : u(U) { } + + void setRGBA(uint16 R, uint16 G, uint16 B, uint16 A) + { + r = R; + g = G; + b = B; + a = A; + } + + operator uint64 () const { + return u; + } + + union { + struct { +#if NV_LITTLE_ENDIAN + uint16 r, a, b, g; +#else + uint16 a: 16; + uint16 r: 16; + uint16 g: 16; + uint16 b: 16; +#endif + }; + uint64 u; + }; + }; + + /// 32 bit color stored as BGRA. + class NVMATH_CLASS Color32 + { + public: + Color32() { } + Color32(const Color32 & c) : u(c.u) { } + Color32(uint8 R, uint8 G, uint8 B) { setRGBA(R, G, B, 0xFF); } + Color32(uint8 R, uint8 G, uint8 B, uint8 A) { setRGBA( R, G, B, A); } + //Color32(uint8 c[4]) { setRGBA(c[0], c[1], c[2], c[3]); } + //Color32(float R, float G, float B) { setRGBA(uint(R*255), uint(G*255), uint(B*255), 0xFF); } + //Color32(float R, float G, float B, float A) { setRGBA(uint(R*255), uint(G*255), uint(B*255), uint(A*255)); } + explicit Color32(uint32 U) : u(U) { } + + void setRGBA(uint8 R, uint8 G, uint8 B, uint8 A) + { + r = R; + g = G; + b = B; + a = A; + } + + void setBGRA(uint8 B, uint8 G, uint8 R, uint8 A = 0xFF) + { + r = R; + g = G; + b = B; + a = A; + } + + operator uint32 () const { + return u; + } + + union { + struct { +#if NV_LITTLE_ENDIAN + uint8 b, g, r, a; +#else + uint8 a: 8; + uint8 r: 8; + uint8 g: 8; + uint8 b: 8; +#endif + }; + uint8 component[4]; + uint32 u; + }; + }; + + + /// 16 bit 565 BGR color. + class NVMATH_CLASS Color16 + { + public: + Color16() { } + Color16(const Color16 & c) : u(c.u) { } + explicit Color16(uint16 U) : u(U) { } + + union { + struct { +#if NV_LITTLE_ENDIAN + uint16 b : 5; + uint16 g : 6; + uint16 r : 5; +#else + uint16 r : 5; + uint16 g : 6; + uint16 b : 5; +#endif + }; + uint16 u; + }; + }; + + /// 16 bit 4444 BGRA color. + class NVMATH_CLASS Color16_4444 + { + public: + Color16_4444() { } + Color16_4444(const Color16_4444 & c) : u(c.u) { } + explicit Color16_4444(uint16 U) : u(U) { } + + union { + struct { +#if NV_LITTLE_ENDIAN + uint16 b : 4; + uint16 g : 4; + uint16 r : 4; + uint16 a : 4; +#else + uint16 a : 4; + uint16 r : 4; + uint16 g : 4; + uint16 b : 4; +#endif + }; + uint16 u; + }; + }; + +} // nv namespace + +#endif // NV_MATH_COLOR_H diff --git a/thirdparty/thekla_atlas/src/nvmath/ConvexHull.cpp b/thirdparty/thekla_atlas/src/nvmath/ConvexHull.cpp new file mode 100755 index 00000000..a4a95dac --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/ConvexHull.cpp @@ -0,0 +1,120 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "ConvexHull.h" + +#include "Vector.inl" + +#include "nvcore/RadixSort.h" +#include "nvcore/Array.inl" + +using namespace nv; + +inline static float triangleArea(Vector2::Arg v1, Vector2::Arg v2, Vector2::Arg v3) +{ + return 0.5f * (v3.x * v1.y + v1.x * v2.y + v2.x * v3.y - v2.x * v1.y - v3.x * v2.y - v1.x * v3.y); +} + + +// Compute the convex hull using Graham Scan. +void nv::convexHull(const Array & input, Array & output, float epsilon/*=0*/) +{ + const uint inputCount = input.count(); + + Array coords; + coords.resize(inputCount); + + for (uint i = 0; i < inputCount; i++) { + coords[i] = input[i].x; + } + + RadixSort radix; + radix.sort(coords); + + const uint * ranks = radix.ranks(); + + Array top(inputCount); + Array bottom(inputCount); + + Vector2 P = input[ranks[0]]; + Vector2 Q = input[ranks[inputCount-1]]; + + float topy = max(P.y, Q.y); + float boty = min(P.y, Q.y); + + for (uint i = 0; i < inputCount; i++) { + Vector2 p = input[ranks[i]]; + if (p.y >= boty) top.append(p); + } + + for (uint i = 0; i < inputCount; i++) { + Vector2 p = input[ranks[inputCount-1-i]]; + if (p.y <= topy) bottom.append(p); + } + + // Filter top list. + output.clear(); + output.append(top[0]); + output.append(top[1]); + + for (uint i = 2; i < top.count(); ) { + Vector2 a = output[output.count()-2]; + Vector2 b = output[output.count()-1]; + Vector2 c = top[i]; + + float area = triangleArea(a, b, c); + + if (area >= -epsilon) { + output.popBack(); + } + + if (area < -epsilon || output.count() == 1) { + output.append(c); + i++; + } + } + + uint top_count = output.count(); + output.append(bottom[1]); + + // Filter bottom list. + for (uint i = 2; i < bottom.count(); ) { + Vector2 a = output[output.count()-2]; + Vector2 b = output[output.count()-1]; + Vector2 c = bottom[i]; + + float area = triangleArea(a, b, c); + + if (area >= -epsilon) { + output.popBack(); + } + + if (area < -epsilon || output.count() == top_count) { + output.append(c); + i++; + } + } + + // Remove duplicate element. + nvDebugCheck(output.front() == output.back()); + output.popBack(); +} + +/* +void testConvexHull() { + + Array points; + points.append(Vector2(1.00, 1.00)); + points.append(Vector2(0.00, 0.00)); + points.append(Vector2(1.00, 1.00)); + points.append(Vector2(1.00, -1.00)); + points.append(Vector2(2.00, 5.00)); + points.append(Vector2(-5.00, 3.00)); + points.append(Vector2(-4.00, -3.00)); + points.append(Vector2(7.00, -4.00)); + + Array hull; + convexHull(points, hull); + +} +*/ + diff --git a/thirdparty/thekla_atlas/src/nvmath/ConvexHull.h b/thirdparty/thekla_atlas/src/nvmath/ConvexHull.h new file mode 100755 index 00000000..6c2db5d7 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/ConvexHull.h @@ -0,0 +1,17 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_MATH_CONVEXHULL_H +#define NV_MATH_CONVEXHULL_H + +#include "nvmath.h" +#include "nvcore/Array.h" + +namespace nv { + class Vector2; + + void convexHull(const Array & input, Array & output, float epsilon = 0); + +} // namespace nv + +#endif // NV_MATH_CONVEXHULL_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Fitting.cpp b/thirdparty/thekla_atlas/src/nvmath/Fitting.cpp new file mode 100755 index 00000000..6cd5cb0f --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Fitting.cpp @@ -0,0 +1,1205 @@ +// This code is in the public domain -- Ignacio CastaƱo + +#include "Fitting.h" +#include "Vector.inl" +#include "Plane.inl" + +#include "nvcore/Array.inl" +#include "nvcore/Utils.h" // max, swap + +#include // FLT_MAX +//#include +#include + +using namespace nv; + +// @@ Move to EigenSolver.h + +// @@ We should be able to do something cheaper... +static Vector3 estimatePrincipalComponent(const float * __restrict matrix) +{ + const Vector3 row0(matrix[0], matrix[1], matrix[2]); + const Vector3 row1(matrix[1], matrix[3], matrix[4]); + const Vector3 row2(matrix[2], matrix[4], matrix[5]); + + float r0 = lengthSquared(row0); + float r1 = lengthSquared(row1); + float r2 = lengthSquared(row2); + + if (r0 > r1 && r0 > r2) return row0; + if (r1 > r2) return row1; + return row2; +} + + +static inline Vector3 firstEigenVector_PowerMethod(const float *__restrict matrix) +{ + if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) + { + return Vector3(0.0f); + } + + Vector3 v = estimatePrincipalComponent(matrix); + + const int NUM = 8; + for (int i = 0; i < NUM; i++) + { + float x = v.x * matrix[0] + v.y * matrix[1] + v.z * matrix[2]; + float y = v.x * matrix[1] + v.y * matrix[3] + v.z * matrix[4]; + float z = v.x * matrix[2] + v.y * matrix[4] + v.z * matrix[5]; + + float norm = max(max(x, y), z); + + v = Vector3(x, y, z) / norm; + } + + return v; +} + + +Vector3 nv::Fit::computeCentroid(int n, const Vector3 *__restrict points) +{ + Vector3 centroid(0.0f); + + for (int i = 0; i < n; i++) + { + centroid += points[i]; + } + centroid /= float(n); + + return centroid; +} + +Vector3 nv::Fit::computeCentroid(int n, const Vector3 *__restrict points, const float *__restrict weights, Vector3::Arg metric) +{ + Vector3 centroid(0.0f); + float total = 0.0f; + + for (int i = 0; i < n; i++) + { + total += weights[i]; + centroid += weights[i]*points[i]; + } + centroid /= total; + + return centroid; +} + +Vector4 nv::Fit::computeCentroid(int n, const Vector4 *__restrict points) +{ + Vector4 centroid(0.0f); + + for (int i = 0; i < n; i++) + { + centroid += points[i]; + } + centroid /= float(n); + + return centroid; +} + +Vector4 nv::Fit::computeCentroid(int n, const Vector4 *__restrict points, const float *__restrict weights, Vector4::Arg metric) +{ + Vector4 centroid(0.0f); + float total = 0.0f; + + for (int i = 0; i < n; i++) + { + total += weights[i]; + centroid += weights[i]*points[i]; + } + centroid /= total; + + return centroid; +} + + + +Vector3 nv::Fit::computeCovariance(int n, const Vector3 *__restrict points, float *__restrict covariance) +{ + // compute the centroid + Vector3 centroid = computeCentroid(n, points); + + // compute covariance matrix + for (int i = 0; i < 6; i++) + { + covariance[i] = 0.0f; + } + + for (int i = 0; i < n; i++) + { + Vector3 v = points[i] - centroid; + + covariance[0] += v.x * v.x; + covariance[1] += v.x * v.y; + covariance[2] += v.x * v.z; + covariance[3] += v.y * v.y; + covariance[4] += v.y * v.z; + covariance[5] += v.z * v.z; + } + + return centroid; +} + +Vector3 nv::Fit::computeCovariance(int n, const Vector3 *__restrict points, const float *__restrict weights, Vector3::Arg metric, float *__restrict covariance) +{ + // compute the centroid + Vector3 centroid = computeCentroid(n, points, weights, metric); + + // compute covariance matrix + for (int i = 0; i < 6; i++) + { + covariance[i] = 0.0f; + } + + for (int i = 0; i < n; i++) + { + Vector3 a = (points[i] - centroid) * metric; + Vector3 b = weights[i]*a; + + covariance[0] += a.x * b.x; + covariance[1] += a.x * b.y; + covariance[2] += a.x * b.z; + covariance[3] += a.y * b.y; + covariance[4] += a.y * b.z; + covariance[5] += a.z * b.z; + } + + return centroid; +} + +Vector4 nv::Fit::computeCovariance(int n, const Vector4 *__restrict points, float *__restrict covariance) +{ + // compute the centroid + Vector4 centroid = computeCentroid(n, points); + + // compute covariance matrix + for (int i = 0; i < 10; i++) + { + covariance[i] = 0.0f; + } + + for (int i = 0; i < n; i++) + { + Vector4 v = points[i] - centroid; + + covariance[0] += v.x * v.x; + covariance[1] += v.x * v.y; + covariance[2] += v.x * v.z; + covariance[3] += v.x * v.w; + + covariance[4] += v.y * v.y; + covariance[5] += v.y * v.z; + covariance[6] += v.y * v.w; + + covariance[7] += v.z * v.z; + covariance[8] += v.z * v.w; + + covariance[9] += v.w * v.w; + } + + return centroid; +} + +Vector4 nv::Fit::computeCovariance(int n, const Vector4 *__restrict points, const float *__restrict weights, Vector4::Arg metric, float *__restrict covariance) +{ + // compute the centroid + Vector4 centroid = computeCentroid(n, points, weights, metric); + + // compute covariance matrix + for (int i = 0; i < 10; i++) + { + covariance[i] = 0.0f; + } + + for (int i = 0; i < n; i++) + { + Vector4 a = (points[i] - centroid) * metric; + Vector4 b = weights[i]*a; + + covariance[0] += a.x * b.x; + covariance[1] += a.x * b.y; + covariance[2] += a.x * b.z; + covariance[3] += a.x * b.w; + + covariance[4] += a.y * b.y; + covariance[5] += a.y * b.z; + covariance[6] += a.y * b.w; + + covariance[7] += a.z * b.z; + covariance[8] += a.z * b.w; + + covariance[9] += a.w * b.w; + } + + return centroid; +} + + + +Vector3 nv::Fit::computePrincipalComponent_PowerMethod(int n, const Vector3 *__restrict points) +{ + float matrix[6]; + computeCovariance(n, points, matrix); + + return firstEigenVector_PowerMethod(matrix); +} + +Vector3 nv::Fit::computePrincipalComponent_PowerMethod(int n, const Vector3 *__restrict points, const float *__restrict weights, Vector3::Arg metric) +{ + float matrix[6]; + computeCovariance(n, points, weights, metric, matrix); + + return firstEigenVector_PowerMethod(matrix); +} + + + +static inline Vector3 firstEigenVector_EigenSolver3(const float *__restrict matrix) +{ + if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) + { + return Vector3(0.0f); + } + + float eigenValues[3]; + Vector3 eigenVectors[3]; + if (!nv::Fit::eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) + { + return Vector3(0.0f); + } + + return eigenVectors[0]; +} + +Vector3 nv::Fit::computePrincipalComponent_EigenSolver(int n, const Vector3 *__restrict points) +{ + float matrix[6]; + computeCovariance(n, points, matrix); + + return firstEigenVector_EigenSolver3(matrix); +} + +Vector3 nv::Fit::computePrincipalComponent_EigenSolver(int n, const Vector3 *__restrict points, const float *__restrict weights, Vector3::Arg metric) +{ + float matrix[6]; + computeCovariance(n, points, weights, metric, matrix); + + return firstEigenVector_EigenSolver3(matrix); +} + + + +static inline Vector4 firstEigenVector_EigenSolver4(const float *__restrict matrix) +{ + if (matrix[0] == 0 && matrix[4] == 0 && matrix[7] == 0&& matrix[9] == 0) + { + return Vector4(0.0f); + } + + float eigenValues[4]; + Vector4 eigenVectors[4]; + if (!nv::Fit::eigenSolveSymmetric4(matrix, eigenValues, eigenVectors)) + { + return Vector4(0.0f); + } + + return eigenVectors[0]; +} + +Vector4 nv::Fit::computePrincipalComponent_EigenSolver(int n, const Vector4 *__restrict points) +{ + float matrix[10]; + computeCovariance(n, points, matrix); + + return firstEigenVector_EigenSolver4(matrix); +} + +Vector4 nv::Fit::computePrincipalComponent_EigenSolver(int n, const Vector4 *__restrict points, const float *__restrict weights, Vector4::Arg metric) +{ + float matrix[10]; + computeCovariance(n, points, weights, metric, matrix); + + return firstEigenVector_EigenSolver4(matrix); +} + + + +void ArvoSVD(int rows, int cols, float * Q, float * diag, float * R); + +Vector3 nv::Fit::computePrincipalComponent_SVD(int n, const Vector3 *__restrict points) +{ + // Store the points in an n x n matrix + Array Q; Q.resize(n*n, 0.0f); + for (int i = 0; i < n; ++i) + { + Q[i*n+0] = points[i].x; + Q[i*n+1] = points[i].y; + Q[i*n+2] = points[i].z; + } + + // Alloc space for the SVD outputs + Array diag; diag.resize(n, 0.0f); + Array R; R.resize(n*n, 0.0f); + + ArvoSVD(n, n, &Q[0], &diag[0], &R[0]); + + // Get the principal component + return Vector3(R[0], R[1], R[2]); +} + +Vector4 nv::Fit::computePrincipalComponent_SVD(int n, const Vector4 *__restrict points) +{ + // Store the points in an n x n matrix + Array Q; Q.resize(n*n, 0.0f); + for (int i = 0; i < n; ++i) + { + Q[i*n+0] = points[i].x; + Q[i*n+1] = points[i].y; + Q[i*n+2] = points[i].z; + Q[i*n+3] = points[i].w; + } + + // Alloc space for the SVD outputs + Array diag; diag.resize(n, 0.0f); + Array R; R.resize(n*n, 0.0f); + + ArvoSVD(n, n, &Q[0], &diag[0], &R[0]); + + // Get the principal component + return Vector4(R[0], R[1], R[2], R[3]); +} + + + +Plane nv::Fit::bestPlane(int n, const Vector3 *__restrict points) +{ + // compute the centroid and covariance + float matrix[6]; + Vector3 centroid = computeCovariance(n, points, matrix); + + if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) + { + // If no plane defined, then return a horizontal plane. + return Plane(Vector3(0, 0, 1), centroid); + } + + float eigenValues[3]; + Vector3 eigenVectors[3]; + if (!eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) { + // If no plane defined, then return a horizontal plane. + return Plane(Vector3(0, 0, 1), centroid); + } + + return Plane(eigenVectors[2], centroid); +} + +bool nv::Fit::isPlanar(int n, const Vector3 * points, float epsilon/*=NV_EPSILON*/) +{ + // compute the centroid and covariance + float matrix[6]; + computeCovariance(n, points, matrix); + + float eigenValues[3]; + Vector3 eigenVectors[3]; + if (!eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) { + return false; + } + + return eigenValues[2] < epsilon; +} + + + +// Tridiagonal solver from Charles Bloom. +// Householder transforms followed by QL decomposition. +// Seems to be based on the code from Numerical Recipes in C. + +static void EigenSolver3_Tridiagonal(float mat[3][3], float * diag, float * subd); +static bool EigenSolver3_QLAlgorithm(float mat[3][3], float * diag, float * subd); + +bool nv::Fit::eigenSolveSymmetric3(const float matrix[6], float eigenValues[3], Vector3 eigenVectors[3]) +{ + nvDebugCheck(matrix != NULL && eigenValues != NULL && eigenVectors != NULL); + + float subd[3]; + float diag[3]; + float work[3][3]; + + work[0][0] = matrix[0]; + work[0][1] = work[1][0] = matrix[1]; + work[0][2] = work[2][0] = matrix[2]; + work[1][1] = matrix[3]; + work[1][2] = work[2][1] = matrix[4]; + work[2][2] = matrix[5]; + + EigenSolver3_Tridiagonal(work, diag, subd); + if (!EigenSolver3_QLAlgorithm(work, diag, subd)) + { + for (int i = 0; i < 3; i++) { + eigenValues[i] = 0; + eigenVectors[i] = Vector3(0); + } + return false; + } + + for (int i = 0; i < 3; i++) { + eigenValues[i] = (float)diag[i]; + } + + // eigenvectors are the columns; make them the rows : + + for (int i=0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + eigenVectors[j].component[i] = (float) work[i][j]; + } + } + + // shuffle to sort by singular value : + if (eigenValues[2] > eigenValues[0] && eigenValues[2] > eigenValues[1]) + { + swap(eigenValues[0], eigenValues[2]); + swap(eigenVectors[0], eigenVectors[2]); + } + if (eigenValues[1] > eigenValues[0]) + { + swap(eigenValues[0], eigenValues[1]); + swap(eigenVectors[0], eigenVectors[1]); + } + if (eigenValues[2] > eigenValues[1]) + { + swap(eigenValues[1], eigenValues[2]); + swap(eigenVectors[1], eigenVectors[2]); + } + + nvDebugCheck(eigenValues[0] >= eigenValues[1] && eigenValues[0] >= eigenValues[2]); + nvDebugCheck(eigenValues[1] >= eigenValues[2]); + + return true; +} + +static void EigenSolver3_Tridiagonal(float mat[3][3], float * diag, float * subd) +{ + // Householder reduction T = Q^t M Q + // Input: + // mat, symmetric 3x3 matrix M + // Output: + // mat, orthogonal matrix Q + // diag, diagonal entries of T + // subd, subdiagonal entries of T (T is symmetric) + const float epsilon = 1e-08f; + + float a = mat[0][0]; + float b = mat[0][1]; + float c = mat[0][2]; + float d = mat[1][1]; + float e = mat[1][2]; + float f = mat[2][2]; + + diag[0] = a; + subd[2] = 0.f; + if (fabsf(c) >= epsilon) + { + const float ell = sqrtf(b*b+c*c); + b /= ell; + c /= ell; + const float q = 2*b*e+c*(f-d); + diag[1] = d+c*q; + diag[2] = f-c*q; + subd[0] = ell; + subd[1] = e-b*q; + mat[0][0] = 1; mat[0][1] = 0; mat[0][2] = 0; + mat[1][0] = 0; mat[1][1] = b; mat[1][2] = c; + mat[2][0] = 0; mat[2][1] = c; mat[2][2] = -b; + } + else + { + diag[1] = d; + diag[2] = f; + subd[0] = b; + subd[1] = e; + mat[0][0] = 1; mat[0][1] = 0; mat[0][2] = 0; + mat[1][0] = 0; mat[1][1] = 1; mat[1][2] = 0; + mat[2][0] = 0; mat[2][1] = 0; mat[2][2] = 1; + } +} + +static bool EigenSolver3_QLAlgorithm(float mat[3][3], float * diag, float * subd) +{ + // QL iteration with implicit shifting to reduce matrix from tridiagonal + // to diagonal + const int maxiter = 32; + + for (int ell = 0; ell < 3; ell++) + { + int iter; + for (iter = 0; iter < maxiter; iter++) + { + int m; + for (m = ell; m <= 1; m++) + { + float dd = fabsf(diag[m]) + fabsf(diag[m+1]); + if ( fabsf(subd[m]) + dd == dd ) + break; + } + if ( m == ell ) + break; + + float g = (diag[ell+1]-diag[ell])/(2*subd[ell]); + float r = sqrtf(g*g+1); + if ( g < 0 ) + g = diag[m]-diag[ell]+subd[ell]/(g-r); + else + g = diag[m]-diag[ell]+subd[ell]/(g+r); + float s = 1, c = 1, p = 0; + for (int i = m-1; i >= ell; i--) + { + float f = s*subd[i], b = c*subd[i]; + if ( fabsf(f) >= fabsf(g) ) + { + c = g/f; + r = sqrtf(c*c+1); + subd[i+1] = f*r; + c *= (s = 1/r); + } + else + { + s = f/g; + r = sqrtf(s*s+1); + subd[i+1] = g*r; + s *= (c = 1/r); + } + g = diag[i+1]-p; + r = (diag[i]-g)*s+2*b*c; + p = s*r; + diag[i+1] = g+p; + g = c*r-b; + + for (int k = 0; k < 3; k++) + { + f = mat[k][i+1]; + mat[k][i+1] = s*mat[k][i]+c*f; + mat[k][i] = c*mat[k][i]-s*f; + } + } + diag[ell] -= p; + subd[ell] = g; + subd[m] = 0; + } + + if ( iter == maxiter ) + // should not get here under normal circumstances + return false; + } + + return true; +} + + + +// Tridiagonal solver for 4x4 symmetric matrices. + +static void EigenSolver4_Tridiagonal(float mat[4][4], float * diag, float * subd); +static bool EigenSolver4_QLAlgorithm(float mat[4][4], float * diag, float * subd); + +bool nv::Fit::eigenSolveSymmetric4(const float matrix[10], float eigenValues[4], Vector4 eigenVectors[4]) +{ + nvDebugCheck(matrix != NULL && eigenValues != NULL && eigenVectors != NULL); + + float subd[4]; + float diag[4]; + float work[4][4]; + + work[0][0] = matrix[0]; + work[0][1] = work[1][0] = matrix[1]; + work[0][2] = work[2][0] = matrix[2]; + work[0][3] = work[3][0] = matrix[3]; + work[1][1] = matrix[4]; + work[1][2] = work[2][1] = matrix[5]; + work[1][3] = work[3][1] = matrix[6]; + work[2][2] = matrix[7]; + work[2][3] = work[3][2] = matrix[8]; + work[3][3] = matrix[9]; + + EigenSolver4_Tridiagonal(work, diag, subd); + if (!EigenSolver4_QLAlgorithm(work, diag, subd)) + { + for (int i = 0; i < 4; i++) { + eigenValues[i] = 0; + eigenVectors[i] = Vector4(0); + } + return false; + } + + for (int i = 0; i < 4; i++) { + eigenValues[i] = (float)diag[i]; + } + + // eigenvectors are the columns; make them the rows + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + eigenVectors[j].component[i] = (float) work[i][j]; + } + } + + // sort by singular value + + for (int i = 0; i < 3; ++i) + { + for (int j = i+1; j < 4; ++j) + { + if (eigenValues[j] > eigenValues[i]) + { + swap(eigenValues[i], eigenValues[j]); + swap(eigenVectors[i], eigenVectors[j]); + } + } + } + + nvDebugCheck(eigenValues[0] >= eigenValues[1] && eigenValues[0] >= eigenValues[2] && eigenValues[0] >= eigenValues[3]); + nvDebugCheck(eigenValues[1] >= eigenValues[2] && eigenValues[1] >= eigenValues[3]); + nvDebugCheck(eigenValues[2] >= eigenValues[2]); + + return true; +} + +#include "nvmath/Matrix.inl" + +inline float signNonzero(float x) +{ + return (x >= 0.0f) ? 1.0f : -1.0f; +} + +static void EigenSolver4_Tridiagonal(float mat[4][4], float * diag, float * subd) +{ + // Householder reduction T = Q^t M Q + // Input: + // mat, symmetric 3x3 matrix M + // Output: + // mat, orthogonal matrix Q + // diag, diagonal entries of T + // subd, subdiagonal entries of T (T is symmetric) + + static const int n = 4; + + // Set epsilon relative to size of elements in matrix + static const float relEpsilon = 1e-6f; + float maxElement = FLT_MAX; + for (int i = 0; i < n; ++i) + for (int j = 0; j < n; ++j) + maxElement = max(maxElement, fabsf(mat[i][j])); + float epsilon = relEpsilon * maxElement; + + // Iterative algorithm, works for any size of matrix but might be slower than + // a closed-form solution for symmetric 4x4 matrices. Based on this article: + // http://en.wikipedia.org/wiki/Householder_transformation#Tridiagonalization + + Matrix A, Q(identity); + memcpy(&A, mat, sizeof(float)*n*n); + + // We proceed from left to right, making the off-tridiagonal entries zero in + // one column of the matrix at a time. + for (int k = 0; k < n - 2; ++k) + { + float sum = 0.0f; + for (int j = k+1; j < n; ++j) + sum += A(j,k)*A(j,k); + float alpha = -signNonzero(A(k+1,k)) * sqrtf(sum); + float r = sqrtf(0.5f * (alpha*alpha - A(k+1,k)*alpha)); + + // If r is zero, skip this column - already in tridiagonal form + if (fabsf(r) < epsilon) + continue; + + float v[n] = {}; + v[k+1] = 0.5f * (A(k+1,k) - alpha) / r; + for (int j = k+2; j < n; ++j) + v[j] = 0.5f * A(j,k) / r; + + Matrix P(identity); + for (int i = 0; i < n; ++i) + for (int j = 0; j < n; ++j) + P(i,j) -= 2.0f * v[i] * v[j]; + + A = mul(mul(P, A), P); + Q = mul(Q, P); + } + + nvDebugCheck(fabsf(A(2,0)) < epsilon); + nvDebugCheck(fabsf(A(0,2)) < epsilon); + nvDebugCheck(fabsf(A(3,0)) < epsilon); + nvDebugCheck(fabsf(A(0,3)) < epsilon); + nvDebugCheck(fabsf(A(3,1)) < epsilon); + nvDebugCheck(fabsf(A(1,3)) < epsilon); + + for (int i = 0; i < n; ++i) + diag[i] = A(i,i); + for (int i = 0; i < n - 1; ++i) + subd[i] = A(i+1,i); + subd[n-1] = 0.0f; + + memcpy(mat, &Q, sizeof(float)*n*n); +} + +static bool EigenSolver4_QLAlgorithm(float mat[4][4], float * diag, float * subd) +{ + // QL iteration with implicit shifting to reduce matrix from tridiagonal + // to diagonal + const int maxiter = 32; + + for (int ell = 0; ell < 4; ell++) + { + int iter; + for (iter = 0; iter < maxiter; iter++) + { + int m; + for (m = ell; m < 3; m++) + { + float dd = fabsf(diag[m]) + fabsf(diag[m+1]); + if ( fabsf(subd[m]) + dd == dd ) + break; + } + if ( m == ell ) + break; + + float g = (diag[ell+1]-diag[ell])/(2*subd[ell]); + float r = sqrtf(g*g+1); + if ( g < 0 ) + g = diag[m]-diag[ell]+subd[ell]/(g-r); + else + g = diag[m]-diag[ell]+subd[ell]/(g+r); + float s = 1, c = 1, p = 0; + for (int i = m-1; i >= ell; i--) + { + float f = s*subd[i], b = c*subd[i]; + if ( fabsf(f) >= fabsf(g) ) + { + c = g/f; + r = sqrtf(c*c+1); + subd[i+1] = f*r; + c *= (s = 1/r); + } + else + { + s = f/g; + r = sqrtf(s*s+1); + subd[i+1] = g*r; + s *= (c = 1/r); + } + g = diag[i+1]-p; + r = (diag[i]-g)*s+2*b*c; + p = s*r; + diag[i+1] = g+p; + g = c*r-b; + + for (int k = 0; k < 4; k++) + { + f = mat[k][i+1]; + mat[k][i+1] = s*mat[k][i]+c*f; + mat[k][i] = c*mat[k][i]-s*f; + } + } + diag[ell] -= p; + subd[ell] = g; + subd[m] = 0; + } + + if ( iter == maxiter ) + // should not get here under normal circumstances + return false; + } + + return true; +} + + + +int nv::Fit::compute4Means(int n, const Vector3 *__restrict points, const float *__restrict weights, Vector3::Arg metric, Vector3 *__restrict cluster) +{ + // Compute principal component. + float matrix[6]; + Vector3 centroid = computeCovariance(n, points, weights, metric, matrix); + Vector3 principal = firstEigenVector_PowerMethod(matrix); + + // Pick initial solution. + int mini, maxi; + mini = maxi = 0; + + float mindps, maxdps; + mindps = maxdps = dot(points[0] - centroid, principal); + + for (int i = 1; i < n; ++i) + { + float dps = dot(points[i] - centroid, principal); + + if (dps < mindps) { + mindps = dps; + mini = i; + } + else { + maxdps = dps; + maxi = i; + } + } + + cluster[0] = centroid + mindps * principal; + cluster[1] = centroid + maxdps * principal; + cluster[2] = (2.0f * cluster[0] + cluster[1]) / 3.0f; + cluster[3] = (2.0f * cluster[1] + cluster[0]) / 3.0f; + + // Now we have to iteratively refine the clusters. + while (true) + { + Vector3 newCluster[4] = { Vector3(0.0f), Vector3(0.0f), Vector3(0.0f), Vector3(0.0f) }; + float total[4] = {0, 0, 0, 0}; + + for (int i = 0; i < n; ++i) + { + // Find nearest cluster. + int nearest = 0; + float mindist = FLT_MAX; + for (int j = 0; j < 4; j++) + { + float dist = lengthSquared((cluster[j] - points[i]) * metric); + if (dist < mindist) + { + mindist = dist; + nearest = j; + } + } + + newCluster[nearest] += weights[i] * points[i]; + total[nearest] += weights[i]; + } + + for (int j = 0; j < 4; j++) + { + if (total[j] != 0) + newCluster[j] /= total[j]; + } + + if (equal(cluster[0], newCluster[0]) && equal(cluster[1], newCluster[1]) && + equal(cluster[2], newCluster[2]) && equal(cluster[3], newCluster[3])) + { + return (total[0] != 0) + (total[1] != 0) + (total[2] != 0) + (total[3] != 0); + } + + cluster[0] = newCluster[0]; + cluster[1] = newCluster[1]; + cluster[2] = newCluster[2]; + cluster[3] = newCluster[3]; + + // Sort clusters by weight. + for (int i = 0; i < 4; i++) + { + for (int j = i; j > 0 && total[j] > total[j - 1]; j--) + { + swap( total[j], total[j - 1] ); + swap( cluster[j], cluster[j - 1] ); + } + } + } +} + + + +// Adaptation of James Arvo's SVD code, as found in ZOH. + +inline float Sqr(float x) { return x*x; } + +inline float svd_pythag( float a, float b ) +{ + float at = fabsf(a); + float bt = fabsf(b); + if( at > bt ) + return at * sqrtf( 1.0f + Sqr( bt / at ) ); + else if( bt > 0.0f ) + return bt * sqrtf( 1.0f + Sqr( at / bt ) ); + else return 0.0f; +} + +inline float SameSign( float a, float b ) +{ + float t; + if( b >= 0.0f ) t = fabsf( a ); + else t = -fabsf( a ); + return t; +} + +void ArvoSVD(int rows, int cols, float * Q, float * diag, float * R) +{ + static const int MaxIterations = 30; + + int i, j, k, l, p, q, iter; + float c, f, h, s, x, y, z; + float norm = 0.0f; + float g = 0.0f; + float scale = 0.0f; + + Array temp; temp.resize(cols, 0.0f); + + for( i = 0; i < cols; i++ ) + { + temp[i] = scale * g; + scale = 0.0f; + g = 0.0f; + s = 0.0f; + l = i + 1; + + if( i < rows ) + { + for( k = i; k < rows; k++ ) scale += fabsf( Q[k*cols+i] ); + if( scale != 0.0f ) + { + for( k = i; k < rows; k++ ) + { + Q[k*cols+i] /= scale; + s += Sqr( Q[k*cols+i] ); + } + f = Q[i*cols+i]; + g = -SameSign( sqrtf(s), f ); + h = f * g - s; + Q[i*cols+i] = f - g; + if( i != cols - 1 ) + { + for( j = l; j < cols; j++ ) + { + s = 0.0f; + for( k = i; k < rows; k++ ) s += Q[k*cols+i] * Q[k*cols+j]; + f = s / h; + for( k = i; k < rows; k++ ) Q[k*cols+j] += f * Q[k*cols+i]; + } + } + for( k = i; k < rows; k++ ) Q[k*cols+i] *= scale; + } + } + + diag[i] = scale * g; + g = 0.0f; + s = 0.0f; + scale = 0.0f; + + if( i < rows && i != cols - 1 ) + { + for( k = l; k < cols; k++ ) scale += fabsf( Q[i*cols+k] ); + if( scale != 0.0f ) + { + for( k = l; k < cols; k++ ) + { + Q[i*cols+k] /= scale; + s += Sqr( Q[i*cols+k] ); + } + f = Q[i*cols+l]; + g = -SameSign( sqrtf(s), f ); + h = f * g - s; + Q[i*cols+l] = f - g; + for( k = l; k < cols; k++ ) temp[k] = Q[i*cols+k] / h; + if( i != rows - 1 ) + { + for( j = l; j < rows; j++ ) + { + s = 0.0f; + for( k = l; k < cols; k++ ) s += Q[j*cols+k] * Q[i*cols+k]; + for( k = l; k < cols; k++ ) Q[j*cols+k] += s * temp[k]; + } + } + for( k = l; k < cols; k++ ) Q[i*cols+k] *= scale; + } + } + norm = max( norm, fabsf( diag[i] ) + fabsf( temp[i] ) ); + } + + + for( i = cols - 1; i >= 0; i-- ) + { + if( i < cols - 1 ) + { + if( g != 0.0f ) + { + for( j = l; j < cols; j++ ) R[i*cols+j] = ( Q[i*cols+j] / Q[i*cols+l] ) / g; + for( j = l; j < cols; j++ ) + { + s = 0.0f; + for( k = l; k < cols; k++ ) s += Q[i*cols+k] * R[j*cols+k]; + for( k = l; k < cols; k++ ) R[j*cols+k] += s * R[i*cols+k]; + } + } + for( j = l; j < cols; j++ ) + { + R[i*cols+j] = 0.0f; + R[j*cols+i] = 0.0f; + } + } + R[i*cols+i] = 1.0f; + g = temp[i]; + l = i; + } + + + for( i = cols - 1; i >= 0; i-- ) + { + l = i + 1; + g = diag[i]; + if( i < cols - 1 ) for( j = l; j < cols; j++ ) Q[i*cols+j] = 0.0f; + if( g != 0.0f ) + { + g = 1.0f / g; + if( i != cols - 1 ) + { + for( j = l; j < cols; j++ ) + { + s = 0.0f; + for( k = l; k < rows; k++ ) s += Q[k*cols+i] * Q[k*cols+j]; + f = ( s / Q[i*cols+i] ) * g; + for( k = i; k < rows; k++ ) Q[k*cols+j] += f * Q[k*cols+i]; + } + } + for( j = i; j < rows; j++ ) Q[j*cols+i] *= g; + } + else + { + for( j = i; j < rows; j++ ) Q[j*cols+i] = 0.0f; + } + Q[i*cols+i] += 1.0f; + } + + + for( k = cols - 1; k >= 0; k-- ) + { + for( iter = 1; iter <= MaxIterations; iter++ ) + { + int jump = 0; + + for( l = k; l >= 0; l-- ) + { + q = l - 1; + if( fabsf( temp[l] ) + norm == norm ) { jump = 1; break; } + if( fabsf( diag[q] ) + norm == norm ) { jump = 0; break; } + } + + if( !jump ) + { + c = 0.0f; + s = 1.0f; + for( i = l; i <= k; i++ ) + { + f = s * temp[i]; + temp[i] *= c; + if( fabsf( f ) + norm == norm ) break; + g = diag[i]; + h = svd_pythag( f, g ); + diag[i] = h; + h = 1.0f / h; + c = g * h; + s = -f * h; + for( j = 0; j < rows; j++ ) + { + y = Q[j*cols+q]; + z = Q[j*cols+i]; + Q[j*cols+q] = y * c + z * s; + Q[j*cols+i] = z * c - y * s; + } + } + } + + z = diag[k]; + if( l == k ) + { + if( z < 0.0f ) + { + diag[k] = -z; + for( j = 0; j < cols; j++ ) R[k*cols+j] *= -1.0f; + } + break; + } + if( iter >= MaxIterations ) return; + x = diag[l]; + q = k - 1; + y = diag[q]; + g = temp[q]; + h = temp[k]; + f = ( ( y - z ) * ( y + z ) + ( g - h ) * ( g + h ) ) / ( 2.0f * h * y ); + g = svd_pythag( f, 1.0f ); + f = ( ( x - z ) * ( x + z ) + h * ( ( y / ( f + SameSign( g, f ) ) ) - h ) ) / x; + c = 1.0f; + s = 1.0f; + for( j = l; j <= q; j++ ) + { + i = j + 1; + g = temp[i]; + y = diag[i]; + h = s * g; + g = c * g; + z = svd_pythag( f, h ); + temp[j] = z; + c = f / z; + s = h / z; + f = x * c + g * s; + g = g * c - x * s; + h = y * s; + y = y * c; + for( p = 0; p < cols; p++ ) + { + x = R[j*cols+p]; + z = R[i*cols+p]; + R[j*cols+p] = x * c + z * s; + R[i*cols+p] = z * c - x * s; + } + z = svd_pythag( f, h ); + diag[j] = z; + if( z != 0.0f ) + { + z = 1.0f / z; + c = f * z; + s = h * z; + } + f = c * g + s * y; + x = c * y - s * g; + for( p = 0; p < rows; p++ ) + { + y = Q[p*cols+j]; + z = Q[p*cols+i]; + Q[p*cols+j] = y * c + z * s; + Q[p*cols+i] = z * c - y * s; + } + } + temp[l] = 0.0f; + temp[k] = f; + diag[k] = x; + } + } + + // Sort the singular values into descending order. + + for( i = 0; i < cols - 1; i++ ) + { + float biggest = diag[i]; // Biggest singular value so far. + int bindex = i; // The row/col it occurred in. + for( j = i + 1; j < cols; j++ ) + { + if( diag[j] > biggest ) + { + biggest = diag[j]; + bindex = j; + } + } + if( bindex != i ) // Need to swap rows and columns. + { + // Swap columns in Q. + for (int j = 0; j < rows; ++j) + swap(Q[j*cols+i], Q[j*cols+bindex]); + + // Swap rows in R. + for (int j = 0; j < rows; ++j) + swap(R[i*cols+j], R[bindex*cols+j]); + + // Swap elements in diag. + swap(diag[i], diag[bindex]); + } + } +} diff --git a/thirdparty/thekla_atlas/src/nvmath/Fitting.h b/thirdparty/thekla_atlas/src/nvmath/Fitting.h new file mode 100755 index 00000000..7a88cd28 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Fitting.h @@ -0,0 +1,50 @@ +// This code is in the public domain -- Ignacio CastaƱo + +#pragma once +#ifndef NV_MATH_FITTING_H +#define NV_MATH_FITTING_H + +#include "Vector.h" +#include "Plane.h" + +namespace nv +{ + namespace Fit + { + Vector3 computeCentroid(int n, const Vector3 * points); + Vector3 computeCentroid(int n, const Vector3 * points, const float * weights, const Vector3 & metric); + + Vector4 computeCentroid(int n, const Vector4 * points); + Vector4 computeCentroid(int n, const Vector4 * points, const float * weights, const Vector4 & metric); + + Vector3 computeCovariance(int n, const Vector3 * points, float * covariance); + Vector3 computeCovariance(int n, const Vector3 * points, const float * weights, const Vector3 & metric, float * covariance); + + Vector4 computeCovariance(int n, const Vector4 * points, float * covariance); + Vector4 computeCovariance(int n, const Vector4 * points, const float * weights, const Vector4 & metric, float * covariance); + + Vector3 computePrincipalComponent_PowerMethod(int n, const Vector3 * points); + Vector3 computePrincipalComponent_PowerMethod(int n, const Vector3 * points, const float * weights, const Vector3 & metric); + + Vector3 computePrincipalComponent_EigenSolver(int n, const Vector3 * points); + Vector3 computePrincipalComponent_EigenSolver(int n, const Vector3 * points, const float * weights, const Vector3 & metric); + + Vector4 computePrincipalComponent_EigenSolver(int n, const Vector4 * points); + Vector4 computePrincipalComponent_EigenSolver(int n, const Vector4 * points, const float * weights, const Vector4 & metric); + + Vector3 computePrincipalComponent_SVD(int n, const Vector3 * points); + Vector4 computePrincipalComponent_SVD(int n, const Vector4 * points); + + Plane bestPlane(int n, const Vector3 * points); + bool isPlanar(int n, const Vector3 * points, float epsilon = NV_EPSILON); + + bool eigenSolveSymmetric3(const float matrix[6], float eigenValues[3], Vector3 eigenVectors[3]); + bool eigenSolveSymmetric4(const float matrix[10], float eigenValues[4], Vector4 eigenVectors[4]); + + // Returns number of clusters [1-4]. + int compute4Means(int n, const Vector3 * points, const float * weights, const Vector3 & metric, Vector3 * cluster); + } + +} // nv namespace + +#endif // NV_MATH_FITTING_H diff --git a/thirdparty/thekla_atlas/src/nvmath/KahanSum.h b/thirdparty/thekla_atlas/src/nvmath/KahanSum.h new file mode 100755 index 00000000..18d475e7 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/KahanSum.h @@ -0,0 +1,39 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_MATH_KAHANSUM_H +#define NV_MATH_KAHANSUM_H + +#include "nvmath.h" + +namespace nv +{ + + class KahanSum + { + public: + KahanSum() : accum(0.0f), err(0) {}; + + void add(float f) + { + float compensated = f + err; + float tmp = accum + compensated; + err = accum - tmp; + err += compensated; + accum = tmp; + } + + float sum() const + { + return accum; + } + + private: + float accum; + float err; + }; + +} // nv namespace + + +#endif // NV_MATH_KAHANSUM_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Matrix.cpp b/thirdparty/thekla_atlas/src/nvmath/Matrix.cpp new file mode 100755 index 00000000..29bd19f5 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Matrix.cpp @@ -0,0 +1,441 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "Matrix.inl" +#include "Vector.inl" + +#include "nvcore/Array.inl" + +#include + +#if !NV_CC_MSVC && !NV_OS_ORBIS +#include +#endif + +using namespace nv; + + +// Given a matrix a[1..n][1..n], this routine replaces it by the LU decomposition of a rowwise +// permutation of itself. a and n are input. a is output, arranged as in equation (2.3.14) above; +// indx[1..n] is an output vector that records the row permutation effected by the partial +// pivoting; d is output as -1 depending on whether the number of row interchanges was even +// or odd, respectively. This routine is used in combination with lubksb to solve linear equations +// or invert a matrix. +static bool ludcmp(float **a, int n, int *indx, float *d) +{ + const float TINY = 1.0e-20f; + + float * vv = (float*)alloca(sizeof(float) * n); // vv stores the implicit scaling of each row. + + *d = 1.0; // No row interchanges yet. + for (int i = 0; i < n; i++) { // Loop over rows to get the implicit scaling information. + + float big = 0.0; + for (int j = 0; j < n; j++) { + big = max(big, fabsf(a[i][j])); + } + if (big == 0) { + return false; // Singular matrix + } + + // No nonzero largest element. + vv[i] = 1.0f / big; // Save the scaling. + } + + for (int j = 0; j < n; j++) { // This is the loop over columns of Crout's method. + for (int i = 0; i < j; i++) { // This is equation (2.3.12) except for i = j. + float sum = a[i][j]; + for (int k = 0; k < i; k++) sum -= a[i][k]*a[k][j]; + a[i][j] = sum; + } + + int imax = -1; + float big = 0.0; // Initialize for the search for largest pivot element. + for (int i = j; i < n; i++) { // This is i = j of equation (2.3.12) and i = j+ 1 : : : N + float sum = a[i][j]; // of equation (2.3.13). + for (int k = 0; k < j; k++) { + sum -= a[i][k]*a[k][j]; + } + a[i][j]=sum; + + float dum = vv[i]*fabs(sum); + if (dum >= big) { + // Is the figure of merit for the pivot better than the best so far? + big = dum; + imax = i; + } + } + nvDebugCheck(imax != -1); + + if (j != imax) { // Do we need to interchange rows? + for (int k = 0; k < n; k++) { // Yes, do so... + swap(a[imax][k], a[j][k]); + } + *d = -(*d); // ...and change the parity of d. + vv[imax]=vv[j]; // Also interchange the scale factor. + } + + indx[j]=imax; + if (a[j][j] == 0.0) a[j][j] = TINY; + + // If the pivot element is zero the matrix is singular (at least to the precision of the + // algorithm). For some applications on singular matrices, it is desirable to substitute + // TINY for zero. + if (j != n-1) { // Now, finally, divide by the pivot element. + float dum = 1.0f / a[j][j]; + for (int i = j+1; i < n; i++) a[i][j] *= dum; + } + } // Go back for the next column in the reduction. + + return true; +} + + +// Solves the set of n linear equations Ax = b. Here a[1..n][1..n] is input, not as the matrix +// A but rather as its LU decomposition, determined by the routine ludcmp. indx[1..n] is input +// as the permutation vector returned by ludcmp. b[1..n] is input as the right-hand side vector +// B, and returns with the solution vector X. a, n, and indx are not modified by this routine +// and can be left in place for successive calls with different right-hand sides b. This routine takes +// into account the possibility that b will begin with many zero elements, so it is efficient for use +// in matrix inversion. +static void lubksb(float **a, int n, int *indx, float b[]) +{ + int ii = 0; + for (int i=0; i=0; i--) { // Now we do the backsubstitution, equation (2.3.7). + float sum = b[i]; + for (int j = i+1; j < n; j++) { + sum -= a[i][j]*b[j]; + } + b[i] = sum/a[i][i]; // Store a component of the solution vector X. + } // All done! +} + + +bool nv::solveLU(const Matrix & A, const Vector4 & b, Vector4 * x) +{ + nvDebugCheck(x != NULL); + + float m[4][4]; + float *a[4] = {m[0], m[1], m[2], m[3]}; + int idx[4]; + float d; + + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + a[x][y] = A(x, y); + } + } + + // Create LU decomposition. + if (!ludcmp(a, 4, idx, &d)) { + // Singular matrix. + return false; + } + + // Init solution. + *x = b; + + // Do back substitution. + lubksb(a, 4, idx, x->component); + + return true; +} + +// @@ Not tested. +Matrix nv::inverseLU(const Matrix & A) +{ + Vector4 Ai[4]; + + solveLU(A, Vector4(1, 0, 0, 0), &Ai[0]); + solveLU(A, Vector4(0, 1, 0, 0), &Ai[1]); + solveLU(A, Vector4(0, 0, 1, 0), &Ai[2]); + solveLU(A, Vector4(0, 0, 0, 1), &Ai[3]); + + return Matrix(Ai[0], Ai[1], Ai[2], Ai[3]); +} + + + +bool nv::solveLU(const Matrix3 & A, const Vector3 & b, Vector3 * x) +{ + nvDebugCheck(x != NULL); + + float m[3][3]; + float *a[3] = {m[0], m[1], m[2]}; + int idx[3]; + float d; + + for (int y = 0; y < 3; y++) { + for (int x = 0; x < 3; x++) { + a[x][y] = A(x, y); + } + } + + // Create LU decomposition. + if (!ludcmp(a, 3, idx, &d)) { + // Singular matrix. + return false; + } + + // Init solution. + *x = b; + + // Do back substitution. + lubksb(a, 3, idx, x->component); + + return true; +} + + +bool nv::solveCramer(const Matrix & A, const Vector4 & b, Vector4 * x) +{ + nvDebugCheck(x != NULL); + + *x = transform(inverseCramer(A), b); + + return true; // @@ Return false if determinant(A) == 0 ! +} + +bool nv::solveCramer(const Matrix3 & A, const Vector3 & b, Vector3 * x) +{ + nvDebugCheck(x != NULL); + + const float det = A.determinant(); + if (equal(det, 0.0f)) { // @@ Use input epsilon. + return false; + } + + Matrix3 Ai = inverseCramer(A); + + *x = transform(Ai, b); + + return true; +} + + + +// Inverse using gaussian elimination. From Jon's code. +Matrix nv::inverse(const Matrix & m) { + + Matrix A = m; + Matrix B(identity); + + int i, j, k; + float max, t, det, pivot; + + det = 1.0; + for (i=0; i<4; i++) { /* eliminate in column i, below diag */ + max = -1.; + for (k=i; k<4; k++) /* find pivot for column i */ + if (fabs(A(k, i)) > max) { + max = fabs(A(k, i)); + j = k; + } + if (max<=0.) return B; /* if no nonzero pivot, PUNT */ + if (j!=i) { /* swap rows i and j */ + for (k=i; k<4; k++) + swap(A(i, k), A(j, k)); + for (k=0; k<4; k++) + swap(B(i, k), B(j, k)); + det = -det; + } + pivot = A(i, i); + det *= pivot; + for (k=i+1; k<4; k++) /* only do elems to right of pivot */ + A(i, k) /= pivot; + for (k=0; k<4; k++) + B(i, k) /= pivot; + /* we know that A(i, i) will be set to 1, so don't bother to do it */ + + for (j=i+1; j<4; j++) { /* eliminate in rows below i */ + t = A(j, i); /* we're gonna zero this guy */ + for (k=i+1; k<4; k++) /* subtract scaled row i from row j */ + A(j, k) -= A(i, k)*t; /* (ignore k<=i, we know they're 0) */ + for (k=0; k<4; k++) + B(j, k) -= B(i, k)*t; + } + } + + /*---------- backward elimination ----------*/ + + for (i=4-1; i>0; i--) { /* eliminate in column i, above diag */ + for (j=0; j max) { + max = fabs(A(k, i)); + j = k; + } + if (max<=0.) return B; /* if no nonzero pivot, PUNT */ + if (j!=i) { /* swap rows i and j */ + for (k=i; k<3; k++) + swap(A(i, k), A(j, k)); + for (k=0; k<3; k++) + swap(B(i, k), B(j, k)); + det = -det; + } + pivot = A(i, i); + det *= pivot; + for (k=i+1; k<3; k++) /* only do elems to right of pivot */ + A(i, k) /= pivot; + for (k=0; k<3; k++) + B(i, k) /= pivot; + /* we know that A(i, i) will be set to 1, so don't bother to do it */ + + for (j=i+1; j<3; j++) { /* eliminate in rows below i */ + t = A(j, i); /* we're gonna zero this guy */ + for (k=i+1; k<3; k++) /* subtract scaled row i from row j */ + A(j, k) -= A(i, k)*t; /* (ignore k<=i, we know they're 0) */ + for (k=0; k<3; k++) + B(j, k) -= B(i, k)*t; + } + } + + /*---------- backward elimination ----------*/ + + for (i=3-1; i>0; i--) { /* eliminate in column i, above diag */ + for (j=0; j. +// +// Returns determinant of A, and B=inverse(A) +// If matrix A is singular, returns 0 and leaves trash in B. +// +#define SWAP(a, b, t) {t = a; a = b; b = t;} +double invert(Mat4& B, const Mat4& m) +{ + Mat4 A = m; + int i, j, k; + double max, t, det, pivot; + + /*---------- forward elimination ----------*/ + + for (i=0; i<4; i++) /* put identity matrix in B */ + for (j=0; j<4; j++) + B(i, j) = (double)(i==j); + + det = 1.0; + for (i=0; i<4; i++) { /* eliminate in column i, below diag */ + max = -1.; + for (k=i; k<4; k++) /* find pivot for column i */ + if (fabs(A(k, i)) > max) { + max = fabs(A(k, i)); + j = k; + } + if (max<=0.) return 0.; /* if no nonzero pivot, PUNT */ + if (j!=i) { /* swap rows i and j */ + for (k=i; k<4; k++) + SWAP(A(i, k), A(j, k), t); + for (k=0; k<4; k++) + SWAP(B(i, k), B(j, k), t); + det = -det; + } + pivot = A(i, i); + det *= pivot; + for (k=i+1; k<4; k++) /* only do elems to right of pivot */ + A(i, k) /= pivot; + for (k=0; k<4; k++) + B(i, k) /= pivot; + /* we know that A(i, i) will be set to 1, so don't bother to do it */ + + for (j=i+1; j<4; j++) { /* eliminate in rows below i */ + t = A(j, i); /* we're gonna zero this guy */ + for (k=i+1; k<4; k++) /* subtract scaled row i from row j */ + A(j, k) -= A(i, k)*t; /* (ignore k<=i, we know they're 0) */ + for (k=0; k<4; k++) + B(j, k) -= B(i, k)*t; + } + } + + /*---------- backward elimination ----------*/ + + for (i=4-1; i>0; i--) { /* eliminate in column i, above diag */ + for (j=0; jx = orig.x * data[0] + orig.y * data[4] + orig.z * data[8]; + dest->y = orig.x * data[1] + orig.y * data[5] + orig.z * data[9]; + dest->z = orig.x * data[2] + orig.y * data[6] + orig.z * data[10]; +} +/** Transform 3d vector by the transpose (w=0). */ +void TransformVec3T(const Vec3 & restrict orig, Vec3 * restrict dest) const { + piDebugCheck(&orig != dest); + dest->x = orig.x * data[0] + orig.y * data[1] + orig.z * data[2]; + dest->y = orig.x * data[4] + orig.y * data[5] + orig.z * data[6]; + dest->z = orig.x * data[8] + orig.y * data[9] + orig.z * data[10]; +} + +/** Transform a 3d homogeneous vector, where the fourth coordinate is assumed to be 1. */ +void TransformPoint(const Vec3 & restrict orig, Vec3 * restrict dest) const { + piDebugCheck(&orig != dest); + dest->x = orig.x * data[0] + orig.y * data[4] + orig.z * data[8] + data[12]; + dest->y = orig.x * data[1] + orig.y * data[5] + orig.z * data[9] + data[13]; + dest->z = orig.x * data[2] + orig.y * data[6] + orig.z * data[10] + data[14]; +} + +/** Transform a point, normalize it, and return w. */ +float TransformPointAndNormalize(const Vec3 & restrict orig, Vec3 * restrict dest) const { + piDebugCheck(&orig != dest); + float w; + dest->x = orig.x * data[0] + orig.y * data[4] + orig.z * data[8] + data[12]; + dest->y = orig.x * data[1] + orig.y * data[5] + orig.z * data[9] + data[13]; + dest->z = orig.x * data[2] + orig.y * data[6] + orig.z * data[10] + data[14]; + w = 1 / (orig.x * data[3] + orig.y * data[7] + orig.z * data[11] + data[15]); + *dest *= w; + return w; +} + +/** Transform a point and return w. */ +float TransformPointReturnW(const Vec3 & restrict orig, Vec3 * restrict dest) const { + piDebugCheck(&orig != dest); + dest->x = orig.x * data[0] + orig.y * data[4] + orig.z * data[8] + data[12]; + dest->y = orig.x * data[1] + orig.y * data[5] + orig.z * data[9] + data[13]; + dest->z = orig.x * data[2] + orig.y * data[6] + orig.z * data[10] + data[14]; + return orig.x * data[3] + orig.y * data[7] + orig.z * data[11] + data[15]; +} + +/** Transform a normalized 3d point by a 4d matrix and return the resulting 4d vector. */ +void TransformVec4(const Vec3 & orig, Vec4 * dest) const { + dest->x = orig.x * data[0] + orig.y * data[4] + orig.z * data[8] + data[12]; + dest->y = orig.x * data[1] + orig.y * data[5] + orig.z * data[9] + data[13]; + dest->z = orig.x * data[2] + orig.y * data[6] + orig.z * data[10] + data[14]; + dest->w = orig.x * data[3] + orig.y * data[7] + orig.z * data[11] + data[15]; +} +//@} + +/** @name Matrix analysis. */ +//@{ + +/** Get the ZYZ euler angles from the matrix. Assumes the matrix is orthonormal. */ +void GetEulerAnglesZYZ(float * s, float * t, float * r) const { + if( GetElem(2,2) < 1.0f ) { + if( GetElem(2,2) > -1.0f ) { + // cs*ct*cr-ss*sr -ss*ct*cr-cs*sr st*cr + // cs*ct*sr+ss*cr -ss*ct*sr+cs*cr st*sr + // -cs*st ss*st ct + *s = atan2(GetElem(1,2), -GetElem(0,2)); + *t = acos(GetElem(2,2)); + *r = atan2(GetElem(2,1), GetElem(2,0)); + } + else { + // -c(s-r) s(s-r) 0 + // s(s-r) c(s-r) 0 + // 0 0 -1 + *s = atan2(GetElem(0, 1), -GetElem(0, 0)); // = s-r + *t = PI; + *r = 0; + } + } + else { + // c(s+r) -s(s+r) 0 + // s(s+r) c(s+r) 0 + // 0 0 1 + *s = atan2(GetElem(0, 1), GetElem(0, 0)); // = s+r + *t = 0; + *r = 0; + } +} + +//@} + +MATHLIB_API friend PiStream & operator<< ( PiStream & s, Matrix & m ); + +/** Print to debug output. */ +void Print() const { + piDebug( "[ %5.2f %5.2f %5.2f %5.2f ]\n", data[0], data[4], data[8], data[12] ); + piDebug( "[ %5.2f %5.2f %5.2f %5.2f ]\n", data[1], data[5], data[9], data[13] ); + piDebug( "[ %5.2f %5.2f %5.2f %5.2f ]\n", data[2], data[6], data[10], data[14] ); + piDebug( "[ %5.2f %5.2f %5.2f %5.2f ]\n", data[3], data[7], data[11], data[15] ); +} + + +public: + + float data[16]; + +}; +#endif + + +#endif // NV_MATH_MATRIX_INL diff --git a/thirdparty/thekla_atlas/src/nvmath/Morton.h b/thirdparty/thekla_atlas/src/nvmath/Morton.h new file mode 100755 index 00000000..10e0d815 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Morton.h @@ -0,0 +1,83 @@ + +// Code from ryg: +// http://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/ + + +// "Insert" a 0 bit after each of the 16 low bits of x +inline uint32 part1By1(uint32 x) +{ + x &= 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210 + x = (x ^ (x << 8)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210 + x = (x ^ (x << 4)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210 + x = (x ^ (x << 2)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10 + x = (x ^ (x << 1)) & 0x55555555; // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0 + return x; +} + +// "Insert" two 0 bits after each of the 10 low bits of x +inline uint32 part1By2(uint32 x) +{ + x &= 0x000003ff; // x = ---- ---- ---- ---- ---- --98 7654 3210 + x = (x ^ (x << 16)) & 0xff0000ff; // x = ---- --98 ---- ---- ---- ---- 7654 3210 + x = (x ^ (x << 8)) & 0x0300f00f; // x = ---- --98 ---- ---- 7654 ---- ---- 3210 + x = (x ^ (x << 4)) & 0x030c30c3; // x = ---- --98 ---- 76-- --54 ---- 32-- --10 + x = (x ^ (x << 2)) & 0x09249249; // x = ---- 9--8 --7- -6-- 5--4 --3- -2-- 1--0 + return x; +} + +inline uint32 encodeMorton2(uint32 x, uint32 y) +{ + return (part1By1(y) << 1) + part1By1(x); +} + +inline uint32 encodeMorton3(uint32 x, uint32 y, uint32 z) +{ + return (part1By2(z) << 2) + (part1By2(y) << 1) + part1By2(x); +} + +// Inverse of part1By1 - "delete" all odd-indexed bits +inline uint32 compact1By1(uint32 x) +{ + x &= 0x55555555; // x = -f-e -d-c -b-a -9-8 -7-6 -5-4 -3-2 -1-0 + x = (x ^ (x >> 1)) & 0x33333333; // x = --fe --dc --ba --98 --76 --54 --32 --10 + x = (x ^ (x >> 2)) & 0x0f0f0f0f; // x = ---- fedc ---- ba98 ---- 7654 ---- 3210 + x = (x ^ (x >> 4)) & 0x00ff00ff; // x = ---- ---- fedc ba98 ---- ---- 7654 3210 + x = (x ^ (x >> 8)) & 0x0000ffff; // x = ---- ---- ---- ---- fedc ba98 7654 3210 + return x; +} + +// Inverse of part1By2 - "delete" all bits not at positions divisible by 3 +inline uint32 compact1By2(uint32 x) +{ + x &= 0x09249249; // x = ---- 9--8 --7- -6-- 5--4 --3- -2-- 1--0 + x = (x ^ (x >> 2)) & 0x030c30c3; // x = ---- --98 ---- 76-- --54 ---- 32-- --10 + x = (x ^ (x >> 4)) & 0x0300f00f; // x = ---- --98 ---- ---- 7654 ---- ---- 3210 + x = (x ^ (x >> 8)) & 0xff0000ff; // x = ---- --98 ---- ---- ---- ---- 7654 3210 + x = (x ^ (x >> 16)) & 0x000003ff; // x = ---- ---- ---- ---- ---- --98 7654 3210 + return x; +} + +inline uint32 decodeMorton2X(uint32 code) +{ + return compact1By1(code >> 0); +} + +inline uint32 decodeMorton2Y(uint32 code) +{ + return compact1By1(code >> 1); +} + +inline uint32 decodeMorton3X(uint32 code) +{ + return compact1By2(code >> 0); +} + +inline uint32 decodeMorton3Y(uint32 code) +{ + return compact1By2(code >> 1); +} + +inline uint32 decodeMorton3Z(uint32 code) +{ + return compact1By2(code >> 2); +} \ No newline at end of file diff --git a/thirdparty/thekla_atlas/src/nvmath/Plane.cpp b/thirdparty/thekla_atlas/src/nvmath/Plane.cpp new file mode 100755 index 00000000..8b54f829 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Plane.cpp @@ -0,0 +1,27 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "Plane.h" +#include "Plane.inl" +#include "Matrix.inl" + +namespace nv +{ + Plane transformPlane(const Matrix & m, const Plane & p) + { + Vector3 newVec = transformVector(m, p.vector()); + + Vector3 ptInPlane = p.offset() * p.vector(); + ptInPlane = transformPoint(m, ptInPlane); + + return Plane(newVec, ptInPlane); + } + + Vector3 planeIntersection(const Plane & a, const Plane & b, const Plane & c) + { + return dot(a.vector(), cross(b.vector(), c.vector())) * ( + a.offset() * cross(b.vector(), c.vector()) + + c.offset() * cross(a.vector(), b.vector()) + + b.offset() * cross(c.vector(), a.vector())); + } + +} // nv namespace diff --git a/thirdparty/thekla_atlas/src/nvmath/Plane.h b/thirdparty/thekla_atlas/src/nvmath/Plane.h new file mode 100755 index 00000000..dc468b28 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Plane.h @@ -0,0 +1,42 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_MATH_PLANE_H +#define NV_MATH_PLANE_H + +#include "nvmath.h" +#include "Vector.h" + +namespace nv +{ + class Matrix; + + class NVMATH_CLASS Plane + { + public: + Plane(); + Plane(float x, float y, float z, float w); + Plane(const Vector4 & v); + Plane(const Vector3 & v, float d); + Plane(const Vector3 & normal, const Vector3 & point); + Plane(const Vector3 & v0, const Vector3 & v1, const Vector3 & v2); + + const Plane & operator=(const Plane & v); + + Vector3 vector() const; + float offset() const; + Vector3 normal() const; + + void operator*=(float s); + + Vector4 v; + }; + + Plane transformPlane(const Matrix &, const Plane &); + + Vector3 planeIntersection(const Plane & a, const Plane & b, const Plane & c); + + +} // nv namespace + +#endif // NV_MATH_PLANE_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Plane.inl b/thirdparty/thekla_atlas/src/nvmath/Plane.inl new file mode 100755 index 00000000..2277e38c --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Plane.inl @@ -0,0 +1,50 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_MATH_PLANE_INL +#define NV_MATH_PLANE_INL + +#include "Plane.h" +#include "Vector.inl" + +namespace nv +{ + inline Plane::Plane() {} + inline Plane::Plane(float x, float y, float z, float w) : v(x, y, z, w) {} + inline Plane::Plane(const Vector4 & v) : v(v) {} + inline Plane::Plane(const Vector3 & v, float d) : v(v, d) {} + inline Plane::Plane(const Vector3 & normal, const Vector3 & point) : v(normal, -dot(normal, point)) {} + inline Plane::Plane(const Vector3 & v0, const Vector3 & v1, const Vector3 & v2) { + Vector3 n = cross(v1-v0, v2-v0); + float d = -dot(n, v0); + v = Vector4(n, d); + } + + inline const Plane & Plane::operator=(const Plane & p) { v = p.v; return *this; } + + inline Vector3 Plane::vector() const { return v.xyz(); } + inline float Plane::offset() const { return v.w; } + inline Vector3 Plane::normal() const { return normalize(vector(), 0.0f); } + + // Normalize plane. + inline Plane normalize(const Plane & plane, float epsilon = NV_EPSILON) + { + const float len = length(plane.vector()); + const float inv = isZero(len, epsilon) ? 0 : 1.0f / len; + return Plane(plane.v * inv); + } + + // Get the signed distance from the given point to this plane. + inline float distance(const Plane & plane, const Vector3 & point) + { + return dot(plane.vector(), point) + plane.offset(); + } + + inline void Plane::operator*=(float s) + { + v *= s; + } + +} // nv namespace + +#endif // NV_MATH_PLANE_H diff --git a/thirdparty/thekla_atlas/src/nvmath/ProximityGrid.cpp b/thirdparty/thekla_atlas/src/nvmath/ProximityGrid.cpp new file mode 100755 index 00000000..3553e48f --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/ProximityGrid.cpp @@ -0,0 +1,158 @@ +#include "ProximityGrid.h" + +#include "Box.inl" +#include "Morton.h" + + +using namespace nv; + +ProximityGrid::ProximityGrid() { +} + +void ProximityGrid::reset() { + cellArray.clear(); +} + +void ProximityGrid::init(const Array & pointArray) { + + // Compute bounding box. + Box box; + box.clearBounds(); + + const uint count = pointArray.count(); + + for (uint i = 0; i < count; i++) { + box.addPointToBounds(pointArray[i]); + } + + init(box, count); + + // Insert all points. + for (uint i = 0; i < count; i++) { + add(pointArray[i], i); + } +} + + +void ProximityGrid::init(const Box & box, uint count) { + reset(); + + // Determine grid size. + float cellWidth; + + Vector3 diagonal = box.extents() * 2.f; + float volume = box.volume(); + + if (equal(volume, 0)) { + // Degenerate box, treat like a quad. + Vector2 quad; + if (diagonal.x < diagonal.y && diagonal.x < diagonal.z) { + quad.x = diagonal.y; + quad.y = diagonal.z; + } + else if (diagonal.y < diagonal.x && diagonal.y < diagonal.z) { + quad.x = diagonal.x; + quad.y = diagonal.z; + } + else { + quad.x = diagonal.x; + quad.y = diagonal.y; + } + + float cellArea = quad.x * quad.y / count; + cellWidth = sqrtf(cellArea); // pow(cellArea, 1.0f / 2.0f); + } + else { + // Ideally we want one cell per point. + float cellVolume = volume / count; + cellWidth = pow(cellVolume, 1.0f / 3.0f); + } + + nvDebugCheck(cellWidth != 0); + + sx = max(1, ftoi_ceil(diagonal.x / cellWidth)); + sy = max(1, ftoi_ceil(diagonal.y / cellWidth)); + sz = max(1, ftoi_ceil(diagonal.z / cellWidth)); + + invCellSize.x = float(sx) / diagonal.x; + invCellSize.y = float(sy) / diagonal.y; + invCellSize.z = float(sz) / diagonal.z; + + cellArray.resize(sx * sy * sz); + + corner = box.minCorner; // @@ Align grid better? +} + +// Gather all points inside the given sphere. +// Radius is assumed to be small, so we don't bother culling the cells. +void ProximityGrid::gather(const Vector3 & position, float radius, Array & indexArray) { + int x0 = index_x(position.x - radius); + int x1 = index_x(position.x + radius); + + int y0 = index_y(position.y - radius); + int y1 = index_y(position.y + radius); + + int z0 = index_z(position.z - radius); + int z1 = index_z(position.z + radius); + + for (int z = z0; z <= z1; z++) { + for (int y = y0; y <= y1; y++) { + for (int x = x0; x <= x1; x++) { + int idx = index(x, y, z); + indexArray.append(cellArray[idx].indexArray); + } + } + } +} + + +uint32 ProximityGrid::mortonCount() const { + uint64 s = U64(max3(sx, sy, sz)); + s = nextPowerOfTwo(s); + + if (s > 1024) { + return U32(s * s * min3(sx, sy, sz)); + } + + return U32(s * s * s); +} + +int ProximityGrid::mortonIndex(uint32 code) const { + uint32 x, y, z; + + uint s = U32(max3(sx, sy, sz)); + if (s > 1024) { + // Use layered two-dimensional morton order. + s = nextPowerOfTwo(s); + uint layer = code / (s * s); + code = code % (s * s); + + uint layer_count = U32(min3(sx, sy, sz)); + if (sx == layer_count) { + x = layer; + y = decodeMorton2X(code); + z = decodeMorton2Y(code); + } + else if (sy == layer_count) { + x = decodeMorton2Y(code); + y = layer; + z = decodeMorton2X(code); + } + else /*if (sz == layer_count)*/ { + x = decodeMorton2X(code); + y = decodeMorton2Y(code); + z = layer; + } + } + else { + x = decodeMorton3X(code); + y = decodeMorton3Y(code); + z = decodeMorton3Z(code); + } + + if (x >= U32(sx) || y >= U32(sy) || z >= U32(sz)) { + return -1; + } + + return index(x, y, z); +} diff --git a/thirdparty/thekla_atlas/src/nvmath/ProximityGrid.h b/thirdparty/thekla_atlas/src/nvmath/ProximityGrid.h new file mode 100755 index 00000000..a21bb3bd --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/ProximityGrid.h @@ -0,0 +1,99 @@ +#pragma once +#ifndef NV_MATH_PROXIMITYGRID_H +#define NV_MATH_PROXIMITYGRID_H + +#include "Vector.h" +#include "ftoi.h" + +#include "nvcore/Array.inl" + + +// A simple, dynamic proximity grid based on Jon's code. +// Instead of storing pointers here I store indices. + +namespace nv { + + class Box; + + struct Cell { + Array indexArray; + }; + + struct ProximityGrid { + ProximityGrid(); + + void reset(); + void init(const Array & pointArray); + void init(const Box & box, uint count); + + int index_x(float x) const; + int index_y(float y) const; + int index_z(float z) const; + int index(int x, int y, int z) const; + int index(const Vector3 & pos) const; + + uint32 mortonCount() const; + int mortonIndex(uint32 code) const; + + void add(const Vector3 & pos, uint key); + bool remove(const Vector3 & pos, uint key); + + void gather(const Vector3 & pos, float radius, Array & indices); + + Array cellArray; + + Vector3 corner; + Vector3 invCellSize; + int sx, sy, sz; + }; + + // For morton traversal, do: + // for (int code = 0; code < mortonCount(); code++) { + // int idx = mortonIndex(code); + // if (idx < 0) continue; + // } + + + + inline int ProximityGrid::index_x(float x) const { + return clamp(ftoi_floor((x - corner.x) * invCellSize.x), 0, sx-1); + } + + inline int ProximityGrid::index_y(float y) const { + return clamp(ftoi_floor((y - corner.y) * invCellSize.y), 0, sy-1); + } + + inline int ProximityGrid::index_z(float z) const { + return clamp(ftoi_floor((z - corner.z) * invCellSize.z), 0, sz-1); + } + + inline int ProximityGrid::index(int x, int y, int z) const { + nvDebugCheck(x >= 0 && x < sx); + nvDebugCheck(y >= 0 && y < sy); + nvDebugCheck(z >= 0 && z < sz); + int idx = (z * sy + y) * sx + x; + nvDebugCheck(idx >= 0 && uint(idx) < cellArray.count()); + return idx; + } + + inline int ProximityGrid::index(const Vector3 & pos) const { + int x = index_x(pos.x); + int y = index_y(pos.y); + int z = index_z(pos.z); + return index(x, y, z); + } + + + inline void ProximityGrid::add(const Vector3 & pos, uint key) { + uint idx = index(pos); + cellArray[idx].indexArray.append(key); + } + + inline bool ProximityGrid::remove(const Vector3 & pos, uint key) { + uint idx = index(pos); + return cellArray[idx].indexArray.remove(key); + } + +} // nv namespace + +#endif // NV_MATH_PROXIMITYGRID_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Quaternion.h b/thirdparty/thekla_atlas/src/nvmath/Quaternion.h new file mode 100755 index 00000000..dc5219e5 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Quaternion.h @@ -0,0 +1,213 @@ +// This code is in the public domain -- castano@gmail.com + +#pragma once +#ifndef NV_MATH_QUATERNION_H +#define NV_MATH_QUATERNION_H + +#include "nvmath/nvmath.h" +#include "nvmath/Vector.inl" // @@ Do not include inl files from header files. +#include "nvmath/Matrix.h" + +namespace nv +{ + + class NVMATH_CLASS Quaternion + { + public: + typedef Quaternion const & Arg; + + Quaternion(); + explicit Quaternion(float f); + Quaternion(float x, float y, float z, float w); + Quaternion(Vector4::Arg v); + + const Quaternion & operator=(Quaternion::Arg v); + + Vector4 asVector() const; + + union { + struct { + float x, y, z, w; + }; + float component[4]; + }; + }; + + inline Quaternion::Quaternion() {} + inline Quaternion::Quaternion(float f) : x(f), y(f), z(f), w(f) {} + inline Quaternion::Quaternion(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} + inline Quaternion::Quaternion(Vector4::Arg v) : x(v.x), y(v.y), z(v.z), w(v.w) {} + + // @@ Move all these to Quaternion.inl! + + inline const Quaternion & Quaternion::operator=(Quaternion::Arg v) { + x = v.x; + y = v.y; + z = v.z; + w = v.w; + return *this; + } + + inline Vector4 Quaternion::asVector() const { return Vector4(x, y, z, w); } + + inline Quaternion mul(Quaternion::Arg a, Quaternion::Arg b) + { + return Quaternion( + + a.x*b.w + a.y*b.z - a.z*b.y + a.w*b.x, + - a.x*b.z + a.y*b.w + a.z*b.x + a.w*b.y, + + a.x*b.y - a.y*b.x + a.z*b.w + a.w*b.z, + - a.x*b.x - a.y*b.y - a.z*b.z + a.w*b.w); + } + + inline Quaternion mul(Quaternion::Arg a, Vector3::Arg b) + { + return Quaternion( + + a.y*b.z - a.z*b.y + a.w*b.x, + - a.x*b.z + a.z*b.x + a.w*b.y, + + a.x*b.y - a.y*b.x + a.w*b.z, + - a.x*b.x - a.y*b.y - a.z*b.z ); + } + + inline Quaternion mul(Vector3::Arg a, Quaternion::Arg b) + { + return Quaternion( + + a.x*b.w + a.y*b.z - a.z*b.y, + - a.x*b.z + a.y*b.w + a.z*b.x, + + a.x*b.y - a.y*b.x + a.z*b.w, + - a.x*b.x - a.y*b.y - a.z*b.z); + } + + inline Quaternion operator *(Quaternion::Arg a, Quaternion::Arg b) + { + return mul(a, b); + } + + inline Quaternion operator *(Quaternion::Arg a, Vector3::Arg b) + { + return mul(a, b); + } + + inline Quaternion operator *(Vector3::Arg a, Quaternion::Arg b) + { + return mul(a, b); + } + + + inline Quaternion scale(Quaternion::Arg q, float s) + { + return scale(q.asVector(), s); + } + inline Quaternion operator *(Quaternion::Arg q, float s) + { + return scale(q, s); + } + inline Quaternion operator *(float s, Quaternion::Arg q) + { + return scale(q, s); + } + + inline Quaternion scale(Quaternion::Arg q, Vector4::Arg s) + { + return scale(q.asVector(), s); + } + /*inline Quaternion operator *(Quaternion::Arg q, Vector4::Arg s) + { + return scale(q, s); + } + inline Quaternion operator *(Vector4::Arg s, Quaternion::Arg q) + { + return scale(q, s); + }*/ + + inline Quaternion conjugate(Quaternion::Arg q) + { + return scale(q, Vector4(-1, -1, -1, 1)); + } + + inline float length(Quaternion::Arg q) + { + return length(q.asVector()); + } + + inline bool isNormalized(Quaternion::Arg q, float epsilon = NV_NORMAL_EPSILON) + { + return equal(length(q), 1, epsilon); + } + + inline Quaternion normalize(Quaternion::Arg q, float epsilon = NV_EPSILON) + { + float l = length(q); + nvDebugCheck(!isZero(l, epsilon)); + Quaternion n = scale(q, 1.0f / l); + nvDebugCheck(isNormalized(n)); + return n; + } + + inline Quaternion inverse(Quaternion::Arg q) + { + return conjugate(normalize(q)); + } + + /// Create a rotation quaternion for @a angle alpha around normal vector @a v. + inline Quaternion axisAngle(Vector3::Arg v, float alpha) + { + float s = sinf(alpha * 0.5f); + float c = cosf(alpha * 0.5f); + return Quaternion(Vector4(v * s, c)); + } + + inline Vector3 imag(Quaternion::Arg q) + { + return q.asVector().xyz(); + } + + inline float real(Quaternion::Arg q) + { + return q.w; + } + + + /// Transform vector. + inline Vector3 transform(Quaternion::Arg q, Vector3::Arg v) + { + //Quaternion t = q * v * conjugate(q); + //return imag(t); + + // Faster method by Fabian Giesen and others: + // http://molecularmusings.wordpress.com/2013/05/24/a-faster-quaternion-vector-multiplication/ + // http://mollyrocket.com/forums/viewtopic.php?t=833&sid=3a84e00a70ccb046cfc87ac39881a3d0 + + Vector3 t = 2 * cross(imag(q), v); + return v + q.w * t + cross(imag(q), t); + } + + // @@ Not tested. + // From Insomniac's Mike Day: + // http://www.insomniacgames.com/converting-a-rotation-matrix-to-a-quaternion/ + inline Quaternion fromMatrix(const Matrix & m) { + if (m(2, 2) < 0) { + if (m(0, 0) < m(1,1)) { + float t = 1 - m(0, 0) - m(1, 1) - m(2, 2); + return Quaternion(t, m(0,1)+m(1,0), m(2,0)+m(0,2), m(1,2)-m(2,1)); + } + else { + float t = 1 - m(0, 0) + m(1, 1) - m(2, 2); + return Quaternion(t, m(0,1) + m(1,0), m(1,2) + m(2,1), m(2,0) - m(0,2)); + } + } + else { + if (m(0, 0) < -m(1, 1)) { + float t = 1 - m(0, 0) - m(1, 1) + m(2, 2); + return Quaternion(t, m(2,0) + m(0,2), m(1,2) + m(2,1), m(0,1) - m(1,0)); + } + else { + float t = 1 + m(0, 0) + m(1, 1) + m(2, 2); + return Quaternion(t, m(1,2) - m(2,1), m(2,0) - m(0,2), m(0,1) - m(1,0)); + } + } + } + + +} // nv namespace + +#endif // NV_MATH_QUATERNION_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Random.cpp b/thirdparty/thekla_atlas/src/nvmath/Random.cpp new file mode 100755 index 00000000..1a60e7f5 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Random.cpp @@ -0,0 +1,54 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include +#include + +using namespace nv; + +// Statics +const uint16 Rand48::a0 = 0xE66D; +const uint16 Rand48::a1 = 0xDEEC; +const uint16 Rand48::a2 = 0x0005; +const uint16 Rand48::c0 = 0x000B; + + +/// Get a random seed based on the current time. +uint Rand::randomSeed() +{ + return (uint)time(NULL); +} + + +void MTRand::initialize( uint32 seed ) +{ + // Initialize generator state with seed + // See Knuth TAOCP Vol 2, 3rd Ed, p.106 for multiplier. + // In previous versions, most significant bits (MSBs) of the seed affect + // only MSBs of the state array. Modified 9 Jan 2002 by Makoto Matsumoto. + uint32 *s = state; + uint32 *r = state; + int i = 1; + *s++ = seed & 0xffffffffUL; + for( ; i < N; ++i ) + { + *s++ = ( 1812433253UL * ( *r ^ (*r >> 30) ) + i ) & 0xffffffffUL; + r++; + } +} + + +void MTRand::reload() +{ + // Generate N new values in state + // Made clearer and faster by Matthew Bellew (matthew.bellew@home.com) + uint32 *p = state; + int i; + for( i = N - M; i--; ++p ) + *p = twist( p[M], p[0], p[1] ); + for( i = M; --i; ++p ) + *p = twist( p[M-N], p[0], p[1] ); + *p = twist( p[M-N], p[0], state[0] ); + + left = N, next = state; +} + diff --git a/thirdparty/thekla_atlas/src/nvmath/Random.h b/thirdparty/thekla_atlas/src/nvmath/Random.h new file mode 100755 index 00000000..22329270 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Random.h @@ -0,0 +1,376 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MATH_RANDOM_H +#define NV_MATH_RANDOM_H + +#include "nvmath.h" +#include "nvcore/Utils.h" // nextPowerOfTwo + + +namespace nv +{ + + /// Interface of the random number generators. + class Rand + { + public: + + virtual ~Rand() {} + + enum time_e { Time }; + + /// Provide a new seed. + virtual void seed( uint s ) { /* empty */ }; + + /// Get an integer random number. + virtual uint get() = 0; + + /// Get a random number on [0, max] interval. + uint getRange( uint max ) + { + if (max == 0) return 0; + if (max == NV_UINT32_MAX) return get(); + + const uint np2 = nextPowerOfTwo( max+1 ); // @@ This fails if max == NV_UINT32_MAX + const uint mask = np2 - 1; + uint n; + do { n = get() & mask; } while( n > max ); + return n; + } + + /// Random number on [0.0, 1.0] interval. + float getFloat() + { + union + { + uint32 i; + float f; + } pun; + + pun.i = 0x3f800000UL | (get() & 0x007fffffUL); + return pun.f - 1.0f; + } + + float getFloatRange(float min, float max) { + return getFloat() * (max - min) + min; + } + + /* + /// Random number on [0.0, 1.0] interval. + double getReal() + { + return double(get()) * (1.0/4294967295.0); // 2^32-1 + } + + /// Random number on [0.0, 1.0) interval. + double getRealExclusive() + { + return double(get()) * (1.0/4294967296.0); // 2^32 + } + */ + + /// Get the max value of the random number. + uint max() const { return NV_UINT32_MAX; } + + // Get a random seed. + static uint randomSeed(); + + }; + + + /// Very simple random number generator with low storage requirements. + class SimpleRand : public Rand + { + public: + + /// Constructor that uses the current time as the seed. + SimpleRand( time_e ) + { + seed(randomSeed()); + } + + /// Constructor that uses the given seed. + SimpleRand( uint s = 0 ) + { + seed(s); + } + + /// Set the given seed. + virtual void seed( uint s ) + { + current = s; + } + + /// Get a random number. + virtual uint get() + { + return current = current * 1103515245 + 12345; + } + + private: + + uint current; + + }; + + + /// Mersenne twister random number generator. + class MTRand : public Rand + { + public: + + enum { N = 624 }; // length of state vector + enum { M = 397 }; + + /// Constructor that uses the current time as the seed. + MTRand( time_e ) + { + seed(randomSeed()); + } + + /// Constructor that uses the given seed. + MTRand( uint s = 0 ) + { + seed(s); + } + + /// Constructor that uses the given seeds. + NVMATH_API MTRand( const uint * seed_array, uint length ); + + + /// Provide a new seed. + virtual void seed( uint s ) + { + initialize(s); + reload(); + } + + /// Get a random number between 0 - 65536. + virtual uint get() + { + // Pull a 32-bit integer from the generator state + // Every other access function simply transforms the numbers extracted here + if( left == 0 ) { + reload(); + } + left--; + + uint s1; + s1 = *next++; + s1 ^= (s1 >> 11); + s1 ^= (s1 << 7) & 0x9d2c5680U; + s1 ^= (s1 << 15) & 0xefc60000U; + return ( s1 ^ (s1 >> 18) ); + }; + + + private: + + NVMATH_API void initialize( uint32 seed ); + NVMATH_API void reload(); + + uint hiBit( uint u ) const { return u & 0x80000000U; } + uint loBit( uint u ) const { return u & 0x00000001U; } + uint loBits( uint u ) const { return u & 0x7fffffffU; } + uint mixBits( uint u, uint v ) const { return hiBit(u) | loBits(v); } + uint twist( uint m, uint s0, uint s1 ) const { return m ^ (mixBits(s0,s1)>>1) ^ ((~loBit(s1)+1) & 0x9908b0dfU); } + + private: + + uint state[N]; // internal state + uint * next; // next value to get from state + int left; // number of values left before reload needed + + }; + + + + /** George Marsaglia's random number generator. + * Code based on Thatcher Ulrich public domain source code: + * http://cvs.sourceforge.net/viewcvs.py/tu-testbed/tu-testbed/base/tu_random.cpp?rev=1.7&view=auto + * + * PRNG code adapted from the complimentary-multiply-with-carry + * code in the article: George Marsaglia, "Seeds for Random Number + * Generators", Communications of the ACM, May 2003, Vol 46 No 5, + * pp90-93. + * + * The article says: + * + * "Any one of the choices for seed table size and multiplier will + * provide a RNG that has passed extensive tests of randomness, + * particularly those in [3], yet is simple and fast -- + * approximately 30 million random 32-bit integers per second on a + * 850MHz PC. The period is a*b^n, where a is the multiplier, n + * the size of the seed table and b=2^32-1. (a is chosen so that + * b is a primitive root of the prime a*b^n + 1.)" + * + * [3] Marsaglia, G., Zaman, A., and Tsang, W. Toward a universal + * random number generator. _Statistics and Probability Letters + * 8_ (1990), 35-39. + */ + class GMRand : public Rand + { + public: + + enum { SEED_COUNT = 8 }; + + // const uint64 a = 123471786; // for SEED_COUNT=1024 + // const uint64 a = 123554632; // for SEED_COUNT=512 + // const uint64 a = 8001634; // for SEED_COUNT=255 + // const uint64 a = 8007626; // for SEED_COUNT=128 + // const uint64 a = 647535442; // for SEED_COUNT=64 + // const uint64 a = 547416522; // for SEED_COUNT=32 + // const uint64 a = 487198574; // for SEED_COUNT=16 + // const uint64 a = 716514398U; // for SEED_COUNT=8 + enum { a = 716514398U }; + + + GMRand( time_e ) + { + seed(randomSeed()); + } + + GMRand(uint s = 987654321) + { + seed(s); + } + + + /// Provide a new seed. + virtual void seed( uint s ) + { + c = 362436; + i = SEED_COUNT - 1; + + for(int i = 0; i < SEED_COUNT; i++) { + s = s ^ (s << 13); + s = s ^ (s >> 17); + s = s ^ (s << 5); + Q[i] = s; + } + } + + /// Get a random number between 0 - 65536. + virtual uint get() + { + const uint32 r = 0xFFFFFFFE; + + uint64 t; + uint32 x; + + i = (i + 1) & (SEED_COUNT - 1); + t = a * Q[i] + c; + c = uint32(t >> 32); + x = uint32(t + c); + + if( x < c ) { + x++; + c++; + } + + uint32 val = r - x; + Q[i] = val; + return val; + }; + + + private: + + uint32 c; + uint32 i; + uint32 Q[8]; + + }; + + + /** Random number implementation from the GNU Sci. Lib. (GSL). + * Adapted from Nicholas Chapman version: + * + * Copyright (C) 1996, 1997, 1998, 1999, 2000 James Theiler, Brian Gough + * This is the Unix rand48() generator. The generator returns the + * upper 32 bits from each term of the sequence, + * + * x_{n+1} = (a x_n + c) mod m + * + * using 48-bit unsigned arithmetic, with a = 0x5DEECE66D , c = 0xB + * and m = 2^48. The seed specifies the upper 32 bits of the initial + * value, x_1, with the lower 16 bits set to 0x330E. + * + * The theoretical value of x_{10001} is 244131582646046. + * + * The period of this generator is ? FIXME (probably around 2^48). + */ + class Rand48 : public Rand + { + public: + + Rand48( time_e ) + { + seed(randomSeed()); + } + + Rand48( uint s = 0x1234ABCD ) + { + seed(s); + } + + + /** Set the given seed. */ + virtual void seed( uint s ) { + vstate.x0 = 0x330E; + vstate.x1 = uint16(s & 0xFFFF); + vstate.x2 = uint16((s >> 16) & 0xFFFF); + } + + /** Get a random number. */ + virtual uint get() { + + advance(); + + uint x1 = vstate.x1; + uint x2 = vstate.x2; + return (x2 << 16) + x1; + } + + + private: + + void advance() + { + /* work with unsigned long ints throughout to get correct integer + promotions of any unsigned short ints */ + const uint32 x0 = vstate.x0; + const uint32 x1 = vstate.x1; + const uint32 x2 = vstate.x2; + + uint32 a; + a = a0 * x0 + c0; + + vstate.x0 = uint16(a & 0xFFFF); + a >>= 16; + + /* although the next line may overflow we only need the top 16 bits + in the following stage, so it does not matter */ + + a += a0 * x1 + a1 * x0; + vstate.x1 = uint16(a & 0xFFFF); + + a >>= 16; + a += a0 * x2 + a1 * x1 + a2 * x0; + vstate.x2 = uint16(a & 0xFFFF); + } + + + private: + NVMATH_API static const uint16 a0, a1, a2, c0; + + struct rand48_state_t { + uint16 x0, x1, x2; + } vstate; + + }; + +} // nv namespace + +#endif // NV_MATH_RANDOM_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Solver.cpp b/thirdparty/thekla_atlas/src/nvmath/Solver.cpp new file mode 100755 index 00000000..191793ee --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Solver.cpp @@ -0,0 +1,744 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "Solver.h" +#include "Sparse.h" + +#include "nvcore/Array.inl" + +using namespace nv; + +namespace +{ + class Preconditioner + { + public: + // Virtual dtor. + virtual ~Preconditioner() { } + + // Apply preconditioning step. + virtual void apply(const FullVector & x, FullVector & y) const = 0; + }; + + + // Jacobi preconditioner. + class JacobiPreconditioner : public Preconditioner + { + public: + + JacobiPreconditioner(const SparseMatrix & M, bool symmetric) : m_inverseDiagonal(M.width()) + { + nvCheck(M.isSquare()); + + for(uint x = 0; x < M.width(); x++) + { + float elem = M.getCoefficient(x, x); + //nvDebugCheck( elem != 0.0f ); // This can be zero in the presence of zero area triangles. + + if (symmetric) + { + m_inverseDiagonal[x] = (elem != 0) ? 1.0f / sqrtf(fabsf(elem)) : 1.0f; + } + else + { + m_inverseDiagonal[x] = (elem != 0) ? 1.0f / elem : 1.0f; + } + } + } + + void apply(const FullVector & x, FullVector & y) const + { + nvDebugCheck(x.dimension() == m_inverseDiagonal.dimension()); + nvDebugCheck(y.dimension() == m_inverseDiagonal.dimension()); + + // @@ Wrap vector component-wise product into a separate function. + const uint D = x.dimension(); + for (uint i = 0; i < D; i++) + { + y[i] = m_inverseDiagonal[i] * x[i]; + } + } + + private: + + FullVector m_inverseDiagonal; + + }; + +} // namespace + + +static bool ConjugateGradientSolver(const SparseMatrix & A, const FullVector & b, FullVector & x, float epsilon); +static bool ConjugateGradientSolver(const Preconditioner & preconditioner, const SparseMatrix & A, const FullVector & b, FullVector & x, float epsilon); + + +// Solve the symmetric system: At·A·x = At·b +bool nv::LeastSquaresSolver(const SparseMatrix & A, const FullVector & b, FullVector & x, float epsilon/*1e-5f*/) +{ + nvDebugCheck(A.width() == x.dimension()); + nvDebugCheck(A.height() == b.dimension()); + nvDebugCheck(A.height() >= A.width()); // @@ If height == width we could solve it directly... + + const uint D = A.width(); + + SparseMatrix At(A.height(), A.width()); + transpose(A, At); + + FullVector Atb(D); + //mult(Transposed, A, b, Atb); + mult(At, b, Atb); + + SparseMatrix AtA(D); + //mult(Transposed, A, NoTransposed, A, AtA); + mult(At, A, AtA); + + return SymmetricSolver(AtA, Atb, x, epsilon); +} + + +// See section 10.4.3 in: Mesh Parameterization: Theory and Practice, Siggraph Course Notes, August 2007 +bool nv::LeastSquaresSolver(const SparseMatrix & A, const FullVector & b, FullVector & x, const uint * lockedParameters, uint lockedCount, float epsilon/*= 1e-5f*/) +{ + nvDebugCheck(A.width() == x.dimension()); + nvDebugCheck(A.height() == b.dimension()); + nvDebugCheck(A.height() >= A.width() - lockedCount); + + // @@ This is not the most efficient way of building a system with reduced degrees of freedom. It would be faster to do it on the fly. + + const uint D = A.width() - lockedCount; + nvDebugCheck(D > 0); + + // Compute: b - Al * xl + FullVector b_Alxl(b); + + for (uint y = 0; y < A.height(); y++) + { + const uint count = A.getRow(y).count(); + for (uint e = 0; e < count; e++) + { + uint column = A.getRow(y)[e].x; + + bool isFree = true; + for (uint i = 0; i < lockedCount; i++) + { + isFree &= (lockedParameters[i] != column); + } + + if (!isFree) + { + b_Alxl[y] -= x[column] * A.getRow(y)[e].v; + } + } + } + + // Remove locked columns from A. + SparseMatrix Af(D, A.height()); + + for (uint y = 0; y < A.height(); y++) + { + const uint count = A.getRow(y).count(); + for (uint e = 0; e < count; e++) + { + uint column = A.getRow(y)[e].x; + uint ix = column; + + bool isFree = true; + for (uint i = 0; i < lockedCount; i++) + { + isFree &= (lockedParameters[i] != column); + if (column > lockedParameters[i]) ix--; // shift columns + } + + if (isFree) + { + Af.setCoefficient(ix, y, A.getRow(y)[e].v); + } + } + } + + // Remove elements from x + FullVector xf(D); + + for (uint i = 0, j = 0; i < A.width(); i++) + { + bool isFree = true; + for (uint l = 0; l < lockedCount; l++) + { + isFree &= (lockedParameters[l] != i); + } + + if (isFree) + { + xf[j++] = x[i]; + } + } + + // Solve reduced system. + bool result = LeastSquaresSolver(Af, b_Alxl, xf, epsilon); + + // Copy results back to x. + for (uint i = 0, j = 0; i < A.width(); i++) + { + bool isFree = true; + for (uint l = 0; l < lockedCount; l++) + { + isFree &= (lockedParameters[l] != i); + } + + if (isFree) + { + x[i] = xf[j++]; + } + } + + return result; +} + + +bool nv::SymmetricSolver(const SparseMatrix & A, const FullVector & b, FullVector & x, float epsilon/*1e-5f*/) +{ + nvDebugCheck(A.height() == A.width()); + nvDebugCheck(A.height() == b.dimension()); + nvDebugCheck(b.dimension() == x.dimension()); + + JacobiPreconditioner jacobi(A, true); + return ConjugateGradientSolver(jacobi, A, b, x, epsilon); + + //return ConjugateGradientSolver(A, b, x, epsilon); +} + + +/** +* Compute the solution of the sparse linear system Ab=x using the Conjugate +* Gradient method. +* +* Solving sparse linear systems: +* (1) A·x = b +* +* The conjugate gradient algorithm solves (1) only in the case that A is +* symmetric and positive definite. It is based on the idea of minimizing the +* function +* +* (2) f(x) = 1/2·x·A·x - b·x +* +* This function is minimized when its gradient +* +* (3) df = A·x - b +* +* is zero, which is equivalent to (1). The minimization is carried out by +* generating a succession of search directions p.k and improved minimizers x.k. +* At each stage a quantity alfa.k is found that minimizes f(x.k + alfa.k·p.k), +* and x.k+1 is set equal to the new point x.k + alfa.k·p.k. The p.k and x.k are +* built up in such a way that x.k+1 is also the minimizer of f over the whole +* vector space of directions already taken, {p.1, p.2, . . . , p.k}. After N +* iterations you arrive at the minimizer over the entire vector space, i.e., the +* solution to (1). +* +* For a really good explanation of the method see: +* +* "An Introduction to the Conjugate Gradient Method Without the Agonizing Pain", +* Jonhathan Richard Shewchuk. +* +**/ +/*static*/ bool ConjugateGradientSolver(const SparseMatrix & A, const FullVector & b, FullVector & x, float epsilon) +{ + nvDebugCheck( A.isSquare() ); + nvDebugCheck( A.width() == b.dimension() ); + nvDebugCheck( A.width() == x.dimension() ); + + int i = 0; + const int D = A.width(); + const int i_max = 4 * D; // Convergence should be linear, but in some cases, it's not. + + FullVector r(D); // residual + FullVector p(D); // search direction + FullVector q(D); // + float delta_0; + float delta_old; + float delta_new; + float alpha; + float beta; + + // r = b - A·x; + copy(b, r); + sgemv(-1, A, x, 1, r); + + // p = r; + copy(r, p); + + delta_new = dot( r, r ); + delta_0 = delta_new; + + while (i < i_max && delta_new > epsilon*epsilon*delta_0) + { + i++; + + // q = A·p + mult(A, p, q); + + // alpha = delta_new / p·q + alpha = delta_new / dot( p, q ); + + // x = alfa·p + x + saxpy(alpha, p, x); + + if ((i & 31) == 0) // recompute r after 32 steps + { + // r = b - A·x + copy(b, r); + sgemv(-1, A, x, 1, r); + } + else + { + // r = r - alpha·q + saxpy(-alpha, q, r); + } + + delta_old = delta_new; + delta_new = dot( r, r ); + + beta = delta_new / delta_old; + + // p = beta·p + r + scal(beta, p); + saxpy(1, r, p); + } + + return delta_new <= epsilon*epsilon*delta_0; +} + + +// Conjugate gradient with preconditioner. +/*static*/ bool ConjugateGradientSolver(const Preconditioner & preconditioner, const SparseMatrix & A, const FullVector & b, FullVector & x, float epsilon) +{ + nvDebugCheck( A.isSquare() ); + nvDebugCheck( A.width() == b.dimension() ); + nvDebugCheck( A.width() == x.dimension() ); + + int i = 0; + const int D = A.width(); + const int i_max = 4 * D; // Convergence should be linear, but in some cases, it's not. + + FullVector r(D); // residual + FullVector p(D); // search direction + FullVector q(D); // + FullVector s(D); // preconditioned + float delta_0; + float delta_old; + float delta_new; + float alpha; + float beta; + + // r = b - A·x + copy(b, r); + sgemv(-1, A, x, 1, r); + + + // p = M^-1 · r + preconditioner.apply(r, p); + //copy(r, p); + + + delta_new = dot(r, p); + delta_0 = delta_new; + + while (i < i_max && delta_new > epsilon*epsilon*delta_0) + { + i++; + + // q = A·p + mult(A, p, q); + + // alpha = delta_new / p·q + alpha = delta_new / dot(p, q); + + // x = alfa·p + x + saxpy(alpha, p, x); + + if ((i & 31) == 0) // recompute r after 32 steps + { + // r = b - A·x + copy(b, r); + sgemv(-1, A, x, 1, r); + } + else + { + // r = r - alfa·q + saxpy(-alpha, q, r); + } + + // s = M^-1 · r + preconditioner.apply(r, s); + //copy(r, s); + + delta_old = delta_new; + delta_new = dot( r, s ); + + beta = delta_new / delta_old; + + // p = s + beta·p + scal(beta, p); + saxpy(1, s, p); + } + + return delta_new <= epsilon*epsilon*delta_0; +} + + +#if 0 // Nonsymmetric solvers + +/** Bi-conjugate gradient method. */ +MATHLIB_API int BiConjugateGradientSolve( const SparseMatrix &A, const DenseVector &b, DenseVector &x, float epsilon ) { + piDebugCheck( A.IsSquare() ); + piDebugCheck( A.Width() == b.Dim() ); + piDebugCheck( A.Width() == x.Dim() ); + + int i = 0; + const int D = A.Width(); + const int i_max = 4 * D; + + float resid; + float rho_1 = 0; + float rho_2 = 0; + float alpha; + float beta; + + DenseVector r(D); + DenseVector rtilde(D); + DenseVector p(D); + DenseVector ptilde(D); + DenseVector q(D); + DenseVector qtilde(D); + DenseVector tmp(D); // temporal vector. + + // r = b - A·x; + A.Product( x, tmp ); + r.Sub( b, tmp ); + + // rtilde = r + rtilde.Set( r ); + + // p = r; + p.Set( r ); + + // ptilde = rtilde + ptilde.Set( rtilde ); + + + + float normb = b.Norm(); + if( normb == 0.0 ) normb = 1; + + // test convergence + resid = r.Norm() / normb; + if( resid < epsilon ) { + // method converges? + return 0; + } + + + while( i < i_max ) { + + i++; + + rho_1 = DenseVectorDotProduct( r, rtilde ); + + if( rho_1 == 0 ) { + // method fails. + return -i; + } + + if (i == 1) { + p.Set( r ); + ptilde.Set( rtilde ); + } + else { + beta = rho_1 / rho_2; + + // p = r + beta * p; + p.Mad( r, p, beta ); + + // ptilde = ztilde + beta * ptilde; + ptilde.Mad( rtilde, ptilde, beta ); + } + + // q = A * p; + A.Product( p, q ); + + // qtilde = A^t * ptilde; + A.TransProduct( ptilde, qtilde ); + + alpha = rho_1 / DenseVectorDotProduct( ptilde, q ); + + // x += alpha * p; + x.Mad( x, p, alpha ); + + // r -= alpha * q; + r.Mad( r, q, -alpha ); + + // rtilde -= alpha * qtilde; + rtilde.Mad( rtilde, qtilde, -alpha ); + + rho_2 = rho_1; + + // test convergence + resid = r.Norm() / normb; + if( resid < epsilon ) { + // method converges + return i; + } + } + + return i; +} + + +/** Bi-conjugate gradient stabilized method. */ +int BiCGSTABSolve( const SparseMatrix &A, const DenseVector &b, DenseVector &x, float epsilon ) { + piDebugCheck( A.IsSquare() ); + piDebugCheck( A.Width() == b.Dim() ); + piDebugCheck( A.Width() == x.Dim() ); + + int i = 0; + const int D = A.Width(); + const int i_max = 2 * D; + + + float resid; + float rho_1 = 0; + float rho_2 = 0; + float alpha = 0; + float beta = 0; + float omega = 0; + + DenseVector p(D); + DenseVector phat(D); + DenseVector s(D); + DenseVector shat(D); + DenseVector t(D); + DenseVector v(D); + + DenseVector r(D); + DenseVector rtilde(D); + + DenseVector tmp(D); + + // r = b - A·x; + A.Product( x, tmp ); + r.Sub( b, tmp ); + + // rtilde = r + rtilde.Set( r ); + + + float normb = b.Norm(); + if( normb == 0.0 ) normb = 1; + + // test convergence + resid = r.Norm() / normb; + if( resid < epsilon ) { + // method converges? + return 0; + } + + + while( i + +#include "Sparse.h" +#include "KahanSum.h" + +#include "nvcore/Array.inl" + +#define USE_KAHAN_SUM 0 + + +using namespace nv; + + +FullVector::FullVector(uint dim) +{ + m_array.resize(dim); +} + +FullVector::FullVector(const FullVector & v) : m_array(v.m_array) +{ +} + +const FullVector & FullVector::operator=(const FullVector & v) +{ + nvCheck(dimension() == v.dimension()); + + m_array = v.m_array; + + return *this; +} + + +void FullVector::fill(float f) +{ + const uint dim = dimension(); + for (uint i = 0; i < dim; i++) + { + m_array[i] = f; + } +} + +void FullVector::operator+= (const FullVector & v) +{ + nvDebugCheck(dimension() == v.dimension()); + + const uint dim = dimension(); + for (uint i = 0; i < dim; i++) + { + m_array[i] += v.m_array[i]; + } +} + +void FullVector::operator-= (const FullVector & v) +{ + nvDebugCheck(dimension() == v.dimension()); + + const uint dim = dimension(); + for (uint i = 0; i < dim; i++) + { + m_array[i] -= v.m_array[i]; + } +} + +void FullVector::operator*= (const FullVector & v) +{ + nvDebugCheck(dimension() == v.dimension()); + + const uint dim = dimension(); + for (uint i = 0; i < dim; i++) + { + m_array[i] *= v.m_array[i]; + } +} + +void FullVector::operator+= (float f) +{ + const uint dim = dimension(); + for (uint i = 0; i < dim; i++) + { + m_array[i] += f; + } +} + +void FullVector::operator-= (float f) +{ + const uint dim = dimension(); + for (uint i = 0; i < dim; i++) + { + m_array[i] -= f; + } +} + +void FullVector::operator*= (float f) +{ + const uint dim = dimension(); + for (uint i = 0; i < dim; i++) + { + m_array[i] *= f; + } +} + + +void nv::saxpy(float a, const FullVector & x, FullVector & y) +{ + nvDebugCheck(x.dimension() == y.dimension()); + + const uint dim = x.dimension(); + for (uint i = 0; i < dim; i++) + { + y[i] += a * x[i]; + } +} + +void nv::copy(const FullVector & x, FullVector & y) +{ + nvDebugCheck(x.dimension() == y.dimension()); + + const uint dim = x.dimension(); + for (uint i = 0; i < dim; i++) + { + y[i] = x[i]; + } +} + +void nv::scal(float a, FullVector & x) +{ + const uint dim = x.dimension(); + for (uint i = 0; i < dim; i++) + { + x[i] *= a; + } +} + +float nv::dot(const FullVector & x, const FullVector & y) +{ + nvDebugCheck(x.dimension() == y.dimension()); + + const uint dim = x.dimension(); + +#if USE_KAHAN_SUM + KahanSum kahan; + for (uint i = 0; i < dim; i++) + { + kahan.add(x[i] * y[i]); + } + return kahan.sum(); +#else + float sum = 0; + for (uint i = 0; i < dim; i++) + { + sum += x[i] * y[i]; + } + return sum; +#endif +} + + +FullMatrix::FullMatrix(uint d) : m_width(d), m_height(d) +{ + m_array.resize(d*d, 0.0f); +} + +FullMatrix::FullMatrix(uint w, uint h) : m_width(w), m_height(h) +{ + m_array.resize(w*h, 0.0f); +} + +FullMatrix::FullMatrix(const FullMatrix & m) : m_width(m.m_width), m_height(m.m_height) +{ + m_array = m.m_array; +} + +const FullMatrix & FullMatrix::operator=(const FullMatrix & m) +{ + nvCheck(width() == m.width()); + nvCheck(height() == m.height()); + + m_array = m.m_array; + + return *this; +} + + +float FullMatrix::getCoefficient(uint x, uint y) const +{ + nvDebugCheck( x < width() ); + nvDebugCheck( y < height() ); + + return m_array[y * width() + x]; +} + +void FullMatrix::setCoefficient(uint x, uint y, float f) +{ + nvDebugCheck( x < width() ); + nvDebugCheck( y < height() ); + + m_array[y * width() + x] = f; +} + +void FullMatrix::addCoefficient(uint x, uint y, float f) +{ + nvDebugCheck( x < width() ); + nvDebugCheck( y < height() ); + + m_array[y * width() + x] += f; +} + +void FullMatrix::mulCoefficient(uint x, uint y, float f) +{ + nvDebugCheck( x < width() ); + nvDebugCheck( y < height() ); + + m_array[y * width() + x] *= f; +} + +float FullMatrix::dotRow(uint y, const FullVector & v) const +{ + nvDebugCheck( v.dimension() == width() ); + nvDebugCheck( y < height() ); + + float sum = 0; + + const uint count = v.dimension(); + for (uint i = 0; i < count; i++) + { + sum += m_array[y * count + i] * v[i]; + } + + return sum; +} + +void FullMatrix::madRow(uint y, float alpha, FullVector & v) const +{ + nvDebugCheck( v.dimension() == width() ); + nvDebugCheck( y < height() ); + + const uint count = v.dimension(); + for (uint i = 0; i < count; i++) + { + v[i] += m_array[y * count + i]; + } +} + + +// y = M * x +void nv::mult(const FullMatrix & M, const FullVector & x, FullVector & y) +{ + mult(NoTransposed, M, x, y); +} + +void nv::mult(Transpose TM, const FullMatrix & M, const FullVector & x, FullVector & y) +{ + const uint w = M.width(); + const uint h = M.height(); + + if (TM == Transposed) + { + nvDebugCheck( h == x.dimension() ); + nvDebugCheck( w == y.dimension() ); + + y.fill(0.0f); + + for (uint i = 0; i < h; i++) + { + M.madRow(i, x[i], y); + } + } + else + { + nvDebugCheck( w == x.dimension() ); + nvDebugCheck( h == y.dimension() ); + + for (uint i = 0; i < h; i++) + { + y[i] = M.dotRow(i, x); + } + } +} + +// y = alpha*A*x + beta*y +void nv::sgemv(float alpha, const FullMatrix & A, const FullVector & x, float beta, FullVector & y) +{ + sgemv(alpha, NoTransposed, A, x, beta, y); +} + +void nv::sgemv(float alpha, Transpose TA, const FullMatrix & A, const FullVector & x, float beta, FullVector & y) +{ + const uint w = A.width(); + const uint h = A.height(); + + if (TA == Transposed) + { + nvDebugCheck( h == x.dimension() ); + nvDebugCheck( w == y.dimension() ); + + for (uint i = 0; i < h; i++) + { + A.madRow(i, alpha * x[i], y); + } + } + else + { + nvDebugCheck( w == x.dimension() ); + nvDebugCheck( h == y.dimension() ); + + for (uint i = 0; i < h; i++) + { + y[i] = alpha * A.dotRow(i, x) + beta * y[i]; + } + } +} + + +// Multiply a row of A by a column of B. +static float dot(uint j, Transpose TA, const FullMatrix & A, uint i, Transpose TB, const FullMatrix & B) +{ + const uint w = (TA == NoTransposed) ? A.width() : A.height(); + nvDebugCheck(w == ((TB == NoTransposed) ? B.height() : A.width())); + + float sum = 0.0f; + + for (uint k = 0; k < w; k++) + { + const float a = (TA == NoTransposed) ? A.getCoefficient(k, j) : A.getCoefficient(j, k); // @@ Move branches out of the loop? + const float b = (TB == NoTransposed) ? B.getCoefficient(i, k) : A.getCoefficient(k, i); + sum += a * b; + } + + return sum; +} + + +// C = A * B +void nv::mult(const FullMatrix & A, const FullMatrix & B, FullMatrix & C) +{ + mult(NoTransposed, A, NoTransposed, B, C); +} + +void nv::mult(Transpose TA, const FullMatrix & A, Transpose TB, const FullMatrix & B, FullMatrix & C) +{ + sgemm(1.0f, TA, A, TB, B, 0.0f, C); +} + +// C = alpha*A*B + beta*C +void nv::sgemm(float alpha, const FullMatrix & A, const FullMatrix & B, float beta, FullMatrix & C) +{ + sgemm(alpha, NoTransposed, A, NoTransposed, B, beta, C); +} + +void nv::sgemm(float alpha, Transpose TA, const FullMatrix & A, Transpose TB, const FullMatrix & B, float beta, FullMatrix & C) +{ + const uint w = C.width(); + const uint h = C.height(); + + uint aw = (TA == NoTransposed) ? A.width() : A.height(); + uint ah = (TA == NoTransposed) ? A.height() : A.width(); + uint bw = (TB == NoTransposed) ? B.width() : B.height(); + uint bh = (TB == NoTransposed) ? B.height() : B.width(); + + nvDebugCheck(aw == bh); + nvDebugCheck(bw == ah); + nvDebugCheck(w == bw); + nvDebugCheck(h == ah); + + for (uint y = 0; y < h; y++) + { + for (uint x = 0; x < w; x++) + { + float c = alpha * ::dot(x, TA, A, y, TB, B) + beta * C.getCoefficient(x, y); + C.setCoefficient(x, y, c); + } + } +} + + + + + +/// Ctor. Init the size of the sparse matrix. +SparseMatrix::SparseMatrix(uint d) : m_width(d) +{ + m_array.resize(d); +} + +/// Ctor. Init the size of the sparse matrix. +SparseMatrix::SparseMatrix(uint w, uint h) : m_width(w) +{ + m_array.resize(h); +} + +SparseMatrix::SparseMatrix(const SparseMatrix & m) : m_width(m.m_width) +{ + m_array = m.m_array; +} + +const SparseMatrix & SparseMatrix::operator=(const SparseMatrix & m) +{ + nvCheck(width() == m.width()); + nvCheck(height() == m.height()); + + m_array = m.m_array; + + return *this; +} + + +// x is column, y is row +float SparseMatrix::getCoefficient(uint x, uint y) const +{ + nvDebugCheck( x < width() ); + nvDebugCheck( y < height() ); + + const uint count = m_array[y].count(); + for (uint i = 0; i < count; i++) + { + if (m_array[y][i].x == x) return m_array[y][i].v; + } + + return 0.0f; +} + +void SparseMatrix::setCoefficient(uint x, uint y, float f) +{ + nvDebugCheck( x < width() ); + nvDebugCheck( y < height() ); + + const uint count = m_array[y].count(); + for (uint i = 0; i < count; i++) + { + if (m_array[y][i].x == x) + { + m_array[y][i].v = f; + return; + } + } + + if (f != 0.0f) + { + Coefficient c = { x, f }; + m_array[y].append( c ); + } +} + +void SparseMatrix::addCoefficient(uint x, uint y, float f) +{ + nvDebugCheck( x < width() ); + nvDebugCheck( y < height() ); + + if (f != 0.0f) + { + const uint count = m_array[y].count(); + for (uint i = 0; i < count; i++) + { + if (m_array[y][i].x == x) + { + m_array[y][i].v += f; + return; + } + } + + Coefficient c = { x, f }; + m_array[y].append( c ); + } +} + +void SparseMatrix::mulCoefficient(uint x, uint y, float f) +{ + nvDebugCheck( x < width() ); + nvDebugCheck( y < height() ); + + const uint count = m_array[y].count(); + for (uint i = 0; i < count; i++) + { + if (m_array[y][i].x == x) + { + m_array[y][i].v *= f; + return; + } + } + + if (f != 0.0f) + { + Coefficient c = { x, f }; + m_array[y].append( c ); + } +} + + +float SparseMatrix::sumRow(uint y) const +{ + nvDebugCheck( y < height() ); + + const uint count = m_array[y].count(); + +#if USE_KAHAN_SUM + KahanSum kahan; + for (uint i = 0; i < count; i++) + { + kahan.add(m_array[y][i].v); + } + return kahan.sum(); +#else + float sum = 0; + for (uint i = 0; i < count; i++) + { + sum += m_array[y][i].v; + } + return sum; +#endif +} + +float SparseMatrix::dotRow(uint y, const FullVector & v) const +{ + nvDebugCheck( y < height() ); + + const uint count = m_array[y].count(); + +#if USE_KAHAN_SUM + KahanSum kahan; + for (uint i = 0; i < count; i++) + { + kahan.add(m_array[y][i].v * v[m_array[y][i].x]); + } + return kahan.sum(); +#else + float sum = 0; + for (uint i = 0; i < count; i++) + { + sum += m_array[y][i].v * v[m_array[y][i].x]; + } + return sum; +#endif +} + +void SparseMatrix::madRow(uint y, float alpha, FullVector & v) const +{ + nvDebugCheck(y < height()); + + const uint count = m_array[y].count(); + for (uint i = 0; i < count; i++) + { + v[m_array[y][i].x] += alpha * m_array[y][i].v; + } +} + + +void SparseMatrix::clearRow(uint y) +{ + nvDebugCheck( y < height() ); + + m_array[y].clear(); +} + +void SparseMatrix::scaleRow(uint y, float f) +{ + nvDebugCheck( y < height() ); + + const uint count = m_array[y].count(); + for (uint i = 0; i < count; i++) + { + m_array[y][i].v *= f; + } +} + +void SparseMatrix::normalizeRow(uint y) +{ + nvDebugCheck( y < height() ); + + float norm = 0.0f; + + const uint count = m_array[y].count(); + for (uint i = 0; i < count; i++) + { + float f = m_array[y][i].v; + norm += f * f; + } + + scaleRow(y, 1.0f / sqrtf(norm)); +} + + +void SparseMatrix::clearColumn(uint x) +{ + nvDebugCheck(x < width()); + + for (uint y = 0; y < height(); y++) + { + const uint count = m_array[y].count(); + for (uint e = 0; e < count; e++) + { + if (m_array[y][e].x == x) + { + m_array[y][e].v = 0.0f; + break; + } + } + } +} + +void SparseMatrix::scaleColumn(uint x, float f) +{ + nvDebugCheck(x < width()); + + for (uint y = 0; y < height(); y++) + { + const uint count = m_array[y].count(); + for (uint e = 0; e < count; e++) + { + if (m_array[y][e].x == x) + { + m_array[y][e].v *= f; + break; + } + } + } +} + +const Array & SparseMatrix::getRow(uint y) const +{ + return m_array[y]; +} + + +bool SparseMatrix::isSymmetric() const +{ + for (uint y = 0; y < height(); y++) + { + const uint count = m_array[y].count(); + for (uint e = 0; e < count; e++) + { + const uint x = m_array[y][e].x; + if (x > y) { + float v = m_array[y][e].v; + + if (!equal(getCoefficient(y, x), v)) { // @@ epsilon + return false; + } + } + } + } + + return true; +} + + +// y = M * x +void nv::mult(const SparseMatrix & M, const FullVector & x, FullVector & y) +{ + mult(NoTransposed, M, x, y); +} + +void nv::mult(Transpose TM, const SparseMatrix & M, const FullVector & x, FullVector & y) +{ + const uint w = M.width(); + const uint h = M.height(); + + if (TM == Transposed) + { + nvDebugCheck( h == x.dimension() ); + nvDebugCheck( w == y.dimension() ); + + y.fill(0.0f); + + for (uint i = 0; i < h; i++) + { + M.madRow(i, x[i], y); + } + } + else + { + nvDebugCheck( w == x.dimension() ); + nvDebugCheck( h == y.dimension() ); + + for (uint i = 0; i < h; i++) + { + y[i] = M.dotRow(i, x); + } + } +} + +// y = alpha*A*x + beta*y +void nv::sgemv(float alpha, const SparseMatrix & A, const FullVector & x, float beta, FullVector & y) +{ + sgemv(alpha, NoTransposed, A, x, beta, y); +} + +void nv::sgemv(float alpha, Transpose TA, const SparseMatrix & A, const FullVector & x, float beta, FullVector & y) +{ + const uint w = A.width(); + const uint h = A.height(); + + if (TA == Transposed) + { + nvDebugCheck( h == x.dimension() ); + nvDebugCheck( w == y.dimension() ); + + for (uint i = 0; i < h; i++) + { + A.madRow(i, alpha * x[i], y); + } + } + else + { + nvDebugCheck( w == x.dimension() ); + nvDebugCheck( h == y.dimension() ); + + for (uint i = 0; i < h; i++) + { + y[i] = alpha * A.dotRow(i, x) + beta * y[i]; + } + } +} + + +// dot y-row of A by x-column of B +static float dotRowColumn(int y, const SparseMatrix & A, int x, const SparseMatrix & B) +{ + const Array & row = A.getRow(y); + + const uint count = row.count(); + +#if USE_KAHAN_SUM + KahanSum kahan; + for (uint i = 0; i < count; i++) + { + const SparseMatrix::Coefficient & c = row[i]; + kahan.add(c.v * B.getCoefficient(x, c.x)); + } + return kahan.sum(); +#else + float sum = 0.0f; + for (uint i = 0; i < count; i++) + { + const SparseMatrix::Coefficient & c = row[i]; + sum += c.v * B.getCoefficient(x, c.x); + } + return sum; +#endif +} + +// dot y-row of A by x-row of B +static float dotRowRow(int y, const SparseMatrix & A, int x, const SparseMatrix & B) +{ + const Array & row = A.getRow(y); + + const uint count = row.count(); + +#if USE_KAHAN_SUM + KahanSum kahan; + for (uint i = 0; i < count; i++) + { + const SparseMatrix::Coefficient & c = row[i]; + kahan.add(c.v * B.getCoefficient(c.x, x)); + } + return kahan.sum(); +#else + float sum = 0.0f; + for (uint i = 0; i < count; i++) + { + const SparseMatrix::Coefficient & c = row[i]; + sum += c.v * B.getCoefficient(c.x, x); + } + return sum; +#endif +} + +// dot y-column of A by x-column of B +static float dotColumnColumn(int y, const SparseMatrix & A, int x, const SparseMatrix & B) +{ + nvDebugCheck(A.height() == B.height()); + + const uint h = A.height(); + +#if USE_KAHAN_SUM + KahanSum kahan; + for (uint i = 0; i < h; i++) + { + kahan.add(A.getCoefficient(y, i) * B.getCoefficient(x, i)); + } + return kahan.sum(); +#else + float sum = 0.0f; + for (uint i = 0; i < h; i++) + { + sum += A.getCoefficient(y, i) * B.getCoefficient(x, i); + } + return sum; +#endif +} + + +void nv::transpose(const SparseMatrix & A, SparseMatrix & B) +{ + nvDebugCheck(A.width() == B.height()); + nvDebugCheck(B.width() == A.height()); + + const uint w = A.width(); + for (uint x = 0; x < w; x++) + { + B.clearRow(x); + } + + const uint h = A.height(); + for (uint y = 0; y < h; y++) + { + const Array & row = A.getRow(y); + + const uint count = row.count(); + for (uint i = 0; i < count; i++) + { + const SparseMatrix::Coefficient & c = row[i]; + nvDebugCheck(c.x < w); + + B.setCoefficient(y, c.x, c.v); + } + } +} + +// C = A * B +void nv::mult(const SparseMatrix & A, const SparseMatrix & B, SparseMatrix & C) +{ + mult(NoTransposed, A, NoTransposed, B, C); +} + +void nv::mult(Transpose TA, const SparseMatrix & A, Transpose TB, const SparseMatrix & B, SparseMatrix & C) +{ + sgemm(1.0f, TA, A, TB, B, 0.0f, C); +} + +// C = alpha*A*B + beta*C +void nv::sgemm(float alpha, const SparseMatrix & A, const SparseMatrix & B, float beta, SparseMatrix & C) +{ + sgemm(alpha, NoTransposed, A, NoTransposed, B, beta, C); +} + +void nv::sgemm(float alpha, Transpose TA, const SparseMatrix & A, Transpose TB, const SparseMatrix & B, float beta, SparseMatrix & C) +{ + const uint w = C.width(); + const uint h = C.height(); + + uint aw = (TA == NoTransposed) ? A.width() : A.height(); + uint ah = (TA == NoTransposed) ? A.height() : A.width(); + uint bw = (TB == NoTransposed) ? B.width() : B.height(); + uint bh = (TB == NoTransposed) ? B.height() : B.width(); + + nvDebugCheck(aw == bh); + nvDebugCheck(bw == ah); + nvDebugCheck(w == bw); + nvDebugCheck(h == ah); + + + for (uint y = 0; y < h; y++) + { + for (uint x = 0; x < w; x++) + { + float c = beta * C.getCoefficient(x, y); + + if (TA == NoTransposed && TB == NoTransposed) + { + // dot y-row of A by x-column of B. + c += alpha * dotRowColumn(y, A, x, B); + } + else if (TA == Transposed && TB == Transposed) + { + // dot y-column of A by x-row of B. + c += alpha * dotRowColumn(x, B, y, A); + } + else if (TA == Transposed && TB == NoTransposed) + { + // dot y-column of A by x-column of B. + c += alpha * dotColumnColumn(y, A, x, B); + } + else if (TA == NoTransposed && TB == Transposed) + { + // dot y-row of A by x-row of B. + c += alpha * dotRowRow(y, A, x, B); + } + + C.setCoefficient(x, y, c); + } + } +} + +// C = At * A +void nv::sqm(const SparseMatrix & A, SparseMatrix & C) +{ + // This is quite expensive... + mult(Transposed, A, NoTransposed, A, C); +} diff --git a/thirdparty/thekla_atlas/src/nvmath/Sparse.h b/thirdparty/thekla_atlas/src/nvmath/Sparse.h new file mode 100755 index 00000000..6b03ed51 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Sparse.h @@ -0,0 +1,204 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MATH_SPARSE_H +#define NV_MATH_SPARSE_H + +#include "nvmath.h" +#include "nvcore/Array.h" + + +// Full and sparse vector and matrix classes. BLAS subset. + +namespace nv +{ + class FullVector; + class FullMatrix; + class SparseMatrix; + + + /// Fixed size vector class. + class FullVector + { + public: + + FullVector(uint dim); + FullVector(const FullVector & v); + + const FullVector & operator=(const FullVector & v); + + uint dimension() const { return m_array.count(); } + + const float & operator[]( uint index ) const { return m_array[index]; } + float & operator[] ( uint index ) { return m_array[index]; } + + void fill(float f); + + void operator+= (const FullVector & v); + void operator-= (const FullVector & v); + void operator*= (const FullVector & v); + + void operator+= (float f); + void operator-= (float f); + void operator*= (float f); + + + private: + + Array m_array; + + }; + + // Pseudo-BLAS interface. + NVMATH_API void saxpy(float a, const FullVector & x, FullVector & y); // y = a * x + y + NVMATH_API void copy(const FullVector & x, FullVector & y); + NVMATH_API void scal(float a, FullVector & x); + NVMATH_API float dot(const FullVector & x, const FullVector & y); + + + enum Transpose + { + NoTransposed = 0, + Transposed = 1 + }; + + /// Full matrix class. + class FullMatrix + { + public: + + FullMatrix(uint d); + FullMatrix(uint w, uint h); + FullMatrix(const FullMatrix & m); + + const FullMatrix & operator=(const FullMatrix & m); + + uint width() const { return m_width; } + uint height() const { return m_height; } + bool isSquare() const { return m_width == m_height; } + + float getCoefficient(uint x, uint y) const; + + void setCoefficient(uint x, uint y, float f); + void addCoefficient(uint x, uint y, float f); + void mulCoefficient(uint x, uint y, float f); + + float dotRow(uint y, const FullVector & v) const; + void madRow(uint y, float alpha, FullVector & v) const; + + protected: + + bool isValid() const { + return m_array.size() == (m_width * m_height); + } + + private: + + const uint m_width; + const uint m_height; + Array m_array; + + }; + + NVMATH_API void mult(const FullMatrix & M, const FullVector & x, FullVector & y); + NVMATH_API void mult(Transpose TM, const FullMatrix & M, const FullVector & x, FullVector & y); + + // y = alpha*A*x + beta*y + NVMATH_API void sgemv(float alpha, const FullMatrix & A, const FullVector & x, float beta, FullVector & y); + NVMATH_API void sgemv(float alpha, Transpose TA, const FullMatrix & A, const FullVector & x, float beta, FullVector & y); + + NVMATH_API void mult(const FullMatrix & A, const FullMatrix & B, FullMatrix & C); + NVMATH_API void mult(Transpose TA, const FullMatrix & A, Transpose TB, const FullMatrix & B, FullMatrix & C); + + // C = alpha*A*B + beta*C + NVMATH_API void sgemm(float alpha, const FullMatrix & A, const FullMatrix & B, float beta, FullMatrix & C); + NVMATH_API void sgemm(float alpha, Transpose TA, const FullMatrix & A, Transpose TB, const FullMatrix & B, float beta, FullMatrix & C); + + + /** + * Sparse matrix class. The matrix is assumed to be sparse and to have + * very few non-zero elements, for this reason it's stored in indexed + * format. To multiply column vectors efficiently, the matrix stores + * the elements in indexed-column order, there is a list of indexed + * elements for each row of the matrix. As with the FullVector the + * dimension of the matrix is constant. + **/ + class SparseMatrix + { + friend class FullMatrix; + public: + + // An element of the sparse array. + struct Coefficient { + uint x; // column + float v; // value + }; + + + public: + + SparseMatrix(uint d); + SparseMatrix(uint w, uint h); + SparseMatrix(const SparseMatrix & m); + + const SparseMatrix & operator=(const SparseMatrix & m); + + + uint width() const { return m_width; } + uint height() const { return m_array.count(); } + bool isSquare() const { return width() == height(); } + + float getCoefficient(uint x, uint y) const; // x is column, y is row + + void setCoefficient(uint x, uint y, float f); + void addCoefficient(uint x, uint y, float f); + void mulCoefficient(uint x, uint y, float f); + + float sumRow(uint y) const; + float dotRow(uint y, const FullVector & v) const; + void madRow(uint y, float alpha, FullVector & v) const; + + void clearRow(uint y); + void scaleRow(uint y, float f); + void normalizeRow(uint y); + + void clearColumn(uint x); + void scaleColumn(uint x, float f); + + const Array & getRow(uint y) const; + + bool isSymmetric() const; + + private: + + /// Number of columns. + const uint m_width; + + /// Array of matrix elements. + Array< Array > m_array; + + }; + + NVMATH_API void transpose(const SparseMatrix & A, SparseMatrix & B); + + NVMATH_API void mult(const SparseMatrix & M, const FullVector & x, FullVector & y); + NVMATH_API void mult(Transpose TM, const SparseMatrix & M, const FullVector & x, FullVector & y); + + // y = alpha*A*x + beta*y + NVMATH_API void sgemv(float alpha, const SparseMatrix & A, const FullVector & x, float beta, FullVector & y); + NVMATH_API void sgemv(float alpha, Transpose TA, const SparseMatrix & A, const FullVector & x, float beta, FullVector & y); + + NVMATH_API void mult(const SparseMatrix & A, const SparseMatrix & B, SparseMatrix & C); + NVMATH_API void mult(Transpose TA, const SparseMatrix & A, Transpose TB, const SparseMatrix & B, SparseMatrix & C); + + // C = alpha*A*B + beta*C + NVMATH_API void sgemm(float alpha, const SparseMatrix & A, const SparseMatrix & B, float beta, SparseMatrix & C); + NVMATH_API void sgemm(float alpha, Transpose TA, const SparseMatrix & A, Transpose TB, const SparseMatrix & B, float beta, SparseMatrix & C); + + // C = At * A + NVMATH_API void sqm(const SparseMatrix & A, SparseMatrix & C); + +} // nv namespace + + +#endif // NV_MATH_SPARSE_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Sphere.cpp b/thirdparty/thekla_atlas/src/nvmath/Sphere.cpp new file mode 100755 index 00000000..e0c1ad65 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Sphere.cpp @@ -0,0 +1,431 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "Sphere.h" +#include "Vector.inl" +#include "Box.inl" + +#include // FLT_MAX + +using namespace nv; + +const float radiusEpsilon = 1e-4f; + +Sphere::Sphere(Vector3::Arg p0, Vector3::Arg p1) +{ + if (p0 == p1) *this = Sphere(p0); + else { + center = (p0 + p1) * 0.5f; + radius = length(p0 - center) + radiusEpsilon; + + float d0 = length(p0 - center); + float d1 = length(p1 - center); + nvDebugCheck(equal(d0, radius - radiusEpsilon)); + nvDebugCheck(equal(d1, radius - radiusEpsilon)); + } +} + +Sphere::Sphere(Vector3::Arg p0, Vector3::Arg p1, Vector3::Arg p2) +{ + if (p0 == p1 || p0 == p2) *this = Sphere(p1, p2); + else if (p1 == p2) *this = Sphere(p0, p2); + else { + Vector3 a = p1 - p0; + Vector3 b = p2 - p0; + Vector3 c = cross(a, b); + + float denominator = 2.0f * lengthSquared(c); + + if (!isZero(denominator)) { + Vector3 d = (lengthSquared(b) * cross(c, a) + lengthSquared(a) * cross(b, c)) / denominator; + + center = p0 + d; + radius = length(d) + radiusEpsilon; + + float d0 = length(p0 - center); + float d1 = length(p1 - center); + float d2 = length(p2 - center); + nvDebugCheck(equal(d0, radius - radiusEpsilon)); + nvDebugCheck(equal(d1, radius - radiusEpsilon)); + nvDebugCheck(equal(d2, radius - radiusEpsilon)); + } + else { + // @@ This is a specialization of the code below, but really, the only thing we need to do here is to find the two most distant points. + // Compute all possible spheres, invalidate those that do not contain the four points, keep the smallest. + Sphere s0(p1, p2); + float d0 = distanceSquared(s0, p0); + if (d0 > 0) s0.radius = NV_FLOAT_MAX; + + Sphere s1(p0, p2); + float d1 = distanceSquared(s1, p1); + if (d1 > 0) s1.radius = NV_FLOAT_MAX; + + Sphere s2(p0, p1); + float d2 = distanceSquared(s2, p2); + if (d2 > 0) s1.radius = NV_FLOAT_MAX; + + if (s0.radius < s1.radius && s0.radius < s2.radius) { + center = s0.center; + radius = s0.radius; + } + else if (s1.radius < s2.radius) { + center = s1.center; + radius = s1.radius; + } + else { + center = s2.center; + radius = s2.radius; + } + } + } +} + +Sphere::Sphere(Vector3::Arg p0, Vector3::Arg p1, Vector3::Arg p2, Vector3::Arg p3) +{ + if (p0 == p1 || p0 == p2 || p0 == p3) *this = Sphere(p1, p2, p3); + else if (p1 == p2 || p1 == p3) *this = Sphere(p0, p2, p3); + else if (p2 == p3) *this = Sphere(p0, p1, p2); + else { + // @@ This only works if the points are not coplanar! + Vector3 a = p1 - p0; + Vector3 b = p2 - p0; + Vector3 c = p3 - p0; + + float denominator = 2.0f * dot(c, cross(a, b)); // triple product. + + if (!isZero(denominator)) { + Vector3 d = (lengthSquared(c) * cross(a, b) + lengthSquared(b) * cross(c, a) + lengthSquared(a) * cross(b, c)) / denominator; + + center = p0 + d; + radius = length(d) + radiusEpsilon; + + float d0 = length(p0 - center); + float d1 = length(p1 - center); + float d2 = length(p2 - center); + float d3 = length(p3 - center); + nvDebugCheck(equal(d0, radius - radiusEpsilon)); + nvDebugCheck(equal(d1, radius - radiusEpsilon)); + nvDebugCheck(equal(d2, radius - radiusEpsilon)); + nvDebugCheck(equal(d3, radius - radiusEpsilon)); + } + else { + // Compute all possible spheres, invalidate those that do not contain the four points, keep the smallest. + Sphere s0(p1, p2, p3); + float d0 = distanceSquared(s0, p0); + if (d0 > 0) s0.radius = NV_FLOAT_MAX; + + Sphere s1(p0, p2, p3); + float d1 = distanceSquared(s1, p1); + if (d1 > 0) s1.radius = NV_FLOAT_MAX; + + Sphere s2(p0, p1, p3); + float d2 = distanceSquared(s2, p2); + if (d2 > 0) s2.radius = NV_FLOAT_MAX; + + Sphere s3(p0, p1, p2); + float d3 = distanceSquared(s3, p3); + if (d3 > 0) s2.radius = NV_FLOAT_MAX; + + if (s0.radius < s1.radius && s0.radius < s2.radius && s0.radius < s3.radius) { + center = s0.center; + radius = s0.radius; + } + else if (s1.radius < s2.radius && s1.radius < s3.radius) { + center = s1.center; + radius = s1.radius; + } + else if (s1.radius < s3.radius) { + center = s2.center; + radius = s2.radius; + } + else { + center = s3.center; + radius = s3.radius; + } + } + } +} + + +float nv::distanceSquared(const Sphere & sphere, const Vector3 & point) +{ + return lengthSquared(sphere.center - point) - square(sphere.radius); +} + + + +// Implementation of "MiniBall" based on: +// http://www.flipcode.com/archives/Smallest_Enclosing_Spheres.shtml + +static Sphere recurseMini(const Vector3 *P[], uint p, uint b = 0) +{ + Sphere MB; + + switch(b) + { + case 0: + MB = Sphere(*P[0]); + break; + case 1: + MB = Sphere(*P[-1]); + break; + case 2: + MB = Sphere(*P[-1], *P[-2]); + break; + case 3: + MB = Sphere(*P[-1], *P[-2], *P[-3]); + break; + case 4: + MB = Sphere(*P[-1], *P[-2], *P[-3], *P[-4]); + return MB; + } + + for (uint i = 0; i < p; i++) + { + if (distanceSquared(MB, *P[i]) > 0) // Signed square distance to sphere + { + for (uint j = i; j > 0; j--) + { + swap(P[j], P[j-1]); + } + + MB = recurseMini(P + 1, i, b + 1); + } + } + + return MB; +} + +static bool allInside(const Sphere & sphere, const Vector3 * pointArray, const uint pointCount) { + for (uint i = 0; i < pointCount; i++) { + if (distanceSquared(sphere, pointArray[i]) >= NV_EPSILON) { + return false; + } + } + return true; +} + + +Sphere nv::miniBall(const Vector3 * pointArray, const uint pointCount) +{ + nvDebugCheck(pointArray != NULL); + nvDebugCheck(pointCount > 0); + + const Vector3 **L = new const Vector3*[pointCount]; + + for (uint i = 0; i < pointCount; i++) { + L[i] = &pointArray[i]; + } + + Sphere sphere = recurseMini(L, pointCount); + + delete [] L; + + nvDebugCheck(allInside(sphere, pointArray, pointCount)); + + return sphere; +} + + +// Approximate bounding sphere, based on "An Efficient Bounding Sphere" by Jack Ritter, from "Graphics Gems" +Sphere nv::approximateSphere_Ritter(const Vector3 * pointArray, const uint pointCount) +{ + nvDebugCheck(pointArray != NULL); + nvDebugCheck(pointCount > 0); + + Vector3 xmin, xmax, ymin, ymax, zmin, zmax; + + xmin = xmax = ymin = ymax = zmin = zmax = pointArray[0]; + + // FIRST PASS: find 6 minima/maxima points + xmin.x = ymin.y = zmin.z = FLT_MAX; + xmax.x = ymax.y = zmax.z = -FLT_MAX; + + for (uint i = 0; i < pointCount; i++) + { + const Vector3 & p = pointArray[i]; + if (p.x < xmin.x) xmin = p; + if (p.x > xmax.x) xmax = p; + if (p.y < ymin.y) ymin = p; + if (p.y > ymax.y) ymax = p; + if (p.z < zmin.z) zmin = p; + if (p.z > zmax.z) zmax = p; + } + + float xspan = lengthSquared(xmax - xmin); + float yspan = lengthSquared(ymax - ymin); + float zspan = lengthSquared(zmax - zmin); + + // Set points dia1 & dia2 to the maximally separated pair. + Vector3 dia1 = xmin; + Vector3 dia2 = xmax; + float maxspan = xspan; + if (yspan > maxspan) { + maxspan = yspan; + dia1 = ymin; + dia2 = ymax; + } + if (zspan > maxspan) { + dia1 = zmin; + dia2 = zmax; + } + + // |dia1-dia2| is a diameter of initial sphere + + // calc initial center + Sphere sphere; + sphere.center = (dia1 + dia2) / 2.0f; + + // calculate initial radius**2 and radius + float rad_sq = lengthSquared(dia2 - sphere.center); + sphere.radius = sqrtf(rad_sq); + + + // SECOND PASS: increment current sphere + for (uint i = 0; i < pointCount; i++) + { + const Vector3 & p = pointArray[i]; + + float old_to_p_sq = lengthSquared(p - sphere.center); + + if (old_to_p_sq > rad_sq) // do r**2 test first + { + // this point is outside of current sphere + float old_to_p = sqrtf(old_to_p_sq); + + // calc radius of new sphere + sphere.radius = (sphere.radius + old_to_p) / 2.0f; + rad_sq = sphere.radius * sphere.radius; // for next r**2 compare + + float old_to_new = old_to_p - sphere.radius; + + // calc center of new sphere + sphere.center = (sphere.radius * sphere.center + old_to_new * p) / old_to_p; + } + } + + nvDebugCheck(allInside(sphere, pointArray, pointCount)); + + return sphere; +} + + +static float computeSphereRadius(const Vector3 & center, const Vector3 * pointArray, const uint pointCount) { + + float maxRadius2 = 0; + + for (uint i = 0; i < pointCount; i++) + { + const Vector3 & p = pointArray[i]; + + float r2 = lengthSquared(center - p); + + if (r2 > maxRadius2) { + maxRadius2 = r2; + } + } + + return sqrtf(maxRadius2) + radiusEpsilon; +} + + +Sphere nv::approximateSphere_AABB(const Vector3 * pointArray, const uint pointCount) +{ + nvDebugCheck(pointArray != NULL); + nvDebugCheck(pointCount > 0); + + Box box; + box.clearBounds(); + + for (uint i = 0; i < pointCount; i++) { + box.addPointToBounds(pointArray[i]); + } + + Sphere sphere; + sphere.center = box.center(); + sphere.radius = computeSphereRadius(sphere.center, pointArray, pointCount); + + nvDebugCheck(allInside(sphere, pointArray, pointCount)); + + return sphere; +} + + +static void computeExtremalPoints(const Vector3 & dir, const Vector3 * pointArray, uint pointCount, Vector3 * minPoint, Vector3 * maxPoint) { + nvDebugCheck(pointCount > 0); + + uint mini = 0; + uint maxi = 0; + float minDist = FLT_MAX; + float maxDist = -FLT_MAX; + + for (uint i = 0; i < pointCount; i++) { + float d = dot(dir, pointArray[i]); + + if (d < minDist) { + minDist = d; + mini = i; + } + if (d > maxDist) { + maxDist = d; + maxi = i; + } + } + nvDebugCheck(minDist != FLT_MAX); + nvDebugCheck(maxDist != -FLT_MAX); + + *minPoint = pointArray[mini]; + *maxPoint = pointArray[maxi]; +} + +// EPOS algorithm based on: +// http://www.ep.liu.se/ecp/034/009/ecp083409.pdf +Sphere nv::approximateSphere_EPOS6(const Vector3 * pointArray, uint pointCount) +{ + nvDebugCheck(pointArray != NULL); + nvDebugCheck(pointCount > 0); + + Vector3 extremalPoints[6]; + + // Compute 6 extremal points. + computeExtremalPoints(Vector3(1, 0, 0), pointArray, pointCount, extremalPoints+0, extremalPoints+1); + computeExtremalPoints(Vector3(0, 1, 0), pointArray, pointCount, extremalPoints+2, extremalPoints+3); + computeExtremalPoints(Vector3(0, 0, 1), pointArray, pointCount, extremalPoints+4, extremalPoints+5); + + Sphere sphere = miniBall(extremalPoints, 6); + sphere.radius = computeSphereRadius(sphere.center, pointArray, pointCount); + + nvDebugCheck(allInside(sphere, pointArray, pointCount)); + + return sphere; +} + +Sphere nv::approximateSphere_EPOS14(const Vector3 * pointArray, uint pointCount) +{ + nvDebugCheck(pointArray != NULL); + nvDebugCheck(pointCount > 0); + + Vector3 extremalPoints[14]; + + // Compute 14 extremal points. + computeExtremalPoints(Vector3(1, 0, 0), pointArray, pointCount, extremalPoints+0, extremalPoints+1); + computeExtremalPoints(Vector3(0, 1, 0), pointArray, pointCount, extremalPoints+2, extremalPoints+3); + computeExtremalPoints(Vector3(0, 0, 1), pointArray, pointCount, extremalPoints+4, extremalPoints+5); + + float d = sqrtf(1.0f/3.0f); + + computeExtremalPoints(Vector3(d, d, d), pointArray, pointCount, extremalPoints+6, extremalPoints+7); + computeExtremalPoints(Vector3(-d, d, d), pointArray, pointCount, extremalPoints+8, extremalPoints+9); + computeExtremalPoints(Vector3(-d, -d, d), pointArray, pointCount, extremalPoints+10, extremalPoints+11); + computeExtremalPoints(Vector3(d, -d, d), pointArray, pointCount, extremalPoints+12, extremalPoints+13); + + + Sphere sphere = miniBall(extremalPoints, 14); + sphere.radius = computeSphereRadius(sphere.center, pointArray, pointCount); + + nvDebugCheck(allInside(sphere, pointArray, pointCount)); + + return sphere; +} + + + diff --git a/thirdparty/thekla_atlas/src/nvmath/Sphere.h b/thirdparty/thekla_atlas/src/nvmath/Sphere.h new file mode 100755 index 00000000..300731af --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Sphere.h @@ -0,0 +1,43 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_MATH_SPHERE_H +#define NV_MATH_SPHERE_H + +#include "Vector.h" + +namespace nv +{ + + class Sphere + { + public: + Sphere() {} + Sphere(Vector3::Arg center, float radius) : center(center), radius(radius) {} + + Sphere(Vector3::Arg center) : center(center), radius(0.0f) {} + Sphere(Vector3::Arg p0, Vector3::Arg p1); + Sphere(Vector3::Arg p0, Vector3::Arg p1, Vector3::Arg p2); + Sphere(Vector3::Arg p0, Vector3::Arg p1, Vector3::Arg p2, Vector3::Arg p3); + + Vector3 center; + float radius; + }; + + // Returns negative values if point is inside. + float distanceSquared(const Sphere & sphere, const Vector3 &point); + + + // Welz's algorithm. Fairly slow, recursive implementation uses large stack. + Sphere miniBall(const Vector3 * pointArray, uint pointCount); + + Sphere approximateSphere_Ritter(const Vector3 * pointArray, uint pointCount); + Sphere approximateSphere_AABB(const Vector3 * pointArray, uint pointCount); + Sphere approximateSphere_EPOS6(const Vector3 * pointArray, uint pointCount); + Sphere approximateSphere_EPOS14(const Vector3 * pointArray, uint pointCount); + + +} // nv namespace + + +#endif // NV_MATH_SPHERE_H diff --git a/thirdparty/thekla_atlas/src/nvmath/TypeSerialization.cpp b/thirdparty/thekla_atlas/src/nvmath/TypeSerialization.cpp new file mode 100755 index 00000000..72fa678f --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/TypeSerialization.cpp @@ -0,0 +1,54 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "TypeSerialization.h" + +#include "nvcore/Stream.h" + +#include "nvmath/Vector.h" +#include "nvmath/Matrix.h" +#include "nvmath/Quaternion.h" +#include "nvmath/Basis.h" +#include "nvmath/Box.h" +#include "nvmath/Plane.inl" + +using namespace nv; + +Stream & nv::operator<< (Stream & s, Vector2 & v) +{ + return s << v.x << v.y; +} + +Stream & nv::operator<< (Stream & s, Vector3 & v) +{ + return s << v.x << v.y << v.z; +} + +Stream & nv::operator<< (Stream & s, Vector4 & v) +{ + return s << v.x << v.y << v.z << v.w; +} + +Stream & nv::operator<< (Stream & s, Matrix & m) +{ + return s; +} + +Stream & nv::operator<< (Stream & s, Quaternion & q) +{ + return s << q.x << q.y << q.z << q.w; +} + +Stream & nv::operator<< (Stream & s, Basis & basis) +{ + return s << basis.tangent << basis.bitangent << basis.normal; +} + +Stream & nv::operator<< (Stream & s, Box & box) +{ + return s << box.minCorner << box.maxCorner; +} + +Stream & nv::operator<< (Stream & s, Plane & plane) +{ + return s << plane.v; +} diff --git a/thirdparty/thekla_atlas/src/nvmath/TypeSerialization.h b/thirdparty/thekla_atlas/src/nvmath/TypeSerialization.h new file mode 100755 index 00000000..32d6de82 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/TypeSerialization.h @@ -0,0 +1,35 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_MATH_TYPESERIALIZATION_H +#define NV_MATH_TYPESERIALIZATION_H + +#include "nvmath.h" + +namespace nv +{ + class Stream; + + class Vector2; + class Vector3; + class Vector4; + + class Matrix; + class Quaternion; + class Basis; + class Box; + class Plane; + + NVMATH_API Stream & operator<< (Stream & s, Vector2 & obj); + NVMATH_API Stream & operator<< (Stream & s, Vector3 & obj); + NVMATH_API Stream & operator<< (Stream & s, Vector4 & obj); + + NVMATH_API Stream & operator<< (Stream & s, Matrix & obj); + NVMATH_API Stream & operator<< (Stream & s, Quaternion & obj); + NVMATH_API Stream & operator<< (Stream & s, Basis & obj); + NVMATH_API Stream & operator<< (Stream & s, Box & obj); + NVMATH_API Stream & operator<< (Stream & s, Plane & obj); + +} // nv namespace + +#endif // NV_MATH_TYPESERIALIZATION_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Vector.cpp b/thirdparty/thekla_atlas/src/nvmath/Vector.cpp new file mode 100755 index 00000000..9122a1b0 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Vector.cpp @@ -0,0 +1,4 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "Vector.h" +#include "Vector.inl" diff --git a/thirdparty/thekla_atlas/src/nvmath/Vector.h b/thirdparty/thekla_atlas/src/nvmath/Vector.h new file mode 100755 index 00000000..9236f2a5 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Vector.h @@ -0,0 +1,151 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MATH_VECTOR_H +#define NV_MATH_VECTOR_H + +#include "nvmath.h" + +namespace nv +{ + class NVMATH_CLASS Vector2 + { + public: + typedef Vector2 const & Arg; + + Vector2(); + explicit Vector2(float f); + Vector2(float x, float y); + Vector2(Vector2::Arg v); + + //template explicit Vector2(const T & v) : x(v.x), y(v.y) {} + //template operator T() const { return T(x, y); } + + const Vector2 & operator=(Vector2::Arg v); + + const float * ptr() const; + + void set(float x, float y); + + Vector2 operator-() const; + void operator+=(Vector2::Arg v); + void operator-=(Vector2::Arg v); + void operator*=(float s); + void operator*=(Vector2::Arg v); + + friend bool operator==(Vector2::Arg a, Vector2::Arg b); + friend bool operator!=(Vector2::Arg a, Vector2::Arg b); + + union { + struct { + float x, y; + }; + float component[2]; + }; + }; + + class NVMATH_CLASS Vector3 + { + public: + typedef Vector3 const & Arg; + + Vector3(); + explicit Vector3(float x); + //explicit Vector3(int x) : x(float(x)), y(float(x)), z(float(x)) {} + Vector3(float x, float y, float z); + Vector3(Vector2::Arg v, float z); + Vector3(Vector3::Arg v); + + //template explicit Vector3(const T & v) : x(v.x), y(v.y), z(v.z) {} + //template operator T() const { return T(x, y, z); } + + const Vector3 & operator=(Vector3::Arg v); + + Vector2 xy() const; + + const float * ptr() const; + + void set(float x, float y, float z); + + Vector3 operator-() const; + void operator+=(Vector3::Arg v); + void operator-=(Vector3::Arg v); + void operator*=(float s); + void operator/=(float s); + void operator*=(Vector3::Arg v); + void operator/=(Vector3::Arg v); + + friend bool operator==(Vector3::Arg a, Vector3::Arg b); + friend bool operator!=(Vector3::Arg a, Vector3::Arg b); + + union { + struct { + float x, y, z; + }; + float component[3]; + }; + }; + + class NVMATH_CLASS Vector4 + { + public: + typedef Vector4 const & Arg; + + Vector4(); + explicit Vector4(float x); + Vector4(float x, float y, float z, float w); + Vector4(Vector2::Arg v, float z, float w); + Vector4(Vector2::Arg v, Vector2::Arg u); + Vector4(Vector3::Arg v, float w); + Vector4(Vector4::Arg v); + // Vector4(const Quaternion & v); + + //template explicit Vector4(const T & v) : x(v.x), y(v.y), z(v.z), w(v.w) {} + //template operator T() const { return T(x, y, z, w); } + + const Vector4 & operator=(Vector4::Arg v); + + Vector2 xy() const; + Vector2 zw() const; + Vector3 xyz() const; + + const float * ptr() const; + + void set(float x, float y, float z, float w); + + Vector4 operator-() const; + void operator+=(Vector4::Arg v); + void operator-=(Vector4::Arg v); + void operator*=(float s); + void operator/=(float s); + void operator*=(Vector4::Arg v); + void operator/=(Vector4::Arg v); + + friend bool operator==(Vector4::Arg a, Vector4::Arg b); + friend bool operator!=(Vector4::Arg a, Vector4::Arg b); + + union { + struct { + float x, y, z, w; + }; + float component[4]; + }; + }; + +} // nv namespace + +// If we had these functions, they would be ambiguous, the compiler would not know which one to pick: +//template Vector2 to(const T & v) { return Vector2(v.x, v.y); } +//template Vector3 to(const T & v) { return Vector3(v.x, v.y, v.z); } +//template Vector4 to(const T & v) { return Vector4(v.x, v.y, v.z, v.z); } + +// We could use a cast operator so that we could infer the expected type, but that doesn't work the same way in all compilers and produces horrible error messages. + +// Instead we simply have explicit casts: +template T to(const nv::Vector2 & v) { NV_COMPILER_CHECK(sizeof(T) == sizeof(nv::Vector2)); return T(v.x, v.y); } +template T to(const nv::Vector3 & v) { NV_COMPILER_CHECK(sizeof(T) == sizeof(nv::Vector3)); return T(v.x, v.y, v.z); } +template T to(const nv::Vector4 & v) { NV_COMPILER_CHECK(sizeof(T) == sizeof(nv::Vector4)); return T(v.x, v.y, v.z, v.w); } + +#include "Vector.inl" + +#endif // NV_MATH_VECTOR_H diff --git a/thirdparty/thekla_atlas/src/nvmath/Vector.inl b/thirdparty/thekla_atlas/src/nvmath/Vector.inl new file mode 100755 index 00000000..bcaec7bf --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/Vector.inl @@ -0,0 +1,919 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MATH_VECTOR_INL +#define NV_MATH_VECTOR_INL + +#include "Vector.h" +#include "nvcore/Utils.h" // min, max +#include "nvcore/Hash.h" // hash + +namespace nv +{ + + // Helpers to convert vector types. Assume T has x,y members and 2 argument constructor. + //template T to(Vector2::Arg v) { return T(v.x, v.y); } + + // Helpers to convert vector types. Assume T has x,y,z members and 3 argument constructor. + //template T to(Vector3::Arg v) { return T(v.x, v.y, v.z); } + + // Helpers to convert vector types. Assume T has x,y,z members and 3 argument constructor. + //template T to(Vector4::Arg v) { return T(v.x, v.y, v.z, v.w); } + + + // Vector2 + inline Vector2::Vector2() {} + inline Vector2::Vector2(float f) : x(f), y(f) {} + inline Vector2::Vector2(float x, float y) : x(x), y(y) {} + inline Vector2::Vector2(Vector2::Arg v) : x(v.x), y(v.y) {} + + inline const Vector2 & Vector2::operator=(Vector2::Arg v) + { + x = v.x; + y = v.y; + return *this; + } + + inline const float * Vector2::ptr() const + { + return &x; + } + + inline void Vector2::set(float x, float y) + { + this->x = x; + this->y = y; + } + + inline Vector2 Vector2::operator-() const + { + return Vector2(-x, -y); + } + + inline void Vector2::operator+=(Vector2::Arg v) + { + x += v.x; + y += v.y; + } + + inline void Vector2::operator-=(Vector2::Arg v) + { + x -= v.x; + y -= v.y; + } + + inline void Vector2::operator*=(float s) + { + x *= s; + y *= s; + } + + inline void Vector2::operator*=(Vector2::Arg v) + { + x *= v.x; + y *= v.y; + } + + inline bool operator==(Vector2::Arg a, Vector2::Arg b) + { + return a.x == b.x && a.y == b.y; + } + inline bool operator!=(Vector2::Arg a, Vector2::Arg b) + { + return a.x != b.x || a.y != b.y; + } + + + // Vector3 + inline Vector3::Vector3() {} + inline Vector3::Vector3(float f) : x(f), y(f), z(f) {} + inline Vector3::Vector3(float x, float y, float z) : x(x), y(y), z(z) {} + inline Vector3::Vector3(Vector2::Arg v, float z) : x(v.x), y(v.y), z(z) {} + inline Vector3::Vector3(Vector3::Arg v) : x(v.x), y(v.y), z(v.z) {} + + inline const Vector3 & Vector3::operator=(Vector3::Arg v) + { + x = v.x; + y = v.y; + z = v.z; + return *this; + } + + + inline Vector2 Vector3::xy() const + { + return Vector2(x, y); + } + + inline const float * Vector3::ptr() const + { + return &x; + } + + inline void Vector3::set(float x, float y, float z) + { + this->x = x; + this->y = y; + this->z = z; + } + + inline Vector3 Vector3::operator-() const + { + return Vector3(-x, -y, -z); + } + + inline void Vector3::operator+=(Vector3::Arg v) + { + x += v.x; + y += v.y; + z += v.z; + } + + inline void Vector3::operator-=(Vector3::Arg v) + { + x -= v.x; + y -= v.y; + z -= v.z; + } + + inline void Vector3::operator*=(float s) + { + x *= s; + y *= s; + z *= s; + } + + inline void Vector3::operator/=(float s) + { + float is = 1.0f / s; + x *= is; + y *= is; + z *= is; + } + + inline void Vector3::operator*=(Vector3::Arg v) + { + x *= v.x; + y *= v.y; + z *= v.z; + } + + inline void Vector3::operator/=(Vector3::Arg v) + { + x /= v.x; + y /= v.y; + z /= v.z; + } + + inline bool operator==(Vector3::Arg a, Vector3::Arg b) + { + return a.x == b.x && a.y == b.y && a.z == b.z; + } + inline bool operator!=(Vector3::Arg a, Vector3::Arg b) + { + return a.x != b.x || a.y != b.y || a.z != b.z; + } + + + // Vector4 + inline Vector4::Vector4() {} + inline Vector4::Vector4(float f) : x(f), y(f), z(f), w(f) {} + inline Vector4::Vector4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} + inline Vector4::Vector4(Vector2::Arg v, float z, float w) : x(v.x), y(v.y), z(z), w(w) {} + inline Vector4::Vector4(Vector2::Arg v, Vector2::Arg u) : x(v.x), y(v.y), z(u.x), w(u.y) {} + inline Vector4::Vector4(Vector3::Arg v, float w) : x(v.x), y(v.y), z(v.z), w(w) {} + inline Vector4::Vector4(Vector4::Arg v) : x(v.x), y(v.y), z(v.z), w(v.w) {} + + inline const Vector4 & Vector4::operator=(const Vector4 & v) + { + x = v.x; + y = v.y; + z = v.z; + w = v.w; + return *this; + } + + inline Vector2 Vector4::xy() const + { + return Vector2(x, y); + } + + inline Vector2 Vector4::zw() const + { + return Vector2(z, w); + } + + inline Vector3 Vector4::xyz() const + { + return Vector3(x, y, z); + } + + inline const float * Vector4::ptr() const + { + return &x; + } + + inline void Vector4::set(float x, float y, float z, float w) + { + this->x = x; + this->y = y; + this->z = z; + this->w = w; + } + + inline Vector4 Vector4::operator-() const + { + return Vector4(-x, -y, -z, -w); + } + + inline void Vector4::operator+=(Vector4::Arg v) + { + x += v.x; + y += v.y; + z += v.z; + w += v.w; + } + + inline void Vector4::operator-=(Vector4::Arg v) + { + x -= v.x; + y -= v.y; + z -= v.z; + w -= v.w; + } + + inline void Vector4::operator*=(float s) + { + x *= s; + y *= s; + z *= s; + w *= s; + } + + inline void Vector4::operator/=(float s) + { + x /= s; + y /= s; + z /= s; + w /= s; + } + + inline void Vector4::operator*=(Vector4::Arg v) + { + x *= v.x; + y *= v.y; + z *= v.z; + w *= v.w; + } + + inline void Vector4::operator/=(Vector4::Arg v) + { + x /= v.x; + y /= v.y; + z /= v.z; + w /= v.w; + } + + inline bool operator==(Vector4::Arg a, Vector4::Arg b) + { + return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w; + } + inline bool operator!=(Vector4::Arg a, Vector4::Arg b) + { + return a.x != b.x || a.y != b.y || a.z != b.z || a.w != b.w; + } + + + + // Functions + + + // Vector2 + + inline Vector2 add(Vector2::Arg a, Vector2::Arg b) + { + return Vector2(a.x + b.x, a.y + b.y); + } + inline Vector2 operator+(Vector2::Arg a, Vector2::Arg b) + { + return add(a, b); + } + + inline Vector2 sub(Vector2::Arg a, Vector2::Arg b) + { + return Vector2(a.x - b.x, a.y - b.y); + } + inline Vector2 operator-(Vector2::Arg a, Vector2::Arg b) + { + return sub(a, b); + } + + inline Vector2 scale(Vector2::Arg v, float s) + { + return Vector2(v.x * s, v.y * s); + } + + inline Vector2 scale(Vector2::Arg v, Vector2::Arg s) + { + return Vector2(v.x * s.x, v.y * s.y); + } + + inline Vector2 operator*(Vector2::Arg v, float s) + { + return scale(v, s); + } + + inline Vector2 operator*(Vector2::Arg v1, Vector2::Arg v2) + { + return Vector2(v1.x*v2.x, v1.y*v2.y); + } + + inline Vector2 operator*(float s, Vector2::Arg v) + { + return scale(v, s); + } + + inline Vector2 operator/(Vector2::Arg v, float s) + { + return scale(v, 1.0f/s); + } + + inline Vector2 lerp(Vector2::Arg v1, Vector2::Arg v2, float t) + { + const float s = 1.0f - t; + return Vector2(v1.x * s + t * v2.x, v1.y * s + t * v2.y); + } + + inline float dot(Vector2::Arg a, Vector2::Arg b) + { + return a.x * b.x + a.y * b.y; + } + + inline float lengthSquared(Vector2::Arg v) + { + return v.x * v.x + v.y * v.y; + } + + inline float length(Vector2::Arg v) + { + return sqrtf(lengthSquared(v)); + } + + inline float distance(Vector2::Arg a, Vector2::Arg b) + { + return length(a - b); + } + + inline float inverseLength(Vector2::Arg v) + { + return 1.0f / sqrtf(lengthSquared(v)); + } + + inline bool isNormalized(Vector2::Arg v, float epsilon = NV_NORMAL_EPSILON) + { + return equal(length(v), 1, epsilon); + } + + inline Vector2 normalize(Vector2::Arg v, float epsilon = NV_EPSILON) + { + float l = length(v); + nvDebugCheck(!isZero(l, epsilon)); + Vector2 n = scale(v, 1.0f / l); + nvDebugCheck(isNormalized(n)); + return n; + } + + inline Vector2 normalizeSafe(Vector2::Arg v, Vector2::Arg fallback, float epsilon = NV_EPSILON) + { + float l = length(v); + if (isZero(l, epsilon)) { + return fallback; + } + return scale(v, 1.0f / l); + } + + // Safe, branchless normalization from Andy Firth. All error checking ommitted. + // http://altdevblogaday.com/2011/08/21/practical-flt-point-tricks/ + inline Vector2 normalizeFast(Vector2::Arg v) + { + const float very_small_float = 1.0e-037f; + float l = very_small_float + length(v); + return scale(v, 1.0f / l); + } + + inline bool equal(Vector2::Arg v1, Vector2::Arg v2, float epsilon = NV_EPSILON) + { + return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon); + } + + inline Vector2 min(Vector2::Arg a, Vector2::Arg b) + { + return Vector2(min(a.x, b.x), min(a.y, b.y)); + } + + inline Vector2 max(Vector2::Arg a, Vector2::Arg b) + { + return Vector2(max(a.x, b.x), max(a.y, b.y)); + } + + inline Vector2 clamp(Vector2::Arg v, float min, float max) + { + return Vector2(clamp(v.x, min, max), clamp(v.y, min, max)); + } + + inline Vector2 saturate(Vector2::Arg v) + { + return Vector2(saturate(v.x), saturate(v.y)); + } + + inline bool isFinite(Vector2::Arg v) + { + return isFinite(v.x) && isFinite(v.y); + } + + inline Vector2 validate(Vector2::Arg v, Vector2::Arg fallback = Vector2(0.0f)) + { + if (!isFinite(v)) return fallback; + Vector2 vf = v; + nv::floatCleanup(vf.component, 2); + return vf; + } + + // Note, this is the area scaled by 2! + inline float triangleArea(Vector2::Arg v0, Vector2::Arg v1) + { + return (v0.x * v1.y - v0.y * v1.x); // * 0.5f; + } + inline float triangleArea(Vector2::Arg a, Vector2::Arg b, Vector2::Arg c) + { + // IC: While it may be appealing to use the following expression: + //return (c.x * a.y + a.x * b.y + b.x * c.y - b.x * a.y - c.x * b.y - a.x * c.y); // * 0.5f; + + // That's actually a terrible idea. Small triangles far from the origin can end up producing fairly large floating point + // numbers and the results becomes very unstable and dependent on the order of the factors. + + // Instead, it's preferable to subtract the vertices first, and multiply the resulting small values together. The result + // in this case is always much more accurate (as long as the triangle is small) and less dependent of the location of + // the triangle. + + //return ((a.x - c.x) * (b.y - c.y) - (a.y - c.y) * (b.x - c.x)); // * 0.5f; + return triangleArea(a-c, b-c); + } + + + template <> + inline uint hash(const Vector2 & v, uint h) + { + return sdbmFloatHash(v.component, 2, h); + } + + + + // Vector3 + + inline Vector3 add(Vector3::Arg a, Vector3::Arg b) + { + return Vector3(a.x + b.x, a.y + b.y, a.z + b.z); + } + inline Vector3 add(Vector3::Arg a, float b) + { + return Vector3(a.x + b, a.y + b, a.z + b); + } + inline Vector3 operator+(Vector3::Arg a, Vector3::Arg b) + { + return add(a, b); + } + inline Vector3 operator+(Vector3::Arg a, float b) + { + return add(a, b); + } + + inline Vector3 sub(Vector3::Arg a, Vector3::Arg b) + { + return Vector3(a.x - b.x, a.y - b.y, a.z - b.z); + } + inline Vector3 sub(Vector3::Arg a, float b) + { + return Vector3(a.x - b, a.y - b, a.z - b); + } + inline Vector3 operator-(Vector3::Arg a, Vector3::Arg b) + { + return sub(a, b); + } + inline Vector3 operator-(Vector3::Arg a, float b) + { + return sub(a, b); + } + + inline Vector3 cross(Vector3::Arg a, Vector3::Arg b) + { + return Vector3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); + } + + inline Vector3 scale(Vector3::Arg v, float s) + { + return Vector3(v.x * s, v.y * s, v.z * s); + } + + inline Vector3 scale(Vector3::Arg v, Vector3::Arg s) + { + return Vector3(v.x * s.x, v.y * s.y, v.z * s.z); + } + + inline Vector3 operator*(Vector3::Arg v, float s) + { + return scale(v, s); + } + + inline Vector3 operator*(float s, Vector3::Arg v) + { + return scale(v, s); + } + + inline Vector3 operator*(Vector3::Arg v, Vector3::Arg s) + { + return scale(v, s); + } + + inline Vector3 operator/(Vector3::Arg v, float s) + { + return scale(v, 1.0f/s); + } + + /*inline Vector3 add_scaled(Vector3::Arg a, Vector3::Arg b, float s) + { + return Vector3(a.x + b.x * s, a.y + b.y * s, a.z + b.z * s); + }*/ + + inline Vector3 lerp(Vector3::Arg v1, Vector3::Arg v2, float t) + { + const float s = 1.0f - t; + return Vector3(v1.x * s + t * v2.x, v1.y * s + t * v2.y, v1.z * s + t * v2.z); + } + + inline float dot(Vector3::Arg a, Vector3::Arg b) + { + return a.x * b.x + a.y * b.y + a.z * b.z; + } + + inline float lengthSquared(Vector3::Arg v) + { + return v.x * v.x + v.y * v.y + v.z * v.z; + } + + inline float length(Vector3::Arg v) + { + return sqrtf(lengthSquared(v)); + } + + inline float distance(Vector3::Arg a, Vector3::Arg b) + { + return length(a - b); + } + + inline float distanceSquared(Vector3::Arg a, Vector3::Arg b) + { + return lengthSquared(a - b); + } + + inline float inverseLength(Vector3::Arg v) + { + return 1.0f / sqrtf(lengthSquared(v)); + } + + inline bool isNormalized(Vector3::Arg v, float epsilon = NV_NORMAL_EPSILON) + { + return equal(length(v), 1, epsilon); + } + + inline Vector3 normalize(Vector3::Arg v, float epsilon = NV_EPSILON) + { + float l = length(v); + nvDebugCheck(!isZero(l, epsilon)); + Vector3 n = scale(v, 1.0f / l); + nvDebugCheck(isNormalized(n)); + return n; + } + + inline Vector3 normalizeSafe(Vector3::Arg v, Vector3::Arg fallback, float epsilon = NV_EPSILON) + { + float l = length(v); + if (isZero(l, epsilon)) { + return fallback; + } + return scale(v, 1.0f / l); + } + + // Safe, branchless normalization from Andy Firth. All error checking ommitted. + // http://altdevblogaday.com/2011/08/21/practical-flt-point-tricks/ + inline Vector3 normalizeFast(Vector3::Arg v) + { + const float very_small_float = 1.0e-037f; + float l = very_small_float + length(v); + return scale(v, 1.0f / l); + } + + inline bool equal(Vector3::Arg v1, Vector3::Arg v2, float epsilon = NV_EPSILON) + { + return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon) && equal(v1.z, v2.z, epsilon); + } + + inline Vector3 min(Vector3::Arg a, Vector3::Arg b) + { + return Vector3(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z)); + } + + inline Vector3 max(Vector3::Arg a, Vector3::Arg b) + { + return Vector3(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z)); + } + + inline Vector3 clamp(Vector3::Arg v, float min, float max) + { + return Vector3(clamp(v.x, min, max), clamp(v.y, min, max), clamp(v.z, min, max)); + } + + inline Vector3 saturate(Vector3::Arg v) + { + return Vector3(saturate(v.x), saturate(v.y), saturate(v.z)); + } + + inline Vector3 floor(Vector3::Arg v) + { + return Vector3(floorf(v.x), floorf(v.y), floorf(v.z)); + } + + inline Vector3 ceil(Vector3::Arg v) + { + return Vector3(ceilf(v.x), ceilf(v.y), ceilf(v.z)); + } + + inline bool isFinite(Vector3::Arg v) + { + return isFinite(v.x) && isFinite(v.y) && isFinite(v.z); + } + + inline Vector3 validate(Vector3::Arg v, Vector3::Arg fallback = Vector3(0.0f)) + { + if (!isFinite(v)) return fallback; + Vector3 vf = v; + nv::floatCleanup(vf.component, 3); + return vf; + } + + inline Vector3 reflect(Vector3::Arg v, Vector3::Arg n) + { + return v - (2 * dot(v, n)) * n; + } + + template <> + inline uint hash(const Vector3 & v, uint h) + { + return sdbmFloatHash(v.component, 3, h); + } + + + // Vector4 + + inline Vector4 add(Vector4::Arg a, Vector4::Arg b) + { + return Vector4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); + } + inline Vector4 operator+(Vector4::Arg a, Vector4::Arg b) + { + return add(a, b); + } + + inline Vector4 sub(Vector4::Arg a, Vector4::Arg b) + { + return Vector4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); + } + inline Vector4 operator-(Vector4::Arg a, Vector4::Arg b) + { + return sub(a, b); + } + + inline Vector4 scale(Vector4::Arg v, float s) + { + return Vector4(v.x * s, v.y * s, v.z * s, v.w * s); + } + + inline Vector4 scale(Vector4::Arg v, Vector4::Arg s) + { + return Vector4(v.x * s.x, v.y * s.y, v.z * s.z, v.w * s.w); + } + + inline Vector4 operator*(Vector4::Arg v, float s) + { + return scale(v, s); + } + + inline Vector4 operator*(float s, Vector4::Arg v) + { + return scale(v, s); + } + + inline Vector4 operator*(Vector4::Arg v, Vector4::Arg s) + { + return scale(v, s); + } + + inline Vector4 operator/(Vector4::Arg v, float s) + { + return scale(v, 1.0f/s); + } + + /*inline Vector4 add_scaled(Vector4::Arg a, Vector4::Arg b, float s) + { + return Vector4(a.x + b.x * s, a.y + b.y * s, a.z + b.z * s, a.w + b.w * s); + }*/ + + inline Vector4 lerp(Vector4::Arg v1, Vector4::Arg v2, float t) + { + const float s = 1.0f - t; + return Vector4(v1.x * s + t * v2.x, v1.y * s + t * v2.y, v1.z * s + t * v2.z, v1.w * s + t * v2.w); + } + + inline float dot(Vector4::Arg a, Vector4::Arg b) + { + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; + } + + inline float lengthSquared(Vector4::Arg v) + { + return v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w; + } + + inline float length(Vector4::Arg v) + { + return sqrtf(lengthSquared(v)); + } + + inline float inverseLength(Vector4::Arg v) + { + return 1.0f / sqrtf(lengthSquared(v)); + } + + inline bool isNormalized(Vector4::Arg v, float epsilon = NV_NORMAL_EPSILON) + { + return equal(length(v), 1, epsilon); + } + + inline Vector4 normalize(Vector4::Arg v, float epsilon = NV_EPSILON) + { + float l = length(v); + nvDebugCheck(!isZero(l, epsilon)); + Vector4 n = scale(v, 1.0f / l); + nvDebugCheck(isNormalized(n)); + return n; + } + + inline Vector4 normalizeSafe(Vector4::Arg v, Vector4::Arg fallback, float epsilon = NV_EPSILON) + { + float l = length(v); + if (isZero(l, epsilon)) { + return fallback; + } + return scale(v, 1.0f / l); + } + + // Safe, branchless normalization from Andy Firth. All error checking ommitted. + // http://altdevblogaday.com/2011/08/21/practical-flt-point-tricks/ + inline Vector4 normalizeFast(Vector4::Arg v) + { + const float very_small_float = 1.0e-037f; + float l = very_small_float + length(v); + return scale(v, 1.0f / l); + } + + inline bool equal(Vector4::Arg v1, Vector4::Arg v2, float epsilon = NV_EPSILON) + { + return equal(v1.x, v2.x, epsilon) && equal(v1.y, v2.y, epsilon) && equal(v1.z, v2.z, epsilon) && equal(v1.w, v2.w, epsilon); + } + + inline Vector4 min(Vector4::Arg a, Vector4::Arg b) + { + return Vector4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)); + } + + inline Vector4 max(Vector4::Arg a, Vector4::Arg b) + { + return Vector4(max(a.x, b.x), max(a.y, b.y), max(a.z, b.z), max(a.w, b.w)); + } + + inline Vector4 clamp(Vector4::Arg v, float min, float max) + { + return Vector4(clamp(v.x, min, max), clamp(v.y, min, max), clamp(v.z, min, max), clamp(v.w, min, max)); + } + + inline Vector4 saturate(Vector4::Arg v) + { + return Vector4(saturate(v.x), saturate(v.y), saturate(v.z), saturate(v.w)); + } + + inline bool isFinite(Vector4::Arg v) + { + return isFinite(v.x) && isFinite(v.y) && isFinite(v.z) && isFinite(v.w); + } + + inline Vector4 validate(Vector4::Arg v, Vector4::Arg fallback = Vector4(0.0f)) + { + if (!isFinite(v)) return fallback; + Vector4 vf = v; + nv::floatCleanup(vf.component, 4); + return vf; + } + + template <> + inline uint hash(const Vector4 & v, uint h) + { + return sdbmFloatHash(v.component, 4, h); + } + + +#if NV_OS_IOS // LLVM is not happy with implicit conversion of immediate constants to float + + //int: + + inline Vector2 scale(Vector2::Arg v, int s) + { + return Vector2(v.x * s, v.y * s); + } + + inline Vector2 operator*(Vector2::Arg v, int s) + { + return scale(v, s); + } + + inline Vector2 operator*(int s, Vector2::Arg v) + { + return scale(v, s); + } + + inline Vector2 operator/(Vector2::Arg v, int s) + { + return scale(v, 1.0f/s); + } + + inline Vector3 scale(Vector3::Arg v, int s) + { + return Vector3(v.x * s, v.y * s, v.z * s); + } + + inline Vector3 operator*(Vector3::Arg v, int s) + { + return scale(v, s); + } + + inline Vector3 operator*(int s, Vector3::Arg v) + { + return scale(v, s); + } + + inline Vector3 operator/(Vector3::Arg v, int s) + { + return scale(v, 1.0f/s); + } + + inline Vector4 scale(Vector4::Arg v, int s) + { + return Vector4(v.x * s, v.y * s, v.z * s, v.w * s); + } + + inline Vector4 operator*(Vector4::Arg v, int s) + { + return scale(v, s); + } + + inline Vector4 operator*(int s, Vector4::Arg v) + { + return scale(v, s); + } + + inline Vector4 operator/(Vector4::Arg v, int s) + { + return scale(v, 1.0f/s); + } + + //double: + + inline Vector3 operator*(Vector3::Arg v, double s) + { + return scale(v, (float)s); + } + + inline Vector3 operator*(double s, Vector3::Arg v) + { + return scale(v, (float)s); + } + + inline Vector3 operator/(Vector3::Arg v, double s) + { + return scale(v, 1.f/((float)s)); + } + +#endif //NV_OS_IOS + +} // nv namespace + +#endif // NV_MATH_VECTOR_INL diff --git a/thirdparty/thekla_atlas/src/nvmath/ftoi.h b/thirdparty/thekla_atlas/src/nvmath/ftoi.h new file mode 100755 index 00000000..5a1c8c58 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/ftoi.h @@ -0,0 +1,253 @@ +// This code is in the public domain -- castano@gmail.com + +#pragma once +#ifndef NV_MATH_FTOI_H +#define NV_MATH_FTOI_H + +#include "nvmath/nvmath.h" + +#include + +namespace nv +{ + // Optimized float to int conversions. See: + // http://cbloomrants.blogspot.com/2009/01/01-17-09-float-to-int.html + // http://www.stereopsis.com/sree/fpu2006.html + // http://assemblyrequired.crashworks.org/2009/01/12/why-you-should-never-cast-floats-to-ints/ + // http://chrishecker.com/Miscellaneous_Technical_Articles#Floating_Point + + + union DoubleAnd64 { + uint64 i; + double d; + }; + + static const double floatutil_xs_doublemagic = (6755399441055744.0); // 2^52 * 1.5 + static const double floatutil_xs_doublemagicdelta = (1.5e-8); // almost .5f = .5f + 1e^(number of exp bit) + static const double floatutil_xs_doublemagicroundeps = (0.5f - floatutil_xs_doublemagicdelta); // almost .5f = .5f - 1e^(number of exp bit) + + NV_FORCEINLINE int ftoi_round_xs(double val, double magic) { +#if 1 + DoubleAnd64 dunion; + dunion.d = val + magic; + return (int32) dunion.i; // just cast to grab the bottom bits +#else + val += magic; + return ((int*)&val)[0]; // @@ Assumes little endian. +#endif + } + + NV_FORCEINLINE int ftoi_round_xs(float val) { + return ftoi_round_xs(val, floatutil_xs_doublemagic); + } + + NV_FORCEINLINE int ftoi_floor_xs(float val) { + return ftoi_round_xs(val - floatutil_xs_doublemagicroundeps, floatutil_xs_doublemagic); + } + + NV_FORCEINLINE int ftoi_ceil_xs(float val) { + return ftoi_round_xs(val + floatutil_xs_doublemagicroundeps, floatutil_xs_doublemagic); + } + + NV_FORCEINLINE int ftoi_trunc_xs(float val) { + return (val<0) ? ftoi_ceil_xs(val) : ftoi_floor_xs(val); + } + + +#if NV_USE_SSE + + NV_FORCEINLINE int ftoi_round_sse(float f) { + return _mm_cvt_ss2si(_mm_set_ss(f)); + } + + NV_FORCEINLINE int ftoi_trunc_sse(float f) { + return _mm_cvtt_ss2si(_mm_set_ss(f)); + } + + NV_FORCEINLINE int ftoi_round(float val) { + return ftoi_round_sse(val); + } + + NV_FORCEINLINE int ftoi_trunc(float f) { + return ftoi_trunc_sse(f); + } + + // We can probably do better than this. See for example: + // http://dss.stephanierct.com/DevBlog/?p=8 + NV_FORCEINLINE int ftoi_floor(float val) { + return ftoi_round(floorf(val)); + } + + NV_FORCEINLINE int ftoi_ceil(float val) { + return ftoi_round(ceilf(val)); + } + +#else + + // In theory this should work with any double floating point math implementation, but it appears that MSVC produces incorrect code + // when SSE2 is targeted and fast math is enabled (/arch:SSE2 & /fp:fast). These problems go away with /fp:precise, which is the default mode. + + NV_FORCEINLINE int ftoi_round(float val) { + return ftoi_round_xs(val); + } + + NV_FORCEINLINE int ftoi_floor(float val) { + return ftoi_floor_xs(val); + } + + NV_FORCEINLINE int ftoi_ceil(float val) { + return ftoi_ceil_xs(val); + } + + NV_FORCEINLINE int ftoi_trunc(float f) { + return ftoi_trunc_xs(f); + } + +#endif + + + inline void test_ftoi() { + + // Round to nearest integer. + nvCheck(ftoi_round(0.1f) == 0); + nvCheck(ftoi_round(0.6f) == 1); + nvCheck(ftoi_round(-0.2f) == 0); + nvCheck(ftoi_round(-0.7f) == -1); + nvCheck(ftoi_round(10.1f) == 10); + nvCheck(ftoi_round(10.6f) == 11); + nvCheck(ftoi_round(-90.1f) == -90); + nvCheck(ftoi_round(-90.6f) == -91); + + nvCheck(ftoi_round(0) == 0); + nvCheck(ftoi_round(1) == 1); + nvCheck(ftoi_round(-1) == -1); + + nvCheck(ftoi_round(0.5f) == 0); // How are midpoints rounded? Bankers rounding. + nvCheck(ftoi_round(1.5f) == 2); + nvCheck(ftoi_round(2.5f) == 2); + nvCheck(ftoi_round(3.5f) == 4); + nvCheck(ftoi_round(4.5f) == 4); + nvCheck(ftoi_round(-0.5f) == 0); + nvCheck(ftoi_round(-1.5f) == -2); + + + // Truncation (round down if > 0, round up if < 0). + nvCheck(ftoi_trunc(0.1f) == 0); + nvCheck(ftoi_trunc(0.6f) == 0); + nvCheck(ftoi_trunc(-0.2f) == 0); + nvCheck(ftoi_trunc(-0.7f) == 0); // @@ When using /arch:SSE2 in Win32, msvc produce wrong code for this one. It is skipping the addition. + nvCheck(ftoi_trunc(1.99f) == 1); + nvCheck(ftoi_trunc(-1.2f) == -1); + + // Floor (round down). + nvCheck(ftoi_floor(0.1f) == 0); + nvCheck(ftoi_floor(0.6f) == 0); + nvCheck(ftoi_floor(-0.2f) == -1); + nvCheck(ftoi_floor(-0.7f) == -1); + nvCheck(ftoi_floor(1.99f) == 1); + nvCheck(ftoi_floor(-1.2f) == -2); + + nvCheck(ftoi_floor(0) == 0); + nvCheck(ftoi_floor(1) == 1); + nvCheck(ftoi_floor(-1) == -1); + nvCheck(ftoi_floor(2) == 2); + nvCheck(ftoi_floor(-2) == -2); + + // Ceil (round up). + nvCheck(ftoi_ceil(0.1f) == 1); + nvCheck(ftoi_ceil(0.6f) == 1); + nvCheck(ftoi_ceil(-0.2f) == 0); + nvCheck(ftoi_ceil(-0.7f) == 0); + nvCheck(ftoi_ceil(1.99f) == 2); + nvCheck(ftoi_ceil(-1.2f) == -1); + + nvCheck(ftoi_ceil(0) == 0); + nvCheck(ftoi_ceil(1) == 1); + nvCheck(ftoi_ceil(-1) == -1); + nvCheck(ftoi_ceil(2) == 2); + nvCheck(ftoi_ceil(-2) == -2); + } + + + + + + // Safe versions using standard casts. + + inline int iround(float f) + { + return ftoi_round(f); + //return int(floorf(f + 0.5f)); + } + + inline int iround(double f) + { + return int(::floor(f + 0.5)); + } + + inline int ifloor(float f) + { + return ftoi_floor(f); + //return int(floorf(f)); + } + + inline int iceil(float f) + { + return int(ceilf(f)); + } + + + + // I'm always confused about which quantizer to use. I think we should choose a quantizer based on how the values are expanded later and this is generally using the 'exact endpoints' rule. + // Some notes from cbloom: http://cbloomrants.blogspot.com/2011/07/07-26-11-pixel-int-to-float-options.html + + // Quantize a float in the [0,1] range, using exact end points or uniform bins. + inline float quantizeFloat(float x, uint bits, bool exactEndPoints = true) { + nvDebugCheck(bits <= 16); + + float range = float(1 << bits); + if (exactEndPoints) { + return floorf(x * (range-1) + 0.5f) / (range-1); + } + else { + return (floorf(x * range) + 0.5f) / range; + } + } + + + // This is the most common rounding mode: + // + // 0 1 2 3 + // |___|_______|_______|___| + // 0 1 + // + // You get that if you take the unit floating point number multiply by 'N-1' and round to nearest. That is, `i = round(f * (N-1))`. + // You reconstruct the original float dividing by 'N-1': `f = i / (N-1)` + + + // 0 1 2 3 + // |_____|_____|_____|_____| + // 0 1 + + /*enum BinningMode { + RoundMode_ExactEndPoints, + RoundMode_UniformBins, + };*/ + + template + inline uint unitFloatToFixed(float f) { + return ftoi_round(f * ((1<(f); + } + + inline uint16 unitFloatToFixed16(float f) { + return (uint16)unitFloatToFixed<16>(f); + } + + +} // nv + +#endif // NV_MATH_FTOI_H diff --git a/thirdparty/thekla_atlas/src/nvmath/nvmath.h b/thirdparty/thekla_atlas/src/nvmath/nvmath.h new file mode 100755 index 00000000..d07300ee --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmath/nvmath.h @@ -0,0 +1,334 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MATH_H +#define NV_MATH_H + +#include "nvcore/nvcore.h" +#include "nvcore/Debug.h" // nvDebugCheck +#include "nvcore/Utils.h" // max, clamp + +#include + +#if NV_OS_WIN32 || NV_OS_XBOX || NV_OS_DURANGO +#include // finite, isnan +#endif + + +// Function linkage +#if NVMATH_SHARED +#ifdef NVMATH_EXPORTS +#define NVMATH_API DLL_EXPORT +#define NVMATH_CLASS DLL_EXPORT_CLASS +#else +#define NVMATH_API DLL_IMPORT +#define NVMATH_CLASS DLL_IMPORT +#endif +#else // NVMATH_SHARED +#define NVMATH_API +#define NVMATH_CLASS +#endif // NVMATH_SHARED + +// Set some reasonable defaults. +#ifndef NV_USE_ALTIVEC +# define NV_USE_ALTIVEC NV_CPU_PPC +//# define NV_USE_ALTIVEC defined(__VEC__) +#endif + +#ifndef NV_USE_SSE + // 1=SSE, 2=SSE2 +# if NV_CPU_X86_64 + // x64 always supports at least SSE2 +# define NV_USE_SSE 2 +# elif NV_CC_MSVC && defined(_M_IX86_FP) + // Also on x86 with the /arch:SSE flag in MSVC. +# define NV_USE_SSE _M_IX86_FP +# elif defined(__SSE2__) +# define NV_USE_SSE 2 +# elif defined(__SSE__) +# define NV_USE_SSE 1 +# else + // Otherwise we assume no SSE. +# define NV_USE_SSE 0 +# endif +#endif + +#if NV_USE_SSE + #include +#endif + +// Internally set NV_USE_SIMD when either altivec or sse is available. +#if NV_USE_ALTIVEC && NV_USE_SSE +# error "Cannot enable both altivec and sse!" +#endif + + +#ifndef PI +#define PI float(3.1415926535897932384626433833) +#endif + +#define NV_EPSILON (0.0001f) +#define NV_NORMAL_EPSILON (0.001f) + +/* +#define SQ(r) ((r)*(r)) + +#define SIGN_BITMASK 0x80000000 + +/// Integer representation of a floating-point value. +#define IR(x) ((uint32 &)(x)) + +/// Absolute integer representation of a floating-point value +#define AIR(x) (IR(x) & 0x7fffffff) + +/// Floating-point representation of an integer value. +#define FR(x) ((float&)(x)) + +/// Integer-based comparison of a floating point value. +/// Don't use it blindly, it can be faster or slower than the FPU comparison, depends on the context. +#define IS_NEGATIVE_FLOAT(x) (IR(x)&SIGN_BITMASK) +*/ + +extern "C" inline double sqrt_assert(const double f) +{ + nvDebugCheck(f >= 0.0f); + return sqrt(f); +} + +inline float sqrtf_assert(const float f) +{ + nvDebugCheck(f >= 0.0f); + return sqrtf(f); +} + +extern "C" inline double acos_assert(const double f) +{ + nvDebugCheck(f >= -1.0f && f <= 1.0f); + return acos(f); +} + +inline float acosf_assert(const float f) +{ + nvDebugCheck(f >= -1.0f && f <= 1.0f); + return acosf(f); +} + +extern "C" inline double asin_assert(const double f) +{ + nvDebugCheck(f >= -1.0f && f <= 1.0f); + return asin(f); +} + +inline float asinf_assert(const float f) +{ + nvDebugCheck(f >= -1.0f && f <= 1.0f); + return asinf(f); +} + +// Replace default functions with asserting ones. +#if !NV_CC_MSVC || (NV_CC_MSVC && (_MSC_VER < 1700)) // IC: Apparently this was causing problems in Visual Studio 2012. See Issue 194: https://code.google.com/p/nvidia-texture-tools/issues/detail?id=194 +#define sqrt sqrt_assert +#define sqrtf sqrtf_assert +#define acos acos_assert +#define acosf acosf_assert +#define asin asin_assert +#define asinf asinf_assert +#endif + +#if NV_CC_MSVC +NV_FORCEINLINE float log2f(float x) +{ + nvCheck(x >= 0); + return logf(x) / logf(2.0f); +} +NV_FORCEINLINE float exp2f(float x) +{ + return powf(2.0f, x); +} +#endif + +namespace nv +{ + inline float toRadian(float degree) { return degree * (PI / 180.0f); } + inline float toDegree(float radian) { return radian * (180.0f / PI); } + + // Robust floating point comparisons: + // http://realtimecollisiondetection.net/blog/?p=89 + inline bool equal(const float f0, const float f1, const float epsilon = NV_EPSILON) + { + //return fabs(f0-f1) <= epsilon; + return fabs(f0-f1) <= epsilon * max3(1.0f, fabsf(f0), fabsf(f1)); + } + + inline bool isZero(const float f, const float epsilon = NV_EPSILON) + { + return fabs(f) <= epsilon; + } + + inline bool isFinite(const float f) + { +#if NV_OS_WIN32 || NV_OS_XBOX || NV_OS_DURANGO + return _finite(f) != 0; +#elif NV_OS_DARWIN || NV_OS_FREEBSD || NV_OS_OPENBSD || NV_OS_ORBIS + return isfinite(f); +#elif NV_OS_LINUX + return finitef(f); +#else +# error "isFinite not supported" +#endif + //return std::isfinite (f); + //return finite (f); + } + + inline bool isNan(const float f) + { +#if NV_OS_WIN32 || NV_OS_XBOX || NV_OS_DURANGO + return _isnan(f) != 0; +#elif NV_OS_DARWIN || NV_OS_FREEBSD || NV_OS_OPENBSD || NV_OS_ORBIS + return isnan(f); +#elif NV_OS_LINUX + return isnanf(f); +#else +# error "isNan not supported" +#endif + } + + inline uint log2(uint32 i) + { + uint32 value = 0; + while( i >>= 1 ) value++; + return value; + } + + inline uint log2(uint64 i) + { + uint64 value = 0; + while (i >>= 1) value++; + return U32(value); + } + + inline float lerp(float f0, float f1, float t) + { + const float s = 1.0f - t; + return f0 * s + f1 * t; + } + + inline float square(float f) { return f * f; } + inline int square(int i) { return i * i; } + + inline float cube(float f) { return f * f * f; } + inline int cube(int i) { return i * i * i; } + + inline float frac(float f) + { + return f - floor(f); + } + + inline float floatRound(float f) + { + return floorf(f + 0.5f); + } + + // Eliminates negative zeros from a float array. + inline void floatCleanup(float * fp, int n) + { + for (int i = 0; i < n; i++) { + //nvDebugCheck(isFinite(fp[i])); + union { float f; uint32 i; } x = { fp[i] }; + if (x.i == 0x80000000) fp[i] = 0.0f; + } + } + + inline float saturate(float f) { + return clamp(f, 0.0f, 1.0f); + } + + inline float linearstep(float edge0, float edge1, float x) { + // Scale, bias and saturate x to 0..1 range + return saturate((x - edge0) / (edge1 - edge0)); + } + + inline float smoothstep(float edge0, float edge1, float x) { + x = linearstep(edge0, edge1, x); + + // Evaluate polynomial + return x*x*(3 - 2*x); + } + + inline int sign(float a) + { + return (a > 0) - (a < 0); + //if (a > 0.0f) return 1; + //if (a < 0.0f) return -1; + //return 0; + } + + union Float754 { + unsigned int raw; + float value; + struct { + #if NV_BIG_ENDIAN + unsigned int negative:1; + unsigned int biasedexponent:8; + unsigned int mantissa:23; + #else + unsigned int mantissa:23; + unsigned int biasedexponent:8; + unsigned int negative:1; + #endif + } field; + }; + + // Return the exponent of x ~ Floor(Log2(x)) + inline int floatExponent(float x) + { + Float754 f; + f.value = x; + return (f.field.biasedexponent - 127); + } + + + // FloatRGB9E5 + union Float3SE { + uint32 v; + struct { + #if NV_BIG_ENDIAN + uint32 e : 5; + uint32 zm : 9; + uint32 ym : 9; + uint32 xm : 9; + #else + uint32 xm : 9; + uint32 ym : 9; + uint32 zm : 9; + uint32 e : 5; + #endif + }; + }; + + // FloatR11G11B10 + union Float3PK { + uint32 v; + struct { + #if NV_BIG_ENDIAN + uint32 ze : 5; + uint32 zm : 5; + uint32 ye : 5; + uint32 ym : 6; + uint32 xe : 5; + uint32 xm : 6; + #else + uint32 xm : 6; + uint32 xe : 5; + uint32 ym : 6; + uint32 ye : 5; + uint32 zm : 5; + uint32 ze : 5; + #endif + }; + }; + + +} // nv + +#endif // NV_MATH_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/BaseMesh.cpp b/thirdparty/thekla_atlas/src/nvmesh/BaseMesh.cpp new file mode 100755 index 00000000..f17d3b46 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/BaseMesh.cpp @@ -0,0 +1,19 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "BaseMesh.h" +#include "Stream.h" +#include "nvmath/TypeSerialization.h" + + +namespace nv +{ + static Stream & operator<< (Stream & s, BaseMesh::Vertex & vertex) + { + return s << vertex.id << vertex.pos << vertex.nor << vertex.tex; + } + + Stream & operator<< (Stream & s, BaseMesh & mesh) + { + return s << mesh.m_vertexArray; + } +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/BaseMesh.h b/thirdparty/thekla_atlas/src/nvmesh/BaseMesh.h new file mode 100755 index 00000000..c8559511 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/BaseMesh.h @@ -0,0 +1,72 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_MESH_BASEMESH_H +#define NV_MESH_BASEMESH_H + +#include "nvmesh.h" +#include "nvmath/Vector.h" +#include "nvcore/Array.h" +#include "nvcore/Hash.h" + +namespace nv +{ + + /// Base mesh without connectivity. + class BaseMesh + { + public: + struct Vertex; + + BaseMesh() {} + + BaseMesh(uint vertexNum) : + m_vertexArray(vertexNum) {} + + // Vertex methods. + uint vertexCount() const { return m_vertexArray.count(); } + const Vertex & vertexAt(uint i) const { return m_vertexArray[i]; } + Vertex & vertexAt(uint i) { return m_vertexArray[i]; } + const Array & vertices() const { return m_vertexArray; } + Array & vertices() { return m_vertexArray; } + + friend Stream & operator<< (Stream & s, BaseMesh & obj); + + protected: + + Array m_vertexArray; + }; + + + /// BaseMesh vertex. + struct BaseMesh::Vertex + { + Vertex() : id(NIL), pos(0.0f), nor(0.0f), tex(0.0f) {} + + uint id; // @@ Vertex should be an index into the vertex data. + Vector3 pos; + Vector3 nor; + Vector2 tex; + }; + + inline bool operator==(const BaseMesh::Vertex & a, const BaseMesh::Vertex & b) + { + return a.pos == b.pos && a.nor == b.nor && a.tex == b.tex; + } + + inline bool operator!=(const BaseMesh::Vertex & a, const BaseMesh::Vertex & b) + { + return a.pos != b.pos && a.nor != b.nor && a.tex != b.tex; + } + + template <> struct Hash + { + uint operator()(const BaseMesh::Vertex & v) const + { + return Hash()(v.pos); + } + }; + +} // nv namespace + +#endif // NV_MESH_BASEMESH_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/CMakeLists.txt b/thirdparty/thekla_atlas/src/nvmesh/CMakeLists.txt new file mode 100755 index 00000000..b2b1fc06 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/CMakeLists.txt @@ -0,0 +1,73 @@ +PROJECT(nvmesh) + +SET(MESH_SRCS + nvmesh.h + BaseMesh.h + BaseMesh.cpp + MeshBuilder.h + MeshBuilder.cpp + TriMesh.h + TriMesh.cpp + QuadTriMesh.h + QuadTriMesh.cpp + MeshTopology.h + MeshTopology.cpp + halfedge/Edge.h + halfedge/Edge.cpp + halfedge/Mesh.h + halfedge/Mesh.cpp + halfedge/Face.h + halfedge/Face.cpp + halfedge/Vertex.h + halfedge/Vertex.cpp + geometry/Bounds.h + geometry/Bounds.cpp + geometry/Measurements.h + geometry/Measurements.cpp + raster/Raster.h + raster/Raster.cpp + raster/ClippedTriangle.h + param/Atlas.h + param/Atlas.cpp + param/AtlasBuilder.h + param/AtlasBuilder.cpp + param/AtlasPacker.h + param/AtlasPacker.cpp + param/LeastSquaresConformalMap.h + param/LeastSquaresConformalMap.cpp + param/OrthogonalProjectionMap.h + param/OrthogonalProjectionMap.cpp + param/ParameterizationQuality.h + param/ParameterizationQuality.cpp + param/SingleFaceMap.h + param/SingleFaceMap.cpp + param/Util.h + param/Util.cpp + weld/VertexWeld.h + weld/VertexWeld.cpp + weld/Weld.h + weld/Snap.h + weld/Snap.cpp) + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +# if Tootle +#INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/render/tootle/include) +#SET(LIBS ${LIBS} ${CMAKE_CURRENT_SOURCE_DIR}/render/tootle/lib/libTootle.a) + +# targets +ADD_DEFINITIONS(-DNVMESH_EXPORTS) + +IF(NVMESH_SHARED) + ADD_LIBRARY(nvmesh SHARED ${MESH_SRCS}) +ELSE(NVMESH_SHARED) + ADD_LIBRARY(nvmesh ${MESH_SRCS}) +ENDIF(NVMESH_SHARED) + +TARGET_LINK_LIBRARIES(nvmesh ${LIBS} nvcore nvmath nvimage) + +INSTALL(TARGETS nvmesh + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib/static) + diff --git a/thirdparty/thekla_atlas/src/nvmesh/MeshBuilder.cpp b/thirdparty/thekla_atlas/src/nvmesh/MeshBuilder.cpp new file mode 100755 index 00000000..24d8ddff --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/MeshBuilder.cpp @@ -0,0 +1,1000 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "nvmesh.h" // pch + +#include "MeshBuilder.h" +#include "TriMesh.h" +#include "QuadTriMesh.h" +#include "halfedge/Mesh.h" +#include "halfedge/Vertex.h" +#include "halfedge/Face.h" + +#include "weld/Weld.h" + +#include "nvmath/Box.h" +#include "nvmath/Vector.inl" + +#include "nvcore/StrLib.h" +#include "nvcore/RadixSort.h" +#include "nvcore/Ptr.h" +#include "nvcore/Array.inl" +#include "nvcore/HashMap.inl" + + +using namespace nv; + +/* +By default the mesh builder creates 3 streams (position, normal, texcoord), I'm planning to add support for extra streams as follows: + +enum StreamType { StreamType_Float, StreamType_Vector2, StreamType_Vector3, StreamType_Vector4 }; + +uint addStream(const char *, uint idx, StreamType); + +uint addAttribute(float) +uint addAttribute(Vector2) +uint addAttribute(Vector3) +uint addAttribute(Vector4) + +struct Vertex +{ + uint pos; + uint nor; + uint tex; + uint * attribs; // NULL or NIL terminated array? +}; + +All streams must be added before hand, so that you know the size of the attribs array. + +The vertex hash function could be kept as is, but the == operator should be extended to test +the extra atributes when available. + +That might require a custom hash implementation, or an extension of the current one. How to +handle the variable number of attributes in the attribs array? + +bool operator()(const Vertex & a, const Vertex & b) const +{ + if (a.pos != b.pos || a.nor != b.nor || a.tex != b.tex) return false; + if (a.attribs == NULL && b.attribs == NULL) return true; + return 0 == memcmp(a.attribs, b.attribs, ???); +} + +We could use a NIL terminated array, or provide custom user data to the equals functor. + +vertexMap.setUserData((void *)vertexAttribCount); + +bool operator()(const Vertex & a, const Vertex & b, void * userData) const { ... } + +*/ + + + +namespace +{ + struct Material + { + Material() : faceCount(0) {} + Material(const String & str) : name(str), faceCount(0) {} + + String name; + uint faceCount; + }; + + struct Vertex + { + //Vertex() {} + //Vertex(uint p, uint n, uint t0, uint t1, uint c) : pos(p), nor(n), tex0(t0), tex1(t1), col(c) {} + + friend bool operator==(const Vertex & a, const Vertex & b) + { + return a.pos == b.pos && a.nor == b.nor && a.tex[0] == b.tex[0] && a.tex[1] == b.tex[1] && a.col[0] == b.col[0] && a.col[1] == b.col[1] && a.col[2] == b.col[2]; + } + + uint pos; + uint nor; + uint tex[2]; + uint col[3]; + }; + + struct Face + { + uint id; + uint firstIndex; + uint indexCount; + uint material; + uint group; + }; + +} // namespace + + +namespace nv +{ + // This is a much better hash than the default and greatly improves performance! + template <> struct Hash + { + uint operator()(const Vertex & v) const { return v.pos + v.nor + v.tex[0]/* + v.col*/; } + }; +} + +struct MeshBuilder::PrivateData +{ + PrivateData() : currentGroup(NIL), currentMaterial(NIL), maxFaceIndexCount(0) {} + + uint pushVertex(uint p, uint n, uint t0, uint t1, uint c0, uint c1, uint c2); + uint pushVertex(const Vertex & v); + + Array posArray; + Array norArray; + Array texArray[2]; + Array colArray[3]; + + Array vertexArray; + HashMap vertexMap; + + HashMap materialMap; + Array materialArray; + + uint currentGroup; + uint currentMaterial; + + Array indexArray; + Array faceArray; + + uint maxFaceIndexCount; +}; + + +uint MeshBuilder::PrivateData::pushVertex(uint p, uint n, uint t0, uint t1, uint c0, uint c1, uint c2) +{ + Vertex v; + v.pos = p; + v.nor = n; + v.tex[0] = t0; + v.tex[1] = t1; + v.col[0] = c0; + v.col[1] = c1; + v.col[2] = c2; + return pushVertex(v); +} + +uint MeshBuilder::PrivateData::pushVertex(const Vertex & v) +{ + // Lookup vertex v in map. + uint idx; + if (vertexMap.get(v, &idx)) + { + return idx; + } + + idx = vertexArray.count(); + vertexArray.pushBack(v); + vertexMap.add(v, idx); + + return idx; +} + + +MeshBuilder::MeshBuilder() : d(new PrivateData()) +{ +} + +MeshBuilder::~MeshBuilder() +{ + nvDebugCheck(d != NULL); + delete d; +} + + +// Builder methods. +uint MeshBuilder::addPosition(const Vector3 & v) +{ + d->posArray.pushBack(validate(v)); + return d->posArray.count() - 1; +} + +uint MeshBuilder::addNormal(const Vector3 & v) +{ + d->norArray.pushBack(validate(v)); + return d->norArray.count() - 1; +} + +uint MeshBuilder::addTexCoord(const Vector2 & v, uint set/*=0*/) +{ + d->texArray[set].pushBack(validate(v)); + return d->texArray[set].count() - 1; +} + +uint MeshBuilder::addColor(const Vector4 & v, uint set/*=0*/) +{ + d->colArray[set].pushBack(validate(v)); + return d->colArray[set].count() - 1; +} + +void MeshBuilder::beginGroup(uint id) +{ + d->currentGroup = id; +} + +void MeshBuilder::endGroup() +{ + d->currentGroup = NIL; +} + +// Add named material, check for uniquenes. +uint MeshBuilder::addMaterial(const char * name) +{ + uint index; + if (d->materialMap.get(name, &index)) { + nvDebugCheck(d->materialArray[index].name == name); + } + else { + index = d->materialArray.count(); + d->materialMap.add(name, index); + + Material material(name); + d->materialArray.append(material); + } + return index; +} + +void MeshBuilder::beginMaterial(uint id) +{ + d->currentMaterial = id; +} + +void MeshBuilder::endMaterial() +{ + d->currentMaterial = NIL; +} + +void MeshBuilder::beginPolygon(uint id/*=0*/) +{ + Face face; + face.id = id; + face.firstIndex = d->indexArray.count(); + face.indexCount = 0; + face.material = d->currentMaterial; + face.group = d->currentGroup; + + d->faceArray.pushBack(face); +} + +uint MeshBuilder::addVertex(uint p, uint n/*= NIL*/, uint t0/*= NIL*/, uint t1/*= NIL*/, uint c0/*= NIL*/, uint c1/*= NIL*/, uint c2/*= NIL*/) +{ + // @@ In theory there's no need to add vertices before faces, but I'm adding this to debug problems in our maya exporter: + nvDebugCheck(p < d->posArray.count()); + nvDebugCheck(n == NIL || n < d->norArray.count()); + nvDebugCheck(t0 == NIL || t0 < d->texArray[0].count()); + nvDebugCheck(t1 == NIL || t1 < d->texArray[1].count()); + //nvDebugCheck(c0 == NIL || c0 < d->colArray[0].count()); + if (c0 > d->colArray[0].count()) c0 = NIL; // @@ This seems to be happening in loc_swamp_catwalk.mb! No idea why. + nvDebugCheck(c1 == NIL || c1 < d->colArray[1].count()); + nvDebugCheck(c2 == NIL || c2 < d->colArray[2].count()); + + uint idx = d->pushVertex(p, n, t0, t1, c0, c1, c2); + d->indexArray.pushBack(idx); + d->faceArray.back().indexCount++; + return idx; +} + +uint MeshBuilder::addVertex(const Vector3 & pos) +{ + uint p = addPosition(pos); + return addVertex(p); +} + +#if 0 +uint MeshBuilder::addVertex(const Vector3 & pos, const Vector3 & nor, const Vector2 & tex0, const Vector2 & tex1, const Vector4 & col0, const Vector4 & col1) +{ + uint p = addPosition(pos); + uint n = addNormal(nor); + uint t0 = addTexCoord(tex0, 0); + uint t1 = addTexCoord(tex1, 1); + uint c0 = addColor(col0); + uint c1 = addColor(col1); + return addVertex(p, n, t0, t1, c0, c1); +} +#endif + +// Return true if the face is valid and was added to the mesh. +bool MeshBuilder::endPolygon() +{ + const Face & face = d->faceArray.back(); + const uint count = face.indexCount; + + // Validate polygon here. + bool invalid = count <= 2; + + if (!invalid) { + // Skip zero area polygons. Or polygons with degenerate edges (which will result in zero-area triangles). + const uint first = face.firstIndex; + for (uint j = count - 1, i = 0; i < count; j = i, i++) { + uint v0 = d->indexArray[first + i]; + uint v1 = d->indexArray[first + j]; + + uint p0 = d->vertexArray[v0].pos; + uint p1 = d->vertexArray[v1].pos; + + if (p0 == p1) { + invalid = true; + break; + } + + if (equal(d->posArray[p0], d->posArray[p1], FLT_EPSILON)) { + invalid = true; + break; + } + } + + uint v0 = d->indexArray[first]; + uint p0 = d->vertexArray[v0].pos; + Vector3 x0 = d->posArray[p0]; + + float area = 0.0f; + for (uint j = 1, i = 2; i < count; j = i, i++) { + uint v1 = d->indexArray[first + i]; + uint v2 = d->indexArray[first + j]; + + uint p1 = d->vertexArray[v1].pos; + uint p2 = d->vertexArray[v2].pos; + + Vector3 x1 = d->posArray[p1]; + Vector3 x2 = d->posArray[p2]; + + area += length(cross(x1-x0, x2-x0)); + } + + if (0.5 * area < 1e-6) { // Reduce this threshold if artists have legitimate complains. + invalid = true; + } + + // @@ This is not complete. We may still get zero area triangles after triangulation. + // However, our plugin triangulates before building the mesh, so hopefully that's not a problem. + + } + + if (invalid) + { + d->indexArray.resize(d->indexArray.size() - count); + d->faceArray.popBack(); + return false; + } + else + { + if (d->currentMaterial != NIL) { + d->materialArray[d->currentMaterial].faceCount++; + } + + d->maxFaceIndexCount = max(d->maxFaceIndexCount, count); + return true; + } +} + + +uint MeshBuilder::weldPositions() +{ + Array xrefs; + Weld weldVector3; + + if (d->posArray.count()) { + // Weld vertex attributes. + weldVector3(d->posArray, xrefs); + + // Remap vertex indices. + const uint vertexCount = d->vertexArray.count(); + for (uint v = 0; v < vertexCount; v++) + { + Vertex & vertex = d->vertexArray[v]; + if (vertex.pos != NIL) vertex.pos = xrefs[vertex.pos]; + } + } + + return d->posArray.count(); +} + +uint MeshBuilder::weldNormals() +{ + Array xrefs; + Weld weldVector3; + + if (d->norArray.count()) { + // Weld vertex attributes. + weldVector3(d->norArray, xrefs); + + // Remap vertex indices. + const uint vertexCount = d->vertexArray.count(); + for (uint v = 0; v < vertexCount; v++) + { + Vertex & vertex = d->vertexArray[v]; + if (vertex.nor != NIL) vertex.nor = xrefs[vertex.nor]; + } + } + + return d->norArray.count(); +} + +uint MeshBuilder::weldTexCoords(uint set/*=0*/) +{ + Array xrefs; + Weld weldVector2; + + if (d->texArray[set].count()) { + // Weld vertex attributes. + weldVector2(d->texArray[set], xrefs); + + // Remap vertex indices. + const uint vertexCount = d->vertexArray.count(); + for (uint v = 0; v < vertexCount; v++) + { + Vertex & vertex = d->vertexArray[v]; + if (vertex.tex[set] != NIL) vertex.tex[set] = xrefs[vertex.tex[set]]; + } + } + + return d->texArray[set].count(); +} + +uint MeshBuilder::weldColors(uint set/*=0*/) +{ + Array xrefs; + Weld weldVector4; + + if (d->colArray[set].count()) { + // Weld vertex attributes. + weldVector4(d->colArray[set], xrefs); + + // Remap vertex indices. + const uint vertexCount = d->vertexArray.count(); + for (uint v = 0; v < vertexCount; v++) + { + Vertex & vertex = d->vertexArray[v]; + if (vertex.col[set] != NIL) vertex.col[set] = xrefs[vertex.col[set]]; + } + } + + return d->colArray[set].count(); +} + +void MeshBuilder::weldVertices() { + + if (d->vertexArray.count() == 0) { + // Nothing to do. + return; + } + + Array xrefs; + Weld weldVertex; + + // Weld vertices. + weldVertex(d->vertexArray, xrefs); + + // Remap face indices. + const uint indexCount = d->indexArray.count(); + for (uint i = 0; i < indexCount; i++) + { + d->indexArray[i] = xrefs[d->indexArray[i]]; + } + + // Remap vertex map. + foreach(i, d->vertexMap) + { + d->vertexMap[i].value = xrefs[d->vertexMap[i].value]; + } +} + + +void MeshBuilder::optimize() +{ + if (d->vertexArray.count() == 0) + { + return; + } + + weldPositions(); + weldNormals(); + weldTexCoords(0); + weldTexCoords(1); + weldColors(); + + weldVertices(); +} + + + + + + +void MeshBuilder::removeUnusedMaterials(Array & newMaterialId) +{ + uint materialCount = d->materialArray.count(); + + // Reset face counts. + for (uint i = 0; i < materialCount; i++) { + d->materialArray[i].faceCount = 0; + } + + // Count faces. + foreach(i, d->faceArray) { + Face & face = d->faceArray[i]; + + if (face.material != NIL) { + nvDebugCheck(face.material < materialCount); + + d->materialArray[face.material].faceCount++; + } + } + + // Remove unused materials. + newMaterialId.resize(materialCount); + + for (uint i = 0, m = 0; i < materialCount; i++) + { + if (d->materialArray[m].faceCount > 0) + { + newMaterialId[i] = m++; + } + else + { + newMaterialId[i] = NIL; + d->materialArray.removeAt(m); + } + } + + materialCount = d->materialArray.count(); + + // Update face material ids. + foreach(i, d->faceArray) { + Face & face = d->faceArray[i]; + + if (face.material != NIL) { + uint id = newMaterialId[face.material]; + nvDebugCheck(id != NIL && id < materialCount); + + face.material = id; + } + } +} + +void MeshBuilder::sortFacesByGroup() +{ + const uint faceCount = d->faceArray.count(); + + Array faceGroupArray; + faceGroupArray.resize(faceCount); + + for (uint i = 0; i < faceCount; i++) { + faceGroupArray[i] = d->faceArray[i].group; + } + + RadixSort radix; + radix.sort(faceGroupArray); + + Array newFaceArray; + newFaceArray.resize(faceCount); + + for (uint i = 0; i < faceCount; i++) { + newFaceArray[i] = d->faceArray[radix.rank(i)]; + } + + swap(newFaceArray, d->faceArray); +} + +void MeshBuilder::sortFacesByMaterial() +{ + const uint faceCount = d->faceArray.count(); + + Array faceMaterialArray; + faceMaterialArray.resize(faceCount); + + for (uint i = 0; i < faceCount; i++) { + faceMaterialArray[i] = d->faceArray[i].material; + } + + RadixSort radix; + radix.sort(faceMaterialArray); + + Array newFaceArray; + newFaceArray.resize(faceCount); + + for (uint i = 0; i < faceCount; i++) { + newFaceArray[i] = d->faceArray[radix.rank(i)]; + } + + swap(newFaceArray, d->faceArray); +} + + +void MeshBuilder::reset() +{ + nvDebugCheck(d != NULL); + delete d; + d = new PrivateData(); +} + +void MeshBuilder::done() +{ + if (d->currentGroup != NIL) { + endGroup(); + } + + if (d->currentMaterial != NIL) { + endMaterial(); + } +} + +// Hints. +void MeshBuilder::hintTriangleCount(uint count) +{ + d->indexArray.reserve(d->indexArray.count() + count * 4); +} + +void MeshBuilder::hintVertexCount(uint count) +{ + d->vertexArray.reserve(d->vertexArray.count() + count); + d->vertexMap.resize(d->vertexMap.count() + count); +} + +void MeshBuilder::hintPositionCount(uint count) +{ + d->posArray.reserve(d->posArray.count() + count); +} + +void MeshBuilder::hintNormalCount(uint count) +{ + d->norArray.reserve(d->norArray.count() + count); +} + +void MeshBuilder::hintTexCoordCount(uint count, uint set/*=0*/) +{ + d->texArray[set].reserve(d->texArray[set].count() + count); +} + +void MeshBuilder::hintColorCount(uint count, uint set/*=0*/) +{ + d->colArray[set].reserve(d->colArray[set].count() + count); +} + + +// Helpers. +void MeshBuilder::addTriangle(uint v0, uint v1, uint v2) +{ + beginPolygon(); + addVertex(v0); + addVertex(v1); + addVertex(v2); + endPolygon(); +} + +void MeshBuilder::addQuad(uint v0, uint v1, uint v2, uint v3) +{ + beginPolygon(); + addVertex(v0); + addVertex(v1); + addVertex(v2); + addVertex(v3); + endPolygon(); +} + + +// Get tri mesh. +TriMesh * MeshBuilder::buildTriMesh() const +{ + const uint faceCount = d->faceArray.count(); + uint triangleCount = 0; + for (uint f = 0; f < faceCount; f++) { + triangleCount += d->faceArray[f].indexCount - 2; + } + + const uint vertexCount = d->vertexArray.count(); + TriMesh * mesh = new TriMesh(triangleCount, vertexCount); + + // Build faces. + Array & faces = mesh->faces(); + + for(uint f = 0; f < faceCount; f++) + { + int firstIndex = d->faceArray[f].firstIndex; + int indexCount = d->faceArray[f].indexCount; + + int v0 = d->indexArray[firstIndex + 0]; + int v1 = d->indexArray[firstIndex + 1]; + + for(int t = 0; t < indexCount - 2; t++) { + int v2 = d->indexArray[firstIndex + t + 2]; + + TriMesh::Face face; + face.id = faces.count(); + face.v[0] = v0; + face.v[1] = v1; + face.v[2] = v2; + faces.append(face); + + v1 = v2; + } + } + + // Build vertices. + Array & vertices = mesh->vertices(); + + for(uint i = 0; i < vertexCount; i++) + { + BaseMesh::Vertex vertex; + vertex.id = i; + if (d->vertexArray[i].pos != NIL) vertex.pos = d->posArray[d->vertexArray[i].pos]; + if (d->vertexArray[i].nor != NIL) vertex.nor = d->norArray[d->vertexArray[i].nor]; + if (d->vertexArray[i].tex[0] != NIL) vertex.tex = d->texArray[0][d->vertexArray[i].tex[0]]; + + vertices.append(vertex); + } + + return mesh; +} + +// Get quad/tri mesh. +QuadTriMesh * MeshBuilder::buildQuadTriMesh() const +{ + const uint faceCount = d->faceArray.count(); + const uint vertexCount = d->vertexArray.count(); + QuadTriMesh * mesh = new QuadTriMesh(faceCount, vertexCount); + + // Build faces. + Array & faces = mesh->faces(); + + for (uint f = 0; f < faceCount; f++) + { + int firstIndex = d->faceArray[f].firstIndex; + int indexCount = d->faceArray[f].indexCount; + + QuadTriMesh::Face face; + face.id = f; + + face.v[0] = d->indexArray[firstIndex + 0]; + face.v[1] = d->indexArray[firstIndex + 1]; + face.v[2] = d->indexArray[firstIndex + 2]; + + // Only adds triangles and quads. Ignores polygons. + if (indexCount == 3) { + face.v[3] = NIL; + faces.append(face); + } + else if (indexCount == 4) { + face.v[3] = d->indexArray[firstIndex + 3]; + faces.append(face); + } + } + + // Build vertices. + Array & vertices = mesh->vertices(); + + for(uint i = 0; i < vertexCount; i++) + { + BaseMesh::Vertex vertex; + vertex.id = i; + if (d->vertexArray[i].pos != NIL) vertex.pos = d->posArray[d->vertexArray[i].pos]; + if (d->vertexArray[i].nor != NIL) vertex.nor = d->norArray[d->vertexArray[i].nor]; + if (d->vertexArray[i].tex[0] != NIL) vertex.tex = d->texArray[0][d->vertexArray[i].tex[0]]; + + vertices.append(vertex); + } + + return mesh; +} + +// Get half edge mesh. +HalfEdge::Mesh * MeshBuilder::buildHalfEdgeMesh(bool weldPositions, Error * error/*=NULL*/, Array * badFaces/*=NULL*/) const +{ + if (error != NULL) *error = Error_None; + + const uint vertexCount = d->vertexArray.count(); + AutoPtr mesh(new HalfEdge::Mesh()); + + for(uint v = 0; v < vertexCount; v++) + { + HalfEdge::Vertex * vertex = mesh->addVertex(d->posArray[d->vertexArray[v].pos]); + if (d->vertexArray[v].nor != NIL) vertex->nor = d->norArray[d->vertexArray[v].nor]; + if (d->vertexArray[v].tex[0] != NIL) vertex->tex = Vector2(d->texArray[0][d->vertexArray[v].tex[0]]); + if (d->vertexArray[v].col[0] != NIL) vertex->col = d->colArray[0][d->vertexArray[v].col[0]]; + } + + if (weldPositions) { + mesh->linkColocals(); + } + else { + // Build canonical map from position indices. + Array canonicalMap(vertexCount); + + foreach (i, d->vertexArray) { + canonicalMap.append(d->vertexArray[i].pos); + } + + mesh->linkColocalsWithCanonicalMap(canonicalMap); + } + + const uint faceCount = d->faceArray.count(); + for (uint f = 0; f < faceCount; f++) + { + const uint firstIndex = d->faceArray[f].firstIndex; + const uint indexCount = d->faceArray[f].indexCount; + + HalfEdge::Face * face = mesh->addFace(d->indexArray, firstIndex, indexCount); + + // @@ This is too late, removing the face here will leave the mesh improperly connected. + /*if (face->area() <= FLT_EPSILON) { + mesh->remove(face); + face = NULL; + }*/ + + if (face == NULL) { + // Non manifold mesh. + if (error != NULL) *error = Error_NonManifoldEdge; + if (badFaces != NULL) { + badFaces->append(d->faceArray[f].id); + } + //return NULL; // IC: Ignore error and continue building the mesh. + } + + if (face != NULL) { + face->group = d->faceArray[f].group; + face->material = d->faceArray[f].material; + } + } + + mesh->linkBoundary(); + + // We cannot fix functions here, because this would introduce new vertices and these vertices won't have the corresponding builder data. + + // Maybe the builder should perform the search for T-junctions and update the vertex data directly. + + // For now, we don't fix T-junctions at export time, but only during parameterization. + + //mesh->fixBoundaryJunctions(); + + //mesh->sewBoundary(); + + return mesh.release(); +} + + +bool MeshBuilder::buildPositions(Array & positionArray) +{ + const uint vertexCount = d->vertexArray.count(); + positionArray.resize(vertexCount); + + for (uint v = 0; v < vertexCount; v++) + { + nvDebugCheck(d->vertexArray[v].pos != NIL); + positionArray[v] = d->posArray[d->vertexArray[v].pos]; + } + + return true; +} + +bool MeshBuilder::buildNormals(Array & normalArray) +{ + bool anyNormal = false; + + const uint vertexCount = d->vertexArray.count(); + normalArray.resize(vertexCount); + + for (uint v = 0; v < vertexCount; v++) + { + if (d->vertexArray[v].nor == NIL) { + normalArray[v] = Vector3(0, 0, 1); + } + else { + anyNormal = true; + normalArray[v] = d->norArray[d->vertexArray[v].nor]; + } + } + + return anyNormal; +} + +bool MeshBuilder::buildTexCoords(Array & texCoordArray, uint set/*=0*/) +{ + bool anyTexCoord = false; + + const uint vertexCount = d->vertexArray.count(); + texCoordArray.resize(vertexCount); + + for (uint v = 0; v < vertexCount; v++) + { + if (d->vertexArray[v].tex[set] == NIL) { + texCoordArray[v] = Vector2(0, 0); + } + else { + anyTexCoord = true; + texCoordArray[v] = d->texArray[set][d->vertexArray[v].tex[set]]; + } + } + + return anyTexCoord; +} + +bool MeshBuilder::buildColors(Array & colorArray, uint set/*=0*/) +{ + bool anyColor = false; + + const uint vertexCount = d->vertexArray.count(); + colorArray.resize(vertexCount); + + for (uint v = 0; v < vertexCount; v++) + { + if (d->vertexArray[v].col[set] == NIL) { + colorArray[v] = Vector4(0, 0, 0, 1); + } + else { + anyColor = true; + colorArray[v] = d->colArray[set][d->vertexArray[v].col[set]]; + } + } + + return anyColor; +} + +void MeshBuilder::buildVertexToPositionMap(Array &map) +{ + const uint vertexCount = d->vertexArray.count(); + map.resize(vertexCount); + + foreach (i, d->vertexArray) { + map[i] = d->vertexArray[i].pos; + } +} + + + +uint MeshBuilder::vertexCount() const +{ + return d->vertexArray.count(); +} + + +uint MeshBuilder::positionCount() const +{ + return d->posArray.count(); +} + +uint MeshBuilder::normalCount() const +{ + return d->norArray.count(); +} + +uint MeshBuilder::texCoordCount(uint set/*=0*/) const +{ + return d->texArray[set].count(); +} + +uint MeshBuilder::colorCount(uint set/*=0*/) const +{ + return d->colArray[set].count(); +} + + +uint MeshBuilder::materialCount() const +{ + return d->materialArray.count(); +} + +const char * MeshBuilder::material(uint i) const +{ + return d->materialArray[i].name; +} + + +uint MeshBuilder::positionIndex(uint vertex) const +{ + return d->vertexArray[vertex].pos; +} +uint MeshBuilder::normalIndex(uint vertex) const +{ + return d->vertexArray[vertex].nor; +} +uint MeshBuilder::texCoordIndex(uint vertex, uint set/*=0*/) const +{ + return d->vertexArray[vertex].tex[set]; +} +uint MeshBuilder::colorIndex(uint vertex, uint set/*=0*/) const +{ + return d->vertexArray[vertex].col[set]; +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/MeshBuilder.h b/thirdparty/thekla_atlas/src/nvmesh/MeshBuilder.h new file mode 100755 index 00000000..5b3af3fc --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/MeshBuilder.h @@ -0,0 +1,119 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MESH_MESHBUILDER_H +#define NV_MESH_MESHBUILDER_H + +#include "nvmesh.h" +#include "nvcore/Array.h" +#include "nvmath/Vector.h" + +namespace nv +{ + class String; + class TriMesh; + class QuadTriMesh; + namespace HalfEdge { class Mesh; } + + + /// Mesh builder is a helper class for importers. + /// Ideally it should handle any vertex data, but for now it only accepts positions, + /// normals and texcoords. + class MeshBuilder + { + NV_FORBID_COPY(MeshBuilder); + NV_FORBID_HEAPALLOC(); + public: + MeshBuilder(); + ~MeshBuilder(); + + // Builder methods. + uint addPosition(const Vector3 & v); + uint addNormal(const Vector3 & v); + uint addTexCoord(const Vector2 & v, uint set = 0); + uint addColor(const Vector4 & v, uint set = 0); + + void beginGroup(uint id); + void endGroup(); + + uint addMaterial(const char * name); + void beginMaterial(uint id); + void endMaterial(); + + void beginPolygon(uint id = 0); + uint addVertex(uint p, uint n = NIL, uint t0 = NIL, uint t1 = NIL, uint c0 = NIL, uint c1 = NIL, uint c2 = NIL); + uint addVertex(const Vector3 & p); + //uint addVertex(const Vector3 & p, const Vector3 & n, const Vector2 & t0 = Vector2(0), const Vector2 & t1 = Vector2(0), const Vector4 & c0 = Vector4(0), const Vector4 & c1 = Vector4(0)); + bool endPolygon(); + + uint weldPositions(); + uint weldNormals(); + uint weldTexCoords(uint set = 0); + uint weldColors(uint set = 0); + void weldVertices(); + + void optimize(); // eliminate duplicate components and duplicate vertices. + void removeUnusedMaterials(Array & newMaterialId); + void sortFacesByGroup(); + void sortFacesByMaterial(); + + void done(); + void reset(); + + // Hints. + void hintTriangleCount(uint count); + void hintVertexCount(uint count); + void hintPositionCount(uint count); + void hintNormalCount(uint count); + void hintTexCoordCount(uint count, uint set = 0); + void hintColorCount(uint count, uint set = 0); + + // Helpers. + void addTriangle(uint v0, uint v1, uint v2); + void addQuad(uint v0, uint v1, uint v2, uint v3); + + // Get result. + TriMesh * buildTriMesh() const; + QuadTriMesh * buildQuadTriMesh() const; + + enum Error { + Error_None, + Error_NonManifoldEdge, + Error_NonManifoldVertex, + }; + + HalfEdge::Mesh * buildHalfEdgeMesh(bool weldPositions, Error * error = NULL, Array * badFaces = NULL) const; + + bool buildPositions(Array & positionArray); + bool buildNormals(Array & normalArray); + bool buildTexCoords(Array & texCoordArray, uint set = 0); + bool buildColors(Array & colorArray, uint set = 0); + void buildVertexToPositionMap(Array & map); + + + // Expose attribute indices of the unified vertex array. + uint vertexCount() const; + + uint positionCount() const; + uint normalCount() const; + uint texCoordCount(uint set = 0) const; + uint colorCount(uint set = 0) const; + + uint materialCount() const; + const char * material(uint i) const; + + uint positionIndex(uint vertex) const; + uint normalIndex(uint vertex) const; + uint texCoordIndex(uint vertex, uint set = 0) const; + uint colorIndex(uint vertex, uint set = 0) const; + + private: + + struct PrivateData; + PrivateData * d; + + }; + +} // nv namespace + +#endif // NV_MESH_MESHBUILDER_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/MeshTopology.cpp b/thirdparty/thekla_atlas/src/nvmesh/MeshTopology.cpp new file mode 100755 index 00000000..e7e1dce4 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/MeshTopology.cpp @@ -0,0 +1,122 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "nvmesh.h" // pch + +#include "nvcore/Array.h" +#include "nvcore/BitArray.h" + +#include "nvmesh/MeshTopology.h" +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Edge.h" +#include "nvmesh/halfedge/Face.h" + +using namespace nv; + +void MeshTopology::buildTopologyInfo(const HalfEdge::Mesh * mesh) +{ + const uint vertexCount = mesh->colocalVertexCount(); + const uint faceCount = mesh->faceCount(); + const uint edgeCount = mesh->edgeCount(); + + nvDebug( "--- Building mesh topology:\n" ); + + Array stack(faceCount); + + BitArray bitFlags(faceCount); + bitFlags.clearAll(); + + // Compute connectivity. + nvDebug( "--- Computing connectivity.\n" ); + + m_connectedCount = 0; + + for(uint f = 0; f < faceCount; f++ ) { + if( bitFlags.bitAt(f) == false ) { + m_connectedCount++; + + stack.pushBack( f ); + while( !stack.isEmpty() ) { + + const uint top = stack.back(); + nvCheck(top != NIL); + stack.popBack(); + + if( bitFlags.bitAt(top) == false ) { + bitFlags.setBitAt(top); + + const HalfEdge::Face * face = mesh->faceAt(top); + const HalfEdge::Edge * firstEdge = face->edge; + const HalfEdge::Edge * edge = firstEdge; + + do { + const HalfEdge::Face * neighborFace = edge->pair->face; + if (neighborFace != NULL) { + stack.pushBack(neighborFace->id); + } + edge = edge->next; + } while(edge != firstEdge); + } + } + } + } + nvCheck(stack.isEmpty()); + nvDebug( "--- %d connected components.\n", m_connectedCount ); + + + // Count boundary loops. + nvDebug( "--- Counting boundary loops.\n" ); + m_boundaryCount = 0; + + bitFlags.resize(edgeCount); + bitFlags.clearAll(); + + // Don't forget to link the boundary otherwise this won't work. + for (uint e = 0; e < edgeCount; e++) + { + const HalfEdge::Edge * startEdge = mesh->edgeAt(e); + if (startEdge != NULL && startEdge->isBoundary() && bitFlags.bitAt(e) == false) + { + nvDebugCheck(startEdge->face != NULL); + nvDebugCheck(startEdge->pair->face == NULL); + + startEdge = startEdge->pair; + + m_boundaryCount++; + + const HalfEdge::Edge * edge = startEdge; + do { + bitFlags.setBitAt(edge->id / 2); + edge = edge->next; + } while(startEdge != edge); + } + } + nvDebug("--- %d boundary loops found.\n", m_boundaryCount ); + + + // Compute euler number. + m_eulerNumber = vertexCount - edgeCount + faceCount; + nvDebug("--- Euler number: %d.\n", m_eulerNumber); + + + // Compute genus. (only valid on closed connected surfaces) + m_genus = -1; + if( isClosed() && isConnected() ) { + m_genus = (2 - m_eulerNumber) / 2; + nvDebug("--- Genus: %d.\n", m_genus); + } +} + + +/*static*/ bool MeshTopology::isQuadOnly(const HalfEdge::Mesh * mesh) +{ + const uint faceCount = mesh->faceCount(); + for(uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = mesh->faceAt(f); + if (face->edgeCount() != 4) { + return false; + } + } + + return true; +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/MeshTopology.h b/thirdparty/thekla_atlas/src/nvmesh/MeshTopology.h new file mode 100755 index 00000000..c3d7477b --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/MeshTopology.h @@ -0,0 +1,66 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MESH_MESHTOPOLOGY_H +#define NV_MESH_MESHTOPOLOGY_H + +#include + +namespace nv +{ + namespace HalfEdge { class Mesh; } + class MeshAdjacency; + + /// Mesh topology information. + class MeshTopology + { + public: + MeshTopology(const HalfEdge::Mesh * mesh) { buildTopologyInfo(mesh); } + + /// Determine if the mesh is connected. + bool isConnected() const { return m_connectedCount == 1; } + + /// Determine if the mesh is closed. (Each edge is shared by two faces) + bool isClosed() const { return m_boundaryCount == 0; } + + /// Return true if the mesh has the topology of a disk. + bool isDisk() const { return isConnected() && m_boundaryCount == 1/* && m_eulerNumber == 1*/; } + + /// Return the number of connected components. + int connectedCount() const { return m_connectedCount; } + + /// Return the number of open holes. + int holeCount() const { return m_boundaryCount; } + + /// Return the genus of the mesh. + int genus() const { return m_genus; } + + /// Return the euler number of the mesh. + int euler() const { return m_eulerNumber; } + + + static bool isQuadOnly(const HalfEdge::Mesh * mesh); + + + private: + + NVMESH_API void buildTopologyInfo(const HalfEdge::Mesh * mesh); + + private: + + ///< Number of boundary loops. + int m_boundaryCount; + + ///< Number of connected components. + int m_connectedCount; + + ///< Euler number. + int m_eulerNumber; + + /// Mesh genus. + int m_genus; + }; + +} // nv namespace + +#endif // NV_MESH_MESHTOPOLOGY_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/QuadTriMesh.cpp b/thirdparty/thekla_atlas/src/nvmesh/QuadTriMesh.cpp new file mode 100755 index 00000000..64a071ab --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/QuadTriMesh.cpp @@ -0,0 +1,36 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "QuadTriMesh.h" +#include "Stream.h" + +using namespace nv; + + +bool QuadTriMesh::isQuadFace(uint i) const +{ + return m_faceArray[i].isQuadFace(); +} + +const QuadTriMesh::Vertex & QuadTriMesh::faceVertex(uint f, uint v) const +{ + if (isQuadFace(f)) nvDebugCheck(v < 4); + else nvDebugCheck(v < 3); + + const Face & face = this->faceAt(f); + return this->vertexAt(face.v[v]); +} + + +namespace nv +{ + static Stream & operator<< (Stream & s, QuadTriMesh::Face & face) + { + return s << face.id << face.v[0] << face.v[1] << face.v[2] << face.v[3]; + } + + Stream & operator<< (Stream & s, QuadTriMesh & mesh) + { + return s << mesh.m_faceArray << (BaseMesh &) mesh; + } +} + diff --git a/thirdparty/thekla_atlas/src/nvmesh/QuadTriMesh.h b/thirdparty/thekla_atlas/src/nvmesh/QuadTriMesh.h new file mode 100755 index 00000000..b8465f2d --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/QuadTriMesh.h @@ -0,0 +1,60 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_MESH_QUADTRIMESH_H +#define NV_MESH_QUADTRIMESH_H + +#include "nvcore/Array.h" +#include "nvmath/Vector.h" +#include "nvmesh/nvmesh.h" +#include "nvmesh/BaseMesh.h" + +namespace nv +{ + class Stream; + + /// Mixed quad/triangle mesh. + class QuadTriMesh : public BaseMesh + { + public: + struct Face; + typedef BaseMesh::Vertex Vertex; + + QuadTriMesh() {}; + QuadTriMesh(uint faceCount, uint vertexCount) : BaseMesh(vertexCount), m_faceArray(faceCount) {} + + // Face methods. + uint faceCount() const { return m_faceArray.count(); } + + const Face & faceAt(uint i) const { return m_faceArray[i]; } + Face & faceAt(uint i) { return m_faceArray[i]; } + + const Array & faces() const { return m_faceArray; } + Array & faces() { return m_faceArray; } + + bool isQuadFace(uint i) const; + + const Vertex & faceVertex(uint f, uint v) const; + + friend Stream & operator<< (Stream & s, QuadTriMesh & obj); + + private: + + Array m_faceArray; + + }; + + + /// QuadTriMesh face. + struct QuadTriMesh::Face + { + uint id; + uint v[4]; + + bool isQuadFace() const { return v[3] != NIL; } + }; + +} // nv namespace + + +#endif // NV_MESH_QUADTRIMESH_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/TriMesh.cpp b/thirdparty/thekla_atlas/src/nvmesh/TriMesh.cpp new file mode 100755 index 00000000..bf10a474 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/TriMesh.cpp @@ -0,0 +1,25 @@ +// This code is in the public domain -- Ignacio Castańo + +#include "TriMesh.h" + +using namespace nv; + + +/// Triangle mesh. +Vector3 TriMesh::faceNormal(uint f) const +{ + const Face & face = this->faceAt(f); + const Vector3 & p0 = this->vertexAt(face.v[0]).pos; + const Vector3 & p1 = this->vertexAt(face.v[1]).pos; + const Vector3 & p2 = this->vertexAt(face.v[2]).pos; + return normalizeSafe(cross(p1 - p0, p2 - p0), Vector3(0.0f), 0.0f); +} + +/// Get face vertex. +const TriMesh::Vertex & TriMesh::faceVertex(uint f, uint v) const +{ + nvDebugCheck(v < 3); + const Face & face = this->faceAt(f); + return this->vertexAt(face.v[v]); +} + diff --git a/thirdparty/thekla_atlas/src/nvmesh/TriMesh.h b/thirdparty/thekla_atlas/src/nvmesh/TriMesh.h new file mode 100755 index 00000000..bc5672c1 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/TriMesh.h @@ -0,0 +1,51 @@ +// This code is in the public domain -- Ignacio Castańo + +#pragma once +#ifndef NV_MESH_TRIMESH_H +#define NV_MESH_TRIMESH_H + +#include "nvcore/Array.h" +#include "nvmath/Vector.inl" +#include "nvmesh/nvmesh.h" +#include "nvmesh/BaseMesh.h" + +namespace nv +{ + /// Triangle mesh. + class TriMesh : public BaseMesh + { + public: + struct Face; + typedef BaseMesh::Vertex Vertex; + + TriMesh(uint faceCount, uint vertexCount) : BaseMesh(vertexCount), m_faceArray(faceCount) {} + + // Face methods. + uint faceCount() const { return m_faceArray.count(); } + const Face & faceAt(uint i) const { return m_faceArray[i]; } + Face & faceAt(uint i) { return m_faceArray[i]; } + const Array & faces() const { return m_faceArray; } + Array & faces() { return m_faceArray; } + + NVMESH_API Vector3 faceNormal(uint f) const; + NVMESH_API const Vertex & faceVertex(uint f, uint v) const; + + friend Stream & operator<< (Stream & s, BaseMesh & obj); + + private: + + Array m_faceArray; + + }; + + + /// TriMesh face. + struct TriMesh::Face + { + uint id; + uint v[3]; + }; + +} // nv namespace + +#endif // NV_MESH_TRIMESH_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/geometry/Bounds.cpp b/thirdparty/thekla_atlas/src/nvmesh/geometry/Bounds.cpp new file mode 100755 index 00000000..69fd1deb --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/geometry/Bounds.cpp @@ -0,0 +1,54 @@ +// This code is in the public domain -- Ignacio CastaƱo + +#include "nvmesh.h" // pch + +#include "Bounds.h" + +#include "nvmesh/BaseMesh.h" +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Vertex.h" + +#include "nvmath/Box.inl" + +using namespace nv; + +Box MeshBounds::box(const BaseMesh * mesh) +{ + nvCheck(mesh != NULL); + + Box bounds; + bounds.clearBounds(); + + const uint vertexCount = mesh->vertexCount(); + for (uint v = 0; v < vertexCount; v++) + { + const BaseMesh::Vertex & vertex = mesh->vertexAt(v); + bounds.addPointToBounds( vertex.pos ); + } + + return bounds; +} + +Box MeshBounds::box(const HalfEdge::Mesh * mesh) +{ + nvCheck(mesh != NULL); + + Box bounds; + bounds.clearBounds(); + + const uint vertexCount = mesh->vertexCount(); + for (uint v = 0; v < vertexCount; v++) + { + const HalfEdge::Vertex * vertex = mesh->vertexAt(v); + nvDebugCheck(vertex != NULL); + bounds.addPointToBounds( vertex->pos ); + } + + return bounds; +} + +/*Sphere MeshBounds::sphere(const HalfEdge::Mesh * mesh) +{ + // @@ TODO + return Sphere(); +}*/ diff --git a/thirdparty/thekla_atlas/src/nvmesh/geometry/Bounds.h b/thirdparty/thekla_atlas/src/nvmesh/geometry/Bounds.h new file mode 100755 index 00000000..1cb5b7b9 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/geometry/Bounds.h @@ -0,0 +1,28 @@ +// This code is in the public domain -- Ignacio CastaƱo + +#pragma once +#ifndef NV_MESH_MESHBOUNDS_H +#define NV_MESH_MESHBOUNDS_H + +#include +#include + +#include + +namespace nv +{ + class BaseMesh; + namespace HalfEdge { class Mesh; } + + // Bounding volumes computation. + namespace MeshBounds + { + Box box(const BaseMesh * mesh); + Box box(const HalfEdge::Mesh * mesh); + + Sphere sphere(const HalfEdge::Mesh * mesh); + } + +} // nv namespace + +#endif // NV_MESH_MESHBOUNDS_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/geometry/CMakeLists.txt b/thirdparty/thekla_atlas/src/nvmesh/geometry/CMakeLists.txt new file mode 100755 index 00000000..4540274a --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/geometry/CMakeLists.txt @@ -0,0 +1,2 @@ + +#SUBDIRS(StanHull) diff --git a/thirdparty/thekla_atlas/src/nvmesh/geometry/Measurements.cpp b/thirdparty/thekla_atlas/src/nvmesh/geometry/Measurements.cpp new file mode 100755 index 00000000..e0c27166 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/geometry/Measurements.cpp @@ -0,0 +1,36 @@ +// This code is in the public domain -- castano@gmail.com + +#include "nvmesh.h" // pch + +#include "Measurements.h" +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Face.h" + +using namespace nv; + +float nv::computeSurfaceArea(const HalfEdge::Mesh * mesh) +{ + float area = 0; + + for (HalfEdge::Mesh::ConstFaceIterator it(mesh->faces()); !it.isDone(); it.advance()) + { + const HalfEdge::Face * face = it.current(); + area += face->area(); + } + nvDebugCheck(area >= 0); + + return area; +} + +float nv::computeParametricArea(const HalfEdge::Mesh * mesh) +{ + float area = 0; + + for (HalfEdge::Mesh::ConstFaceIterator it(mesh->faces()); !it.isDone(); it.advance()) + { + const HalfEdge::Face * face = it.current(); + area += face->parametricArea(); + } + + return area; +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/geometry/Measurements.h b/thirdparty/thekla_atlas/src/nvmesh/geometry/Measurements.h new file mode 100755 index 00000000..0be863b7 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/geometry/Measurements.h @@ -0,0 +1,18 @@ +// This code is in the public domain -- castano@gmail.com + +#pragma once +#ifndef NV_MESH_MESHMEASUREMENTS_H +#define NV_MESH_MESHMEASUREMENTS_H + +#include "nvmesh/nvmesh.h" + +namespace nv +{ + namespace HalfEdge { class Mesh; } + + float computeSurfaceArea(const HalfEdge::Mesh * mesh); + float computeParametricArea(const HalfEdge::Mesh * mesh); + +} // nv namespace + +#endif // NV_MESH_MESHMEASUREMENTS_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/halfedge/Edge.cpp b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Edge.cpp new file mode 100755 index 00000000..67165029 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Edge.cpp @@ -0,0 +1,57 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "nvmesh.h" // pch + +#include "Edge.h" +#include "Vertex.h" + +#include "nvmath/Vector.inl" + +using namespace nv; +using namespace HalfEdge; + +Vector3 Edge::midPoint() const +{ + return (to()->pos + from()->pos) * 0.5f; +} + +float Edge::length() const +{ + return ::length(to()->pos - from()->pos); +} + +// Return angle between this edge and the previous one. +float Edge::angle() const { + Vector3 p = vertex->pos; + Vector3 a = prev->vertex->pos; + Vector3 b = next->vertex->pos; + + Vector3 v0 = a - p; + Vector3 v1 = b - p; + + return acosf(dot(v0, v1) / (nv::length(v0) * nv::length(v1))); +} + +bool Edge::isValid() const +{ + // null face is OK. + if (next == NULL || prev == NULL || pair == NULL || vertex == NULL) return false; + if (next->prev != this) return false; + if (prev->next != this) return false; + if (pair->pair != this) return false; + return true; +} + +/* +Edge * Edge::nextBoundary() { + nvDebugCheck(this->m_pair == NULL); + +} + +Edge * Edge::prevBoundary() { + nvDebugCheck(this->m_pair == NULL); + +} +*/ + + diff --git a/thirdparty/thekla_atlas/src/nvmesh/halfedge/Edge.h b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Edge.h new file mode 100755 index 00000000..25c47f48 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Edge.h @@ -0,0 +1,70 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MESH_HALFEDGE_EDGE_H +#define NV_MESH_HALFEDGE_EDGE_H + +#include "nvmath/Vector.h" + +namespace nv +{ + namespace HalfEdge { class Vertex; class Face; class Edge; } + + /// Half edge edge. + class HalfEdge::Edge + { + NV_FORBID_COPY(Edge); + public: + + uint id; + + Edge * next; + Edge * prev; // This is not strictly half-edge, but makes algorithms easier and faster. + Edge * pair; + Vertex * vertex; + Face * face; + + + // Default constructor. + Edge(uint id) : id(id), next(NULL), prev(NULL), pair(NULL), vertex(NULL), face(NULL) + { + } + + + // Vertex queries. + const Vertex * from() const { return vertex; } + Vertex * from() { return vertex; } + + const Vertex * to() const { return pair->vertex; } // This used to be 'next->vertex', but that changed often when the connectivity of the mesh changes. + Vertex * to() { return pair->vertex; } + + + // Edge queries. + void setNext(Edge * e) { next = e; if (e != NULL) e->prev = this; } + void setPrev(Edge * e) { prev = e; if (e != NULL) e->next = this; } + + // @@ Add these helpers: + //Edge * nextBoundary(); + //Edge * prevBoundary(); + + + // @@ It would be more simple to only check m_pair == NULL + // Face queries. + bool isBoundary() const { return !(face && pair->face); } + + // @@ This is not exactly accurate, we should compare the texture coordinates... + bool isSeam() const { return vertex != pair->next->vertex || next->vertex != pair->vertex; } + + bool isValid() const; + + // Geometric queries. + Vector3 midPoint() const; + float length() const; + float angle() const; + + }; + +} // nv namespace + + +#endif // NV_MESH_HALFEDGE_EDGE_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/halfedge/Face.cpp b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Face.cpp new file mode 100755 index 00000000..9f698715 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Face.cpp @@ -0,0 +1,268 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "nvmesh.h" // pch + +#include "Face.h" +#include "Vertex.h" + +#include "nvmath/Fitting.h" +#include "nvmath/Plane.h" +#include "nvmath/Vector.inl" + +#include "nvcore/Array.h" + + +using namespace nv; +using namespace HalfEdge; + +/// Get face area. +float Face::area() const +{ + float area = 0; + const Vector3 & v0 = edge->from()->pos; + + for (ConstEdgeIterator it(edges(edge->next)); it.current() != edge->prev; it.advance()) + { + const Edge * e = it.current(); + + const Vector3 & v1 = e->vertex->pos; + const Vector3 & v2 = e->next->vertex->pos; + + area += length(cross(v1-v0, v2-v0)); + } + + return area * 0.5f; +} + +float Face::parametricArea() const +{ + float area = 0; + const Vector2 & v0 = edge->from()->tex; + + for (ConstEdgeIterator it(edges(edge->next)); it.current() != edge->prev; it.advance()) + { + const Edge * e = it.current(); + + const Vector2 & v1 = e->vertex->tex; + const Vector2 & v2 = e->next->vertex->tex; + + area += triangleArea(v0, v1, v2); + } + + return area * 0.5f; +} + + +/// Get boundary length. +float Face::boundaryLength() const +{ + float bl = 0; + + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) + { + const Edge * edge = it.current(); + bl += edge->length(); + } + + return bl; +} + + +/// Get face normal. +Vector3 Face::normal() const +{ + Vector3 n(0); + + const Vertex * vertex0 = NULL; + + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) + { + const Edge * edge = it.current(); + nvCheck(edge != NULL); + + if (vertex0 == NULL) + { + vertex0 = edge->vertex; + } + else if (edge->next->vertex != vertex0) + { + const HalfEdge::Vertex * vertex1 = edge->from(); + const HalfEdge::Vertex * vertex2 = edge->to(); + + const Vector3 & p0 = vertex0->pos; + const Vector3 & p1 = vertex1->pos; + const Vector3 & p2 = vertex2->pos; + + Vector3 v10 = p1 - p0; + Vector3 v20 = p2 - p0; + + n += cross(v10, v20); + } + } + + return normalizeSafe(n, Vector3(0, 0, 1), 0.0f); + + + // Get face points eliminating duplicates. + /*Array points(4); + + points.append(m_edge->prev()->from()->pos); + + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) + { + const Edge * edge = it.current(); + nvDebugCheck(edge != NULL); + + const Vector3 & p = edge->from()->pos; + if (points.back() != p) + { + points.append(edge->from()->pos); + } + } + + points.popBack(); + + if (points.count() < 3) + { + // Invalid normal. + return Vector3(0.0f); + } + else + { + // Compute regular normal. + Vector3 normal = normalizeSafe(cross(points[1] - points[0], points[2] - points[0]), Vector3(0.0f), 0.0f); + +#pragma NV_MESSAGE("TODO: make sure these three points are not colinear") + + if (points.count() > 3) + { + // Compute best fitting plane to the points. + Plane plane = Fit::bestPlane(points.count(), points.buffer()); + + // Adjust normal orientation. + if (dot(normal, plane.vector()) > 0) { + normal = plane.vector(); + } + else { + normal = -plane.vector(); + } + } + + nvDebugCheck(isNormalized(normal)); + return normal; + }*/ +} + +Vector3 Face::centroid() const +{ + Vector3 sum(0.0f); + uint count = 0; + + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) + { + const Edge * edge = it.current(); + sum += edge->from()->pos; + count++; + } + + return sum / float(count); +} + + +bool Face::isValid() const +{ + uint count = 0; + + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) + { + const Edge * edge = it.current(); + if (edge->face != this) return false; + if (!edge->isValid()) return false; + if (!edge->pair->isValid()) return false; + count++; + } + + if (count < 3) return false; + + return true; +} + + +// Determine if this face contains the given edge. +bool Face::contains(const Edge * e) const +{ + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) + { + if(it.current() == e) return true; + } + return false; +} + +// Returns index in this face of the given edge. +uint Face::edgeIndex(const Edge * e) const +{ + int i = 0; + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance(), i++) + { + if(it.current() == e) return i; + } + return NIL; +} + + +Edge * Face::edgeAt(uint idx) +{ + int i = 0; + for(EdgeIterator it(edges()); !it.isDone(); it.advance(), i++) { + if (i == idx) return it.current(); + } + return NULL; +} +const Edge * Face::edgeAt(uint idx) const +{ + int i = 0; + for(ConstEdgeIterator it(edges()); !it.isDone(); it.advance(), i++) { + if (i == idx) return it.current(); + } + return NULL; +} + + +// Count the number of edges in this face. +uint Face::edgeCount() const +{ + uint count = 0; + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) { ++count; } + return count; +} + +// Determine if this is a boundary face. +bool Face::isBoundary() const +{ + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) + { + const Edge * edge = it.current(); + nvDebugCheck(edge->pair != NULL); + + if (edge->pair->face == NULL) { + return true; + } + } + return false; +} + +// Count the number of boundary edges in the face. +uint Face::boundaryCount() const +{ + uint count = 0; + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) + { + const Edge * edge = it.current(); + nvDebugCheck(edge->pair != NULL); + + if (edge->pair->face == NULL) { + count++; + } + } + return count; +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/halfedge/Face.h b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Face.h new file mode 100755 index 00000000..677f8666 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Face.h @@ -0,0 +1,106 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MESH_HALFEDGE_FACE_H +#define NV_MESH_HALFEDGE_FACE_H + +#include + +namespace nv +{ + namespace HalfEdge { class Vertex; class Face; class Edge; } + + /// Face of a half-edge mesh. + class HalfEdge::Face + { + NV_FORBID_COPY(Face); + public: + + uint id; + uint16 group; + uint16 material; + Edge * edge; + + + Face(uint id) : id(id), group(~0), material(~0), edge(NULL) {} + + float area() const; + float parametricArea() const; + float boundaryLength() const; + Vector3 normal() const; + Vector3 centroid() const; + + bool isValid() const; + + bool contains(const Edge * e) const; + uint edgeIndex(const Edge * e) const; + + Edge * edgeAt(uint idx); + const Edge * edgeAt(uint idx) const; + + uint edgeCount() const; + bool isBoundary() const; + uint boundaryCount() const; + + + // The iterator that visits the edges of this face in clockwise order. + class EdgeIterator //: public Iterator + { + public: + EdgeIterator(Edge * e) : m_end(NULL), m_current(e) { } + + virtual void advance() + { + if (m_end == NULL) m_end = m_current; + m_current = m_current->next; + } + + virtual bool isDone() const { return m_end == m_current; } + virtual Edge * current() const { return m_current; } + Vertex * vertex() const { return m_current->vertex; } + + private: + Edge * m_end; + Edge * m_current; + }; + + EdgeIterator edges() { return EdgeIterator(edge); } + EdgeIterator edges(Edge * e) + { + nvDebugCheck(contains(e)); + return EdgeIterator(e); + } + + // The iterator that visits the edges of this face in clockwise order. + class ConstEdgeIterator //: public Iterator + { + public: + ConstEdgeIterator(const Edge * e) : m_end(NULL), m_current(e) { } + ConstEdgeIterator(const EdgeIterator & it) : m_end(NULL), m_current(it.current()) { } + + virtual void advance() + { + if (m_end == NULL) m_end = m_current; + m_current = m_current->next; + } + + virtual bool isDone() const { return m_end == m_current; } + virtual const Edge * current() const { return m_current; } + const Vertex * vertex() const { return m_current->vertex; } + + private: + const Edge * m_end; + const Edge * m_current; + }; + + ConstEdgeIterator edges() const { return ConstEdgeIterator(edge); } + ConstEdgeIterator edges(const Edge * e) const + { + nvDebugCheck(contains(e)); + return ConstEdgeIterator(e); + } + }; + +} // nv namespace + +#endif // NV_MESH_HALFEDGE_FACE_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/halfedge/Mesh.cpp b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Mesh.cpp new file mode 100755 index 00000000..0012513b --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Mesh.cpp @@ -0,0 +1,1284 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include "nvmesh.h" // pch + +#include "Mesh.h" +#include "Edge.h" +#include "Vertex.h" +#include "Face.h" + +#include "nvmesh/TriMesh.h" +#include "nvmesh/QuadTriMesh.h" +#include "nvmesh/MeshBuilder.h" + +#include "nvmath/Vector.inl" +#include "nvcore/Array.inl" +#include "nvcore/HashMap.inl" + + +using namespace nv; +using namespace HalfEdge; + +Mesh::Mesh() : m_colocalVertexCount(0) +{ + errorCount = 0; +} + +Mesh::Mesh(const Mesh * mesh) +{ + errorCount = 0; + + // Copy mesh vertices. + const uint vertexCount = mesh->vertexCount(); + m_vertexArray.resize(vertexCount); + + for (uint v = 0; v < vertexCount; v++) + { + const Vertex * vertex = mesh->vertexAt(v); + nvDebugCheck(vertex->id == v); + + m_vertexArray[v] = new Vertex(v); + m_vertexArray[v]->pos = vertex->pos; + m_vertexArray[v]->nor = vertex->nor; + m_vertexArray[v]->tex = vertex->tex; + } + + m_colocalVertexCount = vertexCount; + + + // Copy mesh faces. + const uint faceCount = mesh->faceCount(); + + Array indexArray(3); + + for (uint f = 0; f < faceCount; f++) + { + const Face * face = mesh->faceAt(f); + + for(Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { + const Vertex * vertex = it.current()->from(); + indexArray.append(vertex->id); + } + + addFace(indexArray); + indexArray.clear(); + } +} + +Mesh::~Mesh() +{ + clear(); +} + + +void Mesh::clear() +{ + deleteAll(m_vertexArray); + m_vertexArray.clear(); + + foreach(i, m_edgeMap) + { + delete m_edgeMap[i].value; + } + //deleteAll(m_edgeArray); // edgeArray only contains 1/2 of the edges! + m_edgeArray.clear(); + m_edgeMap.clear(); + + deleteAll(m_faceArray); + m_faceArray.clear(); +} + + +Vertex * Mesh::addVertex(const Vector3 & pos) +{ + nvDebugCheck(isFinite(pos)); + + Vertex * v = new Vertex(m_vertexArray.count()); + v->pos = pos; + m_vertexArray.append(v); + + return v; + +// return addVertex(m_vertexArray.count(), pos); +} + +/*Vertex * Mesh::addVertex(uint id, const Vector3 & pos) +{ + nvDebugCheck(isFinite(pos)); + + Vertex * v = new Vertex(id); + v->pos = pos; + m_vertexArray.append(v); + + return v; +}*/ + +/*void Mesh::addVertices(const Mesh * mesh) +{ +nvCheck(mesh != NULL); + +// Add mesh vertices +for (uint v = 0; v < vertexCount; v++) +{ +const Vertex * vertex = mesh->vertexAt(v); +nvDebugCheck(vertex != NULL); + +Vertex * v = addVertex(vertex->pos()); +nvDebugCheck(v != NULL); + +v->setNor(vertex->nor()); +v->setTex(vertex->tex()); +} +}*/ + + +/// Link colocal vertices based on geometric location only. +void Mesh::linkColocals() +{ + nvDebug("--- Linking colocals:\n"); + + const uint vertexCount = this->vertexCount(); + HashMap vertexMap(vertexCount); + + for (uint v = 0; v < vertexCount; v++) + { + Vertex * vertex = vertexAt(v); + + Vertex * colocal; + if (vertexMap.get(vertex->pos, &colocal)) + { + colocal->linkColocal(vertex); + } + else + { + vertexMap.add(vertex->pos, vertex); + } + } + + m_colocalVertexCount = vertexMap.count(); + + nvDebug("--- %d vertex positions.\n", m_colocalVertexCount); + + // @@ Remove duplicated vertices? or just leave them as colocals? +} + +void Mesh::linkColocalsWithCanonicalMap(const Array & canonicalMap) +{ + nvDebug("--- Linking colocals:\n"); + + uint vertexMapSize = 0; + foreach(i, canonicalMap) { + vertexMapSize = max(vertexMapSize, canonicalMap[i] + 1); + } + + Array vertexMap; + vertexMap.resize(vertexMapSize, NULL); + + m_colocalVertexCount = 0; + + const uint vertexCount = this->vertexCount(); + for (uint v = 0; v < vertexCount; v++) + { + Vertex * vertex = vertexAt(v); + + Vertex * colocal = vertexMap[canonicalMap[v]]; + if (colocal != NULL) + { + nvDebugCheck(vertex->pos == colocal->pos); + colocal->linkColocal(vertex); + } + else + { + vertexMap[canonicalMap[v]] = vertex; + m_colocalVertexCount++; + } + } + + nvDebug("--- %d vertex positions.\n", m_colocalVertexCount); +} + + +Face * Mesh::addFace() +{ + Face * f = new Face(m_faceArray.count()); + m_faceArray.append(f); + return f; +} + +Face * Mesh::addFace(uint v0, uint v1, uint v2) +{ + Array indexArray(3); + indexArray << v0 << v1 << v2; + return addFace(indexArray, 0, 3); +} + +Face * Mesh::addFace(uint v0, uint v1, uint v2, uint v3) +{ + Array indexArray(4); + indexArray << v0 << v1 << v2 << v3; + return addFace(indexArray, 0, 4); +} + +Face * Mesh::addFace(const Array & indexArray) +{ + return addFace(indexArray, 0, indexArray.count()); +} + + +Face * Mesh::addFace(const Array & indexArray, uint first, uint num) +{ + nvDebugCheck(first < indexArray.count()); + nvDebugCheck(num <= indexArray.count()-first); + nvDebugCheck(num > 2); + + if (!canAddFace(indexArray, first, num)) { + errorCount++; + return NULL; + } + + Face * f = new Face(m_faceArray.count()); + + Edge * firstEdge = NULL; + Edge * last = NULL; + Edge * current = NULL; + + for(uint i = 0; i < num-1; i++) + { + current = addEdge(indexArray[first+i], indexArray[first+i+1]); + nvCheck(current != NULL && current->face == NULL); + + current->face = f; + + if (last != NULL) last->setNext(current); + else firstEdge = current; + + last = current; + } + + current = addEdge(indexArray[first+num-1], indexArray[first]); + nvCheck(current != NULL && current->face == NULL); + + current->face = f; + + last->setNext(current); + current->setNext(firstEdge); + + f->edge = firstEdge; + m_faceArray.append(f); + + return f; +} + +/*void Mesh::addFaces(const Mesh * mesh) +{ +nvCheck(mesh != NULL); + +Array indexArray; +// Add faces + +}*/ + + +// Return true if the face can be added to the manifold mesh. +bool Mesh::canAddFace(const Array & indexArray, uint first, uint num) const +{ + for (uint j = num - 1, i = 0; i < num; j = i++) { + if (!canAddEdge(indexArray[first+j], indexArray[first+i])) { + errorIndex0 = indexArray[first+j]; + errorIndex1 = indexArray[first+i]; + return false; + } + } + + // We also have to make sure the face does not have any duplicate edge! + for (uint i = 0; i < num; i++) { + + int i0 = indexArray[first + i + 0]; + int i1 = indexArray[first + (i + 1)%num]; + + for (uint j = i + 1; j < num; j++) { + int j0 = indexArray[first + j + 0]; + int j1 = indexArray[first + (j + 1)%num]; + + if (i0 == j0 && i1 == j1) { + return false; + } + } + } + + return true; +} + +// Return true if the edge doesn't exist or doesn't have any adjacent face. +bool Mesh::canAddEdge(uint i, uint j) const +{ + if (i == j) { + // Skip degenerate edges. + return false; + } + + // Same check, but taking into account colocal vertices. + const Vertex * v0 = vertexAt(i); + const Vertex * v1 = vertexAt(j); + + for(Vertex::ConstVertexIterator it(v0->colocals()); !it.isDone(); it.advance()) + { + if (it.current() == v1) + { + // Skip degenerate edges. + return false; + } + } + + // Make sure edge has not been added yet. + Edge * edge = findEdge(i, j); + + return edge == NULL || edge->face == NULL; // We ignore edges that don't have an adjacent face yet, since this face could become the edge's face. +} + +Edge * Mesh::addEdge(uint i, uint j) +{ + nvCheck(i != j); + + Edge * edge = findEdge(i, j); + + if (edge != NULL) { + // Edge may already exist, but its face must not be set. + nvDebugCheck(edge->face == NULL); + + // Nothing else to do! + + } + else { + // Add new edge. + + // Lookup pair. + Edge * pair = findEdge(j, i); + + if (pair != NULL) + { + // Create edge with same id. + edge = new Edge(pair->id + 1); + + // Link edge pairs. + edge->pair = pair; + pair->pair = edge; + + // @@ I'm not sure this is necessary! + pair->vertex->setEdge(pair); + } + else + { + // Create edge. + edge = new Edge(2*m_edgeArray.count()); + + // Add only unpaired edges. + m_edgeArray.append(edge); + } + + edge->vertex = m_vertexArray[i]; + m_edgeMap.add(Key(i,j), edge); + } + + // Face and Next are set by addFace. + + return edge; +} + + +/// Find edge, test all colocals. +Edge * Mesh::findEdge(uint i, uint j) const +{ + Edge * edge = NULL; + + const Vertex * v0 = vertexAt(i); + const Vertex * v1 = vertexAt(j); + + // Test all colocal pairs. + for(Vertex::ConstVertexIterator it0(v0->colocals()); !it0.isDone(); it0.advance()) + { + for(Vertex::ConstVertexIterator it1(v1->colocals()); !it1.isDone(); it1.advance()) + { + Key key(it0.current()->id, it1.current()->id); + + if (edge == NULL) { + m_edgeMap.get(key, &edge); +#if !defined(_DEBUG) + if (edge != NULL) return edge; +#endif + } + else { + // Make sure that only one edge is found. + nvDebugCheck(!m_edgeMap.get(key)); + } + } + } + + return edge; +} + +/// Link boundary edges once the mesh has been created. +void Mesh::linkBoundary() +{ + nvDebug("--- Linking boundaries:\n"); + + int num = 0; + + // Create boundary edges. + uint edgeCount = this->edgeCount(); + for(uint e = 0; e < edgeCount; e++) + { + Edge * edge = edgeAt(e); + if (edge != NULL && edge->pair == NULL) { + Edge * pair = new Edge(edge->id + 1); + + uint i = edge->from()->id; + uint j = edge->next->from()->id; + + Key key(j,i); + nvCheck(!m_edgeMap.get(key)); + + pair->vertex = m_vertexArray[j]; + m_edgeMap.add(key, pair); + + edge->pair = pair; + pair->pair = edge; + + num++; + } + } + + // Link boundary edges. + for (uint e = 0; e < edgeCount; e++) { + Edge * edge = edgeAt(e); + if (edge != NULL && edge->pair->face == NULL) { + linkBoundaryEdge(edge->pair); + } + } + + nvDebug("--- %d boundary edges.\n", num); +} + +/// Link this boundary edge. +void Mesh::linkBoundaryEdge(Edge * edge) +{ + nvCheck(edge->face == NULL); + + // Make sure next pointer has not been set. @@ We want to be able to relink boundary edges after mesh changes. + //nvCheck(edge->next() == NULL); + + Edge * next = edge; + while(next->pair->face != NULL) { + // Get pair prev + Edge * e = next->pair->next; + while (e->next != next->pair) { + e = e->next; + } + next = e; + } + edge->setNext(next->pair); + + // Adjust vertex edge, so that it's the boundary edge. (required for isBoundary()) + if (edge->vertex->edge != edge) + { + // Multiple boundaries in the same edge. + //nvCheck( edge->vertex()->edge() == NULL || edge->vertex()->edge()->face() != NULL ); + edge->vertex->edge = edge; + } +} + + +/// Convert to tri mesh. +TriMesh * Mesh::toTriMesh() const +{ + uint triangleCount = 0; + + // Count triangle faces. + const uint faceCount = this->faceCount(); + for(uint f = 0; f < faceCount; f++) + { + const Face * face = faceAt(f); + triangleCount += face->edgeCount() - 2; + } + + TriMesh * triMesh = new TriMesh(triangleCount, vertexCount()); + + // Add vertices. + Array & vertices = triMesh->vertices(); + + const uint vertexCount = this->vertexCount(); + for(uint v = 0; v < vertexCount; v++) + { + const Vertex * vertex = vertexAt(v); + + TriMesh::Vertex triVertex; + triVertex.id = vertices.count(); + triVertex.pos = vertex->pos; + triVertex.nor = vertex->nor; + triVertex.tex = vertex->tex; + + vertices.append(triVertex); + } + + // Add triangles. + Array & triangles = triMesh->faces(); + + for(uint f = 0; f < faceCount; f++) + { + const Face * face = faceAt(f); + + // @@ Triangulate arbitrary polygons correctly. + const uint v0 = face->edge->vertex->id; + uint v1 = face->edge->next->vertex->id; + + for(Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + uint v2 = it.current()->vertex->id; + + // Skip the first two vertices. + if (v2 == v0 || v2 == v1) continue; + + TriMesh::Face triangle; + triangle.id = triangles.count(); + triangle.v[0] = v0; + triangle.v[1] = v1; + triangle.v[2] = v2; + + v1 = v2; + + triangles.append(triangle); + } + } + + return triMesh; +} + +QuadTriMesh * Mesh::toQuadTriMesh() const +{ + MeshBuilder builder; + + const uint vertexCount = this->vertexCount(); + builder.hintVertexCount(vertexCount); + + for(uint v = 0; v < vertexCount; v++) + { + const Vertex * vertex = vertexAt(v); + + builder.addPosition(vertex->pos); + builder.addNormal(vertex->nor); + builder.addTexCoord(vertex->tex); + } + + const uint faceCount = this->faceCount(); + builder.hintTriangleCount(faceCount); + + for(uint f = 0; f < faceCount; f++) + { + const Face * face = faceAt(f); + + builder.beginPolygon(); + + for(Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + uint v = it.current()->vertex->id; + builder.addVertex(v, v, v); + } + + builder.endPolygon(); + } + + builder.done(); + + return builder.buildQuadTriMesh(); +} + + +// Triangulate in place. +void Mesh::triangulate() { + + bool all_triangles = true; + + const uint faceCount = m_faceArray.count(); + for (uint f = 0; f < faceCount; f++) { + Face * face = m_faceArray[f]; + if (face->edgeCount() != 3) { + all_triangles = false; + break; + } + } + + if (all_triangles) { + return; + } + + + // Do not touch vertices, but rebuild edges and faces. + Array edgeArray; + Array faceArray; + + swap(edgeArray, m_edgeArray); + swap(faceArray, m_faceArray); + m_edgeMap.clear(); + + for (uint f = 0; f < faceCount; f++) { + Face * face = faceArray[f]; + + // Trivial fan-like triangulation. + const uint v0 = face->edge->vertex->id; + uint v2, v1 = -1; + + for (Face::EdgeIterator it(face->edges()); !it.isDone(); it.advance()) { + Edge * edge = it.current(); + v2 = edge->to()->id; + if (v2 == v0) break; + if (v1 != -1) addFace(v0, v1, v2); + v1 = v2; + } + } + + nvDebugCheck(m_faceArray.count() > faceCount); // triangle count > face count + + linkBoundary(); + + deleteAll(edgeArray); + deleteAll(faceArray); +} + + +/* +Fixing T-junctions. + +- Find T-junctions. Find vertices that are on an edge. + - This test is approximate. + - Insert edges on a spatial index to speedup queries. + - Consider only open edges, that is edges that have no pairs. + - Consider only vertices on boundaries. +- Close T-junction. + - Split edge. + +*/ +bool Mesh::splitBoundaryEdges() { + + Array boundaryVertices; + + foreach(i, m_vertexArray) { + Vertex * v = m_vertexArray[i]; + if (v->isBoundary()) { + boundaryVertices.append(v); + } + } + + nvDebug("Fixing T-junctions:\n"); + + int splitCount = 0; + + foreach(v, boundaryVertices) { + Vertex * vertex = boundaryVertices[v]; + + Vector3 x0 = vertex->pos; + + // Find edges that this vertex overlaps with. + foreach(e, m_edgeArray) { + //for (uint e = 0; e < m_edgeArray.count(); e++) { + Edge * edge = m_edgeArray[e]; + if (edge != NULL && edge->isBoundary()) { + + if (edge->from() == vertex || edge->to() == vertex) { + continue; + } + + Vector3 x1 = edge->from()->pos; + Vector3 x2 = edge->to()->pos; + + Vector3 v01 = x0 - x1; + Vector3 v21 = x2 - x1; + + float l = length(v21); + float d = length(cross(v01, v21)) / l; + + if (isZero(d)) { + float t = dot(v01, v21) / (l * l); + + // @@ Snap x0 to x1 or x2, if too close? No, do vertex snapping elsewhere. + /*if (equal(t, 0.0f, 0.01f)) { + //vertex->setPos(x1); + } + else if (equal(t, 1.0f, 0.01f)) { + //vertex->setPos(x2); + } + else*/ + if (t > 0.0f + NV_EPSILON && t < 1.0f - NV_EPSILON) { + nvDebugCheck(equal(lerp(x1, x2, t), x0)); + + Vertex * splitVertex = splitBoundaryEdge(edge, t, x0); + vertex->linkColocal(splitVertex); // @@ Should we do this here? + splitCount++; + } + } + } + } + } + + nvDebug(" - %d edges split.\n", splitCount); + + nvDebugCheck(isValid()); + + return splitCount != 0; +} + + +// For this to be effective, we have to fix the boundary junctions first. +Edge * Mesh::sewBoundary(Edge * startEdge) { + nvDebugCheck(startEdge->face == NULL); + + // @@ We may want to be more conservative linking colocals in order to preserve the input topology. One way of doing that is by linking colocals only + // if the vertices next to them are linked as well. That is, by sewing boundaries after detecting them. If any pair of consecutive edges have their first + // and last vertex in the same position, then it can be linked. + + Edge * lastBoundarySeen = startEdge; + + nvDebug("Sewing Boundary:\n"); + + int count = 0; + int sewnCount = 0; + + Edge * edge = startEdge; + do { + nvDebugCheck(edge->face == NULL); + + Edge * edge_a = edge; + Edge * edge_b = edge->prev; + + Edge * pair_a = edge_a->pair; + Edge * pair_b = edge_b->pair; + + Vertex * v0a = edge_a->to(); + Vertex * v0b = edge_b->from(); + Vertex * v1a = edge_a->from(); + Vertex * v1b = edge_b->to(); + + nvDebugCheck(v1a->isColocal(v1b)); + + /* + v0b + _+ v0a + \ / + b \ / a + \|/ + v1b + v1a + */ + + // @@ This should not happen while sewing, but it may be produced somewhere else. + nvDebugCheck(edge_a != edge_b); + + if (v0a->pos == v0b->pos) { + + // Link vertices. + v0a->linkColocal(v0b); + + // Remove edges to be collapsed. + disconnect(edge_a); + disconnect(edge_b); + disconnect(pair_a); + disconnect(pair_b); + + // Link new boundary edges. + Edge * prevBoundary = edge_b->prev; + Edge * nextBoundary = edge_a->next; + if (nextBoundary != NULL) { + nvDebugCheck(nextBoundary->face == NULL); + nvDebugCheck(prevBoundary->face == NULL); + nextBoundary->setPrev(prevBoundary); + + // Make sure boundary vertex points to boundary edge. + v0a->setEdge(nextBoundary); // This updates all colocals. + } + lastBoundarySeen = prevBoundary; + + // Creat new edge. + Edge * newEdge_a = addEdge(v0a->id, v1a->id); // pair_a->from()->id, pair_a->to()->id + Edge * newEdge_b = addEdge(v1b->id, v0b->id); + + newEdge_a->pair = newEdge_b; + newEdge_b->pair = newEdge_a; + + newEdge_a->face = pair_a->face; + newEdge_b->face = pair_b->face; + + newEdge_a->setNext(pair_a->next); + newEdge_a->setPrev(pair_a->prev); + + newEdge_b->setNext(pair_b->next); + newEdge_b->setPrev(pair_b->prev); + + delete edge_a; + delete edge_b; + delete pair_a; + delete pair_b; + + edge = nextBoundary; // If nextBoundary is NULL we have closed the loop. + sewnCount++; + } + else { + edge = edge->next; + } + + count++; + } while(edge != NULL && edge != lastBoundarySeen); + + nvDebug(" - Sewn %d out of %d.\n", sewnCount, count); + + if (lastBoundarySeen != NULL) { + nvDebugCheck(lastBoundarySeen->face == NULL); + } + + return lastBoundarySeen; +} + + +// @@ We must always disconnect edge pairs simultaneously. +void Mesh::disconnect(Edge * edge) { + nvDebugCheck(edge != NULL); + + // Remove from edge list. + if ((edge->id & 1) == 0) { + nvDebugCheck(m_edgeArray[edge->id / 2] == edge); + m_edgeArray[edge->id / 2] = NULL; + } + + // Remove edge from map. @@ Store map key inside edge? + nvDebugCheck(edge->from() != NULL && edge->to() != NULL); + bool removed = m_edgeMap.remove(Key(edge->from()->id, edge->to()->id)); + nvDebugCheck(removed == true); + + // Disconnect from vertex. + if (edge->vertex != NULL) { + if (edge->vertex->edge == edge) { + if (edge->prev && edge->prev->pair) { + edge->vertex->edge = edge->prev->pair; + } + else if (edge->pair && edge->pair->next) { + edge->vertex->edge = edge->pair->next; + } + else { + edge->vertex->edge = NULL; + // @@ Remove disconnected vertex? + } + } + //edge->setVertex(NULL); + } + + // Disconnect from face. + if (edge->face != NULL) { + if (edge->face->edge == edge) { + if (edge->next != NULL && edge->next != edge) { + edge->face->edge = edge->next; + } + else if (edge->prev != NULL && edge->prev != edge) { + edge->face->edge = edge->prev; + } + else { + edge->face->edge = NULL; + // @@ Remove disconnected face? + } + } + //edge->setFace(NULL); + } + + // @@ Hack, we don't disconnect from pair, because pair needs us to remove itself from the map. + // Disconect from pair. + /*if (edge->pair != NULL) { + if (edge->pair->pair == edge) { + edge->pair->setPair(NULL); + } + //edge->setPair(NULL); + }*/ + + // Disconnect from previous. + if (edge->prev) { + if (edge->prev->next == edge) { + edge->prev->setNext(NULL); + } + //edge->setPrev(NULL); + } + + // Disconnect from next. + if (edge->next) { + if (edge->next->prev == edge) { + edge->next->setPrev(NULL); + } + //edge->setNext(NULL); + } +} + + +void Mesh::remove(Edge * edge) { + nvDebugCheck(edge != NULL); + + disconnect(edge); + + delete edge; +} + +void Mesh::remove(Vertex * vertex) { + nvDebugCheck(vertex != NULL); + + // Remove from vertex list. + m_vertexArray[vertex->id] = NULL; + + // Disconnect from colocals. + vertex->unlinkColocal(); + + // Disconnect from edges. + if (vertex->edge != NULL) { + // @@ Removing a connected vertex is asking for trouble... + if (vertex->edge->vertex == vertex) { + // @@ Connect edge to a colocal? + vertex->edge->vertex = NULL; + } + + vertex->setEdge(NULL); + } + + delete vertex; +} + +void Mesh::remove(Face * face) { + nvDebugCheck(face != NULL); + + // Remove from face list. + m_faceArray[face->id] = NULL; + + // Disconnect from edges. + if (face->edge != NULL) { + nvDebugCheck(face->edge->face == face); + + face->edge->face = NULL; + + face->edge = NULL; + } + + delete face; +} + + +void Mesh::compactEdges() { + const uint edgeCount = m_edgeArray.count(); + + uint c = 0; + for (uint i = 0; i < edgeCount; i++) { + if (m_edgeArray[i] != NULL) { + if (i != c) { + m_edgeArray[c] = m_edgeArray[i]; + m_edgeArray[c]->id = 2 * c; + if (m_edgeArray[c]->pair != NULL) { + m_edgeArray[c]->pair->id = 2 * c + 1; + } + } + c++; + } + } + + m_edgeArray.resize(c); +} + + +void Mesh::compactVertices() { + const uint vertexCount = m_vertexArray.count(); + + uint c = 0; + for (uint i = 0; i < vertexCount; i++) { + if (m_vertexArray[i] != NULL) { + if (i != c) { + m_vertexArray[c] = m_vertexArray[i]; + m_vertexArray[c]->id = c; + } + c++; + } + } + + m_vertexArray.resize(c); + + // @@ Generate xref array for external attributes. +} + + +void Mesh::compactFaces() { + const uint faceCount = m_faceArray.count(); + + uint c = 0; + for (uint i = 0; i < faceCount; i++) { + if (m_faceArray[i] != NULL) { + if (i != c) { + m_faceArray[c] = m_faceArray[i]; + m_faceArray[c]->id = c; + } + c++; + } + } + + m_faceArray.resize(c); +} + + +Vertex * Mesh::splitBoundaryEdge(Edge * edge, float t, const Vector3 & pos) { + + /* + We want to go from this configuration: + + + + + | ^ + edge |<->| pair + v | + + + + + To this one: + + + + + | ^ + e0 |<->| p0 + v | + vertex + + + | ^ + e1 |<->| p1 + v | + + + + + */ + + + Edge * pair = edge->pair; + + // Make sure boundaries are linked. + nvDebugCheck(pair != NULL); + + // Make sure edge is a boundary edge. + nvDebugCheck(pair->face == NULL); + + // Add new vertex. + Vertex * vertex = addVertex(pos); + vertex->nor = lerp(edge->from()->nor, edge->to()->nor, t); + vertex->tex = lerp(edge->from()->tex, edge->to()->tex, t); + vertex->col = lerp(edge->from()->col, edge->to()->col, t); + + disconnect(edge); + disconnect(pair); + + // Add edges. + Edge * e0 = addEdge(edge->from()->id, vertex->id); + Edge * p0 = addEdge(vertex->id, pair->to()->id); + + Edge * e1 = addEdge(vertex->id, edge->to()->id); + Edge * p1 = addEdge(pair->from()->id, vertex->id); + + // Link edges. + e0->setNext(e1); + p1->setNext(p0); + + e0->setPrev(edge->prev); + e1->setNext(edge->next); + + p1->setPrev(pair->prev); + p0->setNext(pair->next); + + nvDebugCheck(e0->next == e1); + nvDebugCheck(e1->prev == e0); + + nvDebugCheck(p1->next == p0); + nvDebugCheck(p0->prev == p1); + + nvDebugCheck(p0->pair == e0); + nvDebugCheck(e0->pair == p0); + + nvDebugCheck(p1->pair == e1); + nvDebugCheck(e1->pair == p1); + + // Link faces. + e0->face = edge->face; + e1->face = edge->face; + + // Link vertices. + edge->from()->setEdge(e0); + vertex->setEdge(e1); + + delete edge; + delete pair; + + return vertex; +} + +#if 0 +// Without introducing new vertices. +void Mesh::splitBoundaryEdge(Edge * edge, Vertex * vertex) { + + /* + We want to go from this configuration: + + | | pn + + + + | ^ + | | + edge |<->| pair + | | + v | + + + + | | pp + + To this one: + \ / + \ / + + + + | ^ + e0 |<->| p0 + v | + vertex + + + | ^ + e1 |<->| p1 + v | + + + + / \ + / \ + */ + + + Edge * pair = edge->pair; + Edge * pn = pair->next(); + Edge * pp = pair->prev(); + + // Make sure boundaries are linked. + nvDebugCheck(pair != NULL); + + // Make sure edge is a boundary edge. + nvDebugCheck(pair->face() == NULL); + + nvDebugCheck(edge->isValid()); + nvDebugCheck(pair->isValid()); + + disconnect(edge); + disconnect(pair); + + // Add edges. + Edge * e0 = addEdge(edge->from()->id(), vertex->id()); + Edge * e1 = addEdge(vertex->id(), edge->to()->id()); + + // Link faces. + e0->setFace(edge->face()); + e1->setFace(edge->face()); + + // Link pairs. + Edge * p0 = findEdge(vertex->id(), pair->to()->id()); + if (p0 == NULL) { + p0 = addEdge(vertex->id(), pair->to()->id()); + pn->setPrev(p0); + } + else { + nvDebugCheck(p0->face() != NULL); + if (e0->prev() != NULL) { + pn->setPrev(e0->prev()); + } + else { + nvDebugCheck(pn == e0); + } + } + + Edge * p1 = findEdge(pair->from()->id(), vertex->id()); + if (p1 == NULL) { + p1 = addEdge(pair->from()->id(), vertex->id()); + pp->setNext(p1); + } + else { + nvDebugCheck(p1->face() != NULL); + if (e1->next() != NULL) { + pp->setPrev(e1->next()); + } + else { + nvDebugCheck(pp == e1); + } + } + + // Link edges. + e0->setNext(e1); // e1->setPrev(e0) + + if (p0->face() == p1->face()) { // can be null + p1->setNext(p0); // p0->setPrev(p1) + } + else { + //if (p1->face() == NULL) p1->setNext( + } + + + e0->setPrev(edge->prev()); + e1->setNext(edge->next()); + + nvDebugCheck(e0->pair == p0); + nvDebugCheck(e1->pair == p1); + nvDebugCheck(p0->pair == e0); + nvDebugCheck(p1->pair == e1); + + nvDebugCheck(e0->isValid()); + nvDebugCheck(e1->isValid()); + nvDebugCheck(pp->isValid()); + nvDebugCheck(pn->isValid()); + + nvDebugCheck(e0->pair->isValid()); + nvDebugCheck(e1->pair->isValid()); + nvDebugCheck(pp->pair->isValid()); + nvDebugCheck(pn->pair->isValid()); + + nvDebugCheck(edge->face->isValid()); + + if (pn->pair->face != NULL) { + nvDebugCheck(pn->pair->face->isValid()); + } + + if (pp->pair->face() != NULL) { + nvDebugCheck(pn->pair->face->isValid()); + } + + if (p0->face != NULL) { + nvDebugCheck(p0->face->isValid()); + } + + if (p1->face() != NULL) { + nvDebugCheck(p1->face()->isValid()); + } + + nvDebugCheck(isValid()); // Only for extreme debugging. + + // Link vertices. + edge->from()->setEdge(e0); + vertex->setEdge(p0); + + delete edge; + delete pair; +} +#endif + +bool Mesh::isValid() const +{ + // Make sure all edges are valid. + const uint edgeCount = m_edgeArray.count(); + for (uint e = 0; e < edgeCount; e++) { + Edge * edge = m_edgeArray[e]; + if (edge != NULL) { + if (edge->id != 2*e) { + return false; + } + if (!edge->isValid()) { + return false; + } + + if (edge->pair->id != 2*e+1) { + return false; + } + if (!edge->pair->isValid()) { + return false; + } + } + } + + // @@ Make sure all faces are valid. + + // @@ Make sure all vertices are valid. + + return true; +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/halfedge/Mesh.h b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Mesh.h new file mode 100755 index 00000000..c202c2ef --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Mesh.h @@ -0,0 +1,274 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MESH_HALFEDGE_MESH_H +#define NV_MESH_HALFEDGE_MESH_H + +#include "nvmesh/nvmesh.h" +#include "nvcore/Array.h" +#include "nvcore/HashMap.h" + +/* +If I were to redo this again, there are a number of things that I would do differently. +- Edge map is only useful when importing a mesh to guarantee the result is two-manifold. However, when manipulating the mesh + it's a pain to maintain the map up to date. +- Edge array only points to the even vertices. There's no good reason for that. The map becomes required to traverse all edges + or you have to make sure edges are properly paired. +- Linked boundaries. It's cleaner to assume a NULL pair means a boundary edge. Makes easier to seal boundaries. The only reason + why we link boundaries is to simplify traversal, but that could be done with two helper functions (nextBoundary, prevBoundary). +- Minimize the amount of state that needs to be set in a certain way: + - boundary vertices point to boundary edge. +- Remove parenthesis! Make some members public. +- Remove member functions with side effects: + - e->setNext(n) modifies e->next and n->prev, instead use "link(e, n)", or "e->next = n, n->prev = e" +*/ + + +namespace nv +{ + class Vector3; + class TriMesh; + class QuadTriMesh; + //template struct Hash; + + namespace HalfEdge + { + class Edge; + class Face; + class Vertex; + + /// Simple half edge mesh designed for dynamic mesh manipulation. + class Mesh + { + public: + + Mesh(); + Mesh(const Mesh * mesh); + ~Mesh(); + + void clear(); + + Vertex * addVertex(const Vector3 & pos); + //Vertex * addVertex(uint id, const Vector3 & pos); + //void addVertices(const Mesh * mesh); + + void linkColocals(); + void linkColocalsWithCanonicalMap(const Array & canonicalMap); + void resetColocalLinks(); + + Face * addFace(); + Face * addFace(uint v0, uint v1, uint v2); + Face * addFace(uint v0, uint v1, uint v2, uint v3); + Face * addFace(const Array & indexArray); + Face * addFace(const Array & indexArray, uint first, uint num); + //void addFaces(const Mesh * mesh); + + // These functions disconnect the given element from the mesh and delete it. + void disconnect(Edge * edge); + void disconnectPair(Edge * edge); + void disconnect(Vertex * vertex); + void disconnect(Face * face); + + void remove(Edge * edge); + void remove(Vertex * vertex); + void remove(Face * face); + + // Remove holes from arrays and reassign indices. + void compactEdges(); + void compactVertices(); + void compactFaces(); + + void triangulate(); + + void linkBoundary(); + + bool splitBoundaryEdges(); // Returns true if any split was made. + + // Sew the boundary that starts at the given edge, returns one edge that still belongs to boundary, or NULL if boundary closed. + HalfEdge::Edge * sewBoundary(Edge * startEdge); + + + // Vertices + uint vertexCount() const { return m_vertexArray.count(); } + const Vertex * vertexAt(int i) const { return m_vertexArray[i]; } + Vertex * vertexAt(int i) { return m_vertexArray[i]; } + + uint colocalVertexCount() const { return m_colocalVertexCount; } + + // Faces + uint faceCount() const { return m_faceArray.count(); } + const Face * faceAt(int i) const { return m_faceArray[i]; } + Face * faceAt(int i) { return m_faceArray[i]; } + + // Edges + uint edgeCount() const { return m_edgeArray.count(); } + const Edge * edgeAt(int i) const { return m_edgeArray[i]; } + Edge * edgeAt(int i) { return m_edgeArray[i]; } + + class ConstVertexIterator; + + class VertexIterator + { + friend class ConstVertexIterator; + public: + VertexIterator(Mesh * mesh) : m_mesh(mesh), m_current(0) { } + + virtual void advance() { m_current++; } + virtual bool isDone() const { return m_current == m_mesh->vertexCount(); } + virtual Vertex * current() const { return m_mesh->vertexAt(m_current); } + + private: + HalfEdge::Mesh * m_mesh; + uint m_current; + }; + VertexIterator vertices() { return VertexIterator(this); } + + class ConstVertexIterator + { + public: + ConstVertexIterator(const Mesh * mesh) : m_mesh(mesh), m_current(0) { } + ConstVertexIterator(class VertexIterator & it) : m_mesh(it.m_mesh), m_current(it.m_current) { } + + virtual void advance() { m_current++; } + virtual bool isDone() const { return m_current == m_mesh->vertexCount(); } + virtual const Vertex * current() const { return m_mesh->vertexAt(m_current); } + + private: + const HalfEdge::Mesh * m_mesh; + uint m_current; + }; + ConstVertexIterator vertices() const { return ConstVertexIterator(this); } + + class ConstFaceIterator; + + class FaceIterator + { + friend class ConstFaceIterator; + public: + FaceIterator(Mesh * mesh) : m_mesh(mesh), m_current(0) { } + + virtual void advance() { m_current++; } + virtual bool isDone() const { return m_current == m_mesh->faceCount(); } + virtual Face * current() const { return m_mesh->faceAt(m_current); } + + private: + HalfEdge::Mesh * m_mesh; + uint m_current; + }; + FaceIterator faces() { return FaceIterator(this); } + + class ConstFaceIterator + { + public: + ConstFaceIterator(const Mesh * mesh) : m_mesh(mesh), m_current(0) { } + ConstFaceIterator(const FaceIterator & it) : m_mesh(it.m_mesh), m_current(it.m_current) { } + + virtual void advance() { m_current++; } + virtual bool isDone() const { return m_current == m_mesh->faceCount(); } + virtual const Face * current() const { return m_mesh->faceAt(m_current); } + + private: + const HalfEdge::Mesh * m_mesh; + uint m_current; + }; + ConstFaceIterator faces() const { return ConstFaceIterator(this); } + + class ConstEdgeIterator; + + class EdgeIterator + { + friend class ConstEdgeIterator; + public: + EdgeIterator(Mesh * mesh) : m_mesh(mesh), m_current(0) { } + + virtual void advance() { m_current++; } + virtual bool isDone() const { return m_current == m_mesh->edgeCount(); } + virtual Edge * current() const { return m_mesh->edgeAt(m_current); } + + private: + HalfEdge::Mesh * m_mesh; + uint m_current; + }; + EdgeIterator edges() { return EdgeIterator(this); } + + class ConstEdgeIterator + { + public: + ConstEdgeIterator(const Mesh * mesh) : m_mesh(mesh), m_current(0) { } + ConstEdgeIterator(const EdgeIterator & it) : m_mesh(it.m_mesh), m_current(it.m_current) { } + + virtual void advance() { m_current++; } + virtual bool isDone() const { return m_current == m_mesh->edgeCount(); } + virtual const Edge * current() const { return m_mesh->edgeAt(m_current); } + + private: + const HalfEdge::Mesh * m_mesh; + uint m_current; + }; + ConstEdgeIterator edges() const { return ConstEdgeIterator(this); } + + // @@ Add half-edge iterator. + + + + // Convert to tri mesh. + TriMesh * toTriMesh() const; + QuadTriMesh * toQuadTriMesh() const; + + bool isValid() const; + + public: + + // Error status: + mutable uint errorCount; + mutable uint errorIndex0; + mutable uint errorIndex1; + + private: + + bool canAddFace(const Array & indexArray, uint first, uint num) const; + bool canAddEdge(uint i, uint j) const; + Edge * addEdge(uint i, uint j); + + Edge * findEdge(uint i, uint j) const; + + void linkBoundaryEdge(Edge * edge); + Vertex * splitBoundaryEdge(Edge * edge, float t, const Vector3 & pos); + void splitBoundaryEdge(Edge * edge, Vertex * vertex); + + private: + + Array m_vertexArray; + Array m_edgeArray; + Array m_faceArray; + + struct Key { + Key() {} + Key(const Key & k) : p0(k.p0), p1(k.p1) {} + Key(uint v0, uint v1) : p0(v0), p1(v1) {} + void operator=(const Key & k) { p0 = k.p0; p1 = k.p1; } + bool operator==(const Key & k) const { return p0 == k.p0 && p1 == k.p1; } + + uint p0; + uint p1; + }; + friend struct Hash; + + HashMap m_edgeMap; + + uint m_colocalVertexCount; + + }; + /* + // This is a much better hash than the default and greatly improves performance! + template <> struct hash + { + uint operator()(const Mesh::Key & k) const { return k.p0 + k.p1; } + }; + */ + + } // HalfEdge namespace + +} // nv namespace + +#endif // NV_MESH_HALFEDGE_MESH_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/halfedge/Vertex.cpp b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Vertex.cpp new file mode 100755 index 00000000..66dad69f --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Vertex.cpp @@ -0,0 +1,94 @@ +// This code is in the public domain -- castano@gmail.com + +#include "nvmesh.h" // pch + +#include "Vertex.h" + +#include "nvmath/Vector.inl" + +using namespace nv; +using namespace HalfEdge; + + +// Set first edge of all colocals. +void Vertex::setEdge(Edge * e) +{ + for (VertexIterator it(colocals()); !it.isDone(); it.advance()) { + it.current()->edge = e; + } +} + +// Update position of all colocals. +void Vertex::setPos(const Vector3 & p) +{ + for (VertexIterator it(colocals()); !it.isDone(); it.advance()) { + it.current()->pos = p; + } +} + + +uint HalfEdge::Vertex::colocalCount() const +{ + uint count = 0; + for (ConstVertexIterator it(colocals()); !it.isDone(); it.advance()) { ++count; } + return count; +} + +uint HalfEdge::Vertex::valence() const +{ + uint count = 0; + for (ConstEdgeIterator it(edges()); !it.isDone(); it.advance()) { ++count; } + return count; +} + +const HalfEdge::Vertex * HalfEdge::Vertex::firstColocal() const +{ + uint firstId = id; + const Vertex * vertex = this; + + for (ConstVertexIterator it(colocals()); !it.isDone(); it.advance()) + { + if (it.current()->id < firstId) { + firstId = vertex->id; + vertex = it.current(); + } + } + + return vertex; +} + +HalfEdge::Vertex * HalfEdge::Vertex::firstColocal() +{ + Vertex * vertex = this; + uint firstId = id; + + for (VertexIterator it(colocals()); !it.isDone(); it.advance()) + { + if (it.current()->id < firstId) { + firstId = vertex->id; + vertex = it.current(); + } + } + + return vertex; +} + +bool HalfEdge::Vertex::isFirstColocal() const +{ + return firstColocal() == this; +} + +bool HalfEdge::Vertex::isColocal(const Vertex * v) const { + if (this == v) return true; + if (pos != v->pos) return false; + + for (ConstVertexIterator it(colocals()); !it.isDone(); it.advance()) + { + if (v == it.current()) { + return true; + } + } + + return false; +} + diff --git a/thirdparty/thekla_atlas/src/nvmesh/halfedge/Vertex.h b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Vertex.h new file mode 100755 index 00000000..1c5c8d71 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/halfedge/Vertex.h @@ -0,0 +1,221 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MESH_HALFEDGE_VERTEX_H +#define NV_MESH_HALFEDGE_VERTEX_H + +#include "nvmesh/halfedge/Edge.h" + +namespace nv +{ + namespace HalfEdge { class Vertex; class Face; class Edge; } + + // Half edge vertex. + class HalfEdge::Vertex + { + NV_FORBID_COPY(Vertex); + public: + + uint id; + + Edge * edge; + Vertex * next; + Vertex * prev; + + Vector3 pos; + Vector3 nor; + Vector2 tex; + Vector4 col; + + + Vertex(uint id) : id(id), edge(NULL), pos(0.0f), nor(0.0f), tex(0.0f), col(0.0f) { + next = this; + prev = this; + } + + + void setEdge(Edge * e); + void setPos(const Vector3 & p); + + uint colocalCount() const; + uint valence() const; + bool isFirstColocal() const; + const Vertex * firstColocal() const; + Vertex * firstColocal(); + + bool isColocal(const Vertex * v) const; + + + void linkColocal(Vertex * v) { + next->prev = v; + v->next = next; + next = v; + v->prev = this; + } + void unlinkColocal() { + next->prev = prev; + prev->next = next; + next = this; + prev = this; + } + + + // @@ Note: This only works if linkBoundary has been called. + bool isBoundary() const { + return (edge && !edge->face); + } + + + // for(EdgeIterator it(iterator()); !it.isDone(); it.advance()) { ... } + // + // EdgeIterator it(iterator()); + // while(!it.isDone()) { + // ... + // id.advance(); + // } + + // Iterator that visits the edges around this vertex in counterclockwise order. + class EdgeIterator //: public Iterator + { + public: + EdgeIterator(Edge * e) : m_end(NULL), m_current(e) { } + + virtual void advance() + { + if (m_end == NULL) m_end = m_current; + m_current = m_current->pair->next; + //m_current = m_current->prev->pair; + } + + virtual bool isDone() const { return m_end == m_current; } + virtual Edge * current() const { return m_current; } + Vertex * vertex() const { return m_current->vertex; } + + private: + Edge * m_end; + Edge * m_current; + }; + + EdgeIterator edges() { return EdgeIterator(edge); } + EdgeIterator edges(Edge * e) { return EdgeIterator(e); } + + // Iterator that visits the edges around this vertex in counterclockwise order. + class ConstEdgeIterator //: public Iterator + { + public: + ConstEdgeIterator(const Edge * e) : m_end(NULL), m_current(e) { } + ConstEdgeIterator(EdgeIterator it) : m_end(NULL), m_current(it.current()) { } + + virtual void advance() + { + if (m_end == NULL) m_end = m_current; + m_current = m_current->pair->next; + //m_current = m_current->prev->pair; + } + + virtual bool isDone() const { return m_end == m_current; } + virtual const Edge * current() const { return m_current; } + const Vertex * vertex() const { return m_current->to(); } + + private: + const Edge * m_end; + const Edge * m_current; + }; + + ConstEdgeIterator edges() const { return ConstEdgeIterator(edge); } + ConstEdgeIterator edges(const Edge * e) const { return ConstEdgeIterator(e); } + + + // Iterator that visits the edges around this vertex in counterclockwise order. + class ReverseEdgeIterator //: public Iterator + { + public: + ReverseEdgeIterator(Edge * e) : m_end(NULL), m_current(e) { } + + virtual void advance() + { + if (m_end == NULL) m_end = m_current; + m_current = m_current->prev->pair; + } + + virtual bool isDone() const { return m_end == m_current; } + virtual Edge * current() const { return m_current; } + Vertex * vertex() const { return m_current->vertex; } + + private: + Edge * m_end; + Edge * m_current; + }; + + // Iterator that visits the edges around this vertex in counterclockwise order. + class ReverseConstEdgeIterator //: public Iterator + { + public: + ReverseConstEdgeIterator(const Edge * e) : m_end(NULL), m_current(e) { } + + virtual void advance() + { + if (m_end == NULL) m_end = m_current; + m_current = m_current->prev->pair; + } + + virtual bool isDone() const { return m_end == m_current; } + virtual const Edge * current() const { return m_current; } + const Vertex * vertex() const { return m_current->to(); } + + private: + const Edge * m_end; + const Edge * m_current; + }; + + + + // Iterator that visits all the colocal vertices. + class VertexIterator //: public Iterator + { + public: + VertexIterator(Vertex * v) : m_end(NULL), m_current(v) { } + + virtual void advance() + { + if (m_end == NULL) m_end = m_current; + m_current = m_current->next; + } + + virtual bool isDone() const { return m_end == m_current; } + virtual Vertex * current() const { return m_current; } + + private: + Vertex * m_end; + Vertex * m_current; + }; + + VertexIterator colocals() { return VertexIterator(this); } + + // Iterator that visits all the colocal vertices. + class ConstVertexIterator //: public Iterator + { + public: + ConstVertexIterator(const Vertex * v) : m_end(NULL), m_current(v) { } + + virtual void advance() + { + if (m_end == NULL) m_end = m_current; + m_current = m_current->next; + } + + virtual bool isDone() const { return m_end == m_current; } + virtual const Vertex * current() const { return m_current; } + + private: + const Vertex * m_end; + const Vertex * m_current; + }; + + ConstVertexIterator colocals() const { return ConstVertexIterator(this); } + + }; + +} // nv namespace + +#endif // NV_MESH_HALFEDGE_VERTEX_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/nvmesh.cpp b/thirdparty/thekla_atlas/src/nvmesh/nvmesh.cpp new file mode 100755 index 00000000..d007eda3 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/nvmesh.cpp @@ -0,0 +1,2 @@ +#include "nvmesh.h" // pch + diff --git a/thirdparty/thekla_atlas/src/nvmesh/nvmesh.h b/thirdparty/thekla_atlas/src/nvmesh/nvmesh.h new file mode 100755 index 00000000..eb681967 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/nvmesh.h @@ -0,0 +1,34 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MESH_H +#define NV_MESH_H + +#include "nvcore/nvcore.h" + +// Function linkage +#if NVMESH_SHARED +#ifdef NVMESH_EXPORTS +#define NVMESH_API DLL_EXPORT +#define NVMESH_CLASS DLL_EXPORT_CLASS +#else +#define NVMESH_API DLL_IMPORT +#define NVMESH_CLASS DLL_IMPORT +#endif +#else +#define NVMESH_API +#define NVMESH_CLASS +#endif + +#if 1 //USE_PRECOMPILED_HEADERS // If using precompiled headers: +//#include // strlen, strcmp, etc. +//#include "nvcore/StrLib.h" +//#include "nvcore/StdStream.h" +//#include "nvcore/Memory.h" +//#include "nvcore/Debug.h" +//#include "nvmath/Vector.h" +//#include "nvcore/Array.h" +//#include "nvcore/HashMap.h" +#endif + +#endif // NV_MESH_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/Atlas.cpp b/thirdparty/thekla_atlas/src/nvmesh/param/Atlas.cpp new file mode 100755 index 00000000..4e41e766 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/Atlas.cpp @@ -0,0 +1,1515 @@ +// Copyright NVIDIA Corporation 2006 -- Ignacio Castano + +#include "nvmesh.h" // pch + +#include "Atlas.h" +#include "Util.h" +#include "AtlasBuilder.h" +#include "AtlasPacker.h" +#include "SingleFaceMap.h" +#include "OrthogonalProjectionMap.h" +#include "LeastSquaresConformalMap.h" +#include "ParameterizationQuality.h" + +//#include "nvmesh/export/MeshExportOBJ.h" + +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Face.h" +#include "nvmesh/halfedge/Vertex.h" + +#include "nvmesh/MeshBuilder.h" +#include "nvmesh/MeshTopology.h" +#include "nvmesh/param/Util.h" +#include "nvmesh/geometry/Measurements.h" + +#include "nvmath/Vector.inl" +#include "nvmath/Fitting.h" +#include "nvmath/Box.inl" +#include "nvmath/ProximityGrid.h" +#include "nvmath/Morton.h" + +#include "nvcore/StrLib.h" +#include "nvcore/Array.inl" +#include "nvcore/HashMap.inl" + +using namespace nv; + + +/// Ctor. +Atlas::Atlas() +{ +} + +// Dtor. +Atlas::~Atlas() +{ + deleteAll(m_meshChartsArray); +} + +uint Atlas::chartCount() const +{ + uint count = 0; + foreach(c, m_meshChartsArray) { + count += m_meshChartsArray[c]->chartCount(); + } + return count; +} + +const Chart * Atlas::chartAt(uint i) const +{ + foreach(c, m_meshChartsArray) { + uint count = m_meshChartsArray[c]->chartCount(); + + if (i < count) { + return m_meshChartsArray[c]->chartAt(i); + } + + i -= count; + } + + return NULL; +} + +Chart * Atlas::chartAt(uint i) +{ + foreach(c, m_meshChartsArray) { + uint count = m_meshChartsArray[c]->chartCount(); + + if (i < count) { + return m_meshChartsArray[c]->chartAt(i); + } + + i -= count; + } + + return NULL; +} + +// Extract the charts and add to this atlas. +void Atlas::addMeshCharts(MeshCharts * meshCharts) +{ + m_meshChartsArray.append(meshCharts); +} + +void Atlas::extractCharts(const HalfEdge::Mesh * mesh) +{ + MeshCharts * meshCharts = new MeshCharts(mesh); + meshCharts->extractCharts(); + addMeshCharts(meshCharts); +} + +void Atlas::computeCharts(const HalfEdge::Mesh * mesh, const SegmentationSettings & settings, const Array & unchartedMaterialArray) +{ + MeshCharts * meshCharts = new MeshCharts(mesh); + meshCharts->computeCharts(settings, unchartedMaterialArray); + addMeshCharts(meshCharts); +} + + + + +#if 0 + +/// Compute a seamless texture atlas. +bool Atlas::computeSeamlessTextureAtlas(bool groupFaces/*= true*/, bool scaleTiles/*= false*/, uint w/*= 1024*/, uint h/* = 1024*/) +{ + // Implement seamless texture atlas similar to what ZBrush does. See also: + // "Meshed Atlases for Real-Time Procedural Solid Texturing" + // http://graphics.cs.uiuc.edu/~jch/papers/rtpst.pdf + + // Other methods that we should experiment with: + // + // Seamless Texture Atlases: + // http://www.cs.jhu.edu/~bpurnomo/STA/index.html + // + // Rectangular Multi-Chart Geometry Images: + // http://graphics.cs.uiuc.edu/~jch/papers/rmcgi.pdf + // + // Discrete differential geometry also provide a way of constructing + // seamless quadrangulations as shown in: + // http://www.geometry.caltech.edu/pubs/TACD06.pdf + // + +#pragma message(NV_FILE_LINE "TODO: Implement seamless texture atlas.") + + if (groupFaces) + { + // @@ TODO. + } + else + { + // @@ Create one atlas per face. + } + + if (scaleTiles) + { + // @@ TODO + } + + /* + if (!isQuadMesh(m_mesh)) { + // Only handle quads for now. + return false; + } + + // Each face is a chart. + const uint faceCount = m_mesh->faceCount(); + m_chartArray.resize(faceCount); + + for(uint f = 0; f < faceCount; f++) { + m_chartArray[f].faceArray.clear(); + m_chartArray[f].faceArray.append(f); + } + + // Map each face to a separate square. + + // Determine face layout according to width and height. + float aspect = float(m_width) / float(m_height); + + uint i = 2; + uint total = (m_width / (i+1)) * (m_height / (i+1)); + while(total > faceCount) { + i *= 2; + total = (m_width / (i+1)) * (m_height / (i+1)); + } + + uint tileSize = i / 2; + + int x = 0; + int y = 0; + + m_result = new HalfEdge::Mesh(); + + // Once you have that it's just matter of traversing the faces. + for(uint f = 0; f < faceCount; f++) { + // Compute texture coordinates. + Vector2 tex[4]; + tex[0] = Vector2(float(x), float(y)); + tex[1] = Vector2(float(x+tileSize), float(y)); + tex[2] = Vector2(float(x+tileSize), float(y+tileSize)); + tex[3] = Vector2(float(x), float(y+tileSize)); + + Array indexArray(4); + + const HalfEdge::Face * face = m_mesh->faceAt(f); + + int i = 0; + for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance(), i++) { + const HalfEdge::Edge * edge = it.current(); + const HalfEdge::Vertex * vertex = edge->from(); + + HalfEdge::Vertex * newVertex = m_result->addVertex(vertex->id(), vertex->pos()); + + newVertex->setTex(Vector3(tex[i], 0)); + newVertex->setNor(vertex->nor()); + + indexArray.append(m_result->vertexCount() + 1); + } + + m_result->addFace(indexArray); + + // Move to the next tile. + x += tileSize + 1; + if (x + tileSize > m_width) { + x = 0; + y += tileSize + 1; + } + } + */ + + return false; +} + +#endif + + +void Atlas::parameterizeCharts() +{ + foreach(i, m_meshChartsArray) { + m_meshChartsArray[i]->parameterizeCharts(); + } +} + + +float Atlas::packCharts(int quality, float texelsPerUnit, bool blockAlign, bool conservative) +{ + AtlasPacker packer(this); + packer.packCharts(quality, texelsPerUnit, blockAlign, conservative); + return packer.computeAtlasUtilization(); +} + + + + +/// Ctor. +MeshCharts::MeshCharts(const HalfEdge::Mesh * mesh) : m_mesh(mesh) +{ +} + +// Dtor. +MeshCharts::~MeshCharts() +{ + deleteAll(m_chartArray); +} + + +void MeshCharts::extractCharts() +{ + const uint faceCount = m_mesh->faceCount(); + + int first = 0; + Array queue(faceCount); + + BitArray bitFlags(faceCount); + bitFlags.clearAll(); + + for (uint f = 0; f < faceCount; f++) + { + if (bitFlags.bitAt(f) == false) + { + // Start new patch. Reset queue. + first = 0; + queue.clear(); + queue.append(f); + bitFlags.setBitAt(f); + + while (first != queue.count()) + { + const HalfEdge::Face * face = m_mesh->faceAt(queue[first]); + + // Visit face neighbors of queue[first] + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + nvDebugCheck(edge->pair != NULL); + + if (!edge->isBoundary() && /*!edge->isSeam()*/ + //!(edge->from()->tex() != edge->pair()->to()->tex() || edge->to()->tex() != edge->pair()->from()->tex())) + !(edge->from() != edge->pair->to() || edge->to() != edge->pair->from())) // Preserve existing seams (not just texture seams). + { + const HalfEdge::Face * neighborFace = edge->pair->face; + nvDebugCheck(neighborFace != NULL); + + if (bitFlags.bitAt(neighborFace->id) == false) + { + queue.append(neighborFace->id); + bitFlags.setBitAt(neighborFace->id); + } + } + } + + first++; + } + + Chart * chart = new Chart(); + chart->build(m_mesh, queue); + + m_chartArray.append(chart); + } + } +} + + +/* +LSCM: +- identify sharp features using local dihedral angles. +- identify seed faces farthest from sharp features. +- grow charts from these seeds. + +MCGIM: +- phase 1: chart growth + - grow all charts simultaneously using dijkstra search on the dual graph of the mesh. + - graph edges are weighted based on planarity metric. + - metric uses distance to global chart normal. + - terminate when all faces have been assigned. +- phase 2: seed computation: + - place new seed of the chart at the most interior face. + - most interior is evaluated using distance metric only. + +- method repeates the two phases, until the location of the seeds does not change. + - cycles are detected by recording all the previous seeds and chartification terminates. + +D-Charts: + +- Uniaxial conic metric: + - N_c = axis of the generalized cone that best fits the chart. (cone can a be cylinder or a plane). + - omega_c = angle between the face normals and the axis. + - Fitting error between chart C and tringle t: F(c,t) = (N_c*n_t - cos(omega_c))^2 + +- Compactness metrics: + - Roundness: + - C(c,t) = pi * D(S_c,t)^2 / A_c + - S_c = chart seed. + - D(S_c,t) = length of the shortest path inside the chart betwen S_c and t. + - A_c = chart area. + - Straightness: + - P(c,t) = l_out(c,t) / l_in(c,t) + - l_out(c,t) = lenght of the edges not shared between C and t. + - l_in(c,t) = lenght of the edges shared between C and t. + +- Combined metric: + - Cost(c,t) = F(c,t)^alpha + C(c,t)^beta + P(c,t)^gamma + - alpha = 1, beta = 0.7, gamma = 0.5 + + + + +Our basic approach: +- Just one iteration of k-means? +- Avoid dijkstra by greedily growing charts until a threshold is met. Increase threshold and repeat until no faces left. +- If distortion metric is too high, split chart, add two seeds. +- If chart size is low, try removing chart. + + +Postprocess: +- If topology is not disk: + - Fill holes, if new faces fit proxy. + - Find best cut, otherwise. +- After parameterization: + - If boundary self-intersects: + - cut chart along the closest two diametral boundary vertices, repeat parametrization. + - what if the overlap is on an appendix? How do we find that out and cut appropiately? + - emphasize roundness metrics to prevent those cases. + - If interior self-overlaps: preserve boundary parameterization and use mean-value map. + +*/ + + +SegmentationSettings::SegmentationSettings() +{ + // Charts have no area or boundary limits right now. + maxChartArea = NV_FLOAT_MAX; + maxBoundaryLength = NV_FLOAT_MAX; + + proxyFitMetricWeight = 1.0f; + roundnessMetricWeight = 0.1f; + straightnessMetricWeight = 0.25f; + normalSeamMetricWeight = 1.0f; + textureSeamMetricWeight = 0.1f; +} + + + +void MeshCharts::computeCharts(const SegmentationSettings & settings, const Array & unchartedMaterialArray) +{ + Chart * vertexMap = NULL; + + if (unchartedMaterialArray.count() != 0) { + vertexMap = new Chart(); + vertexMap->buildVertexMap(m_mesh, unchartedMaterialArray); + + if (vertexMap->faceCount() == 0) { + delete vertexMap; + vertexMap = NULL; + } + } + + + AtlasBuilder builder(m_mesh); + + if (vertexMap != NULL) { + // Mark faces that do not need to be charted. + builder.markUnchartedFaces(vertexMap->faceArray()); + + m_chartArray.append(vertexMap); + } + + if (builder.facesLeft != 0) { + + // Tweak these values: + const float maxThreshold = 2; + const uint growFaceCount = 32; + const uint maxIterations = 4; + + builder.settings = settings; + + //builder.settings.proxyFitMetricWeight *= 0.75; // relax proxy fit weight during initial seed placement. + //builder.settings.roundnessMetricWeight = 0; + //builder.settings.straightnessMetricWeight = 0; + + // This seems a reasonable estimate. + uint maxSeedCount = max(6U, builder.facesLeft); + + // Create initial charts greedely. + nvDebug("### Placing seeds\n"); + builder.placeSeeds(maxThreshold, maxSeedCount); + nvDebug("### Placed %d seeds (max = %d)\n", builder.chartCount(), maxSeedCount); + + builder.updateProxies(); + + builder.mergeCharts(); + + #if 1 + nvDebug("### Relocating seeds\n"); + builder.relocateSeeds(); + + nvDebug("### Reset charts\n"); + builder.resetCharts(); + + if (vertexMap != NULL) { + builder.markUnchartedFaces(vertexMap->faceArray()); + } + + builder.settings = settings; + + nvDebug("### Growing charts\n"); + + // Restart process growing charts in parallel. + uint iteration = 0; + while (true) + { + if (!builder.growCharts(maxThreshold, growFaceCount)) + { + nvDebug("### Can't grow anymore\n"); + + // If charts cannot grow more: fill holes, merge charts, relocate seeds and start new iteration. + + nvDebug("### Filling holes\n"); + builder.fillHoles(maxThreshold); + nvDebug("### Using %d charts now\n", builder.chartCount()); + + builder.updateProxies(); + + nvDebug("### Merging charts\n"); + builder.mergeCharts(); + nvDebug("### Using %d charts now\n", builder.chartCount()); + + nvDebug("### Reseeding\n"); + if (!builder.relocateSeeds()) + { + nvDebug("### Cannot relocate seeds anymore\n"); + + // Done! + break; + } + + if (iteration == maxIterations) + { + nvDebug("### Reached iteration limit\n"); + break; + } + iteration++; + + nvDebug("### Reset charts\n"); + builder.resetCharts(); + + if (vertexMap != NULL) { + builder.markUnchartedFaces(vertexMap->faceArray()); + } + + nvDebug("### Growing charts\n"); + } + }; + #endif + + // Make sure no holes are left! + nvDebugCheck(builder.facesLeft == 0); + + const uint chartCount = builder.chartArray.count(); + for (uint i = 0; i < chartCount; i++) + { + Chart * chart = new Chart(); + m_chartArray.append(chart); + + chart->build(m_mesh, builder.chartFaces(i)); + } + } + + + const uint chartCount = m_chartArray.count(); + + // Build face indices. + m_faceChart.resize(m_mesh->faceCount()); + m_faceIndex.resize(m_mesh->faceCount()); + + for (uint i = 0; i < chartCount; i++) + { + const Chart * chart = m_chartArray[i]; + + const uint faceCount = chart->faceCount(); + for (uint f = 0; f < faceCount; f++) + { + uint idx = chart->faceAt(f); + m_faceChart[idx] = i; + m_faceIndex[idx] = f; + } + } + + // Build an exclusive prefix sum of the chart vertex counts. + m_chartVertexCountPrefixSum.resize(chartCount); + + if (chartCount > 0) + { + m_chartVertexCountPrefixSum[0] = 0; + + for (uint i = 1; i < chartCount; i++) + { + const Chart * chart = m_chartArray[i-1]; + m_chartVertexCountPrefixSum[i] = m_chartVertexCountPrefixSum[i-1] + chart->vertexCount(); + } + + m_totalVertexCount = m_chartVertexCountPrefixSum[chartCount - 1] + m_chartArray[chartCount-1]->vertexCount(); + } + else + { + m_totalVertexCount = 0; + } +} + + +void MeshCharts::parameterizeCharts() +{ + ParameterizationQuality globalParameterizationQuality; + + // Parameterize the charts. + uint diskCount = 0; + const uint chartCount = m_chartArray.count(); + for (uint i = 0; i < chartCount; i++)\ + { + Chart * chart = m_chartArray[i]; + + bool isValid = false; + + if (chart->isVertexMapped()) { + continue; + } + + if (chart->isDisk()) + { + diskCount++; + + ParameterizationQuality chartParameterizationQuality; + + if (chart->faceCount() == 1) { + computeSingleFaceMap(chart->unifiedMesh()); + + chartParameterizationQuality = ParameterizationQuality(chart->unifiedMesh()); + } + else { + computeOrthogonalProjectionMap(chart->unifiedMesh()); + ParameterizationQuality orthogonalQuality(chart->unifiedMesh()); + + computeLeastSquaresConformalMap(chart->unifiedMesh()); + ParameterizationQuality lscmQuality(chart->unifiedMesh()); + + // If the orthogonal projection produces better results, just use that. + // @@ It may be dangerous to do this, because isValid() does not detect self-overlaps. + // @@ Another problem is that with very thin patches with nearly zero parametric area, the results of our metric are not accurate. + /*if (orthogonalQuality.isValid() && orthogonalQuality.rmsStretchMetric() < lscmQuality.rmsStretchMetric()) { + computeOrthogonalProjectionMap(chart->unifiedMesh()); + chartParameterizationQuality = orthogonalQuality; + } + else*/ { + chartParameterizationQuality = lscmQuality; + } + + // If conformal map failed, + + // @@ Experiment with other parameterization methods. + //computeCircularBoundaryMap(chart->unifiedMesh()); + //computeConformalMap(chart->unifiedMesh()); + //computeNaturalConformalMap(chart->unifiedMesh()); + //computeGuidanceGradientMap(chart->unifiedMesh()); + } + + //ParameterizationQuality chartParameterizationQuality(chart->unifiedMesh()); + + isValid = chartParameterizationQuality.isValid(); + + if (!isValid) + { + nvDebug("*** Invalid parameterization.\n"); +#if 0 + // Dump mesh to inspect problem: + static int pieceCount = 0; + + StringBuilder fileName; + fileName.format("invalid_chart_%d.obj", pieceCount++); + exportMesh(chart->unifiedMesh(), fileName.str()); +#endif + } + + // @@ Check that parameterization quality is above a certain threshold. + + // @@ Detect boundary self-intersections. + + globalParameterizationQuality += chartParameterizationQuality; + } + + if (!isValid) + { + //nvDebugBreak(); + // @@ Run the builder again, but only on this chart. + //AtlasBuilder builder(chart->chartMesh()); + } + + // Transfer parameterization from unified mesh to chart mesh. + chart->transferParameterization(); + + } + + nvDebug(" Parameterized %d/%d charts.\n", diskCount, chartCount); + nvDebug(" RMS stretch metric: %f\n", globalParameterizationQuality.rmsStretchMetric()); + nvDebug(" MAX stretch metric: %f\n", globalParameterizationQuality.maxStretchMetric()); + nvDebug(" RMS conformal metric: %f\n", globalParameterizationQuality.rmsConformalMetric()); + nvDebug(" RMS authalic metric: %f\n", globalParameterizationQuality.maxAuthalicMetric()); +} + + + +Chart::Chart() : m_chartMesh(NULL), m_unifiedMesh(NULL), m_isDisk(false), m_isVertexMapped(false) +{ +} + +void Chart::build(const HalfEdge::Mesh * originalMesh, const Array & faceArray) +{ + // Copy face indices. + m_faceArray = faceArray; + + const uint meshVertexCount = originalMesh->vertexCount(); + + m_chartMesh = new HalfEdge::Mesh(); + m_unifiedMesh = new HalfEdge::Mesh(); + + Array chartMeshIndices; + chartMeshIndices.resize(meshVertexCount, ~0); + + Array unifiedMeshIndices; + unifiedMeshIndices.resize(meshVertexCount, ~0); + + // Add vertices. + const uint faceCount = faceArray.count(); + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = originalMesh->faceAt(faceArray[f]); + nvDebugCheck(face != NULL); + + for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Vertex * vertex = it.current()->vertex; + const HalfEdge::Vertex * unifiedVertex = vertex->firstColocal(); + + if (unifiedMeshIndices[unifiedVertex->id] == ~0) + { + unifiedMeshIndices[unifiedVertex->id] = m_unifiedMesh->vertexCount(); + + nvDebugCheck(vertex->pos == unifiedVertex->pos); + m_unifiedMesh->addVertex(vertex->pos); + } + + if (chartMeshIndices[vertex->id] == ~0) + { + chartMeshIndices[vertex->id] = m_chartMesh->vertexCount(); + m_chartToOriginalMap.append(vertex->id); + m_chartToUnifiedMap.append(unifiedMeshIndices[unifiedVertex->id]); + + HalfEdge::Vertex * v = m_chartMesh->addVertex(vertex->pos); + v->nor = vertex->nor; + v->tex = vertex->tex; + } + } + } + + // This is ignoring the canonical map: + // - Is it really necessary to link colocals? + + m_chartMesh->linkColocals(); + //m_unifiedMesh->linkColocals(); // Not strictly necessary, no colocals in the unified mesh. # Wrong. + + // This check is not valid anymore, if the original mesh vertices were linked with a canonical map, then it might have + // some colocal vertices that were unlinked. So, the unified mesh might have some duplicate vertices, because firstColocal() + // is not guaranteed to return the same vertex for two colocal vertices. + //nvCheck(m_chartMesh->colocalVertexCount() == m_unifiedMesh->vertexCount()); + + // Is that OK? What happens in meshes were that happens? Does anything break? Apparently not... + + + + Array faceIndices(7); + + // Add faces. + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = originalMesh->faceAt(faceArray[f]); + nvDebugCheck(face != NULL); + + faceIndices.clear(); + + for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Vertex * vertex = it.current()->vertex; + nvDebugCheck(vertex != NULL); + + faceIndices.append(chartMeshIndices[vertex->id]); + } + + m_chartMesh->addFace(faceIndices); + + faceIndices.clear(); + + for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Vertex * vertex = it.current()->vertex; + nvDebugCheck(vertex != NULL); + + vertex = vertex->firstColocal(); + + faceIndices.append(unifiedMeshIndices[vertex->id]); + } + + m_unifiedMesh->addFace(faceIndices); + } + + m_chartMesh->linkBoundary(); + m_unifiedMesh->linkBoundary(); + + //exportMesh(m_unifiedMesh.ptr(), "debug_input.obj"); + + if (m_unifiedMesh->splitBoundaryEdges()) { + m_unifiedMesh = unifyVertices(m_unifiedMesh.ptr()); + } + + //exportMesh(m_unifiedMesh.ptr(), "debug_split.obj"); + + // Closing the holes is not always the best solution and does not fix all the problems. + // We need to do some analysis of the holes and the genus to: + // - Find cuts that reduce genus. + // - Find cuts to connect holes. + // - Use minimal spanning trees or seamster. + if (!closeHoles()) { + /*static int pieceCount = 0; + StringBuilder fileName; + fileName.format("debug_hole_%d.obj", pieceCount++); + exportMesh(m_unifiedMesh.ptr(), fileName.str());*/ + } + + m_unifiedMesh = triangulate(m_unifiedMesh.ptr()); + + //exportMesh(m_unifiedMesh.ptr(), "debug_triangulated.obj"); + + + // Analyze chart topology. + MeshTopology topology(m_unifiedMesh.ptr()); + m_isDisk = topology.isDisk(); + + // This is sometimes failing, when triangulate fails to add a triangle, it generates a hole in the mesh. + //nvDebugCheck(m_isDisk); + + /*if (!m_isDisk) { + static int pieceCount = 0; + StringBuilder fileName; + fileName.format("debug_hole_%d.obj", pieceCount++); + exportMesh(m_unifiedMesh.ptr(), fileName.str()); + }*/ + + +#if 0 + if (!m_isDisk) { + nvDebugBreak(); + + static int pieceCount = 0; + + StringBuilder fileName; + fileName.format("debug_nodisk_%d.obj", pieceCount++); + exportMesh(m_chartMesh.ptr(), fileName.str()); + } +#endif + +} + + +void Chart::buildVertexMap(const HalfEdge::Mesh * originalMesh, const Array & unchartedMaterialArray) +{ + nvCheck(m_chartMesh == NULL && m_unifiedMesh == NULL); + + m_isVertexMapped = true; + + // Build face indices. + m_faceArray.clear(); + + const uint meshFaceCount = originalMesh->faceCount(); + for (uint f = 0; f < meshFaceCount; f++) { + const HalfEdge::Face * face = originalMesh->faceAt(f); + + if (unchartedMaterialArray.contains(face->material)) { + m_faceArray.append(f); + } + } + + const uint faceCount = m_faceArray.count(); + + if (faceCount == 0) { + return; + } + + + // @@ The chartMesh construction is basically the same as with regular charts, don't duplicate! + + const uint meshVertexCount = originalMesh->vertexCount(); + + m_chartMesh = new HalfEdge::Mesh(); + + Array chartMeshIndices; + chartMeshIndices.resize(meshVertexCount, ~0); + + // Vertex map mesh only has disconnected vertices. + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = originalMesh->faceAt(m_faceArray[f]); + nvDebugCheck(face != NULL); + + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Vertex * vertex = it.current()->vertex; + + if (chartMeshIndices[vertex->id] == ~0) + { + chartMeshIndices[vertex->id] = m_chartMesh->vertexCount(); + m_chartToOriginalMap.append(vertex->id); + + HalfEdge::Vertex * v = m_chartMesh->addVertex(vertex->pos); + v->nor = vertex->nor; + v->tex = vertex->tex; // @@ Not necessary. + } + } + } + + // @@ Link colocals using the original mesh canonical map? Build canonical map on the fly? Do we need to link colocals at all for this? + //m_chartMesh->linkColocals(); + + Array faceIndices(7); + + // Add faces. + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = originalMesh->faceAt(m_faceArray[f]); + nvDebugCheck(face != NULL); + + faceIndices.clear(); + + for(HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Vertex * vertex = it.current()->vertex; + nvDebugCheck(vertex != NULL); + nvDebugCheck(chartMeshIndices[vertex->id] != ~0); + + faceIndices.append(chartMeshIndices[vertex->id]); + } + + HalfEdge::Face * new_face = m_chartMesh->addFace(faceIndices); + nvDebugCheck(new_face != NULL); + } + + m_chartMesh->linkBoundary(); + + + const uint chartVertexCount = m_chartMesh->vertexCount(); + + Box bounds; + bounds.clearBounds(); + + for (uint i = 0; i < chartVertexCount; i++) { + HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i); + bounds.addPointToBounds(vertex->pos); + } + + ProximityGrid grid; + grid.init(bounds, chartVertexCount); + + for (uint i = 0; i < chartVertexCount; i++) { + HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i); + grid.add(vertex->pos, i); + } + + +#if 0 + // Arrange vertices in a rectangle. + vertexMapWidth = ftoi_ceil(sqrtf(float(chartVertexCount))); + vertexMapHeight = (chartVertexCount + vertexMapWidth - 1) / vertexMapWidth; + nvDebugCheck(vertexMapWidth >= vertexMapHeight); + + int x = 0, y = 0; + for (uint i = 0; i < chartVertexCount; i++) { + HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i); + + vertex->tex.x = float(x); + vertex->tex.y = float(y); + + x++; + if (x == vertexMapWidth) { + x = 0; + y++; + nvCheck(y < vertexMapHeight); + } + } + +#elif 0 + // Arrange vertices in a rectangle, traversing grid in 3D morton order and laying them down in 2D morton order. + vertexMapWidth = ftoi_ceil(sqrtf(float(chartVertexCount))); + vertexMapHeight = (chartVertexCount + vertexMapWidth - 1) / vertexMapWidth; + nvDebugCheck(vertexMapWidth >= vertexMapHeight); + + int n = 0; + uint32 texelCode = 0; + + uint cellsVisited = 0; + + const uint32 cellCodeCount = grid.mortonCount(); + for (uint32 cellCode = 0; cellCode < cellCodeCount; cellCode++) { + int cell = grid.mortonIndex(cellCode); + if (cell < 0) continue; + + cellsVisited++; + + const Array & indexArray = grid.cellArray[cell].indexArray; + + foreach(i, indexArray) { + uint idx = indexArray[i]; + HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(idx); + + //vertex->tex.x = float(n % rectangleWidth) + 0.5f; + //vertex->tex.y = float(n / rectangleWidth) + 0.5f; + + // Lay down the points in z order too. + uint x, y; + do { + x = decodeMorton2X(texelCode); + y = decodeMorton2Y(texelCode); + texelCode++; + } while (x >= U32(vertexMapWidth) || y >= U32(vertexMapHeight)); + + vertex->tex.x = float(x); + vertex->tex.y = float(y); + + n++; + } + } + + nvDebugCheck(cellsVisited == grid.cellArray.count()); + nvDebugCheck(n == chartVertexCount); + +#else + + uint texelCount = 0; + + const float positionThreshold = 0.01f; + const float normalThreshold = 0.01f; + + uint verticesVisited = 0; + uint cellsVisited = 0; + + Array vertexIndexArray; + vertexIndexArray.resize(chartVertexCount, -1); // Init all indices to -1. + + // Traverse vertices in morton order. @@ It may be more interesting to sort them based on orientation. + const uint cellCodeCount = grid.mortonCount(); + for (uint cellCode = 0; cellCode < cellCodeCount; cellCode++) { + int cell = grid.mortonIndex(cellCode); + if (cell < 0) continue; + + cellsVisited++; + + const Array & indexArray = grid.cellArray[cell].indexArray; + + foreach(i, indexArray) { + uint idx = indexArray[i]; + HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(idx); + + nvDebugCheck(vertexIndexArray[idx] == -1); + + Array neighbors; + grid.gather(vertex->pos, positionThreshold, /*ref*/neighbors); + + // Compare against all nearby vertices, cluster greedily. + foreach(j, neighbors) { + uint otherIdx = neighbors[j]; + + if (vertexIndexArray[otherIdx] != -1) { + HalfEdge::Vertex * otherVertex = m_chartMesh->vertexAt(otherIdx); + + if (distance(vertex->pos, otherVertex->pos) < positionThreshold && + distance(vertex->nor, otherVertex->nor) < normalThreshold) + { + vertexIndexArray[idx] = vertexIndexArray[otherIdx]; + break; + } + } + } + + // If index not assigned, assign new one. + if (vertexIndexArray[idx] == -1) { + vertexIndexArray[idx] = texelCount++; + } + + verticesVisited++; + } + } + + nvDebugCheck(cellsVisited == grid.cellArray.count()); + nvDebugCheck(verticesVisited == chartVertexCount); + + vertexMapWidth = ftoi_ceil(sqrtf(float(texelCount))); + vertexMapWidth = (vertexMapWidth + 3) & ~3; // Width aligned to 4. + vertexMapHeight = vertexMapWidth == 0 ? 0 : (texelCount + vertexMapWidth - 1) / vertexMapWidth; + //vertexMapHeight = (vertexMapHeight + 3) & ~3; // Height aligned to 4. + nvDebugCheck(vertexMapWidth >= vertexMapHeight); + + nvDebug("Reduced vertex count from %d to %d.\n", chartVertexCount, texelCount); + +#if 0 + // This lays down the clustered vertices linearly. + for (uint i = 0; i < chartVertexCount; i++) { + HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i); + + int idx = vertexIndexArray[i]; + + vertex->tex.x = float(idx % vertexMapWidth); + vertex->tex.y = float(idx / vertexMapWidth); + } +#else + // Lay down the clustered vertices in morton order. + + Array texelCodes; + texelCodes.resize(texelCount); + + // For each texel, assign one morton code. + uint texelCode = 0; + for (uint i = 0; i < texelCount; i++) { + uint x, y; + do { + x = decodeMorton2X(texelCode); + y = decodeMorton2Y(texelCode); + texelCode++; + } while (x >= U32(vertexMapWidth) || y >= U32(vertexMapHeight)); + + texelCodes[i] = texelCode - 1; + } + + for (uint i = 0; i < chartVertexCount; i++) { + HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(i); + + int idx = vertexIndexArray[i]; + if (idx != -1) { + uint texelCode = texelCodes[idx]; + uint x = decodeMorton2X(texelCode); + uint y = decodeMorton2Y(texelCode); + + vertex->tex.x = float(x); + vertex->tex.y = float(y); + } + } + +#endif + +#endif + +} + + + +static void getBoundaryEdges(HalfEdge::Mesh * mesh, Array & boundaryEdges) +{ + nvDebugCheck(mesh != NULL); + + const uint edgeCount = mesh->edgeCount(); + + BitArray bitFlags(edgeCount); + bitFlags.clearAll(); + + boundaryEdges.clear(); + + // Search for boundary edges. Mark all the edges that belong to the same boundary. + for (uint e = 0; e < edgeCount; e++) + { + HalfEdge::Edge * startEdge = mesh->edgeAt(e); + + if (startEdge != NULL && startEdge->isBoundary() && bitFlags.bitAt(e) == false) + { + nvDebugCheck(startEdge->face != NULL); + nvDebugCheck(startEdge->pair->face == NULL); + + startEdge = startEdge->pair; + + const HalfEdge::Edge * edge = startEdge; + do { + nvDebugCheck(edge->face == NULL); + nvDebugCheck(bitFlags.bitAt(edge->id/2) == false); + + bitFlags.setBitAt(edge->id / 2); + edge = edge->next; + } while(startEdge != edge); + + boundaryEdges.append(startEdge); + } + } +} + + +bool Chart::closeLoop(uint start, const Array & loop) +{ + const uint vertexCount = loop.count() - start; + + nvDebugCheck(vertexCount >= 3); + if (vertexCount < 3) return false; + + nvDebugCheck(loop[start]->vertex->isColocal(loop[start+vertexCount-1]->to())); + + // If the hole is planar, then we add a single face that will be properly triangulated later. + // If the hole is not planar, we add a triangle fan with a vertex at the hole centroid. + // This is still a bit of a hack. There surely are better hole filling algorithms out there. + + Array points; + points.resize(vertexCount); + for (uint i = 0; i < vertexCount; i++) { + points[i] = loop[start+i]->vertex->pos; + } + + bool isPlanar = Fit::isPlanar(vertexCount, points.buffer()); + + if (isPlanar) { + // Add face and connect edges. + HalfEdge::Face * face = m_unifiedMesh->addFace(); + for (uint i = 0; i < vertexCount; i++) { + HalfEdge::Edge * edge = loop[start + i]; + + edge->face = face; + edge->setNext(loop[start + (i + 1) % vertexCount]); + } + face->edge = loop[start]; + + nvDebugCheck(face->isValid()); + } + else { + // If the polygon is not planar, we just cross our fingers, and hope this will work: + + // Compute boundary centroid: + Vector3 centroidPos(0); + + for (uint i = 0; i < vertexCount; i++) { + centroidPos += points[i]; + } + + centroidPos *= (1.0f / vertexCount); + + HalfEdge::Vertex * centroid = m_unifiedMesh->addVertex(centroidPos); + + // Add one pair of edges for each boundary vertex. + for (uint j = vertexCount-1, i = 0; i < vertexCount; j = i++) { + HalfEdge::Face * face = m_unifiedMesh->addFace(centroid->id, loop[start+j]->vertex->id, loop[start+i]->vertex->id); + nvDebugCheck(face != NULL); + } + } + + return true; +} + + +bool Chart::closeHoles() +{ + nvDebugCheck(!m_isVertexMapped); + + Array boundaryEdges; + getBoundaryEdges(m_unifiedMesh.ptr(), boundaryEdges); + + uint boundaryCount = boundaryEdges.count(); + if (boundaryCount <= 1) + { + // Nothing to close. + return true; + } + + // Compute lengths and areas. + Array boundaryLengths; + //Array boundaryCentroids; + + for (uint i = 0; i < boundaryCount; i++) + { + const HalfEdge::Edge * startEdge = boundaryEdges[i]; + nvCheck(startEdge->face == NULL); + + //float boundaryEdgeCount = 0; + float boundaryLength = 0.0f; + //Vector3 boundaryCentroid(zero); + + const HalfEdge::Edge * edge = startEdge; + do { + Vector3 t0 = edge->from()->pos; + Vector3 t1 = edge->to()->pos; + + //boundaryEdgeCount++; + boundaryLength += length(t1 - t0); + //boundaryCentroid += edge->vertex()->pos; + + edge = edge->next; + } while(edge != startEdge); + + boundaryLengths.append(boundaryLength); + //boundaryCentroids.append(boundaryCentroid / boundaryEdgeCount); + } + + + // Find disk boundary. + uint diskBoundary = 0; + float maxLength = boundaryLengths[0]; + + for (uint i = 1; i < boundaryCount; i++) + { + if (boundaryLengths[i] > maxLength) + { + maxLength = boundaryLengths[i]; + diskBoundary = i; + } + } + + + // Sew holes. + /*for (uint i = 0; i < boundaryCount; i++) + { + if (diskBoundary == i) + { + // Skip disk boundary. + continue; + } + + HalfEdge::Edge * startEdge = boundaryEdges[i]; + nvCheck(startEdge->face() == NULL); + + boundaryEdges[i] = m_unifiedMesh->sewBoundary(startEdge); + } + + exportMesh(m_unifiedMesh.ptr(), "debug_sewn.obj");*/ + + //bool hasNewHoles = false; + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // @@ Close loop is wrong, after closing a loop, we do not only have to add the face, but make sure that every edge in he loop is pointing to the right place. + + // Close holes. + for (uint i = 0; i < boundaryCount; i++) + { + if (diskBoundary == i) + { + // Skip disk boundary. + continue; + } + + HalfEdge::Edge * startEdge = boundaryEdges[i]; + nvDebugCheck(startEdge != NULL); + nvDebugCheck(startEdge->face == NULL); + +#if 1 + Array vertexLoop; + Array edgeLoop; + + HalfEdge::Edge * edge = startEdge; + do { + HalfEdge::Vertex * vertex = edge->next->vertex; // edge->to() + + uint i; + for (i = 0; i < vertexLoop.count(); i++) { + if (vertex->isColocal(vertexLoop[i])) { + break; + } + } + + bool isCrossing = (i != vertexLoop.count()); + + if (isCrossing) { + + HalfEdge::Edge * prev = edgeLoop[i]; // Previous edge before the loop. + HalfEdge::Edge * next = edge->next; // Next edge after the loop. + + nvDebugCheck(prev->to()->isColocal(next->from())); + + // Close loop. + edgeLoop.append(edge); + closeLoop(i+1, edgeLoop); + + // Link boundary loop. + prev->setNext(next); + vertex->setEdge(next); + + // Start over again. + vertexLoop.clear(); + edgeLoop.clear(); + + edge = startEdge; + vertex = edge->to(); + } + + vertexLoop.append(vertex); + edgeLoop.append(edge); + + edge = edge->next; + } while(edge != startEdge); + + closeLoop(0, edgeLoop); +#endif + + /* + + // Add face and connect boundary edges. + HalfEdge::Face * face = m_unifiedMesh->addFace(); + face->setEdge(startEdge); + + HalfEdge::Edge * edge = startEdge; + do { + edge->setFace(face); + + edge = edge->next(); + } while(edge != startEdge); + + */ + + + /* + uint edgeCount = 0; + HalfEdge::Edge * edge = startEdge; + do { + edgeCount++; + edge = edge->next(); + } while(edge != startEdge); + + + + // Count edges in this boundary. + uint edgeCount = 0; + HalfEdge::Edge * edge = startEdge; + do { + edgeCount++; + edge = edge->next(); + } while(edge != startEdge); + + // Trivial hole, fill with one triangle. This actually works for all convex boundaries with non colinear vertices. + if (edgeCount == 3) { + // Add face and connect boundary edges. + HalfEdge::Face * face = m_unifiedMesh->addFace(); + face->setEdge(startEdge); + + edge = startEdge; + do { + edge->setFace(face); + + edge = edge->next(); + } while(edge != startEdge); + + // @@ Implement the above using addFace, it should now work with existing edges, as long as their face pointers is zero. + + } + else { + // Ideally we should: + // - compute best fit plane of boundary vertices. + // - project boundary polygon onto plane. + // - triangulate boundary polygon. + // - add faces of the resulting triangulation. + + // I don't have a good triangulator available. A more simple solution that works in more (but not all) cases: + // - compute boundary centroid. + // - add vertex centroid. + // - connect centroid vertex with boundary vertices. + // - connect radial edges with boundary edges. + + // This should work for non-convex boundaries with colinear vertices as long as the kernel of the polygon is not empty. + + // Compute boundary centroid: + Vector3 centroid_pos(0); + Vector2 centroid_tex(0); + + HalfEdge::Edge * edge = startEdge; + do { + centroid_pos += edge->vertex()->pos; + centroid_tex += edge->vertex()->tex; + edge = edge->next(); + } while(edge != startEdge); + + centroid_pos *= (1.0f / edgeCount); + centroid_tex *= (1.0f / edgeCount); + + HalfEdge::Vertex * centroid = m_unifiedMesh->addVertex(centroid_pos); + centroid->tex = centroid_tex; + + // Add one pair of edges for each boundary vertex. + edge = startEdge; + do { + HalfEdge::Edge * next = edge->next(); + + nvCheck(edge->face() == NULL); + HalfEdge::Face * face = m_unifiedMesh->addFace(centroid->id(), edge->from()->id(), edge->to()->id()); + + if (face != NULL) { + nvCheck(edge->face() == face); + } + else { + hasNewHoles = true; + } + + edge = next; + } while(edge != startEdge); + } + */ + } + + /*nvDebugCheck(!hasNewHoles); + + if (hasNewHoles) { + // Link boundary again, in case closeHoles created new holes! + m_unifiedMesh->linkBoundary(); + }*/ + + // Because some algorithms do not expect sparse edge buffers. + //m_unifiedMesh->compactEdges(); + + // In case we messed up: + //m_unifiedMesh->linkBoundary(); + + getBoundaryEdges(m_unifiedMesh.ptr(), boundaryEdges); + + boundaryCount = boundaryEdges.count(); + nvDebugCheck(boundaryCount == 1); + + //exportMesh(m_unifiedMesh.ptr(), "debug_hole_filled.obj"); + + return boundaryCount == 1; +} + + +// Transfer parameterization from unified mesh to chart mesh. +void Chart::transferParameterization() { + nvDebugCheck(!m_isVertexMapped); + + uint vertexCount = m_chartMesh->vertexCount(); + for (uint v = 0; v < vertexCount; v++) { + HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(v); + HalfEdge::Vertex * unifiedVertex = m_unifiedMesh->vertexAt(mapChartVertexToUnifiedVertex(v)); + vertex->tex = unifiedVertex->tex; + } +} + +float Chart::computeSurfaceArea() const { + return nv::computeSurfaceArea(m_chartMesh.ptr()) * scale; +} + +float Chart::computeParametricArea() const { + // This only makes sense in parameterized meshes. + nvDebugCheck(m_isDisk); + nvDebugCheck(!m_isVertexMapped); + + return nv::computeParametricArea(m_chartMesh.ptr()); +} + +Vector2 Chart::computeParametricBounds() const { + // This only makes sense in parameterized meshes. + nvDebugCheck(m_isDisk); + nvDebugCheck(!m_isVertexMapped); + + Box bounds; + bounds.clearBounds(); + + uint vertexCount = m_chartMesh->vertexCount(); + for (uint v = 0; v < vertexCount; v++) { + HalfEdge::Vertex * vertex = m_chartMesh->vertexAt(v); + bounds.addPointToBounds(Vector3(vertex->tex, 0)); + } + + return bounds.extents().xy(); +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/Atlas.h b/thirdparty/thekla_atlas/src/nvmesh/param/Atlas.h new file mode 100755 index 00000000..8b478207 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/Atlas.h @@ -0,0 +1,183 @@ +// Copyright NVIDIA Corporation 2006 -- Ignacio Castano + +#pragma once +#ifndef NV_MESH_ATLAS_H +#define NV_MESH_ATLAS_H + +#include "nvcore/Array.h" +#include "nvcore/Ptr.h" +#include "nvmath/Vector.h" +#include "nvmesh/nvmesh.h" +#include "nvmesh/halfedge/Mesh.h" + + +namespace nv +{ + namespace HalfEdge { class Mesh; } + + class Chart; + class MeshCharts; + class VertexMap; + + struct SegmentationSettings + { + SegmentationSettings(); + + float maxChartArea; + float maxBoundaryLength; + + float proxyFitMetricWeight; + float roundnessMetricWeight; + float straightnessMetricWeight; + float normalSeamMetricWeight; + float textureSeamMetricWeight; + }; + + + /// An atlas is a set of charts. + class Atlas + { + public: + + Atlas(); + ~Atlas(); + + uint meshCount() const { return m_meshChartsArray.count(); } + const MeshCharts * meshAt(uint i) const { return m_meshChartsArray[i]; } + MeshCharts * meshAt(uint i) { return m_meshChartsArray[i]; } + + uint chartCount() const; + const Chart * chartAt(uint i) const; + Chart * chartAt(uint i); + + // Add mesh charts and takes ownership. + void addMeshCharts(MeshCharts * meshCharts); + + void extractCharts(const HalfEdge::Mesh * mesh); + void computeCharts(const HalfEdge::Mesh * mesh, const SegmentationSettings & settings, const Array & unchartedMaterialArray); + + + // Compute a trivial seamless texture similar to ZBrush. + //bool computeSeamlessTextureAtlas(bool groupFaces = true, bool scaleTiles = false, uint w = 1024, uint h = 1024); + + void parameterizeCharts(); + + // Pack charts in the smallest possible rectangle. + float packCharts(int quality, float texelArea, bool blockAlign, bool conservative); + + private: + + Array m_meshChartsArray; + + }; + + + // Set of charts corresponding to a single mesh. + class MeshCharts + { + public: + MeshCharts(const HalfEdge::Mesh * mesh); + ~MeshCharts(); + + uint chartCount() const { return m_chartArray.count(); } + uint vertexCount () const { return m_totalVertexCount; } + + const Chart * chartAt(uint i) const { return m_chartArray[i]; } + Chart * chartAt(uint i) { return m_chartArray[i]; } + + void computeVertexMap(const Array & unchartedMaterialArray); + + // Extract the charts of the input mesh. + void extractCharts(); + + // Compute charts using a simple segmentation algorithm. + void computeCharts(const SegmentationSettings & settings, const Array & unchartedMaterialArray); + + void parameterizeCharts(); + + uint faceChartAt(uint i) const { return m_faceChart[i]; } + uint faceIndexWithinChartAt(uint i) const { return m_faceIndex[i]; } + + uint vertexCountBeforeChartAt(uint i) const { return m_chartVertexCountPrefixSum[i]; } + + private: + + const HalfEdge::Mesh * m_mesh; + + Array m_chartArray; + + Array m_chartVertexCountPrefixSum; + uint m_totalVertexCount; + + Array m_faceChart; // the chart of every face of the input mesh. + Array m_faceIndex; // the index within the chart for every face of the input mesh. + }; + + + /// A chart is a connected set of faces with a certain topology (usually a disk). + class Chart + { + public: + + Chart(); + + void build(const HalfEdge::Mesh * originalMesh, const Array & faceArray); + void buildVertexMap(const HalfEdge::Mesh * originalMesh, const Array & unchartedMaterialArray); + + bool closeHoles(); + + bool isDisk() const { return m_isDisk; } + bool isVertexMapped() const { return m_isVertexMapped; } + + uint vertexCount() const { return m_chartMesh->vertexCount(); } + uint colocalVertexCount() const { return m_unifiedMesh->vertexCount(); } + + uint faceCount() const { return m_faceArray.count(); } + uint faceAt(uint i) const { return m_faceArray[i]; } + + const HalfEdge::Mesh * chartMesh() const { return m_chartMesh.ptr(); } + HalfEdge::Mesh * chartMesh() { return m_chartMesh.ptr(); } + const HalfEdge::Mesh * unifiedMesh() const { return m_unifiedMesh.ptr(); } + HalfEdge::Mesh * unifiedMesh() { return m_unifiedMesh.ptr(); } + + //uint vertexIndex(uint i) const { return m_vertexIndexArray[i]; } + + uint mapChartVertexToOriginalVertex(uint i) const { return m_chartToOriginalMap[i]; } + uint mapChartVertexToUnifiedVertex(uint i) const { return m_chartToUnifiedMap[i]; } + + const Array & faceArray() const { return m_faceArray; } + + void transferParameterization(); + + float computeSurfaceArea() const; + float computeParametricArea() const; + Vector2 computeParametricBounds() const; + + + float scale = 1.0f; + uint vertexMapWidth; + uint vertexMapHeight; + + private: + + bool closeLoop(uint start, const Array & loop); + + // Chart mesh. + AutoPtr m_chartMesh; + AutoPtr m_unifiedMesh; + + bool m_isDisk; + bool m_isVertexMapped; + + // List of faces of the original mesh that belong to this chart. + Array m_faceArray; + + // Map vertices of the chart mesh to vertices of the original mesh. + Array m_chartToOriginalMap; + + Array m_chartToUnifiedMap; + }; + +} // nv namespace + +#endif // NV_MESH_ATLAS_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/AtlasBuilder.cpp b/thirdparty/thekla_atlas/src/nvmesh/param/AtlasBuilder.cpp new file mode 100755 index 00000000..bd2140c2 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/AtlasBuilder.cpp @@ -0,0 +1,1320 @@ +// This code is in the public domain -- castano@gmail.com + +#include "nvmesh.h" // pch + +#include "AtlasBuilder.h" +#include "Util.h" + +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Face.h" +#include "nvmesh/halfedge/Vertex.h" + +#include "nvmath/Matrix.inl" +#include "nvmath/Vector.inl" + +//#include "nvcore/IntroSort.h" +#include "nvcore/Array.inl" + +#include // std::sort + +#include // FLT_MAX +#include // UINT_MAX + +using namespace nv; + +namespace +{ + + // Dummy implementation of a priority queue using sort at insertion. + // - Insertion is o(n) + // - Smallest element goes at the end, so that popping it is o(1). + // - Resorting is n*log(n) + // @@ Number of elements in the queue is usually small, and we'd have to rebalance often. I'm not sure it's worth implementing a heap. + // @@ Searcing at removal would remove the need for sorting when priorities change. + struct PriorityQueue + { + PriorityQueue(uint size = UINT_MAX) : maxSize(size) {} + + void push(float priority, uint face) { + uint i = 0; + const uint count = pairs.count(); + for (; i < count; i++) { + if (pairs[i].priority > priority) break; + } + + Pair p = { priority, face }; + pairs.insertAt(i, p); + + if (pairs.count() > maxSize) { + pairs.removeAt(0); + } + } + + // push face out of order, to be sorted later. + void push(uint face) { + Pair p = { 0.0f, face }; + pairs.append(p); + } + + uint pop() { + uint f = pairs.back().face; + pairs.pop_back(); + return f; + } + + void sort() { + //nv::sort(pairs); // @@ My intro sort appears to be much slower than it should! + std::sort(pairs.buffer(), pairs.buffer() + pairs.count()); + } + + void clear() { + pairs.clear(); + } + + uint count() const { return pairs.count(); } + + float firstPriority() const { return pairs.back().priority; } + + + const uint maxSize; + + struct Pair { + bool operator <(const Pair & p) const { return priority > p.priority; } // !! Sort in inverse priority order! + float priority; + uint face; + }; + + + Array pairs; + }; + + static bool isNormalSeam(const HalfEdge::Edge * edge) { + return (edge->vertex->nor != edge->pair->next->vertex->nor || edge->next->vertex->nor != edge->pair->vertex->nor); + } + + static bool isTextureSeam(const HalfEdge::Edge * edge) { + return (edge->vertex->tex != edge->pair->next->vertex->tex || edge->next->vertex->tex != edge->pair->vertex->tex); + } + +} // namespace + + +struct nv::ChartBuildData +{ + ChartBuildData(int id) : id(id) { + planeNormal = Vector3(0); + centroid = Vector3(0); + coneAxis = Vector3(0); + coneAngle = 0; + area = 0; + boundaryLength = 0; + normalSum = Vector3(0); + centroidSum = Vector3(0); + } + + int id; + + // Proxy info: + Vector3 planeNormal; + Vector3 centroid; + Vector3 coneAxis; + float coneAngle; + + float area; + float boundaryLength; + Vector3 normalSum; + Vector3 centroidSum; + + Array seeds; // @@ These could be a pointers to the HalfEdge faces directly. + Array faces; + PriorityQueue candidates; +}; + + + +AtlasBuilder::AtlasBuilder(const HalfEdge::Mesh * m) : mesh(m), facesLeft(m->faceCount()) +{ + const uint faceCount = m->faceCount(); + faceChartArray.resize(faceCount, -1); + faceCandidateArray.resize(faceCount, -1); + + // @@ Floyd for the whole mesh is too slow. We could compute floyd progressively per patch as the patch grows. We need a better solution to compute most central faces. + //computeShortestPaths(); + + // Precompute edge lengths and face areas. + uint edgeCount = m->edgeCount(); + edgeLengths.resize(edgeCount); + + for (uint i = 0; i < edgeCount; i++) { + uint id = m->edgeAt(i)->id; + nvDebugCheck(id / 2 == i); + + edgeLengths[i] = m->edgeAt(i)->length(); + } + + faceAreas.resize(faceCount); + for (uint i = 0; i < faceCount; i++) { + faceAreas[i] = m->faceAt(i)->area(); + } +} + +AtlasBuilder::~AtlasBuilder() +{ + const uint chartCount = chartArray.count(); + for (uint i = 0; i < chartCount; i++) + { + delete chartArray[i]; + } +} + + +void AtlasBuilder::markUnchartedFaces(const Array & unchartedFaces) +{ + const uint unchartedFaceCount = unchartedFaces.count(); + for (uint i = 0; i < unchartedFaceCount; i++){ + uint f = unchartedFaces[i]; + faceChartArray[f] = -2; + //faceCandidateArray[f] = -2; // @@ ? + + removeCandidate(f); + } + + nvDebugCheck(facesLeft >= unchartedFaceCount); + facesLeft -= unchartedFaceCount; +} + + +void AtlasBuilder::computeShortestPaths() +{ + const uint faceCount = mesh->faceCount(); + shortestPaths.resize(faceCount*faceCount, FLT_MAX); + + // Fill edges: + for (uint i = 0; i < faceCount; i++) + { + shortestPaths[i*faceCount + i] = 0.0f; + + const HalfEdge::Face * face_i = mesh->faceAt(i); + Vector3 centroid_i = face_i->centroid(); + + for (HalfEdge::Face::ConstEdgeIterator it(face_i->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + + if (!edge->isBoundary()) + { + const HalfEdge::Face * face_j = edge->pair->face; + + uint j = face_j->id; + Vector3 centroid_j = face_j->centroid(); + + shortestPaths[i*faceCount + j] = shortestPaths[j*faceCount + i] = length(centroid_i - centroid_j); + } + } + } + + // Use Floyd-Warshall algorithm to compute all paths: + for (uint k = 0; k < faceCount; k++) + { + for (uint i = 0; i < faceCount; i++) + { + for (uint j = 0; j < faceCount; j++) + { + shortestPaths[i*faceCount + j] = min(shortestPaths[i*faceCount + j], shortestPaths[i*faceCount + k]+shortestPaths[k*faceCount + j]); + } + } + } +} + + +void AtlasBuilder::placeSeeds(float threshold, uint maxSeedCount) +{ + // Instead of using a predefiened number of seeds: + // - Add seeds one by one, growing chart until a certain treshold. + // - Undo charts and restart growing process. + + // @@ How can we give preference to faces far from sharp features as in the LSCM paper? + // - those points can be found using a simple flood filling algorithm. + // - how do we weight the probabilities? + + for (uint i = 0; i < maxSeedCount; i++) + { + if (facesLeft == 0) { + // No faces left, stop creating seeds. + break; + } + + createRandomChart(threshold); + } +} + + +void AtlasBuilder::createRandomChart(float threshold) +{ + ChartBuildData * chart = new ChartBuildData(chartArray.count()); + chartArray.append(chart); + + // Pick random face that is not used by any chart yet. + uint randomFaceIdx = rand.getRange(facesLeft - 1); + uint i = 0; + for (uint f = 0; f != randomFaceIdx; f++, i++) + { + while (faceChartArray[i] != -1) i++; + } + while (faceChartArray[i] != -1) i++; + + chart->seeds.append(i); + + addFaceToChart(chart, i, true); + + // Grow the chart as much as possible within the given threshold. + growChart(chart, threshold * 0.5f, facesLeft); + //growCharts(threshold - threshold * 0.75f / chartCount(), facesLeft); +} + +void AtlasBuilder::addFaceToChart(ChartBuildData * chart, uint f, bool recomputeProxy) +{ + // Add face to chart. + chart->faces.append(f); + + nvDebugCheck(faceChartArray[f] == -1); + faceChartArray[f] = chart->id; + + facesLeft--; + + // Update area and boundary length. + chart->area = evaluateChartArea(chart, f); + chart->boundaryLength = evaluateBoundaryLength(chart, f); + chart->normalSum = evaluateChartNormalSum(chart, f); + chart->centroidSum = evaluateChartCentroidSum(chart, f); + + if (recomputeProxy) { + // Update proxy and candidate's priorities. + updateProxy(chart); + } + + // Update candidates. + removeCandidate(f); + updateCandidates(chart, f); + updatePriorities(chart); +} + +// @@ Get N best candidates in one pass. +const AtlasBuilder::Candidate & AtlasBuilder::getBestCandidate() const +{ + uint best = 0; + float bestCandidateMetric = FLT_MAX; + + const uint candidateCount = candidateArray.count(); + nvCheck(candidateCount > 0); + + for (uint i = 0; i < candidateCount; i++) + { + const Candidate & candidate = candidateArray[i]; + + if (candidate.metric < bestCandidateMetric) { + bestCandidateMetric = candidate.metric; + best = i; + } + } + + return candidateArray[best]; +} + + +// Returns true if any of the charts can grow more. +bool AtlasBuilder::growCharts(float threshold, uint faceCount) +{ +#if 1 // Using one global list. + + faceCount = min(faceCount, facesLeft); + + for (uint i = 0; i < faceCount; i++) + { + const Candidate & candidate = getBestCandidate(); + + if (candidate.metric > threshold) { + return false; // Can't grow more. + } + + addFaceToChart(candidate.chart, candidate.face); + } + + return facesLeft != 0; // Can continue growing. + +#else // Using one list per chart. + bool canGrowMore = false; + + const uint chartCount = chartArray.count(); + for (uint i = 0; i < chartCount; i++) + { + if (growChart(chartArray[i], threshold, faceCount)) + { + canGrowMore = true; + } + } + + return canGrowMore; +#endif +} + +bool AtlasBuilder::growChart(ChartBuildData * chart, float threshold, uint faceCount) +{ + // Try to add faceCount faces within threshold to chart. + for (uint i = 0; i < faceCount; ) + { + if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold) + { + return false; + } + + uint f = chart->candidates.pop(); + if (faceChartArray[f] == -1) + { + addFaceToChart(chart, f); + i++; + } + } + + if (chart->candidates.count() == 0 || chart->candidates.firstPriority() > threshold) + { + return false; + } + + return true; +} + + +void AtlasBuilder::resetCharts() +{ + const uint faceCount = mesh->faceCount(); + for (uint i = 0; i < faceCount; i++) + { + faceChartArray[i] = -1; + faceCandidateArray[i] = -1; + } + + facesLeft = faceCount; + + candidateArray.clear(); + + const uint chartCount = chartArray.count(); + for (uint i = 0; i < chartCount; i++) + { + ChartBuildData * chart = chartArray[i]; + + const uint seed = chart->seeds.back(); + + chart->area = 0.0f; + chart->boundaryLength = 0.0f; + chart->normalSum = Vector3(0); + chart->centroidSum = Vector3(0); + + chart->faces.clear(); + chart->candidates.clear(); + + addFaceToChart(chart, seed); + } +} + + +void AtlasBuilder::updateCandidates(ChartBuildData * chart, uint f) +{ + const HalfEdge::Face * face = mesh->faceAt(f); + + // Traverse neighboring faces, add the ones that do not belong to any chart yet. + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current()->pair; + + if (!edge->isBoundary()) + { + uint f = edge->face->id; + + if (faceChartArray[f] == -1) + { + chart->candidates.push(f); + } + } + } +} + + +void AtlasBuilder::updateProxies() +{ + const uint chartCount = chartArray.count(); + for (uint i = 0; i < chartCount; i++) + { + updateProxy(chartArray[i]); + } +} + + +namespace { + + float absoluteSum(Vector4::Arg v) + { + return fabs(v.x) + fabs(v.y) + fabs(v.z) + fabs(v.w); + } + + //#pragma message(NV_FILE_LINE "FIXME: Using the c=cos(teta) substitution, the equation system becomes linear and we can avoid the newton solver.") + + struct ConeFitting + { + ConeFitting(const HalfEdge::Mesh * m, float g, float tf, float tx) : mesh(m), gamma(g), tolf(tf), tolx(tx), F(0), D(0), H(0) { + } + + void addTerm(Vector3 N, float A) + { + const float c = cosf(X.w); + const float s = sinf(X.w); + const float tmp = dot(X.xyz(), N) - c; + + F += tmp * tmp; + + D.x += 2 * X.x * tmp; + D.y += 2 * X.y * tmp; + D.z += 2 * X.z * tmp; + D.w += 2 * s * tmp; + + H(0,0) = 2 * X.x * N.x + 2 * tmp; + H(0,1) = 2 * X.x * N.y; + H(0,2) = 2 * X.x * N.z; + H(0,3) = 2 * X.x * s; + + H(1,0) = 2 * X.y * N.x; + H(1,1) = 2 * X.y * N.y + 2 * tmp; + H(1,2) = 2 * X.y * N.z; + H(1,3) = 2 * X.y * s; + + H(2,0) = 2 * X.z * N.x; + H(2,1) = 2 * X.z * N.y; + H(2,2) = 2 * X.z * N.z + 2 * tmp; + H(2,3) = 2 * X.z * s; + + H(3,0) = 2 * s * N.x; + H(3,1) = 2 * s * N.y; + H(3,2) = 2 * s * N.z; + H(3,3) = 2 * s * s + 2 * c * tmp; + } + + Vector4 solve(ChartBuildData * chart, Vector4 start) + { + const uint faceCount = chart->faces.count(); + + X = start; + + Vector4 dX; + + do { + for (uint i = 0; i < faceCount; i++) + { + const HalfEdge::Face * face = mesh->faceAt(chart->faces[i]); + + addTerm(face->normal(), face->area()); + } + + Vector4 dX; + //solveKramer(H, D, &dX); + solveLU(H, D, &dX); + + // @@ Do a full newton step and reduce by half if F doesn't decrease. + X -= gamma * dX; + + // Constrain normal to be normalized. + X = Vector4(normalize(X.xyz()), X.w); + + } while(absoluteSum(D) > tolf || absoluteSum(dX) > tolx); + + return X; + } + + HalfEdge::Mesh const * const mesh; + const float gamma; + const float tolf; + const float tolx; + + Vector4 X; + + float F; + Vector4 D; + Matrix H; + }; + + // Unnormalized face normal assuming it's a triangle. + static Vector3 triangleNormal(const HalfEdge::Face * face) + { + Vector3 p0 = face->edge->vertex->pos; + Vector3 p1 = face->edge->next->vertex->pos; + Vector3 p2 = face->edge->next->next->vertex->pos; + + Vector3 e0 = p2 - p0; + Vector3 e1 = p1 - p0; + + return normalizeSafe(cross(e0, e1), Vector3(0), 0.0f); + } + + static Vector3 triangleNormalAreaScaled(const HalfEdge::Face * face) + { + Vector3 p0 = face->edge->vertex->pos; + Vector3 p1 = face->edge->next->vertex->pos; + Vector3 p2 = face->edge->next->next->vertex->pos; + + Vector3 e0 = p2 - p0; + Vector3 e1 = p1 - p0; + + return cross(e0, e1); + } + + // Average of the edge midpoints weighted by the edge length. + // I want a point inside the triangle, but closer to the cirumcenter. + static Vector3 triangleCenter(const HalfEdge::Face * face) + { + Vector3 p0 = face->edge->vertex->pos; + Vector3 p1 = face->edge->next->vertex->pos; + Vector3 p2 = face->edge->next->next->vertex->pos; + + float l0 = length(p1 - p0); + float l1 = length(p2 - p1); + float l2 = length(p0 - p2); + + Vector3 m0 = (p0 + p1) * l0 / (l0 + l1 + l2); + Vector3 m1 = (p1 + p2) * l1 / (l0 + l1 + l2); + Vector3 m2 = (p2 + p0) * l2 / (l0 + l1 + l2); + + return m0 + m1 + m2; + } + +} // namespace + +void AtlasBuilder::updateProxy(ChartBuildData * chart) +{ + //#pragma message(NV_FILE_LINE "TODO: Use best fit plane instead of average normal.") + + chart->planeNormal = normalizeSafe(chart->normalSum, Vector3(0), 0.0f); + chart->centroid = chart->centroidSum / float(chart->faces.count()); + + //#pragma message(NV_FILE_LINE "TODO: Experiment with conic fitting.") + + // F = (Nc*Nt - cos Oc)^2 = (x*Nt_x + y*Nt_y + z*Nt_z - cos w)^2 + // dF/dx = 2 * x * (x*Nt_x + y*Nt_y + z*Nt_z - cos w) + // dF/dy = 2 * y * (x*Nt_x + y*Nt_y + z*Nt_z - cos w) + // dF/dz = 2 * z * (x*Nt_x + y*Nt_y + z*Nt_z - cos w) + // dF/dw = 2 * sin w * (x*Nt_x + y*Nt_y + z*Nt_z - cos w) + + // JacobianMatrix({ + // 2 * x * (x*Nt_x + y*Nt_y + z*Nt_z - Cos(w)), + // 2 * y * (x*Nt_x + y*Nt_y + z*Nt_z - Cos(w)), + // 2 * z * (x*Nt_x + y*Nt_y + z*Nt_z - Cos(w)), + // 2 * Sin(w) * (x*Nt_x + y*Nt_y + z*Nt_z - Cos(w))}, {x,y,z,w}) + + // H[0,0] = 2 * x * Nt_x + 2 * (x*Nt_x + y*Nt_y + z*Nt_z - cos(w)); + // H[0,1] = 2 * x * Nt_y; + // H[0,2] = 2 * x * Nt_z; + // H[0,3] = 2 * x * sin(w); + + // H[1,0] = 2 * y * Nt_x; + // H[1,1] = 2 * y * Nt_y + 2 * (x*Nt_x + y*Nt_y + z*Nt_z - cos(w)); + // H[1,2] = 2 * y * Nt_z; + // H[1,3] = 2 * y * sin(w); + + // H[2,0] = 2 * z * Nt_x; + // H[2,1] = 2 * z * Nt_y; + // H[2,2] = 2 * z * Nt_z + 2 * (x*Nt_x + y*Nt_y + z*Nt_z - cos(w)); + // H[2,3] = 2 * z * sin(w); + + // H[3,0] = 2 * sin(w) * Nt_x; + // H[3,1] = 2 * sin(w) * Nt_y; + // H[3,2] = 2 * sin(w) * Nt_z; + // H[3,3] = 2 * sin(w) * sin(w) + 2 * cos(w) * (x*Nt_x + y*Nt_y + z*Nt_z - cos(w)); + + // @@ Cone fitting might be quite slow. + + /*ConeFitting coneFitting(mesh, 0.1f, 0.001f, 0.001f); + + Vector4 start = Vector4(chart->coneAxis, chart->coneAngle); + Vector4 solution = coneFitting.solve(chart, start); + + chart->coneAxis = solution.xyz(); + chart->coneAngle = solution.w;*/ +} + + + +bool AtlasBuilder::relocateSeeds() +{ + bool anySeedChanged = false; + + const uint chartCount = chartArray.count(); + for (uint i = 0; i < chartCount; i++) + { + if (relocateSeed(chartArray[i])) + { + anySeedChanged = true; + } + } + + return anySeedChanged; +} + + +bool AtlasBuilder::relocateSeed(ChartBuildData * chart) +{ + Vector3 centroid = computeChartCentroid(chart); + + const uint N = 10; // @@ Hardcoded to 10? + PriorityQueue bestTriangles(N); + + // Find the first N triangles that fit the proxy best. + const uint faceCount = chart->faces.count(); + for (uint i = 0; i < faceCount; i++) + { + float priority = evaluateProxyFitMetric(chart, chart->faces[i]); + bestTriangles.push(priority, chart->faces[i]); + } + + // Of those, choose the most central triangle. + uint mostCentral; + float maxDistance = -1; + + const uint bestCount = bestTriangles.count(); + for (uint i = 0; i < bestCount; i++) + { + const HalfEdge::Face * face = mesh->faceAt(bestTriangles.pairs[i].face); + Vector3 faceCentroid = triangleCenter(face); + + float distance = length(centroid - faceCentroid); + + /*#pragma message(NV_FILE_LINE "TODO: Implement evaluateDistanceToBoundary.") + float distance = evaluateDistanceToBoundary(chart, bestTriangles.pairs[i].face);*/ + + if (distance > maxDistance) + { + maxDistance = distance; + mostCentral = bestTriangles.pairs[i].face; + } + } + nvDebugCheck(maxDistance >= 0); + + // In order to prevent k-means cyles we record all the previously chosen seeds. + uint index; + if (chart->seeds.find(mostCentral, &index)) + { + // Move new seed to the end of the seed array. + uint last = chart->seeds.count() - 1; + swap(chart->seeds[index], chart->seeds[last]); + return false; + } + else + { + // Append new seed. + chart->seeds.append(mostCentral); + return true; + } +} + +void AtlasBuilder::removeCandidate(uint f) +{ + int c = faceCandidateArray[f]; + if (c != -1) { + faceCandidateArray[f] = -1; + + if (c == candidateArray.count() - 1) { + candidateArray.popBack(); + } + else { + candidateArray.replaceWithLast(c); + faceCandidateArray[candidateArray[c].face] = c; + } + } +} + +void AtlasBuilder::updateCandidate(ChartBuildData * chart, uint f, float metric) +{ + if (faceCandidateArray[f] == -1) { + const uint index = candidateArray.count(); + faceCandidateArray[f] = index; + candidateArray.resize(index + 1); + candidateArray[index].face = f; + candidateArray[index].chart = chart; + candidateArray[index].metric = metric; + } + else { + int c = faceCandidateArray[f]; + nvDebugCheck(c != -1); + + Candidate & candidate = candidateArray[c]; + nvDebugCheck(candidate.face == f); + + if (metric < candidate.metric || chart == candidate.chart) { + candidate.metric = metric; + candidate.chart = chart; + } + } + +} + + +void AtlasBuilder::updatePriorities(ChartBuildData * chart) +{ + // Re-evaluate candidate priorities. + uint candidateCount = chart->candidates.count(); + for (uint i = 0; i < candidateCount; i++) + { + chart->candidates.pairs[i].priority = evaluatePriority(chart, chart->candidates.pairs[i].face); + + if (faceChartArray[chart->candidates.pairs[i].face] == -1) + { + updateCandidate(chart, chart->candidates.pairs[i].face, chart->candidates.pairs[i].priority); + } + } + + // Sort candidates. + chart->candidates.sort(); +} + + +// Evaluate combined metric. +float AtlasBuilder::evaluatePriority(ChartBuildData * chart, uint face) +{ + // Estimate boundary length and area: + float newBoundaryLength = evaluateBoundaryLength(chart, face); + float newChartArea = evaluateChartArea(chart, face); + + float F = evaluateProxyFitMetric(chart, face); + float C = evaluateRoundnessMetric(chart, face, newBoundaryLength, newChartArea); + float P = evaluateStraightnessMetric(chart, face); + + // Penalize faces that cross seams, reward faces that close seams or reach boundaries. + float N = evaluateNormalSeamMetric(chart, face); + float T = evaluateTextureSeamMetric(chart, face); + + //float R = evaluateCompletenessMetric(chart, face); + + //float D = evaluateDihedralAngleMetric(chart, face); + // @@ Add a metric based on local dihedral angle. + + // @@ Tweaking the normal and texture seam metrics. + // - Cause more impedance. Never cross 90 degree edges. + // - + + float cost = float( + settings.proxyFitMetricWeight * F + + settings.roundnessMetricWeight * C + + settings.straightnessMetricWeight * P + + settings.normalSeamMetricWeight * N + + settings.textureSeamMetricWeight * T); + + /*cost = settings.proxyFitMetricWeight * powf(F, settings.proxyFitMetricExponent); + cost = max(cost, settings.roundnessMetricWeight * powf(C, settings.roundnessMetricExponent)); + cost = max(cost, settings.straightnessMetricWeight * pow(P, settings.straightnessMetricExponent)); + cost = max(cost, settings.normalSeamMetricWeight * N); + cost = max(cost, settings.textureSeamMetricWeight * T);*/ + + // Enforce limits strictly: + if (newChartArea > settings.maxChartArea) cost = FLT_MAX; + if (newBoundaryLength > settings.maxBoundaryLength) cost = FLT_MAX; + + // Make sure normal seams are fully respected: + if (settings.normalSeamMetricWeight >= 1000 && N != 0) cost = FLT_MAX; + + nvCheck(isFinite(cost)); + return cost; +} + + +// Returns a value in [0-1]. +float AtlasBuilder::evaluateProxyFitMetric(ChartBuildData * chart, uint f) +{ + const HalfEdge::Face * face = mesh->faceAt(f); + Vector3 faceNormal = triangleNormal(face); + //return square(dot(chart->coneAxis, faceNormal) - cosf(chart->coneAngle)); + + // Use plane fitting metric for now: + //return square(1 - dot(faceNormal, chart->planeNormal)); // @@ normal deviations should be weighted by face area + return 1 - dot(faceNormal, chart->planeNormal); // @@ normal deviations should be weighted by face area + + // Find distance to chart. + /*Vector3 faceCentroid = face->centroid(); + + float dist = 0; + int count = 0; + + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + + if (!edge->isBoundary()) { + const HalfEdge::Face * neighborFace = edge->pair()->face(); + if (faceChartArray[neighborFace->id()] == chart->id) { + dist += length(neighborFace->centroid() - faceCentroid); + count++; + } + } + } + + dist /= (count * count); + + return (1 - dot(faceNormal, chart->planeNormal)) * dist;*/ + + //return (1 - dot(faceNormal, chart->planeNormal)); +} + +float AtlasBuilder::evaluateDistanceToBoundary(ChartBuildData * chart, uint face) +{ +//#pragma message(NV_FILE_LINE "TODO: Evaluate distance to boundary metric.") + + // @@ This is needed for the seed relocation code. + // @@ This could provide a better roundness metric. + + return 0.0f; +} + +float AtlasBuilder::evaluateDistanceToSeed(ChartBuildData * chart, uint f) +{ + //const uint seed = chart->seeds.back(); + //const uint faceCount = mesh->faceCount(); + //return shortestPaths[seed * faceCount + f]; + + const HalfEdge::Face * seed = mesh->faceAt(chart->seeds.back()); + const HalfEdge::Face * face = mesh->faceAt(f); + return length(triangleCenter(seed) - triangleCenter(face)); +} + + +float AtlasBuilder::evaluateRoundnessMetric(ChartBuildData * chart, uint face, float newBoundaryLength, float newChartArea) +{ + // @@ D-charts use distance to seed. + // C(c,t) = pi * D(S_c,t)^2 / A_c + //return PI * square(evaluateDistanceToSeed(chart, face)) / chart->area; + //return PI * square(evaluateDistanceToSeed(chart, face)) / chart->area; + //return 2 * PI * evaluateDistanceToSeed(chart, face) / chart->boundaryLength; + + // Garland's Hierarchical Face Clustering paper uses ratio between boundary and area, which is easier to compute and might work as well: + // roundness = D^2/4*pi*A -> circle = 1, non circle greater than 1 + + //return square(newBoundaryLength) / (newChartArea * 4 * PI); + float roundness = square(chart->boundaryLength) / chart->area; + float newRoundness = square(newBoundaryLength) / newChartArea; + if (newRoundness > roundness) { + return square(newBoundaryLength) / (newChartArea * 4 * PI); + } + else { + // Offer no impedance to faces that improve roundness. + return 0; + } + + //return square(newBoundaryLength) / (4 * PI * newChartArea); + //return clamp(1 - (4 * PI * newChartArea) / square(newBoundaryLength), 0.0f, 1.0f); + + // Use the ratio between the new roundness vs. the previous roundness. + // - If we use the absolute metric, when the initial face is very long, then it's hard to make any progress. + //return (square(newBoundaryLength) * chart->area) / (square(chart->boundaryLength) * newChartArea); + //return (4 * PI * newChartArea) / square(newBoundaryLength) - (4 * PI * chart->area) / square(chart->boundaryLength); + + //if (square(newBoundaryLength) * chart->area) / (square(chart->boundaryLength) * newChartArea); + +} + +float AtlasBuilder::evaluateStraightnessMetric(ChartBuildData * chart, uint f) +{ + float l_out = 0.0f; + float l_in = 0.0f; + + const HalfEdge::Face * face = mesh->faceAt(f); + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + + //float l = edge->length(); + float l = edgeLengths[edge->id/2]; + + if (edge->isBoundary()) + { + l_out += l; + } + else + { + uint neighborFaceId = edge->pair->face->id; + if (faceChartArray[neighborFaceId] != chart->id) { + l_out += l; + } + else { + l_in += l; + } + } + } + nvDebugCheck(l_in != 0.0f); // Candidate face must be adjacent to chart. @@ This is not true if the input mesh has zero-length edges. + + //return l_out / l_in; + float ratio = (l_out - l_in) / (l_out + l_in); + //if (ratio < 0) ratio *= 10; // Encourage closing gaps. + return min(ratio, 0.0f); // Only use the straightness metric to close gaps. + //return ratio; +} + + +float AtlasBuilder::evaluateNormalSeamMetric(ChartBuildData * chart, uint f) +{ + float seamFactor = 0.0f; + float totalLength = 0.0f; + + const HalfEdge::Face * face = mesh->faceAt(f); + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + + if (edge->isBoundary()) { + continue; + } + + const uint neighborFaceId = edge->pair->face->id; + if (faceChartArray[neighborFaceId] != chart->id) { + continue; + } + + //float l = edge->length(); + float l = edgeLengths[edge->id/2]; + + totalLength += l; + + if (!edge->isSeam()) { + continue; + } + + // Make sure it's a normal seam. + if (isNormalSeam(edge)) + { + float d0 = clamp(dot(edge->vertex->nor, edge->pair->next->vertex->nor), 0.0f, 1.0f); + float d1 = clamp(dot(edge->next->vertex->nor, edge->pair->vertex->nor), 0.0f, 1.0f); + //float a0 = clamp(acosf(d0) / (PI/2), 0.0f, 1.0f); + //float a1 = clamp(acosf(d1) / (PI/2), 0.0f, 1.0f); + //l *= (a0 + a1) * 0.5f; + + l *= 1 - (d0 + d1) * 0.5f; + + seamFactor += l; + } + } + + if (seamFactor == 0) return 0.0f; + return seamFactor / totalLength; +} + + +float AtlasBuilder::evaluateTextureSeamMetric(ChartBuildData * chart, uint f) +{ + float seamLength = 0.0f; + //float newSeamLength = 0.0f; + //float oldSeamLength = 0.0f; + float totalLength = 0.0f; + + const HalfEdge::Face * face = mesh->faceAt(f); + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + + /*float l = edge->length(); + totalLength += l; + + if (edge->isBoundary() || !edge->isSeam()) { + continue; + } + + // Make sure it's a texture seam. + if (isTextureSeam(edge)) + { + uint neighborFaceId = edge->pair()->face()->id(); + if (faceChartArray[neighborFaceId] != chart->id) { + newSeamLength += l; + } + else { + oldSeamLength += l; + } + }*/ + + if (edge->isBoundary()) { + continue; + } + + const uint neighborFaceId = edge->pair->face->id; + if (faceChartArray[neighborFaceId] != chart->id) { + continue; + } + + //float l = edge->length(); + float l = edgeLengths[edge->id/2]; + totalLength += l; + + if (!edge->isSeam()) { + continue; + } + + // Make sure it's a texture seam. + if (isTextureSeam(edge)) + { + seamLength += l; + } + } + + if (seamLength == 0.0f) { + return 0.0f; // Avoid division by zero. + } + + return seamLength / totalLength; +} + + +float AtlasBuilder::evaluateSeamMetric(ChartBuildData * chart, uint f) +{ + float newSeamLength = 0.0f; + float oldSeamLength = 0.0f; + float totalLength = 0.0f; + + const HalfEdge::Face * face = mesh->faceAt(f); + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + + //float l = edge->length(); + float l = edgeLengths[edge->id/2]; + + if (edge->isBoundary()) + { + newSeamLength += l; + } + else + { + if (edge->isSeam()) + { + uint neighborFaceId = edge->pair->face->id; + if (faceChartArray[neighborFaceId] != chart->id) { + newSeamLength += l; + } + else { + oldSeamLength += l; + } + } + } + + totalLength += l; + } + + return (newSeamLength - oldSeamLength) / totalLength; +} + + +float AtlasBuilder::evaluateChartArea(ChartBuildData * chart, uint f) +{ + const HalfEdge::Face * face = mesh->faceAt(f); + //return chart->area + face->area(); + return chart->area + faceAreas[face->id]; +} + + +float AtlasBuilder::evaluateBoundaryLength(ChartBuildData * chart, uint f) +{ + float boundaryLength = chart->boundaryLength; + + // Add new edges, subtract edges shared with the chart. + const HalfEdge::Face * face = mesh->faceAt(f); + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + //float edgeLength = edge->length(); + float edgeLength = edgeLengths[edge->id/2]; + + if (edge->isBoundary()) + { + boundaryLength += edgeLength; + } + else + { + uint neighborFaceId = edge->pair->face->id; + if (faceChartArray[neighborFaceId] != chart->id) { + boundaryLength += edgeLength; + } + else { + boundaryLength -= edgeLength; + } + } + } + //nvDebugCheck(boundaryLength >= 0); + + return max(0.0f, boundaryLength); // @@ Hack! +} + +Vector3 AtlasBuilder::evaluateChartNormalSum(ChartBuildData * chart, uint f) +{ + const HalfEdge::Face * face = mesh->faceAt(f); + return chart->normalSum + triangleNormalAreaScaled(face); +} + +Vector3 AtlasBuilder::evaluateChartCentroidSum(ChartBuildData * chart, uint f) +{ + const HalfEdge::Face * face = mesh->faceAt(f); + return chart->centroidSum + face->centroid(); +} + + +Vector3 AtlasBuilder::computeChartCentroid(const ChartBuildData * chart) +{ + Vector3 centroid(0); + + const uint faceCount = chart->faces.count(); + for (uint i = 0; i < faceCount; i++) + { + const HalfEdge::Face * face = mesh->faceAt(chart->faces[i]); + centroid += triangleCenter(face); + } + + return centroid / float(faceCount); +} + + +void AtlasBuilder::fillHoles(float threshold) +{ + while (facesLeft > 0) + { + createRandomChart(threshold); + } +} + + +void AtlasBuilder::mergeChart(ChartBuildData * owner, ChartBuildData * chart, float sharedBoundaryLength) +{ + const uint faceCount = chart->faces.count(); + for (uint i = 0; i < faceCount; i++) + { + uint f = chart->faces[i]; + + nvDebugCheck(faceChartArray[f] == chart->id); + faceChartArray[f] = owner->id; + + owner->faces.append(f); + } + + // Update adjacencies? + + owner->area += chart->area; + owner->boundaryLength += chart->boundaryLength - sharedBoundaryLength; + + owner->normalSum += chart->normalSum; + owner->centroidSum += chart->centroidSum; + + updateProxy(owner); +} + +void AtlasBuilder::mergeCharts() +{ + Array sharedBoundaryLengths; + + const uint chartCount = chartArray.count(); + for (int c = chartCount-1; c >= 0; c--) + { + sharedBoundaryLengths.clear(); + sharedBoundaryLengths.resize(chartCount, 0.0f); + + ChartBuildData * chart = chartArray[c]; + + float externalBoundary = 0.0f; + + const uint faceCount = chart->faces.count(); + for (uint i = 0; i < faceCount; i++) + { + uint f = chart->faces[i]; + const HalfEdge::Face * face = mesh->faceAt(f); + + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + + //float l = edge->length(); + float l = edgeLengths[edge->id/2]; + + if (edge->isBoundary()) { + externalBoundary += l; + } + else { + uint neighborFace = edge->pair->face->id; + uint neighborChart = faceChartArray[neighborFace]; + + if (neighborChart != c) { + if ((edge->isSeam() && (isNormalSeam(edge) || isTextureSeam(edge))) || neighborChart == -2) { + externalBoundary += l; + } + else { + sharedBoundaryLengths[neighborChart] += l; + } + } + } + } + } + + for (int cc = chartCount-1; cc >= 0; cc--) + { + if (cc == c) + continue; + + ChartBuildData * chart2 = chartArray[cc]; + if (chart2 == NULL) + continue; + + if (sharedBoundaryLengths[cc] > 0.8 * max(0.0f, chart->boundaryLength - externalBoundary)) { + + // Try to avoid degenerate configurations. + if (chart2->boundaryLength > sharedBoundaryLengths[cc]) + { + if (dot(chart2->planeNormal, chart->planeNormal) > -0.25) { + mergeChart(chart2, chart, sharedBoundaryLengths[cc]); + delete chart; + chartArray[c] = NULL; + break; + } + } + } + + if (sharedBoundaryLengths[cc] > 0.20 * max(0.0f, chart->boundaryLength - externalBoundary)) { + + // Compare proxies. + if (dot(chart2->planeNormal, chart->planeNormal) > 0) { + mergeChart(chart2, chart, sharedBoundaryLengths[cc]); + delete chart; + chartArray[c] = NULL; + break; + } + } + } + } + + // Remove deleted charts. + for (int c = 0; c < I32(chartArray.count()); /*do not increment if removed*/) + { + if (chartArray[c] == NULL) { + chartArray.removeAt(c); + + // Update faceChartArray. + const uint faceCount = faceChartArray.count(); + for (uint i = 0; i < faceCount; i++) { + nvDebugCheck (faceChartArray[i] != -1); + nvDebugCheck (faceChartArray[i] != c); + nvDebugCheck (faceChartArray[i] <= I32(chartArray.count())); + + if (faceChartArray[i] > c) { + faceChartArray[i]--; + } + } + } + else { + chartArray[c]->id = c; + c++; + } + } +} + + + +const Array & AtlasBuilder::chartFaces(uint i) const +{ + return chartArray[i]->faces; +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/AtlasBuilder.h b/thirdparty/thekla_atlas/src/nvmesh/param/AtlasBuilder.h new file mode 100755 index 00000000..f25c724f --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/AtlasBuilder.h @@ -0,0 +1,111 @@ +// This code is in the public domain -- castano@gmail.com + +#pragma once +#ifndef NV_MESH_ATLASBUILDER_H +#define NV_MESH_ATLASBUILDER_H + +#include "Atlas.h" + +#include "nvmath/Vector.h" +#include "nvmath/Random.h" +#include "nvmesh/nvmesh.h" + +#include "nvcore/Array.h" +#include "nvcore/BitArray.h" + + + +namespace nv +{ + namespace HalfEdge { class Mesh; } + + struct ChartBuildData; + + struct AtlasBuilder + { + AtlasBuilder(const HalfEdge::Mesh * m); + ~AtlasBuilder(); + + void markUnchartedFaces(const Array & unchartedFaces); + + void computeShortestPaths(); + + void placeSeeds(float threshold, uint maxSeedCount); + void createRandomChart(float threshold); + + void addFaceToChart(ChartBuildData * chart, uint f, bool recomputeProxy=false); + + bool growCharts(float threshold, uint faceCount); + bool growChart(ChartBuildData * chart, float threshold, uint faceCount); + + void resetCharts(); + + void updateCandidates(ChartBuildData * chart, uint face); + + void updateProxies(); + void updateProxy(ChartBuildData * chart); + + bool relocateSeeds(); + bool relocateSeed(ChartBuildData * chart); + + void updatePriorities(ChartBuildData * chart); + + float evaluatePriority(ChartBuildData * chart, uint face); + float evaluateProxyFitMetric(ChartBuildData * chart, uint face); + float evaluateDistanceToBoundary(ChartBuildData * chart, uint face); + float evaluateDistanceToSeed(ChartBuildData * chart, uint face); + float evaluateRoundnessMetric(ChartBuildData * chart, uint face, float newBoundaryLength, float newChartArea); + float evaluateStraightnessMetric(ChartBuildData * chart, uint face); + + float evaluateNormalSeamMetric(ChartBuildData * chart, uint f); + float evaluateTextureSeamMetric(ChartBuildData * chart, uint f); + float evaluateSeamMetric(ChartBuildData * chart, uint f); + + float evaluateChartArea(ChartBuildData * chart, uint f); + float evaluateBoundaryLength(ChartBuildData * chart, uint f); + Vector3 evaluateChartNormalSum(ChartBuildData * chart, uint f); + Vector3 evaluateChartCentroidSum(ChartBuildData * chart, uint f); + + Vector3 computeChartCentroid(const ChartBuildData * chart); + + + void fillHoles(float threshold); + void mergeCharts(); + + // @@ Cleanup. + struct Candidate { + uint face; + ChartBuildData * chart; + float metric; + }; + + const Candidate & getBestCandidate() const; + void removeCandidate(uint f); + void updateCandidate(ChartBuildData * chart, uint f, float metric); + + void mergeChart(ChartBuildData * owner, ChartBuildData * chart, float sharedBoundaryLength); + + + uint chartCount() const { return chartArray.count(); } + const Array & chartFaces(uint i) const; + + const HalfEdge::Mesh * mesh; + uint facesLeft; + Array faceChartArray; + Array chartArray; + Array shortestPaths; + + Array edgeLengths; + Array faceAreas; + + Array candidateArray; // + Array faceCandidateArray; // Map face index to candidate index. + + MTRand rand; + + SegmentationSettings settings; + }; + +} // nv namespace + +#endif // NV_MESH_ATLASBUILDER_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/AtlasPacker.cpp b/thirdparty/thekla_atlas/src/nvmesh/param/AtlasPacker.cpp new file mode 100755 index 00000000..f2156899 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/AtlasPacker.cpp @@ -0,0 +1,1379 @@ +// This code is in the public domain -- castano@gmail.com + +#include "nvmesh.h" // pch + +#include "AtlasPacker.h" +#include "nvmesh/halfedge/Vertex.h" +#include "nvmesh/halfedge/Face.h" +#include "nvmesh/param/Atlas.h" +#include "nvmesh/param/Util.h" +#include "nvmesh/raster/Raster.h" + +#include "nvmath/Vector.inl" +#include "nvmath/ConvexHull.h" +#include "nvmath/Color.h" +#include "nvmath/ftoi.h" + +#include "nvcore/StrLib.h" // debug +#include "nvcore/StdStream.h" // fileOpen + +#include // FLT_MAX +#include // UINT_MAX + +using namespace nv; + +#define DEBUG_OUTPUT 0 + +#if DEBUG_OUTPUT + +#include "nvimage/ImageIO.h" + +namespace +{ + const uint TGA_TYPE_GREY = 3; + const uint TGA_TYPE_RGB = 2; + const uint TGA_ORIGIN_UPPER = 0x20; + +#pragma pack(push, 1) + struct TgaHeader { + uint8 id_length; + uint8 colormap_type; + uint8 image_type; + uint16 colormap_index; + uint16 colormap_length; + uint8 colormap_size; + uint16 x_origin; + uint16 y_origin; + uint16 width; + uint16 height; + uint8 pixel_size; + uint8 flags; + + enum { Size = 18 }; //const static int SIZE = 18; + }; +#pragma pack(pop) + + static void outputDebugBitmap(const char * fileName, const BitMap & bitmap, int w, int h) + { + FILE * fp = fileOpen(fileName, "wb"); + if (fp == NULL) return; + + nvStaticCheck(sizeof(TgaHeader) == TgaHeader::Size); + TgaHeader tga; + tga.id_length = 0; + tga.colormap_type = 0; + tga.image_type = TGA_TYPE_GREY; + + tga.colormap_index = 0; + tga.colormap_length = 0; + tga.colormap_size = 0; + + tga.x_origin = 0; + tga.y_origin = 0; + tga.width = w; + tga.height = h; + tga.pixel_size = 8; + tga.flags = TGA_ORIGIN_UPPER; + + fwrite(&tga, sizeof(TgaHeader), 1, fp); + + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + uint8 color = bitmap.bitAt(i, j) ? 0xFF : 0x0; + fwrite(&color, 1, 1, fp); + } + } + + fclose(fp); + } + + static void outputDebugImage(const char * fileName, const Image & bitmap, int w, int h) + { + FILE * fp = fileOpen(fileName, "wb"); + if (fp == NULL) return; + + nvStaticCheck(sizeof(TgaHeader) == TgaHeader::Size); + TgaHeader tga; + tga.id_length = 0; + tga.colormap_type = 0; + tga.image_type = TGA_TYPE_RGB; + + tga.colormap_index = 0; + tga.colormap_length = 0; + tga.colormap_size = 0; + + tga.x_origin = 0; + tga.y_origin = 0; + tga.width = w; + tga.height = h; + tga.pixel_size = 24; + tga.flags = TGA_ORIGIN_UPPER; + + fwrite(&tga, sizeof(TgaHeader), 1, fp); + + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + Color32 color = bitmap.pixel(i, j); + fwrite(&color.r, 1, 1, fp); + fwrite(&color.g, 1, 1, fp); + fwrite(&color.b, 1, 1, fp); + } + } + + fclose(fp); + } +} + +#endif // DEBUG_OUTPUT + +inline int align(int x, int a) { + //return a * ((x + a - 1) / a); + //return (x + a - 1) & -a; + return (x + a - 1) & ~(a - 1); +} + +inline bool isAligned(int x, int a) { + return (x & (a - 1)) == 0; +} + + + +AtlasPacker::AtlasPacker(Atlas * atlas) : m_atlas(atlas), m_bitmap(256, 256) +{ + m_width = 0; + m_height = 0; + + m_debug_bitmap.allocate(256, 256); + m_debug_bitmap.fill(Color32(0,0,0,0)); +} + +AtlasPacker::~AtlasPacker() +{ +} + +// This should compute convex hull and use rotating calipers to find the best box. Currently it uses a brute force method. +static void computeBoundingBox(Chart * chart, Vector2 * majorAxis, Vector2 * minorAxis, Vector2 * minCorner, Vector2 * maxCorner) +{ + // Compute list of boundary points. + Array points(16); + + HalfEdge::Mesh * mesh = chart->chartMesh(); + const uint vertexCount = mesh->vertexCount(); + + for (uint i = 0; i < vertexCount; i++) { + HalfEdge::Vertex * vertex = mesh->vertexAt(i); + if (vertex->isBoundary()) { + points.append(vertex->tex); + } + } + + // This is not valid anymore. The chart mesh may have multiple boundaries! + /*const HalfEdge::Vertex * vertex = findBoundaryVertex(chart->chartMesh()); + + // Traverse boundary. + const HalfEdge::Edge * const firstEdge = vertex->edge(); + const HalfEdge::Edge * edge = firstEdge; + do { + vertex = edge->vertex(); + + nvDebugCheck (vertex->isBoundary()); + points.append(vertex->tex); + + edge = edge->next(); + } while (edge != firstEdge);*/ + +#if 1 + Array hull; + + convexHull(points, hull, 0.00001f); + + // @@ Ideally I should use rotating calipers to find the best box. Using brute force for now. + + float best_area = FLT_MAX; + Vector2 best_min; + Vector2 best_max; + Vector2 best_axis; + + const uint hullCount = hull.count(); + for (uint i = 0, j = hullCount-1; i < hullCount; j = i, i++) { + + if (equal(hull[i], hull[j])) { + continue; + } + + Vector2 axis = normalize(hull[i] - hull[j], 0.0f); + nvDebugCheck(isFinite(axis)); + + // Compute bounding box. + Vector2 box_min(FLT_MAX, FLT_MAX); + Vector2 box_max(-FLT_MAX, -FLT_MAX); + + for (uint v = 0; v < hullCount; v++) { + + Vector2 point = hull[v]; + + float x = dot(axis, point); + if (x < box_min.x) box_min.x = x; + if (x > box_max.x) box_max.x = x; + + float y = dot(Vector2(-axis.y, axis.x), point); + if (y < box_min.y) box_min.y = y; + if (y > box_max.y) box_max.y = y; + } + + // Compute box area. + float area = (box_max.x - box_min.x) * (box_max.y - box_min.y); + + if (area < best_area) { + best_area = area; + best_min = box_min; + best_max = box_max; + best_axis = axis; + } + } + + // Make sure the box contains all the input points since the convex hull is not 100% accurate. + /*const uint pointCount = points.count(); + for (uint v = 0; v < pointCount; v++) { + + Vector2 point = points[v]; + + float x = dot(best_axis, point); + if (x < best_min.x) best_min.x = x; + + float y = dot(Vector2(-best_axis.y, best_axis.x), point); + if (y < best_min.y) best_min.y = y; + }*/ + + // Consider all points, not only boundary points, in case the input chart is malformed. + for (uint i = 0; i < vertexCount; i++) { + HalfEdge::Vertex * vertex = mesh->vertexAt(i); + Vector2 point = vertex->tex; + + float x = dot(best_axis, point); + if (x < best_min.x) best_min.x = x; + if (x > best_max.x) best_max.x = x; + + float y = dot(Vector2(-best_axis.y, best_axis.x), point); + if (y < best_min.y) best_min.y = y; + if (y > best_max.y) best_max.y = y; + } + + *majorAxis = best_axis; + *minorAxis = Vector2(-best_axis.y, best_axis.x); + *minCorner = best_min; + *maxCorner = best_max; + +#else + // Approximate implementation: try 16 different directions and keep the best. + + const uint N = 16; + Vector2 axis[N]; + + float minAngle = 0; + float maxAngle = PI / 2; + + int best; + Vector2 mins[N]; + Vector2 maxs[N]; + + const int iterationCount = 1; + for (int j = 0; j < iterationCount; j++) + { + // Init predefined directions. + for (int i = 0; i < N; i++) + { + float angle = lerp(minAngle, maxAngle, float(i)/N); + axis[i].set(cosf(angle), sinf(angle)); + } + + // Compute box for each direction. + for (int i = 0; i < N; i++) + { + mins[i].set(FLT_MAX, FLT_MAX); + maxs[i].set(-FLT_MAX, -FLT_MAX); + } + + for (uint p = 0; p < points.count(); p++) + { + Vector2 point = points[p]; + + for (int i = 0; i < N; i++) + { + float x = dot(axis[i], point); + if (x < mins[i].x) mins[i].x = x; + if (x > maxs[i].x) maxs[i].x = x; + + float y = dot(Vector2(-axis[i].y, axis[i].x), point); + if (y < mins[i].y) mins[i].y = y; + if (y > maxs[i].y) maxs[i].y = y; + } + } + + // Find box with minimum area. + best = -1; + int second_best = -1; + float best_area = FLT_MAX; + float second_best_area = FLT_MAX; + + for (int i = 0; i < N; i++) + { + float area = (maxs[i].x - mins[i].x) * (maxs[i].y - mins[i].y); + + if (area < best_area) + { + second_best_area = best_area; + second_best = best; + + best_area = area; + best = i; + } + else if (area < second_best_area) + { + second_best_area = area; + second_best = i; + } + } + nvDebugCheck(best != -1); + nvDebugCheck(second_best != -1); + nvDebugCheck(best != second_best); + + if (j != iterationCount-1) + { + // Handle wrap-around during the first iteration. + if (j == 0) { + if (best == 0 && second_best == N-1) best = N; + if (best == N-1 && second_best == 0) second_best = N; + } + + if (best < second_best) swap(best, second_best); + + // Update angles. + float deltaAngle = (maxAngle - minAngle) / N; + maxAngle = minAngle + (best - 0.5f) * deltaAngle; + minAngle = minAngle + (second_best + 0.5f) * deltaAngle; + } + } + + // Compute major and minor axis, and origin. + *majorAxis = axis[best]; + *minorAxis = Vector2(-axis[best].y, axis[best].x); + *origin = mins[best]; + + // @@ If the parameterization is invalid, we could have an interior vertex outside the boundary. + // @@ In that case the returned bounding box would be incorrect. Compute updated bounds here. + /*for (uint p = 0; p < points.count(); p++) + { + Vector2 point = points[p]; + + for (int i = 0; i < N; i++) + { + float x = dot(*majorAxis, point); + float y = dot(*minorAxis, point); + } + }*/ +#endif +} + + +void AtlasPacker::packCharts(int quality, float texelsPerUnit, bool blockAligned, bool conservative) +{ + const uint chartCount = m_atlas->chartCount(); + if (chartCount == 0) return; + + Array chartOrderArray; + chartOrderArray.resize(chartCount); + + Array chartExtents; + chartExtents.resize(chartCount); + + float meshArea = 0; + for (uint c = 0; c < chartCount; c++) + { + Chart * chart = m_atlas->chartAt(c); + + if (!chart->isVertexMapped() && !chart->isDisk()) { + chartOrderArray[c] = 0; + + // Skip non-disks. + continue; + } + + Vector2 extents(0.0f); + + if (chart->isVertexMapped()) { + // Let's assume vertex maps are arranged in a rectangle. + //HalfEdge::Mesh * mesh = chart->chartMesh(); + + // Arrange vertices in a rectangle. + extents.x = float(chart->vertexMapWidth); + extents.y = float(chart->vertexMapHeight); + } + else { + // Compute surface area to sort charts. + float chartArea = chart->computeSurfaceArea(); + meshArea += chartArea; + //chartOrderArray[c] = chartArea; + + // Compute chart scale + float parametricArea = fabs(chart->computeParametricArea()); // @@ There doesn't seem to be anything preventing parametric area to be negative. + if (parametricArea < NV_EPSILON) { + // When the parametric area is too small we use a rough approximation to prevent divisions by very small numbers. + Vector2 bounds = chart->computeParametricBounds(); + parametricArea = bounds.x * bounds.y; + } + float scale = (chartArea / parametricArea) * texelsPerUnit; + if (parametricArea == 0) // < NV_EPSILON) + { + scale = 0; + } + nvCheck(isFinite(scale)); + + // Compute bounding box of chart. + Vector2 majorAxis, minorAxis, origin, end; + computeBoundingBox(chart, &majorAxis, &minorAxis, &origin, &end); + + nvCheck(isFinite(majorAxis) && isFinite(minorAxis) && isFinite(origin)); + + // Sort charts by perimeter. @@ This is sometimes producing somewhat unexpected results. Is this right? + //chartOrderArray[c] = ((end.x - origin.x) + (end.y - origin.y)) * scale; + + // Translate, rotate and scale vertices. Compute extents. + HalfEdge::Mesh * mesh = chart->chartMesh(); + const uint vertexCount = mesh->vertexCount(); + for (uint i = 0; i < vertexCount; i++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(i); + + //Vector2 t = vertex->tex - origin; + Vector2 tmp; + tmp.x = dot(vertex->tex, majorAxis); + tmp.y = dot(vertex->tex, minorAxis); + tmp -= origin; + tmp *= scale; + if (tmp.x < 0 || tmp.y < 0) { + nvDebug("tmp: %f %f\n", tmp.x, tmp.y); + nvDebug("scale: %f\n", scale); + nvDebug("origin: %f %f\n", origin.x, origin.y); + nvDebug("majorAxis: %f %f\n", majorAxis.x, majorAxis.y); + nvDebug("minorAxis: %f %f\n", minorAxis.x, minorAxis.y); + nvDebugBreak(); + } + //nvCheck(tmp.x >= 0 && tmp.y >= 0); + + vertex->tex = tmp; + + nvCheck(isFinite(vertex->tex.x) && isFinite(vertex->tex.y)); + + extents = max(extents, tmp); + } + nvDebugCheck(extents.x >= 0 && extents.y >= 0); + + // Limit chart size. + if (extents.x > 1024 || extents.y > 1024) { + float limit = max(extents.x, extents.y); + + scale = 1024 / (limit + 1); + + for (uint i = 0; i < vertexCount; i++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(i); + vertex->tex *= scale; + } + + extents *= scale; + + nvDebugCheck(extents.x <= 1024 && extents.y <= 1024); + } + + + // Scale the charts to use the entire texel area available. So, if the width is 0.1 we could scale it to 1 without increasing the lightmap usage and making a better + // use of it. In many cases this also improves the look of the seams, since vertices on the chart boundaries have more chances of being aligned with the texel centers. + + float scale_x = 1.0f; + float scale_y = 1.0f; + + float divide_x = 1.0f; + float divide_y = 1.0f; + + if (extents.x > 0) { + int cw = ftoi_ceil(extents.x); + + if (blockAligned) { + // Align all chart extents to 4x4 blocks, but taking padding into account. + if (conservative) { + cw = align(cw + 2, 4) - 2; + } + else { + cw = align(cw + 1, 4) - 1; + } + } + + scale_x = (float(cw) - NV_EPSILON); + divide_x = extents.x; + extents.x = float(cw); + } + + if (extents.y > 0) { + int ch = ftoi_ceil(extents.y); + + if (blockAligned) { + // Align all chart extents to 4x4 blocks, but taking padding into account. + if (conservative) { + ch = align(ch + 2, 4) - 2; + } + else { + ch = align(ch + 1, 4) - 1; + } + } + + scale_y = (float(ch) - NV_EPSILON); + divide_y = extents.y; + extents.y = float(ch); + } + + for (uint v = 0; v < vertexCount; v++) { + HalfEdge::Vertex * vertex = mesh->vertexAt(v); + + vertex->tex.x /= divide_x; + vertex->tex.y /= divide_y; + vertex->tex.x *= scale_x; + vertex->tex.y *= scale_y; + + nvCheck(isFinite(vertex->tex.x) && isFinite(vertex->tex.y)); + } + } + + chartExtents[c] = extents; + + // Sort charts by perimeter. + chartOrderArray[c] = extents.x + extents.y; + } + + // @@ We can try to improve compression of small charts by sorting them by proximity like we do with vertex samples. + // @@ How to do that? One idea: compute chart centroid, insert into grid, compute morton index of the cell, sort based on morton index. + // @@ We would sort by morton index, first, then quantize the chart sizes, so that all small charts have the same size, and sort by size preserving the morton order. + + //nvDebug("Sorting charts.\n"); + + // Sort charts by area. + m_radix.sort(chartOrderArray); + const uint32 * ranks = m_radix.ranks(); + + // Estimate size of the map based on the mesh surface area and given texel scale. + float texelCount = meshArea * square(texelsPerUnit) / 0.75f; // Assume 75% utilization. + if (texelCount < 1) texelCount = 1; + uint approximateExtent = nextPowerOfTwo(uint(sqrtf(texelCount))); + + //nvDebug("Init bitmap.\n"); + + // @@ Pack all charts smaller than a texel into a compact rectangle. + // @@ Start considering only 1x1 charts. Extend to 1xn charts later. + + /*for (uint i = 0; i < chartCount; i++) + { + uint c = ranks[chartCount - i - 1]; // largest chart first + + Chart * chart = m_atlas->chartAt(c); + + if (!chart->isDisk()) continue; + + if (iceil(chartExtents[c].x) == 1 && iceil(chartExtents[c].x) == 1) { + // @@ Add to + } + }*/ + + + + // Init bit map. + m_bitmap.clearAll(); + if (approximateExtent > m_bitmap.width()) { + m_bitmap.resize(approximateExtent, approximateExtent, false); + m_debug_bitmap.resize(approximateExtent, approximateExtent); + m_debug_bitmap.fill(Color32(0,0,0,0)); + } + + + int w = 0; + int h = 0; + +#if 1 + // Add sorted charts to bitmap. + for (uint i = 0; i < chartCount; i++) + { + uint c = ranks[chartCount - i - 1]; // largest chart first + + Chart * chart = m_atlas->chartAt(c); + + if (!chart->isVertexMapped() && !chart->isDisk()) continue; + + //float scale_x = 1; + //float scale_y = 1; + + BitMap chart_bitmap; + + if (chart->isVertexMapped()) { + // Init all bits to 1. + chart_bitmap.resize(ftoi_ceil(chartExtents[c].x), ftoi_ceil(chartExtents[c].y), /*initValue=*/true); + + // @@ Another alternative would be to try to map each vertex to a different texel trying to fill all the available unused texels. + } + else { + // @@ Add special cases for dot and line charts. @@ Lightmap rasterizer also needs to handle these special cases. + // @@ We could also have a special case for chart quads. If the quad surface <= 4 texels, align vertices with texel centers and do not add padding. May be very useful for foliage. + + // @@ In general we could reduce the padding of all charts by one texel by using a rasterizer that takes into account the 2-texel footprint of the tent bilinear filter. For example, + // if we have a chart that is less than 1 texel wide currently we add one texel to the left and one texel to the right creating a 3-texel-wide bitmap. However, if we know that the + // chart is only 1 texel wide we could align it so that it only touches the footprint of two texels: + + // | | <- Touches texels 0, 1 and 2. + // | | <- Only touches texels 0 and 1. + // \ \ / \ / / + // \ X X / + // \ / \ / \ / + // V V V + // 0 1 2 + + if (conservative) { + // Init all bits to 0. + chart_bitmap.resize(ftoi_ceil(chartExtents[c].x) + 2, ftoi_ceil(chartExtents[c].y) + 2, /*initValue=*/false); // + 2 to add padding on both sides. + + // Rasterize chart and dilate. + drawChartBitmapDilate(chart, &chart_bitmap, /*padding=*/1); + } + else { + // Init all bits to 0. + chart_bitmap.resize(ftoi_ceil(chartExtents[c].x) + 1, ftoi_ceil(chartExtents[c].y) + 1, /*initValue=*/false); // Add half a texels on each side. + + // Rasterize chart and dilate. + drawChartBitmap(chart, &chart_bitmap, Vector2(1), Vector2(0.5)); + } + } + + int best_x, best_y; + int best_cw, best_ch; // Includes padding now. + int best_r; + findChartLocation(quality, &chart_bitmap, chartExtents[c], w, h, &best_x, &best_y, &best_cw, &best_ch, &best_r); + + /*if (w < best_x + best_cw || h < best_y + best_ch) + { + nvDebug("Resize extents to (%d, %d).\n", best_x + best_cw, best_y + best_ch); + }*/ + + // Update parametric extents. + w = max(w, best_x + best_cw); + h = max(h, best_y + best_ch); + + w = align(w, 4); + h = align(h, 4); + + // Resize bitmap if necessary. + if (uint(w) > m_bitmap.width() || uint(h) > m_bitmap.height()) + { + //nvDebug("Resize bitmap (%d, %d).\n", nextPowerOfTwo(w), nextPowerOfTwo(h)); + m_bitmap.resize(nextPowerOfTwo(U32(w)), nextPowerOfTwo(U32(h)), false); + m_debug_bitmap.resize(nextPowerOfTwo(U32(w)), nextPowerOfTwo(U32(h))); + } + + //nvDebug("Add chart at (%d, %d).\n", best_x, best_y); + + addChart(&chart_bitmap, w, h, best_x, best_y, best_r, /*debugOutput=*/NULL); + + // IC: Output chart again to debug bitmap. + if (chart->isVertexMapped()) { + addChart(&chart_bitmap, w, h, best_x, best_y, best_r, &m_debug_bitmap); + } + else { + addChart(chart, w, h, best_x, best_y, best_r, &m_debug_bitmap); + } + + //float best_angle = 2 * PI * best_r; + + // Translate and rotate chart texture coordinates. + HalfEdge::Mesh * mesh = chart->chartMesh(); + const uint vertexCount = mesh->vertexCount(); + for (uint v = 0; v < vertexCount; v++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(v); + + Vector2 t = vertex->tex; + if (best_r) swap(t.x, t.y); + //vertex->tex.x = best_x + t.x * cosf(best_angle) - t.y * sinf(best_angle); + //vertex->tex.y = best_y + t.x * sinf(best_angle) + t.y * cosf(best_angle); + + vertex->tex.x = best_x + t.x + 0.5f; + vertex->tex.y = best_y + t.y + 0.5f; + + nvCheck(vertex->tex.x >= 0 && vertex->tex.y >= 0); + nvCheck(isFinite(vertex->tex.x) && isFinite(vertex->tex.y)); + } + +#if DEBUG_OUTPUT && 0 + StringBuilder fileName; + fileName.format("debug_packer_%d.tga", i); + //outputDebugBitmap(fileName.str(), m_bitmap, w, h); + outputDebugImage(fileName.str(), m_debug_bitmap, w, h); +#endif + } + +#else // 0 + + // Add sorted charts to bitmap. + for (uint i = 0; i < chartCount; i++) + { + uint c = ranks[chartCount - i - 1]; // largest chart first + + Chart * chart = m_atlas->chartAt(c); + + if (!chart->isDisk()) continue; + + Vector2 scale(1, 1); + +#if 0 // old method. + //m_padding_x = 2*padding; + //m_padding_y = 2*padding; +#else + //m_padding_x = 0; //padding; + //m_padding_y = 0; //padding; +#endif + + int bw = ftoi_ceil(chartExtents[c].x + 1); + int bh = ftoi_ceil(chartExtents[c].y + 1); + + if (chartExtents[c].x < 1.0f) { + scale.x = 0.01f; // @@ Ideally we would like to scale it to 0, but then our rasterizer would not touch any pixels. + bw = 1; + } + if (chartExtents[c].y < 1.0f) { + scale.y = 0.01f; + bh = 1; + } + + //BitMap chart_bitmap(iceil(chartExtents[c].x) + 1 + m_padding_x * 2, iceil(chartExtents[c].y) + 1 + m_padding_y * 2); + //BitMap chart_bitmap(ftoi_ceil(chartExtents[c].x/2)*2, ftoi_ceil(chartExtents[c].y/2)*2); + BitMap chart_bitmap(bw, bh); + chart_bitmap.clearAll(); + + Vector2 offset; + offset.x = 0; // (chart_bitmap.width() - chartExtents[c].x) * 0.5f; + offset.y = 0; // (chart_bitmap.height() - chartExtents[c].y) * 0.5f; + + drawChartBitmap(chart, &chart_bitmap, scale, offset); + + int best_x, best_y; + int best_cw, best_ch; + int best_r; + findChartLocation(quality, &chart_bitmap, chartExtents[c], w, h, &best_x, &best_y, &best_cw, &best_ch, &best_r); + + /*if (w < best_x + best_cw || h < best_y + best_ch) + { + nvDebug("Resize extents to (%d, %d).\n", best_x + best_cw, best_y + best_ch); + }*/ + + // Update parametric extents. + w = max(w, best_x + best_cw); + h = max(h, best_y + best_ch); + + // Resize bitmap if necessary. + if (uint(w) > m_bitmap.width() || uint(h) > m_bitmap.height()) + { + //nvDebug("Resize bitmap (%d, %d).\n", nextPowerOfTwo(w), nextPowerOfTwo(h)); + m_bitmap.resize(nextPowerOfTwo(w), nextPowerOfTwo(h), false); + m_debug_bitmap.resize(nextPowerOfTwo(w), nextPowerOfTwo(h)); + } + + //nvDebug("Add chart at (%d, %d).\n", best_x, best_y); + +#if 0 // old method. +#if _DEBUG + checkCanAddChart(chart, w, h, best_x, best_y, best_r); +#endif + + // Add chart. + addChart(chart, w, h, best_x, best_y, best_r); +#else + // Add chart reusing its bitmap. + addChart(&chart_bitmap, w, h, best_x, best_y, best_r); +#endif + + //float best_angle = 2 * PI * best_r; + + // Translate and rotate chart texture coordinates. + HalfEdge::Mesh * mesh = chart->chartMesh(); + const uint vertexCount = mesh->vertexCount(); + for (uint v = 0; v < vertexCount; v++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(v); + + Vector2 t = vertex->tex * scale + offset; + if (best_r) swap(t.x, t.y); + //vertex->tex.x = best_x + t.x * cosf(best_angle) - t.y * sinf(best_angle); + //vertex->tex.y = best_y + t.x * sinf(best_angle) + t.y * cosf(best_angle); + vertex->tex.x = best_x + t.x + 0.5f; + vertex->tex.y = best_y + t.y + 0.5f; + + nvCheck(vertex->tex.x >= 0 && vertex->tex.y >= 0); + } + +#if DEBUG_OUTPUT && 0 + StringBuilder fileName; + fileName.format("debug_packer_%d.tga", i); + //outputDebugBitmap(fileName.str(), m_bitmap, w, h); + outputDebugImage(fileName.str(), m_debug_bitmap, w, h); +#endif + } + +#endif // 0 + + //w -= padding - 1; // Leave one pixel border! + //h -= padding - 1; + + m_width = max(0, w); + m_height = max(0, h); + + nvCheck(isAligned(m_width, 4)); + nvCheck(isAligned(m_height, 4)); + + m_debug_bitmap.resize(m_width, m_height); + m_debug_bitmap.setFormat(Image::Format_ARGB); + +#if DEBUG_OUTPUT + //outputDebugBitmap("debug_packer_final.tga", m_bitmap, w, h); + //outputDebugImage("debug_packer_final.tga", m_debug_bitmap, w, h); + ImageIO::save("debug_packer_final.tga", &m_debug_bitmap); +#endif +} + + +// IC: Brute force is slow, and random may take too much time to converge. We start inserting large charts in a small atlas. Using brute force is lame, because most of the space +// is occupied at this point. At the end we have many small charts and a large atlas with sparse holes. Finding those holes randomly is slow. A better approach would be to +// start stacking large charts as if they were tetris pieces. Once charts get small try to place them randomly. It may be interesting to try a intermediate strategy, first try +// along one axis and then try exhaustively along that axis. +void AtlasPacker::findChartLocation(int quality, const BitMap * bitmap, Vector2::Arg extents, int w, int h, int * best_x, int * best_y, int * best_w, int * best_h, int * best_r) +{ + int attempts = 256; + if (quality == 1) attempts = 4096; + if (quality == 2) attempts = 2048; + if (quality == 3) attempts = 1024; + if (quality == 4) attempts = 512; + + if (quality == 0 || w*h < attempts) + { + findChartLocation_bruteForce(bitmap, extents, w, h, best_x, best_y, best_w, best_h, best_r); + } + else + { + findChartLocation_random(bitmap, extents, w, h, best_x, best_y, best_w, best_h, best_r, attempts); + } +} + +#define BLOCK_SIZE 4 + +void AtlasPacker::findChartLocation_bruteForce(const BitMap * bitmap, Vector2::Arg extents, int w, int h, int * best_x, int * best_y, int * best_w, int * best_h, int * best_r) +{ + int best_metric = INT_MAX; + + // Try two different orientations. + for (int r = 0; r < 2; r++) + { + int cw = bitmap->width(); + int ch = bitmap->height(); + if (r & 1) swap(cw, ch); + + for (int y = 0; y <= h + 1; y += BLOCK_SIZE) // + 1 to extend atlas in case atlas full. + { + for (int x = 0; x <= w + 1; x += BLOCK_SIZE) // + 1 not really necessary here. + { + // Early out. + int area = max(w, x+cw) * max(h, y+ch); + //int perimeter = max(w, x+cw) + max(h, y+ch); + int extents = max(max(w, x+cw), max(h, y+ch)); + + int metric = extents*extents + area; + + if (metric > best_metric) { + continue; + } + if (metric == best_metric && max(x, y) >= max(*best_x, *best_y)) { + // If metric is the same, pick the one closest to the origin. + continue; + } + + if (canAddChart(bitmap, w, h, x, y, r)) + { + best_metric = metric; + *best_x = x; + *best_y = y; + *best_w = cw; + *best_h = ch; + *best_r = r; + + if (area == w*h) + { + // Chart is completely inside, do not look at any other location. + goto done; + } + } + } + } + } + +done: + nvDebugCheck (best_metric != INT_MAX); +} + + +void AtlasPacker::findChartLocation_random(const BitMap * bitmap, Vector2::Arg extents, int w, int h, int * best_x, int * best_y, int * best_w, int * best_h, int * best_r, int minTrialCount) +{ + int best_metric = INT_MAX; + + for (int i = 0; i < minTrialCount || best_metric == INT_MAX; i++) + { + int r = m_rand.getRange(1); + int x = m_rand.getRange(w + 1); // + 1 to extend atlas in case atlas full. We may want to use a higher number to increase probability of extending atlas. + int y = m_rand.getRange(h + 1); // + 1 to extend atlas in case atlas full. + + x = align(x, BLOCK_SIZE); + y = align(y, BLOCK_SIZE); + + int cw = bitmap->width(); + int ch = bitmap->height(); + if (r & 1) swap(cw, ch); + + // Early out. + int area = max(w, x+cw) * max(h, y+ch); + //int perimeter = max(w, x+cw) + max(h, y+ch); + int extents = max(max(w, x+cw), max(h, y+ch)); + + int metric = extents*extents + area; + + if (metric > best_metric) { + continue; + } + if (metric == best_metric && min(x, y) > min(*best_x, *best_y)) { + // If metric is the same, pick the one closest to the origin. + continue; + } + + if (canAddChart(bitmap, w, h, x, y, r)) + { + best_metric = metric; + *best_x = x; + *best_y = y; + *best_w = cw; + *best_h = ch; + *best_r = r; + + if (area == w*h) + { + // Chart is completely inside, do not look at any other location. + break; + } + } + } +} + + +void AtlasPacker::drawChartBitmapDilate(const Chart * chart, BitMap * bitmap, int padding) +{ + const int w = bitmap->width(); + const int h = bitmap->height(); + const Vector2 extents = Vector2(float(w), float(h)); + + // Rasterize chart faces, check that all bits are not set. + const uint faceCount = chart->faceCount(); + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = chart->chartMesh()->faceAt(f); + + Vector2 vertices[4]; + + uint edgeCount = 0; + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + if (edgeCount < 4) + { + vertices[edgeCount] = it.vertex()->tex + Vector2(0.5) + Vector2(float(padding), float(padding)); + } + edgeCount++; + } + + if (edgeCount == 3) + { + Raster::drawTriangle(Raster::Mode_Antialiased, extents, true, vertices, AtlasPacker::setBitsCallback, bitmap); + } + else + { + Raster::drawQuad(Raster::Mode_Antialiased, extents, true, vertices, AtlasPacker::setBitsCallback, bitmap); + } + } + + // Expand chart by padding pixels. (dilation) + BitMap tmp(w, h); + for (int i = 0; i < padding; i++) { + tmp.clearAll(); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + bool b = bitmap->bitAt(x, y); + if (!b) { + if (x > 0) { + b |= bitmap->bitAt(x - 1, y); + if (y > 0) b |= bitmap->bitAt(x - 1, y - 1); + if (y < h-1) b |= bitmap->bitAt(x - 1, y + 1); + } + if (y > 0) b |= bitmap->bitAt(x, y - 1); + if (y < h-1) b |= bitmap->bitAt(x, y + 1); + if (x < w-1) { + b |= bitmap->bitAt(x + 1, y); + if (y > 0) b |= bitmap->bitAt(x + 1, y - 1); + if (y < h-1) b |= bitmap->bitAt(x + 1, y + 1); + } + } + if (b) tmp.setBitAt(x, y); + } + } + + swap(tmp, *bitmap); + } +} + + +void AtlasPacker::drawChartBitmap(const Chart * chart, BitMap * bitmap, const Vector2 & scale, const Vector2 & offset) +{ + const int w = bitmap->width(); + const int h = bitmap->height(); + const Vector2 extents = Vector2(float(w), float(h)); + + static const Vector2 pad[4] = { + Vector2(-0.5, -0.5), + Vector2(0.5, -0.5), + Vector2(-0.5, 0.5), + Vector2(0.5, 0.5) + }; + /*static const Vector2 pad[4] = { + Vector2(-1, -1), + Vector2(1, -1), + Vector2(-1, 1), + Vector2(1, 1) + };*/ + + // Rasterize 4 times to add proper padding. + for (int i = 0; i < 4; i++) { + + // Rasterize chart faces, check that all bits are not set. + const uint faceCount = chart->chartMesh()->faceCount(); + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = chart->chartMesh()->faceAt(f); + + Vector2 vertices[4]; + + uint edgeCount = 0; + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + if (edgeCount < 4) + { + vertices[edgeCount] = it.vertex()->tex * scale + offset + pad[i]; + nvCheck(ftoi_ceil(vertices[edgeCount].x) >= 0); + nvCheck(ftoi_ceil(vertices[edgeCount].y) >= 0); + nvCheck(ftoi_ceil(vertices[edgeCount].x) <= w); + nvCheck(ftoi_ceil(vertices[edgeCount].y) <= h); + } + edgeCount++; + } + + if (edgeCount == 3) + { + Raster::drawTriangle(Raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, AtlasPacker::setBitsCallback, bitmap); + } + else + { + Raster::drawQuad(Raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, AtlasPacker::setBitsCallback, bitmap); + } + } + } + + // @@ This only allows us to expand the size in texel intervals. + /*if (m_padding_x != 0 && m_padding_y != 0)*/ { + + // Expand chart by padding pixels. (dilation) + BitMap tmp(w, h); + //for (int i = 0; i < 1; i++) { + tmp.clearAll(); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + bool b = bitmap->bitAt(x, y); + if (!b) { + if (x > 0) { + b |= bitmap->bitAt(x - 1, y); + if (y > 0) b |= bitmap->bitAt(x - 1, y - 1); + if (y < h-1) b |= bitmap->bitAt(x - 1, y + 1); + } + if (y > 0) b |= bitmap->bitAt(x, y - 1); + if (y < h-1) b |= bitmap->bitAt(x, y + 1); + if (x < w-1) { + b |= bitmap->bitAt(x + 1, y); + if (y > 0) b |= bitmap->bitAt(x + 1, y - 1); + if (y < h-1) b |= bitmap->bitAt(x + 1, y + 1); + } + } + if (b) tmp.setBitAt(x, y); + } + } + + swap(tmp, *bitmap); + //} + } +} + +bool AtlasPacker::canAddChart(const BitMap * bitmap, int atlas_w, int atlas_h, int offset_x, int offset_y, int r) +{ + nvDebugCheck(r == 0 || r == 1); + + // Check whether the two bitmaps overlap. + + const int w = bitmap->width(); + const int h = bitmap->height(); + + if (r == 0) { + for (int y = 0; y < h; y++) { + int yy = y + offset_y; + if (yy >= 0) { + for (int x = 0; x < w; x++) { + int xx = x + offset_x; + if (xx >= 0) { + if (bitmap->bitAt(x, y)) { + if (xx < atlas_w && yy < atlas_h) { + if (m_bitmap.bitAt(xx, yy)) return false; + } + } + } + } + } + } + } + else if (r == 1) { + for (int y = 0; y < h; y++) { + int xx = y + offset_x; + if (xx >= 0) { + for (int x = 0; x < w; x++) { + int yy = x + offset_y; + if (yy >= 0) { + if (bitmap->bitAt(x, y)) { + if (xx < atlas_w && yy < atlas_h) { + if (m_bitmap.bitAt(xx, yy)) return false; + } + } + } + } + } + } + } + + return true; +} + +#if 0 +void AtlasPacker::checkCanAddChart(const Chart * chart, int w, int h, int x, int y, int r) +{ + nvDebugCheck(r == 0 || r == 1); + Vector2 extents = Vector2(float(w), float(h)); + Vector2 offset = Vector2(float(x), float(y)); + + // Rasterize chart faces, set bits. + const uint faceCount = chart->faceCount(); + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = chart->chartMesh()->faceAt(f); + + Vector2 vertices[4]; + + uint edgeCount = 0; + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + if (edgeCount < 4) + { + Vector2 t = it.vertex()->tex; + if (r == 1) swap(t.x, t.y); + vertices[edgeCount] = t + offset; + } + edgeCount++; + } + + if (edgeCount == 3) + { + Raster::drawTriangle(Raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, AtlasPacker::checkBitsCallback, &m_bitmap); + } + else + { + Raster::drawQuad(Raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, AtlasPacker::checkBitsCallback, &m_bitmap); + } + } +} +#endif // 0 + + +static Color32 chartColor = Color32(0); +static void selectRandomColor(MTRand & rand) { + // Pick random color for this chart. @@ Select random hue, but fixed saturation/luminance? + chartColor.r = 128 + rand.getRange(127); + chartColor.g = 128 + rand.getRange(127); + chartColor.b = 128 + rand.getRange(127); + chartColor.a = 255; +} +static bool debugDrawCallback(void * param, int x, int y, Vector3::Arg, Vector3::Arg, Vector3::Arg, float area) +{ + Image * image = (Image *)param; + + if (area > 0.0) { + Color32 c = image->pixel(x, y); + c.r = chartColor.r; + c.g = chartColor.g; + c.b = chartColor.b; + c.a += U8(ftoi_round(0.5f * area * 255)); + image->pixel(x, y) = c; + } + + return true; +} + +void AtlasPacker::addChart(const Chart * chart, int w, int h, int x, int y, int r, Image * debugOutput) +{ + nvDebugCheck(r == 0 || r == 1); + + nvDebugCheck(debugOutput != NULL); + selectRandomColor(m_rand); + + Vector2 extents = Vector2(float(w), float(h)); + Vector2 offset = Vector2(float(x), float(y)) + Vector2(0.5); + + // Rasterize chart faces, set bits. + const uint faceCount = chart->faceCount(); + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = chart->chartMesh()->faceAt(f); + + Vector2 vertices[4]; + + uint edgeCount = 0; + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + if (edgeCount < 4) + { + Vector2 t = it.vertex()->tex; + if (r == 1) swap(t.x, t.y); + vertices[edgeCount] = t + offset; + } + edgeCount++; + } + + if (edgeCount == 3) + { + Raster::drawTriangle(Raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, debugDrawCallback, debugOutput); + } + else + { + Raster::drawQuad(Raster::Mode_Antialiased, extents, /*enableScissors=*/true, vertices, debugDrawCallback, debugOutput); + } + } +} + + +void AtlasPacker::addChart(const BitMap * bitmap, int atlas_w, int atlas_h, int offset_x, int offset_y, int r, Image * debugOutput) +{ + nvDebugCheck(r == 0 || r == 1); + + // Check whether the two bitmaps overlap. + + const int w = bitmap->width(); + const int h = bitmap->height(); + + if (debugOutput != NULL) { + selectRandomColor(m_rand); + } + + if (r == 0) { + for (int y = 0; y < h; y++) { + int yy = y + offset_y; + if (yy >= 0) { + for (int x = 0; x < w; x++) { + int xx = x + offset_x; + if (xx >= 0) { + if (bitmap->bitAt(x, y)) { + if (xx < atlas_w && yy < atlas_h) { + if (debugOutput) debugOutput->pixel(xx, yy) = chartColor; + else { + nvDebugCheck(m_bitmap.bitAt(xx, yy) == false); + m_bitmap.setBitAt(xx, yy); + } + } + } + } + } + } + } + } + else if (r == 1) { + for (int y = 0; y < h; y++) { + int xx = y + offset_x; + if (xx >= 0) { + for (int x = 0; x < w; x++) { + int yy = x + offset_y; + if (yy >= 0) { + if (bitmap->bitAt(x, y)) { + if (xx < atlas_w && yy < atlas_h) { + if (debugOutput) debugOutput->pixel(xx, yy) = chartColor; + else { + nvDebugCheck(m_bitmap.bitAt(xx, yy) == false); + m_bitmap.setBitAt(xx, yy); + } + } + } + } + } + } + } + } +} + + + +/*static*/ bool AtlasPacker::checkBitsCallback(void * param, int x, int y, Vector3::Arg, Vector3::Arg, Vector3::Arg, float) +{ + BitMap * bitmap = (BitMap * )param; + + nvDebugCheck(bitmap->bitAt(x, y) == false); + + return true; +} + +/*static*/ bool AtlasPacker::setBitsCallback(void * param, int x, int y, Vector3::Arg, Vector3::Arg, Vector3::Arg, float area) +{ + BitMap * bitmap = (BitMap * )param; + + if (area > 0.0) { + bitmap->setBitAt(x, y); + } + + return true; +} + + + +float AtlasPacker::computeAtlasUtilization() const { + const uint w = m_width; + const uint h = m_height; + nvDebugCheck(w <= m_bitmap.width()); + nvDebugCheck(h <= m_bitmap.height()); + + uint count = 0; + for (uint y = 0; y < h; y++) { + for (uint x = 0; x < w; x++) { + count += m_bitmap.bitAt(x, y); + } + } + + return float(count) / (w * h); +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/AtlasPacker.h b/thirdparty/thekla_atlas/src/nvmesh/param/AtlasPacker.h new file mode 100755 index 00000000..2d305f38 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/AtlasPacker.h @@ -0,0 +1,63 @@ +// This code is in the public domain -- castano@gmail.com + +#pragma once +#ifndef NV_MESH_ATLASPACKER_H +#define NV_MESH_ATLASPACKER_H + +#include "nvcore/RadixSort.h" +#include "nvmath/Vector.h" +#include "nvmath/Random.h" +#include "nvimage/BitMap.h" +#include "nvimage/Image.h" + +#include "nvmesh/nvmesh.h" + + +namespace nv +{ + class Atlas; + class Chart; + + struct AtlasPacker + { + AtlasPacker(Atlas * atlas); + ~AtlasPacker(); + + void packCharts(int quality, float texelArea, bool blockAligned, bool conservative); + float computeAtlasUtilization() const; + + private: + + void findChartLocation(int quality, const BitMap * bitmap, Vector2::Arg extents, int w, int h, int * best_x, int * best_y, int * best_w, int * best_h, int * best_r); + void findChartLocation_bruteForce(const BitMap * bitmap, Vector2::Arg extents, int w, int h, int * best_x, int * best_y, int * best_w, int * best_h, int * best_r); + void findChartLocation_random(const BitMap * bitmap, Vector2::Arg extents, int w, int h, int * best_x, int * best_y, int * best_w, int * best_h, int * best_r, int minTrialCount); + + void drawChartBitmapDilate(const Chart * chart, BitMap * bitmap, int padding); + void drawChartBitmap(const Chart * chart, BitMap * bitmap, const Vector2 & scale, const Vector2 & offset); + + bool canAddChart(const BitMap * bitmap, int w, int h, int x, int y, int r); + void addChart(const BitMap * bitmap, int w, int h, int x, int y, int r, Image * debugOutput); + //void checkCanAddChart(const Chart * chart, int w, int h, int x, int y, int r); + void addChart(const Chart * chart, int w, int h, int x, int y, int r, Image * debugOutput); + + + static bool checkBitsCallback(void * param, int x, int y, Vector3::Arg bar, Vector3::Arg dx, Vector3::Arg dy, float coverage); + static bool setBitsCallback(void * param, int x, int y, Vector3::Arg bar, Vector3::Arg dx, Vector3::Arg dy, float coverage); + + private: + + Atlas * m_atlas; + BitMap m_bitmap; + Image m_debug_bitmap; + RadixSort m_radix; + + uint m_width; + uint m_height; + + MTRand m_rand; + + }; + +} // nv namespace + +#endif // NV_MESH_ATLASPACKER_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/BoundaryMap.cpp b/thirdparty/thekla_atlas/src/nvmesh/param/BoundaryMap.cpp new file mode 100755 index 00000000..93e09b14 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/BoundaryMap.cpp @@ -0,0 +1,50 @@ +// This code is in the public domain -- castano@gmail.com + +#include "BoundaryMap.h" +#include "Util.h" + +#include +#include + +#include +#include +#include +#include + +using namespace nv; + + + +bool nv::computeCircularBoundaryMap(HalfEdge::Mesh * mesh) +{ + HalfEdge::Vertex * vertex = findBoundaryVertex(mesh); + + if (vertex == NULL) + { + return false; + } + + // Compute boundary length. + float boundaryLength = 0.0f; + + HalfEdge::Edge * const firstEdge = vertex->edge(); + HalfEdge::Edge * edge = firstEdge; + do { + boundaryLength += edge->length(); + edge = edge->next(); + } while (edge != firstEdge); + + float length = 0.0f; + + edge = firstEdge; + do { + float angle = length * 2.0f * PI / boundaryLength; + edge->vertex()->tex.set(cos(angle), sin(angle)); + + length += edge->length(); + edge = edge->next(); + } while (edge != firstEdge); + + return true; +} + diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/BoundaryMap.h b/thirdparty/thekla_atlas/src/nvmesh/param/BoundaryMap.h new file mode 100755 index 00000000..420a1807 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/BoundaryMap.h @@ -0,0 +1,15 @@ +// This code is in the public domain -- castano@gmail.com + +#pragma once +#ifndef NV_MESH_BOUNDARYMAP_H +#define NV_MESH_BOUNDARYMAP_H + +namespace nv +{ + namespace HalfEdge { class Mesh; } + + bool computeCircularBoundaryMap(HalfEdge::Mesh * mesh); + +} // nv namespace + +#endif // NV_MESH_BOUNDARYMAP_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/ConformalMap.cpp b/thirdparty/thekla_atlas/src/nvmesh/param/ConformalMap.cpp new file mode 100755 index 00000000..81ab6455 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/ConformalMap.cpp @@ -0,0 +1,204 @@ +// This code is in the public domain -- castano@gmail.com + +#include "ConformalMap.h" +#include "Util.h" + +#include +#include +#include + +#include +#include +#include +#include + +using namespace nv; + +namespace { + + static float triangleArea(Vector3::Arg a, Vector3::Arg b) + { + return 0.5f * sqrtf(lengthSquared(a) * lengthSquared(b) - square(dot(a, b))); + } + + + static float computeHarmonicCoordinate(const HalfEdge::Edge * edge) + { + // Sum of the cotangents of the opposite angles. + // w_ij = cot theta_ij + cot theta_ji + + // R + // / \ + // / \ + // P --- Q + // \ / + // \ / + // S + + const Vector3 P = edge->from()->pos; + const Vector3 Q = edge->to()->pos; + const Vector3 R = edge->next->to()->pos; + const Vector3 S = edge->pair->next->to()->pos; + + const Vector3 RP = P - R; + const Vector3 RQ = Q - R; + const Vector3 SP = P - S; + const Vector3 SQ = Q - S; + + float cotR = dot(RP, RQ) / triangleArea(RP, RQ); + float cotS = dot(SP, SQ) / triangleArea(SP, SQ); + + //float cotR = 0.5f * (lengthSquared(RP) + lengthSquared(RQ) - lengthSquared(PQ)) / triangleArea(RP, RQ); + //float cotS = 0.5f * (lengthSquared(SP) + lengthSquared(SQ) - lengthSquared(SQ)) / triangleArea(SP, SQ); + + return cotR + cotS; + } + + static Vector2 computeMeanValueCoordinate(const HalfEdge::Edge * edge) + { + const Vector3 P = edge->from()->pos; + const Vector3 Q = edge->to()->pos; + const Vector3 R = edge->next->to()->pos; + const Vector3 S = edge->pair->next->to()->pos; + + const Vector3 QR = R - Q; + const Vector3 QS = S - Q; + const Vector3 QP = P - Q; + + const Vector3 PR = R - P; + const Vector3 PS = S - P; + const Vector3 PQ = Q - P; + + float tanS0 = (length(PQ) * length(PS) - dot(PQ, PS)) / triangleArea(PQ, PS); + float tanR0 = (length(PQ) * length(PR) - dot(PQ, PR)) / triangleArea(PQ, PR); + + float tanS1 = (length(QP) * length(QS) - dot(QP, QS)) / triangleArea(QP, QS); + float tanR1 = (length(QP) * length(QR) - dot(QP, QR)) / triangleArea(QP, QR); + + float mv1 = (tanS0 + tanR0) / length(PQ); + float mv0 = (tanS1 + tanR1) / length(PQ); + + return Vector2(mv0, mv1); + } + + static Vector2 computeWachpressCoordinate(const HalfEdge::Edge * edge) + { + const Vector3 P = edge->from()->pos; + const Vector3 Q = edge->to()->pos; + const Vector3 R = edge->next->to()->pos; + const Vector3 S = edge->pair->next->to()->pos; + + const Vector3 QR = R - Q; + const Vector3 QS = S - Q; + const Vector3 QP = P - Q; + + const Vector3 PR = R - P; + const Vector3 PS = S - P; + const Vector3 PQ = Q - P; + + float PRQ = triangleArea(PQ, PR); + float PSQ = triangleArea(PQ, PS); + + float QRS = triangleArea(QS, QR); + float PSR = triangleArea(PR, PS); + + float chi0 = PSR / (PSQ * PRQ); + float chi1 = QRS / (PSQ * PRQ); + + return Vector2(chi0, chi1); + } + + enum Coordinate { + Coordinate_Harmonic, + Coordinate_MeanValue, + Coordinate_Wachpress, + }; + +} // namespace + + + + +bool nv::computeConformalMap(HalfEdge::Mesh * mesh) +{ + // @@ computeBarycentricMap(mesh, Coordinate_Harmonic); + + nvDebugCheck(mesh != NULL); + nvDebugCheck(mesh->faceCount() > 1); + +#if _DEBUG + nvDebugCheck(isTriangularMesh(mesh)); + + MeshTopology topology(mesh); + nvDebugCheck(topology.isDisk()); +#endif + + const uint vertexCount = mesh->vertexCount(); + + // Two equations: Ab=x, Ac=y + SparseMatrix A(vertexCount, vertexCount); + FullVector b(vertexCount); + FullVector c(vertexCount); + FullVector x(vertexCount); + FullVector y(vertexCount); + + // Set right hand side to boundary parameterization or zero. + for (uint v = 0; v < vertexCount; v++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(v); + + if (vertex->isBoundary()) { + x[v] = vertex->tex.x; + y[v] = vertex->tex.y; + } + else { + x[v] = 0; + y[v] = 0; + } + } + + // Set initial solution. @@ It may make more sense to set this to the input parameterization. + b = x; + c = y; + + // Fill matrix. + for (uint v = 0; v < vertexCount; v++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(v); + + if (vertex->isBoundary()) { + A.setCoefficient(v, v, 1.0f); + } + else { + float sum = 0.0f; + + // Traverse edges around vertex. + for (HalfEdge::Vertex::ConstEdgeIterator it(vertex->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + + const float energy = computeHarmonicCoordinate(edge); + + A.setCoefficient(v, edge->to()->id, energy); + sum += energy; + } + + // Set diagonal. + A.setCoefficient(v, v, -sum); + } + } + + // Solve equations. + SymmetricSolver(A, b, x); + SymmetricSolver(A, c, y); + + // Set texture coordinates. + for (uint v = 0; v < vertexCount; v++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(v); + vertex->tex.x = x[v]; + vertex->tex.y = y[v]; + } + + return true; +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/ConformalMap.h b/thirdparty/thekla_atlas/src/nvmesh/param/ConformalMap.h new file mode 100755 index 00000000..b34b3a86 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/ConformalMap.h @@ -0,0 +1,16 @@ +// This code is in the public domain -- castano@gmail.com + +#pragma once +#ifndef NV_MESH_CONFORMALMAP_H +#define NV_MESH_CONFORMALMAP_H + +namespace nv +{ + namespace HalfEdge { class Mesh; } + + bool computeConformalMap(HalfEdge::Mesh * mesh); + bool computeNaturalConformalMap(HalfEdge::Mesh * mesh); + +} // nv namespace + +#endif // NV_MESH_CONFORMALMAP_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/LeastSquaresConformalMap.cpp b/thirdparty/thekla_atlas/src/nvmesh/param/LeastSquaresConformalMap.cpp new file mode 100755 index 00000000..cd1e8bbb --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/LeastSquaresConformalMap.cpp @@ -0,0 +1,483 @@ +// Copyright NVIDIA Corporation 2008 -- Ignacio Castano + +#include "nvmesh.h" // pch + +#include "LeastSquaresConformalMap.h" +#include "ParameterizationQuality.h" +#include "Util.h" + +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Vertex.h" +#include "nvmesh/halfedge/Face.h" + +#include "nvmath/Sparse.h" +#include "nvmath/Solver.h" +#include "nvmath/Vector.inl" + +#include "nvcore/Array.inl" + + +using namespace nv; +using namespace HalfEdge; + +namespace +{ + + // Test all pairs of vertices in the boundary and check distance. + static void findDiameterVertices(HalfEdge::Mesh * mesh, HalfEdge::Vertex ** a, HalfEdge::Vertex ** b) + { + nvDebugCheck(mesh != NULL); + nvDebugCheck(a != NULL); + nvDebugCheck(b != NULL); + + const uint vertexCount = mesh->vertexCount(); + + float maxLength = 0.0f; + + for (uint v0 = 1; v0 < vertexCount; v0++) + { + HalfEdge::Vertex * vertex0 = mesh->vertexAt(v0); + nvDebugCheck(vertex0 != NULL); + + if (!vertex0->isBoundary()) continue; + + for (uint v1 = 0; v1 < v0; v1++) + { + HalfEdge::Vertex * vertex1 = mesh->vertexAt(v1); + nvDebugCheck(vertex1 != NULL); + + if (!vertex1->isBoundary()) continue; + + float len = length(vertex0->pos - vertex1->pos); + + if (len > maxLength) + { + maxLength = len; + + *a = vertex0; + *b = vertex1; + } + } + } + + nvDebugCheck(*a != NULL && *b != NULL); + } + + // Fast sweep in 3 directions + static bool findApproximateDiameterVertices(HalfEdge::Mesh * mesh, HalfEdge::Vertex ** a, HalfEdge::Vertex ** b) + { + nvDebugCheck(mesh != NULL); + nvDebugCheck(a != NULL); + nvDebugCheck(b != NULL); + + const uint vertexCount = mesh->vertexCount(); + + HalfEdge::Vertex * minVertex[3]; + HalfEdge::Vertex * maxVertex[3]; + + minVertex[0] = minVertex[1] = minVertex[2] = NULL; + maxVertex[0] = maxVertex[1] = maxVertex[2] = NULL; + + for (uint v = 1; v < vertexCount; v++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(v); + nvDebugCheck(vertex != NULL); + + if (vertex->isBoundary()) + { + minVertex[0] = minVertex[1] = minVertex[2] = vertex; + maxVertex[0] = maxVertex[1] = maxVertex[2] = vertex; + break; + } + } + + if (minVertex[0] == NULL) + { + // Input mesh has not boundaries. + return false; + } + + for (uint v = 1; v < vertexCount; v++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(v); + nvDebugCheck(vertex != NULL); + + if (!vertex->isBoundary()) + { + // Skip interior vertices. + continue; + } + + if (vertex->pos.x < minVertex[0]->pos.x) minVertex[0] = vertex; + else if (vertex->pos.x > maxVertex[0]->pos.x) maxVertex[0] = vertex; + + if (vertex->pos.y < minVertex[1]->pos.y) minVertex[1] = vertex; + else if (vertex->pos.y > maxVertex[1]->pos.y) maxVertex[1] = vertex; + + if (vertex->pos.z < minVertex[2]->pos.z) minVertex[2] = vertex; + else if (vertex->pos.z > maxVertex[2]->pos.z) maxVertex[2] = vertex; + } + + float lengths[3]; + for (int i = 0; i < 3; i++) + { + lengths[i] = length(minVertex[i]->pos - maxVertex[i]->pos); + } + + if (lengths[0] > lengths[1] && lengths[0] > lengths[2]) + { + *a = minVertex[0]; + *b = maxVertex[0]; + } + else if (lengths[1] > lengths[2]) + { + *a = minVertex[1]; + *b = maxVertex[1]; + } + else + { + *a = minVertex[2]; + *b = maxVertex[2]; + } + + return true; + } + + // Conformal relations from Bruno Levy: + + // Computes the coordinates of the vertices of a triangle + // in a local 2D orthonormal basis of the triangle's plane. + static void project_triangle(Vector3::Arg p0, Vector3::Arg p1, Vector3::Arg p2, Vector2 * z0, Vector2 * z1, Vector2 * z2) + { + Vector3 X = normalize(p1 - p0, 0.0f); + Vector3 Z = normalize(cross(X, (p2 - p0)), 0.0f); + Vector3 Y = normalize(cross(Z, X), 0.0f); + + float x0 = 0.0f; + float y0 = 0.0f; + float x1 = length(p1 - p0); + float y1 = 0.0f; + float x2 = dot((p2 - p0), X); + float y2 = dot((p2 - p0), Y); + + *z0 = Vector2(x0, y0); + *z1 = Vector2(x1, y1); + *z2 = Vector2(x2, y2); + } + + // LSCM equation, geometric form : + // (Z1 - Z0)(U2 - U0) = (Z2 - Z0)(U1 - U0) + // Where Uk = uk + i.vk is the complex number + // corresponding to (u,v) coords + // Zk = xk + i.yk is the complex number + // corresponding to local (x,y) coords + // cool: no divide with this expression, + // makes it more numerically stable in + // the presence of degenerate triangles. + + static void setup_conformal_map_relations(SparseMatrix & A, int row, const HalfEdge::Vertex * v0, const HalfEdge::Vertex * v1, const HalfEdge::Vertex * v2) + { + int id0 = v0->id; + int id1 = v1->id; + int id2 = v2->id; + + Vector3 p0 = v0->pos; + Vector3 p1 = v1->pos; + Vector3 p2 = v2->pos; + + Vector2 z0, z1, z2; + project_triangle(p0, p1, p2, &z0, &z1, &z2); + + Vector2 z01 = z1 - z0; + Vector2 z02 = z2 - z0; + + float a = z01.x; + float b = z01.y; + float c = z02.x; + float d = z02.y; + nvCheck(b == 0.0f); + + // Note : 2*id + 0 --> u + // 2*id + 1 --> v + int u0_id = 2 * id0 + 0; + int v0_id = 2 * id0 + 1; + int u1_id = 2 * id1 + 0; + int v1_id = 2 * id1 + 1; + int u2_id = 2 * id2 + 0; + int v2_id = 2 * id2 + 1; + + // Note : b = 0 + + // Real part + A.setCoefficient(u0_id, 2 * row + 0, -a+c); + A.setCoefficient(v0_id, 2 * row + 0, b-d); + A.setCoefficient(u1_id, 2 * row + 0, -c); + A.setCoefficient(v1_id, 2 * row + 0, d); + A.setCoefficient(u2_id, 2 * row + 0, a); + + // Imaginary part + A.setCoefficient(u0_id, 2 * row + 1, -b+d); + A.setCoefficient(v0_id, 2 * row + 1, -a+c); + A.setCoefficient(u1_id, 2 * row + 1, -d); + A.setCoefficient(v1_id, 2 * row + 1, -c); + A.setCoefficient(v2_id, 2 * row + 1, a); + } + + + // Conformal relations from Brecht Van Lommel (based on ABF): + + static float vec_angle_cos(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3) + { + Vector3 d1 = v1 - v2; + Vector3 d2 = v3 - v2; + return clamp(dot(d1, d2) / (length(d1) * length(d2)), -1.0f, 1.0f); + } + + static float vec_angle(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3) + { + float dot = vec_angle_cos(v1, v2, v3); + return acosf(dot); + } + + static void triangle_angles(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3, float *a1, float *a2, float *a3) + { + *a1 = vec_angle(v3, v1, v2); + *a2 = vec_angle(v1, v2, v3); + *a3 = PI - *a2 - *a1; + } + + static void triangle_cosines(Vector3::Arg v1, Vector3::Arg v2, Vector3::Arg v3, float *a1, float *a2, float *a3) + { + *a1 = vec_angle_cos(v3, v1, v2); + *a2 = vec_angle_cos(v1, v2, v3); + *a3 = vec_angle_cos(v2, v3, v1); + } + + static void setup_abf_relations(SparseMatrix & A, int row, const HalfEdge::Vertex * v0, const HalfEdge::Vertex * v1, const HalfEdge::Vertex * v2) + { + int id0 = v0->id; + int id1 = v1->id; + int id2 = v2->id; + + Vector3 p0 = v0->pos; + Vector3 p1 = v1->pos; + Vector3 p2 = v2->pos; + +#if 1 + // @@ IC: Wouldn't it be more accurate to return cos and compute 1-cos^2? + // It does indeed seem to be a little bit more robust. + // @@ Need to revisit this more carefully! + + float a0, a1, a2; + triangle_angles(p0, p1, p2, &a0, &a1, &a2); + + float s0 = sinf(a0); + float s1 = sinf(a1); + float s2 = sinf(a2); + + /*// Hack for degenerate triangles. + if (equal(s0, 0) && equal(s1, 0) && equal(s2, 0)) { + if (equal(a0, 0)) a0 += 0.001f; + if (equal(a1, 0)) a1 += 0.001f; + if (equal(a2, 0)) a2 += 0.001f; + + if (equal(a0, PI)) a0 = PI - a1 - a2; + if (equal(a1, PI)) a1 = PI - a0 - a2; + if (equal(a2, PI)) a2 = PI - a0 - a1; + + s0 = sinf(a0); + s1 = sinf(a1); + s2 = sinf(a2); + }*/ + + if (s1 > s0 && s1 > s2) + { + swap(s1, s2); + swap(s0, s1); + + swap(a1, a2); + swap(a0, a1); + + swap(id1, id2); + swap(id0, id1); + } + else if (s0 > s1 && s0 > s2) + { + swap(s0, s2); + swap(s0, s1); + + swap(a0, a2); + swap(a0, a1); + + swap(id0, id2); + swap(id0, id1); + } + + float c0 = cosf(a0); +#else + float c0, c1, c2; + triangle_cosines(p0, p1, p2, &c0, &c1, &c2); + + float s0 = 1 - c0*c0; + float s1 = 1 - c1*c1; + float s2 = 1 - c2*c2; + + nvDebugCheck(s0 != 0 || s1 != 0 || s2 != 0); + + if (s1 > s0 && s1 > s2) + { + swap(s1, s2); + swap(s0, s1); + + swap(c1, c2); + swap(c0, c1); + + swap(id1, id2); + swap(id0, id1); + } + else if (s0 > s1 && s0 > s2) + { + swap(s0, s2); + swap(s0, s1); + + swap(c0, c2); + swap(c0, c1); + + swap(id0, id2); + swap(id0, id1); + } +#endif + + float ratio = (s2 == 0.0f) ? 1.0f: s1/s2; + float cosine = c0 * ratio; + float sine = s0 * ratio; + + // Note : 2*id + 0 --> u + // 2*id + 1 --> v + int u0_id = 2 * id0 + 0; + int v0_id = 2 * id0 + 1; + int u1_id = 2 * id1 + 0; + int v1_id = 2 * id1 + 1; + int u2_id = 2 * id2 + 0; + int v2_id = 2 * id2 + 1; + + // Real part + A.setCoefficient(u0_id, 2 * row + 0, cosine - 1.0f); + A.setCoefficient(v0_id, 2 * row + 0, -sine); + A.setCoefficient(u1_id, 2 * row + 0, -cosine); + A.setCoefficient(v1_id, 2 * row + 0, sine); + A.setCoefficient(u2_id, 2 * row + 0, 1); + + // Imaginary part + A.setCoefficient(u0_id, 2 * row + 1, sine); + A.setCoefficient(v0_id, 2 * row + 1, cosine - 1.0f); + A.setCoefficient(u1_id, 2 * row + 1, -sine); + A.setCoefficient(v1_id, 2 * row + 1, -cosine); + A.setCoefficient(v2_id, 2 * row + 1, 1); + } + +} // namespace + + +bool nv::computeLeastSquaresConformalMap(HalfEdge::Mesh * mesh) +{ + nvDebugCheck(mesh != NULL); + + // For this to work properly, mesh should not have colocals that have the same + // attributes, unless you want the vertices to actually have different texcoords. + + const uint vertexCount = mesh->vertexCount(); + const uint D = 2 * vertexCount; + const uint N = 2 * countMeshTriangles(mesh); + + // N is the number of equations (one per triangle) + // D is the number of variables (one per vertex; there are 2 pinned vertices). + if (N < D - 4) { + return false; + } + + SparseMatrix A(D, N); + FullVector b(N); + FullVector x(D); + + // Fill b: + b.fill(0.0f); + + // Fill x: + HalfEdge::Vertex * v0; + HalfEdge::Vertex * v1; + if (!findApproximateDiameterVertices(mesh, &v0, &v1)) + { + // Mesh has no boundaries. + return false; + } + if (v0->tex == v1->tex) + { + // LSCM expects an existing parameterization. + return false; + } + + for (uint v = 0; v < vertexCount; v++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(v); + nvDebugCheck(vertex != NULL); + + // Initial solution. + x[2 * v + 0] = vertex->tex.x; + x[2 * v + 1] = vertex->tex.y; + } + + // Fill A: + const uint faceCount = mesh->faceCount(); + for (uint f = 0, t = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = mesh->faceAt(f); + nvDebugCheck(face != NULL); + nvDebugCheck(face->edgeCount() == 3); + + const HalfEdge::Vertex * vertex0 = NULL; + + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + nvCheck(edge != NULL); + + if (vertex0 == NULL) + { + vertex0 = edge->vertex; + } + else if (edge->next->vertex != vertex0) + { + const HalfEdge::Vertex * vertex1 = edge->from(); + const HalfEdge::Vertex * vertex2 = edge->to(); + + setup_abf_relations(A, t, vertex0, vertex1, vertex2); + //setup_conformal_map_relations(A, t, vertex0, vertex1, vertex2); + + t++; + } + } + } + + const uint lockedParameters[] = + { + 2 * v0->id + 0, + 2 * v0->id + 1, + 2 * v1->id + 0, + 2 * v1->id + 1 + }; + + // Solve + LeastSquaresSolver(A, b, x, lockedParameters, 4, 0.000001f); + + // Map x back to texcoords: + for (uint v = 0; v < vertexCount; v++) + { + HalfEdge::Vertex * vertex = mesh->vertexAt(v); + nvDebugCheck(vertex != NULL); + + vertex->tex = Vector2(x[2 * v + 0], x[2 * v + 1]); + } + + return true; +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/LeastSquaresConformalMap.h b/thirdparty/thekla_atlas/src/nvmesh/param/LeastSquaresConformalMap.h new file mode 100755 index 00000000..51fbf193 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/LeastSquaresConformalMap.h @@ -0,0 +1,15 @@ +// Copyright NVIDIA Corporation 2008 -- Ignacio Castano + +#pragma once +#ifndef NV_MESH_LEASTSQUARESCONFORMALMAP_H +#define NV_MESH_LEASTSQUARESCONFORMALMAP_H + +namespace nv +{ + namespace HalfEdge { class Mesh; } + + bool computeLeastSquaresConformalMap(HalfEdge::Mesh * mesh); + +} // nv namespace + +#endif // NV_MESH_LEASTSQUARESCONFORMALMAP_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/OrthogonalProjectionMap.cpp b/thirdparty/thekla_atlas/src/nvmesh/param/OrthogonalProjectionMap.cpp new file mode 100755 index 00000000..d6e5e305 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/OrthogonalProjectionMap.cpp @@ -0,0 +1,99 @@ +// This code is in the public domain -- castano@gmail.com + +#include "nvmesh.h" // pch + +#include "OrthogonalProjectionMap.h" + +#include "nvcore/Array.inl" + +#include "nvmath/Fitting.h" +#include "nvmath/Vector.inl" +#include "nvmath/Box.inl" +#include "nvmath/Plane.inl" + +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Vertex.h" +#include "nvmesh/halfedge/Face.h" +#include "nvmesh/geometry/Bounds.h" + + +using namespace nv; + +bool nv::computeOrthogonalProjectionMap(HalfEdge::Mesh * mesh) +{ + Vector3 axis[2]; + +#if 1 + + uint vertexCount = mesh->vertexCount(); + Array points(vertexCount); + points.resize(vertexCount); + + for (uint i = 0; i < vertexCount; i++) + { + points[i] = mesh->vertexAt(i)->pos; + } + +#if 0 + axis[0] = Fit::computePrincipalComponent_EigenSolver(vertexCount, points.buffer()); + axis[0] = normalize(axis[0]); + + Plane plane = Fit::bestPlane(vertexCount, points.buffer()); + + Vector3 n = plane.vector(); + + axis[1] = cross(axis[0], n); + axis[1] = normalize(axis[1]); +#else + // Avoid redundant computations. + float matrix[6]; + Fit::computeCovariance(vertexCount, points.buffer(), matrix); + + if (matrix[0] == 0 && matrix[3] == 0 && matrix[5] == 0) { + return false; + } + + float eigenValues[3]; + Vector3 eigenVectors[3]; + if (!nv::Fit::eigenSolveSymmetric3(matrix, eigenValues, eigenVectors)) { + return false; + } + + axis[0] = normalize(eigenVectors[0]); + axis[1] = normalize(eigenVectors[1]); +#endif + + +#else + + // IC: I thought this was generally more robust, but turns out it's not even guaranteed to return a valid projection. Imagine a narrow quad perpendicular to one plane, but rotated so that the shortest axis of + // the bounding box is in the direction of that plane. + + // Use the shortest box axis + Box box = MeshBounds::box(mesh); + Vector3 dir = box.extents(); + + if (fabs(dir.x) <= fabs(dir.y) && fabs(dir.x) <= fabs(dir.z)) { + axis[0] = Vector3(0, 1, 0); + axis[1] = Vector3(0, 0, 1); + } + else if (fabs(dir.y) <= fabs(dir.z)) { + axis[0] = Vector3(1, 0, 0); + axis[1] = Vector3(0, 0, 1); + } + else { + axis[0] = Vector3(1, 0, 0); + axis[1] = Vector3(0, 1, 0); + } +#endif + + // Project vertices to plane. + for (HalfEdge::Mesh::VertexIterator it(mesh->vertices()); !it.isDone(); it.advance()) + { + HalfEdge::Vertex * vertex = it.current(); + vertex->tex.x = dot(axis[0], vertex->pos); + vertex->tex.y = dot(axis[1], vertex->pos); + } + + return true; +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/OrthogonalProjectionMap.h b/thirdparty/thekla_atlas/src/nvmesh/param/OrthogonalProjectionMap.h new file mode 100755 index 00000000..54920413 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/OrthogonalProjectionMap.h @@ -0,0 +1,15 @@ +// This code is in the public domain -- castano@gmail.com + +#pragma once +#ifndef NV_MESH_ORTHOGONALPROJECTIONMAP_H +#define NV_MESH_ORTHOGONALPROJECTIONMAP_H + +namespace nv +{ + namespace HalfEdge { class Mesh; } + + bool computeOrthogonalProjectionMap(HalfEdge::Mesh * mesh); + +} // nv namespace + +#endif // NV_MESH_ORTHOGONALPROJECTIONMAP_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/ParameterizationQuality.cpp b/thirdparty/thekla_atlas/src/nvmesh/param/ParameterizationQuality.cpp new file mode 100755 index 00000000..683ee603 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/ParameterizationQuality.cpp @@ -0,0 +1,323 @@ +// Copyright NVIDIA Corporation 2008 -- Ignacio Castano + +#include "nvmesh.h" // pch + +#include "ParameterizationQuality.h" + +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Face.h" +#include "nvmesh/halfedge/Vertex.h" +#include "nvmesh/halfedge/Edge.h" + +#include "nvmath/Vector.inl" + +#include "nvcore/Debug.h" + +#include + + +using namespace nv; + +#if 0 +/* +float triangleConformalEnergy(Vector3 q[3], Vector2 p[3]) +{ +const Vector3 v1 = q[0]; +const Vector3 v2 = q[1]; +const Vector3 v3 = q[2]; + +const Vector2 w1 = p[0]; +const Vector2 w2 = p[1]; +const Vector2 w3 = p[2]; + +float x1 = v2.x() - v1.x(); +float x2 = v3.x() - v1.x(); +float y1 = v2.y() - v1.y(); +float y2 = v3.y() - v1.y(); +float z1 = v2.z() - v1.z(); +float z2 = v3.z() - v1.z(); + +float s1 = w2.x() - w1.x(); +float s2 = w3.x() - w1.x(); +float t1 = w2.y() - w1.y(); +float t2 = w3.y() - w1.y(); + +float r = 1.0f / (s1 * t2 - s2 * t1); +Vector3 sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); +Vector3 tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); + +Vector3 N = cross(v3-v1, v2-v1); + +// Rotate 90 around N. +} +*/ + +static float triangleConformalEnergy(Vector3 q[3], Vector2 p[3]) +{ + // Using Denis formulas: + Vector3 c0 = q[1] - q[2]; + Vector3 c1 = q[2] - q[0]; + Vector3 c2 = q[0] - q[1]; + + Vector3 N = cross(-c0, c1); + float T = length(N); // 2T + N = normalize(N, 0); + + float cot_alpha0 = dot(-c1, c2) / length(cross(-c1, c2)); + float cot_alpha1 = dot(-c2, c0) / length(cross(-c2, c0)); + float cot_alpha2 = dot(-c0, c1) / length(cross(-c0, c1)); + + Vector3 t0 = -cot_alpha1 * c1 + cot_alpha2 * c2; + Vector3 t1 = -cot_alpha2 * c2 + cot_alpha0 * c0; + Vector3 t2 = -cot_alpha0 * c0 + cot_alpha1 * c1; + + nvCheck(equal(length(t0), length(c0))); + nvCheck(equal(length(t1), length(c1))); + nvCheck(equal(length(t2), length(c2))); + nvCheck(equal(dot(t0, c0), 0)); + nvCheck(equal(dot(t1, c1), 0)); + nvCheck(equal(dot(t2, c2), 0)); + + // Gradients + Vector3 grad_u = 1.0f / T * (p[0].x * t0 + p[1].x * t1 + p[2].x * t2); + Vector3 grad_v = 1.0f / T * (p[0].y * t0 + p[1].y * t1 + p[2].y * t2); + + // Rotated gradients + Vector3 Jgrad_u = 1.0f / T * (p[0].x * c0 + p[1].x * c1 + p[2].x * c2); + Vector3 Jgrad_v = 1.0f / T * (p[0].y * c0 + p[1].y * c1 + p[2].y * c2); + + // Using Lengyel's formulas: + { + const Vector3 v1 = q[0]; + const Vector3 v2 = q[1]; + const Vector3 v3 = q[2]; + + const Vector2 w1 = p[0]; + const Vector2 w2 = p[1]; + const Vector2 w3 = p[2]; + + float x1 = v2.x - v1.x; + float x2 = v3.x - v1.x; + float y1 = v2.y - v1.y; + float y2 = v3.y - v1.y; + float z1 = v2.z - v1.z; + float z2 = v3.z - v1.z; + + float s1 = w2.x - w1.x; + float s2 = w3.x - w1.x; + float t1 = w2.y - w1.y; + float t2 = w3.y - w1.y; + + float r = 1.0f / (s1 * t2 - s2 * t1); + Vector3 sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r); + Vector3 tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r); + + Vector3 Jsdir = cross(N, sdir); + Vector3 Jtdir = cross(N, tdir); + + float x = 3; + } + + // check: sdir == grad_u + // check: tdir == grad_v + + return length(grad_u - Jgrad_v); +} +#endif // 0 + + +ParameterizationQuality::ParameterizationQuality() +{ + m_totalTriangleCount = 0; + m_flippedTriangleCount = 0; + m_zeroAreaTriangleCount = 0; + + m_parametricArea = 0.0f; + m_geometricArea = 0.0f; + + m_stretchMetric = 0.0f; + m_maxStretchMetric = 0.0f; + + m_conformalMetric = 0.0f; + m_authalicMetric = 0.0f; +} + +ParameterizationQuality::ParameterizationQuality(const HalfEdge::Mesh * mesh) +{ + nvDebugCheck(mesh != NULL); + + m_totalTriangleCount = 0; + m_flippedTriangleCount = 0; + m_zeroAreaTriangleCount = 0; + + m_parametricArea = 0.0f; + m_geometricArea = 0.0f; + + m_stretchMetric = 0.0f; + m_maxStretchMetric = 0.0f; + + m_conformalMetric = 0.0f; + m_authalicMetric = 0.0f; + + const uint faceCount = mesh->faceCount(); + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = mesh->faceAt(f); + const HalfEdge::Vertex * vertex0 = NULL; + + Vector3 p[3]; + Vector2 t[3]; + + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + + if (vertex0 == NULL) + { + vertex0 = edge->vertex; + + p[0] = vertex0->pos; + t[0] = vertex0->tex; + } + else if (edge->to() != vertex0) + { + p[1] = edge->from()->pos; + p[2] = edge->to()->pos; + t[1] = edge->from()->tex; + t[2] = edge->to()->tex; + + processTriangle(p, t); + } + } + } + + if (m_flippedTriangleCount + m_zeroAreaTriangleCount == faceCount) + { + // If all triangles are flipped, then none is. + m_flippedTriangleCount = 0; + } + + nvDebugCheck(isFinite(m_parametricArea) && m_parametricArea >= 0); + nvDebugCheck(isFinite(m_geometricArea) && m_geometricArea >= 0); + nvDebugCheck(isFinite(m_stretchMetric)); + nvDebugCheck(isFinite(m_maxStretchMetric)); + nvDebugCheck(isFinite(m_conformalMetric)); + nvDebugCheck(isFinite(m_authalicMetric)); +} + +bool ParameterizationQuality::isValid() const +{ + return m_flippedTriangleCount == 0; // @@ Does not test for self-overlaps. +} + +float ParameterizationQuality::rmsStretchMetric() const +{ + if (m_geometricArea == 0) return 0.0f; + float normFactor = sqrtf(m_parametricArea / m_geometricArea); + return sqrtf(m_stretchMetric / m_geometricArea) * normFactor; +} + +float ParameterizationQuality::maxStretchMetric() const +{ + if (m_geometricArea == 0) return 0.0f; + float normFactor = sqrtf(m_parametricArea / m_geometricArea); + return m_maxStretchMetric * normFactor; +} + +float ParameterizationQuality::rmsConformalMetric() const +{ + if (m_geometricArea == 0) return 0.0f; + return sqrtf(m_conformalMetric / m_geometricArea); +} + +float ParameterizationQuality::maxAuthalicMetric() const +{ + if (m_geometricArea == 0) return 0.0f; + return sqrtf(m_authalicMetric / m_geometricArea); +} + +void ParameterizationQuality::operator += (const ParameterizationQuality & pq) +{ + m_totalTriangleCount += pq.m_totalTriangleCount; + m_flippedTriangleCount += pq.m_flippedTriangleCount; + m_zeroAreaTriangleCount += pq.m_zeroAreaTriangleCount; + + m_parametricArea += pq.m_parametricArea; + m_geometricArea += pq.m_geometricArea; + + m_stretchMetric += pq.m_stretchMetric; + m_maxStretchMetric = max(m_maxStretchMetric, pq.m_maxStretchMetric); + + m_conformalMetric += pq.m_conformalMetric; + m_authalicMetric += pq.m_authalicMetric; +} + + +void ParameterizationQuality::processTriangle(Vector3 q[3], Vector2 p[3]) +{ + m_totalTriangleCount++; + + // Evaluate texture stretch metric. See: + // - "Texture Mapping Progressive Meshes", Sander, Snyder, Gortler & Hoppe + // - "Mesh Parameterization: Theory and Practice", Siggraph'07 Course Notes, Hormann, Levy & Sheffer. + + float t1 = p[0].x; + float s1 = p[0].y; + float t2 = p[1].x; + float s2 = p[1].y; + float t3 = p[2].x; + float s3 = p[2].y; + + float geometricArea = length(cross(q[1] - q[0], q[2] - q[0])) / 2; + float parametricArea = ((s2 - s1)*(t3 - t1) - (s3 - s1)*(t2 - t1)) / 2; + + if (isZero(parametricArea)) + { + m_zeroAreaTriangleCount++; + return; + } + + Vector3 Ss = (q[0] * (t2- t3) + q[1] * (t3 - t1) + q[2] * (t1 - t2)) / (2 * parametricArea); + Vector3 St = (q[0] * (s3- s2) + q[1] * (s1 - s3) + q[2] * (s2 - s1)) / (2 * parametricArea); + + float a = dot(Ss, Ss); // E + float b = dot(Ss, St); // F + float c = dot(St, St); // G + + // Compute eigen-values of the first fundamental form: + float sigma1 = sqrtf(0.5f * max(0.0f, a + c - sqrtf(square(a - c) + 4 * square(b)))); // gamma uppercase, min eigenvalue. + float sigma2 = sqrtf(0.5f * max(0.0f, a + c + sqrtf(square(a - c) + 4 * square(b)))); // gamma lowercase, max eigenvalue. + nvCheck(sigma2 >= sigma1); + + // isometric: sigma1 = sigma2 = 1 + // conformal: sigma1 / sigma2 = 1 + // authalic: sigma1 * sigma2 = 1 + + float rmsStretch = sqrtf((a + c) * 0.5f); + float rmsStretch2 = sqrtf((square(sigma1) + square(sigma2)) * 0.5f); + nvDebugCheck(equal(rmsStretch, rmsStretch2, 0.01f)); + + if (parametricArea < 0.0f) + { + // Count flipped triangles. + m_flippedTriangleCount++; + + parametricArea = fabsf(parametricArea); + } + + m_stretchMetric += square(rmsStretch) * geometricArea; + m_maxStretchMetric = max(m_maxStretchMetric, sigma2); + + if (!isZero(sigma1, 0.000001f)) { + // sigma1 is zero when geometricArea is zero. + m_conformalMetric += (sigma2 / sigma1) * geometricArea; + } + m_authalicMetric += (sigma1 * sigma2) * geometricArea; + + // Accumulate total areas. + m_geometricArea += geometricArea; + m_parametricArea += parametricArea; + + + //triangleConformalEnergy(q, p); +} diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/ParameterizationQuality.h b/thirdparty/thekla_atlas/src/nvmesh/param/ParameterizationQuality.h new file mode 100755 index 00000000..342e26b8 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/ParameterizationQuality.h @@ -0,0 +1,56 @@ +// Copyright NVIDIA Corporation 2008 -- Ignacio Castano + +#pragma once +#ifndef NV_MESH_PARAMETERIZATIONQUALITY_H +#define NV_MESH_PARAMETERIZATIONQUALITY_H + +#include + +namespace nv +{ + class Vector2; + class Vector3; + + namespace HalfEdge { class Mesh; } + + // Estimate quality of existing parameterization. + NVMESH_CLASS class ParameterizationQuality + { + public: + ParameterizationQuality(); + ParameterizationQuality(const HalfEdge::Mesh * mesh); + + bool isValid() const; + + float rmsStretchMetric() const; + float maxStretchMetric() const; + + float rmsConformalMetric() const; + float maxAuthalicMetric() const; + + void operator += (const ParameterizationQuality & pq); + + private: + + void processTriangle(Vector3 p[3], Vector2 t[3]); + + private: + + uint m_totalTriangleCount; + uint m_flippedTriangleCount; + uint m_zeroAreaTriangleCount; + + float m_parametricArea; + float m_geometricArea; + + float m_stretchMetric; + float m_maxStretchMetric; + + float m_conformalMetric; + float m_authalicMetric; + + }; + +} // nv namespace + +#endif // NV_MESH_PARAMETERIZATIONQUALITY_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/SingleFaceMap.cpp b/thirdparty/thekla_atlas/src/nvmesh/param/SingleFaceMap.cpp new file mode 100755 index 00000000..4b205de8 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/SingleFaceMap.cpp @@ -0,0 +1,53 @@ +// Copyright NVIDIA Corporation 2008 -- Ignacio Castano + +#include "nvmesh.h" // pch + +#include "SingleFaceMap.h" + +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Vertex.h" +#include "nvmesh/halfedge/Face.h" + +#include "nvmath/Vector.inl" + +using namespace nv; + + + +void nv::computeSingleFaceMap(HalfEdge::Mesh * mesh) +{ + nvDebugCheck(mesh != NULL); + nvDebugCheck(mesh->faceCount() == 1); + + HalfEdge::Face * face = mesh->faceAt(0); + nvCheck(face != NULL); + + Vector3 p0 = face->edge->from()->pos; + Vector3 p1 = face->edge->to()->pos; + + Vector3 X = normalizeSafe(p1 - p0, Vector3(0.0f), 0.0f); + Vector3 Z = face->normal(); + Vector3 Y = normalizeSafe(cross(Z, X), Vector3(0.0f), 0.0f); + + uint i = 0; + for (HalfEdge::Face::EdgeIterator it(face->edges()); !it.isDone(); it.advance(), i++) + { + HalfEdge::Vertex * vertex = it.vertex(); + nvCheck(vertex != NULL); + + if (i == 0) + { + vertex->tex = Vector2(0); + } + else + { + Vector3 pn = vertex->pos; + + float xn = dot((pn - p0), X); + float yn = dot((pn - p0), Y); + + vertex->tex = Vector2(xn, yn); + } + } +} + diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/SingleFaceMap.h b/thirdparty/thekla_atlas/src/nvmesh/param/SingleFaceMap.h new file mode 100755 index 00000000..b70719f5 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/SingleFaceMap.h @@ -0,0 +1,18 @@ +// Copyright NVIDIA Corporation 2008 -- Ignacio Castano + +#pragma once +#ifndef NV_MESH_SINGLEFACEMAP_H +#define NV_MESH_SINGLEFACEMAP_H + +namespace nv +{ + namespace HalfEdge + { + class Mesh; + } + + void computeSingleFaceMap(HalfEdge::Mesh * mesh); + +} // nv namespace + +#endif // NV_MESH_SINGLEFACEMAP_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/Util.cpp b/thirdparty/thekla_atlas/src/nvmesh/param/Util.cpp new file mode 100755 index 00000000..fe7b58ed --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/Util.cpp @@ -0,0 +1,326 @@ +// This code is in the public domain -- castano@gmail.com + +#include "nvmesh.h" // pch + +#include "Util.h" + +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Face.h" +#include "nvmesh/halfedge/Vertex.h" + +#include "nvmath/Vector.inl" + +#include "nvcore/Array.inl" + + +using namespace nv; + +// Determine if the given mesh is a quad mesh. +bool nv::isQuadMesh(const HalfEdge::Mesh * mesh) +{ + nvDebugCheck(mesh != NULL); + + const uint faceCount = mesh->faceCount(); + for(uint i = 0; i < faceCount; i++) { + const HalfEdge::Face * face = mesh->faceAt(i); + if (face->edgeCount() != 4) { + return false; + } + } + + return true; +} + +bool nv::isTriangularMesh(const HalfEdge::Mesh * mesh) +{ + for (HalfEdge::Mesh::ConstFaceIterator it(mesh->faces()); !it.isDone(); it.advance()) + { + const HalfEdge::Face * face = it.current(); + if (face->edgeCount() != 3) return false; + } + return true; +} + + +uint nv::countMeshTriangles(const HalfEdge::Mesh * mesh) +{ + const uint faceCount = mesh->faceCount(); + + uint triangleCount = 0; + + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = mesh->faceAt(f); + + uint edgeCount = face->edgeCount(); + nvDebugCheck(edgeCount > 2); + + triangleCount += edgeCount - 2; + } + + return triangleCount; +} + +const HalfEdge::Vertex * nv::findBoundaryVertex(const HalfEdge::Mesh * mesh) +{ + const uint vertexCount = mesh->vertexCount(); + + for (uint v = 0; v < vertexCount; v++) + { + const HalfEdge::Vertex * vertex = mesh->vertexAt(v); + if (vertex->isBoundary()) return vertex; + } + + return NULL; +} + + +HalfEdge::Mesh * nv::unifyVertices(const HalfEdge::Mesh * inputMesh) +{ + HalfEdge::Mesh * mesh = new HalfEdge::Mesh; + + // Only add the first colocal. + const uint vertexCount = inputMesh->vertexCount(); + for (uint v = 0; v < vertexCount; v++) { + const HalfEdge::Vertex * vertex = inputMesh->vertexAt(v); + + if (vertex->isFirstColocal()) { + mesh->addVertex(vertex->pos); + } + } + + nv::Array indexArray; + + // Add new faces pointing to first colocals. + uint faceCount = inputMesh->faceCount(); + for (uint f = 0; f < faceCount; f++) { + const HalfEdge::Face * face = inputMesh->faceAt(f); + + indexArray.clear(); + + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) { + const HalfEdge::Edge * edge = it.current(); + const HalfEdge::Vertex * vertex = edge->vertex->firstColocal(); + + indexArray.append(vertex->id); + } + + mesh->addFace(indexArray); + } + + mesh->linkBoundary(); + + return mesh; +} + +#include "nvmath/Basis.h" + +static bool pointInTriangle(const Vector2 & p, const Vector2 & a, const Vector2 & b, const Vector2 & c) +{ + return triangleArea(a, b, p) >= 0.00001f && + triangleArea(b, c, p) >= 0.00001f && + triangleArea(c, a, p) >= 0.00001f; +} + + +// This is doing a simple ear-clipping algorithm that skips invalid triangles. Ideally, we should +// also sort the ears by angle, start with the ones that have the smallest angle and proceed in order. +HalfEdge::Mesh * nv::triangulate(const HalfEdge::Mesh * inputMesh) +{ + HalfEdge::Mesh * mesh = new HalfEdge::Mesh; + + // Add all vertices. + const uint vertexCount = inputMesh->vertexCount(); + for (uint v = 0; v < vertexCount; v++) { + const HalfEdge::Vertex * vertex = inputMesh->vertexAt(v); + mesh->addVertex(vertex->pos); + } + + Array polygonVertices; + Array polygonAngles; + Array polygonPoints; + + const uint faceCount = inputMesh->faceCount(); + for (uint f = 0; f < faceCount; f++) + { + const HalfEdge::Face * face = inputMesh->faceAt(f); + nvDebugCheck(face != NULL); + + const uint edgeCount = face->edgeCount(); + nvDebugCheck(edgeCount >= 3); + + polygonVertices.clear(); + polygonVertices.reserve(edgeCount); + + if (edgeCount == 3) { + // Simple case for triangles. + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + const HalfEdge::Vertex * vertex = edge->vertex; + polygonVertices.append(vertex->id); + } + + int v0 = polygonVertices[0]; + int v1 = polygonVertices[1]; + int v2 = polygonVertices[2]; + + mesh->addFace(v0, v1, v2); + } + else { + // Build 2D polygon projecting vertices onto normal plane. + // Faces are not necesarily planar, this is for example the case, when the face comes from filling a hole. In such cases + // it's much better to use the best fit plane. + const Vector3 fn = face->normal(); + + Basis basis; + basis.buildFrameForDirection(fn); + + polygonPoints.clear(); + polygonPoints.reserve(edgeCount); + polygonAngles.clear(); + polygonAngles.reserve(edgeCount); + + for (HalfEdge::Face::ConstEdgeIterator it(face->edges()); !it.isDone(); it.advance()) + { + const HalfEdge::Edge * edge = it.current(); + const HalfEdge::Vertex * vertex = edge->vertex; + polygonVertices.append(vertex->id); + + Vector2 p; + p.x = dot(basis.tangent, vertex->pos); + p.y = dot(basis.bitangent, vertex->pos); + + polygonPoints.append(p); + } + polygonAngles.resize(edgeCount); + + while (polygonVertices.size() > 2) { + uint size = polygonVertices.size(); + + // Update polygon angles. @@ Update only those that have changed. + float minAngle = 2 * PI; + uint bestEar = 0; // Use first one if none of them is valid. + bool bestIsValid = false; + for (uint i = 0; i < size; i++) { + uint i0 = i; + uint i1 = (i+1) % size; // Use Sean's polygon interation trick. + uint i2 = (i+2) % size; + + Vector2 p0 = polygonPoints[i0]; + Vector2 p1 = polygonPoints[i1]; + Vector2 p2 = polygonPoints[i2]; + + float d = clamp(dot(p0-p1, p2-p1) / (length(p0-p1) * length(p2-p1)), -1.0f, 1.0f); + float angle = acosf(d); + + float area = triangleArea(p0, p1, p2); + if (area < 0.0f) angle = 2.0f * PI - angle; + + polygonAngles[i1] = angle; + + if (angle < minAngle || !bestIsValid) { + + // Make sure this is a valid ear, if not, skip this point. + bool valid = true; + for (uint j = 0; j < size; j++) { + if (j == i0 || j == i1 || j == i2) continue; + Vector2 p = polygonPoints[j]; + + if (pointInTriangle(p, p0, p1, p2)) { + valid = false; + break; + } + } + + if (valid || !bestIsValid) { + minAngle = angle; + bestEar = i1; + bestIsValid = valid; + } + } + } + + nvDebugCheck(minAngle <= 2 * PI); + + // Clip best ear: + + uint i0 = (bestEar+size-1) % size; + uint i1 = (bestEar+0) % size; + uint i2 = (bestEar+1) % size; + + int v0 = polygonVertices[i0]; + int v1 = polygonVertices[i1]; + int v2 = polygonVertices[i2]; + + mesh->addFace(v0, v1, v2); + + polygonVertices.removeAt(i1); + polygonPoints.removeAt(i1); + polygonAngles.removeAt(i1); + } + } + +#if 0 + + uint i = 0; + while (polygonVertices.size() > 2 && i < polygonVertices.size()) { + uint size = polygonVertices.size(); + uint i0 = (i+0) % size; + uint i1 = (i+1) % size; + uint i2 = (i+2) % size; + + const HalfEdge::Vertex * v0 = polygonVertices[i0]; + const HalfEdge::Vertex * v1 = polygonVertices[i1]; + const HalfEdge::Vertex * v2 = polygonVertices[i2]; + + const Vector3 p0 = v0->pos; + const Vector3 p1 = v1->pos; + const Vector3 p2 = v2->pos; + + const Vector3 e0 = p2 - p1; + const Vector3 e1 = p0 - p1; + + // If this ear forms a valid triangle, setup relations, remove v1 and repeat. + Vector3 n = cross(e0, e1); + float len = dot(fn, n); // = sin(angle) + + float angle = asin(len); + + + if (len > 0.0f) { + mesh->addFace(v0->id(), v1->id(), v2->id()); + polygonVertices.removeAt(i1); + polygonAngles.removeAt(i1); + if (i2 > i1) i2--; + // @@ Update angles at i0 and i2 + } + else { + i++; + } + } + + // @@ Create a few degenerate triangles to avoid introducing holes. + i = 0; + const uint size = polygonVertices.size(); + while (i < size - 2) { + uint i0 = (i+0) % size; + uint i1 = (i+1) % size; + uint i2 = (i+2) % size; + + const HalfEdge::Vertex * v0 = polygonVertices[i0]; + const HalfEdge::Vertex * v1 = polygonVertices[i1]; + const HalfEdge::Vertex * v2 = polygonVertices[i2]; + + mesh->addFace(v0->id(), v1->id(), v2->id()); + i++; + } +#endif + } + + mesh->linkBoundary(); + + return mesh; +} + + diff --git a/thirdparty/thekla_atlas/src/nvmesh/param/Util.h b/thirdparty/thekla_atlas/src/nvmesh/param/Util.h new file mode 100755 index 00000000..774563ac --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/param/Util.h @@ -0,0 +1,18 @@ +// This code is in the public domain -- castano@gmail.com + +#include "nvmesh/nvmesh.h" + +namespace nv { + + namespace HalfEdge { class Mesh; class Vertex; } + + bool isQuadMesh(const HalfEdge::Mesh * mesh); + bool isTriangularMesh(const HalfEdge::Mesh * mesh); + + uint countMeshTriangles(const HalfEdge::Mesh * mesh); + const HalfEdge::Vertex * findBoundaryVertex(const HalfEdge::Mesh * mesh); + + HalfEdge::Mesh * unifyVertices(const HalfEdge::Mesh * inputMesh); + HalfEdge::Mesh * triangulate(const HalfEdge::Mesh * inputMesh); + +} // nv namespace diff --git a/thirdparty/thekla_atlas/src/nvmesh/raster/ClippedTriangle.h b/thirdparty/thekla_atlas/src/nvmesh/raster/ClippedTriangle.h new file mode 100755 index 00000000..0947d485 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/raster/ClippedTriangle.h @@ -0,0 +1,159 @@ +// Copyright NVIDIA Corporation 2007 -- Denis Kovacs + +#pragma once +#ifndef NV_MESH_CLIPPEDTRIANGLE_H +#define NV_MESH_CLIPPEDTRIANGLE_H + +#include + +namespace nv +{ + + class ClippedTriangle + { + public: + ClippedTriangle(Vector2::Arg a, Vector2::Arg b, Vector2::Arg c) + { + m_numVertices = 3; + m_activeVertexBuffer = 0; + + m_verticesA[0]=a; + m_verticesA[1]=b; + m_verticesA[2]=c; + + m_vertexBuffers[0] = m_verticesA; + m_vertexBuffers[1] = m_verticesB; + } + + uint vertexCount() + { + return m_numVertices; + } + + const Vector2 * vertices() + { + return m_vertexBuffers[m_activeVertexBuffer]; + } + + inline void clipHorizontalPlane(float offset, float clipdirection) + { + Vector2 * v = m_vertexBuffers[m_activeVertexBuffer]; + m_activeVertexBuffer ^= 1; + Vector2 * v2 = m_vertexBuffers[m_activeVertexBuffer]; + + v[m_numVertices] = v[0]; + + float dy2, dy1 = offset - v[0].y; + int dy2in, dy1in = clipdirection*dy1 >= 0; + uint p=0; + + for (uint k=0; k= 0; + + if (dy1in) v2[p++] = v[k]; + + if ( dy1in + dy2in == 1 ) // not both in/out + { + float dx = v[k+1].x - v[k].x; + float dy = v[k+1].y - v[k].y; + v2[p++] = Vector2(v[k].x + dy1*(dx/dy), offset); + } + + dy1 = dy2; dy1in = dy2in; + } + m_numVertices = p; + + //for (uint k=0; k= 0; + uint p=0; + + for (uint k=0; k= 0; + + if (dx1in) v2[p++] = v[k]; + + if ( dx1in + dx2in == 1 ) // not both in/out + { + float dx = v[k+1].x - v[k].x; + float dy = v[k+1].y - v[k].y; + v2[p++] = Vector2(offset, v[k].y + dx1*(dy/dx)); + } + + dx1 = dx2; dx1in = dx2in; + } + m_numVertices = p; + + //for (uint k=0; kv1 = v0; + this->v2 = v2; + this->v3 = v1; + + // Set barycentric coordinates. + this->t1 = t0; + this->t2 = t2; + this->t3 = t1; + + // make sure every triangle is front facing. + flipBackface(); + + // Compute deltas. + valid = computeDeltas(); + + computeUnitInwardNormals(); + } + + + /// Compute texture space deltas. + /// This method takes two edge vectors that form a basis, determines the + /// coordinates of the canonic vectors in that basis, and computes the + /// texture gradient that corresponds to those vectors. + bool Triangle::computeDeltas() + { + Vector2 e0 = v3 - v1; + Vector2 e1 = v2 - v1; + + Vector3 de0 = t3 - t1; + Vector3 de1 = t2 - t1; + + float denom = 1.0f / (e0.y * e1.x - e1.y * e0.x); + if (!isFinite(denom)) { + return false; + } + + float lambda1 = - e1.y * denom; + float lambda2 = e0.y * denom; + float lambda3 = e1.x * denom; + float lambda4 = - e0.x * denom; + + dx = de0 * lambda1 + de1 * lambda2; + dy = de0 * lambda3 + de1 * lambda4; + + return true; + } + + // compute unit inward normals for each edge. + void Triangle::computeUnitInwardNormals() + { + n1 = v1 - v2; n1 = Vector2(-n1.y, n1.x); n1 = n1 * (1.0f/sqrtf(n1.x*n1.x + n1.y*n1.y)); + n2 = v2 - v3; n2 = Vector2(-n2.y, n2.x); n2 = n2 * (1.0f/sqrtf(n2.x*n2.x + n2.y*n2.y)); + n3 = v3 - v1; n3 = Vector2(-n3.y, n3.x); n3 = n3 * (1.0f/sqrtf(n3.x*n3.x + n3.y*n3.y)); + } + + void Triangle::flipBackface() + { + // check if triangle is backfacing, if so, swap two vertices + if ( ((v3.x-v1.x)*(v2.y-v1.y) - (v3.y-v1.y)*(v2.x-v1.x)) < 0 ) { + Vector2 hv=v1; v1=v2; v2=hv; // swap pos + Vector3 ht=t1; t1=t2; t2=ht; // swap tex + } + } + + bool Triangle::draw(const Vector2 & extents, bool enableScissors, SamplingCallback cb, void * param) + { + // 28.4 fixed-point coordinates + const int Y1 = iround(16.0f * v1.y); + const int Y2 = iround(16.0f * v2.y); + const int Y3 = iround(16.0f * v3.y); + + const int X1 = iround(16.0f * v1.x); + const int X2 = iround(16.0f * v2.x); + const int X3 = iround(16.0f * v3.x); + + // Deltas + const int DX12 = X1 - X2; + const int DX23 = X2 - X3; + const int DX31 = X3 - X1; + + const int DY12 = Y1 - Y2; + const int DY23 = Y2 - Y3; + const int DY31 = Y3 - Y1; + + // Fixed-point deltas + const int FDX12 = DX12 << 4; + const int FDX23 = DX23 << 4; + const int FDX31 = DX31 << 4; + + const int FDY12 = DY12 << 4; + const int FDY23 = DY23 << 4; + const int FDY31 = DY31 << 4; + + int minx, miny, maxx, maxy; + if (enableScissors) { + int frustumX0 = 0 << 4; + int frustumY0 = 0 << 4; + int frustumX1 = (int)extents.x << 4; + int frustumY1 = (int)extents.y << 4; + + // Bounding rectangle + minx = (nv::max(min3(X1, X2, X3), frustumX0) + 0xF) >> 4; + miny = (nv::max(min3(Y1, Y2, Y3), frustumY0) + 0xF) >> 4; + maxx = (nv::min(max3(X1, X2, X3), frustumX1) + 0xF) >> 4; + maxy = (nv::min(max3(Y1, Y2, Y3), frustumY1) + 0xF) >> 4; + } + else { + // Bounding rectangle + minx = (min3(X1, X2, X3) + 0xF) >> 4; + miny = (min3(Y1, Y2, Y3) + 0xF) >> 4; + maxx = (max3(X1, X2, X3) + 0xF) >> 4; + maxy = (max3(Y1, Y2, Y3) + 0xF) >> 4; + } + + // Block size, standard 8x8 (must be power of two) + const int q = 8; + + // @@ This won't work when minx,miny are negative. This code path is not used. Leaving as is for now. + nvCheck(minx >= 0); + nvCheck(miny >= 0); + + // Start in corner of 8x8 block + minx &= ~(q - 1); + miny &= ~(q - 1); + + // Half-edge constants + int C1 = DY12 * X1 - DX12 * Y1; + int C2 = DY23 * X2 - DX23 * Y2; + int C3 = DY31 * X3 - DX31 * Y3; + + // Correct for fill convention + if(DY12 < 0 || (DY12 == 0 && DX12 > 0)) C1++; + if(DY23 < 0 || (DY23 == 0 && DX23 > 0)) C2++; + if(DY31 < 0 || (DY31 == 0 && DX31 > 0)) C3++; + + // Loop through blocks + for(int y = miny; y < maxy; y += q) + { + for(int x = minx; x < maxx; x += q) + { + // Corners of block + int x0 = x << 4; + int x1 = (x + q - 1) << 4; + int y0 = y << 4; + int y1 = (y + q - 1) << 4; + + // Evaluate half-space functions + bool a00 = C1 + DX12 * y0 - DY12 * x0 > 0; + bool a10 = C1 + DX12 * y0 - DY12 * x1 > 0; + bool a01 = C1 + DX12 * y1 - DY12 * x0 > 0; + bool a11 = C1 + DX12 * y1 - DY12 * x1 > 0; + int a = (a00 << 0) | (a10 << 1) | (a01 << 2) | (a11 << 3); + + bool b00 = C2 + DX23 * y0 - DY23 * x0 > 0; + bool b10 = C2 + DX23 * y0 - DY23 * x1 > 0; + bool b01 = C2 + DX23 * y1 - DY23 * x0 > 0; + bool b11 = C2 + DX23 * y1 - DY23 * x1 > 0; + int b = (b00 << 0) | (b10 << 1) | (b01 << 2) | (b11 << 3); + + bool c00 = C3 + DX31 * y0 - DY31 * x0 > 0; + bool c10 = C3 + DX31 * y0 - DY31 * x1 > 0; + bool c01 = C3 + DX31 * y1 - DY31 * x0 > 0; + bool c11 = C3 + DX31 * y1 - DY31 * x1 > 0; + int c = (c00 << 0) | (c10 << 1) | (c01 << 2) | (c11 << 3); + + // Skip block when outside an edge + if(a == 0x0 || b == 0x0 || c == 0x0) continue; + + // Accept whole block when totally covered + if(a == 0xF && b == 0xF && c == 0xF) + { + Vector3 texRow = t1 + dy*(y0 - v1.y) + dx*(x0 - v1.x); + + for(int iy = y; iy < y + q; iy++) + { + Vector3 tex = texRow; + for(int ix = x; ix < x + q; ix++) + { + //Vector3 tex = t1 + dx * (ix - v1.x) + dy * (iy - v1.y); + if (!cb(param, ix, iy, tex, dx, dy, 1.0)) { + // early out. + return false; + } + tex += dx; + } + texRow += dy; + } + } + else // Partially covered block + { + int CY1 = C1 + DX12 * y0 - DY12 * x0; + int CY2 = C2 + DX23 * y0 - DY23 * x0; + int CY3 = C3 + DX31 * y0 - DY31 * x0; + Vector3 texRow = t1 + dy*(y0 - v1.y) + dx*(x0 - v1.x); + + for(int iy = y; iy < y + q; iy++) + { + int CX1 = CY1; + int CX2 = CY2; + int CX3 = CY3; + Vector3 tex = texRow; + + for(int ix = x; ix < x + q; ix++) + { + if(CX1 > 0 && CX2 > 0 && CX3 > 0) + { + if (!cb(param, ix, iy, tex, dx, dy, 1.0)) + { + // early out. + return false; + } + } + + CX1 -= FDY12; + CX2 -= FDY23; + CX3 -= FDY31; + tex += dx; + } + + CY1 += FDX12; + CY2 += FDX23; + CY3 += FDX31; + texRow += dy; + } + } + } + } + + return true; + } + + +#define PX_INSIDE 1.0f/sqrt(2.0f) +#define PX_OUTSIDE -1.0f/sqrt(2.0f) + +#define BK_SIZE 8 +#define BK_INSIDE sqrt(BK_SIZE*BK_SIZE/2.0f) +#define BK_OUTSIDE -sqrt(BK_SIZE*BK_SIZE/2.0f) + + // extents has to be multiple of BK_SIZE!! + bool Triangle::drawAA(const Vector2 & extents, bool enableScissors, SamplingCallback cb, void * param) + { + float minx, miny, maxx, maxy; + if (enableScissors) { + // Bounding rectangle + minx = floorf(max(min3(v1.x, v2.x, v3.x), 0.0f)); + miny = floorf(max(min3(v1.y, v2.y, v3.y), 0.0f)); + maxx = ceilf( min(max3(v1.x, v2.x, v3.x), extents.x-1.0f)); + maxy = ceilf( min(max3(v1.y, v2.y, v3.y), extents.y-1.0f)); + } + else { + // Bounding rectangle + minx = floorf(min3(v1.x, v2.x, v3.x)); + miny = floorf(min3(v1.y, v2.y, v3.y)); + maxx = ceilf( max3(v1.x, v2.x, v3.x)); + maxy = ceilf( max3(v1.y, v2.y, v3.y)); + } + + // There's no reason to align the blocks to the viewport, instead we align them to the origin of the triangle bounds. + minx = floorf(minx); + miny = floorf(miny); + //minx = (float)(((int)minx) & (~((int)BK_SIZE - 1))); // align to blocksize (we don't need to worry about blocks partially out of viewport) + //miny = (float)(((int)miny) & (~((int)BK_SIZE - 1))); + + minx += 0.5; miny +=0.5; // sampling at texel centers! + maxx += 0.5; maxy +=0.5; + + // Half-edge constants + float C1 = n1.x * (-v1.x) + n1.y * (-v1.y); + float C2 = n2.x * (-v2.x) + n2.y * (-v2.y); + float C3 = n3.x * (-v3.x) + n3.y * (-v3.y); + + // Loop through blocks + for(float y0 = miny; y0 <= maxy; y0 += BK_SIZE) + { + for(float x0 = minx; x0 <= maxx; x0 += BK_SIZE) + { + // Corners of block + float xc = (x0 + (BK_SIZE-1)/2.0f); + float yc = (y0 + (BK_SIZE-1)/2.0f); + + // Evaluate half-space functions + float aC = C1 + n1.x * xc + n1.y * yc; + float bC = C2 + n2.x * xc + n2.y * yc; + float cC = C3 + n3.x * xc + n3.y * yc; + + // Skip block when outside an edge + if( (aC <= BK_OUTSIDE) || (bC <= BK_OUTSIDE) || (cC <= BK_OUTSIDE) ) continue; + + // Accept whole block when totally covered + if( (aC >= BK_INSIDE) && (bC >= BK_INSIDE) && (cC >= BK_INSIDE) ) + { + Vector3 texRow = t1 + dy*(y0 - v1.y) + dx*(x0 - v1.x); + + for (float y = y0; y < y0 + BK_SIZE; y++) + { + Vector3 tex = texRow; + for(float x = x0; x < x0 + BK_SIZE; x++) + { + if (!cb(param, (int)x, (int)y, tex, dx, dy, 1.0f)) + { + return false; + } + tex += dx; + } + texRow += dy; + } + } + else // Partially covered block + { + float CY1 = C1 + n1.x * x0 + n1.y * y0; + float CY2 = C2 + n2.x * x0 + n2.y * y0; + float CY3 = C3 + n3.x * x0 + n3.y * y0; + Vector3 texRow = t1 + dy*(y0 - v1.y) + dx*(x0 - v1.x); + + for(float y = y0; y < y0 + BK_SIZE; y++) // @@ This is not clipping to scissor rectangle correctly. + { + float CX1 = CY1; + float CX2 = CY2; + float CX3 = CY3; + Vector3 tex = texRow; + + for (float x = x0; x < x0 + BK_SIZE; x++) // @@ This is not clipping to scissor rectangle correctly. + { + if (CX1 >= PX_INSIDE && CX2 >= PX_INSIDE && CX3 >= PX_INSIDE) + { + // pixel completely covered + Vector3 tex = t1 + dx * (x - v1.x) + dy * (y - v1.y); + if (!cb(param, (int)x, (int)y, tex, dx, dy, 1.0f)) + { + return false; + } + } + else if ((CX1 >= PX_OUTSIDE) && (CX2 >= PX_OUTSIDE) && (CX3 >= PX_OUTSIDE)) + { + // triangle partially covers pixel. do clipping. + ClippedTriangle ct(v1-Vector2(x,y), v2-Vector2(x,y), v3-Vector2(x,y)); + ct.clipAABox(-0.5, -0.5, 0.5, 0.5); + Vector2 centroid = ct.centroid(); + float area = ct.area(); + if (area > 0.0f) + { + Vector3 texCent = tex - dx*centroid.x - dy*centroid.y; + //nvCheck(texCent.x >= -0.1f && texCent.x <= 1.1f); // @@ Centroid is not very exact... + //nvCheck(texCent.y >= -0.1f && texCent.y <= 1.1f); + //nvCheck(texCent.z >= -0.1f && texCent.z <= 1.1f); + //Vector3 texCent2 = t1 + dx * (x - v1.x) + dy * (y - v1.y); + if (!cb(param, (int)x, (int)y, texCent, dx, dy, area)) + { + return false; + } + } + } + + CX1 += n1.x; + CX2 += n2.x; + CX3 += n3.x; + tex += dx; + } + + CY1 += n1.y; + CY2 += n2.y; + CY3 += n3.y; + texRow += dy; + } + } + } + } + + return true; + } + +} // namespace + + +/// Process the given triangle. +bool nv::Raster::drawTriangle(Mode mode, Vector2::Arg extents, bool enableScissors, const Vector2 v[3], SamplingCallback cb, void * param) +{ + Triangle tri(v[0], v[1], v[2], Vector3(1, 0, 0), Vector3(0, 1, 0), Vector3(0, 0, 1)); + + // @@ It would be nice to have a conservative drawing mode that enlarges the triangle extents by one texel and is able to handle degenerate triangles. + // @@ Maybe the simplest thing to do would be raster triangle edges. + + if (tri.valid) { + if (mode == Mode_Antialiased) { + return tri.drawAA(extents, enableScissors, cb, param); + } + if (mode == Mode_Nearest) { + return tri.draw(extents, enableScissors, cb, param); + } + } + + return true; +} + +inline static float triangleArea(Vector2::Arg v1, Vector2::Arg v2, Vector2::Arg v3) +{ + return 0.5f * (v3.x * v1.y + v1.x * v2.y + v2.x * v3.y - v2.x * v1.y - v3.x * v2.y - v1.x * v3.y); +} + +/// Process the given quad. +bool nv::Raster::drawQuad(Mode mode, Vector2::Arg extents, bool enableScissors, const Vector2 v[4], SamplingCallback cb, void * param) +{ + bool sign0 = triangleArea(v[0], v[1], v[2]) > 0.0f; + bool sign1 = triangleArea(v[0], v[2], v[3]) > 0.0f; + + // Divide the quad into two non overlapping triangles. + if (sign0 == sign1) { + Triangle tri0(v[0], v[1], v[2], Vector3(0,0,0), Vector3(1,0,0), Vector3(1,1,0)); + Triangle tri1(v[0], v[2], v[3], Vector3(0,0,0), Vector3(1,1,0), Vector3(0,1,0)); + + if (tri0.valid && tri1.valid) { + if (mode == Mode_Antialiased) { + return tri0.drawAA(extents, enableScissors, cb, param) && tri1.drawAA(extents, enableScissors, cb, param); + } else { + return tri0.draw(extents, enableScissors, cb, param) && tri1.draw(extents, enableScissors, cb, param); + } + } + } + else + { + Triangle tri0(v[0], v[1], v[3], Vector3(0,0,0), Vector3(1,0,0), Vector3(0,1,0)); + Triangle tri1(v[1], v[2], v[3], Vector3(1,0,0), Vector3(1,1,0), Vector3(0,1,0)); + + if (tri0.valid && tri1.valid) { + if (mode == Mode_Antialiased) { + return tri0.drawAA(extents, enableScissors, cb, param) && tri1.drawAA(extents, enableScissors, cb, param); + } else { + return tri0.draw(extents, enableScissors, cb, param) && tri1.draw(extents, enableScissors, cb, param); + } + } + } + + return true; +} + + +static bool drawPoint(const Vector2 & p, const Vector2 v[2], LineSamplingCallback cb, void * param) { + + int x = ftoi_round(p.x); + int y = ftoi_round(p.y); + Vector2 ip = Vector2(float(x) + 0.5f, float(y) + 0.5f); + + float t; + + // Return minimum distance between line segment vw and point p + Vector2 dv = v[1] - v[0]; + const float l2 = nv::lengthSquared(dv); // i.e. |w-v|^2 - avoid a sqrt + if (l2 == 0.0) { + t = 0; // v0 == v1 case + } + else { + // Consider the line extending the segment, parameterized as v + t (w - v). + // We find projection of point p onto the line. + // It falls where t = [(p-v) . (w-v)] / |w-v|^2 + t = dot(ip - v[0], dv) / l2; + if (t < 0.0) { + t = 0; // Beyond the 'v0' end of the segment + } + else if (t > 1.0) { + t = 1; // Beyond the 'v1' end of the segment + } + } + + Vector2 projection = v[0] + t * dv; // Projection falls on the segment + + float d = distance(ip, projection); + + return cb(param, x, y, t, saturate(1-d)); +} + + +void nv::Raster::drawLine(bool antialias, Vector2::Arg extents, bool enableScissors, const Vector2 v[2], LineSamplingCallback cb, void * param) +{ + nvCheck(antialias == true); // @@ Not implemented. + //nvCheck(enableScissors == false); // @@ Not implemented. + + // Very crappy DDA implementation. + + Vector2 p = v[0]; + Vector2 dp, dpdy; + + float dx = v[1].x - v[0].x; + float dy = v[1].y - v[0].y; + int n; + + // Degenerate line. + if (dx == 0 && dy == 0) return; + + if (fabsf(dx) >= fabsf(dy)) { + n = iround(fabsf(dx)); + dp.x = dx / fabsf(dx); + dp.y = dy / fabsf(dx); + nvDebugCheck(fabsf(dp.y) <= 1.0f); + dpdy.x = 0; + dpdy.y = 1; + } + else { + n = iround(fabs(dy)); + dp.x = dx / fabsf(dy); + dp.y = dy / fabsf(dy); + nvDebugCheck(fabsf(dp.x) <= 1.0f); + dpdy.x = 1; + dpdy.y = 0; + } + + for (int i = 0; i <= n; i++) { + drawPoint(p, v, cb, param); + drawPoint(p + dpdy, v, cb, param); + drawPoint(p - dpdy, v, cb, param); + p += dp; + } +} + + +// Draw vertical or horizontal segments. For degenerate triangles. +/*bool nv::Raster::drawSegment(Vector2::Arg extents, bool enableScissors, const Vector2 v[2], LineSamplingCallback cb, void * param) +{ + nvCheck(enableScissors == false); + + + if (v[0].x == v[1].x) { // Vertical segment. + + } + else if (v[0].y == v[1].y) { // Horizontal segment. + int y = ftoi_round(v[0].y); + int x0 = ftoi_floor(v[0].x); + int x1 = ftoi_floor(v[0].x); + + for (int x = x0; x <= x1; x++) { + + cb(param, x, y, t, + } + } + + return false; // Not a valid segment. +} +*/ diff --git a/thirdparty/thekla_atlas/src/nvmesh/raster/Raster.h b/thirdparty/thekla_atlas/src/nvmesh/raster/Raster.h new file mode 100755 index 00000000..05af2ddb --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/raster/Raster.h @@ -0,0 +1,49 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#pragma once +#ifndef NV_MESH_RASTER_H +#define NV_MESH_RASTER_H + +/** @file Raster.h + * @brief Rasterization library. + * + * This is just a standard scanline rasterizer that I took from one of my old + * projects. The perspective correction wasn't necessary so I just removed it. +**/ + +#include "nvmath/Vector.h" +#include "nvmesh/nvmesh.h" + +namespace nv +{ + + namespace Raster + { + enum Mode { + Mode_Nearest, + Mode_Antialiased, + //Mode_Conservative + }; + + + /// A callback to sample the environment. Return false to terminate rasterization. + typedef bool (NV_CDECL * SamplingCallback)(void * param, int x, int y, Vector3::Arg bar, Vector3::Arg dx, Vector3::Arg dy, float coverage); + + // Process the given triangle. Returns false if rasterization was interrupted by the callback. + NVMESH_API bool drawTriangle(Mode mode, Vector2::Arg extents, bool enableScissors, const Vector2 v[3], SamplingCallback cb, void * param); + + // Process the given quad. Returns false if rasterization was interrupted by the callback. + NVMESH_API bool drawQuad(Mode mode, Vector2::Arg extents, bool enableScissors, const Vector2 v[4], SamplingCallback cb, void * param); + + typedef bool (NV_CDECL * LineSamplingCallback)(void * param, int x, int y, float t, float d); // t is the position along the segment, d is the distance to the line. + + // Process the given line. + NVMESH_API void drawLine(bool antialias, Vector2::Arg extents, bool enableScissors, const Vector2 v[2], LineSamplingCallback cb, void * param); + + // Draw vertical or horizontal segments. For degenerate triangles. + //NVMESH_API void drawSegment(Vector2::Arg extents, bool enableScissors, const Vector2 v[2], SamplingCallback cb, void * param); + } +} + + +#endif // NV_MESH_RASTER_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/weld/Snap.cpp b/thirdparty/thekla_atlas/src/nvmesh/weld/Snap.cpp new file mode 100755 index 00000000..b6bff4d8 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/weld/Snap.cpp @@ -0,0 +1,100 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#include + +#include +#include +#include + +using namespace nv; + +namespace { + + // Snap the given vertices. + void Snap(TriMesh::Vertex & a, TriMesh::Vertex & b, float texThreshold, float norThreshold) + { + a.pos = b.pos = (a.pos + b.pos) * 0.5f; + + if (equal(a.tex.x, b.tex.x, texThreshold) && equal(a.tex.y, b.tex.y, texThreshold)) { + b.tex = a.tex = (a.tex + b.tex) * 0.5f; + } + + if (equal(a.nor.x, b.nor.x, norThreshold) && equal(a.nor.y, b.nor.y, norThreshold) && equal(a.nor.z, b.nor.z, norThreshold)) { + b.nor = a.nor = (a.nor + b.nor) * 0.5f; + } + }; + +} // nv namespace + +uint nv::SnapVertices(TriMesh * mesh, float posThreshold, float texThreshold, float norThreshold) +{ + nvDebug("--- Snapping vertices.\n"); + + // Determine largest axis. + Box box = MeshBounds::box(mesh); + Vector3 extents = box.extents(); + + int axis = 2; + if( extents.x > extents.y ) { + if( extents.x > extents.z ) { + axis = 0; + } + } + else if(extents.y > extents.z) { + axis = 1; + } + + // @@ Use diagonal instead! + + + // Sort vertices according to the largest axis. + const uint vertexCount = mesh->vertexCount(); + nvCheck(vertexCount > 2); // Must have at least two vertices. + + // Get pos channel. + //PiMesh::Channel * pos_channel = mesh->GetChannel(mesh->FindChannel(VS_POS)); + //nvCheck( pos_channel != NULL ); + + //const PiArray & pos_array = pos_channel->data; + + Array distArray; + distArray.resize(vertexCount); + + for(uint v = 0; v < vertexCount; v++) { + if (axis == 0) distArray[v] = mesh->vertexAt(v).pos.x; + else if (axis == 1) distArray[v] = mesh->vertexAt(v).pos.y; + else distArray[v] = mesh->vertexAt(v).pos.z; + } + + RadixSort radix; + const uint * xrefs = radix.sort(distArray.buffer(), distArray.count()).ranks(); + nvCheck(xrefs != NULL); + + uint snapCount = 0; + for(uint v = 0; v < vertexCount-1; v++) { + for(uint n = v+1; n < vertexCount; n++) { + nvDebugCheck( distArray[xrefs[v]] <= distArray[xrefs[n]] ); + + if (fabs(distArray[xrefs[n]] - distArray[xrefs[v]]) > posThreshold) { + break; + } + + TriMesh::Vertex & v0 = mesh->vertexAt(xrefs[v]); + TriMesh::Vertex & v1 = mesh->vertexAt(xrefs[n]); + + const float dist = length(v0.pos - v1.pos); + + if (dist <= posThreshold) { + Snap(v0, v1, texThreshold, norThreshold); + snapCount++; + } + } + } + + // @@ todo: debug, make sure that the distance between vertices is now >= threshold + + nvDebug("--- %u vertices snapped\n", snapCount); + + return snapCount; +}; + diff --git a/thirdparty/thekla_atlas/src/nvmesh/weld/Snap.h b/thirdparty/thekla_atlas/src/nvmesh/weld/Snap.h new file mode 100755 index 00000000..8e0566cd --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/weld/Snap.h @@ -0,0 +1,18 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#ifndef NV_MESH_SNAP_H +#define NV_MESH_SNAP_H + +#include +#include + +namespace nv +{ + class TriMesh; + + NVMESH_API uint SnapVertices(TriMesh * mesh, float posThreshold=NV_EPSILON, float texThreshold=1.0f/1024, float norThreshold=NV_NORMAL_EPSILON); + +} // nv namespace + + +#endif // NV_MESH_SNAP_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/weld/VertexWeld.cpp b/thirdparty/thekla_atlas/src/nvmesh/weld/VertexWeld.cpp new file mode 100755 index 00000000..2ba4dcae --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/weld/VertexWeld.cpp @@ -0,0 +1,205 @@ +// Copyright NVIDIA Corporation 2006 -- Ignacio Castano + +#include +#include + +#include +#include + +using namespace nv; + +// Weld trimesh vertices +void nv::WeldVertices(TriMesh * mesh) +{ + nvDebug("--- Welding vertices.\n"); + + nvCheck(mesh != NULL); + + uint count = mesh->vertexCount(); + Array xrefs; + Weld weld; + uint newCount = weld(mesh->vertices(), xrefs); + + nvDebug("--- %d vertices welded\n", count - newCount); + + + // Remap faces. + const uint faceCount = mesh->faceCount(); + for(uint f = 0; f < faceCount; f++) + { + TriMesh::Face & face = mesh->faceAt(f); + face.v[0] = xrefs[face.v[0]]; + face.v[1] = xrefs[face.v[1]]; + face.v[2] = xrefs[face.v[2]]; + } +} + + +// Weld trimesh vertices +void nv::WeldVertices(QuadTriMesh * mesh) +{ + nvDebug("--- Welding vertices.\n"); + + nvCheck(mesh != NULL); + + uint count = mesh->vertexCount(); + Array xrefs; + Weld weld; + uint newCount = weld(mesh->vertices(), xrefs); + + nvDebug("--- %d vertices welded\n", count - newCount); + + // Remap faces. + const uint faceCount = mesh->faceCount(); + for(uint f = 0; f < faceCount; f++) + { + QuadTriMesh::Face & face = mesh->faceAt(f); + face.v[0] = xrefs[face.v[0]]; + face.v[1] = xrefs[face.v[1]]; + face.v[2] = xrefs[face.v[2]]; + + if (face.isQuadFace()) + { + face.v[3] = xrefs[face.v[3]]; + } + } +} + + + +// OLD code + +#if 0 + +namespace { + +struct VertexInfo { + uint id; ///< Original vertex id. + uint normal_face_group; + uint tangent_face_group; + uint material; + uint chart; +}; + + +/// VertexInfo hash functor. +struct VertexHash : public IHashFunctor { + VertexHash(PiMeshPtr m) : mesh(m) { + uint c = mesh->FindChannel(VS_POS); + piCheck(c != PI_NULL_INDEX); + channel = mesh->GetChannel(c); + piCheck(channel != NULL); + } + + uint32 operator () (const VertexInfo & v) const { + return channel->data[v.id].GetHash(); + } + +private: + PiMeshPtr mesh; + PiMesh::Channel * channel; +}; + + +/// VertexInfo comparator. +struct VertexEqual : public IBinaryPredicate { + VertexEqual(PiMeshPtr m) : mesh(m) {} + + bool operator () (const VertexInfo & a, const VertexInfo & b) const { + + bool equal = a.normal_face_group == b.normal_face_group && + a.tangent_face_group == b.tangent_face_group && + a.material == b.material && + a.chart == b.chart; + + // Split vertex shared by different face types. + if( !equal ) { + return false; + } + + // They were the same vertex. + if( a.id == b.id ) { + return true; + } + + // Vertex equal if all the channels are equal. + return mesh->IsVertexEqual(a.id, b.id); + } + +private: + PiMeshPtr mesh; +}; + +} // namespace + + +/// Weld the vertices. +void PiMeshVertexWeld::WeldVertices(const PiMeshSmoothGroup * mesh_smooth_group, + const PiMeshMaterial * mesh_material, const PiMeshAtlas * mesh_atlas ) +{ + piDebug( "--- Welding vertices:\n" ); + + piDebug( "--- Expand mesh vertices.\n" ); + PiArray vertex_array; + + const uint face_num = mesh->GetFaceNum(); + const uint vertex_max = face_num * 3; + vertex_array.Resize( vertex_max ); + + for(uint i = 0; i < vertex_max; i++) { + + uint f = i/3; + + const PiMesh::Face & face = mesh->GetFace(f); + vertex_array[i].id = face.v[i%3]; + + // Reset face attributes. + vertex_array[i].normal_face_group = PI_NULL_INDEX; + vertex_array[i].tangent_face_group = PI_NULL_INDEX; + vertex_array[i].material = PI_NULL_INDEX; + vertex_array[i].chart = PI_NULL_INDEX; + + // Set available attributes. + if( mesh_smooth_group != NULL ) { + if( mesh_smooth_group->HasNormalFaceGroups() ) { + vertex_array[i].normal_face_group = mesh_smooth_group->GetNormalFaceGroup( f ); + } + if( mesh_smooth_group->HasTangentFaceGroups() ) { + vertex_array[i].tangent_face_group = mesh_smooth_group->GetTangentFaceGroup( f ); + } + } + if( mesh_material != NULL ) { + vertex_array[i].material = mesh_material->GetFaceMaterial( f ); + } + if( mesh_atlas != NULL && mesh_atlas->HasCharts() ) { + vertex_array[i].chart = mesh_atlas->GetFaceChart( f ); + } + } + piDebug( "--- %d vertices.\n", vertex_max ); + + piDebug( "--- Collapse vertices.\n" ); + + uint * xrefs = new uint[vertex_max]; + VertexHash hash(mesh); + VertexEqual equal(mesh); + const uint vertex_num = Weld( vertex_array, xrefs, hash, equal ); + piCheck(vertex_num <= vertex_max); + piDebug( "--- %d vertices.\n", vertex_num ); + + // Remap face indices. + piDebug( "--- Remapping face indices.\n" ); + mesh->RemapFaceIndices(vertex_max, xrefs); + + + // Overwrite xrefs to map new vertices to old vertices. + for(uint v = 0; v < vertex_num; v++) { + xrefs[v] = vertex_array[v].id; + } + + // Update vertex order. + mesh->ReorderVertices(vertex_num, xrefs); + + delete [] xrefs; +} + +#endif // 0 diff --git a/thirdparty/thekla_atlas/src/nvmesh/weld/VertexWeld.h b/thirdparty/thekla_atlas/src/nvmesh/weld/VertexWeld.h new file mode 100755 index 00000000..1dc2e4ba --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/weld/VertexWeld.h @@ -0,0 +1,19 @@ +// Copyright NVIDIA Corporation 2006 -- Ignacio Castano + +#ifndef NV_MESH_VERTEXWELD_H +#define NV_MESH_VERTEXWELD_H + +#include + +namespace nv +{ + class TriMesh; + class QuadMesh; + + NVMESH_API void WeldVertices(TriMesh * mesh); + NVMESH_API void WeldVertices(QuadTriMesh * mesh); + +} // nv namespace + + +#endif // NV_MESH_VERTEXWELD_H diff --git a/thirdparty/thekla_atlas/src/nvmesh/weld/Weld.h b/thirdparty/thekla_atlas/src/nvmesh/weld/Weld.h new file mode 100755 index 00000000..e6155394 --- /dev/null +++ b/thirdparty/thekla_atlas/src/nvmesh/weld/Weld.h @@ -0,0 +1,171 @@ +// This code is in the public domain -- castanyo@yahoo.es + +#ifndef NV_MESH_WELD_H +#define NV_MESH_WELD_H + +#include "nvcore/Array.h" +#include "nvcore/Hash.h" +#include "nvcore/Utils.h" // nextPowerOfTwo + +#include // for memset, memcmp, memcpy + +// Weld function to remove array duplicates in linear time using hashing. + +namespace nv +{ + +/// Generic welding routine. This function welds the elements of the array p +/// and returns the cross references in the xrefs array. To compare the elements +/// it uses the given hash and equal functors. +/// +/// This code is based on the ideas of Ville Miettinen and Pierre Terdiman. +template , class E=Equal > +struct Weld +{ + // xrefs maps old elements to new elements + uint operator()(Array & p, Array & xrefs) + { + const uint N = p.size(); // # of input vertices. + uint outputCount = 0; // # of output vertices + uint hashSize = nextPowerOfTwo(N); // size of the hash table + uint * hashTable = new uint[hashSize + N]; // hash table + linked list + uint * next = hashTable + hashSize; // use bottom part as linked list + + xrefs.resize(N); + memset( hashTable, NIL, hashSize*sizeof(uint) ); // init hash table (NIL = 0xFFFFFFFF so memset works) + + H hash; + E equal; + for (uint i = 0; i < N; i++) + { + const T & e = p[i]; + uint32 hashValue = hash(e) & (hashSize-1); + uint offset = hashTable[hashValue]; + + // traverse linked list + while( offset != NIL && !equal(p[offset], e) ) + { + offset = next[offset]; + } + + xrefs[i] = offset; + + // no match found - copy vertex & add to hash + if( offset == NIL ) + { + // save xref + xrefs[i] = outputCount; + + // copy element + p[outputCount] = e; + + // link to hash table + next[outputCount] = hashTable[hashValue]; + + // update hash heads and increase output counter + hashTable[hashValue] = outputCount++; + } + } + + // cleanup + delete [] hashTable; + + p.resize(outputCount); + + // number of output vertices + return outputCount; + } +}; + + +/// Reorder the given array accoding to the indices given in xrefs. +template +void reorderArray(Array & array, const Array & xrefs) +{ + const uint count = xrefs.count(); + Array new_array; + new_array.resize(count); + + for(uint i = 0; i < count; i++) { + new_array[i] = array[xrefs[i]]; + } + + swap(array, new_array); +} + +/// Reverse the given array so that new indices point to old indices. +inline void reverseXRefs(Array & xrefs, uint count) +{ + Array new_xrefs; + new_xrefs.resize(count); + + for(uint i = 0; i < xrefs.count(); i++) { + new_xrefs[xrefs[i]] = i; + } + + swap(xrefs, new_xrefs); +} + + + +// +struct WeldN +{ + uint vertexSize; + + WeldN(uint n) : vertexSize(n) {} + + // xrefs maps old elements to new elements + uint operator()(uint8 * ptr, uint N, Array & xrefs) + { + uint outputCount = 0; // # of output vertices + uint hashSize = nextPowerOfTwo(N); // size of the hash table + uint * hashTable = new uint[hashSize + N]; // hash table + linked list + uint * next = hashTable + hashSize; // use bottom part as linked list + + xrefs.resize(N); + memset( hashTable, NIL, hashSize*sizeof(uint) ); // init hash table (NIL = 0xFFFFFFFF so memset works) + + for (uint i = 0; i < N; i++) + { + const uint8 * vertex = ptr + i * vertexSize; + uint32 hashValue = sdbmHash(vertex, vertexSize) & (hashSize-1); + uint offset = hashTable[hashValue]; + + // traverse linked list + while (offset != NIL && memcmp(ptr + offset * vertexSize, vertex, vertexSize) != 0) + { + offset = next[offset]; + } + + xrefs[i] = offset; + + // no match found - copy vertex & add to hash + if (offset == NIL) + { + // save xref + xrefs[i] = outputCount; + + // copy element + memcpy(ptr + outputCount * vertexSize, vertex, vertexSize); + + // link to hash table + next[outputCount] = hashTable[hashValue]; + + // update hash heads and increase output counter + hashTable[hashValue] = outputCount++; + } + } + + // cleanup + delete [] hashTable; + + // number of output vertices + return outputCount; + } +}; + + +} // nv namespace + +#endif // NV_MESH_WELD_H diff --git a/thirdparty/thekla_atlas/src/thekla/thekla_atlas.cpp b/thirdparty/thekla_atlas/src/thekla/thekla_atlas.cpp new file mode 100755 index 00000000..8b6b2cf5 --- /dev/null +++ b/thirdparty/thekla_atlas/src/thekla/thekla_atlas.cpp @@ -0,0 +1,263 @@ + +#include "thekla_atlas.h" + +#include + +#include "nvmesh/halfedge/Edge.h" +#include "nvmesh/halfedge/Mesh.h" +#include "nvmesh/halfedge/Face.h" +#include "nvmesh/halfedge/Vertex.h" +#include "nvmesh/param/Atlas.h" + +#include "nvmath/Vector.inl" +#include "nvmath/ftoi.h" + +#include "nvcore/Array.inl" + + +using namespace Thekla; +using namespace nv; + + +inline Atlas_Output_Mesh * set_error(Atlas_Error * error, Atlas_Error code) { + if (error) *error = code; + return NULL; +} + + + +static void input_to_mesh(const Atlas_Input_Mesh * input, HalfEdge::Mesh * mesh, Atlas_Error * error) { + + Array canonicalMap; + canonicalMap.reserve(input->vertex_count); + + for (int i = 0; i < input->vertex_count; i++) { + const Atlas_Input_Vertex & input_vertex = input->vertex_array[i]; + const float * pos = input_vertex.position; + const float * nor = input_vertex.normal; + const float * tex = input_vertex.uv; + + HalfEdge::Vertex * vertex = mesh->addVertex(Vector3(pos[0], pos[1], pos[2])); + vertex->nor.set(nor[0], nor[1], nor[2]); + vertex->tex.set(tex[0], tex[1]); + + canonicalMap.append(input_vertex.first_colocal); + } + + mesh->linkColocalsWithCanonicalMap(canonicalMap); + + + const int face_count = input->face_count; + + int non_manifold_faces = 0; + for (int i = 0; i < face_count; i++) { + const Atlas_Input_Face & input_face = input->face_array[i]; + + int v0 = input_face.vertex_index[0]; + int v1 = input_face.vertex_index[1]; + int v2 = input_face.vertex_index[2]; + + HalfEdge::Face * face = mesh->addFace(v0, v1, v2); + if (face != NULL) { + face->material = input_face.material_index; + } + else { + non_manifold_faces++; + } + } + + mesh->linkBoundary(); + + if (non_manifold_faces != 0 && error != NULL) { + *error = Atlas_Error_Invalid_Mesh_Non_Manifold; + } +} + +static Atlas_Output_Mesh * mesh_atlas_to_output(const HalfEdge::Mesh * mesh, const Atlas & atlas, Atlas_Error * error) { + + Atlas_Output_Mesh * output = new Atlas_Output_Mesh; + + const MeshCharts * charts = atlas.meshAt(0); + + // Allocate vertices. + const int vertex_count = charts->vertexCount(); + output->vertex_count = vertex_count; + output->vertex_array = new Atlas_Output_Vertex[vertex_count]; + + int w = 0; + int h = 0; + + // Output vertices. + const int chart_count = charts->chartCount(); + for (int i = 0; i < chart_count; i++) { + const Chart * chart = charts->chartAt(i); + uint vertexOffset = charts->vertexCountBeforeChartAt(i); + + const uint chart_vertex_count = chart->vertexCount(); + for (uint v = 0; v < chart_vertex_count; v++) { + Atlas_Output_Vertex & output_vertex = output->vertex_array[vertexOffset + v]; + + uint original_vertex = chart->mapChartVertexToOriginalVertex(v); + output_vertex.xref = original_vertex; + + Vector2 uv = chart->chartMesh()->vertexAt(v)->tex; + output_vertex.uv[0] = uv.x; + output_vertex.uv[1] = uv.y; + w = max(w, ftoi_ceil(uv.x)); + h = max(h, ftoi_ceil(uv.y)); + } + } + + const int face_count = mesh->faceCount(); + output->index_count = face_count * 3; + output->index_array = new int[face_count * 3]; + + // Set face indices. + for (int f = 0; f < face_count; f++) { + uint c = charts->faceChartAt(f); + uint i = charts->faceIndexWithinChartAt(f); + uint vertexOffset = charts->vertexCountBeforeChartAt(c); + + const Chart * chart = charts->chartAt(c); + nvDebugCheck(chart->faceAt(i) == f); + + const HalfEdge::Face * face = chart->chartMesh()->faceAt(i); + const HalfEdge::Edge * edge = face->edge; + + output->index_array[3*f+0] = vertexOffset + edge->vertex->id; + output->index_array[3*f+1] = vertexOffset + edge->next->vertex->id; + output->index_array[3*f+2] = vertexOffset + edge->next->next->vertex->id; + } + + *error = Atlas_Error_Success; + output->atlas_width = w; + output->atlas_height = h; + + return output; +} + + +void Thekla::atlas_set_default_options(Atlas_Options * options) { + if (options != NULL) { + // These are the default values we use on The Witness. + + options->charter = Atlas_Charter_Default; + options->charter_options.witness.proxy_fit_metric_weight = 2.0f; + options->charter_options.witness.roundness_metric_weight = 0.01f; + options->charter_options.witness.straightness_metric_weight = 6.0f; + options->charter_options.witness.normal_seam_metric_weight = 4.0f; + options->charter_options.witness.texture_seam_metric_weight = 0.5f; + options->charter_options.witness.max_chart_area = FLT_MAX; + options->charter_options.witness.max_boundary_length = FLT_MAX; + + options->mapper = Atlas_Mapper_Default; + + options->packer = Atlas_Packer_Default; + options->packer_options.witness.packing_quality = 0; + options->packer_options.witness.texel_area = 8; + options->packer_options.witness.block_align = true; + options->packer_options.witness.conservative = false; + } +} + + +Atlas_Output_Mesh * Thekla::atlas_generate(const Atlas_Input_Mesh * input, const Atlas_Options * options, Atlas_Error * error) { + // Validate args. + if (input == NULL || options == NULL || error == NULL) return set_error(error, Atlas_Error_Invalid_Args); + + // Validate options. + if (options->charter != Atlas_Charter_Witness) { + return set_error(error, Atlas_Error_Invalid_Options); + } + if (options->charter == Atlas_Charter_Witness) { + // @@ Validate input options! + } + + if (options->mapper != Atlas_Mapper_LSCM) { + return set_error(error, Atlas_Error_Invalid_Options); + } + if (options->mapper == Atlas_Mapper_LSCM) { + // No options. + } + + if (options->packer != Atlas_Packer_Witness) { + return set_error(error, Atlas_Error_Invalid_Options); + } + if (options->packer == Atlas_Packer_Witness) { + // @@ Validate input options! + } + + // Validate input mesh. + for (int i = 0; i < input->face_count; i++) { + int v0 = input->face_array[i].vertex_index[0]; + int v1 = input->face_array[i].vertex_index[1]; + int v2 = input->face_array[i].vertex_index[2]; + + if (v0 < 0 || v0 >= input->vertex_count || + v1 < 0 || v1 >= input->vertex_count || + v2 < 0 || v2 >= input->vertex_count) + { + return set_error(error, Atlas_Error_Invalid_Mesh); + } + } + + + // Build half edge mesh. + AutoPtr mesh(new HalfEdge::Mesh); + + input_to_mesh(input, mesh.ptr(), error); + + if (*error == Atlas_Error_Invalid_Mesh) { + return NULL; + } + + Atlas atlas; + + // Charter. + if (options->charter == Atlas_Charter_Extract) { + return set_error(error, Atlas_Error_Not_Implemented); + } + else if (options->charter == Atlas_Charter_Witness) { + SegmentationSettings segmentation_settings; + segmentation_settings.proxyFitMetricWeight = options->charter_options.witness.proxy_fit_metric_weight; + segmentation_settings.roundnessMetricWeight = options->charter_options.witness.roundness_metric_weight; + segmentation_settings.straightnessMetricWeight = options->charter_options.witness.straightness_metric_weight; + segmentation_settings.normalSeamMetricWeight = options->charter_options.witness.normal_seam_metric_weight; + segmentation_settings.textureSeamMetricWeight = options->charter_options.witness.texture_seam_metric_weight; + segmentation_settings.maxChartArea = options->charter_options.witness.max_chart_area; + segmentation_settings.maxBoundaryLength = options->charter_options.witness.max_boundary_length; + + Array uncharted_materials; + atlas.computeCharts(mesh.ptr(), segmentation_settings, uncharted_materials); + } + + // Mapper. + if (options->mapper == Atlas_Mapper_LSCM) { + atlas.parameterizeCharts(); + } + + + // Packer. + if (options->packer == Atlas_Packer_Witness) { + int packing_quality = options->packer_options.witness.packing_quality; + float texel_area = options->packer_options.witness.texel_area; + int block_align = options->packer_options.witness.block_align; + int conservative = options->packer_options.witness.conservative; + + /*float utilization =*/ atlas.packCharts(packing_quality, texel_area, block_align, conservative); + } + + + // Build output mesh. + return mesh_atlas_to_output(mesh.ptr(), atlas, error); +} + + +void Thekla::atlas_free(Atlas_Output_Mesh * output) { + if (output != NULL) { + delete [] output->vertex_array; + delete [] output->index_array; + delete output; + } +} + diff --git a/thirdparty/thekla_atlas/src/thekla/thekla_atlas.h b/thirdparty/thekla_atlas/src/thekla/thekla_atlas.h new file mode 100755 index 00000000..1d0716e7 --- /dev/null +++ b/thirdparty/thekla_atlas/src/thekla/thekla_atlas.h @@ -0,0 +1,116 @@ + +// Thekla Atlas Generator + +namespace Thekla { + +enum Atlas_Charter { + Atlas_Charter_Witness, // Options: threshold + Atlas_Charter_Extract, // Options: --- + Atlas_Charter_Default = Atlas_Charter_Witness +}; + +enum Atlas_Mapper { + Atlas_Mapper_LSCM, // Options: --- + Atlas_Mapper_Default = Atlas_Mapper_LSCM +}; + +enum Atlas_Packer { + Atlas_Packer_Witness, // Options: texel_area + Atlas_Packer_Default = Atlas_Packer_Witness +}; + +struct Atlas_Options { + Atlas_Charter charter; + union { + struct { + float proxy_fit_metric_weight; + float roundness_metric_weight; + float straightness_metric_weight; + float normal_seam_metric_weight; + float texture_seam_metric_weight; + float max_chart_area; + float max_boundary_length; + } witness; + struct { + } extract; + } charter_options; + + Atlas_Mapper mapper; + union { + } mapper_options; + + Atlas_Packer packer; + union { + struct { + int packing_quality; + float texel_area; // This is not really texel area, but 1 / texel width? + bool block_align; // Align charts to 4x4 blocks. + bool conservative; // Pack charts with extra padding. + } witness; + } packer_options; +}; + +struct Atlas_Input_Vertex { + float position[3]; + float normal[3]; + float uv[2]; + int first_colocal; +}; + +struct Atlas_Input_Face { + int vertex_index[3]; + int material_index; +}; + +struct Atlas_Input_Mesh { + int vertex_count; + int face_count; + Atlas_Input_Vertex * vertex_array; + Atlas_Input_Face * face_array; +}; + +struct Atlas_Output_Vertex { + float uv[2]; + int xref; // Index of input vertex from which this output vertex originated. +}; + +struct Atlas_Output_Mesh { + int atlas_width; + int atlas_height; + int vertex_count; + int index_count; + Atlas_Output_Vertex * vertex_array; + int * index_array; +}; + +enum Atlas_Error { + Atlas_Error_Success, + Atlas_Error_Invalid_Args, + Atlas_Error_Invalid_Options, + Atlas_Error_Invalid_Mesh, + Atlas_Error_Invalid_Mesh_Non_Manifold, + Atlas_Error_Not_Implemented, +}; + +void atlas_set_default_options(Atlas_Options * options); + +Atlas_Output_Mesh * atlas_generate(const Atlas_Input_Mesh * input, const Atlas_Options * options, Atlas_Error * error); + +void atlas_free(Atlas_Output_Mesh * output); + + +/* + +Should we represent the input mesh with an opaque structure that simply holds pointers to the user data? That would allow us to avoid having to copy attributes to an intermediate representation. + +struct Atlas_Input_Mesh; + +void mesh_set_vertex_position(Atlas_Input_Mesh * mesh, float * ptr, int stride); +void mesh_set_vertex_normal(Atlas_Input_Mesh * mesh, float * ptr, int stride); +void mesh_set_vertex_uv(Mesh * mesh, float * ptr, int stride); + +void mesh_set_index(Mesh * mesh, int * ptr); +*/ + +} // Thekla namespace + diff --git a/thirdparty/thekla_atlas/src/thekla/thekla_atlas_test.cpp b/thirdparty/thekla_atlas/src/thekla/thekla_atlas_test.cpp new file mode 100755 index 00000000..02fbfe6e --- /dev/null +++ b/thirdparty/thekla_atlas/src/thekla/thekla_atlas_test.cpp @@ -0,0 +1,58 @@ + +#include "thekla_atlas.h" +#include "thekla_mesh_load.h" + +#include +#include + + +using namespace Thekla; + +int main(int argc, char * argv[]) { + + if (argc != 2) { + printf("Usage: %s input_file.obj\n", argv[0]); + return EXIT_FAILURE; + } + + // Load Obj_Mesh. + Obj_Load_Options load_options = {0}; + Obj_Mesh * obj_mesh = obj_mesh_load(argv[1], &load_options); + + if (obj_mesh == NULL) { + printf("Error loading obj file.\n"); + return 1; + } + + // Convert Obj_Mesh to Atlast_Input_Mesh. + assert(sizeof(Atlas_Input_Vertex) == sizeof(Obj_Vertex)); + assert(sizeof(Atlas_Input_Face) == sizeof(Obj_Face)); + + Atlas_Input_Mesh input_mesh; + input_mesh.vertex_count = obj_mesh->vertex_count; + input_mesh.vertex_array = (Atlas_Input_Vertex *)obj_mesh->vertex_array; + input_mesh.face_count = obj_mesh->face_count; + input_mesh.face_array = (Atlas_Input_Face *)obj_mesh->face_array; + + + // Generate Atlas_Output_Mesh. + Atlas_Options atlas_options; + atlas_set_default_options(&atlas_options); + + // Avoid brute force packing, since it can be unusably slow in some situations. + atlas_options.packer_options.witness.packing_quality = 1; + + Atlas_Error error = Atlas_Error_Success; + Atlas_Output_Mesh * output_mesh = atlas_generate(&input_mesh, &atlas_options, &error); + + printf("Atlas mesh has %d verts\n", output_mesh->vertex_count); + printf("Atlas mesh has %d triangles\n", output_mesh->index_count / 3); + printf("Produced debug_packer_final.tga\n"); + + // Free meshes. + obj_mesh_free(obj_mesh); + atlas_free(output_mesh); + + return 0; + +} diff --git a/thirdparty/thekla_atlas/src/thekla/thekla_mesh_load.h b/thirdparty/thekla_atlas/src/thekla/thekla_mesh_load.h new file mode 100755 index 00000000..6dcf769d --- /dev/null +++ b/thirdparty/thekla_atlas/src/thekla/thekla_mesh_load.h @@ -0,0 +1,110 @@ + +#include // FILE + +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +namespace Thekla { + +struct Obj_Vertex { + float position[3]; + float normal[3]; + float uv[2]; + int first_colocal; +}; + +struct Obj_Face { + int vertex_index[3]; + int material_index; +}; + +struct Obj_Material { + // @@ Read obj mtl parameters as is. +}; + +struct Obj_Mesh { + int vertex_count; + Obj_Vertex * vertex_array; + + int face_count; + Obj_Face * face_array; + + int material_count; + Obj_Material * material_array; +}; + +enum Load_Flags { + Load_Flag_Weld_Attributes, +}; + +struct Obj_Load_Options { + int load_flags; +}; + +Obj_Mesh * obj_mesh_load(const char * filename, const Obj_Load_Options * options); +void obj_mesh_free(Obj_Mesh * mesh); + + + +Obj_Mesh * obj_mesh_load(const char * filename, const Obj_Load_Options * options) { + + using namespace std; + vector shapes; + vector materials; + string err; + bool ret = tinyobj::LoadObj(shapes, materials, err, filename); + if (!ret) { + printf("%s\n", err.c_str()); + return NULL; + } + + printf("%lu shapes\n", shapes.size()); + printf("%lu materials\n", materials.size()); + + assert(shapes.size() > 0); + + Obj_Mesh* mesh = new Obj_Mesh(); + + mesh->vertex_count = shapes[0].mesh.positions.size() / 3; + mesh->vertex_array = new Obj_Vertex[mesh->vertex_count]; + for (int nvert = 0; nvert < mesh->vertex_count; nvert++) { + mesh->vertex_array[nvert].position[0] = shapes[0].mesh.positions[nvert * 3]; + mesh->vertex_array[nvert].position[1] = shapes[0].mesh.positions[nvert * 3 + 1]; + mesh->vertex_array[nvert].position[2] = shapes[0].mesh.positions[nvert * 3 + 2]; + mesh->vertex_array[nvert].normal[0] = shapes[0].mesh.normals[nvert * 3]; + mesh->vertex_array[nvert].normal[1] = shapes[0].mesh.normals[nvert * 3 + 1]; + mesh->vertex_array[nvert].normal[2] = shapes[0].mesh.normals[nvert * 3 + 2]; + mesh->vertex_array[nvert].uv[0] = 0; + mesh->vertex_array[nvert].uv[1] = 0; + mesh->vertex_array[nvert].first_colocal = nvert; + } + + mesh->face_count = shapes[0].mesh.indices.size() / 3; + mesh->face_array = new Obj_Face[mesh->face_count]; + for (int nface = 0; nface < mesh->face_count; nface++) { + mesh->face_array[nface].material_index = 0; + mesh->face_array[nface].vertex_index[0] = shapes[0].mesh.indices[nface * 3]; + mesh->face_array[nface].vertex_index[1] = shapes[0].mesh.indices[nface * 3 + 1]; + mesh->face_array[nface].vertex_index[2] = shapes[0].mesh.indices[nface * 3 + 2]; + } + + printf("Reading %d verts\n", mesh->vertex_count); + printf("Reading %d triangles\n", mesh->face_count); + + mesh->material_count = 0; + mesh->material_array = 0; + + return mesh; +} + + +void obj_mesh_free(Obj_Mesh * mesh) { + if (mesh != NULL) { + delete [] mesh->vertex_array; + delete [] mesh->face_array; + delete [] mesh->material_array; + delete mesh; + } +} + +} // Thekla namespace