aboutsummaryrefslogtreecommitdiff
path: root/experimental
diff options
context:
space:
mode:
Diffstat (limited to 'experimental')
-rw-r--r--experimental/README.md16
-rw-r--r--experimental/lfpAlloc/Allocator.hpp89
-rw-r--r--experimental/lfpAlloc/ChunkList.hpp116
-rw-r--r--experimental/lfpAlloc/LICENSE21
-rw-r--r--experimental/lfpAlloc/Pool.hpp48
-rw-r--r--experimental/lfpAlloc/PoolDispatcher.hpp79
-rw-r--r--experimental/lfpAlloc/Utils.hpp20
-rw-r--r--experimental/premake5.lua94
-rw-r--r--experimental/tinyobj_loader_opt.h1684
-rw-r--r--experimental/trackball.cc292
-rw-r--r--experimental/trackball.h75
-rw-r--r--experimental/viewer.cc748
12 files changed, 3282 insertions, 0 deletions
diff --git a/experimental/README.md b/experimental/README.md
new file mode 100644
index 0000000..99378ce
--- /dev/null
+++ b/experimental/README.md
@@ -0,0 +1,16 @@
+# Experimental code for .obj loader.
+
+* Multi-threaded optimized parser : tinyobj_loader_opt.h
+
+## Requirements
+
+* C++-11 compiler
+
+## Compile options
+
+* zstd compressed .obj support. `--with-zstd` premake option.
+* gzip compressed .obj support. `--with-zlib` premake option.
+
+## Licenses
+
+* lfpAlloc : MIT license.
diff --git a/experimental/lfpAlloc/Allocator.hpp b/experimental/lfpAlloc/Allocator.hpp
new file mode 100644
index 0000000..4dddaab
--- /dev/null
+++ b/experimental/lfpAlloc/Allocator.hpp
@@ -0,0 +1,89 @@
+#ifndef LF_POOL_ALLOCATOR
+#define LF_POOL_ALLOCATOR
+
+#include <memory>
+#include <thread>
+#include <lfpAlloc/PoolDispatcher.hpp>
+
+namespace lfpAlloc {
+template <typename T, std::size_t NumPools = 70>
+class lfpAllocator {
+public:
+ using value_type = T;
+ using pointer = T*;
+ using const_pointer = const T*;
+ using reference = T&;
+ using const_reference = T const&;
+
+ template <typename U>
+ struct rebind {
+ typedef lfpAllocator<U, NumPools> other;
+ };
+
+ lfpAllocator() {}
+
+ template <typename U>
+ lfpAllocator(lfpAllocator<U, NumPools>&&) noexcept {}
+
+ template <typename U>
+ lfpAllocator(const lfpAllocator<U, NumPools>&) noexcept {}
+
+ T* allocate(std::size_t count) {
+ if (sizeof(T) * count <=
+ alignof(std::max_align_t) * NumPools - sizeof(void*)) {
+ return reinterpret_cast<T*>(
+ dispatcher_.allocate(sizeof(T) * count));
+ } else {
+ return new T[count];
+ }
+ }
+
+ void deallocate(T* p, std::size_t count) noexcept {
+ if (sizeof(T) * count <=
+ alignof(std::max_align_t) * NumPools - sizeof(void*)) {
+ dispatcher_.deallocate(p, sizeof(T) * count);
+ } else {
+ delete[] p;
+ }
+ }
+
+ // Should not be required, but allocator_traits is not complete in
+ // gcc 4.9.1
+ template <typename U>
+ void destroy(U* p) {
+ p->~U();
+ }
+
+ template <typename U, typename... Args>
+ void construct(U* p, Args&&... args) {
+ new (p) U(std::forward<Args>(args)...);
+ }
+
+ template <typename Ty, typename U, std::size_t N, std::size_t M>
+ friend bool operator==(const lfpAllocator<Ty, N>&,
+ const lfpAllocator<U, M>&) noexcept;
+
+ template <typename U, std::size_t M>
+ friend class lfpAllocator;
+
+private:
+ static PoolDispatcher<NumPools> dispatcher_;
+};
+
+template <typename T, std::size_t N>
+PoolDispatcher<N> lfpAllocator<T, N>::dispatcher_;
+
+template <typename T, typename U, std::size_t N, std::size_t M>
+inline bool operator==(const lfpAllocator<T, N>&,
+ const lfpAllocator<U, M>&) noexcept {
+ return N == M;
+}
+
+template <typename T, typename U, std::size_t N, std::size_t M>
+inline bool operator!=(const lfpAllocator<T, N>& left,
+ const lfpAllocator<U, M>& right) noexcept {
+ return !(left == right);
+}
+}
+
+#endif
diff --git a/experimental/lfpAlloc/ChunkList.hpp b/experimental/lfpAlloc/ChunkList.hpp
new file mode 100644
index 0000000..760c670
--- /dev/null
+++ b/experimental/lfpAlloc/ChunkList.hpp
@@ -0,0 +1,116 @@
+#ifndef LF_POOL_ALLOC_CHUNK_LIST
+#define LF_POOL_ALLOC_CHUNK_LIST
+
+#include <cstdint>
+#include <atomic>
+#include <type_traits>
+
+#ifndef LFP_ALLOW_BLOCKING
+static_assert(ATOMIC_POINTER_LOCK_FREE == 2,
+ "Atomic pointer is not lock-free.");
+#endif
+
+namespace lfpAlloc {
+
+template <std::size_t Size>
+struct Cell {
+ uint8_t val_[Size];
+ Cell* next_ = this + 1;
+};
+
+// For small types (less than the size of void*), no additional
+// space is needed, so union val_ with next_ to avoid overhead.
+template <>
+struct Cell<0> {
+ Cell() : next_{this + 1} {}
+ union {
+ uint8_t val_[sizeof(Cell*)];
+ Cell* next_;
+ };
+};
+
+template <std::size_t Size, std::size_t AllocationsPerChunk>
+struct Chunk {
+ Chunk() noexcept {
+ auto& last = memBlock_[AllocationsPerChunk - 1];
+ last.next_ = nullptr;
+ }
+ Cell<Size> memBlock_[AllocationsPerChunk];
+};
+
+template <typename T>
+struct Node {
+ Node() : val_(), next_(nullptr) {}
+ Node(const T& val) : val_(val), next_(nullptr) {}
+ T val_;
+ std::atomic<Node<T>*> next_;
+};
+
+template <std::size_t Size, std::size_t AllocationsPerChunk>
+class ChunkList {
+ static constexpr auto CellSize =
+ (Size > sizeof(void*)) ? Size - sizeof(void*) : 0;
+ using Chunk_t = Chunk<CellSize, AllocationsPerChunk>;
+ using Cell_t = Cell<CellSize>;
+
+ using ChunkNode = Node<Chunk_t>;
+ using CellNode = Node<Cell_t*>;
+
+public:
+ static ChunkList& getInstance() {
+ static ChunkList c;
+ return c;
+ }
+
+ Cell_t* allocateChain() {
+ CellNode* recentHead = head_.load();
+ CellNode* currentNext = nullptr;
+ do {
+ // If there are no available chains, allocate a new chunk
+ if (!recentHead) {
+ ChunkNode* currentHandle;
+
+ // Make a new node
+ auto newChunk = new ChunkNode();
+
+ // Add the chunk to the chain
+ do {
+ currentHandle = handle_.load();
+ newChunk->next_ = currentHandle;
+ } while (
+ !handle_.compare_exchange_weak(currentHandle, newChunk));
+ return &newChunk->val_.memBlock_[0];
+ }
+
+ currentNext = recentHead->next_;
+ } while (!head_.compare_exchange_weak(recentHead, currentNext));
+
+ auto retnValue = recentHead->val_;
+ delete recentHead;
+ return retnValue;
+ }
+
+ void deallocateChain(Cell_t* newCell) {
+ if (!newCell) {
+ return;
+ }
+ CellNode* currentHead = head_.load();
+
+ // Construct a new node to be added to the linked list
+ CellNode* newHead = new CellNode(newCell);
+
+ // Add the chain to the linked list
+ do {
+ newHead->next_.store(currentHead, std::memory_order_release);
+ } while (!head_.compare_exchange_weak(currentHead, newHead));
+ }
+
+private:
+ ChunkList() : handle_(nullptr), head_(nullptr) {}
+
+ std::atomic<ChunkNode*> handle_;
+ std::atomic<CellNode*> head_;
+};
+}
+
+#endif
diff --git a/experimental/lfpAlloc/LICENSE b/experimental/lfpAlloc/LICENSE
new file mode 100644
index 0000000..b9e2c10
--- /dev/null
+++ b/experimental/lfpAlloc/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Adam Schwalm
+
+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. \ No newline at end of file
diff --git a/experimental/lfpAlloc/Pool.hpp b/experimental/lfpAlloc/Pool.hpp
new file mode 100644
index 0000000..370dab7
--- /dev/null
+++ b/experimental/lfpAlloc/Pool.hpp
@@ -0,0 +1,48 @@
+#ifndef LF_POOL_ALLOC_POOL
+#define LF_POOL_ALLOC_POOL
+
+#include <lfpAlloc/Utils.hpp>
+#include <lfpAlloc/ChunkList.hpp>
+
+namespace lfpAlloc {
+template <std::size_t Size, std::size_t AllocationsPerChunk>
+class Pool {
+ using ChunkList_t = ChunkList<Size, AllocationsPerChunk>;
+
+public:
+ static constexpr auto CellSize =
+ (Size > sizeof(void*)) ? Size - sizeof(void*) : 0;
+ using Cell_t = Cell<CellSize>;
+
+ Pool() : head_(nullptr) {}
+
+ ~Pool() { ChunkList_t::getInstance().deallocateChain(head_); }
+
+ void* allocate() {
+ // Head loaded from head_
+ Cell_t* currentHead = head_;
+ Cell_t* next;
+
+ // Out of cells to allocate
+ if (!currentHead) {
+ currentHead = ChunkList_t::getInstance().allocateChain();
+ }
+
+ next = currentHead->next_;
+ head_ = next;
+ return &currentHead->val_;
+ }
+
+ void deallocate(void* p) noexcept {
+ auto newHead = reinterpret_cast<Cell_t*>(p);
+ Cell_t* currentHead = head_;
+ newHead->next_ = currentHead;
+ head_ = newHead;
+ }
+
+private:
+ Cell_t* head_;
+};
+}
+
+#endif
diff --git a/experimental/lfpAlloc/PoolDispatcher.hpp b/experimental/lfpAlloc/PoolDispatcher.hpp
new file mode 100644
index 0000000..e4d1427
--- /dev/null
+++ b/experimental/lfpAlloc/PoolDispatcher.hpp
@@ -0,0 +1,79 @@
+#ifndef LF_POOL_DISPATCHER
+#define LF_POOL_DISPATCHER
+
+#include <tuple>
+#include <cassert>
+#include <cstddef>
+#include <lfpAlloc/Pool.hpp>
+
+#ifndef LFP_ALLOCATIONS_PER_CHUNK
+#define LFP_ALLOCATIONS_PER_CHUNK 64 * 100
+#endif
+
+namespace lfpAlloc {
+namespace detail {
+
+template <std::size_t Num, uint16_t... Ts>
+struct Pools : Pools<Num - 1, alignof(std::max_align_t) * Num, Ts...> {};
+
+template <uint16_t... Size>
+struct Pools<0, Size...> {
+ using type = std::tuple<Pool<Size, LFP_ALLOCATIONS_PER_CHUNK>...>;
+};
+}
+
+template <std::size_t NumPools>
+class PoolDispatcher {
+public:
+ void* allocate(std::size_t size) { return dispatchAllocate<0>(size); }
+
+ void deallocate(void* p, std::size_t size) noexcept {
+ dispatchDeallocate<0>(p, size);
+ }
+
+private:
+ thread_local static typename detail::Pools<NumPools>::type pools_;
+ static_assert(NumPools > 0, "Invalid number of pools");
+
+ template <std::size_t Index>
+ typename std::enable_if <
+ Index<NumPools, void*>::type
+ dispatchAllocate(std::size_t const& requestSize) {
+ if (requestSize <= std::get<Index>(pools_).CellSize) {
+ return std::get<Index>(pools_).allocate();
+ } else {
+ return dispatchAllocate<Index + 1>(requestSize);
+ }
+ }
+
+ template <std::size_t Index>
+ typename std::enable_if<!(Index < NumPools), void*>::type
+ dispatchAllocate(std::size_t const&) {
+ assert(false && "Invalid allocation size.");
+ return nullptr;
+ }
+
+ template <std::size_t Index>
+ typename std::enable_if <
+ Index<NumPools>::type
+ dispatchDeallocate(void* p, std::size_t const& requestSize) noexcept {
+ if (requestSize <= std::get<Index>(pools_).CellSize) {
+ std::get<Index>(pools_).deallocate(p);
+ } else {
+ dispatchDeallocate<Index + 1>(p, requestSize);
+ }
+ }
+
+ template <std::size_t Index>
+ typename std::enable_if<!(Index < NumPools)>::type
+ dispatchDeallocate(void*, std::size_t const&) noexcept {
+ assert(false && "Invalid deallocation size.");
+ }
+};
+
+template <std::size_t NumPools>
+thread_local typename detail::Pools<NumPools>::type
+ PoolDispatcher<NumPools>::pools_;
+}
+
+#endif
diff --git a/experimental/lfpAlloc/Utils.hpp b/experimental/lfpAlloc/Utils.hpp
new file mode 100644
index 0000000..8740a79
--- /dev/null
+++ b/experimental/lfpAlloc/Utils.hpp
@@ -0,0 +1,20 @@
+#include <cstdint>
+
+namespace lfpAlloc {
+namespace detail {
+template <std::size_t Val, std::size_t base = 2>
+struct Log {
+ enum { value = 1 + Log<Val / base, base>::value };
+};
+
+template <std::size_t base>
+struct Log<1, base> {
+ enum { value = 0 };
+};
+
+template <std::size_t base>
+struct Log<0, base> {
+ enum { value = 0 };
+};
+}
+}
diff --git a/experimental/premake5.lua b/experimental/premake5.lua
new file mode 100644
index 0000000..29511e1
--- /dev/null
+++ b/experimental/premake5.lua
@@ -0,0 +1,94 @@
+newoption {
+ trigger = "with-zlib",
+ description = "Build with zlib."
+}
+
+newoption {
+ trigger = "with-zstd",
+ description = "Build with ZStandard compression."
+}
+
+newoption {
+ trigger = "clang",
+ description = "Use clang compiler."
+}
+
+newoption {
+ trigger = "asan",
+ description = "Enable AddressSanitizer(gcc or clang only)."
+}
+
+solution "objview"
+ -- location ( "build" )
+ configurations { "Release", "Debug" }
+ platforms {"native", "x64", "x32"}
+
+ project "objview"
+
+ kind "ConsoleApp"
+ language "C++"
+ files { "viewer.cc", "trackball.cc" }
+ includedirs { "./" }
+ includedirs { "../../" }
+
+ flags { "c++11" }
+
+ if _OPTIONS['clang'] then
+ toolset "clang"
+ end
+
+ if _OPTIONS['with-zlib'] then
+ defines { 'ENABLE_ZLIB' }
+ links { 'z' }
+ end
+
+ if _OPTIONS['asan'] then
+ buildoptions { '-fsanitize=address' }
+ linkoptions { '-fsanitize=address' }
+ end
+
+ if _OPTIONS['with-zstd'] then
+ print("with-zstd")
+ defines { 'ENABLE_ZSTD' }
+ -- Set path to zstd installed dir.
+ includedirs { '$$HOME/local/include' }
+ libdirs { '$$HOME/local/lib' }
+ links { 'zstd' }
+ end
+
+ -- Uncomment if you want address sanitizer(gcc/clang only)
+ --buildoptions { "-fsanitize=address" }
+ --linkoptions { "-fsanitize=address" }
+
+ configuration { "linux" }
+ linkoptions { "`pkg-config --libs glfw3`" }
+ links { "GL", "GLU", "m", "GLEW", "X11", "Xrandr", "Xinerama", "Xi", "Xxf86vm", "Xcursor", "dl" }
+ linkoptions { "-pthread" }
+
+ configuration { "windows" }
+ -- Path to GLFW3
+ includedirs { '../../../local/glfw-3.2.bin.WIN64/include' }
+ libdirs { '../../../local/glfw-3.2.bin.WIN64/lib-vc2015' }
+ -- Path to GLEW
+ includedirs { '../../../local/glew-1.13.0/include' }
+ libdirs { '../../../local/glew-1.13.0/lib/Release/x64' }
+
+ links { "glfw3", "glew32", "gdi32", "winmm", "user32", "glu32","opengl32", "kernel32" }
+ defines { "_CRT_SECURE_NO_WARNINGS" }
+ defines { "NOMINMAX" }
+
+ configuration { "macosx" }
+ includedirs { "/usr/local/include" }
+ buildoptions { "-Wno-deprecated-declarations" }
+ libdirs { "/usr/local/lib" }
+ links { "glfw3", "GLEW" }
+ linkoptions { "-framework OpenGL", "-framework Cocoa", "-framework IOKit", "-framework CoreVideo" }
+
+ configuration "Debug"
+ defines { "DEBUG" }
+ flags { "Symbols"}
+
+ configuration "Release"
+ defines { "NDEBUG" }
+ flags { "Optimize"}
+
diff --git a/experimental/tinyobj_loader_opt.h b/experimental/tinyobj_loader_opt.h
new file mode 100644
index 0000000..f86b482
--- /dev/null
+++ b/experimental/tinyobj_loader_opt.h
@@ -0,0 +1,1684 @@
+//
+// Optimized wavefront .obj loader.
+// Requires lfpAlloc and C++11
+//
+
+/*
+The MIT License (MIT)
+
+Copyright (c) 2012-2017 Syoyo Fujita and many contributors.
+
+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.
+*/
+#ifndef TINOBJ_LOADER_OPT_H_
+#define TINOBJ_LOADER_OPT_H_
+
+#ifdef _WIN32
+#define atoll(S) _atoi64(S)
+#include <windows.h>
+#else
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#endif
+
+#include <cassert>
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <vector>
+
+#include <atomic> // C++11
+#include <chrono> // C++11
+#include <thread> // C++11
+
+#include "lfpAlloc/Allocator.hpp"
+
+namespace tinyobj_opt {
+
+// ----------------------------------------------------------------------------
+// Small vector class useful for multi-threaded environment.
+//
+// stack_container.h
+//
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This allocator can be used with STL containers to provide a stack buffer
+// from which to allocate memory and overflows onto the heap. This stack buffer
+// would be allocated on the stack and allows us to avoid heap operations in
+// some situations.
+//
+// STL likes to make copies of allocators, so the allocator itself can't hold
+// the data. Instead, we make the creator responsible for creating a
+// StackAllocator::Source which contains the data. Copying the allocator
+// merely copies the pointer to this shared source, so all allocators created
+// based on our allocator will share the same stack buffer.
+//
+// This stack buffer implementation is very simple. The first allocation that
+// fits in the stack buffer will use the stack buffer. Any subsequent
+// allocations will not use the stack buffer, even if there is unused room.
+// This makes it appropriate for array-like containers, but the caller should
+// be sure to reserve() in the container up to the stack buffer size. Otherwise
+// the container will allocate a small array which will "use up" the stack
+// buffer.
+template <typename T, size_t stack_capacity>
+class StackAllocator : public std::allocator<T> {
+ public:
+ typedef typename std::allocator<T>::pointer pointer;
+ typedef typename std::allocator<T>::size_type size_type;
+
+ // Backing store for the allocator. The container owner is responsible for
+ // maintaining this for as long as any containers using this allocator are
+ // live.
+ struct Source {
+ Source() : used_stack_buffer_(false) {}
+
+ // Casts the buffer in its right type.
+ T *stack_buffer() { return reinterpret_cast<T *>(stack_buffer_); }
+ const T *stack_buffer() const {
+ return reinterpret_cast<const T *>(stack_buffer_);
+ }
+
+ //
+ // IMPORTANT: Take care to ensure that stack_buffer_ is aligned
+ // since it is used to mimic an array of T.
+ // Be careful while declaring any unaligned types (like bool)
+ // before stack_buffer_.
+ //
+
+ // The buffer itself. It is not of type T because we don't want the
+ // constructors and destructors to be automatically called. Define a POD
+ // buffer of the right size instead.
+ char stack_buffer_[sizeof(T[stack_capacity])];
+
+ // Set when the stack buffer is used for an allocation. We do not track
+ // how much of the buffer is used, only that somebody is using it.
+ bool used_stack_buffer_;
+ };
+
+ // Used by containers when they want to refer to an allocator of type U.
+ template <typename U>
+ struct rebind {
+ typedef StackAllocator<U, stack_capacity> other;
+ };
+
+ // For the straight up copy c-tor, we can share storage.
+ StackAllocator(const StackAllocator<T, stack_capacity> &rhs)
+ : source_(rhs.source_) {}
+
+ // ISO C++ requires the following constructor to be defined,
+ // and std::vector in VC++2008SP1 Release fails with an error
+ // in the class _Container_base_aux_alloc_real (from <xutility>)
+ // if the constructor does not exist.
+ // For this constructor, we cannot share storage; there's
+ // no guarantee that the Source buffer of Ts is large enough
+ // for Us.
+ // TODO(Google): If we were fancy pants, perhaps we could share storage
+ // iff sizeof(T) == sizeof(U).
+ template <typename U, size_t other_capacity>
+ StackAllocator(const StackAllocator<U, other_capacity> &other)
+ : source_(NULL) {
+ (void)other;
+ }
+
+ explicit StackAllocator(Source *source) : source_(source) {}
+
+ // Actually do the allocation. Use the stack buffer if nobody has used it yet
+ // and the size requested fits. Otherwise, fall through to the standard
+ // allocator.
+ pointer allocate(size_type n, void *hint = 0) {
+ if (source_ != NULL && !source_->used_stack_buffer_ &&
+ n <= stack_capacity) {
+ source_->used_stack_buffer_ = true;
+ return source_->stack_buffer();
+ } else {
+ return std::allocator<T>::allocate(n, hint);
+ }
+ }
+
+ // Free: when trying to free the stack buffer, just mark it as free. For
+ // non-stack-buffer pointers, just fall though to the standard allocator.
+ void deallocate(pointer p, size_type n) {
+ if (source_ != NULL && p == source_->stack_buffer())
+ source_->used_stack_buffer_ = false;
+ else
+ std::allocator<T>::deallocate(p, n);
+ }
+
+ private:
+ Source *source_;
+};
+
+// A wrapper around STL containers that maintains a stack-sized buffer that the
+// initial capacity of the vector is based on. Growing the container beyond the
+// stack capacity will transparently overflow onto the heap. The container must
+// support reserve().
+//
+// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this
+// type. This object is really intended to be used only internally. You'll want
+// to use the wrappers below for different types.
+template <typename TContainerType, int stack_capacity>
+class StackContainer {
+ public:
+ typedef TContainerType ContainerType;
+ typedef typename ContainerType::value_type ContainedType;
+ typedef StackAllocator<ContainedType, stack_capacity> Allocator;
+
+ // Allocator must be constructed before the container!
+ StackContainer() : allocator_(&stack_data_), container_(allocator_) {
+ // Make the container use the stack allocation by reserving our buffer size
+ // before doing anything else.
+ container_.reserve(stack_capacity);
+ }
+
+ // Getters for the actual container.
+ //
+ // Danger: any copies of this made using the copy constructor must have
+ // shorter lifetimes than the source. The copy will share the same allocator
+ // and therefore the same stack buffer as the original. Use std::copy to
+ // copy into a "real" container for longer-lived objects.
+ ContainerType &container() { return container_; }
+ const ContainerType &container() const { return container_; }
+
+ // Support operator-> to get to the container. This allows nicer syntax like:
+ // StackContainer<...> foo;
+ // std::sort(foo->begin(), foo->end());
+ ContainerType *operator->() { return &container_; }
+ const ContainerType *operator->() const { return &container_; }
+
+#ifdef UNIT_TEST
+ // Retrieves the stack source so that that unit tests can verify that the
+ // buffer is being used properly.
+ const typename Allocator::Source &stack_data() const { return stack_data_; }
+#endif
+
+ protected:
+ typename Allocator::Source stack_data_;
+ unsigned char pad_[7];
+ Allocator allocator_;
+ ContainerType container_;
+
+ // DISALLOW_EVIL_CONSTRUCTORS(StackContainer);
+ StackContainer(const StackContainer &);
+ void operator=(const StackContainer &);
+};
+
+// StackVector
+//
+// Example:
+// StackVector<int, 16> foo;
+// foo->push_back(22); // we have overloaded operator->
+// foo[0] = 10; // as well as operator[]
+template <typename T, size_t stack_capacity>
+class StackVector
+ : public StackContainer<std::vector<T, StackAllocator<T, stack_capacity> >,
+ stack_capacity> {
+ public:
+ StackVector()
+ : StackContainer<std::vector<T, StackAllocator<T, stack_capacity> >,
+ stack_capacity>() {}
+
+ // We need to put this in STL containers sometimes, which requires a copy
+ // constructor. We can't call the regular copy constructor because that will
+ // take the stack buffer from the original. Here, we create an empty object
+ // and make a stack buffer of its own.
+ StackVector(const StackVector<T, stack_capacity> &other)
+ : StackContainer<std::vector<T, StackAllocator<T, stack_capacity> >,
+ stack_capacity>() {
+ this->container().assign(other->begin(), other->end());
+ }
+
+ StackVector<T, stack_capacity> &operator=(
+ const StackVector<T, stack_capacity> &other) {
+ this->container().assign(other->begin(), other->end());
+ return *this;
+ }
+
+ // Vectors are commonly indexed, which isn't very convenient even with
+ // operator-> (using "->at()" does exception stuff we don't want).
+ T &operator[](size_t i) { return this->container().operator[](i); }
+ const T &operator[](size_t i) const {
+ return this->container().operator[](i);
+ }
+};
+
+// ----------------------------------------------------------------------------
+
+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;
+
+ int dummy; // Suppress padding warning.
+
+ 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
+
+ // PBR extension
+ // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
+ float roughness; // [0, 1] default 0
+ float metallic; // [0, 1] default 0
+ float sheen; // [0, 1] default 0
+ float clearcoat_thickness; // [0, 1] default 0
+ float clearcoat_roughness; // [0, 1] default 0
+ float anisotropy; // aniso. [0, 1] default 0
+ float anisotropy_rotation; // anisor. [0, 1] default 0
+ std::string roughness_texname; // map_Pr
+ std::string metallic_texname; // map_Pm
+ std::string sheen_texname; // map_Ps
+ std::string emissive_texname; // map_Ke
+ std::string normal_texname; // norm. For normal mapping.
+
+ std::map<std::string, std::string> unknown_parameter;
+} material_t;
+
+typedef struct {
+ std::string name; // group name or object name.
+ unsigned int face_offset;
+ unsigned int length;
+} shape_t;
+
+struct index_t {
+ int vertex_index, texcoord_index, normal_index;
+ index_t() : vertex_index(-1), texcoord_index(-1), normal_index(-1) {}
+ explicit index_t(int idx)
+ : vertex_index(idx), texcoord_index(idx), normal_index(idx) {}
+ index_t(int vidx, int vtidx, int vnidx)
+ : vertex_index(vidx), texcoord_index(vtidx), normal_index(vnidx) {}
+};
+
+typedef struct {
+ std::vector<float, lfpAlloc::lfpAllocator<float> > vertices;
+ std::vector<float, lfpAlloc::lfpAllocator<float> > normals;
+ std::vector<float, lfpAlloc::lfpAllocator<float> > texcoords;
+ std::vector<index_t, lfpAlloc::lfpAllocator<index_t> > indices;
+ std::vector<int, lfpAlloc::lfpAllocator<int> > face_num_verts;
+ std::vector<int, lfpAlloc::lfpAllocator<int> > material_ids;
+} attrib_t;
+
+typedef StackVector<char, 256> ShortString;
+
+#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t'))
+#define IS_DIGIT(x) \
+ (static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10))
+#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0'))
+
+static inline void skip_space(const char **token) {
+ while ((*token)[0] == ' ' || (*token)[0] == '\t') {
+ (*token)++;
+ }
+}
+
+static inline void skip_space_and_cr(const char **token) {
+ while ((*token)[0] == ' ' || (*token)[0] == '\t' || (*token)[0] == '\r') {
+ (*token)++;
+ }
+}
+
+static inline int until_space(const char *token) {
+ const char *p = token;
+ while (p[0] != '\0' && p[0] != ' ' && p[0] != '\t' && p[0] != '\r') {
+ p++;
+ }
+
+ return p - token;
+}
+
+static inline int length_until_newline(const char *token, int n) {
+ int len = 0;
+
+ // Assume token[n-1] = '\0'
+ for (len = 0; len < n - 1; len++) {
+ if (token[len] == '\n') {
+ break;
+ }
+ if ((token[len] == '\r') && ((len < (n - 2)) && (token[len + 1] != '\n'))) {
+ break;
+ }
+ }
+
+ return len;
+}
+
+// http://stackoverflow.com/questions/5710091/how-does-atoi-function-in-c-work
+static inline int my_atoi(const char *c) {
+ int value = 0;
+ int sign = 1;
+ if (*c == '+' || *c == '-') {
+ if (*c == '-') sign = -1;
+ c++;
+ }
+ while (((*c) >= '0') && ((*c) <= '9')) { // isdigit(*c)
+ value *= 10;
+ value += (int)(*c - '0');
+ c++;
+ }
+ return value * sign;
+}
+
+// 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
+}
+
+// Parse raw triples: i, i/j/k, i//k, i/j
+static index_t parseRawTriple(const char **token) {
+ index_t vi(
+ static_cast<int>(0x80000000)); // 0x80000000 = -2147483648 = invalid
+
+ vi.vertex_index = my_atoi((*token));
+ while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
+ (*token)[0] != '\t' && (*token)[0] != '\r') {
+ (*token)++;
+ }
+ if ((*token)[0] != '/') {
+ return vi;
+ }
+ (*token)++;
+
+ // i//k
+ if ((*token)[0] == '/') {
+ (*token)++;
+ vi.normal_index = my_atoi((*token));
+ while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
+ (*token)[0] != '\t' && (*token)[0] != '\r') {
+ (*token)++;
+ }
+ return vi;
+ }
+
+ // i/j/k or i/j
+ vi.texcoord_index = my_atoi((*token));
+ while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
+ (*token)[0] != '\t' && (*token)[0] != '\r') {
+ (*token)++;
+ }
+ if ((*token)[0] != '/') {
+ return vi;
+ }
+
+ // i/j/k
+ (*token)++; // skip '/'
+ vi.normal_index = my_atoi((*token));
+ while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
+ (*token)[0] != '\t' && (*token)[0] != '\r') {
+ (*token)++;
+ }
+ return vi;
+}
+
+static inline bool parseString(ShortString *s, const char **token) {
+ skip_space(token);
+ size_t e = until_space((*token));
+ (*s)->insert((*s)->end(), (*token), (*token) + e);
+ (*token) += e;
+ return true;
+}
+
+static inline int parseInt(const char **token) {
+ skip_space(token);
+ int i = my_atoi((*token));
+ (*token) += until_space((*token));
+ 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 (IS_DIGIT(*curr)) { /* Pass through. */
+ } else {
+ goto fail;
+ }
+
+ // Read the integer part.
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ mantissa *= 10;
+ mantissa += static_cast<int>(*curr - 0x30);
+ curr++;
+ read++;
+ end_not_reached = (curr != s_end);
+ }
+
+ // 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;
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ // pow(10.0, -read)
+ double frac_value = 1.0;
+ for (int f = 0; f < read; f++) {
+ frac_value *= 0.1;
+ }
+ mantissa += static_cast<int>(*curr - 0x30) * frac_value;
+ read++;
+ curr++;
+ end_not_reached = (curr != s_end);
+ }
+ } 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.
+ end_not_reached = (curr != s_end);
+ if (end_not_reached && (*curr == '+' || *curr == '-')) {
+ exp_sign = *curr;
+ curr++;
+ } else if (IS_DIGIT(*curr)) { /* Pass through. */
+ } else {
+ // Empty E is not allowed.
+ goto fail;
+ }
+
+ read = 0;
+ end_not_reached = (curr != s_end);
+ while (end_not_reached && IS_DIGIT(*curr)) {
+ exponent *= 10;
+ exponent += static_cast<int>(*curr - 0x30);
+ curr++;
+ read++;
+ end_not_reached = (curr != s_end);
+ }
+ exponent *= (exp_sign == '+' ? 1 : -1);
+ if (read == 0) goto fail;
+ }
+
+assemble :
+
+{
+ // = pow(5.0, exponent);
+ double a = 1.0;
+ for (int i = 0; i < exponent; i++) {
+ a = a * 5.0;
+ }
+ *result =
+ //(sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent);
+ (sign == '+' ? 1 : -1) * (mantissa * a) *
+ static_cast<double>(1ULL << exponent); // 5.0^exponent * 2^exponent
+}
+
+ return true;
+fail:
+ return false;
+}
+
+static inline float parseFloat(const char **token) {
+ skip_space(token);
+#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER
+ float f = static_cast<float>(atof(*token));
+ (*token) += strcspn((*token), " \t\r");
+#else
+ const char *end = (*token) + until_space((*token));
+ double val = 0.0;
+ tryParseDouble((*token), end, &val);
+ float f = static_cast<float>(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);
+}
+
+static 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 void LoadMtl(std::map<std::string, int> *material_map,
+ std::vector<material_t> *materials,
+ std::istream *inStream) {
+ // Create a default material anyway.
+ material_t material;
+ InitMaterial(&material);
+
+ size_t maxchars = 8192; // Alloc enough size.
+ std::vector<char> buf(maxchars); // Alloc enough size.
+ while (inStream->peek() != -1) {
+ inStream->getline(&buf[0], static_cast<std::streamsize>(maxchars));
+
+ std::string linebuf(&buf[0]);
+
+ // Trim trailing whitespace.
+ if (linebuf.size() > 0) {
+ linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1);
+ }
+
+ // 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)) && IS_SPACE((token[6]))) {
+ // flush previous material.
+ if (!material.name.empty()) {
+ material_map->insert(std::pair<std::string, int>(
+ material.name, static_cast<int>(materials->size())));
+ materials->push_back(material);
+ }
+
+ // initial temporary material
+ InitMaterial(&material);
+
+ // set new mtl name
+ char namebuf[4096];
+ 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' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((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' && IS_SPACE((token[2]))) ||
+ (token[0] == 'T' && token[1] == 'f' && IS_SPACE((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' && IS_SPACE((token[2]))) {
+ token += 2;
+ material.ior = parseFloat(&token);
+ continue;
+ }
+
+ // emission
+ if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(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' && IS_SPACE(token[2])) {
+ token += 2;
+ material.shininess = parseFloat(&token);
+ continue;
+ }
+
+ // illum model
+ if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) {
+ token += 6;
+ material.illum = parseInt(&token);
+ continue;
+ }
+
+ // dissolve
+ if ((token[0] == 'd' && IS_SPACE(token[1]))) {
+ token += 1;
+ material.dissolve = parseFloat(&token);
+ continue;
+ }
+
+ if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) {
+ token += 2;
+ // Invert value of Tr(assume Tr is in range [0, 1])
+ material.dissolve = 1.0f - parseFloat(&token);
+ continue;
+ }
+
+ // PBR: roughness
+ if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) {
+ token += 2;
+ material.roughness = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: metallic
+ if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) {
+ token += 2;
+ material.metallic = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: sheen
+ if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) {
+ token += 2;
+ material.sheen = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: clearcoat thickness
+ if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) {
+ token += 2;
+ material.clearcoat_thickness = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: clearcoat roughness
+ if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) {
+ token += 4;
+ material.clearcoat_roughness = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: anisotropy
+ if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) {
+ token += 6;
+ material.anisotropy = parseFloat(&token);
+ continue;
+ }
+
+ // PBR: anisotropy rotation
+ if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.anisotropy_rotation = parseFloat(&token);
+ continue;
+ }
+
+ // ambient texture
+ if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.ambient_texname = token;
+ continue;
+ }
+
+ // diffuse texture
+ if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.diffuse_texname = token;
+ continue;
+ }
+
+ // specular texture
+ if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.specular_texname = token;
+ continue;
+ }
+
+ // specular highlight texture
+ if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.specular_highlight_texname = token;
+ continue;
+ }
+
+ // bump texture
+ if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) {
+ token += 9;
+ material.bump_texname = token;
+ continue;
+ }
+
+ // alpha texture
+ if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) {
+ token += 6;
+ material.alpha_texname = token;
+ continue;
+ }
+
+ // bump texture
+ if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ material.bump_texname = token;
+ continue;
+ }
+
+ // displacement texture
+ if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ material.displacement_texname = token;
+ continue;
+ }
+
+ // PBR: roughness texture
+ if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.roughness_texname = token;
+ continue;
+ }
+
+ // PBR: metallic texture
+ if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.metallic_texname = token;
+ continue;
+ }
+
+ // PBR: sheen texture
+ if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.sheen_texname = token;
+ continue;
+ }
+
+ // PBR: emissive texture
+ if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
+ token += 7;
+ material.emissive_texname = token;
+ continue;
+ }
+
+ // PBR: normal map texture
+ if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
+ token += 5;
+ material.normal_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, static_cast<size_t>(len));
+ std::string value = _space + 1;
+ material.unknown_parameter.insert(
+ std::pair<std::string, std::string>(key, value));
+ }
+ }
+ // flush last material.
+ material_map->insert(std::pair<std::string, int>(
+ material.name, static_cast<int>(materials->size())));
+ materials->push_back(material);
+}
+
+typedef enum {
+ COMMAND_EMPTY,
+ COMMAND_V,
+ COMMAND_VN,
+ COMMAND_VT,
+ COMMAND_F,
+ COMMAND_G,
+ COMMAND_O,
+ COMMAND_USEMTL,
+ COMMAND_MTLLIB,
+
+} CommandType;
+
+typedef struct {
+ float vx, vy, vz;
+ float nx, ny, nz;
+ float tx, ty;
+
+ // for f
+ std::vector<index_t, lfpAlloc::lfpAllocator<index_t> > f;
+ // std::vector<index_t> f;
+ std::vector<int, lfpAlloc::lfpAllocator<int> > f_num_verts;
+
+ const char *group_name;
+ unsigned int group_name_len;
+ const char *object_name;
+ unsigned int object_name_len;
+ const char *material_name;
+ unsigned int material_name_len;
+
+ const char *mtllib_name;
+ unsigned int mtllib_name_len;
+
+ CommandType type;
+} Command;
+
+struct CommandCount {
+ size_t num_v;
+ size_t num_vn;
+ size_t num_vt;
+ size_t num_f;
+ size_t num_indices;
+ CommandCount() {
+ num_v = 0;
+ num_vn = 0;
+ num_vt = 0;
+ num_f = 0;
+ num_indices = 0;
+ }
+};
+
+class LoadOption {
+ public:
+ LoadOption() : req_num_threads(-1), triangulate(true), verbose(false) {}
+
+ int req_num_threads;
+ bool triangulate;
+ bool verbose;
+};
+
+/// Parse wavefront .obj(.obj string data is expanded to linear char array
+/// `buf')
+/// -1 to req_num_threads use the number of HW threads in the running system.
+bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, const char *buf, size_t len,
+ const LoadOption &option);
+
+} // namespace tinyobj_opt
+
+#endif // TINOBJ_LOADER_OPT_H_
+
+#ifdef TINYOBJ_LOADER_OPT_IMPLEMENTATION
+
+namespace tinyobj_opt {
+
+static bool parseLine(Command *command, const char *p, size_t p_len,
+ bool triangulate = true) {
+ // @todo { operate directly on pointer `p'. to do that, add range check for
+ // string operatoion against `p', since `p' is not null-terminated at p[p_len]
+ // }
+ char linebuf[4096];
+ assert(p_len < 4095);
+ memcpy(linebuf, p, p_len);
+ linebuf[p_len] = '\0';
+
+ const char *token = linebuf;
+
+ command->type = COMMAND_EMPTY;
+
+ // Skip leading space.
+ skip_space(&token);
+
+ assert(token);
+ if (token[0] == '\0') { // empty line
+ return false;
+ }
+
+ if (token[0] == '#') { // comment line
+ return false;
+ }
+
+ // vertex
+ if (token[0] == 'v' && IS_SPACE((token[1]))) {
+ token += 2;
+ float x = 0.0f, y = 0.0f, z = 0.0f;
+ parseFloat3(&x, &y, &z, &token);
+ command->vx = x;
+ command->vy = y;
+ command->vz = z;
+ command->type = COMMAND_V;
+ return true;
+ }
+
+ // normal
+ if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
+ token += 3;
+ float x = 0.0f, y = 0.0f, z = 0.0f;
+ parseFloat3(&x, &y, &z, &token);
+ command->nx = x;
+ command->ny = y;
+ command->nz = z;
+ command->type = COMMAND_VN;
+ return true;
+ }
+
+ // texcoord
+ if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
+ token += 3;
+ float x = 0.0f, y = 0.0f;
+ parseFloat2(&x, &y, &token);
+ command->tx = x;
+ command->ty = y;
+ command->type = COMMAND_VT;
+ return true;
+ }
+
+ // face
+ if (token[0] == 'f' && IS_SPACE((token[1]))) {
+ token += 2;
+ skip_space(&token);
+
+ StackVector<index_t, 8> f;
+
+ while (!IS_NEW_LINE(token[0])) {
+ index_t vi = parseRawTriple(&token);
+ skip_space_and_cr(&token);
+
+ f->push_back(vi);
+ }
+
+ command->type = COMMAND_F;
+
+ if (triangulate) {
+ index_t i0 = f[0];
+ index_t i1(-1);
+ index_t i2 = f[1];
+
+ for (size_t k = 2; k < f->size(); k++) {
+ i1 = i2;
+ i2 = f[k];
+ command->f.emplace_back(i0);
+ command->f.emplace_back(i1);
+ command->f.emplace_back(i2);
+
+ command->f_num_verts.emplace_back(3);
+ }
+
+ } else {
+ for (size_t k = 0; k < f->size(); k++) {
+ command->f.emplace_back(f[k]);
+ }
+
+ command->f_num_verts.emplace_back(f->size());
+ }
+
+ return true;
+ }
+
+ // use mtl
+ if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) {
+ token += 7;
+
+ // int newMaterialId = -1;
+ // if (material_map.find(namebuf) != material_map.end()) {
+ // newMaterialId = material_map[namebuf];
+ //} else {
+ // // { error!! material not found }
+ //}
+
+ // if (newMaterialId != materialId) {
+ // materialId = newMaterialId;
+ //}
+
+ // command->material_name = .insert(command->material_name->end(), namebuf,
+ // namebuf + strlen(namebuf));
+ // command->material_name->push_back('\0');
+ skip_space(&token);
+ command->material_name = p + (token - linebuf);
+ command->material_name_len =
+ length_until_newline(token, p_len - (token - linebuf)) + 1;
+ command->type = COMMAND_USEMTL;
+
+ return true;
+ }
+
+ // load mtl
+ if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
+ // By specification, `mtllib` should be appear only once in .obj
+ token += 7;
+
+ skip_space(&token);
+ command->mtllib_name = p + (token - linebuf);
+ command->mtllib_name_len =
+ length_until_newline(token, p_len - (token - linebuf)) + 1;
+ command->type = COMMAND_MTLLIB;
+
+ return true;
+ }
+
+ // group name
+ if (token[0] == 'g' && IS_SPACE((token[1]))) {
+ // @todo { multiple group name. }
+ token += 2;
+
+ command->group_name = p + (token - linebuf);
+ command->group_name_len =
+ length_until_newline(token, p_len - (token - linebuf)) + 1;
+ command->type = COMMAND_G;
+
+ return true;
+ }
+
+ // object name
+ if (token[0] == 'o' && IS_SPACE((token[1]))) {
+ // @todo { multiple object name? }
+ token += 2;
+
+ command->object_name = p + (token - linebuf);
+ command->object_name_len =
+ length_until_newline(token, p_len - (token - linebuf)) + 1;
+ command->type = COMMAND_O;
+
+ return true;
+ }
+
+ return false;
+}
+
+typedef struct {
+ size_t pos;
+ size_t len;
+} LineInfo;
+
+// Idea come from https://github.com/antonmks/nvParse
+// 1. mmap file
+// 2. find newline(\n, \r\n, \r) and list of line data.
+// 3. Do parallel parsing for each line.
+// 4. Reconstruct final mesh data structure.
+
+#define kMaxThreads (32)
+
+static inline bool is_line_ending(const char *p, size_t i, size_t end_i) {
+ if (p[i] == '\0') return true;
+ if (p[i] == '\n') return true; // this includes \r\n
+ if (p[i] == '\r') {
+ if (((i + 1) < end_i) && (p[i + 1] != '\n')) { // detect only \r case
+ return true;
+ }
+ }
+ return false;
+}
+
+bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes,
+ std::vector<material_t> *materials, const char *buf, size_t len,
+ const LoadOption &option) {
+ attrib->vertices.clear();
+ attrib->normals.clear();
+ attrib->texcoords.clear();
+ attrib->indices.clear();
+ attrib->face_num_verts.clear();
+ attrib->material_ids.clear();
+ shapes->clear();
+
+ if (len < 1) return false;
+
+ auto num_threads = (option.req_num_threads < 0)
+ ? std::thread::hardware_concurrency()
+ : option.req_num_threads;
+ num_threads =
+ (std::max)(1, (std::min)(static_cast<int>(num_threads), kMaxThreads));
+
+ if (option.verbose) {
+ std::cout << "# of threads = " << num_threads << std::endl;
+ }
+
+ auto t1 = std::chrono::high_resolution_clock::now();
+
+ std::vector<LineInfo, lfpAlloc::lfpAllocator<LineInfo> >
+ line_infos[kMaxThreads];
+ for (size_t t = 0; t < static_cast<size_t>(num_threads); t++) {
+ // Pre allocate enough memory. len / 128 / num_threads is just a heuristic
+ // value.
+ line_infos[t].reserve(len / 128 / num_threads);
+ }
+
+ std::chrono::duration<double, std::milli> ms_linedetection;
+ std::chrono::duration<double, std::milli> ms_alloc;
+ std::chrono::duration<double, std::milli> ms_parse;
+ std::chrono::duration<double, std::milli> ms_load_mtl;
+ std::chrono::duration<double, std::milli> ms_merge;
+ std::chrono::duration<double, std::milli> ms_construct;
+
+ // 1. Find '\n' and create line data.
+ {
+ StackVector<std::thread, 16> workers;
+
+ auto start_time = std::chrono::high_resolution_clock::now();
+ auto chunk_size = len / num_threads;
+
+ for (size_t t = 0; t < static_cast<size_t>(num_threads); t++) {
+ workers->push_back(std::thread([&, t]() {
+ auto start_idx = (t + 0) * chunk_size;
+ auto end_idx = (std::min)((t + 1) * chunk_size, len - 1);
+ if (t == static_cast<size_t>((num_threads - 1))) {
+ end_idx = len - 1;
+ }
+
+ size_t prev_pos = start_idx;
+ for (size_t i = start_idx; i < end_idx; i++) {
+ if (is_line_ending(buf, i, end_idx)) {
+ if ((t > 0) && (prev_pos == start_idx) &&
+ (!is_line_ending(buf, start_idx - 1, end_idx))) {
+ // first linebreak found in (chunk > 0), and a line before this
+ // linebreak belongs to previous chunk, so skip it.
+ prev_pos = i + 1;
+ continue;
+ } else {
+ LineInfo info;
+ info.pos = prev_pos;
+ info.len = i - prev_pos;
+
+ if (info.len > 0) {
+ line_infos[t].push_back(info);
+ }
+
+ prev_pos = i + 1;
+ }
+ }
+ }
+
+ // Find extra line which spand across chunk boundary.
+ if ((t < num_threads) && (buf[end_idx - 1] != '\n')) {
+ auto extra_span_idx = (std::min)(end_idx - 1 + chunk_size, len);
+ for (size_t i = end_idx; i < extra_span_idx; i++) {
+ if (is_line_ending(buf, i, extra_span_idx)) {
+ LineInfo info;
+ info.pos = prev_pos;
+ info.len = i - prev_pos;
+
+ if (info.len > 0) {
+ line_infos[t].push_back(info);
+ }
+
+ break;
+ }
+ }
+ }
+ }));
+ }
+
+ for (size_t t = 0; t < workers->size(); t++) {
+ workers[t].join();
+ }
+
+ auto end_time = std::chrono::high_resolution_clock::now();
+
+ ms_linedetection = end_time - start_time;
+ }
+
+ auto line_sum = 0;
+ for (size_t t = 0; t < num_threads; t++) {
+ // std::cout << t << ": # of lines = " << line_infos[t].size() << std::endl;
+ line_sum += line_infos[t].size();
+ }
+ // std::cout << "# of lines = " << line_sum << std::endl;
+
+ std::vector<Command> commands[kMaxThreads];
+
+ // 2. allocate buffer
+ auto t_alloc_start = std::chrono::high_resolution_clock::now();
+ {
+ for (size_t t = 0; t < num_threads; t++) {
+ commands[t].reserve(line_infos[t].size());
+ }
+ }
+
+ CommandCount command_count[kMaxThreads];
+ // Array index to `mtllib` line. According to wavefront .obj spec, `mtllib'
+ // should appear only once in .obj.
+ int mtllib_t_index = -1;
+ int mtllib_i_index = -1;
+
+ ms_alloc = std::chrono::high_resolution_clock::now() - t_alloc_start;
+
+ // 2. parse each line in parallel.
+ {
+ StackVector<std::thread, 16> workers;
+ auto t_start = std::chrono::high_resolution_clock::now();
+
+ for (size_t t = 0; t < num_threads; t++) {
+ workers->push_back(std::thread([&, t]() {
+
+ for (size_t i = 0; i < line_infos[t].size(); i++) {
+ Command command;
+ bool ret = parseLine(&command, &buf[line_infos[t][i].pos],
+ line_infos[t][i].len, option.triangulate);
+ if (ret) {
+ if (command.type == COMMAND_V) {
+ command_count[t].num_v++;
+ } else if (command.type == COMMAND_VN) {
+ command_count[t].num_vn++;
+ } else if (command.type == COMMAND_VT) {
+ command_count[t].num_vt++;
+ } else if (command.type == COMMAND_F) {
+ command_count[t].num_f += command.f.size();
+ command_count[t].num_indices += command.f_num_verts.size();
+ }
+
+ if (command.type == COMMAND_MTLLIB) {
+ mtllib_t_index = t;
+ mtllib_i_index = commands->size();
+ }
+
+ commands[t].emplace_back(std::move(command));
+ }
+ }
+
+ }));
+ }
+
+ for (size_t t = 0; t < workers->size(); t++) {
+ workers[t].join();
+ }
+
+ auto t_end = std::chrono::high_resolution_clock::now();
+
+ ms_parse = t_end - t_start;
+ }
+
+ std::map<std::string, int> material_map;
+
+ // Load material(if exits)
+ if (mtllib_i_index >= 0 && mtllib_t_index >= 0 &&
+ commands[mtllib_t_index][mtllib_i_index].mtllib_name &&
+ commands[mtllib_t_index][mtllib_i_index].mtllib_name_len > 0) {
+ std::string material_filename =
+ std::string(commands[mtllib_t_index][mtllib_i_index].mtllib_name,
+ commands[mtllib_t_index][mtllib_i_index].mtllib_name_len);
+ // std::cout << "mtllib :" << material_filename << std::endl;
+
+ auto t1 = std::chrono::high_resolution_clock::now();
+
+ std::ifstream ifs(material_filename);
+ if (ifs.good()) {
+ LoadMtl(&material_map, materials, &ifs);
+
+ // std::cout << "maetrials = " << materials.size() << std::endl;
+
+ ifs.close();
+ }
+
+ auto t2 = std::chrono::high_resolution_clock::now();
+
+ ms_load_mtl = t2 - t1;
+ }
+
+ auto command_sum = 0;
+ for (size_t t = 0; t < num_threads; t++) {
+ // std::cout << t << ": # of commands = " << commands[t].size() <<
+ // std::endl;
+ command_sum += commands[t].size();
+ }
+ // std::cout << "# of commands = " << command_sum << std::endl;
+
+ size_t num_v = 0;
+ size_t num_vn = 0;
+ size_t num_vt = 0;
+ size_t num_f = 0;
+ size_t num_indices = 0;
+ for (size_t t = 0; t < num_threads; t++) {
+ num_v += command_count[t].num_v;
+ num_vn += command_count[t].num_vn;
+ num_vt += command_count[t].num_vt;
+ num_f += command_count[t].num_f;
+ num_indices += command_count[t].num_indices;
+ }
+
+ // std::cout << "# v " << num_v << std::endl;
+ // std::cout << "# vn " << num_vn << std::endl;
+ // std::cout << "# vt " << num_vt << std::endl;
+ // std::cout << "# f " << num_f << std::endl;
+
+ // 4. merge
+ // @todo { parallelize merge. }
+ {
+ auto t_start = std::chrono::high_resolution_clock::now();
+
+ attrib->vertices.resize(num_v * 3);
+ attrib->normals.resize(num_vn * 3);
+ attrib->texcoords.resize(num_vt * 2);
+ attrib->indices.resize(num_f);
+ attrib->face_num_verts.resize(num_indices);
+ attrib->material_ids.resize(num_indices);
+
+ size_t v_offsets[kMaxThreads];
+ size_t n_offsets[kMaxThreads];
+ size_t t_offsets[kMaxThreads];
+ size_t f_offsets[kMaxThreads];
+ size_t face_offsets[kMaxThreads];
+
+ v_offsets[0] = 0;
+ n_offsets[0] = 0;
+ t_offsets[0] = 0;
+ f_offsets[0] = 0;
+ face_offsets[0] = 0;
+
+ for (size_t t = 1; t < num_threads; t++) {
+ v_offsets[t] = v_offsets[t - 1] + command_count[t - 1].num_v;
+ n_offsets[t] = n_offsets[t - 1] + command_count[t - 1].num_vn;
+ t_offsets[t] = t_offsets[t - 1] + command_count[t - 1].num_vt;
+ f_offsets[t] = f_offsets[t - 1] + command_count[t - 1].num_f;
+ face_offsets[t] = face_offsets[t - 1] + command_count[t - 1].num_indices;
+ }
+
+ StackVector<std::thread, 16> workers;
+
+ for (size_t t = 0; t < num_threads; t++) {
+ int material_id = -1; // -1 = default unknown material.
+ workers->push_back(std::thread([&, t]() {
+ size_t v_count = v_offsets[t];
+ size_t n_count = n_offsets[t];
+ size_t t_count = t_offsets[t];
+ size_t f_count = f_offsets[t];
+ size_t face_count = face_offsets[t];
+
+ for (size_t i = 0; i < commands[t].size(); i++) {
+ if (commands[t][i].type == COMMAND_EMPTY) {
+ continue;
+ } else if (commands[t][i].type == COMMAND_USEMTL) {
+ if (commands[t][i].material_name &&
+ commands[t][i].material_name_len > 0) {
+ std::string material_name(commands[t][i].material_name,
+ commands[t][i].material_name_len);
+
+ if (material_map.find(material_name) != material_map.end()) {
+ material_id = material_map[material_name];
+ } else {
+ // Assign invalid material ID
+ material_id = -1;
+ }
+ }
+ } else if (commands[t][i].type == COMMAND_V) {
+ attrib->vertices[3 * v_count + 0] = commands[t][i].vx;
+ attrib->vertices[3 * v_count + 1] = commands[t][i].vy;
+ attrib->vertices[3 * v_count + 2] = commands[t][i].vz;
+ v_count++;
+ } else if (commands[t][i].type == COMMAND_VN) {
+ attrib->normals[3 * n_count + 0] = commands[t][i].nx;
+ attrib->normals[3 * n_count + 1] = commands[t][i].ny;
+ attrib->normals[3 * n_count + 2] = commands[t][i].nz;
+ n_count++;
+ } else if (commands[t][i].type == COMMAND_VT) {
+ attrib->texcoords[2 * t_count + 0] = commands[t][i].tx;
+ attrib->texcoords[2 * t_count + 1] = commands[t][i].ty;
+ t_count++;
+ } else if (commands[t][i].type == COMMAND_F) {
+ for (size_t k = 0; k < commands[t][i].f.size(); k++) {
+ index_t &vi = commands[t][i].f[k];
+ int vertex_index = fixIndex(vi.vertex_index, v_count);
+ int texcoord_index = fixIndex(vi.texcoord_index, t_count);
+ int normal_index = fixIndex(vi.normal_index, n_count);
+ attrib->indices[f_count + k] =
+ index_t(vertex_index, texcoord_index, normal_index);
+ }
+ for (size_t k = 0; k < commands[t][i].f_num_verts.size(); k++) {
+ attrib->material_ids[face_count + k] = material_id;
+ attrib->face_num_verts[face_count + k] =
+ commands[t][i].f_num_verts[k];
+ }
+
+ f_count += commands[t][i].f.size();
+ face_count += commands[t][i].f_num_verts.size();
+ }
+ }
+ }));
+ }
+
+ for (size_t t = 0; t < workers->size(); t++) {
+ workers[t].join();
+ }
+
+ auto t_end = std::chrono::high_resolution_clock::now();
+ ms_merge = t_end - t_start;
+ }
+
+ auto t4 = std::chrono::high_resolution_clock::now();
+
+ // 5. Construct shape information.
+ {
+ auto t_start = std::chrono::high_resolution_clock::now();
+
+ // @todo { Can we boost the performance by multi-threaded execution? }
+ int face_count = 0;
+ shape_t shape;
+ shape.face_offset = 0;
+ shape.length = 0;
+ int face_prev_offset = 0;
+ for (size_t t = 0; t < num_threads; t++) {
+ for (size_t i = 0; i < commands[t].size(); i++) {
+ if (commands[t][i].type == COMMAND_O ||
+ commands[t][i].type == COMMAND_G) {
+ std::string name;
+ if (commands[t][i].type == COMMAND_O) {
+ name = std::string(commands[t][i].object_name,
+ commands[t][i].object_name_len);
+ } else {
+ name = std::string(commands[t][i].group_name,
+ commands[t][i].group_name_len);
+ }
+
+ if (face_count == 0) {
+ // 'o' or 'g' appears before any 'f'
+ shape.name = name;
+ shape.face_offset = face_count;
+ face_prev_offset = face_count;
+ } else {
+ if (shapes->size() == 0) {
+ // 'o' or 'g' after some 'v' lines.
+ // create a shape with null name
+ shape.length = face_count - face_prev_offset;
+ face_prev_offset = face_count;
+
+ shapes->push_back(shape);
+
+ } else {
+ if ((face_count - face_prev_offset) > 0) {
+ // push previous shape
+ shape.length = face_count - face_prev_offset;
+ shapes->push_back(shape);
+ face_prev_offset = face_count;
+ }
+ }
+
+ // redefine shape.
+ shape.name = name;
+ shape.face_offset = face_count;
+ shape.length = 0;
+ }
+ }
+ if (commands[t][i].type == COMMAND_F) {
+ face_count++;
+ }
+ }
+ }
+
+ if ((face_count - face_prev_offset) > 0) {
+ shape.length = face_count - shape.face_offset;
+ if (shape.length > 0) {
+ shapes->push_back(shape);
+ }
+ } else {
+ // Guess no 'v' line occurrence after 'o' or 'g', so discards current
+ // shape information.
+ }
+
+ auto t_end = std::chrono::high_resolution_clock::now();
+
+ ms_construct = t_end - t_start;
+ }
+
+ std::chrono::duration<double, std::milli> ms_total = t4 - t1;
+ if (option.verbose) {
+ std::cout << "total parsing time: " << ms_total.count() << " ms\n";
+ std::cout << " line detection : " << ms_linedetection.count() << " ms\n";
+ std::cout << " alloc buf : " << ms_alloc.count() << " ms\n";
+ std::cout << " parse : " << ms_parse.count() << " ms\n";
+ std::cout << " merge : " << ms_merge.count() << " ms\n";
+ std::cout << " construct : " << ms_construct.count() << " ms\n";
+ std::cout << " mtl load : " << ms_load_mtl.count() << " ms\n";
+ std::cout << "# of vertices = " << attrib->vertices.size() << std::endl;
+ std::cout << "# of normals = " << attrib->normals.size() << std::endl;
+ std::cout << "# of texcoords = " << attrib->texcoords.size() << std::endl;
+ std::cout << "# of face indices = " << attrib->indices.size() << std::endl;
+ std::cout << "# of indices = " << attrib->material_ids.size() << std::endl;
+ std::cout << "# of shapes = " << shapes->size() << std::endl;
+ }
+
+ return true;
+}
+
+} // namespace tinyobj_opt
+
+#endif // TINYOBJ_LOADER_OPT_IMPLEMENTATION
diff --git a/experimental/trackball.cc b/experimental/trackball.cc
new file mode 100644
index 0000000..27642e8
--- /dev/null
+++ b/experimental/trackball.cc
@@ -0,0 +1,292 @@
+/*
+ * (c) Copyright 1993, 1994, Silicon Graphics, Inc.
+ * ALL RIGHTS RESERVED
+ * Permission to use, copy, modify, and distribute this software for
+ * any purpose and without fee is hereby granted, provided that the above
+ * copyright notice appear in all copies and that both the copyright notice
+ * and this permission notice appear in supporting documentation, and that
+ * the name of Silicon Graphics, Inc. not be used in advertising
+ * or publicity pertaining to distribution of the software without specific,
+ * written prior permission.
+ *
+ * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
+ * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
+ * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
+ * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
+ * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
+ * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
+ * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
+ * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN
+ * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
+ * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * US Government Users Restricted Rights
+ * Use, duplication, or disclosure by the Government is subject to
+ * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
+ * (c)(1)(ii) of the Rights in Technical Data and Computer Software
+ * clause at DFARS 252.227-7013 and/or in similar or successor
+ * clauses in the FAR or the DOD or NASA FAR Supplement.
+ * Unpublished-- rights reserved under the copyright laws of the
+ * United States. Contractor/manufacturer is Silicon Graphics,
+ * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311.
+ *
+ * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
+ */
+/*
+ * Trackball code:
+ *
+ * Implementation of a virtual trackball.
+ * Implemented by Gavin Bell, lots of ideas from Thant Tessman and
+ * the August '88 issue of Siggraph's "Computer Graphics," pp. 121-129.
+ *
+ * Vector manip code:
+ *
+ * Original code from:
+ * David M. Ciemiewicz, Mark Grossman, Henry Moreton, and Paul Haeberli
+ *
+ * Much mucking with by:
+ * Gavin Bell
+ */
+#include <math.h>
+#include "trackball.h"
+
+/*
+ * This size should really be based on the distance from the center of
+ * rotation to the point on the object underneath the mouse. That
+ * point would then track the mouse as closely as possible. This is a
+ * simple example, though, so that is left as an Exercise for the
+ * Programmer.
+ */
+#define TRACKBALLSIZE (0.8)
+
+/*
+ * Local function prototypes (not defined in trackball.h)
+ */
+static float tb_project_to_sphere(float, float, float);
+static void normalize_quat(float[4]);
+
+static void vzero(float *v) {
+ v[0] = 0.0;
+ v[1] = 0.0;
+ v[2] = 0.0;
+}
+
+static void vset(float *v, float x, float y, float z) {
+ v[0] = x;
+ v[1] = y;
+ v[2] = z;
+}
+
+static void vsub(const float *src1, const float *src2, float *dst) {
+ dst[0] = src1[0] - src2[0];
+ dst[1] = src1[1] - src2[1];
+ dst[2] = src1[2] - src2[2];
+}
+
+static void vcopy(const float *v1, float *v2) {
+ int i;
+ for (i = 0; i < 3; i++)
+ v2[i] = v1[i];
+}
+
+static void vcross(const float *v1, const float *v2, float *cross) {
+ float temp[3];
+
+ temp[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]);
+ temp[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]);
+ temp[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]);
+ vcopy(temp, cross);
+}
+
+static float vlength(const float *v) {
+ return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+}
+
+static void vscale(float *v, float div) {
+ v[0] *= div;
+ v[1] *= div;
+ v[2] *= div;
+}
+
+static void vnormal(float *v) { vscale(v, 1.0 / vlength(v)); }
+
+static float vdot(const float *v1, const float *v2) {
+ return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
+}
+
+static void vadd(const float *src1, const float *src2, float *dst) {
+ dst[0] = src1[0] + src2[0];
+ dst[1] = src1[1] + src2[1];
+ dst[2] = src1[2] + src2[2];
+}
+
+/*
+ * Ok, simulate a track-ball. Project the points onto the virtual
+ * trackball, then figure out the axis of rotation, which is the cross
+ * product of P1 P2 and O P1 (O is the center of the ball, 0,0,0)
+ * Note: This is a deformed trackball-- is a trackball in the center,
+ * but is deformed into a hyperbolic sheet of rotation away from the
+ * center. This particular function was chosen after trying out
+ * several variations.
+ *
+ * It is assumed that the arguments to this routine are in the range
+ * (-1.0 ... 1.0)
+ */
+void trackball(float q[4], float p1x, float p1y, float p2x, float p2y) {
+ float a[3]; /* Axis of rotation */
+ float phi; /* how much to rotate about axis */
+ float p1[3], p2[3], d[3];
+ float t;
+
+ if (p1x == p2x && p1y == p2y) {
+ /* Zero rotation */
+ vzero(q);
+ q[3] = 1.0;
+ return;
+ }
+
+ /*
+ * First, figure out z-coordinates for projection of P1 and P2 to
+ * deformed sphere
+ */
+ vset(p1, p1x, p1y, tb_project_to_sphere(TRACKBALLSIZE, p1x, p1y));
+ vset(p2, p2x, p2y, tb_project_to_sphere(TRACKBALLSIZE, p2x, p2y));
+
+ /*
+ * Now, we want the cross product of P1 and P2
+ */
+ vcross(p2, p1, a);
+
+ /*
+ * Figure out how much to rotate around that axis.
+ */
+ vsub(p1, p2, d);
+ t = vlength(d) / (2.0 * TRACKBALLSIZE);
+
+ /*
+ * Avoid problems with out-of-control values...
+ */
+ if (t > 1.0)
+ t = 1.0;
+ if (t < -1.0)
+ t = -1.0;
+ phi = 2.0 * asin(t);
+
+ axis_to_quat(a, phi, q);
+}
+
+/*
+ * Given an axis and angle, compute quaternion.
+ */
+void axis_to_quat(float a[3], float phi, float q[4]) {
+ vnormal(a);
+ vcopy(a, q);
+ vscale(q, sin(phi / 2.0));
+ q[3] = cos(phi / 2.0);
+}
+
+/*
+ * Project an x,y pair onto a sphere of radius r OR a hyperbolic sheet
+ * if we are away from the center of the sphere.
+ */
+static float tb_project_to_sphere(float r, float x, float y) {
+ float d, t, z;
+
+ d = sqrt(x * x + y * y);
+ if (d < r * 0.70710678118654752440) { /* Inside sphere */
+ z = sqrt(r * r - d * d);
+ } else { /* On hyperbola */
+ t = r / 1.41421356237309504880;
+ z = t * t / d;
+ }
+ return z;
+}
+
+/*
+ * Given two rotations, e1 and e2, expressed as quaternion rotations,
+ * figure out the equivalent single rotation and stuff it into dest.
+ *
+ * This routine also normalizes the result every RENORMCOUNT times it is
+ * called, to keep error from creeping in.
+ *
+ * NOTE: This routine is written so that q1 or q2 may be the same
+ * as dest (or each other).
+ */
+
+#define RENORMCOUNT 97
+
+void add_quats(float q1[4], float q2[4], float dest[4]) {
+ static int count = 0;
+ float t1[4], t2[4], t3[4];
+ float tf[4];
+
+ vcopy(q1, t1);
+ vscale(t1, q2[3]);
+
+ vcopy(q2, t2);
+ vscale(t2, q1[3]);
+
+ vcross(q2, q1, t3);
+ vadd(t1, t2, tf);
+ vadd(t3, tf, tf);
+ tf[3] = q1[3] * q2[3] - vdot(q1, q2);
+
+ dest[0] = tf[0];
+ dest[1] = tf[1];
+ dest[2] = tf[2];
+ dest[3] = tf[3];
+
+ if (++count > RENORMCOUNT) {
+ count = 0;
+ normalize_quat(dest);
+ }
+}
+
+/*
+ * Quaternions always obey: a^2 + b^2 + c^2 + d^2 = 1.0
+ * If they don't add up to 1.0, dividing by their magnitued will
+ * renormalize them.
+ *
+ * Note: See the following for more information on quaternions:
+ *
+ * - Shoemake, K., Animating rotation with quaternion curves, Computer
+ * Graphics 19, No 3 (Proc. SIGGRAPH'85), 245-254, 1985.
+ * - Pletinckx, D., Quaternion calculus as a basic tool in computer
+ * graphics, The Visual Computer 5, 2-13, 1989.
+ */
+static void normalize_quat(float q[4]) {
+ int i;
+ float mag;
+
+ mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
+ for (i = 0; i < 4; i++)
+ q[i] /= mag;
+}
+
+/*
+ * Build a rotation matrix, given a quaternion rotation.
+ *
+ */
+void build_rotmatrix(float m[4][4], const float q[4]) {
+ m[0][0] = 1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]);
+ m[0][1] = 2.0 * (q[0] * q[1] - q[2] * q[3]);
+ m[0][2] = 2.0 * (q[2] * q[0] + q[1] * q[3]);
+ m[0][3] = 0.0;
+
+ m[1][0] = 2.0 * (q[0] * q[1] + q[2] * q[3]);
+ m[1][1] = 1.0 - 2.0 * (q[2] * q[2] + q[0] * q[0]);
+ m[1][2] = 2.0 * (q[1] * q[2] - q[0] * q[3]);
+ m[1][3] = 0.0;
+
+ m[2][0] = 2.0 * (q[2] * q[0] - q[1] * q[3]);
+ m[2][1] = 2.0 * (q[1] * q[2] + q[0] * q[3]);
+ m[2][2] = 1.0 - 2.0 * (q[1] * q[1] + q[0] * q[0]);
+ m[2][3] = 0.0;
+
+ m[3][0] = 0.0;
+ m[3][1] = 0.0;
+ m[3][2] = 0.0;
+ m[3][3] = 1.0;
+}
diff --git a/experimental/trackball.h b/experimental/trackball.h
new file mode 100644
index 0000000..b1f9437
--- /dev/null
+++ b/experimental/trackball.h
@@ -0,0 +1,75 @@
+/*
+ * (c) Copyright 1993, 1994, Silicon Graphics, Inc.
+ * ALL RIGHTS RESERVED
+ * Permission to use, copy, modify, and distribute this software for
+ * any purpose and without fee is hereby granted, provided that the above
+ * copyright notice appear in all copies and that both the copyright notice
+ * and this permission notice appear in supporting documentation, and that
+ * the name of Silicon Graphics, Inc. not be used in advertising
+ * or publicity pertaining to distribution of the software without specific,
+ * written prior permission.
+ *
+ * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
+ * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
+ * FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SILICON
+ * GRAPHICS, INC. BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
+ * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
+ * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
+ * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
+ * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC. HAS BEEN
+ * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
+ * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * US Government Users Restricted Rights
+ * Use, duplication, or disclosure by the Government is subject to
+ * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
+ * (c)(1)(ii) of the Rights in Technical Data and Computer Software
+ * clause at DFARS 252.227-7013 and/or in similar or successor
+ * clauses in the FAR or the DOD or NASA FAR Supplement.
+ * Unpublished-- rights reserved under the copyright laws of the
+ * United States. Contractor/manufacturer is Silicon Graphics,
+ * Inc., 2011 N. Shoreline Blvd., Mountain View, CA 94039-7311.
+ *
+ * OpenGL(TM) is a trademark of Silicon Graphics, Inc.
+ */
+/*
+ * trackball.h
+ * A virtual trackball implementation
+ * Written by Gavin Bell for Silicon Graphics, November 1988.
+ */
+
+/*
+ * Pass the x and y coordinates of the last and current positions of
+ * the mouse, scaled so they are from (-1.0 ... 1.0).
+ *
+ * The resulting rotation is returned as a quaternion rotation in the
+ * first paramater.
+ */
+void trackball(float q[4], float p1x, float p1y, float p2x, float p2y);
+
+void negate_quat(float *q, float *qn);
+
+/*
+ * Given two quaternions, add them together to get a third quaternion.
+ * Adding quaternions to get a compound rotation is analagous to adding
+ * translations to get a compound translation. When incrementally
+ * adding rotations, the first argument here should be the new
+ * rotation, the second and third the total rotation (which will be
+ * over-written with the resulting new total rotation).
+ */
+void add_quats(float *q1, float *q2, float *dest);
+
+/*
+ * A useful function, builds a rotation matrix in Matrix based on
+ * given quaternion.
+ */
+void build_rotmatrix(float m[4][4], const float q[4]);
+
+/*
+ * This function computes a quaternion based on an axis (defined by
+ * the given vector) and an angle about which to rotate. The angle is
+ * expressed in radians. The result is put into the third argument.
+ */
+void axis_to_quat(float a[3], float phi, float q[4]);
diff --git a/experimental/viewer.cc b/experimental/viewer.cc
new file mode 100644
index 0000000..be5053f
--- /dev/null
+++ b/experimental/viewer.cc
@@ -0,0 +1,748 @@
+//
+// Simple .obj viewer(vertex only)
+//
+#include <vector>
+#include <string>
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+#include <limits>
+#include <cmath>
+#include <cassert>
+#include <cstring>
+#include <algorithm>
+
+#if defined(ENABLE_ZLIB)
+#include <zlib.h>
+#endif
+
+#if defined(ENABLE_ZSTD)
+#include <zstd.h>
+#endif
+
+#include <GL/glew.h>
+
+#ifdef __APPLE__
+#include <OpenGL/glu.h>
+#else
+#include <GL/glu.h>
+#endif
+
+#include <GLFW/glfw3.h>
+
+#include "trackball.h"
+
+#define TINYOBJ_LOADER_OPT_IMPLEMENTATION
+#include "tinyobj_loader_opt.h"
+
+typedef struct {
+ GLuint vb; // vertex buffer
+ int numTriangles;
+} DrawObject;
+
+std::vector<DrawObject> gDrawObjects;
+
+int width = 768;
+int height = 768;
+
+double prevMouseX, prevMouseY;
+bool mouseLeftPressed;
+bool mouseMiddlePressed;
+bool mouseRightPressed;
+float curr_quat[4];
+float prev_quat[4];
+float eye[3], lookat[3], up[3];
+
+GLFWwindow* window;
+
+void CheckErrors(std::string desc) {
+ GLenum e = glGetError();
+ if (e != GL_NO_ERROR) {
+ fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e);
+ exit(20);
+ }
+}
+
+void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) {
+ float v10[3];
+ v10[0] = v1[0] - v0[0];
+ v10[1] = v1[1] - v0[1];
+ v10[2] = v1[2] - v0[2];
+
+ float v20[3];
+ v20[0] = v2[0] - v0[0];
+ v20[1] = v2[1] - v0[1];
+ v20[2] = v2[2] - v0[2];
+
+ N[0] = v20[1] * v10[2] - v20[2] * v10[1];
+ N[1] = v20[2] * v10[0] - v20[0] * v10[2];
+ N[2] = v20[0] * v10[1] - v20[1] * v10[0];
+
+ float len2 = N[0] * N[0] + N[1] * N[1] + N[2] * N[2];
+ if (len2 > 0.0f) {
+ float len = sqrtf(len2);
+
+ N[0] /= len;
+ N[1] /= len;
+ }
+}
+
+const char *mmap_file(size_t *len, const char* filename)
+{
+ (*len) = 0;
+#ifdef _WIN32
+ HANDLE file = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
+ assert(file != INVALID_HANDLE_VALUE);
+
+ HANDLE fileMapping = CreateFileMapping(file, NULL, PAGE_READONLY, 0, 0, NULL);
+ assert(fileMapping != INVALID_HANDLE_VALUE);
+
+ LPVOID fileMapView = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, 0);
+ auto fileMapViewChar = (const char*)fileMapView;
+ assert(fileMapView != NULL);
+
+ LARGE_INTEGER fileSize;
+ fileSize.QuadPart = 0;
+ GetFileSizeEx(file, &fileSize);
+
+ (*len) = static_cast<size_t>(fileSize.QuadPart);
+ return fileMapViewChar;
+
+#else
+
+ FILE* f = fopen(filename, "rb" );
+ if (!f) {
+ fprintf(stderr, "Failed to open file : %s\n", filename);
+ return nullptr;
+ }
+ fseek(f, 0, SEEK_END);
+ long fileSize = ftell(f);
+ fclose(f);
+
+ if (fileSize < 16) {
+ fprintf(stderr, "Empty or invalid .obj : %s\n", filename);
+ return nullptr;
+ }
+
+ struct stat sb;
+ char *p;
+ int fd;
+
+ fd = open (filename, O_RDONLY);
+ if (fd == -1) {
+ perror ("open");
+ return nullptr;
+ }
+
+ if (fstat (fd, &sb) == -1) {
+ perror ("fstat");
+ return nullptr;
+ }
+
+ if (!S_ISREG (sb.st_mode)) {
+ fprintf (stderr, "%s is not a file\n", "lineitem.tbl");
+ return nullptr;
+ }
+
+ p = (char*)mmap (0, fileSize, PROT_READ, MAP_SHARED, fd, 0);
+
+ if (p == MAP_FAILED) {
+ perror ("mmap");
+ return nullptr;
+ }
+
+ if (close (fd) == -1) {
+ perror ("close");
+ return nullptr;
+ }
+
+ (*len) = fileSize;
+
+ return p;
+
+#endif
+}
+
+bool gz_load(std::vector<char>* buf, const char* filename)
+{
+#ifdef ENABLE_ZLIB
+ gzFile file;
+ file = gzopen (filename, "r");
+ if (! file) {
+ fprintf (stderr, "gzopen of '%s' failed: %s.\n", filename,
+ strerror (errno));
+ exit (EXIT_FAILURE);
+ return false;
+ }
+ while (1) {
+ int err;
+ int bytes_read;
+ unsigned char buffer[1024];
+ bytes_read = gzread (file, buffer, 1024);
+ buf->insert(buf->end(), buffer, buffer + 1024);
+ //printf ("%s", buffer);
+ if (bytes_read < 1024) {
+ if (gzeof (file)) {
+ break;
+ }
+ else {
+ const char * error_string;
+ error_string = gzerror (file, & err);
+ if (err) {
+ fprintf (stderr, "Error: %s.\n", error_string);
+ exit (EXIT_FAILURE);
+ return false;
+ }
+ }
+ }
+ }
+ gzclose (file);
+ return true;
+#else
+ return false;
+#endif
+}
+
+#ifdef ENABLE_ZSTD
+static off_t fsize_X(const char *filename)
+{
+ struct stat st;
+ if (stat(filename, &st) == 0) return st.st_size;
+ /* error */
+ printf("stat: %s : %s \n", filename, strerror(errno));
+ exit(1);
+}
+
+static FILE* fopen_X(const char *filename, const char *instruction)
+{
+ FILE* const inFile = fopen(filename, instruction);
+ if (inFile) return inFile;
+ /* error */
+ printf("fopen: %s : %s \n", filename, strerror(errno));
+ exit(2);
+}
+
+static void* malloc_X(size_t size)
+{
+ void* const buff = malloc(size);
+ if (buff) return buff;
+ /* error */
+ printf("malloc: %s \n", strerror(errno));
+ exit(3);
+}
+#endif
+
+bool zstd_load(std::vector<char>* buf, const char* filename)
+{
+#ifdef ENABLE_ZSTD
+ off_t const buffSize = fsize_X(filename);
+ FILE* const inFile = fopen_X(filename, "rb");
+ void* const buffer = malloc_X(buffSize);
+ size_t const readSize = fread(buffer, 1, buffSize, inFile);
+ if (readSize != (size_t)buffSize) {
+ printf("fread: %s : %s \n", filename, strerror(errno));
+ exit(4);
+ }
+ fclose(inFile);
+
+ unsigned long long const rSize = ZSTD_getDecompressedSize(buffer, buffSize);
+ if (rSize==0) {
+ printf("%s : original size unknown \n", filename);
+ exit(5);
+ }
+
+ buf->resize(rSize);
+
+ size_t const dSize = ZSTD_decompress(buf->data(), rSize, buffer, buffSize);
+
+ if (dSize != rSize) {
+ printf("error decoding %s : %s \n", filename, ZSTD_getErrorName(dSize));
+ exit(7);
+ }
+
+ free(buffer);
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+const char* get_file_data(size_t *len, const char* filename)
+{
+
+ const char *ext = strrchr(filename, '.');
+
+ size_t data_len = 0;
+ const char* data = nullptr;
+
+ if (strcmp(ext, ".gz") == 0) {
+ // gzipped data.
+
+ std::vector<char> buf;
+ bool ret = gz_load(&buf, filename);
+
+ if (ret) {
+ char *p = static_cast<char*>(malloc(buf.size() + 1)); // @fixme { implement deleter }
+ memcpy(p, &buf.at(0), buf.size());
+ p[buf.size()] = '\0';
+ data = p;
+ data_len = buf.size();
+ }
+
+ } else if (strcmp(ext, ".zst") == 0) {
+ printf("zstd\n");
+ // Zstandard data.
+
+ std::vector<char> buf;
+ bool ret = zstd_load(&buf, filename);
+
+ if (ret) {
+ char *p = static_cast<char*>(malloc(buf.size() + 1)); // @fixme { implement deleter }
+ memcpy(p, &buf.at(0), buf.size());
+ p[buf.size()] = '\0';
+ data = p;
+ data_len = buf.size();
+ }
+ } else {
+
+ data = mmap_file(&data_len, filename);
+
+ }
+
+ (*len) = data_len;
+ return data;
+}
+
+
+bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int num_threads, bool verbose)
+{
+ tinyobj_opt::attrib_t attrib;
+ std::vector<tinyobj_opt::shape_t> shapes;
+ std::vector<tinyobj_opt::material_t> materials;
+
+ auto load_t_begin = std::chrono::high_resolution_clock::now();
+ size_t data_len = 0;
+ const char* data = get_file_data(&data_len, filename);
+ if (data == nullptr) {
+ printf("failed to load file\n");
+ exit(-1);
+ return false;
+ }
+ auto load_t_end = std::chrono::high_resolution_clock::now();
+ std::chrono::duration<double, std::milli> load_ms = load_t_end - load_t_begin;
+ if (verbose) {
+ std::cout << "filesize: " << data_len << std::endl;
+ std::cout << "load time: " << load_ms.count() << " [msecs]" << std::endl;
+ }
+
+
+ tinyobj_opt::LoadOption option;
+ option.req_num_threads = num_threads;
+ option.verbose = verbose;
+ bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option);
+
+ if (!ret) {
+ std::cerr << "Failed to parse .obj" << std::endl;
+ return false;
+ }
+ bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
+ bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
+
+ //std::cout << "vertices.size() = " << attrib.vertices.size() << std::endl;
+ //std::cout << "normals.size() = " << attrib.normals.size() << std::endl;
+
+ {
+ DrawObject o;
+ std::vector<float> vb; // pos(3float), normal(3float), color(3float)
+ size_t face_offset = 0;
+ for (size_t v = 0; v < attrib.face_num_verts.size(); v++) {
+ assert(attrib.face_num_verts[v] % 3 == 0); // assume all triangle face.
+ for (size_t f = 0; f < attrib.face_num_verts[v] / 3; f++) {
+ tinyobj_opt::index_t idx0 = attrib.indices[face_offset+3*f+0];
+ tinyobj_opt::index_t idx1 = attrib.indices[face_offset+3*f+1];
+ tinyobj_opt::index_t idx2 = attrib.indices[face_offset+3*f+2];
+
+ float v[3][3];
+ for (int k = 0; k < 3; k++) {
+ int f0 = idx0.vertex_index;
+ int f1 = idx1.vertex_index;
+ int f2 = idx2.vertex_index;
+ assert(f0 >= 0);
+ assert(f1 >= 0);
+ assert(f2 >= 0);
+
+ v[0][k] = attrib.vertices[3*f0+k];
+ v[1][k] = attrib.vertices[3*f1+k];
+ v[2][k] = attrib.vertices[3*f2+k];
+ bmin[k] = std::min(v[0][k], bmin[k]);
+ bmin[k] = std::min(v[1][k], bmin[k]);
+ bmin[k] = std::min(v[2][k], bmin[k]);
+ bmax[k] = std::max(v[0][k], bmax[k]);
+ bmax[k] = std::max(v[1][k], bmax[k]);
+ bmax[k] = std::max(v[2][k], bmax[k]);
+ }
+
+ float n[3][3];
+
+ if (attrib.normals.size() > 0) {
+ int nf0 = idx0.normal_index;
+ int nf1 = idx1.normal_index;
+ int nf2 = idx2.normal_index;
+
+ if (nf0 >= 0 && nf1 >= 0 && nf2 >= 0) {
+ assert(3*nf0+2 < attrib.normals.size());
+ assert(3*nf1+2 < attrib.normals.size());
+ assert(3*nf2+2 < attrib.normals.size());
+ for (int k = 0; k < 3; k++) {
+ n[0][k] = attrib.normals[3*nf0+k];
+ n[1][k] = attrib.normals[3*nf1+k];
+ n[2][k] = attrib.normals[3*nf2+k];
+ }
+ } else {
+ // compute geometric normal
+ CalcNormal(n[0], v[0], v[1], v[2]);
+ n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2];
+ n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2];
+ }
+ } else {
+ // compute geometric normal
+ CalcNormal(n[0], v[0], v[1], v[2]);
+ n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2];
+ n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2];
+ }
+
+ for (int k = 0; k < 3; k++) {
+ vb.push_back(v[k][0]);
+ vb.push_back(v[k][1]);
+ vb.push_back(v[k][2]);
+ vb.push_back(n[k][0]);
+ vb.push_back(n[k][1]);
+ vb.push_back(n[k][2]);
+ // Use normal as color.
+ float c[3] = {n[k][0], n[k][1], n[k][2]};
+ float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2];
+ if (len2 > 1.0e-6f) {
+ float len = sqrtf(len2);
+
+ c[0] /= len;
+ c[1] /= len;
+ c[2] /= len;
+ }
+ vb.push_back(c[0] * 0.5 + 0.5);
+ vb.push_back(c[1] * 0.5 + 0.5);
+ vb.push_back(c[2] * 0.5 + 0.5);
+ }
+ }
+ face_offset += attrib.face_num_verts[v];
+ }
+
+ o.vb = 0;
+ o.numTriangles = 0;
+ if (vb.size() > 0) {
+ glGenBuffers(1, &o.vb);
+ glBindBuffer(GL_ARRAY_BUFFER, o.vb);
+ glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), GL_STATIC_DRAW);
+ o.numTriangles = vb.size() / 9 / 3;
+ }
+
+ gDrawObjects.push_back(o);
+ }
+
+ printf("bmin = %f, %f, %f\n", bmin[0], bmin[1], bmin[2]);
+ printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]);
+
+ return true;
+}
+
+void reshapeFunc(GLFWwindow* window, int w, int h)
+{
+ (void)window;
+ // for retinal display.
+ int fb_w, fb_h;
+ glfwGetFramebufferSize(window, &fb_w, &fb_h);
+
+ glViewport(0, 0, fb_w, fb_h);
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ gluPerspective(45.0, (float)w / (float)h, 0.01f, 100.0f);
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ width = w;
+ height = h;
+}
+
+void keyboardFunc(GLFWwindow *window, int key, int scancode, int action, int mods) {
+ (void)window;
+ (void)scancode;
+ (void)mods;
+ if(action == GLFW_PRESS || action == GLFW_REPEAT){
+ // Move camera
+ float mv_x = 0, mv_y = 0, mv_z = 0;
+ if(key == GLFW_KEY_K) mv_x += 1;
+ else if(key == GLFW_KEY_J) mv_x += -1;
+ else if(key == GLFW_KEY_L) mv_y += 1;
+ else if(key == GLFW_KEY_H) mv_y += -1;
+ else if(key == GLFW_KEY_P) mv_z += 1;
+ else if(key == GLFW_KEY_N) mv_z += -1;
+ //camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05);
+ // Close window
+ if(key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) glfwSetWindowShouldClose(window, GL_TRUE);
+
+ //init_frame = true;
+ }
+}
+
+void clickFunc(GLFWwindow* window, int button, int action, int mods){
+ (void)window;
+ (void)mods;
+ if(button == GLFW_MOUSE_BUTTON_LEFT){
+ if(action == GLFW_PRESS){
+ mouseLeftPressed = true;
+ trackball(prev_quat, 0.0, 0.0, 0.0, 0.0);
+ } else if(action == GLFW_RELEASE){
+ mouseLeftPressed = false;
+ }
+ }
+ if(button == GLFW_MOUSE_BUTTON_RIGHT){
+ if(action == GLFW_PRESS){
+ mouseRightPressed = true;
+ } else if(action == GLFW_RELEASE){
+ mouseRightPressed = false;
+ }
+ }
+ if(button == GLFW_MOUSE_BUTTON_MIDDLE){
+ if(action == GLFW_PRESS){
+ mouseMiddlePressed = true;
+ } else if(action == GLFW_RELEASE){
+ mouseMiddlePressed = false;
+ }
+ }
+}
+
+void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y){
+ (void)window;
+ float rotScale = 1.0f;
+ float transScale = 2.0f;
+
+ if(mouseLeftPressed){
+ trackball(prev_quat,
+ rotScale * (2.0f * prevMouseX - width) / (float)width,
+ rotScale * (height - 2.0f * prevMouseY) / (float)height,
+ rotScale * (2.0f * mouse_x - width) / (float)width,
+ rotScale * (height - 2.0f * mouse_y) / (float)height);
+
+ add_quats(prev_quat, curr_quat, curr_quat);
+ } else if (mouseMiddlePressed) {
+ eye[0] -= transScale * (mouse_x - prevMouseX) / (float)width;
+ lookat[0] -= transScale * (mouse_x - prevMouseX) / (float)width;
+ eye[1] += transScale * (mouse_y - prevMouseY) / (float)height;
+ lookat[1] += transScale * (mouse_y - prevMouseY) / (float)height;
+ } else if (mouseRightPressed) {
+ eye[2] += transScale * (mouse_y - prevMouseY) / (float)height;
+ lookat[2] += transScale * (mouse_y - prevMouseY) / (float)height;
+ }
+
+ // Update mouse point
+ prevMouseX = mouse_x;
+ prevMouseY = mouse_y;
+}
+
+void Draw(const std::vector<DrawObject>& drawObjects)
+{
+ glPolygonMode(GL_FRONT, GL_FILL);
+ glPolygonMode(GL_BACK, GL_FILL);
+
+ glEnable(GL_POLYGON_OFFSET_FILL);
+ glPolygonOffset(1.0, 1.0);
+ glColor3f(1.0f, 1.0f, 1.0f);
+ for (size_t i = 0; i < drawObjects.size(); i++) {
+ DrawObject o = drawObjects[i];
+ if (o.vb < 1) {
+ continue;
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, o.vb);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_NORMAL_ARRAY);
+ glEnableClientState(GL_COLOR_ARRAY);
+ glVertexPointer(3, GL_FLOAT, 36, (const void*)0);
+ glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3));
+ glColorPointer(3, GL_FLOAT, 36, (const void*)(sizeof(float)*6));
+
+ glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles);
+ CheckErrors("drawarrays");
+ }
+
+ // draw wireframe
+ glDisable(GL_POLYGON_OFFSET_FILL);
+ glPolygonMode(GL_FRONT, GL_LINE);
+ glPolygonMode(GL_BACK, GL_LINE);
+
+ glColor3f(0.0f, 0.0f, 0.4f);
+ for (size_t i = 0; i < drawObjects.size(); i++) {
+ DrawObject o = drawObjects[i];
+ if (o.vb < 1) {
+ continue;
+ }
+
+ glBindBuffer(GL_ARRAY_BUFFER, o.vb);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glEnableClientState(GL_NORMAL_ARRAY);
+ glDisableClientState(GL_COLOR_ARRAY);
+ glVertexPointer(3, GL_FLOAT, 36, (const void*)0);
+ glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float)*3));
+
+ glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles);
+ CheckErrors("drawarrays");
+ }
+}
+
+static void Init() {
+ trackball(curr_quat, 0, 0, 0, 0);
+
+ eye[0] = 0.0f;
+ eye[1] = 0.0f;
+ eye[2] = 3.0f;
+
+ lookat[0] = 0.0f;
+ lookat[1] = 0.0f;
+ lookat[2] = 0.0f;
+
+ up[0] = 0.0f;
+ up[1] = 1.0f;
+ up[2] = 0.0f;
+}
+
+
+int main(int argc, char **argv)
+{
+ if (argc < 2) {
+ std::cout << "view input.obj <num_threads> <benchark_only> <verbose>" << std::endl;
+ return 0;
+ }
+
+ bool benchmark_only = false;
+ int num_threads = -1;
+ bool verbose = false;
+
+ if (argc > 2) {
+ num_threads = atoi(argv[2]);
+ }
+
+ if (argc > 3) {
+ benchmark_only = (atoi(argv[3]) > 0) ? true : false;
+ }
+
+ if (argc > 4) {
+ verbose = true;
+ }
+
+ if (benchmark_only) {
+
+ tinyobj_opt::attrib_t attrib;
+ std::vector<tinyobj_opt::shape_t> shapes;
+ std::vector<tinyobj_opt::material_t> materials;
+
+ size_t data_len = 0;
+ const char* data = get_file_data(&data_len, argv[1]);
+ if (data == nullptr) {
+ printf("failed to load file\n");
+ exit(-1);
+ return false;
+ }
+
+ if (data_len < 4) {
+ printf("Empty file\n");
+ exit(-1);
+ return false;
+ }
+ printf("filesize: %d\n", (int)data_len);
+ tinyobj_opt::LoadOption option;
+ option.req_num_threads = num_threads;
+ option.verbose = true;
+
+ bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option);
+
+ return ret;
+ }
+
+ Init();
+
+ std::cout << "Initialize GLFW..." << std::endl;
+
+ if(!glfwInit()){
+ std::cerr << "Failed to initialize GLFW." << std::endl;
+ return -1;
+ }
+
+ std::cout << "GLFW OK." << std::endl;
+
+
+ window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL);
+ if(window == NULL){
+ std::cerr << "Failed to open GLFW window. " << std::endl;
+ glfwTerminate();
+ return 1;
+ }
+
+ glfwMakeContextCurrent(window);
+ glfwSwapInterval(1);
+
+ // Callback
+ glfwSetWindowSizeCallback(window, reshapeFunc);
+ glfwSetKeyCallback(window, keyboardFunc);
+ glfwSetMouseButtonCallback(window, clickFunc);
+ glfwSetCursorPosCallback(window, motionFunc);
+
+ glewExperimental = true;
+ if (glewInit() != GLEW_OK) {
+ std::cerr << "Failed to initialize GLEW." << std::endl;
+ return -1;
+ }
+
+ reshapeFunc(window, width, height);
+
+ float bmin[3], bmax[3];
+ if (false == LoadObjAndConvert(bmin, bmax, argv[1], num_threads, verbose)) {
+ printf("failed to load & conv\n");
+ return -1;
+ }
+
+ float maxExtent = 0.5f * (bmax[0] - bmin[0]);
+ if (maxExtent < 0.5f * (bmax[1] - bmin[1])) {
+ maxExtent = 0.5f * (bmax[1] - bmin[1]);
+ }
+ if (maxExtent < 0.5f * (bmax[2] - bmin[2])) {
+ maxExtent = 0.5f * (bmax[2] - bmin[2]);
+ }
+
+ while(glfwWindowShouldClose(window) == GL_FALSE) {
+ glfwPollEvents();
+ glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glEnable(GL_DEPTH_TEST);
+
+ // camera & rotate
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ GLfloat mat[4][4];
+ gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], up[1], up[2]);
+ build_rotmatrix(mat, curr_quat);
+ glMultMatrixf(&mat[0][0]);
+
+ // Fit to -1, 1
+ glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent);
+
+ // Centerize object.
+ glTranslatef(-0.5*(bmax[0] + bmin[0]), -0.5*(bmax[1] + bmin[1]), -0.5*(bmax[2] + bmin[2]));
+
+ Draw(gDrawObjects);
+
+ glfwSwapBuffers(window);
+ }
+
+ glfwTerminate();
+}