Initial commit
|
@ -0,0 +1,42 @@
|
|||
name: Build
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# Setup caching of build artifacts to reduce total build time
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{github.workspace}}/build
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -yq libglu1-mesa-dev libxxf86vm-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libx11-dev
|
||||
|
||||
- name: Configure CMake
|
||||
# Use a bash shell so we can use the same syntax for environment variable
|
||||
# access regardless of the host operating system
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}/build
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{github.workspace}}/build
|
||||
shell: bash
|
||||
run: cmake --build . --config $BUILD_TYPE -- -j
|
||||
|
||||
- name: Test
|
||||
working-directory: ${{github.workspace}}
|
||||
shell: bash
|
||||
run: python3 run_tests.py
|
|
@ -0,0 +1,27 @@
|
|||
/build
|
||||
.DS_Store
|
||||
/nori
|
||||
/warptest
|
||||
/ext_build
|
||||
CMakeCache.txt
|
||||
CMakeSettings.json
|
||||
CMakeFiles
|
||||
Makefile
|
||||
*.cmake
|
||||
.ninja_deps
|
||||
.ninja_log
|
||||
build.ninja
|
||||
rules.ninja
|
||||
.vs
|
||||
.vscode
|
||||
/out
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
*.vcxproj
|
||||
*.filters
|
||||
*.sdf
|
||||
*.sln
|
||||
*.dir
|
||||
*.opendb
|
||||
Debug
|
||||
x64
|
|
@ -0,0 +1,30 @@
|
|||
[submodule "ext/filesystem"]
|
||||
path = ext/filesystem
|
||||
url = https://github.com/wjakob/filesystem
|
||||
[submodule "ext/tbb"]
|
||||
path = ext/tbb
|
||||
url = https://github.com/wjakob/tbb
|
||||
[submodule "ext/pugixml"]
|
||||
path = ext/pugixml
|
||||
url = http://github.com/zeux/pugixml
|
||||
[submodule "ext/tinyformat"]
|
||||
path = ext/tinyformat
|
||||
url = https://github.com/wjakob/tinyformat
|
||||
[submodule "ext/hypothesis"]
|
||||
path = ext/hypothesis
|
||||
url = https://github.com/wjakob/hypothesis
|
||||
[submodule "ext/pcg32"]
|
||||
path = ext/pcg32
|
||||
url = https://github.com/wjakob/pcg32
|
||||
[submodule "ext/nanogui"]
|
||||
path = ext/nanogui
|
||||
url = https://github.com/mitsuba-renderer/nanogui
|
||||
[submodule "ext/zlib"]
|
||||
path = ext/zlib
|
||||
url = https://github.com/mitsuba-renderer/zlib
|
||||
[submodule "ext/openexr"]
|
||||
path = ext/openexr
|
||||
url = https://github.com/mitsuba-renderer/openexr/
|
||||
[submodule "ext/eigen"]
|
||||
path = ext/eigen
|
||||
url = https://gitlab.com/libeigen/eigen.git
|
|
@ -0,0 +1,128 @@
|
|||
cmake_minimum_required (VERSION 3.8)
|
||||
project(nori)
|
||||
|
||||
add_subdirectory(ext ext_build)
|
||||
|
||||
include_directories(
|
||||
# Nori include files
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
# tinyformat string formatting library
|
||||
${TFM_INCLUDE_DIR}
|
||||
# Eigen linear algebra library
|
||||
SYSTEM ${EIGEN_INCLUDE_DIR}
|
||||
# OpenEXR high dynamic range bitmap library
|
||||
SYSTEM ${OPENEXR_INCLUDE_DIRS}
|
||||
# Intel Thread Building Blocks
|
||||
SYSTEM ${TBB_INCLUDE_DIR}
|
||||
# Pseudorandom number generator
|
||||
${PCG32_INCLUDE_DIR}
|
||||
# PugiXML parser
|
||||
${PUGIXML_INCLUDE_DIR}
|
||||
# Helper functions for statistical hypothesis tests
|
||||
${HYPOTHESIS_INCLUDE_DIR}
|
||||
# GLFW library for OpenGL context creation
|
||||
SYSTEM ${GLFW_INCLUDE_DIR}
|
||||
# GLEW library for accessing OpenGL functions
|
||||
SYSTEM ${GLEW_INCLUDE_DIR}
|
||||
# NanoVG drawing library
|
||||
SYSTEM ${NANOVG_INCLUDE_DIR}
|
||||
# NanoGUI user interface library
|
||||
SYSTEM ${NANOGUI_INCLUDE_DIR}
|
||||
SYSTEM ${NANOGUI_EXTRA_INCS}
|
||||
# Portable filesystem API
|
||||
SYSTEM ${FILESYSTEM_INCLUDE_DIR}
|
||||
# STB Image Write
|
||||
SYSTEM ${STB_IMAGE_WRITE_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
# The following lines build the main executable. If you add a source
|
||||
# code file to Nori, be sure to include it in this list.
|
||||
add_executable(nori
|
||||
|
||||
# Header files
|
||||
include/nori/bbox.h
|
||||
include/nori/bitmap.h
|
||||
include/nori/block.h
|
||||
include/nori/bsdf.h
|
||||
include/nori/accel.h
|
||||
include/nori/camera.h
|
||||
include/nori/color.h
|
||||
include/nori/common.h
|
||||
include/nori/dpdf.h
|
||||
include/nori/frame.h
|
||||
include/nori/integrator.h
|
||||
include/nori/emitter.h
|
||||
include/nori/mesh.h
|
||||
include/nori/object.h
|
||||
include/nori/parser.h
|
||||
include/nori/proplist.h
|
||||
include/nori/ray.h
|
||||
include/nori/rfilter.h
|
||||
include/nori/sampler.h
|
||||
include/nori/scene.h
|
||||
include/nori/timer.h
|
||||
include/nori/transform.h
|
||||
include/nori/vector.h
|
||||
include/nori/warp.h
|
||||
|
||||
# Source code files
|
||||
src/bitmap.cpp
|
||||
src/block.cpp
|
||||
src/accel.cpp
|
||||
src/chi2test.cpp
|
||||
src/common.cpp
|
||||
src/diffuse.cpp
|
||||
src/gui.cpp
|
||||
src/independent.cpp
|
||||
src/main.cpp
|
||||
src/mesh.cpp
|
||||
src/obj.cpp
|
||||
src/object.cpp
|
||||
src/parser.cpp
|
||||
src/perspective.cpp
|
||||
src/proplist.cpp
|
||||
src/rfilter.cpp
|
||||
src/scene.cpp
|
||||
src/ttest.cpp
|
||||
src/warp.cpp
|
||||
src/microfacet.cpp
|
||||
src/mirror.cpp
|
||||
src/dielectric.cpp
|
||||
)
|
||||
|
||||
add_definitions(${NANOGUI_EXTRA_DEFS})
|
||||
|
||||
# The following lines build the warping test application
|
||||
add_executable(warptest
|
||||
include/nori/warp.h
|
||||
src/warp.cpp
|
||||
src/warptest.cpp
|
||||
src/microfacet.cpp
|
||||
src/object.cpp
|
||||
src/proplist.cpp
|
||||
src/common.cpp
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(nori tbb_static pugixml IlmImf nanogui ${NANOGUI_EXTRA_LIBS} zlibstatic)
|
||||
else()
|
||||
target_link_libraries(nori tbb_static pugixml IlmImf nanogui ${NANOGUI_EXTRA_LIBS})
|
||||
endif()
|
||||
|
||||
target_link_libraries(warptest tbb_static nanogui ${NANOGUI_EXTRA_LIBS})
|
||||
|
||||
# Force colored output for the ninja generator
|
||||
if (CMAKE_GENERATOR STREQUAL "Ninja")
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fcolor-diagnostics")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics")
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdiagnostics-color=always")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_compile_features(warptest PRIVATE cxx_std_17)
|
||||
target_compile_features(nori PRIVATE cxx_std_17)
|
||||
|
||||
# vim: set et ts=2 sw=2 ft=cmake nospell:
|
|
@ -0,0 +1,26 @@
|
|||
Advanced Computer Graphics — Homeworks
|
||||
======================================
|
||||
|
||||
Student name:
|
||||
|
||||
Sciper number:
|
||||
|
||||
|
||||
## Build status
|
||||
|
||||
**Insert your build badge URL here**
|
||||
|
||||
## Homework results
|
||||
|
||||
| Homework | Links
|
||||
| ---------: | ---------------------------------------------
|
||||
| 1 | [report.html](results/homework-1/report.html)
|
||||
| 2 | [report.html](results/homework-2/report.html)
|
||||
| 3 | [report.html](results/homework-3/report.html)
|
||||
| 4 | [report.html](results/homework-4/report.html)
|
||||
| 5 | [report.html](results/homework-5/report.html)
|
||||
|
||||
|
||||
## Featured result
|
||||
|
||||
Feel free to show off your best render here!
|
|
@ -0,0 +1,215 @@
|
|||
# =======================================================================
|
||||
# WARNING WARNING WARNING WARNING WARNING WARNING
|
||||
# =======================================================================
|
||||
# Remember to put on SAFETY GOGGLES before looking at this file. You
|
||||
# are most certainly not expected to read or understand any of it.
|
||||
# =======================================================================
|
||||
#
|
||||
# This CMake file is responsible for compiling dependency libraries and
|
||||
# setting up suitable compiler flags for various platforms. You do not
|
||||
# need to read or change anything in this file; see CMakeLists.txt in
|
||||
# the main Nori folder instead.
|
||||
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/openexr/OpenEXR")
|
||||
message(FATAL_ERROR "The Nori dependencies are missing! "
|
||||
"You probably did not clone the project with --recursive. It is possible to recover "
|
||||
"by calling \"git submodule update --init --recursive\"")
|
||||
endif()
|
||||
|
||||
# Set a default build configuration (Release)
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
message(STATUS "Setting build type to 'Release' as none was specified.")
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
|
||||
"MinSizeRel" "RelWithDebInfo")
|
||||
endif()
|
||||
string(TOUPPER "${CMAKE_BUILD_TYPE}" U_CMAKE_BUILD_TYPE)
|
||||
|
||||
# Enable folders for projects in Visual Studio
|
||||
if (CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
set(CMAKE_MACOSX_RPATH ON)
|
||||
endif()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
|
||||
# Prefer libc++ in conjunction with Clang
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
CHECK_CXX_COMPILER_FLAG("-stdlib=libc++" HAS_LIBCPP)
|
||||
if (HAS_LIBCPP)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -D_LIBCPP_VERSION")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++")
|
||||
message(STATUS "Nori: using libc++.")
|
||||
else()
|
||||
message(WARNING "libc++ is recommended in conjunction with clang. Please insteall the libc++ development headers, provided e.g. by the packages 'libc++-dev' and 'libc++abi-dev' on Debian/Ubuntu.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Enable link time optimization and set the default symbol
|
||||
# visibility to hidden (very important to obtain small binaries)
|
||||
if (NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG)
|
||||
# Default symbol visibility
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
|
||||
endif()
|
||||
|
||||
# Disable specific GCC 7 warnings
|
||||
if (CMAKE_COMPILER_IS_GNUCC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 7.0)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations -Wno-misleading-indentation -Wformat-truncation=0 -Wno-int-in-bool-context -Wimplicit-fallthrough=0")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Sanitize build environment for static build with C++11
|
||||
if (MSVC)
|
||||
# Disable annoying secure CRT warnings
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_CRT_SECURE_NO_WARNINGS")
|
||||
|
||||
# We'll select the TBB library ourselves
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D__TBB_NO_IMPLICIT_LINKAGE")
|
||||
|
||||
# Parallel build on MSVC (all targets)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||
|
||||
if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:SSE2")
|
||||
|
||||
# Disable Eigen vectorization for Windows 32 bit builds (issues with unaligned access segfaults)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DEIGEN_DONT_ALIGN")
|
||||
endif()
|
||||
|
||||
# Static build
|
||||
set(CompilerFlags
|
||||
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
|
||||
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO
|
||||
CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
|
||||
CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO)
|
||||
foreach(CompilerFlag ${CompilerFlags})
|
||||
string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
# Build zlib (only on Windows)
|
||||
set(ZLIB_BUILD_STATIC_LIBS ON CACHE BOOL " " FORCE)
|
||||
set(ZLIB_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE)
|
||||
add_subdirectory(zlib)
|
||||
|
||||
set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/zlib" CACHE PATH " " FORCE)
|
||||
if (CMAKE_GENERATOR STREQUAL "Ninja")
|
||||
set(ZLIB_LIBRARY "${CMAKE_CURRENT_BINARY_DIR}/zlib/zlibstatic.lib" CACHE FILEPATH " " FORCE)
|
||||
else()
|
||||
set(ZLIB_LIBRARY "${CMAKE_CURRENT_BINARY_DIR}/zlib/$<CONFIGURATION>/zlibstatic.lib" CACHE FILEPATH " " FORCE)
|
||||
endif()
|
||||
|
||||
set_property(TARGET zlibstatic PROPERTY FOLDER "dependencies")
|
||||
include_directories(${ZLIB_INCLUDE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/zlib")
|
||||
endif()
|
||||
|
||||
# Build OpenER
|
||||
set(ILMBASE_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE)
|
||||
set(OPENEXR_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE)
|
||||
set(ILMBASE_NAMESPACE_VERSIONING OFF CACHE BOOL " " FORCE)
|
||||
set(OPENEXR_NAMESPACE_VERSIONING OFF CACHE BOOL " " FORCE)
|
||||
add_subdirectory(openexr)
|
||||
set_property(TARGET IexMath eLut toFloat b44ExpLogTable dwaLookups IlmThread Half Iex Imath IlmImf PROPERTY FOLDER "dependencies")
|
||||
|
||||
# Build Thread Building Blocks (main shared libraries only)
|
||||
set(TBB_BUILD_SHARED OFF CACHE BOOL " " FORCE)
|
||||
set(TBB_BUILD_STATIC ON CACHE BOOL " " FORCE)
|
||||
set(TBB_BUILD_TBBMALLOC OFF CACHE BOOL " " FORCE)
|
||||
set(TBB_BUILD_TBBMALLOC_PROXY OFF CACHE BOOL " " FORCE)
|
||||
set(TBB_BUILD_TESTS OFF CACHE BOOL " " FORCE)
|
||||
add_subdirectory(tbb)
|
||||
set_property(TARGET tbb_static tbb_def_files PROPERTY FOLDER "dependencies")
|
||||
|
||||
if (APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DGL_SILENCE_DEPRECATION=1")
|
||||
endif()
|
||||
|
||||
# Build NanoGUI
|
||||
# Use OpenGL backend for UI. In the future, this can be removed to use Metal on MacOS
|
||||
set(NANOGUI_BACKEND "OpenGL" CACHE BOOL " " FORCE)
|
||||
set(NANOGUI_BUILD_EXAMPLES OFF CACHE BOOL " " FORCE)
|
||||
set(NANOGUI_BUILD_SHARED OFF CACHE BOOL " " FORCE)
|
||||
set(NANOGUI_BUILD_PYTHON OFF CACHE BOOL " " FORCE)
|
||||
add_subdirectory(nanogui)
|
||||
set_property(TARGET glfw glfw_objects nanogui PROPERTY FOLDER "dependencies")
|
||||
|
||||
# Build the pugixml parser
|
||||
add_library(pugixml STATIC pugixml/src/pugixml.cpp)
|
||||
set_property(TARGET pugixml PROPERTY
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/pugixml")
|
||||
set_property(TARGET pugixml PROPERTY FOLDER "dependencies")
|
||||
|
||||
set(OPENEXR_INCLUDE_DIRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Imath
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Iex
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Half
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/openexr/OpenEXR/IlmImf
|
||||
${CMAKE_CURRENT_BINARY_DIR}/openexr/OpenEXR/config
|
||||
${CMAKE_CURRENT_BINARY_DIR}/openexr/IlmBase/config)
|
||||
set(PCG32_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/pcg32)
|
||||
set(TFM_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tinyformat)
|
||||
set(HYPOTHESIS_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/hypothesis)
|
||||
set(GLFW_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/glfw/include)
|
||||
set(GLEW_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/glew/include)
|
||||
set(NANOVG_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/nanovg/src)
|
||||
set(STB_IMAGE_WRITE_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/nanovg/example)
|
||||
set(NANOGUI_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/nanogui/include)
|
||||
set(EIGEN_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/eigen)
|
||||
set(TBB_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tbb/include)
|
||||
set(FILESYSTEM_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/filesystem)
|
||||
set(PUGIXML_INCLUDE_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/pugixml/src)
|
||||
|
||||
# Compile remainder of the codebase with compiler warnings turned on
|
||||
if(MSVC)
|
||||
if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
|
||||
string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
|
||||
endif()
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
# Re-enable disabled warnings
|
||||
# if(CMAKE_CXX_FLAGS MATCHES "-Wno-[^ ]+")
|
||||
# string(REGEX REPLACE "-Wno-[^ ]+" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
# endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter")
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-anonymous-struct -Wno-c99-extensions -Wno-nested-anon-types -Wno-deprecated-register")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CompilerFlags
|
||||
CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
|
||||
CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS
|
||||
CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL
|
||||
CMAKE_C_FLAGS_RELWITHDEBINFO COMPILE_DEFINITIONS U_CMAKE_BUILD_TYPE
|
||||
CMAKE_MACOSX_RPATH
|
||||
OPENEXR_INCLUDE_DIRS PCG32_INCLUDE_DIR TFM_INCLUDE_DIR
|
||||
HYPOTHESIS_INCLUDE_DIR GLFW_INCLUDE_DIR GLEW_INCLUDE_DIR
|
||||
NANOVG_INCLUDE_DIR NANOGUI_EXTRA_INCS NANOGUI_EXTRA_DEFS
|
||||
NANOGUI_EXTRA_LIBS NANOGUI_INCLUDE_DIR EIGEN_INCLUDE_DIR
|
||||
STB_IMAGE_WRITE_INCLUDE_DIR TBB_INCLUDE_DIR
|
||||
FILESYSTEM_INCLUDE_DIR PUGIXML_INCLUDE_DIR
|
||||
)
|
||||
foreach(CompilerFlag ${CompilerFlags})
|
||||
set(${CompilerFlag} "${${CompilerFlag}}" PARENT_SCOPE)
|
||||
endforeach()
|
|
@ -0,0 +1,15 @@
|
|||
### Overview of dependency libraries used by Nori
|
||||
|
||||
Nori requires several utility libraries to function correctly; a full list with
|
||||
explanations is given below. You should feel free to use any of their
|
||||
functionality in your own submissions—however, you are not required to do so.
|
||||
|
||||
* `filesystem`: tiny self-contained library for manipulating file paths
|
||||
* `hypothesis`: utility functions for statistical hypothesis tests
|
||||
* `nanogui`: minimalistic GUI library for OpenGL
|
||||
* `openexr`: High dynamic range image format library
|
||||
* `pcg32`: tiny self-contained pseudorandom number generator
|
||||
* `pugixml`: light-weight XML processing library
|
||||
* `tbb`: Intel's Boost Thread Building Blocks for multithreading
|
||||
* `tinyformat`: type-safe C++11 version of `sprintf` and friends
|
||||
* `zlib`: data compression library, used by `openexr`
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 0fd6b4f71dd85b2009ee4d1aeb296e2c11fc9d68
|
|
@ -0,0 +1 @@
|
|||
Subproject commit f45da753728cde9b1c380b343e41c8b1ca6498d7
|
|
@ -0,0 +1 @@
|
|||
Subproject commit e165503f7d796d844d5d6ab69b5c7f839cf0ede8
|
|
@ -0,0 +1 @@
|
|||
Subproject commit c6505300bb3036ec87ac68f5f1699c434c3d7fc6
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 080d443cbd8d9672330c1fa2a84dcc9a5c220e25
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 70099eadb86d3999c38cf69d2c55f8adc1f7fe34
|
|
@ -0,0 +1,183 @@
|
|||
|
||||
import math
|
||||
import os
|
||||
import shutil
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
import bpy
|
||||
import bpy_extras
|
||||
from bpy.props import BoolProperty, IntProperty, StringProperty
|
||||
from bpy_extras.io_utils import ExportHelper
|
||||
from mathutils import Matrix
|
||||
|
||||
bl_info = {
|
||||
"name": "Export Nori scenes format",
|
||||
"author": "Adrien Gruson, Delio Vicini, Tizian Zeltner",
|
||||
"version": (0, 1),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "File > Export > Nori exporter (.xml)",
|
||||
"description": "Export Nori scene format (.xml)",
|
||||
"warning": "",
|
||||
"wiki_url": "",
|
||||
"tracker_url": "",
|
||||
"category": "Import-Export"}
|
||||
|
||||
|
||||
class NoriWriter:
|
||||
|
||||
def __init__(self, context, filepath):
|
||||
self.context = context
|
||||
self.filepath = filepath
|
||||
self.working_dir = os.path.dirname(self.filepath)
|
||||
|
||||
def create_xml_element(self, name, attr):
|
||||
el = self.doc.createElement(name)
|
||||
for k, v in attr.items():
|
||||
el.setAttribute(k, v)
|
||||
return el
|
||||
|
||||
def create_xml_entry(self, t, name, value):
|
||||
return self.create_xml_element(t, {"name": name, "value": value})
|
||||
|
||||
def create_xml_transform(self, mat, el=None):
|
||||
transform = self.create_xml_element("transform", {"name": "toWorld"})
|
||||
if(el):
|
||||
transform.appendChild(el)
|
||||
value = ""
|
||||
for j in range(4):
|
||||
for i in range(4):
|
||||
value += str(mat[j][i]) + ","
|
||||
transform.appendChild(self.create_xml_element("matrix", {"value": value[:-1]}))
|
||||
return transform
|
||||
|
||||
def create_xml_mesh_entry(self, filename):
|
||||
meshElement = self.create_xml_element("mesh", {"type": "obj"})
|
||||
meshElement.appendChild(self.create_xml_element("string", {"name": "filename", "value": "meshes/"+filename}))
|
||||
return meshElement
|
||||
|
||||
def write(self):
|
||||
"""Main method to write the blender scene into Nori format"""
|
||||
|
||||
n_samples = 32
|
||||
# create xml document
|
||||
self.doc = Document()
|
||||
self.scene = self.doc.createElement("scene")
|
||||
self.doc.appendChild(self.scene)
|
||||
|
||||
# 1) write integrator configuration
|
||||
self.scene.appendChild(self.create_xml_element("integrator", {"type": "normals"}))
|
||||
|
||||
# 2) write sampler
|
||||
sampler = self.create_xml_element("sampler", {"type": "independent"})
|
||||
sampler.appendChild(self.create_xml_element("integer", {"name": "sampleCount", "value": str(n_samples)}))
|
||||
self.scene.appendChild(sampler)
|
||||
|
||||
# 3) export one camera
|
||||
cameras = [cam for cam in self.context.scene.objects
|
||||
if cam.type in {'CAMERA'}]
|
||||
if(len(cameras) == 0):
|
||||
print("WARN: No camera to export")
|
||||
else:
|
||||
if(len(cameras) > 1):
|
||||
print("WARN: Does not handle multiple camera, only export the active one")
|
||||
self.scene.appendChild(self.write_camera(self.context.scene.camera)) # export the active one
|
||||
|
||||
# 4) export all meshes
|
||||
if not os.path.exists(self.working_dir + "/meshes"):
|
||||
os.makedirs(self.working_dir + "/meshes")
|
||||
|
||||
meshes = [obj for obj in self.context.scene.objects
|
||||
if obj.type in {'MESH', 'FONT', 'SURFACE', 'META'}]
|
||||
print(meshes)
|
||||
for mesh in meshes:
|
||||
self.write_mesh(mesh)
|
||||
|
||||
# 6) write the xml file
|
||||
self.doc.writexml(open(self.filepath, "w"), "", "\t", "\n")
|
||||
|
||||
def write_camera(self, cam):
|
||||
"""convert the selected camera (cam) into xml format"""
|
||||
camera = self.create_xml_element("camera", {"type": "perspective"})
|
||||
camera.appendChild(self.create_xml_entry("float", "fov", str(cam.data.angle * 180 / math.pi)))
|
||||
camera.appendChild(self.create_xml_entry("float", "nearClip", str(cam.data.clip_start)))
|
||||
camera.appendChild(self.create_xml_entry("float", "farClip", str(cam.data.clip_end)))
|
||||
percent = self.context.scene.render.resolution_percentage / 100.0
|
||||
camera.appendChild(self.create_xml_entry("integer", "width", str(
|
||||
int(self.context.scene.render.resolution_x * percent))))
|
||||
camera.appendChild(self.create_xml_entry("integer", "height", str(
|
||||
int(self.context.scene.render.resolution_y * percent))))
|
||||
|
||||
mat = cam.matrix_world
|
||||
|
||||
# Conversion to Y-up coordinate system
|
||||
coord_transf = bpy_extras.io_utils.axis_conversion(
|
||||
from_forward='Y', from_up='Z', to_forward='-Z', to_up='Y').to_4x4()
|
||||
mat = coord_transf @ mat
|
||||
pos = mat.translation
|
||||
# Nori's camera needs this these coordinates to be flipped
|
||||
m = Matrix([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 0]])
|
||||
t = mat.to_3x3() @ m.to_3x3()
|
||||
mat = Matrix([[t[0][0], t[0][1], t[0][2], pos[0]],
|
||||
[t[1][0], t[1][1], t[1][2], pos[1]],
|
||||
[t[2][0], t[2][1], t[2][2], pos[2]],
|
||||
[0, 0, 0, 1]])
|
||||
trans = self.create_xml_transform(mat)
|
||||
camera.appendChild(trans)
|
||||
return camera
|
||||
|
||||
def write_mesh(self, mesh):
|
||||
viewport_selection = self.context.selected_objects
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
obj_name = mesh.name + ".obj"
|
||||
obj_path = os.path.join(self.working_dir, 'meshes', obj_name)
|
||||
mesh.select_set(True)
|
||||
bpy.ops.export_scene.obj(filepath=obj_path, check_existing=False,
|
||||
use_selection=True, use_edges=False, use_smooth_groups=False,
|
||||
use_materials=False, use_triangles=True, use_mesh_modifiers=True)
|
||||
mesh.select_set(False)
|
||||
|
||||
# Add the corresponding entry to the xml
|
||||
mesh_element = self.create_xml_mesh_entry(obj_name)
|
||||
# We currently just export a default material, a more complex material conversion
|
||||
# could be implemented following: http://tinyurl.com/nnhxwuh
|
||||
bsdf_element = self.create_xml_element("bsdf", {"type": "diffuse"})
|
||||
bsdf_element.appendChild(self.create_xml_entry("color", "albedo", "0.75,0.75,0.75"))
|
||||
mesh_element.appendChild(bsdf_element)
|
||||
self.scene.appendChild(mesh_element)
|
||||
|
||||
for ob in viewport_selection:
|
||||
ob.select_set(True)
|
||||
|
||||
|
||||
class NoriExporter(bpy.types.Operator, ExportHelper):
|
||||
"""Export a blender scene into Nori scene format"""
|
||||
|
||||
# add to menu
|
||||
bl_idname = "export_scene.nori"
|
||||
bl_label = "Export Nori scene"
|
||||
|
||||
filename_ext = ".xml"
|
||||
filter_glob: StringProperty(default="*.xml", options={'HIDDEN'})
|
||||
|
||||
def execute(self, context):
|
||||
nori = NoriWriter(context, self.filepath)
|
||||
nori.write()
|
||||
return {'FINISHED'}
|
||||
|
||||
def menu_func_export(self, context):
|
||||
self.layout.operator(NoriExporter.bl_idname, text="Export Nori scene...")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(NoriExporter)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(NoriExporter)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -0,0 +1,22 @@
|
|||
# Nori Exporter for Blender
|
||||
by Delio Vicini and Tizian Zeltner, based on Adrien Gruson's original exporter.
|
||||
|
||||
## Installation
|
||||
|
||||
First, you must download a fairly recent version of Blender (the plugin is tested for versions >= 2.8). You can download blender from https://www.blender.org or using your system's package manager.
|
||||
|
||||
To install the plugin, open Blender and go to "Edit -> Preferences... -> Add-ons" and click on "Install...".
|
||||
This should open a file browser in which you can navigate to the `io_nori.py` file and select it.
|
||||
This will copy the exporter script to Blender's plugin directory.
|
||||
After the plugin is installed, it has to be activated by clicking the checkbox next to it in the Add-ons menu.
|
||||
|
||||
## Usage
|
||||
|
||||
Once the plugin is installed, scenes can be expored by clicking "File -> Export -> Export Nori Scene..."
|
||||
|
||||
The plugin exports all objects in the scene as separate OBJ FIles. It then generates a Nori XML file with the scene's camera, a basic integrator and XML entries to reference all the exported meshes.
|
||||
|
||||
## Limitations
|
||||
|
||||
The plugin does not support exporting BSDFs and emitters. It will just assign a default BSDF to all shapes. It further exports each mesh as is.
|
||||
This means that if you have a mesh with multiple materials in Blender, you will have to split it manually into separate submeshes before exporting (one for each material). This can be done by selecting the mesh with multiple materials, going to edit mode (tab) then selecting all vertices (A) and clicking "separate" (P) and then "by material". This will separate the mesh into meshes with one material each. After exporting, each of these meshes will have a separate entry in the scene's XML file and can therefore be assigned a different BSDF.
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 35a63cb1e63d3487cf68f73e963b6f1e58cf45da
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 141b0e310e1fb552bdca887542c9c1a8544d6503
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 43816c694297a27909da0982f91315fb33d60676
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 54d591eabf9fe0e84c725638f8d5d8d202a093fa
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/mesh.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Acceleration data structure for ray intersection queries
|
||||
*
|
||||
* The current implementation falls back to a brute force loop
|
||||
* through the geometry.
|
||||
*/
|
||||
class Accel {
|
||||
public:
|
||||
/**
|
||||
* \brief Register a triangle mesh for inclusion in the acceleration
|
||||
* data structure
|
||||
*
|
||||
* This function can only be used before \ref build() is called
|
||||
*/
|
||||
void addMesh(Mesh *mesh);
|
||||
|
||||
/// Build the acceleration data structure (currently a no-op)
|
||||
void build();
|
||||
|
||||
/// Return an axis-aligned box that bounds the scene
|
||||
const BoundingBox3f &getBoundingBox() const { return m_bbox; }
|
||||
|
||||
/**
|
||||
* \brief Intersect a ray against all triangles stored in the scene and
|
||||
* return detailed intersection information
|
||||
*
|
||||
* \param ray
|
||||
* A 3-dimensional ray data structure with minimum/maximum extent
|
||||
* information
|
||||
*
|
||||
* \param its
|
||||
* A detailed intersection record, which will be filled by the
|
||||
* intersection query
|
||||
*
|
||||
* \param shadowRay
|
||||
* \c true if this is a shadow ray query, i.e. a query that only aims to
|
||||
* find out whether the ray is blocked or not without returning detailed
|
||||
* intersection information.
|
||||
*
|
||||
* \return \c true if an intersection was found
|
||||
*/
|
||||
bool rayIntersect(const Ray3f &ray, Intersection &its, bool shadowRay) const;
|
||||
|
||||
private:
|
||||
Mesh *m_mesh = nullptr; ///< Mesh (only a single one for now)
|
||||
BoundingBox3f m_bbox; ///< Bounding box of the entire scene
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,399 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/ray.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Generic n-dimensional bounding box data structure
|
||||
*
|
||||
* Maintains a minimum and maximum position along each dimension and provides
|
||||
* various convenience functions for querying and modifying them.
|
||||
*
|
||||
* This class is parameterized by the underlying point data structure,
|
||||
* which permits the use of different scalar types and dimensionalities, e.g.
|
||||
* \code
|
||||
* TBoundingBox<Vector3i> integerBBox(Point3i(0, 1, 3), Point3i(4, 5, 6));
|
||||
* TBoundingBox<Vector2d> doubleBBox(Point2d(0.0, 1.0), Point2d(4.0, 5.0));
|
||||
* \endcode
|
||||
*
|
||||
* \tparam T The underlying point data type (e.g. \c Point2d)
|
||||
* \ingroup libcore
|
||||
*/
|
||||
template <typename _PointType> struct TBoundingBox {
|
||||
enum {
|
||||
Dimension = _PointType::Dimension
|
||||
};
|
||||
|
||||
typedef _PointType PointType;
|
||||
typedef typename PointType::Scalar Scalar;
|
||||
typedef typename PointType::VectorType VectorType;
|
||||
|
||||
/**
|
||||
* \brief Create a new invalid bounding box
|
||||
*
|
||||
* Initializes the components of the minimum
|
||||
* and maximum position to \f$\infty\f$ and \f$-\infty\f$,
|
||||
* respectively.
|
||||
*/
|
||||
TBoundingBox() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/// Create a collapsed bounding box from a single point
|
||||
TBoundingBox(const PointType &p)
|
||||
: min(p), max(p) { }
|
||||
|
||||
/// Create a bounding box from two positions
|
||||
TBoundingBox(const PointType &min, const PointType &max)
|
||||
: min(min), max(max) {
|
||||
}
|
||||
|
||||
/// Test for equality against another bounding box
|
||||
bool operator==(const TBoundingBox &bbox) const {
|
||||
return min == bbox.min && max == bbox.max;
|
||||
}
|
||||
|
||||
/// Test for inequality against another bounding box
|
||||
bool operator!=(const TBoundingBox &bbox) const {
|
||||
return min != bbox.min || max != bbox.max;
|
||||
}
|
||||
|
||||
/// Calculate the n-dimensional volume of the bounding box
|
||||
Scalar getVolume() const {
|
||||
return (max - min).prod();
|
||||
}
|
||||
|
||||
/// Calculate the n-1 dimensional volume of the boundary
|
||||
float getSurfaceArea() const {
|
||||
VectorType d = max - min;
|
||||
float result = 0.0f;
|
||||
for (int i=0; i<Dimension; ++i) {
|
||||
float term = 1.0f;
|
||||
for (int j=0; j<Dimension; ++j) {
|
||||
if (i == j)
|
||||
continue;
|
||||
term *= d[j];
|
||||
}
|
||||
result += term;
|
||||
}
|
||||
return 2.0f * result;
|
||||
}
|
||||
|
||||
/// Return the center point
|
||||
PointType getCenter() const {
|
||||
return (max + min) * (Scalar) 0.5f;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Check whether a point lies \a on or \a inside the bounding box
|
||||
*
|
||||
* \param p The point to be tested
|
||||
*
|
||||
* \param strict Set this parameter to \c true if the bounding
|
||||
* box boundary should be excluded in the test
|
||||
*/
|
||||
bool contains(const PointType &p, bool strict = false) const {
|
||||
if (strict) {
|
||||
return (p.array() > min.array()).all()
|
||||
&& (p.array() < max.array()).all();
|
||||
} else {
|
||||
return (p.array() >= min.array()).all()
|
||||
&& (p.array() <= max.array()).all();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Check whether a specified bounding box lies \a on or \a within
|
||||
* the current bounding box
|
||||
*
|
||||
* Note that by definition, an 'invalid' bounding box (where min=\f$\infty\f$
|
||||
* and max=\f$-\infty\f$) does not cover any space. Hence, this method will always
|
||||
* return \a true when given such an argument.
|
||||
*
|
||||
* \param strict Set this parameter to \c true if the bounding
|
||||
* box boundary should be excluded in the test
|
||||
*/
|
||||
bool contains(const TBoundingBox &bbox, bool strict = false) const {
|
||||
if (strict) {
|
||||
return (bbox.min.array() > min.array()).all()
|
||||
&& (bbox.max.array() < max.array()).all();
|
||||
} else {
|
||||
return (bbox.min.array() >= min.array()).all()
|
||||
&& (bbox.max.array() <= max.array()).all();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Check two axis-aligned bounding boxes for possible overlap.
|
||||
*
|
||||
* \param strict Set this parameter to \c true if the bounding
|
||||
* box boundary should be excluded in the test
|
||||
*
|
||||
* \return \c true If overlap was detected.
|
||||
*/
|
||||
bool overlaps(const TBoundingBox &bbox, bool strict = false) const {
|
||||
if (strict) {
|
||||
return (bbox.min.array() < max.array()).all()
|
||||
&& (bbox.max.array() > min.array()).all();
|
||||
} else {
|
||||
return (bbox.min.array() <= max.array()).all()
|
||||
&& (bbox.max.array() >= min.array()).all();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Calculate the smallest squared distance between
|
||||
* the axis-aligned bounding box and the point \c p.
|
||||
*/
|
||||
Scalar squaredDistanceTo(const PointType &p) const {
|
||||
Scalar result = 0;
|
||||
|
||||
for (int i=0; i<Dimension; ++i) {
|
||||
Scalar value = 0;
|
||||
if (p[i] < min[i])
|
||||
value = min[i] - p[i];
|
||||
else if (p[i] > max[i])
|
||||
value = p[i] - max[i];
|
||||
result += value*value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Calculate the smallest distance between
|
||||
* the axis-aligned bounding box and the point \c p.
|
||||
*/
|
||||
Scalar distanceTo(const PointType &p) const {
|
||||
return std::sqrt(squaredDistanceTo(p));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Calculate the smallest square distance between
|
||||
* the axis-aligned bounding box and \c bbox.
|
||||
*/
|
||||
Scalar squaredDistanceTo(const TBoundingBox &bbox) const {
|
||||
Scalar result = 0;
|
||||
|
||||
for (int i=0; i<Dimension; ++i) {
|
||||
Scalar value = 0;
|
||||
if (bbox.max[i] < min[i])
|
||||
value = min[i] - bbox.max[i];
|
||||
else if (bbox.min[i] > max[i])
|
||||
value = bbox.min[i] - max[i];
|
||||
result += value*value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Calculate the smallest distance between
|
||||
* the axis-aligned bounding box and \c bbox.
|
||||
*/
|
||||
Scalar distanceTo(const TBoundingBox &bbox) const {
|
||||
return std::sqrt(squaredDistanceTo(bbox));
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Check whether this is a valid bounding box
|
||||
*
|
||||
* A bounding box \c bbox is valid when
|
||||
* \code
|
||||
* bbox.min[dim] <= bbox.max[dim]
|
||||
* \endcode
|
||||
* holds along each dimension \c dim.
|
||||
*/
|
||||
bool isValid() const {
|
||||
return (max.array() >= min.array()).all();
|
||||
}
|
||||
|
||||
/// Check whether this bounding box has collapsed to a single point
|
||||
bool isPoint() const {
|
||||
return (max.array() == min.array()).all();
|
||||
}
|
||||
|
||||
/// Check whether this bounding box has any associated volume
|
||||
bool hasVolume() const {
|
||||
return (max.array() > min.array()).all();
|
||||
}
|
||||
|
||||
/// Return the dimension index with the largest associated side length
|
||||
int getMajorAxis() const {
|
||||
VectorType d = max - min;
|
||||
int largest = 0;
|
||||
for (int i=1; i<Dimension; ++i)
|
||||
if (d[i] > d[largest])
|
||||
largest = i;
|
||||
return largest;
|
||||
}
|
||||
|
||||
/// Return the dimension index with the shortest associated side length
|
||||
int getMinorAxis() const {
|
||||
VectorType d = max - min;
|
||||
int shortest = 0;
|
||||
for (int i=1; i<Dimension; ++i)
|
||||
if (d[i] < d[shortest])
|
||||
shortest = i;
|
||||
return shortest;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Calculate the bounding box extents
|
||||
* \return max-min
|
||||
*/
|
||||
VectorType getExtents() const {
|
||||
return max - min;
|
||||
}
|
||||
|
||||
/// Clip to another bounding box
|
||||
void clip(const TBoundingBox &bbox) {
|
||||
min = min.cwiseMax(bbox.min);
|
||||
max = max.cwiseMin(bbox.max);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Mark the bounding box as invalid.
|
||||
*
|
||||
* This operation sets the components of the minimum
|
||||
* and maximum position to \f$\infty\f$ and \f$-\infty\f$,
|
||||
* respectively.
|
||||
*/
|
||||
void reset() {
|
||||
min.setConstant( std::numeric_limits<Scalar>::infinity());
|
||||
max.setConstant(-std::numeric_limits<Scalar>::infinity());
|
||||
}
|
||||
|
||||
/// Expand the bounding box to contain another point
|
||||
void expandBy(const PointType &p) {
|
||||
min = min.cwiseMin(p);
|
||||
max = max.cwiseMax(p);
|
||||
}
|
||||
|
||||
/// Expand the bounding box to contain another bounding box
|
||||
void expandBy(const TBoundingBox &bbox) {
|
||||
min = min.cwiseMin(bbox.min);
|
||||
max = max.cwiseMax(bbox.max);
|
||||
}
|
||||
|
||||
/// Merge two bounding boxes
|
||||
static TBoundingBox merge(const TBoundingBox &bbox1, const TBoundingBox &bbox2) {
|
||||
return TBoundingBox(
|
||||
bbox1.min.cwiseMin(bbox2.min),
|
||||
bbox1.max.cwiseMax(bbox2.max)
|
||||
);
|
||||
}
|
||||
|
||||
/// Return the index of the largest axis
|
||||
int getLargestAxis() const {
|
||||
VectorType extents = max-min;
|
||||
|
||||
if (extents[0] >= extents[1] && extents[0] >= extents[2])
|
||||
return 0;
|
||||
else if (extents[1] >= extents[0] && extents[1] >= extents[2])
|
||||
return 1;
|
||||
else
|
||||
return 2;
|
||||
}
|
||||
|
||||
/// Return the position of a bounding box corner
|
||||
PointType getCorner(int index) const {
|
||||
PointType result;
|
||||
for (int i=0; i<Dimension; ++i)
|
||||
result[i] = (index & (1 << i)) ? max[i] : min[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Return a string representation of the bounding box
|
||||
std::string toString() const {
|
||||
if (!isValid())
|
||||
return "BoundingBox[invalid]";
|
||||
else
|
||||
return tfm::format("BoundingBox[min=%s, max=%s]", min.toString(), max.toString());
|
||||
}
|
||||
|
||||
/// Check if a ray intersects a bounding box
|
||||
bool rayIntersect(const Ray3f &ray) const {
|
||||
float nearT = -std::numeric_limits<float>::infinity();
|
||||
float farT = std::numeric_limits<float>::infinity();
|
||||
|
||||
for (int i=0; i<3; i++) {
|
||||
float origin = ray.o[i];
|
||||
float minVal = min[i], maxVal = max[i];
|
||||
|
||||
if (ray.d[i] == 0) {
|
||||
if (origin < minVal || origin > maxVal)
|
||||
return false;
|
||||
} else {
|
||||
float t1 = (minVal - origin) * ray.dRcp[i];
|
||||
float t2 = (maxVal - origin) * ray.dRcp[i];
|
||||
|
||||
if (t1 > t2)
|
||||
std::swap(t1, t2);
|
||||
|
||||
nearT = std::max(t1, nearT);
|
||||
farT = std::min(t2, farT);
|
||||
|
||||
if (!(nearT <= farT))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return ray.mint <= farT && nearT <= ray.maxt;
|
||||
}
|
||||
|
||||
/// Return the overlapping region of the bounding box and an unbounded ray
|
||||
bool rayIntersect(const Ray3f &ray, float &nearT, float &farT) const {
|
||||
nearT = -std::numeric_limits<float>::infinity();
|
||||
farT = std::numeric_limits<float>::infinity();
|
||||
|
||||
for (int i=0; i<3; i++) {
|
||||
float origin = ray.o[i];
|
||||
float minVal = min[i], maxVal = max[i];
|
||||
|
||||
if (ray.d[i] == 0) {
|
||||
if (origin < minVal || origin > maxVal)
|
||||
return false;
|
||||
} else {
|
||||
float t1 = (minVal - origin) * ray.dRcp[i];
|
||||
float t2 = (maxVal - origin) * ray.dRcp[i];
|
||||
|
||||
if (t1 > t2)
|
||||
std::swap(t1, t2);
|
||||
|
||||
nearT = std::max(t1, nearT);
|
||||
farT = std::min(t2, farT);
|
||||
|
||||
if (!(nearT <= farT))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PointType min; ///< Component-wise minimum
|
||||
PointType max; ///< Component-wise maximum
|
||||
};
|
||||
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/color.h>
|
||||
#include <nori/vector.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Stores a RGB high dynamic-range bitmap
|
||||
*
|
||||
* The bitmap class provides I/O support using the OpenEXR file format
|
||||
*/
|
||||
class Bitmap : public Eigen::Array<Color3f, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> {
|
||||
public:
|
||||
typedef Eigen::Array<Color3f, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> Base;
|
||||
|
||||
/**
|
||||
* \brief Allocate a new bitmap of the specified size
|
||||
*
|
||||
* The contents will initially be undefined, so make sure
|
||||
* to call \ref clear() if necessary
|
||||
*/
|
||||
Bitmap(const Vector2i &size = Vector2i(0, 0))
|
||||
: Base(size.y(), size.x()) { }
|
||||
|
||||
/// Load an OpenEXR file with the specified filename
|
||||
Bitmap(const std::string &filename);
|
||||
|
||||
/// Save the bitmap as an EXR file with the specified filename
|
||||
void saveEXR(const std::string &filename);
|
||||
|
||||
/// Save the bitmap as a PNG file (with sRGB tonemapping) with the specified filename
|
||||
void savePNG(const std::string &filename);
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* =======================================================================
|
||||
This file contains classes for parallel rendering of "image blocks".
|
||||
* ======================================================================= */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/color.h>
|
||||
#include <nori/vector.h>
|
||||
#include <tbb/mutex.h>
|
||||
|
||||
#define NORI_BLOCK_SIZE 32 /* Block size used for parallelization */
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Weighted pixel storage for a rectangular subregion of an image
|
||||
*
|
||||
* This class implements storage for a rectangular subregion of a
|
||||
* larger image that is being rendered. For each pixel, it records color
|
||||
* values along with a weight that specifies the accumulated influence of
|
||||
* nearby samples on the pixel (according to the used reconstruction filter).
|
||||
*
|
||||
* When rendering with filters, the samples in a rectangular
|
||||
* region will generally also contribute to pixels just outside of
|
||||
* this region. For that reason, this class also stores information about
|
||||
* a small border region around the rectangle, whose size depends on the
|
||||
* properties of the reconstruction filter.
|
||||
*/
|
||||
class ImageBlock : public Eigen::Array<Color4f, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> {
|
||||
public:
|
||||
/**
|
||||
* Create a new image block of the specified maximum size
|
||||
* \param size
|
||||
* Desired maximum size of the block
|
||||
* \param filter
|
||||
* Samples will be convolved with the image reconstruction
|
||||
* filter provided here.
|
||||
*/
|
||||
ImageBlock(const Vector2i &size, const ReconstructionFilter *filter);
|
||||
|
||||
/// Release all memory
|
||||
~ImageBlock();
|
||||
|
||||
/// Configure the offset of the block within the main image
|
||||
void setOffset(const Point2i &offset) { m_offset = offset; }
|
||||
|
||||
/// Return the offset of the block within the main image
|
||||
inline const Point2i &getOffset() const { return m_offset; }
|
||||
|
||||
/// Configure the size of the block within the main image
|
||||
void setSize(const Point2i &size) { m_size = size; }
|
||||
|
||||
/// Return the size of the block within the main image
|
||||
inline const Vector2i &getSize() const { return m_size; }
|
||||
|
||||
/// Return the border size in pixels
|
||||
inline int getBorderSize() const { return m_borderSize; }
|
||||
|
||||
/**
|
||||
* \brief Turn the block into a proper bitmap
|
||||
*
|
||||
* This entails normalizing all pixels and discarding
|
||||
* the border region.
|
||||
*/
|
||||
Bitmap *toBitmap() const;
|
||||
|
||||
/// Convert a bitmap into an image block
|
||||
void fromBitmap(const Bitmap &bitmap);
|
||||
|
||||
/// Clear all contents
|
||||
void clear() { setConstant(Color4f()); }
|
||||
|
||||
/// Record a sample with the given position and radiance value
|
||||
void put(const Point2f &pos, const Color3f &value);
|
||||
|
||||
/**
|
||||
* \brief Merge another image block into this one
|
||||
*
|
||||
* During the merge operation, this function locks
|
||||
* the destination block using a mutex.
|
||||
*/
|
||||
void put(ImageBlock &b);
|
||||
|
||||
/// Lock the image block (using an internal mutex)
|
||||
inline void lock() const { m_mutex.lock(); }
|
||||
|
||||
/// Unlock the image block
|
||||
inline void unlock() const { m_mutex.unlock(); }
|
||||
|
||||
/// Return a human-readable string summary
|
||||
std::string toString() const;
|
||||
protected:
|
||||
Point2i m_offset;
|
||||
Vector2i m_size;
|
||||
int m_borderSize = 0;
|
||||
float *m_filter = nullptr;
|
||||
float m_filterRadius = 0;
|
||||
float *m_weightsX = nullptr;
|
||||
float *m_weightsY = nullptr;
|
||||
float m_lookupFactor = 0;
|
||||
mutable tbb::mutex m_mutex;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Spiraling block generator
|
||||
*
|
||||
* This class can be used to chop up an image into many small
|
||||
* rectangular blocks suitable for parallel rendering. The blocks
|
||||
* are ordered in spiraling pattern so that the center is
|
||||
* rendered first.
|
||||
*/
|
||||
class BlockGenerator {
|
||||
public:
|
||||
/**
|
||||
* \brief Create a block generator with
|
||||
* \param size
|
||||
* Size of the image that should be split into blocks
|
||||
* \param blockSize
|
||||
* Maximum size of the individual blocks
|
||||
*/
|
||||
BlockGenerator(const Vector2i &size, int blockSize);
|
||||
|
||||
/**
|
||||
* \brief Return the next block to be rendered
|
||||
*
|
||||
* This function is thread-safe
|
||||
*
|
||||
* \return \c false if there were no more blocks
|
||||
*/
|
||||
bool next(ImageBlock &block);
|
||||
|
||||
/// Return the total number of blocks
|
||||
int getBlockCount() const { return m_blocksLeft; }
|
||||
protected:
|
||||
enum EDirection { ERight = 0, EDown, ELeft, EUp };
|
||||
|
||||
Point2i m_block;
|
||||
Vector2i m_numBlocks;
|
||||
Vector2i m_size;
|
||||
int m_blockSize;
|
||||
int m_numSteps;
|
||||
int m_blocksLeft;
|
||||
int m_stepsLeft;
|
||||
int m_direction;
|
||||
tbb::mutex m_mutex;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/object.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Convenience data structure used to pass multiple
|
||||
* parameters to the evaluation and sampling routines in \ref BSDF
|
||||
*/
|
||||
struct BSDFQueryRecord {
|
||||
/// Incident direction (in the local frame)
|
||||
Vector3f wi;
|
||||
|
||||
/// Outgoing direction (in the local frame)
|
||||
Vector3f wo;
|
||||
|
||||
/// Relative refractive index in the sampled direction
|
||||
float eta;
|
||||
|
||||
/// Measure associated with the sample
|
||||
EMeasure measure;
|
||||
|
||||
/// Create a new record for sampling the BSDF
|
||||
BSDFQueryRecord(const Vector3f &wi)
|
||||
: wi(wi), eta(1.f), measure(EUnknownMeasure) { }
|
||||
|
||||
/// Create a new record for querying the BSDF
|
||||
BSDFQueryRecord(const Vector3f &wi,
|
||||
const Vector3f &wo, EMeasure measure)
|
||||
: wi(wi), wo(wo), eta(1.f), measure(measure) { }
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Superclass of all bidirectional scattering distribution functions
|
||||
*/
|
||||
class BSDF : public NoriObject {
|
||||
public:
|
||||
/**
|
||||
* \brief Sample the BSDF and return the importance weight (i.e. the
|
||||
* value of the BSDF * cos(theta_o) divided by the probability density
|
||||
* of the sample with respect to solid angles).
|
||||
*
|
||||
* \param bRec A BSDF query record
|
||||
* \param sample A uniformly distributed sample on \f$[0,1]^2\f$
|
||||
*
|
||||
* \return The BSDF value divided by the probability density of the sample
|
||||
* sample. The returned value also includes the cosine
|
||||
* foreshortening factor associated with the outgoing direction,
|
||||
* when this is appropriate. A zero value means that sampling
|
||||
* failed.
|
||||
*/
|
||||
virtual Color3f sample(BSDFQueryRecord &bRec, const Point2f &sample) const = 0;
|
||||
|
||||
/**
|
||||
* \brief Evaluate the BSDF for a pair of directions and measure
|
||||
* specified in \code bRec
|
||||
*
|
||||
* \param bRec
|
||||
* A record with detailed information on the BSDF query
|
||||
* \return
|
||||
* The BSDF value, evaluated for each color channel
|
||||
*/
|
||||
virtual Color3f eval(const BSDFQueryRecord &bRec) const = 0;
|
||||
|
||||
/**
|
||||
* \brief Compute the probability of sampling \c bRec.wo
|
||||
* (conditioned on \c bRec.wi).
|
||||
*
|
||||
* This method provides access to the probability density that
|
||||
* is realized by the \ref sample() method.
|
||||
*
|
||||
* \param bRec
|
||||
* A record with detailed information on the BSDF query
|
||||
*
|
||||
* \return
|
||||
* A probability/density value expressed with respect
|
||||
* to the specified measure
|
||||
*/
|
||||
|
||||
virtual float pdf(const BSDFQueryRecord &bRec) const = 0;
|
||||
|
||||
/**
|
||||
* \brief Return the type of object (i.e. Mesh/BSDF/etc.)
|
||||
* provided by this instance
|
||||
* */
|
||||
EClassType getClassType() const { return EBSDF; }
|
||||
|
||||
/**
|
||||
* \brief Return whether or not this BRDF is diffuse. This
|
||||
* is primarily used by photon mapping to decide whether
|
||||
* or not to store photons on a surface
|
||||
*/
|
||||
virtual bool isDiffuse() const { return false; }
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/object.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Generic camera interface
|
||||
*
|
||||
* This class provides an abstract interface to cameras in Nori and
|
||||
* exposes the ability to sample their response function. By default, only
|
||||
* a perspective camera implementation exists, but you may choose to
|
||||
* implement other types (e.g. an environment camera, or a physically-based
|
||||
* camera model that simulates the behavior actual lenses)
|
||||
*/
|
||||
class Camera : public NoriObject {
|
||||
public:
|
||||
/**
|
||||
* \brief Importance sample a ray according to the camera's response function
|
||||
*
|
||||
* \param ray
|
||||
* A ray data structure to be filled with a position
|
||||
* and direction value
|
||||
*
|
||||
* \param samplePosition
|
||||
* Denotes the desired sample position on the film
|
||||
* expressed in fractional pixel coordinates
|
||||
*
|
||||
* \param apertureSample
|
||||
* A uniformly distributed 2D vector that is used to sample
|
||||
* a position on the aperture of the sensor if necessary.
|
||||
*
|
||||
* \return
|
||||
* An importance weight associated with the sampled ray.
|
||||
* This accounts for the difference in the camera response
|
||||
* function and the sampling density.
|
||||
*/
|
||||
virtual Color3f sampleRay(Ray3f &ray,
|
||||
const Point2f &samplePosition,
|
||||
const Point2f &apertureSample) const = 0;
|
||||
|
||||
/// Return the size of the output image in pixels
|
||||
const Vector2i &getOutputSize() const { return m_outputSize; }
|
||||
|
||||
/// Return the camera's reconstruction filter in image space
|
||||
const ReconstructionFilter *getReconstructionFilter() const { return m_rfilter; }
|
||||
|
||||
/**
|
||||
* \brief Return the type of object (i.e. Mesh/Camera/etc.)
|
||||
* provided by this instance
|
||||
* */
|
||||
EClassType getClassType() const { return ECamera; }
|
||||
protected:
|
||||
Vector2i m_outputSize;
|
||||
ReconstructionFilter *m_rfilter;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/common.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Represents a linear RGB color value
|
||||
*/
|
||||
struct Color3f : public Eigen::Array3f {
|
||||
public:
|
||||
typedef Eigen::Array3f Base;
|
||||
|
||||
/// Initialize the color vector with a uniform value
|
||||
Color3f(float value = 0.f) : Base(value, value, value) { }
|
||||
|
||||
/// Initialize the color vector with specific per-channel values
|
||||
Color3f(float r, float g, float b) : Base(r, g, b) { }
|
||||
|
||||
/// Construct a color vector from ArrayBase (needed to play nice with Eigen)
|
||||
template <typename Derived> Color3f(const Eigen::ArrayBase<Derived>& p)
|
||||
: Base(p) { }
|
||||
|
||||
/// Assign a color vector from ArrayBase (needed to play nice with Eigen)
|
||||
template <typename Derived> Color3f &operator=(const Eigen::ArrayBase<Derived>& p) {
|
||||
this->Base::operator=(p);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Return a reference to the red channel
|
||||
float &r() { return x(); }
|
||||
/// Return a reference to the red channel (const version)
|
||||
const float &r() const { return x(); }
|
||||
/// Return a reference to the green channel
|
||||
float &g() { return y(); }
|
||||
/// Return a reference to the green channel (const version)
|
||||
const float &g() const { return y(); }
|
||||
/// Return a reference to the blue channel
|
||||
float &b() { return z(); }
|
||||
/// Return a reference to the blue channel (const version)
|
||||
const float &b() const { return z(); }
|
||||
|
||||
/// Clamp to the positive range
|
||||
Color3f clamp() const { return Color3f(std::max(r(), 0.0f),
|
||||
std::max(g(), 0.0f), std::max(b(), 0.0f)); }
|
||||
|
||||
/// Check if the color vector contains a NaN/Inf/negative value
|
||||
bool isValid() const;
|
||||
|
||||
/// Convert from sRGB to linear RGB
|
||||
Color3f toLinearRGB() const;
|
||||
|
||||
/// Convert from linear RGB to sRGB
|
||||
Color3f toSRGB() const;
|
||||
|
||||
/// Return the associated luminance
|
||||
float getLuminance() const;
|
||||
|
||||
/// Return a human-readable string summary
|
||||
std::string toString() const {
|
||||
return tfm::format("[%f, %f, %f]", coeff(0), coeff(1), coeff(2));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Represents a linear RGB color and a weight
|
||||
*
|
||||
* This is used by Nori's image reconstruction filter code
|
||||
*/
|
||||
struct Color4f : public Eigen::Array4f {
|
||||
public:
|
||||
typedef Eigen::Array4f Base;
|
||||
|
||||
/// Create an zero value
|
||||
Color4f() : Base(0.0f, 0.0f, 0.0f, 0.0f) { }
|
||||
|
||||
/// Create from a 3-channel color
|
||||
Color4f(const Color3f &c) : Base(c.r(), c.g(), c.b(), 1.0f) { }
|
||||
|
||||
/// Initialize the color vector with specific per-channel values
|
||||
Color4f(float r, float g, float b, float w) : Base(r, g, b, w) { }
|
||||
|
||||
/// Construct a color vector from ArrayBase (needed to play nice with Eigen)
|
||||
template <typename Derived> Color4f(const Eigen::ArrayBase<Derived>& p)
|
||||
: Base(p) { }
|
||||
|
||||
/// Assign a color vector from ArrayBase (needed to play nice with Eigen)
|
||||
template <typename Derived> Color4f &operator=(const Eigen::ArrayBase<Derived>& p) {
|
||||
this->Base::operator=(p);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Divide by the filter weight and convert into a \ref Color3f value
|
||||
Color3f divideByFilterWeight() const {
|
||||
if (w() != 0)
|
||||
return head<3>() / w();
|
||||
else
|
||||
return Color3f(0.0f);
|
||||
}
|
||||
|
||||
/// Return a human-readable string summary
|
||||
std::string toString() const {
|
||||
return tfm::format("[%f, %f, %f, %f]", coeff(0), coeff(1), coeff(2), coeff(3));
|
||||
}
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
/* Disable some warnings on MSVC++ */
|
||||
#pragma warning(disable : 4127 4702 4100 4515 4800 4146 4512)
|
||||
#define WIN32_LEAN_AND_MEAN /* Don't ever include MFC on Windows */
|
||||
#define NOMINMAX /* Don't override min/max */
|
||||
#endif
|
||||
|
||||
/* Include the basics needed by any Nori file */
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <Eigen/Core>
|
||||
#include <stdint.h>
|
||||
#include <ImathPlatform.h>
|
||||
#include <tinyformat.h>
|
||||
|
||||
/* Convenience definitions */
|
||||
#define NORI_NAMESPACE_BEGIN namespace nori {
|
||||
#define NORI_NAMESPACE_END }
|
||||
|
||||
#if defined(__NORI_APPLE__NORI_)
|
||||
#define PLATFORM_MACOS
|
||||
#elif defined(__NORI_linux__NORI_)
|
||||
#define PLATFORM_LINUX
|
||||
#elif defined(WIN32)
|
||||
#define PLATFORM_WINDOWS
|
||||
#endif
|
||||
|
||||
/* "Ray epsilon": relative error threshold for ray intersection computations */
|
||||
#define Epsilon 1e-4f
|
||||
|
||||
/* A few useful constants */
|
||||
#undef M_PI
|
||||
|
||||
#define M_PI 3.14159265358979323846f
|
||||
#define INV_PI 0.31830988618379067154f
|
||||
#define INV_TWOPI 0.15915494309189533577f
|
||||
#define INV_FOURPI 0.07957747154594766788f
|
||||
#define SQRT_TWO 1.41421356237309504880f
|
||||
#define INV_SQRT_TWO 0.70710678118654752440f
|
||||
|
||||
/* Forward declarations */
|
||||
namespace filesystem {
|
||||
class path;
|
||||
class resolver;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/* Forward declarations */
|
||||
template <typename Scalar, int Dimension> struct TVector;
|
||||
template <typename Scalar, int Dimension> struct TPoint;
|
||||
template <typename Point, typename Vector> struct TRay;
|
||||
template <typename Point> struct TBoundingBox;
|
||||
|
||||
/* Basic Nori data structures (vectors, points, rays, bounding boxes,
|
||||
kd-trees) are oblivious to the underlying data type and dimension.
|
||||
The following list of typedefs establishes some convenient aliases
|
||||
for specific types. */
|
||||
typedef TVector<float, 1> Vector1f;
|
||||
typedef TVector<float, 2> Vector2f;
|
||||
typedef TVector<float, 3> Vector3f;
|
||||
typedef TVector<float, 4> Vector4f;
|
||||
typedef TVector<double, 1> Vector1d;
|
||||
typedef TVector<double, 2> Vector2d;
|
||||
typedef TVector<double, 3> Vector3d;
|
||||
typedef TVector<double, 4> Vector4d;
|
||||
typedef TVector<int, 1> Vector1i;
|
||||
typedef TVector<int, 2> Vector2i;
|
||||
typedef TVector<int, 3> Vector3i;
|
||||
typedef TVector<int, 4> Vector4i;
|
||||
typedef TPoint<float, 1> Point1f;
|
||||
typedef TPoint<float, 2> Point2f;
|
||||
typedef TPoint<float, 3> Point3f;
|
||||
typedef TPoint<float, 4> Point4f;
|
||||
typedef TPoint<double, 1> Point1d;
|
||||
typedef TPoint<double, 2> Point2d;
|
||||
typedef TPoint<double, 3> Point3d;
|
||||
typedef TPoint<double, 4> Point4d;
|
||||
typedef TPoint<int, 1> Point1i;
|
||||
typedef TPoint<int, 2> Point2i;
|
||||
typedef TPoint<int, 3> Point3i;
|
||||
typedef TPoint<int, 4> Point4i;
|
||||
typedef TBoundingBox<Point1f> BoundingBox1f;
|
||||
typedef TBoundingBox<Point2f> BoundingBox2f;
|
||||
typedef TBoundingBox<Point3f> BoundingBox3f;
|
||||
typedef TBoundingBox<Point4f> BoundingBox4f;
|
||||
typedef TBoundingBox<Point1d> BoundingBox1d;
|
||||
typedef TBoundingBox<Point2d> BoundingBox2d;
|
||||
typedef TBoundingBox<Point3d> BoundingBox3d;
|
||||
typedef TBoundingBox<Point4d> BoundingBox4d;
|
||||
typedef TBoundingBox<Point1i> BoundingBox1i;
|
||||
typedef TBoundingBox<Point2i> BoundingBox2i;
|
||||
typedef TBoundingBox<Point3i> BoundingBox3i;
|
||||
typedef TBoundingBox<Point4i> BoundingBox4i;
|
||||
typedef TRay<Point2f, Vector2f> Ray2f;
|
||||
typedef TRay<Point3f, Vector3f> Ray3f;
|
||||
|
||||
/// Some more forward declarations
|
||||
class BSDF;
|
||||
class Bitmap;
|
||||
class BlockGenerator;
|
||||
class Camera;
|
||||
class ImageBlock;
|
||||
class Integrator;
|
||||
class KDTree;
|
||||
class Emitter;
|
||||
struct EmitterQueryRecord;
|
||||
class Mesh;
|
||||
class NoriObject;
|
||||
class NoriObjectFactory;
|
||||
class NoriScreen;
|
||||
class PhaseFunction;
|
||||
class ReconstructionFilter;
|
||||
class Sampler;
|
||||
class Scene;
|
||||
|
||||
/// Import cout, cerr, endl for debugging purposes
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> MatrixXf;
|
||||
typedef Eigen::Matrix<uint32_t, Eigen::Dynamic, Eigen::Dynamic> MatrixXu;
|
||||
|
||||
/// Simple exception class, which stores a human-readable error description
|
||||
class NoriException : public std::runtime_error {
|
||||
public:
|
||||
/// Variadic template constructor to support printf-style arguments
|
||||
template <typename... Args> NoriException(const char *fmt, const Args &... args)
|
||||
: std::runtime_error(tfm::format(fmt, args...)) { }
|
||||
};
|
||||
|
||||
/// Return the number of cores (real and virtual)
|
||||
extern int getCoreCount();
|
||||
|
||||
/// Indent a string by the specified number of spaces
|
||||
extern std::string indent(const std::string &string, int amount = 2);
|
||||
|
||||
/// Convert a string to lower case
|
||||
extern std::string toLower(const std::string &value);
|
||||
|
||||
/// Convert a string into an boolean value
|
||||
extern bool toBool(const std::string &str);
|
||||
|
||||
/// Convert a string into a signed integer value
|
||||
extern int toInt(const std::string &str);
|
||||
|
||||
/// Convert a string into an unsigned integer value
|
||||
extern unsigned int toUInt(const std::string &str);
|
||||
|
||||
/// Convert a string into a floating point value
|
||||
extern float toFloat(const std::string &str);
|
||||
|
||||
/// Convert a string into a 3D vector
|
||||
extern Eigen::Vector3f toVector3f(const std::string &str);
|
||||
|
||||
/// Tokenize a string into a list by splitting at 'delim'
|
||||
extern std::vector<std::string> tokenize(const std::string &s, const std::string &delim = ", ", bool includeEmpty = false);
|
||||
|
||||
/// Check if a string ends with another string
|
||||
extern bool endsWith(const std::string &value, const std::string &ending);
|
||||
|
||||
/// Convert a time value in milliseconds into a human-readable string
|
||||
extern std::string timeString(double time, bool precise = false);
|
||||
|
||||
/// Convert a memory amount in bytes into a human-readable string
|
||||
extern std::string memString(size_t size, bool precise = false);
|
||||
|
||||
/// Measures associated with probability distributions
|
||||
enum EMeasure {
|
||||
EUnknownMeasure = 0,
|
||||
ESolidAngle,
|
||||
EDiscrete
|
||||
};
|
||||
|
||||
//// Convert radians to degrees
|
||||
inline float radToDeg(float value) { return value * (180.0f / M_PI); }
|
||||
|
||||
/// Convert degrees to radians
|
||||
inline float degToRad(float value) { return value * (M_PI / 180.0f); }
|
||||
|
||||
#if !defined(_GNU_SOURCE)
|
||||
/// Emulate sincosf using sinf() and cosf()
|
||||
inline void sincosf(float theta, float *_sin, float *_cos) {
|
||||
*_sin = sinf(theta);
|
||||
*_cos = cosf(theta);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Simple floating point clamping function
|
||||
inline float clamp(float value, float min, float max) {
|
||||
if (value < min)
|
||||
return min;
|
||||
else if (value > max)
|
||||
return max;
|
||||
else return value;
|
||||
}
|
||||
|
||||
/// Simple integer clamping function
|
||||
inline int clamp(int value, int min, int max) {
|
||||
if (value < min)
|
||||
return min;
|
||||
else if (value > max)
|
||||
return max;
|
||||
else return value;
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two values
|
||||
inline float lerp(float t, float v1, float v2) {
|
||||
return ((float) 1 - t) * v1 + t * v2;
|
||||
}
|
||||
|
||||
/// Always-positive modulo operation
|
||||
inline int mod(int a, int b) {
|
||||
int r = a % b;
|
||||
return (r < 0) ? r+b : r;
|
||||
}
|
||||
|
||||
/// Compute a direction for the given coordinates in spherical coordinates
|
||||
extern Vector3f sphericalDirection(float theta, float phi);
|
||||
|
||||
/// Compute a direction for the given coordinates in spherical coordinates
|
||||
extern Point2f sphericalCoordinates(const Vector3f &dir);
|
||||
|
||||
/**
|
||||
* \brief Calculates the unpolarized fresnel reflection coefficient for a
|
||||
* dielectric material. Handles incidence from either side (i.e.
|
||||
* \code cosThetaI<0 is allowed).
|
||||
*
|
||||
* \param cosThetaI
|
||||
* Cosine of the angle between the normal and the incident ray
|
||||
* \param extIOR
|
||||
* Refractive index of the side that contains the surface normal
|
||||
* \param intIOR
|
||||
* Refractive index of the interior
|
||||
*/
|
||||
extern float fresnel(float cosThetaI, float extIOR, float intIOR);
|
||||
|
||||
/**
|
||||
* \brief Return the global file resolver instance
|
||||
*
|
||||
* This class is used to locate resource files (e.g. mesh or
|
||||
* texture files) referenced by a scene being loaded
|
||||
*/
|
||||
extern filesystem::resolver *getFileResolver();
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/common.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Discrete probability distribution
|
||||
*
|
||||
* This data structure can be used to transform uniformly distributed
|
||||
* samples to a stored discrete probability distribution.
|
||||
*
|
||||
* \ingroup libcore
|
||||
*/
|
||||
struct DiscretePDF {
|
||||
public:
|
||||
/// Allocate memory for a distribution with the given number of entries
|
||||
explicit DiscretePDF(size_t nEntries = 0) {
|
||||
reserve(nEntries);
|
||||
clear();
|
||||
}
|
||||
|
||||
/// Clear all entries
|
||||
void clear() {
|
||||
m_cdf.clear();
|
||||
m_cdf.push_back(0.0f);
|
||||
m_normalized = false;
|
||||
}
|
||||
|
||||
/// Reserve memory for a certain number of entries
|
||||
void reserve(size_t nEntries) {
|
||||
m_cdf.reserve(nEntries+1);
|
||||
}
|
||||
|
||||
/// Append an entry with the specified discrete probability
|
||||
void append(float pdfValue) {
|
||||
m_cdf.push_back(m_cdf[m_cdf.size()-1] + pdfValue);
|
||||
}
|
||||
|
||||
/// Return the number of entries so far
|
||||
size_t size() const {
|
||||
return m_cdf.size()-1;
|
||||
}
|
||||
|
||||
/// Access an entry by its index
|
||||
float operator[](size_t entry) const {
|
||||
return m_cdf[entry+1] - m_cdf[entry];
|
||||
}
|
||||
|
||||
/// Have the probability densities been normalized?
|
||||
bool isNormalized() const {
|
||||
return m_normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return the original (unnormalized) sum of all PDF entries
|
||||
*
|
||||
* This assumes that \ref normalize() has previously been called
|
||||
*/
|
||||
float getSum() const {
|
||||
return m_sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return the normalization factor (i.e. the inverse of \ref getSum())
|
||||
*
|
||||
* This assumes that \ref normalize() has previously been called
|
||||
*/
|
||||
float getNormalization() const {
|
||||
return m_normalization;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Normalize the distribution
|
||||
*
|
||||
* \return Sum of the (previously unnormalized) entries
|
||||
*/
|
||||
float normalize() {
|
||||
m_sum = m_cdf[m_cdf.size()-1];
|
||||
if (m_sum > 0) {
|
||||
m_normalization = 1.0f / m_sum;
|
||||
for (size_t i=1; i<m_cdf.size(); ++i)
|
||||
m_cdf[i] *= m_normalization;
|
||||
m_cdf[m_cdf.size()-1] = 1.0f;
|
||||
m_normalized = true;
|
||||
} else {
|
||||
m_normalization = 0.0f;
|
||||
}
|
||||
return m_sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief %Transform a uniformly distributed sample to the stored distribution
|
||||
*
|
||||
* \param[in] sampleValue
|
||||
* An uniformly distributed sample on [0,1]
|
||||
* \return
|
||||
* The discrete index associated with the sample
|
||||
*/
|
||||
size_t sample(float sampleValue) const {
|
||||
std::vector<float>::const_iterator entry =
|
||||
std::lower_bound(m_cdf.begin(), m_cdf.end(), sampleValue);
|
||||
size_t index = (size_t) std::max((ptrdiff_t) 0, entry - m_cdf.begin() - 1);
|
||||
return std::min(index, m_cdf.size()-2);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief %Transform a uniformly distributed sample to the stored distribution
|
||||
*
|
||||
* \param[in] sampleValue
|
||||
* An uniformly distributed sample on [0,1]
|
||||
* \param[out] pdf
|
||||
* Probability value of the sample
|
||||
* \return
|
||||
* The discrete index associated with the sample
|
||||
*/
|
||||
size_t sample(float sampleValue, float &pdf) const {
|
||||
size_t index = sample(sampleValue);
|
||||
pdf = operator[](index);
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief %Transform a uniformly distributed sample to the stored distribution
|
||||
*
|
||||
* The original sample is value adjusted so that it can be "reused".
|
||||
*
|
||||
* \param[in, out] sampleValue
|
||||
* An uniformly distributed sample on [0,1]
|
||||
* \return
|
||||
* The discrete index associated with the sample
|
||||
*/
|
||||
size_t sampleReuse(float &sampleValue) const {
|
||||
size_t index = sample(sampleValue);
|
||||
sampleValue = (sampleValue - m_cdf[index])
|
||||
/ (m_cdf[index + 1] - m_cdf[index]);
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief %Transform a uniformly distributed sample.
|
||||
*
|
||||
* The original sample is value adjusted so that it can be "reused".
|
||||
*
|
||||
* \param[in,out]
|
||||
* An uniformly distributed sample on [0,1]
|
||||
* \param[out] pdf
|
||||
* Probability value of the sample
|
||||
* \return
|
||||
* The discrete index associated with the sample
|
||||
*/
|
||||
size_t sampleReuse(float &sampleValue, float &pdf) const {
|
||||
size_t index = sample(sampleValue, pdf);
|
||||
sampleValue = (sampleValue - m_cdf[index])
|
||||
/ (m_cdf[index + 1] - m_cdf[index]);
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Turn the underlying distribution into a
|
||||
* human-readable string format
|
||||
*/
|
||||
std::string toString() const {
|
||||
std::string result = tfm::format("DiscretePDF[sum=%f, "
|
||||
"normalized=%f, pdf = {", m_sum, m_normalized);
|
||||
|
||||
for (size_t i=0; i<m_cdf.size(); ++i) {
|
||||
result += std::to_string(operator[](i));
|
||||
if (i != m_cdf.size()-1)
|
||||
result += ", ";
|
||||
}
|
||||
return result + "}]";
|
||||
}
|
||||
private:
|
||||
std::vector<float> m_cdf;
|
||||
float m_sum, m_normalization;
|
||||
bool m_normalized;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/object.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Superclass of all emitters
|
||||
*/
|
||||
class Emitter : public NoriObject {
|
||||
public:
|
||||
|
||||
/**
|
||||
* \brief Return the type of object (i.e. Mesh/Emitter/etc.)
|
||||
* provided by this instance
|
||||
* */
|
||||
EClassType getClassType() const { return EEmitter; }
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/vector.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Stores a three-dimensional orthonormal coordinate frame
|
||||
*
|
||||
* This class is mostly used to quickly convert between different
|
||||
* cartesian coordinate systems and to efficiently compute certain
|
||||
* quantities (e.g. \ref cosTheta(), \ref tanTheta, ..).
|
||||
*/
|
||||
struct Frame {
|
||||
Vector3f s, t;
|
||||
Normal3f n;
|
||||
|
||||
/// Default constructor -- performs no initialization!
|
||||
Frame() { }
|
||||
|
||||
/// Given a normal and tangent vectors, construct a new coordinate frame
|
||||
Frame(const Vector3f &s, const Vector3f &t, const Normal3f &n)
|
||||
: s(s), t(t), n(n) { }
|
||||
|
||||
/// Construct a frame from the given orthonormal vectors
|
||||
Frame(const Vector3f &x, const Vector3f &y, const Vector3f &z)
|
||||
: s(x), t(y), n(z) { }
|
||||
|
||||
/// Construct a new coordinate frame from a single vector
|
||||
Frame(const Vector3f &n) : n(n) {
|
||||
coordinateSystem(n, s, t);
|
||||
}
|
||||
|
||||
/// Convert from world coordinates to local coordinates
|
||||
Vector3f toLocal(const Vector3f &v) const {
|
||||
return Vector3f(
|
||||
v.dot(s), v.dot(t), v.dot(n)
|
||||
);
|
||||
}
|
||||
|
||||
/// Convert from local coordinates to world coordinates
|
||||
Vector3f toWorld(const Vector3f &v) const {
|
||||
return s * v.x() + t * v.y() + n * v.z();
|
||||
}
|
||||
|
||||
/** \brief Assuming that the given direction is in the local coordinate
|
||||
* system, return the cosine of the angle between the normal and v */
|
||||
static float cosTheta(const Vector3f &v) {
|
||||
return v.z();
|
||||
}
|
||||
|
||||
/** \brief Assuming that the given direction is in the local coordinate
|
||||
* system, return the sine of the angle between the normal and v */
|
||||
static float sinTheta(const Vector3f &v) {
|
||||
float temp = sinTheta2(v);
|
||||
if (temp <= 0.0f)
|
||||
return 0.0f;
|
||||
return std::sqrt(temp);
|
||||
}
|
||||
|
||||
/** \brief Assuming that the given direction is in the local coordinate
|
||||
* system, return the tangent of the angle between the normal and v */
|
||||
static float tanTheta(const Vector3f &v) {
|
||||
float temp = 1 - v.z()*v.z();
|
||||
if (temp <= 0.0f)
|
||||
return 0.0f;
|
||||
return std::sqrt(temp) / v.z();
|
||||
}
|
||||
|
||||
/** \brief Assuming that the given direction is in the local coordinate
|
||||
* system, return the squared sine of the angle between the normal and v */
|
||||
static float sinTheta2(const Vector3f &v) {
|
||||
return 1.0f - v.z() * v.z();
|
||||
}
|
||||
|
||||
/** \brief Assuming that the given direction is in the local coordinate
|
||||
* system, return the sine of the phi parameter in spherical coordinates */
|
||||
static float sinPhi(const Vector3f &v) {
|
||||
float sinTheta = Frame::sinTheta(v);
|
||||
if (sinTheta == 0.0f)
|
||||
return 1.0f;
|
||||
return clamp(v.y() / sinTheta, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
/** \brief Assuming that the given direction is in the local coordinate
|
||||
* system, return the cosine of the phi parameter in spherical coordinates */
|
||||
static float cosPhi(const Vector3f &v) {
|
||||
float sinTheta = Frame::sinTheta(v);
|
||||
if (sinTheta == 0.0f)
|
||||
return 1.0f;
|
||||
return clamp(v.x() / sinTheta, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
/** \brief Assuming that the given direction is in the local coordinate
|
||||
* system, return the squared sine of the phi parameter in spherical
|
||||
* coordinates */
|
||||
static float sinPhi2(const Vector3f &v) {
|
||||
return clamp(v.y() * v.y() / sinTheta2(v), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
/** \brief Assuming that the given direction is in the local coordinate
|
||||
* system, return the squared cosine of the phi parameter in spherical
|
||||
* coordinates */
|
||||
static float cosPhi2(const Vector3f &v) {
|
||||
return clamp(v.x() * v.x() / sinTheta2(v), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
/// Equality test
|
||||
bool operator==(const Frame &frame) const {
|
||||
return frame.s == s && frame.t == t && frame.n == n;
|
||||
}
|
||||
|
||||
/// Inequality test
|
||||
bool operator!=(const Frame &frame) const {
|
||||
return !operator==(frame);
|
||||
}
|
||||
|
||||
/// Return a human-readable string summary of this frame
|
||||
std::string toString() const {
|
||||
return tfm::format(
|
||||
"Frame[\n"
|
||||
" s = %s,\n"
|
||||
" t = %s,\n"
|
||||
" n = %s\n"
|
||||
"]", s.toString(), t.toString(), n.toString());
|
||||
}
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/common.h>
|
||||
#include <nanogui/screen.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
class NoriScreen : public nanogui::Screen {
|
||||
public:
|
||||
NoriScreen(const ImageBlock &block);
|
||||
void draw_contents() override;
|
||||
private:
|
||||
const ImageBlock &m_block;
|
||||
nanogui::ref<nanogui::Shader> m_shader;
|
||||
nanogui::ref<nanogui::Texture> m_texture;
|
||||
nanogui::ref<nanogui::RenderPass> m_renderPass;
|
||||
float m_scale = 1.f;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/object.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Abstract integrator (i.e. a rendering technique)
|
||||
*
|
||||
* In Nori, the different rendering techniques are collectively referred to as
|
||||
* integrators, since they perform integration over a high-dimensional
|
||||
* space. Each integrator represents a specific approach for solving
|
||||
* the light transport equation---usually favored in certain scenarios, but
|
||||
* at the same time affected by its own set of intrinsic limitations.
|
||||
*/
|
||||
class Integrator : public NoriObject {
|
||||
public:
|
||||
/// Release all memory
|
||||
virtual ~Integrator() { }
|
||||
|
||||
/// Perform an (optional) preprocess step
|
||||
virtual void preprocess(const Scene *scene) { }
|
||||
|
||||
/**
|
||||
* \brief Sample the incident radiance along a ray
|
||||
*
|
||||
* \param scene
|
||||
* A pointer to the underlying scene
|
||||
* \param sampler
|
||||
* A pointer to a sample generator
|
||||
* \param ray
|
||||
* The ray in question
|
||||
* \return
|
||||
* A (usually) unbiased estimate of the radiance in this direction
|
||||
*/
|
||||
virtual Color3f Li(const Scene *scene, Sampler *sampler, const Ray3f &ray) const = 0;
|
||||
|
||||
/**
|
||||
* \brief Return the type of object (i.e. Mesh/BSDF/etc.)
|
||||
* provided by this instance
|
||||
* */
|
||||
EClassType getClassType() const { return EIntegrator; }
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,181 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/object.h>
|
||||
#include <nori/frame.h>
|
||||
#include <nori/bbox.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Intersection data structure
|
||||
*
|
||||
* This data structure records local information about a ray-triangle intersection.
|
||||
* This includes the position, traveled ray distance, uv coordinates, as well
|
||||
* as well as two local coordinate frames (one that corresponds to the true
|
||||
* geometry, and one that is used for shading computations).
|
||||
*/
|
||||
struct Intersection {
|
||||
/// Position of the surface intersection
|
||||
Point3f p;
|
||||
/// Unoccluded distance along the ray
|
||||
float t;
|
||||
/// UV coordinates, if any
|
||||
Point2f uv;
|
||||
/// Shading frame (based on the shading normal)
|
||||
Frame shFrame;
|
||||
/// Geometric frame (based on the true geometry)
|
||||
Frame geoFrame;
|
||||
/// Pointer to the associated mesh
|
||||
const Mesh *mesh;
|
||||
|
||||
/// Create an uninitialized intersection record
|
||||
Intersection() : mesh(nullptr) { }
|
||||
|
||||
/// Transform a direction vector into the local shading frame
|
||||
Vector3f toLocal(const Vector3f &d) const {
|
||||
return shFrame.toLocal(d);
|
||||
}
|
||||
|
||||
/// Transform a direction vector from local to world coordinates
|
||||
Vector3f toWorld(const Vector3f &d) const {
|
||||
return shFrame.toWorld(d);
|
||||
}
|
||||
|
||||
/// Return a human-readable summary of the intersection record
|
||||
std::string toString() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Triangle mesh
|
||||
*
|
||||
* This class stores a triangle mesh object and provides numerous functions
|
||||
* for querying the individual triangles. Subclasses of \c Mesh implement
|
||||
* the specifics of how to create its contents (e.g. by loading from an
|
||||
* external file)
|
||||
*/
|
||||
class Mesh : public NoriObject {
|
||||
public:
|
||||
/// Release all memory
|
||||
virtual ~Mesh();
|
||||
|
||||
/// Initialize internal data structures (called once by the XML parser)
|
||||
virtual void activate();
|
||||
|
||||
/// Return the total number of triangles in this shape
|
||||
uint32_t getTriangleCount() const { return (uint32_t) m_F.cols(); }
|
||||
|
||||
/// Return the total number of vertices in this shape
|
||||
uint32_t getVertexCount() const { return (uint32_t) m_V.cols(); }
|
||||
|
||||
/// Return the surface area of the given triangle
|
||||
float surfaceArea(uint32_t index) const;
|
||||
|
||||
//// Return an axis-aligned bounding box of the entire mesh
|
||||
const BoundingBox3f &getBoundingBox() const { return m_bbox; }
|
||||
|
||||
//// Return an axis-aligned bounding box containing the given triangle
|
||||
BoundingBox3f getBoundingBox(uint32_t index) const;
|
||||
|
||||
//// Return the centroid of the given triangle
|
||||
Point3f getCentroid(uint32_t index) const;
|
||||
|
||||
/** \brief Ray-triangle intersection test
|
||||
*
|
||||
* Uses the algorithm by Moeller and Trumbore discussed at
|
||||
* <tt>http://www.acm.org/jgt/papers/MollerTrumbore97/code.html</tt>.
|
||||
*
|
||||
* Note that the test only applies to a single triangle in the mesh.
|
||||
* An acceleration data structure like \ref BVH is needed to search
|
||||
* for intersections against many triangles.
|
||||
*
|
||||
* \param index
|
||||
* Index of the triangle that should be intersected
|
||||
* \param ray
|
||||
* The ray segment to be used for the intersection query
|
||||
* \param t
|
||||
* Upon success, \a t contains the distance from the ray origin to the
|
||||
* intersection point,
|
||||
* \param u
|
||||
* Upon success, \c u will contain the 'U' component of the intersection
|
||||
* in barycentric coordinates
|
||||
* \param v
|
||||
* Upon success, \c v will contain the 'V' component of the intersection
|
||||
* in barycentric coordinates
|
||||
* \return
|
||||
* \c true if an intersection has been detected
|
||||
*/
|
||||
bool rayIntersect(uint32_t index, const Ray3f &ray, float &u, float &v, float &t) const;
|
||||
|
||||
/// Return a pointer to the vertex positions
|
||||
const MatrixXf &getVertexPositions() const { return m_V; }
|
||||
|
||||
/// Return a pointer to the vertex normals (or \c nullptr if there are none)
|
||||
const MatrixXf &getVertexNormals() const { return m_N; }
|
||||
|
||||
/// Return a pointer to the texture coordinates (or \c nullptr if there are none)
|
||||
const MatrixXf &getVertexTexCoords() const { return m_UV; }
|
||||
|
||||
/// Return a pointer to the triangle vertex index list
|
||||
const MatrixXu &getIndices() const { return m_F; }
|
||||
|
||||
/// Is this mesh an area emitter?
|
||||
bool isEmitter() const { return m_emitter != nullptr; }
|
||||
|
||||
/// Return a pointer to an attached area emitter instance
|
||||
Emitter *getEmitter() { return m_emitter; }
|
||||
|
||||
/// Return a pointer to an attached area emitter instance (const version)
|
||||
const Emitter *getEmitter() const { return m_emitter; }
|
||||
|
||||
/// Return a pointer to the BSDF associated with this mesh
|
||||
const BSDF *getBSDF() const { return m_bsdf; }
|
||||
|
||||
/// Register a child object (e.g. a BSDF) with the mesh
|
||||
virtual void addChild(NoriObject *child);
|
||||
|
||||
/// Return the name of this mesh
|
||||
const std::string &getName() const { return m_name; }
|
||||
|
||||
/// Return a human-readable summary of this instance
|
||||
std::string toString() const;
|
||||
|
||||
/**
|
||||
* \brief Return the type of object (i.e. Mesh/BSDF/etc.)
|
||||
* provided by this instance
|
||||
* */
|
||||
EClassType getClassType() const { return EMesh; }
|
||||
|
||||
protected:
|
||||
/// Create an empty mesh
|
||||
Mesh();
|
||||
|
||||
protected:
|
||||
std::string m_name; ///< Identifying name
|
||||
MatrixXf m_V; ///< Vertex positions
|
||||
MatrixXf m_N; ///< Vertex normals
|
||||
MatrixXf m_UV; ///< Vertex texture coordinates
|
||||
MatrixXu m_F; ///< Faces
|
||||
BSDF *m_bsdf = nullptr; ///< BSDF of the surface
|
||||
Emitter *m_emitter = nullptr; ///< Associated emitter, if any
|
||||
BoundingBox3f m_bbox; ///< Bounding box of the mesh
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/proplist.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Base class of all objects
|
||||
*
|
||||
* A Nori object represents an instance that is part of
|
||||
* a scene description, e.g. a scattering model or emitter.
|
||||
*/
|
||||
class NoriObject {
|
||||
public:
|
||||
enum EClassType {
|
||||
EScene = 0,
|
||||
EMesh,
|
||||
EBSDF,
|
||||
EPhaseFunction,
|
||||
EEmitter,
|
||||
EMedium,
|
||||
ECamera,
|
||||
EIntegrator,
|
||||
ESampler,
|
||||
ETest,
|
||||
EReconstructionFilter,
|
||||
EClassTypeCount
|
||||
};
|
||||
|
||||
/// Virtual destructor
|
||||
virtual ~NoriObject() { }
|
||||
|
||||
/**
|
||||
* \brief Return the type of object (i.e. Mesh/BSDF/etc.)
|
||||
* provided by this instance
|
||||
* */
|
||||
virtual EClassType getClassType() const = 0;
|
||||
|
||||
/**
|
||||
* \brief Add a child object to the current instance
|
||||
*
|
||||
* The default implementation does not support children and
|
||||
* simply throws an exception
|
||||
*/
|
||||
virtual void addChild(NoriObject *child);
|
||||
|
||||
/**
|
||||
* \brief Set the parent object
|
||||
*
|
||||
* Subclasses may choose to override this method to be
|
||||
* notified when they are added to a parent object. The
|
||||
* default implementation does nothing.
|
||||
*/
|
||||
virtual void setParent(NoriObject *parent);
|
||||
|
||||
/**
|
||||
* \brief Perform some action associated with the object
|
||||
*
|
||||
* The default implementation throws an exception. Certain objects
|
||||
* may choose to override it, e.g. to implement initialization,
|
||||
* testing, or rendering functionality.
|
||||
*
|
||||
* This function is called by the XML parser once it has
|
||||
* constructed an object and added all of its children
|
||||
* using \ref addChild().
|
||||
*/
|
||||
virtual void activate();
|
||||
|
||||
/// Return a brief string summary of the instance (for debugging purposes)
|
||||
virtual std::string toString() const = 0;
|
||||
|
||||
/// Turn a class type into a human-readable string
|
||||
static std::string classTypeName(EClassType type) {
|
||||
switch (type) {
|
||||
case EScene: return "scene";
|
||||
case EMesh: return "mesh";
|
||||
case EBSDF: return "bsdf";
|
||||
case EEmitter: return "emitter";
|
||||
case ECamera: return "camera";
|
||||
case EIntegrator: return "integrator";
|
||||
case ESampler: return "sampler";
|
||||
case ETest: return "test";
|
||||
default: return "<unknown>";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Factory for Nori objects
|
||||
*
|
||||
* This utility class is part of a mini-RTTI framework and can
|
||||
* instantiate arbitrary Nori objects by their name.
|
||||
*/
|
||||
class NoriObjectFactory {
|
||||
public:
|
||||
typedef std::function<NoriObject *(const PropertyList &)> Constructor;
|
||||
|
||||
/**
|
||||
* \brief Register an object constructor with the object factory
|
||||
*
|
||||
* This function is called by the macro \ref NORI_REGISTER_CLASS
|
||||
*
|
||||
* \param name
|
||||
* An internal name that is associated with this class. This is the
|
||||
* 'type' field found in the scene description XML files
|
||||
*
|
||||
* \param constr
|
||||
* A function pointer to an anonymous function that is
|
||||
* able to call the constructor of the class.
|
||||
*/
|
||||
static void registerClass(const std::string &name, const Constructor &constr);
|
||||
|
||||
/**
|
||||
* \brief Construct an instance from the class of the given name
|
||||
*
|
||||
* \param name
|
||||
* An internal name that is associated with this class. This is the
|
||||
* 'type' field found in the scene description XML files
|
||||
*
|
||||
* \param propList
|
||||
* A list of properties that will be passed to the constructor
|
||||
* of the class.
|
||||
*/
|
||||
static NoriObject *createInstance(const std::string &name,
|
||||
const PropertyList &propList) {
|
||||
if (!m_constructors || m_constructors->find(name) == m_constructors->end())
|
||||
throw NoriException("A constructor for class \"%s\" could not be found!", name);
|
||||
return (*m_constructors)[name](propList);
|
||||
}
|
||||
private:
|
||||
static std::map<std::string, Constructor> *m_constructors;
|
||||
};
|
||||
|
||||
/// Macro for registering an object constructor with the \ref NoriObjectFactory
|
||||
#define NORI_REGISTER_CLASS(cls, name) \
|
||||
cls *cls ##_create(const PropertyList &list) { \
|
||||
return new cls(list); \
|
||||
} \
|
||||
static struct cls ##_{ \
|
||||
cls ##_() { \
|
||||
NoriObjectFactory::registerClass(name, cls ##_create); \
|
||||
} \
|
||||
} cls ##__NORI_;
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/object.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Load a scene from the specified filename and
|
||||
* return its root object
|
||||
*/
|
||||
extern NoriObject *loadFromXML(const std::string &filename);
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/color.h>
|
||||
#include <nori/transform.h>
|
||||
#include <map>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief This is an associative container used to supply the constructors
|
||||
* of \ref NoriObject subclasses with parameter information.
|
||||
*/
|
||||
class PropertyList {
|
||||
public:
|
||||
PropertyList() { }
|
||||
|
||||
/// Set a boolean property
|
||||
void setBoolean(const std::string &name, const bool &value);
|
||||
|
||||
/// Get a boolean property, and throw an exception if it does not exist
|
||||
bool getBoolean(const std::string &name) const;
|
||||
|
||||
/// Get a boolean property, and use a default value if it does not exist
|
||||
bool getBoolean(const std::string &name, const bool &defaultValue) const;
|
||||
|
||||
/// Set an integer property
|
||||
void setInteger(const std::string &name, const int &value);
|
||||
|
||||
/// Get an integer property, and throw an exception if it does not exist
|
||||
int getInteger(const std::string &name) const;
|
||||
|
||||
/// Get am integer property, and use a default value if it does not exist
|
||||
int getInteger(const std::string &name, const int &defaultValue) const;
|
||||
|
||||
/// Set a float property
|
||||
void setFloat(const std::string &name, const float &value);
|
||||
|
||||
/// Get a float property, and throw an exception if it does not exist
|
||||
float getFloat(const std::string &name) const;
|
||||
|
||||
/// Get a float property, and use a default value if it does not exist
|
||||
float getFloat(const std::string &name, const float &defaultValue) const;
|
||||
|
||||
/// Set a string property
|
||||
void setString(const std::string &name, const std::string &value);
|
||||
|
||||
/// Get a string property, and throw an exception if it does not exist
|
||||
std::string getString(const std::string &name) const;
|
||||
|
||||
/// Get a string property, and use a default value if it does not exist
|
||||
std::string getString(const std::string &name, const std::string &defaultValue) const;
|
||||
|
||||
/// Set a color property
|
||||
void setColor(const std::string &name, const Color3f &value);
|
||||
|
||||
/// Get a color property, and throw an exception if it does not exist
|
||||
Color3f getColor(const std::string &name) const;
|
||||
|
||||
/// Get a color property, and use a default value if it does not exist
|
||||
Color3f getColor(const std::string &name, const Color3f &defaultValue) const;
|
||||
|
||||
/// Set a point property
|
||||
void setPoint(const std::string &name, const Point3f &value);
|
||||
|
||||
/// Get a point property, and throw an exception if it does not exist
|
||||
Point3f getPoint(const std::string &name) const;
|
||||
|
||||
/// Get a point property, and use a default value if it does not exist
|
||||
Point3f getPoint(const std::string &name, const Point3f &defaultValue) const;
|
||||
|
||||
/// Set a vector property
|
||||
void setVector(const std::string &name, const Vector3f &value);
|
||||
|
||||
/// Get a vector property, and throw an exception if it does not exist
|
||||
Vector3f getVector(const std::string &name) const;
|
||||
|
||||
/// Get a vector property, and use a default value if it does not exist
|
||||
Vector3f getVector(const std::string &name, const Vector3f &defaultValue) const;
|
||||
|
||||
/// Set a transform property
|
||||
void setTransform(const std::string &name, const Transform &value);
|
||||
|
||||
/// Get a transform property, and throw an exception if it does not exist
|
||||
Transform getTransform(const std::string &name) const;
|
||||
|
||||
/// Get a transform property, and use a default value if it does not exist
|
||||
Transform getTransform(const std::string &name, const Transform &defaultValue) const;
|
||||
private:
|
||||
/* Custom variant data type (stores one of boolean/integer/float/...) */
|
||||
struct Property {
|
||||
enum {
|
||||
boolean_type, integer_type, float_type,
|
||||
string_type, color_type, point_type,
|
||||
vector_type, transform_type
|
||||
} type;
|
||||
|
||||
/* Visual studio lacks support for unrestricted unions (as of ver. 2013) */
|
||||
struct Value
|
||||
{
|
||||
Value() : boolean_value(false) { }
|
||||
~Value() { }
|
||||
|
||||
bool boolean_value;
|
||||
int integer_value;
|
||||
float float_value;
|
||||
std::string string_value;
|
||||
Color3f color_value;
|
||||
Point3f point_value;
|
||||
Vector3f vector_value;
|
||||
Transform transform_value;
|
||||
} value;
|
||||
|
||||
Property() : type(boolean_type) { }
|
||||
};
|
||||
|
||||
std::map<std::string, Property> m_properties;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/vector.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Simple n-dimensional ray segment data structure
|
||||
*
|
||||
* Along with the ray origin and direction, this data structure additionally
|
||||
* stores a ray segment [mint, maxt] (whose entries may include positive/negative
|
||||
* infinity), as well as the componentwise reciprocals of the ray direction.
|
||||
* That is just done for convenience, as these values are frequently required.
|
||||
*
|
||||
* \remark Important: be careful when changing the ray direction. You must
|
||||
* call \ref update() to compute the componentwise reciprocals as well, or Nori's
|
||||
* ray-triangle intersection code will go haywire.
|
||||
*/
|
||||
template <typename _PointType, typename _VectorType> struct TRay {
|
||||
typedef _PointType PointType;
|
||||
typedef _VectorType VectorType;
|
||||
typedef typename PointType::Scalar Scalar;
|
||||
|
||||
PointType o; ///< Ray origin
|
||||
VectorType d; ///< Ray direction
|
||||
VectorType dRcp; ///< Componentwise reciprocals of the ray direction
|
||||
Scalar mint; ///< Minimum position on the ray segment
|
||||
Scalar maxt; ///< Maximum position on the ray segment
|
||||
|
||||
/// Construct a new ray
|
||||
TRay() : mint(Epsilon),
|
||||
maxt(std::numeric_limits<Scalar>::infinity()) { }
|
||||
|
||||
/// Construct a new ray
|
||||
TRay(const PointType &o, const VectorType &d) : o(o), d(d),
|
||||
mint(Epsilon), maxt(std::numeric_limits<Scalar>::infinity()) {
|
||||
update();
|
||||
}
|
||||
|
||||
/// Construct a new ray
|
||||
TRay(const PointType &o, const VectorType &d,
|
||||
Scalar mint, Scalar maxt) : o(o), d(d), mint(mint), maxt(maxt) {
|
||||
update();
|
||||
}
|
||||
|
||||
/// Copy constructor
|
||||
TRay(const TRay &ray)
|
||||
: o(ray.o), d(ray.d), dRcp(ray.dRcp),
|
||||
mint(ray.mint), maxt(ray.maxt) { }
|
||||
|
||||
/// Copy a ray, but change the covered segment of the copy
|
||||
TRay(const TRay &ray, Scalar mint, Scalar maxt)
|
||||
: o(ray.o), d(ray.d), dRcp(ray.dRcp), mint(mint), maxt(maxt) { }
|
||||
|
||||
/// Update the reciprocal ray directions after changing 'd'
|
||||
void update() {
|
||||
dRcp = d.cwiseInverse();
|
||||
}
|
||||
|
||||
/// Return the position of a point along the ray
|
||||
PointType operator() (Scalar t) const { return o + t * d; }
|
||||
|
||||
/// Return a ray that points into the opposite direction
|
||||
TRay reverse() const {
|
||||
TRay result;
|
||||
result.o = o; result.d = -d; result.dRcp = -dRcp;
|
||||
result.mint = mint; result.maxt = maxt;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Return a human-readable string summary of this ray
|
||||
std::string toString() const {
|
||||
return tfm::format(
|
||||
"Ray[\n"
|
||||
" o = %s,\n"
|
||||
" d = %s,\n"
|
||||
" mint = %f,\n"
|
||||
" maxt = %f\n"
|
||||
"]", o.toString(), d.toString(), mint, maxt);
|
||||
}
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/object.h>
|
||||
|
||||
/// Reconstruction filters will be tabulated at this resolution
|
||||
#define NORI_FILTER_RESOLUTION 32
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Generic radially symmetric image reconstruction filter
|
||||
*
|
||||
* When adding radiance-valued samples to the rendered image, Nori
|
||||
* first convolves them with a so-called image reconstruction filter.
|
||||
*
|
||||
* To learn more about reconstruction filters and sampling theory
|
||||
* in general, take a look at the excellenent chapter 7 of PBRT,
|
||||
* which is freely available at:
|
||||
*
|
||||
* http://graphics.stanford.edu/~mmp/chapters/pbrt_chapter7.pdf
|
||||
*/
|
||||
class ReconstructionFilter : public NoriObject {
|
||||
public:
|
||||
/// Return the filter radius in fractional pixels
|
||||
float getRadius() const { return m_radius; }
|
||||
|
||||
/// Evaluate the filter function
|
||||
virtual float eval(float x) const = 0;
|
||||
|
||||
/**
|
||||
* \brief Return the type of object (i.e. Mesh/Camera/etc.)
|
||||
* provided by this instance
|
||||
* */
|
||||
EClassType getClassType() const { return EReconstructionFilter; }
|
||||
protected:
|
||||
float m_radius;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/object.h>
|
||||
#include <memory>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
class ImageBlock;
|
||||
|
||||
/**
|
||||
* \brief Abstract sample generator
|
||||
*
|
||||
* A sample generator is responsible for generating the random number stream
|
||||
* that will be passed an \ref Integrator implementation as it computes the
|
||||
* radiance incident along a specified ray.
|
||||
*
|
||||
* The most simple conceivable sample generator is just a wrapper around the
|
||||
* Mersenne-Twister random number generator and is implemented in
|
||||
* <tt>independent.cpp</tt> (it is named this way because it generates
|
||||
* statistically independent random numbers).
|
||||
*
|
||||
* Fancier samplers might use stratification or low-discrepancy sequences
|
||||
* (e.g. Halton, Hammersley, or Sobol point sets) for improved convergence.
|
||||
* Another use of this class is in producing intentionally correlated
|
||||
* random numbers, e.g. as part of a Metropolis-Hastings integration scheme.
|
||||
*
|
||||
* The general interface between a sampler and a rendering algorithm is as
|
||||
* follows: Before beginning to render a pixel, the rendering algorithm calls
|
||||
* \ref generate(). The first pixel sample can now be computed, after which
|
||||
* \ref advance() needs to be invoked. This repeats until all pixel samples have
|
||||
* been exhausted. While computing a pixel sample, the rendering
|
||||
* algorithm requests (pseudo-) random numbers using the \ref next1D() and
|
||||
* \ref next2D() functions.
|
||||
*
|
||||
* Conceptually, the right way of thinking of this goes as follows:
|
||||
* For each sample in a pixel, a sample generator produces a (hypothetical)
|
||||
* point in an infinite dimensional random number hypercube. A rendering
|
||||
* algorithm can then request subsequent 1D or 2D components of this point
|
||||
* using the \ref next1D() and \ref next2D() functions. Fancy implementations
|
||||
* of this class make certain guarantees about the stratification of the
|
||||
* first n components with respect to the other points that are sampled
|
||||
* within a pixel.
|
||||
*/
|
||||
class Sampler : public NoriObject {
|
||||
public:
|
||||
/// Release all memory
|
||||
virtual ~Sampler() { }
|
||||
|
||||
/// Create an exact clone of the current instance
|
||||
virtual std::unique_ptr<Sampler> clone() const = 0;
|
||||
|
||||
/**
|
||||
* \brief Prepare to render a new image block
|
||||
*
|
||||
* This function is called when the sampler begins rendering
|
||||
* a new image block. This can be used to deterministically
|
||||
* initialize the sampler so that repeated program runs
|
||||
* always create the same image.
|
||||
*/
|
||||
virtual void prepare(const ImageBlock &block) = 0;
|
||||
|
||||
/**
|
||||
* \brief Prepare to generate new samples
|
||||
*
|
||||
* This function is called initially and every time the
|
||||
* integrator starts rendering a new pixel.
|
||||
*/
|
||||
virtual void generate() = 0;
|
||||
|
||||
/// Advance to the next sample
|
||||
virtual void advance() = 0;
|
||||
|
||||
/// Retrieve the next component value from the current sample
|
||||
virtual float next1D() = 0;
|
||||
|
||||
/// Retrieve the next two component values from the current sample
|
||||
virtual Point2f next2D() = 0;
|
||||
|
||||
/// Return the number of configured pixel samples
|
||||
virtual size_t getSampleCount() const { return m_sampleCount; }
|
||||
|
||||
/**
|
||||
* \brief Return the type of object (i.e. Mesh/Sampler/etc.)
|
||||
* provided by this instance
|
||||
* */
|
||||
EClassType getClassType() const { return ESampler; }
|
||||
protected:
|
||||
size_t m_sampleCount;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/accel.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Main scene data structure
|
||||
*
|
||||
* This class holds information on scene objects and is responsible for
|
||||
* coordinating rendering jobs. It also provides useful query routines that
|
||||
* are mostly used by the \ref Integrator implementations.
|
||||
*/
|
||||
class Scene : public NoriObject {
|
||||
public:
|
||||
/// Construct a new scene object
|
||||
Scene(const PropertyList &);
|
||||
|
||||
/// Release all memory
|
||||
virtual ~Scene();
|
||||
|
||||
/// Return a pointer to the scene's kd-tree
|
||||
const Accel *getAccel() const { return m_accel; }
|
||||
|
||||
/// Return a pointer to the scene's integrator
|
||||
const Integrator *getIntegrator() const { return m_integrator; }
|
||||
|
||||
/// Return a pointer to the scene's integrator
|
||||
Integrator *getIntegrator() { return m_integrator; }
|
||||
|
||||
/// Return a pointer to the scene's camera
|
||||
const Camera *getCamera() const { return m_camera; }
|
||||
|
||||
/// Return a pointer to the scene's sample generator (const version)
|
||||
const Sampler *getSampler() const { return m_sampler; }
|
||||
|
||||
/// Return a pointer to the scene's sample generator
|
||||
Sampler *getSampler() { return m_sampler; }
|
||||
|
||||
/// Return a reference to an array containing all meshes
|
||||
const std::vector<Mesh *> &getMeshes() const { return m_meshes; }
|
||||
|
||||
/**
|
||||
* \brief Intersect a ray against all triangles stored in the scene
|
||||
* and return detailed intersection information
|
||||
*
|
||||
* \param ray
|
||||
* A 3-dimensional ray data structure with minimum/maximum
|
||||
* extent information
|
||||
*
|
||||
* \param its
|
||||
* A detailed intersection record, which will be filled by the
|
||||
* intersection query
|
||||
*
|
||||
* \return \c true if an intersection was found
|
||||
*/
|
||||
bool rayIntersect(const Ray3f &ray, Intersection &its) const {
|
||||
return m_accel->rayIntersect(ray, its, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Intersect a ray against all triangles stored in the scene
|
||||
* and \a only determine whether or not there is an intersection.
|
||||
*
|
||||
* This method much faster than the other ray tracing function,
|
||||
* but the performance comes at the cost of not providing any
|
||||
* additional information about the detected intersection
|
||||
* (not even its position).
|
||||
*
|
||||
* \param ray
|
||||
* A 3-dimensional ray data structure with minimum/maximum
|
||||
* extent information
|
||||
*
|
||||
* \return \c true if an intersection was found
|
||||
*/
|
||||
bool rayIntersect(const Ray3f &ray) const {
|
||||
Intersection its; /* Unused */
|
||||
return m_accel->rayIntersect(ray, its, true);
|
||||
}
|
||||
|
||||
/// \brief Return an axis-aligned box that bounds the scene
|
||||
const BoundingBox3f &getBoundingBox() const {
|
||||
return m_accel->getBoundingBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Inherited from \ref NoriObject::activate()
|
||||
*
|
||||
* Initializes the internal data structures (kd-tree,
|
||||
* emitter sampling data structures, etc.)
|
||||
*/
|
||||
void activate();
|
||||
|
||||
/// Add a child object to the scene (meshes, integrators etc.)
|
||||
void addChild(NoriObject *obj);
|
||||
|
||||
/// Return a string summary of the scene (for debugging purposes)
|
||||
std::string toString() const;
|
||||
|
||||
EClassType getClassType() const { return EScene; }
|
||||
private:
|
||||
std::vector<Mesh *> m_meshes;
|
||||
Integrator *m_integrator = nullptr;
|
||||
Sampler *m_sampler = nullptr;
|
||||
Camera *m_camera = nullptr;
|
||||
Accel *m_accel = nullptr;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/common.h>
|
||||
#include <chrono>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Simple timer with millisecond precision
|
||||
*
|
||||
* This class is convenient for collecting performance data
|
||||
*/
|
||||
class Timer {
|
||||
public:
|
||||
/// Create a new timer and reset it
|
||||
Timer() { reset(); }
|
||||
|
||||
/// Reset the timer to the current time
|
||||
void reset() { start = std::chrono::system_clock::now(); }
|
||||
|
||||
/// Return the number of milliseconds elapsed since the timer was last reset
|
||||
double elapsed() const {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
|
||||
return (double) duration.count();
|
||||
}
|
||||
|
||||
/// Like \ref elapsed(), but return a human-readable string
|
||||
std::string elapsedString(bool precise = false) const {
|
||||
return timeString(elapsed(), precise);
|
||||
}
|
||||
|
||||
/// Return the number of milliseconds elapsed since the timer was last reset and then reset it
|
||||
double lap() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
|
||||
start = now;
|
||||
return (double) duration.count();
|
||||
}
|
||||
|
||||
/// Like \ref lap(), but return a human-readable string
|
||||
std::string lapString(bool precise = false) {
|
||||
return timeString(lap(), precise);
|
||||
}
|
||||
private:
|
||||
std::chrono::system_clock::time_point start;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/common.h>
|
||||
#include <nori/ray.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* \brief Homogeneous coordinate transformation
|
||||
*
|
||||
* This class stores a general homogeneous coordinate tranformation, such as
|
||||
* rotation, translation, uniform or non-uniform scaling, and perspective
|
||||
* transformations. The inverse of this transformation is also recorded
|
||||
* here, since it is required when transforming normal vectors.
|
||||
*/
|
||||
struct Transform {
|
||||
public:
|
||||
/// Create the identity transform
|
||||
Transform() :
|
||||
m_transform(Eigen::Matrix4f::Identity()),
|
||||
m_inverse(Eigen::Matrix4f::Identity()) { }
|
||||
|
||||
/// Create a new transform instance for the given matrix
|
||||
Transform(const Eigen::Matrix4f &trafo);
|
||||
|
||||
/// Create a new transform instance for the given matrix and its inverse
|
||||
Transform(const Eigen::Matrix4f &trafo, const Eigen::Matrix4f &inv)
|
||||
: m_transform(trafo), m_inverse(inv) { }
|
||||
|
||||
/// Return the underlying matrix
|
||||
const Eigen::Matrix4f &getMatrix() const {
|
||||
return m_transform;
|
||||
}
|
||||
|
||||
/// Return the inverse of the underlying matrix
|
||||
const Eigen::Matrix4f &getInverseMatrix() const {
|
||||
return m_inverse;
|
||||
}
|
||||
|
||||
/// Return the inverse transformation
|
||||
Transform inverse() const {
|
||||
return Transform(m_inverse, m_transform);
|
||||
}
|
||||
|
||||
/// Concatenate with another transform
|
||||
Transform operator*(const Transform &t) const;
|
||||
|
||||
/// Apply the homogeneous transformation to a 3D vector
|
||||
Vector3f operator*(const Vector3f &v) const {
|
||||
return m_transform.topLeftCorner<3,3>() * v;
|
||||
}
|
||||
|
||||
/// Apply the homogeneous transformation to a 3D normal
|
||||
Normal3f operator*(const Normal3f &n) const {
|
||||
return m_inverse.topLeftCorner<3, 3>().transpose() * n;
|
||||
}
|
||||
|
||||
/// Transform a point by an arbitrary matrix in homogeneous coordinates
|
||||
Point3f operator*(const Point3f &p) const {
|
||||
Vector4f result = m_transform * Vector4f(p[0], p[1], p[2], 1.0f);
|
||||
return result.head<3>() / result.w();
|
||||
}
|
||||
|
||||
/// Apply the homogeneous transformation to a ray
|
||||
Ray3f operator*(const Ray3f &r) const {
|
||||
return Ray3f(
|
||||
operator*(r.o),
|
||||
operator*(r.d),
|
||||
r.mint, r.maxt
|
||||
);
|
||||
}
|
||||
|
||||
/// Return a string representation
|
||||
std::string toString() const;
|
||||
private:
|
||||
Eigen::Matrix4f m_transform;
|
||||
Eigen::Matrix4f m_inverse;
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/common.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/* ===================================================================
|
||||
This file contains a few templates and specializations, which
|
||||
provide 2/3D points, vectors, and normals over different
|
||||
underlying data types. Points, vectors, and normals are distinct
|
||||
in Nori, because they transform differently under homogeneous
|
||||
coordinate transformations.
|
||||
* =================================================================== */
|
||||
|
||||
/**
|
||||
* \brief Generic N-dimensional vector data structure based on Eigen::Matrix
|
||||
*/
|
||||
template <typename _Scalar, int _Dimension> struct TVector : public Eigen::Matrix<_Scalar, _Dimension, 1> {
|
||||
public:
|
||||
enum {
|
||||
Dimension = _Dimension
|
||||
};
|
||||
|
||||
typedef _Scalar Scalar;
|
||||
typedef Eigen::Matrix<Scalar, Dimension, 1> Base;
|
||||
typedef TVector<Scalar, Dimension> VectorType;
|
||||
typedef TPoint<Scalar, Dimension> PointType;
|
||||
|
||||
/// Create a new vector with constant component vlaues
|
||||
TVector(Scalar value = (Scalar) 0) { Base::setConstant(value); }
|
||||
|
||||
/// Create a new 2D vector (type error if \c Dimension != 2)
|
||||
TVector(Scalar x, Scalar y) : Base(x, y) { }
|
||||
|
||||
/// Create a new 3D vector (type error if \c Dimension != 3)
|
||||
TVector(Scalar x, Scalar y, Scalar z) : Base(x, y, z) { }
|
||||
|
||||
/// Create a new 4D vector (type error if \c Dimension != 4)
|
||||
TVector(Scalar x, Scalar y, Scalar z, Scalar w) : Base(x, y, z, w) { }
|
||||
|
||||
/// Construct a vector from MatrixBase (needed to play nice with Eigen)
|
||||
template <typename Derived> TVector(const Eigen::MatrixBase<Derived>& p)
|
||||
: Base(p) { }
|
||||
|
||||
/// Assign a vector from MatrixBase (needed to play nice with Eigen)
|
||||
template <typename Derived> TVector &operator=(const Eigen::MatrixBase<Derived>& p) {
|
||||
this->Base::operator=(p);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Return a human-readable string summary
|
||||
std::string toString() const {
|
||||
std::string result;
|
||||
for (size_t i=0; i<Dimension; ++i) {
|
||||
result += std::to_string(this->coeff(i));
|
||||
if (i+1 < Dimension)
|
||||
result += ", ";
|
||||
}
|
||||
return "[" + result + "]";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Generic N-dimensional point data structure based on Eigen::Matrix
|
||||
*/
|
||||
template <typename _Scalar, int _Dimension> struct TPoint : public Eigen::Matrix<_Scalar, _Dimension, 1> {
|
||||
public:
|
||||
enum {
|
||||
Dimension = _Dimension
|
||||
};
|
||||
|
||||
typedef _Scalar Scalar;
|
||||
typedef Eigen::Matrix<Scalar, Dimension, 1> Base;
|
||||
typedef TVector<Scalar, Dimension> VectorType;
|
||||
typedef TPoint<Scalar, Dimension> PointType;
|
||||
|
||||
/// Create a new point with constant component vlaues
|
||||
TPoint(Scalar value = (Scalar) 0) { Base::setConstant(value); }
|
||||
|
||||
/// Create a new 2D point (type error if \c Dimension != 2)
|
||||
TPoint(Scalar x, Scalar y) : Base(x, y) { }
|
||||
|
||||
/// Create a new 3D point (type error if \c Dimension != 3)
|
||||
TPoint(Scalar x, Scalar y, Scalar z) : Base(x, y, z) { }
|
||||
|
||||
/// Create a new 4D point (type error if \c Dimension != 4)
|
||||
TPoint(Scalar x, Scalar y, Scalar z, Scalar w) : Base(x, y, z, w) { }
|
||||
|
||||
/// Construct a point from MatrixBase (needed to play nice with Eigen)
|
||||
template <typename Derived> TPoint(const Eigen::MatrixBase<Derived>& p)
|
||||
: Base(p) { }
|
||||
|
||||
/// Assign a point from MatrixBase (needed to play nice with Eigen)
|
||||
template <typename Derived> TPoint &operator=(const Eigen::MatrixBase<Derived>& p) {
|
||||
this->Base::operator=(p);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Return a human-readable string summary
|
||||
std::string toString() const {
|
||||
std::string result;
|
||||
for (size_t i=0; i<Dimension; ++i) {
|
||||
result += std::to_string(this->coeff(i));
|
||||
if (i+1 < Dimension)
|
||||
result += ", ";
|
||||
}
|
||||
return "[" + result + "]";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief 3-dimensional surface normal representation
|
||||
*/
|
||||
struct Normal3f : public Eigen::Matrix<float, 3, 1> {
|
||||
public:
|
||||
enum {
|
||||
Dimension = 3
|
||||
};
|
||||
|
||||
typedef float Scalar;
|
||||
typedef Eigen::Matrix<Scalar, Dimension, 1> Base;
|
||||
typedef TVector<Scalar, Dimension> VectorType;
|
||||
typedef TPoint<Scalar, Dimension> PointType;
|
||||
|
||||
|
||||
/// Create a new normal with constant component vlaues
|
||||
Normal3f(Scalar value = 0.0f) { Base::setConstant(value); }
|
||||
|
||||
/// Create a new 3D normal
|
||||
Normal3f(Scalar x, Scalar y, Scalar z) : Base(x, y, z) { }
|
||||
|
||||
/// Construct a normal from MatrixBase (needed to play nice with Eigen)
|
||||
template <typename Derived> Normal3f(const Eigen::MatrixBase<Derived>& p)
|
||||
: Base(p) { }
|
||||
|
||||
/// Assign a normal from MatrixBase (needed to play nice with Eigen)
|
||||
template <typename Derived> Normal3f &operator=(const Eigen::MatrixBase<Derived>& p) {
|
||||
this->Base::operator=(p);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Return a human-readable string summary
|
||||
std::string toString() const {
|
||||
return tfm::format("[%f, %f, %f]", coeff(0), coeff(1), coeff(2));
|
||||
}
|
||||
};
|
||||
|
||||
/// Complete the set {a} to an orthonormal base
|
||||
extern void coordinateSystem(const Vector3f &a, Vector3f &b, Vector3f &c);
|
||||
|
||||
NORI_NAMESPACE_END
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
This file is part of Nori, a simple educational ray tracer
|
||||
|
||||
Copyright (c) 2015 by Wenzel Jakob
|
||||
|
||||
Nori is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License Version 3
|
||||
as published by the Free Software Foundation.
|
||||
|
||||
Nori is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <nori/common.h>
|
||||
#include <nori/sampler.h>
|
||||
|
||||
NORI_NAMESPACE_BEGIN
|
||||
|
||||
/// A collection of useful warping functions for importance sampling
|
||||
class Warp {
|
||||
public:
|
||||
/// Dummy warping function: takes uniformly distributed points in a square and just returns them
|
||||
static Point2f squareToUniformSquare(const Point2f &sample);
|
||||
|
||||
/// Probability density of \ref squareToUniformSquare()
|
||||
static float squareToUniformSquarePdf(const Point2f &p);
|
||||
|
||||
/// Sample a 2D tent distribution
|
||||
static Point2f squareToTent(const Point2f &sample);
|
||||
|
||||
/// Probability density of \ref squareToTent()
|
||||
static float squareToTentPdf(const Point2f &p);
|
||||
|
||||
/// Uniformly sample a vector on a 2D disk with radius 1, centered around the origin
|
||||
static Point2f squareToUniformDisk(const Point2f &sample);
|
||||
|
||||
/// Probability density of \ref squareToUniformDisk()
|
||||
static float squareToUniformDiskPdf(const Point2f &p);
|
||||
|
||||
/// Uniformly sample a vector on the unit sphere with respect to solid angles
|
||||
static Vector3f squareToUniformSphere(const Point2f &sample);
|
||||
|
||||
/// Probability density of \ref squareToUniformSphere()
|
||||
static float squareToUniformSpherePdf(const Vector3f &v);
|
||||
|
||||
/// Uniformly sample a vector on the unit hemisphere around the pole (0,0,1) with respect to solid angles
|
||||
static Vector3f squareToUniformHemisphere(const Point2f &sample);
|
||||
|
||||
/// Probability density of \ref squareToUniformHemisphere()
|
||||
static float squareToUniformHemispherePdf(const Vector3f &v);
|
||||
|
||||
/// Uniformly sample a vector on the unit hemisphere around the pole (0,0,1) with respect to projected solid angles
|
||||
static Vector3f squareToCosineHemisphere(const Point2f &sample);
|
||||
|
||||
/// Probability density of \ref squareToCosineHemisphere()
|
||||
static float squareToCosineHemispherePdf(const Vector3f &v);
|
||||
|
||||
/// Warp a uniformly distributed square sample to a Beckmann distribution * cosine for the given 'alpha' parameter
|
||||
static Vector3f squareToBeckmann(const Point2f &sample, float alpha);
|
||||
|
||||
/// Probability density of \ref squareToBeckmann()
|
||||
static float squareToBeckmannPdf(const Vector3f &m, float alpha);
|
||||
};
|
||||
|
||||
NORI_NAMESPACE_END
|
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 104 KiB |
|
@ -0,0 +1,60 @@
|
|||
**Homework 1**
|
||||
|
||||
Student name:
|
||||
|
||||
Sciper number:
|
||||
|
||||
|
||||
Feedback
|
||||
========
|
||||
|
||||
Use this section to provide feedback about the assignment.
|
||||
|
||||
About this template
|
||||
===================
|
||||
|
||||
* Structure your report using numbers and titles following those of the assignment.
|
||||
|
||||
* For every task include all images you generated in a format that is viewable within a web browser. You can use HDRI Tools, or photoshop to tonemap you images and save them as jpgs or pngs. We recommend tonemapping your images by just changing exposure and using sRGB (if available) or a gamma of 2.2.
|
||||
|
||||
* Include descriptions of encountered problems, a list of external libraries that you used (if applicable) and the time you spent on each task.
|
||||
|
||||
For an overview of Markdeep and its syntax, see the [official demo document](https://casual-effects.com/markdeep/features.md.html) and
|
||||
the associated [source code](https://casual-effects.com/markdeep/features.md.html?noformat).
|
||||
|
||||
Exercise writeup
|
||||
================
|
||||
|
||||
Proofs and justifications
|
||||
-------------------------
|
||||
|
||||
LaTeX is also supported:
|
||||
$$
|
||||
L_o(\mathbf{x}, \omega_o) = \int_{\Omega} L_i(\mathbf{x},\omega_i)\, f(\mathbf{x}, \omega_i, \omega_o)\, |\cos\theta_i|\, \mathrm{d}\omega_i
|
||||
$$
|
||||
|
||||
Reference comparisons
|
||||
---------------------
|
||||
|
||||
Comparison 1
|
||||
<div class="twentytwenty-container">
|
||||
<img src="reference.jpg" alt="Reference">
|
||||
<img src="mine.jpg" alt="Mine">
|
||||
</div>
|
||||
|
||||
Comparison 2
|
||||
<div class="twentytwenty-container">
|
||||
<img src="mine.jpg" alt="Reference">
|
||||
<img src="reference.jpg" alt="Mine">
|
||||
</div>
|
||||
|
||||
<!-- Slider -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="../resources/jquery.event.move.js"></script>
|
||||
<script src="../resources/jquery.twentytwenty.js"></script>
|
||||
<link href="../resources/offcanvas.css" rel="stylesheet">
|
||||
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
|
||||
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
|
||||
<!-- Markdeep: -->
|
||||
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
|
||||
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>
|
After Width: | Height: | Size: 380 KiB |
|
@ -0,0 +1,43 @@
|
|||
**Homework 2**
|
||||
|
||||
Student name:
|
||||
|
||||
Sciper number:
|
||||
|
||||
Octree construction (50 pts)
|
||||
============================
|
||||
|
||||
Ray traversal (25 pts)
|
||||
======================
|
||||
|
||||
Improved ray traversal (25 pts)
|
||||
===============================
|
||||
|
||||
Surface normal visualization of the Ajax bust:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="ajax-normals-ref.png" alt="Reference">
|
||||
<img src="ajax-normals.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Note: Nori automatically generates both an `.exr` as well as an sRGB tonemapped `.png` image of your rendering that is directly used for the comparison above. Please still commit both versions in your `results/homework-X` folder.
|
||||
|
||||
Feedback
|
||||
========
|
||||
|
||||
We would appreciate any comments or criticism to improve the projects in future years--naturally, this part will not be graded. Examples of information that is useful to us includes:
|
||||
|
||||
* How much time did you spend on the assignment? How was it divided between designing, coding, and testing?
|
||||
* What advice should we have given you before you started?
|
||||
* What was hard or surprising about the assignment?
|
||||
* What did you like or dislike? What else would you change?
|
||||
|
||||
<!-- Slider -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="../resources/jquery.event.move.js"></script>
|
||||
<script src="../resources/jquery.twentytwenty.js"></script>
|
||||
<link href="../resources/offcanvas.css" rel="stylesheet">
|
||||
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
|
||||
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
|
||||
<!-- Markdeep: -->
|
||||
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
|
||||
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>
|
After Width: | Height: | Size: 182 KiB |
After Width: | Height: | Size: 157 KiB |
|
@ -0,0 +1,74 @@
|
|||
**Homework 3**
|
||||
|
||||
Student name:
|
||||
|
||||
Sciper number:
|
||||
|
||||
Monte Carlo Sampling (60 pts)
|
||||
=============================
|
||||
|
||||
For each of these sections, don't forget to include necessary derivations as well as screenshots of passing $\chi^2$ tests from the <tt>warptest</tt> executable.
|
||||
|
||||
Tent
|
||||
----
|
||||
|
||||
Uniform disk
|
||||
------------
|
||||
|
||||
Uniform sphere
|
||||
--------------
|
||||
|
||||
Uniform hemisphere
|
||||
------------------
|
||||
|
||||
Cosine hemisphere
|
||||
-----------------
|
||||
|
||||
Beckmann distribution
|
||||
---------------------
|
||||
|
||||
Two simple rendering algorithms (40 pts)
|
||||
========================================
|
||||
|
||||
Point lights
|
||||
------------
|
||||
|
||||
Ajax bust illuminated by a point light source:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="ajax-simple-ref.png" alt="Reference">
|
||||
<img src="ajax-simple.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Note: Nori automatically generates both a `.exr` as well as an sRGB tonemapped `.png` image of your rendering that should directly used for the comparison above. Please still commit both versions in your `results/homework-X` folder.
|
||||
|
||||
Ambient occlusion
|
||||
-----------------
|
||||
|
||||
Ajax bust rendered using ambient occlusion:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="ajax-ao-ref.png" alt="Reference">
|
||||
<img src="ajax-ao.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Note: Nori automatically generates both a `.exr` as well as an sRGB tonemapped `.png` image of your rendering that should directly used for the comparison above. Please still commit both versions in your `results/homework-X` folder.
|
||||
|
||||
Feedback
|
||||
========
|
||||
|
||||
We would appreciate any comments or criticism to improve the projects in future years--naturally, this part will not be graded. Examples of information that is useful to us includes:
|
||||
|
||||
* How much time did you spend on the assignment? How was it divided between designing, coding, and testing?
|
||||
* What advice should we have given you before you started?
|
||||
* What was hard or surprising about the assignment?
|
||||
* What did you like or dislike? What else would you change?
|
||||
|
||||
<!-- Slider -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="../resources/jquery.event.move.js"></script>
|
||||
<script src="../resources/jquery.twentytwenty.js"></script>
|
||||
<link href="../resources/offcanvas.css" rel="stylesheet">
|
||||
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
|
||||
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
|
||||
<!-- Markdeep: -->
|
||||
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
|
||||
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>
|
After Width: | Height: | Size: 331 KiB |
After Width: | Height: | Size: 386 KiB |
After Width: | Height: | Size: 433 KiB |
After Width: | Height: | Size: 434 KiB |
|
@ -0,0 +1,66 @@
|
|||
**Homework 4**
|
||||
|
||||
Student name:
|
||||
|
||||
Sciper number:
|
||||
|
||||
Area lights (25 pts)
|
||||
====================
|
||||
|
||||
Distribution Ray Tracing (40 pts)
|
||||
=================================
|
||||
|
||||
Diffuse logo:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="logo-diffuse-ref.png" alt="Reference">
|
||||
<img src="logo-diffuse.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Cornell box (distributed):
|
||||
<div class="twentytwenty-container">
|
||||
<img src="cbox-distributed-ref.png" alt="Reference">
|
||||
<img src="cbox-distributed.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Note: please commit both EXR and PNG versions of your renders in the `results/homework-X` folder.
|
||||
|
||||
Dielectrics (25 pts)
|
||||
====================
|
||||
|
||||
Whitted-style ray tracing (10 pts)
|
||||
==================================
|
||||
|
||||
Dielectric logo:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="logo-dielectric-ref.png" alt="Reference">
|
||||
<img src="logo-dielectric.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Cornell box (Whitted):
|
||||
<div class="twentytwenty-container">
|
||||
<img src="cbox-whitted-ref.png" alt="Reference">
|
||||
<img src="cbox-whitted.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Note: please commit both EXR and PNG versions of your renders in the `results/homework-X` folder.
|
||||
|
||||
Feedback
|
||||
========
|
||||
|
||||
We would appreciate any comments or criticism to improve the projects in future years--naturally, this part will not be graded. Examples of information that is useful to us includes:
|
||||
|
||||
* How much time did you spend on the assignment? How was it divided between designing, coding, and testing?
|
||||
* What advice should we have given you before you started?
|
||||
* What was hard or surprising about the assignment?
|
||||
* What did you like or dislike? What else would you change?
|
||||
|
||||
<!-- Slider -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="../resources/jquery.event.move.js"></script>
|
||||
<script src="../resources/jquery.twentytwenty.js"></script>
|
||||
<link href="../resources/offcanvas.css" rel="stylesheet">
|
||||
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
|
||||
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
|
||||
<!-- Markdeep: -->
|
||||
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
|
||||
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>
|
After Width: | Height: | Size: 273 KiB |
After Width: | Height: | Size: 272 KiB |
After Width: | Height: | Size: 966 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 1017 KiB |
|
@ -0,0 +1,123 @@
|
|||
**Homework 5**
|
||||
|
||||
Student name:
|
||||
|
||||
Sciper number:
|
||||
|
||||
Microfacet BRDF (30 points)
|
||||
===========================
|
||||
|
||||
Evaluating the Microfacet BRDF
|
||||
------------------------------
|
||||
|
||||
Sampling the Microfacet BRDF
|
||||
------------------------------
|
||||
|
||||
Validation
|
||||
----------
|
||||
|
||||
Ajax (smooth):
|
||||
<div class="twentytwenty-container">
|
||||
<img src="ajax-smooth-ref.png" alt="Reference">
|
||||
<img src="ajax-smooth.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Ajax (rough):
|
||||
<div class="twentytwenty-container">
|
||||
<img src="ajax-rough-ref.png" alt="Reference">
|
||||
<img src="ajax-rough.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
|
||||
Brute force path tracer (15 points)
|
||||
===================================
|
||||
|
||||
Validation
|
||||
----------
|
||||
|
||||
Cornell box:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="cbox_mats-ref.png" alt="Reference">
|
||||
<img src="cbox_mats.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Veach material test scene:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="veach_mats-ref.png" alt="Reference">
|
||||
<img src="veach_mats.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Table test scene:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="table_mats-ref.png" alt="Reference">
|
||||
<img src="table_mats.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Path tracer with next event estimation (25 points)
|
||||
==================================================
|
||||
|
||||
Validation
|
||||
----------
|
||||
|
||||
Cornell box:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="cbox_ems-ref.png" alt="Reference">
|
||||
<img src="cbox_ems.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Veach material test scene:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="veach_ems-ref.png" alt="Reference">
|
||||
<img src="veach_ems.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Table test scene:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="table_ems-ref.png" alt="Reference">
|
||||
<img src="table_ems.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Path tracer with Multiple Importance Sampling (30 points)
|
||||
=========================================================
|
||||
|
||||
Validation
|
||||
----------
|
||||
|
||||
Cornell box:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="cbox_mis-ref.png" alt="Reference">
|
||||
<img src="cbox_mis.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Veach material test scene:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="veach_mis-ref.png" alt="Reference">
|
||||
<img src="veach_mis.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Table test scene:
|
||||
<div class="twentytwenty-container">
|
||||
<img src="table_mis-ref.png" alt="Reference">
|
||||
<img src="table_mis.png" alt="Mine">
|
||||
</div>
|
||||
|
||||
Feedback
|
||||
========
|
||||
|
||||
We would appreciate any comments or criticism to improve the projects in future years--naturally, this part will not be graded. Examples of information that is useful to us includes:
|
||||
|
||||
* How much time did you spend on the assignment? How was it divided between designing, coding, and testing?
|
||||
* What advice should we have given you before you started?
|
||||
* What was hard or surprising about the assignment?
|
||||
* What did you like or dislike? What else would you change?
|
||||
|
||||
<!-- Slider -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="../resources/jquery.event.move.js"></script>
|
||||
<script src="../resources/jquery.twentytwenty.js"></script>
|
||||
<link href="../resources/offcanvas.css" rel="stylesheet">
|
||||
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
|
||||
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
|
||||
<!-- Markdeep: -->
|
||||
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
|
||||
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>
|
After Width: | Height: | Size: 884 KiB |
After Width: | Height: | Size: 867 KiB |
After Width: | Height: | Size: 841 KiB |
After Width: | Height: | Size: 616 KiB |
After Width: | Height: | Size: 763 KiB |
After Width: | Height: | Size: 642 KiB |
|
@ -0,0 +1,63 @@
|
|||
**Final Project Proposal**
|
||||
|
||||
Student name:
|
||||
|
||||
Sciper number:
|
||||
|
||||
|
||||
Scene description and motivation
|
||||
================================
|
||||
|
||||
Describe the scene you plan to render. Use at least one motivational image.
|
||||
|
||||
|
||||
Feature list
|
||||
============
|
||||
|
||||
Using the table below, list the features you'll use to achieve your render, and how they are used.
|
||||
|
||||
* Include the point counts, they should add up to *80 points*.
|
||||
* If your list of features adds up to more than 80 points, you must reduce the point count for the features of your choice until the total reaches 80 points exactly.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
<th>Identifier</th>
|
||||
<th>Standard point count</th>
|
||||
<th>Adjusted point count</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Feature 1</td>
|
||||
<td>#S-abc</td>
|
||||
<td>20</td>
|
||||
<td>20</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Feature 2</td>
|
||||
<td>#M-BADCAFE</td>
|
||||
<td>30</td>
|
||||
<td>15</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Feature ...</td>
|
||||
<td>#E-...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><strong>Total</strong></td>
|
||||
<td>95</td>
|
||||
<td>80</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Slider -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="../resources/jquery.event.move.js"></script>
|
||||
<script src="../resources/jquery.twentytwenty.js"></script>
|
||||
<link href="../resources/offcanvas.css" rel="stylesheet">
|
||||
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
|
||||
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
|
||||
<!-- Markdeep: -->
|
||||
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
|
||||
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>
|
|
@ -0,0 +1,84 @@
|
|||
**Final Project Report**
|
||||
|
||||
Student name:
|
||||
|
||||
Sciper number:
|
||||
|
||||
|
||||
Final render
|
||||
============
|
||||
|
||||
Include your final render here.
|
||||
|
||||
|
||||
Motivation
|
||||
==========
|
||||
|
||||
Summarize your motivation for rendering this scene and describe how it connects to the theme (this
|
||||
can be adapted from your project proposal).
|
||||
|
||||
|
||||
Feature list
|
||||
============
|
||||
|
||||
Please paste below the table of features from your project proposal (including adjustments
|
||||
made after submission, if any).
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Feature</th>
|
||||
<th>Standard point count</th>
|
||||
<th>Adjusted point count</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Feature 1</td>
|
||||
<td>20</td>
|
||||
<td>20</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Feature 2</td>
|
||||
<td>30</td>
|
||||
<td>15</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Feature ...</td>
|
||||
<td>...</td>
|
||||
<td>...</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Total</strong></td>
|
||||
<td>95</td>
|
||||
<td>80</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
Feature 1
|
||||
=========
|
||||
|
||||
Description, design choices and **validation** for feature 1.
|
||||
|
||||
|
||||
Feature 2
|
||||
=========
|
||||
|
||||
Description, design choices and **validation** for feature 2.
|
||||
|
||||
|
||||
|
||||
Feedback
|
||||
========
|
||||
|
||||
Please provide feedback about the final project or the course in general.
|
||||
|
||||
<!-- Slider -->
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
<script src="../resources/jquery.event.move.js"></script>
|
||||
<script src="../resources/jquery.twentytwenty.js"></script>
|
||||
<link href="../resources/offcanvas.css" rel="stylesheet">
|
||||
<link href="../resources/twentytwenty.css" rel="stylesheet" type="text/css" />
|
||||
<script>var markdeepOptions = {onLoad: function() {$(".twentytwenty-container").twentytwenty({default_offset_pct: 0.5, move_slider_on_hover: true});} };</script>
|
||||
<!-- Markdeep: -->
|
||||
<script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js?" charset="utf-8"></script>
|
||||
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>
|
|
@ -0,0 +1,599 @@
|
|||
// DOM.event.move
|
||||
//
|
||||
// 2.0.0
|
||||
//
|
||||
// Stephen Band
|
||||
//
|
||||
// Triggers 'movestart', 'move' and 'moveend' events after
|
||||
// mousemoves following a mousedown cross a distance threshold,
|
||||
// similar to the native 'dragstart', 'drag' and 'dragend' events.
|
||||
// Move events are throttled to animation frames. Move event objects
|
||||
// have the properties:
|
||||
//
|
||||
// pageX:
|
||||
// pageY: Page coordinates of pointer.
|
||||
// startX:
|
||||
// startY: Page coordinates of pointer at movestart.
|
||||
// distX:
|
||||
// distY: Distance the pointer has moved since movestart.
|
||||
// deltaX:
|
||||
// deltaY: Distance the finger has moved since last event.
|
||||
// velocityX:
|
||||
// velocityY: Average velocity over last few events.
|
||||
|
||||
|
||||
(function(fn) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], fn);
|
||||
} else if ((typeof module !== "undefined" && module !== null) && module.exports) {
|
||||
module.exports = fn;
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
})(function(){
|
||||
var assign = Object.assign || window.jQuery && jQuery.extend;
|
||||
|
||||
// Number of pixels a pressed pointer travels before movestart
|
||||
// event is fired.
|
||||
var threshold = 8;
|
||||
|
||||
// Shim for requestAnimationFrame, falling back to timer. See:
|
||||
// see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||||
var requestFrame = (function(){
|
||||
return (
|
||||
window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(fn, element){
|
||||
return window.setTimeout(function(){
|
||||
fn();
|
||||
}, 25);
|
||||
}
|
||||
);
|
||||
})();
|
||||
|
||||
// Shim for customEvent
|
||||
// see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
|
||||
(function () {
|
||||
if ( typeof window.CustomEvent === "function" ) return false;
|
||||
function CustomEvent ( event, params ) {
|
||||
params = params || { bubbles: false, cancelable: false, detail: undefined };
|
||||
var evt = document.createEvent( 'CustomEvent' );
|
||||
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
|
||||
return evt;
|
||||
}
|
||||
|
||||
CustomEvent.prototype = window.Event.prototype;
|
||||
window.CustomEvent = CustomEvent;
|
||||
})();
|
||||
|
||||
var ignoreTags = {
|
||||
textarea: true,
|
||||
input: true,
|
||||
select: true,
|
||||
button: true
|
||||
};
|
||||
|
||||
var mouseevents = {
|
||||
move: 'mousemove',
|
||||
cancel: 'mouseup dragstart',
|
||||
end: 'mouseup'
|
||||
};
|
||||
|
||||
var touchevents = {
|
||||
move: 'touchmove',
|
||||
cancel: 'touchend',
|
||||
end: 'touchend'
|
||||
};
|
||||
|
||||
var rspaces = /\s+/;
|
||||
|
||||
|
||||
// DOM Events
|
||||
|
||||
var eventOptions = { bubbles: true, cancelable: true };
|
||||
|
||||
var eventsSymbol = typeof Symbol === "function" ? Symbol('events') : {};
|
||||
|
||||
function createEvent(type) {
|
||||
return new CustomEvent(type, eventOptions);
|
||||
}
|
||||
|
||||
function getEvents(node) {
|
||||
return node[eventsSymbol] || (node[eventsSymbol] = {});
|
||||
}
|
||||
|
||||
function on(node, types, fn, data, selector) {
|
||||
types = types.split(rspaces);
|
||||
|
||||
var events = getEvents(node);
|
||||
var i = types.length;
|
||||
var handlers, type;
|
||||
|
||||
function handler(e) { fn(e, data); }
|
||||
|
||||
while (i--) {
|
||||
type = types[i];
|
||||
handlers = events[type] || (events[type] = []);
|
||||
handlers.push([fn, handler]);
|
||||
node.addEventListener(type, handler);
|
||||
}
|
||||
}
|
||||
|
||||
function off(node, types, fn, selector) {
|
||||
types = types.split(rspaces);
|
||||
|
||||
var events = getEvents(node);
|
||||
var i = types.length;
|
||||
var type, handlers, k;
|
||||
|
||||
if (!events) { return; }
|
||||
|
||||
while (i--) {
|
||||
type = types[i];
|
||||
handlers = events[type];
|
||||
if (!handlers) { continue; }
|
||||
k = handlers.length;
|
||||
while (k--) {
|
||||
if (handlers[k][0] === fn) {
|
||||
node.removeEventListener(type, handlers[k][1]);
|
||||
handlers.splice(k, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function trigger(node, type, properties) {
|
||||
// Don't cache events. It prevents you from triggering an event of a
|
||||
// given type from inside the handler of another event of that type.
|
||||
var event = createEvent(type);
|
||||
if (properties) { assign(event, properties); }
|
||||
node.dispatchEvent(event);
|
||||
}
|
||||
|
||||
|
||||
// Constructors
|
||||
|
||||
function Timer(fn){
|
||||
var callback = fn,
|
||||
active = false,
|
||||
running = false;
|
||||
|
||||
function trigger(time) {
|
||||
if (active){
|
||||
callback();
|
||||
requestFrame(trigger);
|
||||
running = true;
|
||||
active = false;
|
||||
}
|
||||
else {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.kick = function(fn) {
|
||||
active = true;
|
||||
if (!running) { trigger(); }
|
||||
};
|
||||
|
||||
this.end = function(fn) {
|
||||
var cb = callback;
|
||||
|
||||
if (!fn) { return; }
|
||||
|
||||
// If the timer is not running, simply call the end callback.
|
||||
if (!running) {
|
||||
fn();
|
||||
}
|
||||
// If the timer is running, and has been kicked lately, then
|
||||
// queue up the current callback and the end callback, otherwise
|
||||
// just the end callback.
|
||||
else {
|
||||
callback = active ?
|
||||
function(){ cb(); fn(); } :
|
||||
fn ;
|
||||
|
||||
active = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Functions
|
||||
|
||||
function noop() {}
|
||||
|
||||
function preventDefault(e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function isIgnoreTag(e) {
|
||||
return !!ignoreTags[e.target.tagName.toLowerCase()];
|
||||
}
|
||||
|
||||
function isPrimaryButton(e) {
|
||||
// Ignore mousedowns on any button other than the left (or primary)
|
||||
// mouse button, or when a modifier key is pressed.
|
||||
return (e.which === 1 && !e.ctrlKey && !e.altKey);
|
||||
}
|
||||
|
||||
function identifiedTouch(touchList, id) {
|
||||
var i, l;
|
||||
|
||||
if (touchList.identifiedTouch) {
|
||||
return touchList.identifiedTouch(id);
|
||||
}
|
||||
|
||||
// touchList.identifiedTouch() does not exist in
|
||||
// webkit yet… we must do the search ourselves...
|
||||
|
||||
i = -1;
|
||||
l = touchList.length;
|
||||
|
||||
while (++i < l) {
|
||||
if (touchList[i].identifier === id) {
|
||||
return touchList[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function changedTouch(e, data) {
|
||||
var touch = identifiedTouch(e.changedTouches, data.identifier);
|
||||
|
||||
// This isn't the touch you're looking for.
|
||||
if (!touch) { return; }
|
||||
|
||||
// Chrome Android (at least) includes touches that have not
|
||||
// changed in e.changedTouches. That's a bit annoying. Check
|
||||
// that this touch has changed.
|
||||
if (touch.pageX === data.pageX && touch.pageY === data.pageY) { return; }
|
||||
|
||||
return touch;
|
||||
}
|
||||
|
||||
|
||||
// Handlers that decide when the first movestart is triggered
|
||||
|
||||
function mousedown(e){
|
||||
// Ignore non-primary buttons
|
||||
if (!isPrimaryButton(e)) { return; }
|
||||
|
||||
// Ignore form and interactive elements
|
||||
if (isIgnoreTag(e)) { return; }
|
||||
|
||||
on(document, mouseevents.move, mousemove, e);
|
||||
on(document, mouseevents.cancel, mouseend, e);
|
||||
}
|
||||
|
||||
function mousemove(e, data){
|
||||
checkThreshold(e, data, e, removeMouse);
|
||||
}
|
||||
|
||||
function mouseend(e, data) {
|
||||
removeMouse();
|
||||
}
|
||||
|
||||
function removeMouse() {
|
||||
off(document, mouseevents.move, mousemove);
|
||||
off(document, mouseevents.cancel, mouseend);
|
||||
}
|
||||
|
||||
function touchstart(e) {
|
||||
// Don't get in the way of interaction with form elements
|
||||
if (ignoreTags[e.target.tagName.toLowerCase()]) { return; }
|
||||
|
||||
var touch = e.changedTouches[0];
|
||||
|
||||
// iOS live updates the touch objects whereas Android gives us copies.
|
||||
// That means we can't trust the touchstart object to stay the same,
|
||||
// so we must copy the data. This object acts as a template for
|
||||
// movestart, move and moveend event objects.
|
||||
var data = {
|
||||
target: touch.target,
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY,
|
||||
identifier: touch.identifier,
|
||||
|
||||
// The only way to make handlers individually unbindable is by
|
||||
// making them unique.
|
||||
touchmove: function(e, data) { touchmove(e, data); },
|
||||
touchend: function(e, data) { touchend(e, data); }
|
||||
};
|
||||
|
||||
on(document, touchevents.move, data.touchmove, data);
|
||||
on(document, touchevents.cancel, data.touchend, data);
|
||||
}
|
||||
|
||||
function touchmove(e, data) {
|
||||
var touch = changedTouch(e, data);
|
||||
if (!touch) { return; }
|
||||
checkThreshold(e, data, touch, removeTouch);
|
||||
}
|
||||
|
||||
function touchend(e, data) {
|
||||
var touch = identifiedTouch(e.changedTouches, data.identifier);
|
||||
if (!touch) { return; }
|
||||
removeTouch(data);
|
||||
}
|
||||
|
||||
function removeTouch(data) {
|
||||
off(document, touchevents.move, data.touchmove);
|
||||
off(document, touchevents.cancel, data.touchend);
|
||||
}
|
||||
|
||||
function checkThreshold(e, data, touch, fn) {
|
||||
var distX = touch.pageX - data.pageX;
|
||||
var distY = touch.pageY - data.pageY;
|
||||
|
||||
// Do nothing if the threshold has not been crossed.
|
||||
if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; }
|
||||
|
||||
triggerStart(e, data, touch, distX, distY, fn);
|
||||
}
|
||||
|
||||
function triggerStart(e, data, touch, distX, distY, fn) {
|
||||
var touches = e.targetTouches;
|
||||
var time = e.timeStamp - data.timeStamp;
|
||||
|
||||
// Create a movestart object with some special properties that
|
||||
// are passed only to the movestart handlers.
|
||||
var template = {
|
||||
altKey: e.altKey,
|
||||
ctrlKey: e.ctrlKey,
|
||||
shiftKey: e.shiftKey,
|
||||
startX: data.pageX,
|
||||
startY: data.pageY,
|
||||
distX: distX,
|
||||
distY: distY,
|
||||
deltaX: distX,
|
||||
deltaY: distY,
|
||||
pageX: touch.pageX,
|
||||
pageY: touch.pageY,
|
||||
velocityX: distX / time,
|
||||
velocityY: distY / time,
|
||||
identifier: data.identifier,
|
||||
targetTouches: touches,
|
||||
finger: touches ? touches.length : 1,
|
||||
enableMove: function() {
|
||||
this.moveEnabled = true;
|
||||
this.enableMove = noop;
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
// Trigger the movestart event.
|
||||
trigger(data.target, 'movestart', template);
|
||||
|
||||
// Unbind handlers that tracked the touch or mouse up till now.
|
||||
fn(data);
|
||||
}
|
||||
|
||||
|
||||
// Handlers that control what happens following a movestart
|
||||
|
||||
function activeMousemove(e, data) {
|
||||
var timer = data.timer;
|
||||
|
||||
data.touch = e;
|
||||
data.timeStamp = e.timeStamp;
|
||||
timer.kick();
|
||||
}
|
||||
|
||||
function activeMouseend(e, data) {
|
||||
var target = data.target;
|
||||
var event = data.event;
|
||||
var timer = data.timer;
|
||||
|
||||
removeActiveMouse();
|
||||
|
||||
endEvent(target, event, timer, function() {
|
||||
// Unbind the click suppressor, waiting until after mouseup
|
||||
// has been handled.
|
||||
setTimeout(function(){
|
||||
off(target, 'click', preventDefault);
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function removeActiveMouse() {
|
||||
off(document, mouseevents.move, activeMousemove);
|
||||
off(document, mouseevents.end, activeMouseend);
|
||||
}
|
||||
|
||||
function activeTouchmove(e, data) {
|
||||
var event = data.event;
|
||||
var timer = data.timer;
|
||||
var touch = changedTouch(e, event);
|
||||
|
||||
if (!touch) { return; }
|
||||
|
||||
// Stop the interface from gesturing
|
||||
e.preventDefault();
|
||||
|
||||
event.targetTouches = e.targetTouches;
|
||||
data.touch = touch;
|
||||
data.timeStamp = e.timeStamp;
|
||||
|
||||
timer.kick();
|
||||
}
|
||||
|
||||
function activeTouchend(e, data) {
|
||||
var target = data.target;
|
||||
var event = data.event;
|
||||
var timer = data.timer;
|
||||
var touch = identifiedTouch(e.changedTouches, event.identifier);
|
||||
|
||||
// This isn't the touch you're looking for.
|
||||
if (!touch) { return; }
|
||||
|
||||
removeActiveTouch(data);
|
||||
endEvent(target, event, timer);
|
||||
}
|
||||
|
||||
function removeActiveTouch(data) {
|
||||
off(document, touchevents.move, data.activeTouchmove);
|
||||
off(document, touchevents.end, data.activeTouchend);
|
||||
}
|
||||
|
||||
|
||||
// Logic for triggering move and moveend events
|
||||
|
||||
function updateEvent(event, touch, timeStamp) {
|
||||
var time = timeStamp - event.timeStamp;
|
||||
|
||||
event.distX = touch.pageX - event.startX;
|
||||
event.distY = touch.pageY - event.startY;
|
||||
event.deltaX = touch.pageX - event.pageX;
|
||||
event.deltaY = touch.pageY - event.pageY;
|
||||
|
||||
// Average the velocity of the last few events using a decay
|
||||
// curve to even out spurious jumps in values.
|
||||
event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time;
|
||||
event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time;
|
||||
event.pageX = touch.pageX;
|
||||
event.pageY = touch.pageY;
|
||||
}
|
||||
|
||||
function endEvent(target, event, timer, fn) {
|
||||
timer.end(function(){
|
||||
trigger(target, 'moveend', event);
|
||||
return fn && fn();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Set up the DOM
|
||||
|
||||
function movestart(e) {
|
||||
if (e.defaultPrevented) { return; }
|
||||
if (!e.moveEnabled) { return; }
|
||||
|
||||
var event = {
|
||||
startX: e.startX,
|
||||
startY: e.startY,
|
||||
pageX: e.pageX,
|
||||
pageY: e.pageY,
|
||||
distX: e.distX,
|
||||
distY: e.distY,
|
||||
deltaX: e.deltaX,
|
||||
deltaY: e.deltaY,
|
||||
velocityX: e.velocityX,
|
||||
velocityY: e.velocityY,
|
||||
identifier: e.identifier,
|
||||
targetTouches: e.targetTouches,
|
||||
finger: e.finger
|
||||
};
|
||||
|
||||
var data = {
|
||||
target: e.target,
|
||||
event: event,
|
||||
timer: new Timer(update),
|
||||
touch: undefined,
|
||||
timeStamp: e.timeStamp
|
||||
};
|
||||
|
||||
function update(time) {
|
||||
updateEvent(event, data.touch, data.timeStamp);
|
||||
trigger(data.target, 'move', event);
|
||||
}
|
||||
|
||||
if (e.identifier === undefined) {
|
||||
// We're dealing with a mouse event.
|
||||
// Stop clicks from propagating during a move
|
||||
on(e.target, 'click', preventDefault);
|
||||
on(document, mouseevents.move, activeMousemove, data);
|
||||
on(document, mouseevents.end, activeMouseend, data);
|
||||
}
|
||||
else {
|
||||
// In order to unbind correct handlers they have to be unique
|
||||
data.activeTouchmove = function(e, data) { activeTouchmove(e, data); };
|
||||
data.activeTouchend = function(e, data) { activeTouchend(e, data); };
|
||||
|
||||
// We're dealing with a touch.
|
||||
on(document, touchevents.move, data.activeTouchmove, data);
|
||||
on(document, touchevents.end, data.activeTouchend, data);
|
||||
}
|
||||
}
|
||||
|
||||
on(document, 'mousedown', mousedown);
|
||||
on(document, 'touchstart', touchstart);
|
||||
on(document, 'movestart', movestart);
|
||||
|
||||
|
||||
// jQuery special events
|
||||
//
|
||||
// jQuery event objects are copies of DOM event objects. They need
|
||||
// a little help copying the move properties across.
|
||||
|
||||
if (!window.jQuery) { return; }
|
||||
|
||||
var properties = ("startX startY pageX pageY distX distY deltaX deltaY velocityX velocityY").split(' ');
|
||||
|
||||
function enableMove1(e) { e.enableMove(); }
|
||||
function enableMove2(e) { e.enableMove(); }
|
||||
function enableMove3(e) { e.enableMove(); }
|
||||
|
||||
function add(handleObj) {
|
||||
var handler = handleObj.handler;
|
||||
|
||||
handleObj.handler = function(e) {
|
||||
// Copy move properties across from originalEvent
|
||||
var i = properties.length;
|
||||
var property;
|
||||
|
||||
while(i--) {
|
||||
property = properties[i];
|
||||
e[property] = e.originalEvent[property];
|
||||
}
|
||||
|
||||
handler.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
jQuery.event.special.movestart = {
|
||||
setup: function() {
|
||||
// Movestart must be enabled to allow other move events
|
||||
on(this, 'movestart', enableMove1);
|
||||
|
||||
// Do listen to DOM events
|
||||
return false;
|
||||
},
|
||||
|
||||
teardown: function() {
|
||||
off(this, 'movestart', enableMove1);
|
||||
return false;
|
||||
},
|
||||
|
||||
add: add
|
||||
};
|
||||
|
||||
jQuery.event.special.move = {
|
||||
setup: function() {
|
||||
on(this, 'movestart', enableMove2);
|
||||
return false;
|
||||
},
|
||||
|
||||
teardown: function() {
|
||||
off(this, 'movestart', enableMove2);
|
||||
return false;
|
||||
},
|
||||
|
||||
add: add
|
||||
};
|
||||
|
||||
jQuery.event.special.moveend = {
|
||||
setup: function() {
|
||||
on(this, 'movestart', enableMove3);
|
||||
return false;
|
||||
},
|
||||
|
||||
teardown: function() {
|
||||
off(this, 'movestart', enableMove3);
|
||||
return false;
|
||||
},
|
||||
|
||||
add: add
|
||||
};
|
||||
});
|
|
@ -0,0 +1,151 @@
|
|||
(function($){
|
||||
|
||||
$.fn.twentytwenty = function(options) {
|
||||
var options = $.extend({
|
||||
default_offset_pct: 0.5,
|
||||
orientation: 'horizontal',
|
||||
before_label: 'Before',
|
||||
after_label: 'After',
|
||||
no_overlay: false,
|
||||
move_slider_on_hover: false,
|
||||
move_with_handle_only: true,
|
||||
click_to_move: false
|
||||
}, options);
|
||||
|
||||
return this.each(function() {
|
||||
|
||||
var sliderPct = options.default_offset_pct;
|
||||
var container = $(this);
|
||||
var sliderOrientation = options.orientation;
|
||||
var beforeDirection = (sliderOrientation === 'vertical') ? 'down' : 'left';
|
||||
var afterDirection = (sliderOrientation === 'vertical') ? 'up' : 'right';
|
||||
|
||||
|
||||
container.wrap("<div class='twentytwenty-wrapper twentytwenty-" + sliderOrientation + "'></div>");
|
||||
if(!options.no_overlay) {
|
||||
container.append("<div class='twentytwenty-overlay'></div>");
|
||||
var overlay = container.find(".twentytwenty-overlay");
|
||||
overlay.append("<div class='twentytwenty-before-label' data-content='"+options.before_label+"'></div>");
|
||||
overlay.append("<div class='twentytwenty-after-label' data-content='"+options.after_label+"'></div>");
|
||||
}
|
||||
var beforeImg = container.find("img:first");
|
||||
var afterImg = container.find("img:last");
|
||||
container.append("<div class='twentytwenty-handle'></div>");
|
||||
var slider = container.find(".twentytwenty-handle");
|
||||
slider.append("<span class='twentytwenty-" + beforeDirection + "-arrow'></span>");
|
||||
slider.append("<span class='twentytwenty-" + afterDirection + "-arrow'></span>");
|
||||
container.addClass("twentytwenty-container");
|
||||
beforeImg.addClass("twentytwenty-before");
|
||||
afterImg.addClass("twentytwenty-after");
|
||||
|
||||
var calcOffset = function(dimensionPct) {
|
||||
var w = beforeImg.width();
|
||||
var h = beforeImg.height();
|
||||
return {
|
||||
w: w+"px",
|
||||
h: h+"px",
|
||||
cw: (dimensionPct*w)+"px",
|
||||
ch: (dimensionPct*h)+"px"
|
||||
};
|
||||
};
|
||||
|
||||
var adjustContainer = function(offset) {
|
||||
if (sliderOrientation === 'vertical') {
|
||||
beforeImg.css("clip", "rect(0,"+offset.w+","+offset.ch+",0)");
|
||||
afterImg.css("clip", "rect("+offset.ch+","+offset.w+","+offset.h+",0)");
|
||||
}
|
||||
else {
|
||||
beforeImg.css("clip", "rect(0,"+offset.cw+","+offset.h+",0)");
|
||||
afterImg.css("clip", "rect(0,"+offset.w+","+offset.h+","+offset.cw+")");
|
||||
}
|
||||
container.css("height", offset.h);
|
||||
};
|
||||
|
||||
var adjustSlider = function(pct) {
|
||||
var offset = calcOffset(pct);
|
||||
slider.css((sliderOrientation==="vertical") ? "top" : "left", (sliderOrientation==="vertical") ? offset.ch : offset.cw);
|
||||
adjustContainer(offset);
|
||||
};
|
||||
|
||||
// Return the number specified or the min/max number if it outside the range given.
|
||||
var minMaxNumber = function(num, min, max) {
|
||||
return Math.max(min, Math.min(max, num));
|
||||
};
|
||||
|
||||
// Calculate the slider percentage based on the position.
|
||||
var getSliderPercentage = function(positionX, positionY) {
|
||||
var sliderPercentage = (sliderOrientation === 'vertical') ?
|
||||
(positionY-offsetY)/imgHeight :
|
||||
(positionX-offsetX)/imgWidth;
|
||||
|
||||
return minMaxNumber(sliderPercentage, 0, 1);
|
||||
};
|
||||
|
||||
|
||||
$(window).on("resize.twentytwenty", function(e) {
|
||||
adjustSlider(sliderPct);
|
||||
});
|
||||
|
||||
var offsetX = 0;
|
||||
var offsetY = 0;
|
||||
var imgWidth = 0;
|
||||
var imgHeight = 0;
|
||||
var onMoveStart = function(e) {
|
||||
if (((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) && sliderOrientation !== 'vertical') {
|
||||
e.preventDefault();
|
||||
}
|
||||
else if (((e.distX < e.distY && e.distX < -e.distY) || (e.distX > e.distY && e.distX > -e.distY)) && sliderOrientation === 'vertical') {
|
||||
e.preventDefault();
|
||||
}
|
||||
container.addClass("active");
|
||||
offsetX = container.offset().left;
|
||||
offsetY = container.offset().top;
|
||||
imgWidth = beforeImg.width();
|
||||
imgHeight = beforeImg.height();
|
||||
};
|
||||
var onMove = function(e) {
|
||||
if (container.hasClass("active")) {
|
||||
sliderPct = getSliderPercentage(e.pageX, e.pageY);
|
||||
adjustSlider(sliderPct);
|
||||
}
|
||||
};
|
||||
var onMoveEnd = function() {
|
||||
container.removeClass("active");
|
||||
};
|
||||
|
||||
var moveTarget = options.move_with_handle_only ? slider : container;
|
||||
moveTarget.on("movestart",onMoveStart);
|
||||
moveTarget.on("move",onMove);
|
||||
moveTarget.on("moveend",onMoveEnd);
|
||||
|
||||
if (options.move_slider_on_hover) {
|
||||
container.on("mouseenter", onMoveStart);
|
||||
container.on("mousemove", onMove);
|
||||
container.on("mouseleave", onMoveEnd);
|
||||
}
|
||||
|
||||
slider.on("touchmove", function(e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
container.find("img").on("mousedown", function(event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
if (options.click_to_move) {
|
||||
container.on('click', function(e) {
|
||||
offsetX = container.offset().left;
|
||||
offsetY = container.offset().top;
|
||||
imgWidth = beforeImg.width();
|
||||
imgHeight = beforeImg.height();
|
||||
|
||||
sliderPct = getSliderPercentage(e.pageX, e.pageY);
|
||||
adjustSlider(sliderPct);
|
||||
});
|
||||
}
|
||||
|
||||
$(window).trigger("resize.twentytwenty");
|
||||
});
|
||||
};
|
||||
|
||||
})(jQuery);
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Style tweaks
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
html,
|
||||
body {
|
||||
overflow-x: hidden; /* Prevent scroll on narrow devices */
|
||||
}
|
||||
body {
|
||||
padding-top: 70px;
|
||||
}
|
||||
footer {
|
||||
padding: 30px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border: 1px solid gray;
|
||||
text-align: center;
|
||||
}
|
||||
table tr {
|
||||
border-bottom: 1px solid gray;
|
||||
}
|
||||
table th {
|
||||
padding: 0.5em;
|
||||
border-bottom: 1px solid gray;
|
||||
}
|
||||
table td:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
table td {
|
||||
padding: 1em;
|
||||
border-right: 1px solid gray;
|
||||
}
|
||||
|
||||
/*
|
||||
* Off Canvas
|
||||
* --------------------------------------------------
|
||||
*/
|
||||
@media screen and (max-width: 767px) {
|
||||
.row-offcanvas {
|
||||
position: relative;
|
||||
-webkit-transition: all .25s ease-out;
|
||||
-moz-transition: all .25s ease-out;
|
||||
transition: all .25s ease-out;
|
||||
}
|
||||
|
||||
.row-offcanvas-right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.row-offcanvas-left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.row-offcanvas-right
|
||||
.sidebar-offcanvas {
|
||||
right: -80%; /* 6 columns */
|
||||
}
|
||||
|
||||
.row-offcanvas-left
|
||||
.sidebar-offcanvas {
|
||||
left: -80%; /* 6 columns */
|
||||
}
|
||||
|
||||
.row-offcanvas-right.active {
|
||||
right: 80%; /* 6 columns */
|
||||
}
|
||||
|
||||
.row-offcanvas-left.active {
|
||||
left: 80%; /* 6 columns */
|
||||
}
|
||||
|
||||
.sidebar-offcanvas {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 80%; /* 6 columns */
|
||||
}
|
||||
}
|
|
@ -0,0 +1,206 @@
|
|||
.twentytwenty-horizontal .twentytwenty-handle:before, .twentytwenty-horizontal .twentytwenty-handle:after, .twentytwenty-vertical .twentytwenty-handle:before, .twentytwenty-vertical .twentytwenty-handle:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
background: white;
|
||||
position: absolute;
|
||||
z-index: 30;
|
||||
-webkit-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
-moz-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5); }
|
||||
|
||||
.twentytwenty-horizontal .twentytwenty-handle:before, .twentytwenty-horizontal .twentytwenty-handle:after {
|
||||
width: 3px;
|
||||
height: 9999px;
|
||||
left: 50%;
|
||||
margin-left: -1.5px; }
|
||||
|
||||
.twentytwenty-vertical .twentytwenty-handle:before, .twentytwenty-vertical .twentytwenty-handle:after {
|
||||
width: 9999px;
|
||||
height: 3px;
|
||||
top: 50%;
|
||||
margin-top: -1.5px; }
|
||||
|
||||
.twentytwenty-before-label, .twentytwenty-after-label, .twentytwenty-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%; }
|
||||
|
||||
.twentytwenty-before-label, .twentytwenty-after-label, .twentytwenty-overlay {
|
||||
-webkit-transition-duration: 0.5s;
|
||||
-moz-transition-duration: 0.5s;
|
||||
transition-duration: 0.5s; }
|
||||
|
||||
.twentytwenty-before-label, .twentytwenty-after-label {
|
||||
-webkit-transition-property: opacity;
|
||||
-moz-transition-property: opacity;
|
||||
transition-property: opacity; }
|
||||
|
||||
.twentytwenty-before-label:before, .twentytwenty-after-label:before {
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.1em; }
|
||||
|
||||
.twentytwenty-before-label:before, .twentytwenty-after-label:before {
|
||||
position: absolute;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
line-height: 38px;
|
||||
padding: 0 20px;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px; }
|
||||
|
||||
.twentytwenty-horizontal .twentytwenty-before-label:before, .twentytwenty-horizontal .twentytwenty-after-label:before {
|
||||
top: 50%;
|
||||
margin-top: -19px; }
|
||||
|
||||
.twentytwenty-vertical .twentytwenty-before-label:before, .twentytwenty-vertical .twentytwenty-after-label:before {
|
||||
left: 50%;
|
||||
margin-left: -45px;
|
||||
text-align: center;
|
||||
width: 90px; }
|
||||
|
||||
.twentytwenty-left-arrow, .twentytwenty-right-arrow, .twentytwenty-up-arrow, .twentytwenty-down-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 6px inset transparent;
|
||||
position: absolute; }
|
||||
|
||||
.twentytwenty-left-arrow, .twentytwenty-right-arrow {
|
||||
top: 50%;
|
||||
margin-top: -6px; }
|
||||
|
||||
.twentytwenty-up-arrow, .twentytwenty-down-arrow {
|
||||
left: 50%;
|
||||
margin-left: -6px; }
|
||||
|
||||
.twentytwenty-container {
|
||||
-webkit-box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
z-index: 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none; }
|
||||
.twentytwenty-container img {
|
||||
max-width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
display: block; }
|
||||
.twentytwenty-container.active .twentytwenty-overlay, .twentytwenty-container.active :hover.twentytwenty-overlay {
|
||||
background: rgba(0, 0, 0, 0); }
|
||||
.twentytwenty-container.active .twentytwenty-overlay .twentytwenty-before-label,
|
||||
.twentytwenty-container.active .twentytwenty-overlay .twentytwenty-after-label, .twentytwenty-container.active :hover.twentytwenty-overlay .twentytwenty-before-label,
|
||||
.twentytwenty-container.active :hover.twentytwenty-overlay .twentytwenty-after-label {
|
||||
opacity: 0; }
|
||||
.twentytwenty-container * {
|
||||
-webkit-box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box; }
|
||||
|
||||
.twentytwenty-before-label {
|
||||
opacity: 0; }
|
||||
.twentytwenty-before-label:before {
|
||||
content: attr(data-content); }
|
||||
|
||||
.twentytwenty-after-label {
|
||||
opacity: 0; }
|
||||
.twentytwenty-after-label:before {
|
||||
content: attr(data-content); }
|
||||
|
||||
.twentytwenty-horizontal .twentytwenty-before-label:before {
|
||||
left: 10px; }
|
||||
|
||||
.twentytwenty-horizontal .twentytwenty-after-label:before {
|
||||
right: 10px; }
|
||||
|
||||
.twentytwenty-vertical .twentytwenty-before-label:before {
|
||||
top: 10px; }
|
||||
|
||||
.twentytwenty-vertical .twentytwenty-after-label:before {
|
||||
bottom: 10px; }
|
||||
|
||||
.twentytwenty-overlay {
|
||||
-webkit-transition-property: background;
|
||||
-moz-transition-property: background;
|
||||
transition-property: background;
|
||||
background: rgba(0, 0, 0, 0);
|
||||
z-index: 25; }
|
||||
.twentytwenty-overlay:hover {
|
||||
background: rgba(0, 0, 0, 0.5); }
|
||||
.twentytwenty-overlay:hover .twentytwenty-after-label {
|
||||
opacity: 1; }
|
||||
.twentytwenty-overlay:hover .twentytwenty-before-label {
|
||||
opacity: 1; }
|
||||
|
||||
.twentytwenty-before {
|
||||
z-index: 20; }
|
||||
|
||||
.twentytwenty-after {
|
||||
z-index: 10; }
|
||||
|
||||
.twentytwenty-handle {
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
margin-left: -22px;
|
||||
margin-top: -22px;
|
||||
border: 3px solid white;
|
||||
-webkit-border-radius: 1000px;
|
||||
-moz-border-radius: 1000px;
|
||||
border-radius: 1000px;
|
||||
-webkit-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
-moz-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
z-index: 40;
|
||||
cursor: pointer; }
|
||||
|
||||
.twentytwenty-horizontal .twentytwenty-handle:before {
|
||||
bottom: 50%;
|
||||
margin-bottom: 22px;
|
||||
-webkit-box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
-moz-box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
|
||||
.twentytwenty-horizontal .twentytwenty-handle:after {
|
||||
top: 50%;
|
||||
margin-top: 22px;
|
||||
-webkit-box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
-moz-box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
|
||||
|
||||
.twentytwenty-vertical .twentytwenty-handle:before {
|
||||
left: 50%;
|
||||
margin-left: 22px;
|
||||
-webkit-box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
-moz-box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
|
||||
.twentytwenty-vertical .twentytwenty-handle:after {
|
||||
right: 50%;
|
||||
margin-right: 22px;
|
||||
-webkit-box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
-moz-box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
|
||||
box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
|
||||
|
||||
.twentytwenty-left-arrow {
|
||||
border-right: 6px solid white;
|
||||
left: 50%;
|
||||
margin-left: -17px; }
|
||||
|
||||
.twentytwenty-right-arrow {
|
||||
border-left: 6px solid white;
|
||||
right: 50%;
|
||||
margin-right: -17px; }
|
||||
|
||||
.twentytwenty-up-arrow {
|
||||
border-bottom: 6px solid white;
|
||||
top: 50%;
|
||||
margin-top: -17px; }
|
||||
|
||||
.twentytwenty-down-arrow {
|
||||
border-top: 6px solid white;
|
||||
bottom: 50%;
|
||||
margin-bottom: -17px; }
|
|
@ -0,0 +1,87 @@
|
|||
from __future__ import print_function
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
TEST_SCENES = [
|
||||
"pa4/tests/test-mesh.xml",
|
||||
"pa4/tests/test-mesh-furnace.xml",
|
||||
"pa5/tests/chi2test-microfacet.xml",
|
||||
"pa5/tests/ttest-microfacet.xml",
|
||||
"pa5/tests/test-direct.xml",
|
||||
"pa5/tests/test-furnace.xml",
|
||||
]
|
||||
|
||||
TEST_WARPS = [
|
||||
("square", None),
|
||||
("tent", None),
|
||||
("disk", None),
|
||||
("uniform_sphere", None),
|
||||
("uniform_hemisphere", None),
|
||||
("cosine_hemisphere", None),
|
||||
("beckmann", 0.05),
|
||||
("beckmann", 0.10),
|
||||
("beckmann", 0.30),
|
||||
("microfacet_brdf", 0.05),
|
||||
("microfacet_brdf", 0.10),
|
||||
("microfacet_brdf", 0.30),
|
||||
("microfacet_brdf", (0.05, 0.5)),
|
||||
("microfacet_brdf", (0.10, 0.5)),
|
||||
("microfacet_brdf", (0.30, 0.5)),
|
||||
]
|
||||
|
||||
def find_build_directory():
|
||||
root = os.path.join(".")
|
||||
if os.path.isfile(os.path.join(root, "nori")):
|
||||
return root
|
||||
root = os.path.dirname(root)
|
||||
if os.path.isfile(os.path.join(root, "nori")):
|
||||
return root
|
||||
return os.path.join(root, "build")
|
||||
|
||||
|
||||
def test_warps_and_scenes(scenes, warps):
|
||||
total = len(scenes) + len(warps)
|
||||
passed = 0
|
||||
failed = []
|
||||
build_dir = find_build_directory()
|
||||
|
||||
for t in scenes:
|
||||
path = os.path.join("scenes", t)
|
||||
ret = subprocess.call([os.path.join(build_dir, "nori"), path])
|
||||
if ret == 0:
|
||||
passed += 1
|
||||
else:
|
||||
failed.append(t)
|
||||
|
||||
for (warp_type, param) in warps:
|
||||
args = [os.path.join(build_dir, "warptest"), warp_type]
|
||||
if param is not None:
|
||||
if not isinstance(param, (list, tuple)):
|
||||
param = [param]
|
||||
for p in param:
|
||||
args.append(str(p))
|
||||
ret = subprocess.call(args)
|
||||
if ret == 0:
|
||||
passed += 1
|
||||
else:
|
||||
failed.append(' '.join(args))
|
||||
|
||||
print("")
|
||||
if passed < total:
|
||||
print("\033[91m" + "Passed " + str(passed) + " / " + str(total) + " tests." + "\033[0m")
|
||||
print("Failed tests:")
|
||||
for t in failed:
|
||||
print("\t" + str(t))
|
||||
return False
|
||||
else:
|
||||
print("\033[92m" + "Passed " + str(passed) + " / " + str(total) + " tests." + "\033[0m")
|
||||
print("\tGood job!")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if not test_warps_and_scenes(TEST_SCENES, TEST_WARPS):
|
||||
sys.exit(1)
|
|
@ -0,0 +1,32 @@
|
|||
<scene>
|
||||
<!-- Independent sample generator, one sample per pixel -->
|
||||
<sampler type="independent">
|
||||
<integer name="sampleCount" value="1"/>
|
||||
</sampler>
|
||||
|
||||
<!-- Render the visible surface normals -->
|
||||
<integrator type="normals"/>
|
||||
|
||||
<!-- Load the Stanford bunny (https://graphics.stanford.edu/data/3Dscanrep/) -->
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="bunny.obj"/>
|
||||
<bsdf type="diffuse"/>
|
||||
</mesh>
|
||||
|
||||
<!-- Render the scene viewed by a perspective camera -->
|
||||
<camera type="perspective">
|
||||
<!-- 3D origin, target point, and 'up' vector -->
|
||||
<transform name="toWorld">
|
||||
<lookat target="-0.0123771, 0.0540913, -0.239922"
|
||||
origin="-0.0315182, 0.284011, 0.7331"
|
||||
up="0.00717446, 0.973206, -0.229822"/>
|
||||
</transform>
|
||||
|
||||
<!-- Field of view: 30 degrees -->
|
||||
<float name="fov" value="16"/>
|
||||
|
||||
<!-- 768 x 768 pixels -->
|
||||
<integer name="width" value="768"/>
|
||||
<integer name="height" value="768"/>
|
||||
</camera>
|
||||
</scene>
|
|
@ -0,0 +1,33 @@
|
|||
<scene>
|
||||
<!-- Independent sample generator, 32 samples per pixel -->
|
||||
<sampler type="independent">
|
||||
<integer name="sampleCount" value="32"/>
|
||||
</sampler>
|
||||
|
||||
<!-- Render the visible surface normals -->
|
||||
<integrator type="normals"/>
|
||||
|
||||
<!-- Load the Ajax bust (a freely available scan from the Jotero forum) -->
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="ajax.obj"/>
|
||||
|
||||
<bsdf type="diffuse"/>
|
||||
</mesh>
|
||||
|
||||
<!-- Render the scene viewed by a perspective camera -->
|
||||
<camera type="perspective">
|
||||
<!-- 3D origin, target point, and 'up' vector -->
|
||||
<transform name="toWorld">
|
||||
<lookat target="-64.8161, 47.2211, 23.8576"
|
||||
origin="-65.6055, 47.5762, 24.3583"
|
||||
up="0.299858, 0.934836, -0.190177"/>
|
||||
</transform>
|
||||
|
||||
<!-- Field of view: 30 degrees -->
|
||||
<float name="fov" value="30"/>
|
||||
|
||||
<!-- 768 x 768 pixels -->
|
||||
<integer name="width" value="768"/>
|
||||
<integer name="height" value="768"/>
|
||||
</camera>
|
||||
</scene>
|
|
@ -0,0 +1,33 @@
|
|||
<scene>
|
||||
<!-- Independent sample generator, 32 samples per pixel -->
|
||||
<sampler type="independent">
|
||||
<integer name="sampleCount" value="512"/>
|
||||
</sampler>
|
||||
|
||||
<!-- Use the ambient occlusion integrator -->
|
||||
<integrator type="ao"/>
|
||||
|
||||
<!-- Load the Ajax bust (a freely available scan from the Jotero forum) -->
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="ajax.obj"/>
|
||||
|
||||
<bsdf type="diffuse"/>
|
||||
</mesh>
|
||||
|
||||
<!-- Render the scene viewed by a perspective camera -->
|
||||
<camera type="perspective">
|
||||
<!-- 3D origin, target point, and 'up' vector -->
|
||||
<transform name="toWorld">
|
||||
<lookat target="-64.8161, 47.2211, 23.8576"
|
||||
origin="-65.6055, 47.5762, 24.3583"
|
||||
up="0.299858, 0.934836, -0.190177"/>
|
||||
</transform>
|
||||
|
||||
<!-- Field of view: 30 degrees -->
|
||||
<float name="fov" value="30"/>
|
||||
|
||||
<!-- 768 x 768 pixels -->
|
||||
<integer name="width" value="768"/>
|
||||
<integer name="height" value="768"/>
|
||||
</camera>
|
||||
</scene>
|
|
@ -0,0 +1,36 @@
|
|||
<scene>
|
||||
<!-- Independent sample generator, 32 samples per pixel -->
|
||||
<sampler type="independent">
|
||||
<integer name="sampleCount" value="32"/>
|
||||
</sampler>
|
||||
|
||||
<!-- Use the simple point light integrator -->
|
||||
<integrator type="simple">
|
||||
<point name="position" value="-20, 40, 20"/>
|
||||
<color name="energy" value="3.76e4, 3.76e4, 3.76e4"/>
|
||||
</integrator>
|
||||
|
||||
<!-- Load the Ajax bust (a freely available scan from the Jotero forum) -->
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="ajax.obj"/>
|
||||
|
||||
<bsdf type="diffuse"/>
|
||||
</mesh>
|
||||
|
||||
<!-- Render the scene viewed by a perspective camera -->
|
||||
<camera type="perspective">
|
||||
<!-- 3D origin, target point, and 'up' vector -->
|
||||
<transform name="toWorld">
|
||||
<lookat target="-64.8161, 47.2211, 23.8576"
|
||||
origin="-65.6055, 47.5762, 24.3583"
|
||||
up="0.299858, 0.934836, -0.190177"/>
|
||||
</transform>
|
||||
|
||||
<!-- Field of view: 30 degrees -->
|
||||
<float name="fov" value="30"/>
|
||||
|
||||
<!-- 768 x 768 pixels -->
|
||||
<integer name="width" value="768"/>
|
||||
<integer name="height" value="768"/>
|
||||
</camera>
|
||||
</scene>
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
|
||||
<scene>
|
||||
<integrator type="whitted"/>
|
||||
|
||||
<camera type="perspective">
|
||||
<float name="fov" value="27.7856"/>
|
||||
<transform name="toWorld">
|
||||
<scale value="-1,1,1"/>
|
||||
<lookat target="0, 0.893051, 4.41198" origin="0, 0.919769, 5.41159" up="0, 1, 0"/>
|
||||
</transform>
|
||||
|
||||
<integer name="height" value="600"/>
|
||||
<integer name="width" value="800"/>
|
||||
</camera>
|
||||
|
||||
<sampler type="independent">
|
||||
<integer name="sampleCount" value="512"/>
|
||||
</sampler>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/walls.obj"/>
|
||||
|
||||
<bsdf type="diffuse">
|
||||
<color name="albedo" value="0.725 0.71 0.68"/>
|
||||
</bsdf>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/rightwall.obj"/>
|
||||
|
||||
<bsdf type="diffuse">
|
||||
<color name="albedo" value="0.161 0.133 0.427"/>
|
||||
</bsdf>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/leftwall.obj"/>
|
||||
|
||||
<bsdf type="diffuse">
|
||||
<color name="albedo" value="0.630 0.065 0.05"/>
|
||||
</bsdf>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/sphere1.obj"/>
|
||||
|
||||
<bsdf type="diffuse"/>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/sphere2.obj"/>
|
||||
|
||||
<bsdf type="diffuse"/>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/light.obj"/>
|
||||
|
||||
<emitter type="area">
|
||||
<color name="radiance" value="40 40 40"/>
|
||||
</emitter>
|
||||
</mesh>
|
||||
</scene>
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
|
||||
<scene>
|
||||
<integrator type="whitted"/>
|
||||
|
||||
<camera type="perspective">
|
||||
<float name="fov" value="27.7856"/>
|
||||
<transform name="toWorld">
|
||||
<scale value="-1,1,1"/>
|
||||
<lookat target="0, 0.893051, 4.41198" origin="0, 0.919769, 5.41159" up="0, 1, 0"/>
|
||||
</transform>
|
||||
|
||||
<integer name="height" value="600"/>
|
||||
<integer name="width" value="800"/>
|
||||
</camera>
|
||||
|
||||
<sampler type="independent">
|
||||
<integer name="sampleCount" value="512"/>
|
||||
</sampler>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/walls.obj"/>
|
||||
|
||||
<bsdf type="diffuse">
|
||||
<color name="albedo" value="0.725 0.71 0.68"/>
|
||||
</bsdf>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/rightwall.obj"/>
|
||||
|
||||
<bsdf type="diffuse">
|
||||
<color name="albedo" value="0.161 0.133 0.427"/>
|
||||
</bsdf>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/leftwall.obj"/>
|
||||
|
||||
<bsdf type="diffuse">
|
||||
<color name="albedo" value="0.630 0.065 0.05"/>
|
||||
</bsdf>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/sphere1.obj"/>
|
||||
|
||||
<bsdf type="mirror"/>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/sphere2.obj"/>
|
||||
|
||||
<bsdf type="dielectric"/>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/light.obj"/>
|
||||
|
||||
<emitter type="area">
|
||||
<color name="radiance" value="40 40 40"/>
|
||||
</emitter>
|
||||
</mesh>
|
||||
</scene>
|
|
@ -0,0 +1,13 @@
|
|||
# Blender v2.72 (sub 0) OBJ File: ''
|
||||
# www.blender.org
|
||||
mtllib leftwall.mtl
|
||||
o leftWall
|
||||
v -1.020000 1.590000 -1.040000
|
||||
v -1.020000 1.590000 0.990000
|
||||
v -1.010000 -0.000000 0.990000
|
||||
v -0.990000 0.000000 -1.040000
|
||||
vt 0.000000 1.000000
|
||||
usemtl leftWall
|
||||
s 1
|
||||
f 1/1 2/1 3/1
|
||||
f 4/1 1/1 3/1
|
|
@ -0,0 +1,13 @@
|
|||
# Blender v2.72 (sub 0) OBJ File: ''
|
||||
# www.blender.org
|
||||
mtllib light.mtl
|
||||
o light
|
||||
v 0.230000 1.580000 -0.220000
|
||||
v 0.230000 1.580000 0.160000
|
||||
v -0.240000 1.580000 0.160000
|
||||
v -0.240000 1.580000 -0.220000
|
||||
vt 0.000000 1.000000
|
||||
usemtl light
|
||||
s 1
|
||||
f 1/1 2/1 3/1
|
||||
f 4/1 1/1 3/1
|
|
@ -0,0 +1,13 @@
|
|||
# Blender v2.72 (sub 0) OBJ File: ''
|
||||
# www.blender.org
|
||||
mtllib rightwall.mtl
|
||||
o rightWall
|
||||
v 1.000000 1.590000 0.990000
|
||||
v 1.000000 1.590000 -1.040000
|
||||
v 1.000000 0.000000 -1.040000
|
||||
v 1.000000 -0.000000 0.990000
|
||||
vt 0.000000 1.000000
|
||||
usemtl rightWall
|
||||
s 1
|
||||
f 1/1 2/1 3/1
|
||||
f 4/1 1/1 3/1
|
|
@ -0,0 +1,33 @@
|
|||
# Blender v2.72 (sub 0) OBJ File: ''
|
||||
# www.blender.org
|
||||
mtllib walls.mtl
|
||||
o ceiling
|
||||
v 1.000000 1.590000 -1.040000
|
||||
v 1.000000 1.590000 0.990000
|
||||
v -1.020000 1.590000 0.990000
|
||||
v -1.020000 1.590000 -1.040000
|
||||
vt 0.000000 1.000000
|
||||
usemtl ceiling
|
||||
s 1
|
||||
f 1/1 2/1 3/1
|
||||
f 4/1 1/1 3/1
|
||||
o floor
|
||||
v 1.000000 0.000000 -1.040000
|
||||
v -0.990000 0.000000 -1.040000
|
||||
v -1.010000 -0.000000 0.990000
|
||||
v 1.000000 -0.000000 0.990000
|
||||
vt 0.000000 1.000000
|
||||
usemtl floor
|
||||
s 1
|
||||
f 5/2 6/2 7/2
|
||||
f 8/2 5/2 7/2
|
||||
o backWall
|
||||
v 1.000000 1.590000 -1.040000
|
||||
v -1.020000 1.590000 -1.040000
|
||||
v -0.990000 0.000000 -1.040000
|
||||
v 1.000000 0.000000 -1.040000
|
||||
vt 0.000000 1.000000
|
||||
usemtl backWall
|
||||
s 1
|
||||
f 9/3 10/3 11/3
|
||||
f 12/3 9/3 11/3
|
|
@ -0,0 +1,56 @@
|
|||
<scene>
|
||||
<integrator type="whitted"/>
|
||||
|
||||
<sampler type="independent">
|
||||
<!-- You might want to reduce this number during development -->
|
||||
<integer name="sampleCount" value="4096"/>
|
||||
<!-- <integer name="sampleCount" value="16"/> -->
|
||||
</sampler>
|
||||
|
||||
<camera type="perspective">
|
||||
<!-- Camera-to-world coordinate system transformation -->
|
||||
<transform name="toWorld">
|
||||
<lookat origin="2.668, 1.078, 6.668" target="-0.841,0.85,-0.051" up="0,1,0"/>
|
||||
</transform>
|
||||
|
||||
<float name="fov" value="60"/>
|
||||
|
||||
<integer name="width" value="768"/>
|
||||
<integer name="height" value="384"/>
|
||||
</camera>
|
||||
|
||||
<mesh type="obj">
|
||||
<!--
|
||||
Pineapple SVG:
|
||||
CC BY-NC 4.0 Licence
|
||||
"Pineapple Black And White" (http://clipartmag.com/pineapple-black-and-white)
|
||||
http://clipartmag.com/pineapple-black-and-white#pineapple-black-and-white-2.jpg
|
||||
-->
|
||||
<string name="filename" value="meshes/logo.obj"/>
|
||||
<bsdf type="dielectric"/>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/floor.obj"/>
|
||||
<bsdf type="diffuse">
|
||||
<color name="albedo" value="0.5, 0.5, 0.5"/>
|
||||
</bsdf>
|
||||
<transform name="toWorld">
|
||||
<scale value="6, 6, 6"/>
|
||||
</transform>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/light1.obj"/>
|
||||
<emitter type="area">
|
||||
<color name="radiance" value="60, 200, 120"/>
|
||||
</emitter>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/light2.obj"/>
|
||||
<emitter type="area">
|
||||
<color name="radiance" value="80, 80, 800"/>
|
||||
</emitter>
|
||||
</mesh>
|
||||
</scene>
|
|
@ -0,0 +1,58 @@
|
|||
<scene>
|
||||
<integrator type="whitted"/>
|
||||
|
||||
<sampler type="independent">
|
||||
<!-- You might want to reduce this number during development -->
|
||||
<integer name="sampleCount" value="4096"/>
|
||||
<!-- <integer name="sampleCount" value="16"/> -->
|
||||
</sampler>
|
||||
|
||||
<camera type="perspective">
|
||||
<!-- Camera-to-world coordinate system transformation -->
|
||||
<transform name="toWorld">
|
||||
<lookat origin="2.668, 1.078, 6.668" target="-0.841,0.85,-0.051" up="0,1,0"/>
|
||||
</transform>
|
||||
|
||||
<float name="fov" value="60"/>
|
||||
|
||||
<integer name="width" value="768"/>
|
||||
<integer name="height" value="384"/>
|
||||
</camera>
|
||||
|
||||
<mesh type="obj">
|
||||
<!--
|
||||
Pineapple SVG:
|
||||
CC BY-NC 4.0 Licence
|
||||
"Pineapple Black And White" (http://clipartmag.com/pineapple-black-and-white)
|
||||
http://clipartmag.com/pineapple-black-and-white#pineapple-black-and-white-2.jpg
|
||||
-->
|
||||
<string name="filename" value="meshes/logo.obj"/>
|
||||
<bsdf type="diffuse">
|
||||
<color name="albedo" value="0.5, 0.5, 0.5"/>
|
||||
</bsdf>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/floor.obj"/>
|
||||
<bsdf type="diffuse">
|
||||
<color name="albedo" value="0.5, 0.5, 0.5"/>
|
||||
</bsdf>
|
||||
<transform name="toWorld">
|
||||
<scale value="6, 6, 6"/>
|
||||
</transform>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/light1.obj"/>
|
||||
<emitter type="area">
|
||||
<color name="radiance" value="60, 200, 120"/>
|
||||
</emitter>
|
||||
</mesh>
|
||||
|
||||
<mesh type="obj">
|
||||
<string name="filename" value="meshes/light2.obj"/>
|
||||
<emitter type="area">
|
||||
<color name="radiance" value="80, 80, 800"/>
|
||||
</emitter>
|
||||
</mesh>
|
||||
</scene>
|
|
@ -0,0 +1,147 @@
|
|||
v -9.2 -4.5 3.2
|
||||
v -9.2 -4.48421 2.68512
|
||||
v -9.2 -4.43692 2.17227
|
||||
v -9.2 -4.3583 1.66347
|
||||
v -9.2 -4.24867 1.16074
|
||||
v -9.2 -4.10845 0.666061
|
||||
v -9.2 -3.93821 0.181379
|
||||
v -9.2 -3.73862 -0.29139
|
||||
v -9.2 -3.51045 -0.75038
|
||||
v -9.2 -3.25462 -1.19378
|
||||
v -9.2 -2.97214 -1.61984
|
||||
v -9.2 -2.66411 -2.02688
|
||||
v -9.2 -2.33175 -2.41329
|
||||
v -9.2 -1.97638 -2.77754
|
||||
v -9.2 -1.59939 -3.11821
|
||||
v -9.2 -1.20228 -3.43394
|
||||
v -9.2 -0.786614 -3.72349
|
||||
v -9.2 -0.354029 -3.98571
|
||||
v -9.2 0.0937657 -4.21958
|
||||
v -9.2 0.555004 -4.42417
|
||||
v -9.2 1.02786 -4.59866
|
||||
v -9.2 1.51048 -4.74238
|
||||
v -9.2 2.00095 -4.85476
|
||||
v -9.2 2.49733 -4.93534
|
||||
v -9.2 2.99768 -4.98382
|
||||
v -9.2 3.5 -5
|
||||
v -9.2 4.00232 -4.98382
|
||||
v -9.2 4.50267 -4.93534
|
||||
v -9.2 4.99905 -4.85476
|
||||
v -9.2 5.48952 -4.74238
|
||||
v -9.2 5.97214 -4.59866
|
||||
v -9.2 6.445 -4.42417
|
||||
v -9.2 6.90623 -4.21958
|
||||
v -9.2 7.35403 -3.98571
|
||||
v -9.2 7.78661 -3.72349
|
||||
v -9.2 -4.48421 3.71488
|
||||
v 10.8 -4.5 3.2
|
||||
v 10.8 -4.48421 2.68512
|
||||
v 10.8 -4.43692 2.17227
|
||||
v 10.8 -4.3583 1.66347
|
||||
v 10.8 -4.24867 1.16074
|
||||
v 10.8 -4.10845 0.666061
|
||||
v 10.8 -3.93821 0.181379
|
||||
v 10.8 -3.73862 -0.29139
|
||||
v 10.8 -3.51045 -0.75038
|
||||
v 10.8 -3.25462 -1.19378
|
||||
v 10.8 -2.97214 -1.61984
|
||||
v 10.8 -2.66411 -2.02688
|
||||
v 10.8 -2.33175 -2.41329
|
||||
v 10.8 -1.97638 -2.77754
|
||||
v 10.8 -1.59939 -3.11821
|
||||
v 10.8 -1.20228 -3.43394
|
||||
v 10.8 -0.786614 -3.72349
|
||||
v 10.8 -0.354029 -3.98571
|
||||
v 10.8 0.0937657 -4.21958
|
||||
v 10.8 0.555004 -4.42417
|
||||
v 10.8 1.02786 -4.59866
|
||||
v 10.8 1.51048 -4.74238
|
||||
v 10.8 2.00095 -4.85476
|
||||
v 10.8 2.49733 -4.93534
|
||||
v 10.8 2.99768 -4.98382
|
||||
v 10.8 3.5 -5
|
||||
v 10.8 4.00232 -4.98382
|
||||
v 10.8 4.50267 -4.93534
|
||||
v 10.8 4.99905 -4.85476
|
||||
v 10.8 5.48952 -4.74238
|
||||
v 10.8 5.97214 -4.59866
|
||||
v 10.8 6.445 -4.42417
|
||||
v 10.8 6.90623 -4.21958
|
||||
v 10.8 7.35403 -3.98571
|
||||
v 10.8 7.78661 -3.72349
|
||||
v 10.8 -4.48421 3.71488
|
||||
vn 1 0 0
|
||||
vn 0 1 0
|
||||
vn 0 0.998121 0.0612671
|
||||
vn 0 0.99249 0.122328
|
||||
vn 0 0.983118 0.182975
|
||||
vn 0 0.970027 0.242998
|
||||
vn 0 0.953248 0.302189
|
||||
vn 0 0.932821 0.360339
|
||||
vn 0 0.908798 0.417237
|
||||
vn 0 0.881239 0.472672
|
||||
vn 0 0.850218 0.52643
|
||||
vn 0 0.815823 0.578301
|
||||
vn 0 0.778154 0.628074
|
||||
vn 0 0.737325 0.675539
|
||||
vn 0 0.693466 0.72049
|
||||
vn 0 0.646723 0.762725
|
||||
vn 0 0.59726 0.802047
|
||||
vn 0 0.545255 0.83827
|
||||
vn 0 0.490904 0.871214
|
||||
vn 0 0.434418 0.900712
|
||||
vn 0 0.376024 0.92661
|
||||
vn 0 0.315965 0.948771
|
||||
vn 0 0.254497 0.967074
|
||||
vn 0 0.191886 0.981417
|
||||
vn 0 0.128409 0.991721
|
||||
vn 0 0.0643503 0.997927
|
||||
vn 0 -7.45445e-09 1
|
||||
vn 0 -0.0643503 0.997927
|
||||
vn 0 -0.128409 0.991721
|
||||
vn 0 -0.191886 0.981417
|
||||
vn 0 -0.254497 0.967074
|
||||
vn 0 -0.315965 0.948771
|
||||
vn 0 -0.376024 0.92661
|
||||
vn 0 -0.434418 0.900712
|
||||
vn 0 -0.490903 0.871214
|
||||
vn 0 -0.518378 0.855152
|
||||
vn 0 0.99953 -0.0306454
|
||||
vn -1 0 0
|
||||
f 1//1 2//1 3//1 4//1 5//1 6//1 7//1 8//1 9//1 10//1 11//1 12//1 13//1 14//1 15//1 16//1 17//1 18//1 19//1 20//1 21//1 22//1 23//1 24//1 25//1 26//1 27//1 28//1 29//1 30//1 31//1 32//1 33//1 34//1 35//1 36//1
|
||||
f 37//2 38//3 2//3 1//2
|
||||
f 38//3 39//4 3//4 2//3
|
||||
f 39//4 40//5 4//5 3//4
|
||||
f 40//5 41//6 5//6 4//5
|
||||
f 41//6 42//7 6//7 5//6
|
||||
f 42//7 43//8 7//8 6//7
|
||||
f 43//8 44//9 8//9 7//8
|
||||
f 44//9 45//10 9//10 8//9
|
||||
f 45//10 46//11 10//11 9//10
|
||||
f 46//11 47//12 11//12 10//11
|
||||
f 47//12 48//13 12//13 11//12
|
||||
f 48//13 49//14 13//14 12//13
|
||||
f 49//14 50//15 14//15 13//14
|
||||
f 50//15 51//16 15//16 14//15
|
||||
f 51//16 52//17 16//17 15//16
|
||||
f 52//17 53//18 17//18 16//17
|
||||
f 53//18 54//19 18//19 17//18
|
||||
f 54//19 55//20 19//20 18//19
|
||||
f 55//20 56//21 20//21 19//20
|
||||
f 56//21 57//22 21//22 20//21
|
||||
f 57//22 58//23 22//23 21//22
|
||||
f 58//23 59//24 23//24 22//23
|
||||
f 59//24 60//25 24//25 23//24
|
||||
f 60//25 61//26 25//26 24//25
|
||||
f 61//26 62//27 26//27 25//26
|
||||
f 62//27 63//28 27//28 26//27
|
||||
f 63//28 64//29 28//29 27//28
|
||||
f 64//29 65//30 29//30 28//29
|
||||
f 65//30 66//31 30//31 29//30
|
||||
f 66//31 67//32 31//32 30//31
|
||||
f 67//32 68//33 32//33 31//32
|
||||
f 68//33 69//34 33//34 32//33
|
||||
f 69//34 70//35 34//35 33//34
|
||||
f 70//35 71//36 35//36 34//35
|
||||
f 72//37 37//2 1//2 36//37
|
||||
f 37//38 72//38 71//38 70//38 69//38 68//38 67//38 66//38 65//38 64//38 63//38 62//38 61//38 60//38 59//38 58//38 57//38 56//38 55//38 54//38 53//38 52//38 51//38 50//38 49//38 48//38 47//38 46//38 45//38 44//38 43//38 42//38 41//38 40//38 39//38 38//38
|