├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cmake ├── CppLint.cmake └── FindSFML.cmake ├── default.nix ├── misc └── cpplint.py ├── plot_points.gnuplot ├── scene.txt └── src ├── arguments.cpp ├── arguments.hpp ├── color.cpp ├── color.hpp ├── display.cpp ├── display.hpp ├── main.cpp ├── ray.cpp ├── ray.hpp ├── scene.cpp ├── scene.hpp ├── sphere.cpp ├── sphere.hpp ├── triangle.cpp ├── triangle.hpp ├── vec.hpp ├── wall.cpp └── wall.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | ray-tracer 2 | ray-tracer.dSYM/ 3 | *.ppm 4 | *.png 5 | build/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: cpp 3 | addons: 4 | apt: 5 | packages: 6 | - libsfml-dev 7 | compiler: 8 | - clang 9 | - gcc 10 | before_install: 11 | - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test 12 | - sudo apt-add-repository "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" 13 | - sudo apt-get update -qq 14 | install: 15 | - sudo apt-get install --allow-unauthenticated -qq g++-8 clang++-7 libc++-7-dev libstdc++-8-dev 16 | - sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 90 17 | script: 18 | - cmake . 19 | - make 20 | - make lint 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9.2) 2 | project(ray-tracer) 3 | 4 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) 5 | 6 | find_package(SFML REQUIRED system window graphics) 7 | find_package(Threads REQUIRED) 8 | 9 | include_directories(${SFML_INCLUDE_DIR}) 10 | 11 | set(RAY_TRACER_SOURCES 12 | src/arguments.cpp 13 | src/arguments.hpp 14 | src/color.cpp 15 | src/color.hpp 16 | src/display.cpp 17 | src/display.hpp 18 | src/main.cpp 19 | src/scene.cpp 20 | src/scene.hpp 21 | src/sphere.cpp 22 | src/sphere.hpp 23 | src/triangle.cpp 24 | src/triangle.hpp 25 | src/vec.hpp 26 | src/wall.cpp 27 | src/wall.hpp 28 | src/ray.hpp 29 | src/ray.cpp 30 | ) 31 | 32 | add_executable(ray-tracer ${RAY_TRACER_SOURCES}) 33 | 34 | set_target_properties(ray-tracer PROPERTIES 35 | CXX_STANDARD 14) 36 | 37 | target_compile_options(ray-tracer PRIVATE 38 | -Wall 39 | -Werror 40 | -Wconversion 41 | -Wno-missing-braces 42 | -Wunused-variable 43 | -pedantic 44 | ) 45 | 46 | target_link_libraries(ray-tracer ${SFML_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) 47 | 48 | include(CppLint) 49 | add_cpp_lint_target(lint "${RAY_TRACER_SOURCES}") 50 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Issues 2 | 3 | - Feel free to submit an issues in case of 4 | - bug 5 | - suggestion 6 | - feature request 7 | - question regarding the project 8 | - unclear documentation 9 | - unreadable or straight up bad code 10 | - any problem that you ran into while setting up the project on your side 11 | - Please, do not try to fix issues marked with "good for stream" label. They are for the stream. :) 12 | 13 | # Pull Requests 14 | 15 | - Keep your PRs nice and small around 300 changes. PRs that are way over that threshold won't be merged. 16 | - If 300 changes is not enough, 17 | - make as many changes as you can according to the PR size limitation, 18 | - make sure that the code is compilable and the application is usable, 19 | - create TODOs right in the code for the unfinished work, 20 | - submit PR with the unfinished work and TODOs, 21 | - TODOs will be converted to separate issues 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Alexey Kutepov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Tsoding](https://img.shields.io/badge/twitch.tv-tsoding-purple?logo=twitch&style=for-the-badge)](https://www.twitch.tv/tsoding) 2 | [![Build Status](https://travis-ci.org/tsoding/ray-tracer.svg?branch=master)](https://travis-ci.org/tsoding/ray-tracer) 3 | # Ray Tracer in pure (99.999%) C++ 4 | 5 | ![](https://i.imgur.com/WyFWDAP.png) 6 | 7 | ## Quick Start 8 | 9 | ```console 10 | $ nix-shell # Only if you are on NixOS 11 | $ mkdir build && cd build/ 12 | $ cmake .. 13 | $ make 14 | $ ./ray-tracer ../scene.txt # for preview mode 15 | $ ./ray-tracer ../scene.txt output.ppm # for final rendering 16 | $ feh output.ppm 17 | ``` 18 | -------------------------------------------------------------------------------- /cmake/CppLint.cmake: -------------------------------------------------------------------------------- 1 | find_package(PythonInterp) 2 | 3 | set(STYLE_FILTER) 4 | 5 | # disable unwanted filters 6 | set(STYLE_FILTER ${STYLE_FILTER}-legal/copyright,) 7 | set(STYLE_FILTER ${STYLE_FILTER}-readability/todo,) 8 | 9 | function(add_cpp_lint_target TARGET_NAME SOURCES_LIST) 10 | if (NOT PYTHONINTERP_FOUND) 11 | return() 12 | endif() 13 | 14 | list(REMOVE_DUPLICATES SOURCES_LIST) 15 | list(SORT SOURCES_LIST) 16 | 17 | add_custom_target(${TARGET_NAME} 18 | COMMAND "${CMAKE_COMMAND}" -E chdir 19 | "${CMAKE_CURRENT_SOURCE_DIR}" 20 | "${PYTHON_EXECUTABLE}" 21 | "${CMAKE_SOURCE_DIR}/misc/cpplint.py" 22 | "--filter=${STYLE_FILTER}" 23 | "--counting=detailed" 24 | "--extensions=cpp,hpp,h" 25 | "--linelength=250" 26 | ${SOURCES_LIST} 27 | DEPENDS ${SOURCES_LIST} 28 | COMMENT "Linting ${TARGET_NAME}" 29 | VERBATIM) 30 | endfunction() 31 | -------------------------------------------------------------------------------- /cmake/FindSFML.cmake: -------------------------------------------------------------------------------- 1 | # This script locates the SFML library 2 | # ------------------------------------ 3 | # 4 | # Usage 5 | # ----- 6 | # 7 | # When you try to locate the SFML libraries, you must specify which modules you want to use (system, window, graphics, network, audio, main). 8 | # If none is given, the SFML_LIBRARIES variable will be empty and you'll end up linking to nothing. 9 | # example: 10 | # find_package(SFML COMPONENTS graphics window system) // find the graphics, window and system modules 11 | # 12 | # You can enforce a specific version, either MAJOR.MINOR or only MAJOR. 13 | # If nothing is specified, the version won't be checked (i.e. any version will be accepted). 14 | # example: 15 | # find_package(SFML COMPONENTS ...) // no specific version required 16 | # find_package(SFML 2 COMPONENTS ...) // any 2.x version 17 | # find_package(SFML 2.4 COMPONENTS ...) // version 2.4 or greater 18 | # 19 | # By default, the dynamic libraries of SFML will be found. To find the static ones instead, 20 | # you must set the SFML_STATIC_LIBRARIES variable to TRUE before calling find_package(SFML ...). 21 | # Since you have to link yourself all the SFML dependencies when you link it statically, the following 22 | # additional variables are defined: SFML_XXX_DEPENDENCIES and SFML_DEPENDENCIES (see their detailed 23 | # description below). 24 | # In case of static linking, the SFML_STATIC macro will also be defined by this script. 25 | # example: 26 | # set(SFML_STATIC_LIBRARIES TRUE) 27 | # find_package(SFML 2 COMPONENTS network system) 28 | # 29 | # On Mac OS X if SFML_STATIC_LIBRARIES is not set to TRUE then by default CMake will search for frameworks unless 30 | # CMAKE_FIND_FRAMEWORK is set to "NEVER" for example. Please refer to CMake documentation for more details. 31 | # Moreover, keep in mind that SFML frameworks are only available as release libraries unlike dylibs which 32 | # are available for both release and debug modes. 33 | # 34 | # If SFML is not installed in a standard path, you can use the SFML_ROOT CMake (or environment) variable 35 | # to tell CMake where SFML is. 36 | # 37 | # Output 38 | # ------ 39 | # 40 | # This script defines the following variables: 41 | # - For each specified module XXX (system, window, graphics, network, audio, main): 42 | # - SFML_XXX_LIBRARY_DEBUG: the name of the debug library of the xxx module (set to SFML_XXX_LIBRARY_RELEASE is no debug version is found) 43 | # - SFML_XXX_LIBRARY_RELEASE: the name of the release library of the xxx module (set to SFML_XXX_LIBRARY_DEBUG is no release version is found) 44 | # - SFML_XXX_LIBRARY: the name of the library to link to for the xxx module (includes both debug and optimized names if necessary) 45 | # - SFML_XXX_FOUND: true if either the debug or release library of the xxx module is found 46 | # - SFML_XXX_DEPENDENCIES: the list of libraries the module depends on, in case of static linking 47 | # - SFML_LIBRARIES: the list of all libraries corresponding to the required modules 48 | # - SFML_FOUND: true if all the required modules are found 49 | # - SFML_INCLUDE_DIR: the path where SFML headers are located (the directory containing the SFML/Config.hpp file) 50 | # - SFML_DEPENDENCIES: the list of libraries SFML depends on, in case of static linking 51 | # 52 | # example: 53 | # find_package(SFML 2 COMPONENTS system window graphics audio REQUIRED) 54 | # include_directories(${SFML_INCLUDE_DIR}) 55 | # add_executable(myapp ...) 56 | # target_link_libraries(myapp ${SFML_LIBRARIES}) 57 | 58 | # define the SFML_STATIC macro if static build was chosen 59 | if(SFML_STATIC_LIBRARIES) 60 | add_definitions(-DSFML_STATIC) 61 | endif() 62 | 63 | # define the list of search paths for headers and libraries 64 | set(FIND_SFML_PATHS 65 | ${SFML_ROOT} 66 | $ENV{SFML_ROOT} 67 | ~/Library/Frameworks 68 | /Library/Frameworks 69 | /usr/local 70 | /usr 71 | /sw 72 | /opt/local 73 | /opt/csw 74 | /opt) 75 | 76 | # find the SFML include directory 77 | find_path(SFML_INCLUDE_DIR SFML/Config.hpp 78 | PATH_SUFFIXES include 79 | PATHS ${FIND_SFML_PATHS}) 80 | 81 | # check the version number 82 | set(SFML_VERSION_OK TRUE) 83 | if(SFML_FIND_VERSION AND SFML_INCLUDE_DIR) 84 | # extract the major and minor version numbers from SFML/Config.hpp 85 | # we have to handle framework a little bit differently: 86 | if("${SFML_INCLUDE_DIR}" MATCHES "SFML.framework") 87 | set(SFML_CONFIG_HPP_INPUT "${SFML_INCLUDE_DIR}/Headers/Config.hpp") 88 | else() 89 | set(SFML_CONFIG_HPP_INPUT "${SFML_INCLUDE_DIR}/SFML/Config.hpp") 90 | endif() 91 | FILE(READ "${SFML_CONFIG_HPP_INPUT}" SFML_CONFIG_HPP_CONTENTS) 92 | STRING(REGEX MATCH ".*#define SFML_VERSION_MAJOR ([0-9]+).*#define SFML_VERSION_MINOR ([0-9]+).*#define SFML_VERSION_PATCH ([0-9]+).*" SFML_CONFIG_HPP_CONTENTS "${SFML_CONFIG_HPP_CONTENTS}") 93 | STRING(REGEX REPLACE ".*#define SFML_VERSION_MAJOR ([0-9]+).*" "\\1" SFML_VERSION_MAJOR "${SFML_CONFIG_HPP_CONTENTS}") 94 | STRING(REGEX REPLACE ".*#define SFML_VERSION_MINOR ([0-9]+).*" "\\1" SFML_VERSION_MINOR "${SFML_CONFIG_HPP_CONTENTS}") 95 | STRING(REGEX REPLACE ".*#define SFML_VERSION_PATCH ([0-9]+).*" "\\1" SFML_VERSION_PATCH "${SFML_CONFIG_HPP_CONTENTS}") 96 | math(EXPR SFML_REQUESTED_VERSION "${SFML_FIND_VERSION_MAJOR} * 10000 + ${SFML_FIND_VERSION_MINOR} * 100 + ${SFML_FIND_VERSION_PATCH}") 97 | 98 | # if we could extract them, compare with the requested version number 99 | if (SFML_VERSION_MAJOR) 100 | # transform version numbers to an integer 101 | math(EXPR SFML_VERSION "${SFML_VERSION_MAJOR} * 10000 + ${SFML_VERSION_MINOR} * 100 + ${SFML_VERSION_PATCH}") 102 | 103 | # compare them 104 | if(SFML_VERSION LESS SFML_REQUESTED_VERSION) 105 | set(SFML_VERSION_OK FALSE) 106 | endif() 107 | else() 108 | # SFML version is < 2.0 109 | if (SFML_REQUESTED_VERSION GREATER 10900) 110 | set(SFML_VERSION_OK FALSE) 111 | set(SFML_VERSION_MAJOR 1) 112 | set(SFML_VERSION_MINOR x) 113 | set(SFML_VERSION_PATCH x) 114 | endif() 115 | endif() 116 | endif() 117 | 118 | # find the requested modules 119 | set(SFML_FOUND TRUE) # will be set to false if one of the required modules is not found 120 | foreach(FIND_SFML_COMPONENT ${SFML_FIND_COMPONENTS}) 121 | string(TOLOWER ${FIND_SFML_COMPONENT} FIND_SFML_COMPONENT_LOWER) 122 | string(TOUPPER ${FIND_SFML_COMPONENT} FIND_SFML_COMPONENT_UPPER) 123 | set(FIND_SFML_COMPONENT_NAME sfml-${FIND_SFML_COMPONENT_LOWER}) 124 | 125 | # no suffix for sfml-main, it is always a static library 126 | if(FIND_SFML_COMPONENT_LOWER STREQUAL "main") 127 | # release library 128 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE 129 | NAMES ${FIND_SFML_COMPONENT_NAME} 130 | PATH_SUFFIXES lib64 lib 131 | PATHS ${FIND_SFML_PATHS}) 132 | 133 | # debug library 134 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG 135 | NAMES ${FIND_SFML_COMPONENT_NAME}-d 136 | PATH_SUFFIXES lib64 lib 137 | PATHS ${FIND_SFML_PATHS}) 138 | else() 139 | # static release library 140 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE 141 | NAMES ${FIND_SFML_COMPONENT_NAME}-s 142 | PATH_SUFFIXES lib64 lib 143 | PATHS ${FIND_SFML_PATHS}) 144 | 145 | # static debug library 146 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG 147 | NAMES ${FIND_SFML_COMPONENT_NAME}-s-d 148 | PATH_SUFFIXES lib64 lib 149 | PATHS ${FIND_SFML_PATHS}) 150 | 151 | # dynamic release library 152 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE 153 | NAMES ${FIND_SFML_COMPONENT_NAME} 154 | PATH_SUFFIXES lib64 lib 155 | PATHS ${FIND_SFML_PATHS}) 156 | 157 | # dynamic debug library 158 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG 159 | NAMES ${FIND_SFML_COMPONENT_NAME}-d 160 | PATH_SUFFIXES lib64 lib 161 | PATHS ${FIND_SFML_PATHS}) 162 | 163 | # choose the entries that fit the requested link type 164 | if(SFML_STATIC_LIBRARIES) 165 | if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE) 166 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE}) 167 | endif() 168 | if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG) 169 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG}) 170 | endif() 171 | else() 172 | if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE) 173 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE}) 174 | endif() 175 | if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG) 176 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG}) 177 | endif() 178 | endif() 179 | endif() 180 | 181 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG OR SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) 182 | # library found 183 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_FOUND TRUE) 184 | 185 | # if both are found, set SFML_XXX_LIBRARY to contain both 186 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG AND SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) 187 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY debug ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG} 188 | optimized ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) 189 | endif() 190 | 191 | # if only one debug/release variant is found, set the other to be equal to the found one 192 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG AND NOT SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) 193 | # debug and not release 194 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG}) 195 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG}) 196 | endif() 197 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE AND NOT SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG) 198 | # release and not debug 199 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) 200 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) 201 | endif() 202 | else() 203 | # library not found 204 | set(SFML_FOUND FALSE) 205 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_FOUND FALSE) 206 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY "") 207 | set(FIND_SFML_MISSING "${FIND_SFML_MISSING} SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY") 208 | endif() 209 | 210 | # mark as advanced 211 | MARK_AS_ADVANCED(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY 212 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE 213 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG 214 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE 215 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG 216 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE 217 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG) 218 | 219 | # add to the global list of libraries 220 | set(SFML_LIBRARIES ${SFML_LIBRARIES} "${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY}") 221 | endforeach() 222 | 223 | # in case of static linking, we must also define the list of all the dependencies of SFML libraries 224 | if(SFML_STATIC_LIBRARIES) 225 | 226 | # detect the OS 227 | if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") 228 | set(FIND_SFML_OS_WINDOWS 1) 229 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 230 | set(FIND_SFML_OS_LINUX 1) 231 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") 232 | set(FIND_SFML_OS_FREEBSD 1) 233 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 234 | set(FIND_SFML_OS_MACOSX 1) 235 | endif() 236 | 237 | # start with an empty list 238 | set(SFML_DEPENDENCIES) 239 | set(FIND_SFML_DEPENDENCIES_NOTFOUND) 240 | 241 | # macro that searches for a 3rd-party library 242 | macro(find_sfml_dependency output friendlyname) 243 | find_library(${output} NAMES ${ARGN} PATHS ${FIND_SFML_PATHS} PATH_SUFFIXES lib) 244 | if(${${output}} STREQUAL "${output}-NOTFOUND") 245 | unset(output) 246 | set(FIND_SFML_DEPENDENCIES_NOTFOUND "${FIND_SFML_DEPENDENCIES_NOTFOUND} ${friendlyname}") 247 | endif() 248 | endmacro() 249 | 250 | # sfml-system 251 | list(FIND SFML_FIND_COMPONENTS "system" FIND_SFML_SYSTEM_COMPONENT) 252 | if(NOT ${FIND_SFML_SYSTEM_COMPONENT} EQUAL -1) 253 | 254 | # update the list -- these are only system libraries, no need to find them 255 | if(FIND_SFML_OS_LINUX OR FIND_SFML_OS_FREEBSD OR FIND_SFML_OS_MACOSX) 256 | set(SFML_SYSTEM_DEPENDENCIES "pthread") 257 | endif() 258 | if(FIND_SFML_OS_LINUX) 259 | set(SFML_SYSTEM_DEPENDENCIES "rt") 260 | endif() 261 | if(FIND_SFML_OS_WINDOWS) 262 | set(SFML_SYSTEM_DEPENDENCIES "winmm") 263 | endif() 264 | set(SFML_DEPENDENCIES ${SFML_SYSTEM_DEPENDENCIES} ${SFML_DEPENDENCIES}) 265 | endif() 266 | 267 | # sfml-network 268 | list(FIND SFML_FIND_COMPONENTS "network" FIND_SFML_NETWORK_COMPONENT) 269 | if(NOT ${FIND_SFML_NETWORK_COMPONENT} EQUAL -1) 270 | 271 | # update the list -- these are only system libraries, no need to find them 272 | if(FIND_SFML_OS_WINDOWS) 273 | set(SFML_NETWORK_DEPENDENCIES "ws2_32") 274 | endif() 275 | set(SFML_DEPENDENCIES ${SFML_NETWORK_DEPENDENCIES} ${SFML_DEPENDENCIES}) 276 | endif() 277 | 278 | # sfml-window 279 | list(FIND SFML_FIND_COMPONENTS "window" FIND_SFML_WINDOW_COMPONENT) 280 | if(NOT ${FIND_SFML_WINDOW_COMPONENT} EQUAL -1) 281 | 282 | # find libraries 283 | if(FIND_SFML_OS_LINUX) 284 | find_sfml_dependency(X11_LIBRARY "X11" X11) 285 | find_sfml_dependency(XRANDR_LIBRARY "Xrandr" Xrandr) 286 | find_sfml_dependency(UDEV_LIBRARIES "UDev" udev) 287 | endif() 288 | 289 | # update the list 290 | if(FIND_SFML_OS_WINDOWS) 291 | set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "opengl32" "winmm" "gdi32") 292 | elseif(FIND_SFML_OS_LINUX OR FIND_SFML_OS_FREEBSD) 293 | set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "GL" ${X11_LIBRARY} ${XRANDR_LIBRARY}) 294 | if(FIND_SFML_OS_FREEBSD) 295 | set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "usbhid") 296 | else() 297 | set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} ${UDEV_LIBRARIES}) 298 | endif() 299 | elseif(FIND_SFML_OS_MACOSX) 300 | set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "-framework OpenGL -framework Foundation -framework AppKit -framework IOKit -framework Carbon") 301 | endif() 302 | set(SFML_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} ${SFML_DEPENDENCIES}) 303 | endif() 304 | 305 | # sfml-graphics 306 | list(FIND SFML_FIND_COMPONENTS "graphics" FIND_SFML_GRAPHICS_COMPONENT) 307 | if(NOT ${FIND_SFML_GRAPHICS_COMPONENT} EQUAL -1) 308 | 309 | # find libraries 310 | find_sfml_dependency(FREETYPE_LIBRARY "FreeType" freetype) 311 | find_sfml_dependency(GLEW_LIBRARY "GLEW" glew GLEW glew32 glew32s glew64 glew64s) 312 | find_sfml_dependency(JPEG_LIBRARY "libjpeg" jpeg) 313 | 314 | # update the list 315 | set(SFML_GRAPHICS_DEPENDENCIES ${FREETYPE_LIBRARY} ${GLEW_LIBRARY} ${JPEG_LIBRARY}) 316 | set(SFML_DEPENDENCIES ${SFML_GRAPHICS_DEPENDENCIES} ${SFML_DEPENDENCIES}) 317 | endif() 318 | 319 | # sfml-audio 320 | list(FIND SFML_FIND_COMPONENTS "audio" FIND_SFML_AUDIO_COMPONENT) 321 | if(NOT ${FIND_SFML_AUDIO_COMPONENT} EQUAL -1) 322 | 323 | # find libraries 324 | find_sfml_dependency(OPENAL_LIBRARY "OpenAL" openal openal32) 325 | find_sfml_dependency(SNDFILE_LIBRARY "libsndfile" sndfile) 326 | 327 | # update the list 328 | set(SFML_AUDIO_DEPENDENCIES ${OPENAL_LIBRARY} ${SNDFILE_LIBRARY}) 329 | set(SFML_DEPENDENCIES ${SFML_AUDIO_DEPENDENCIES} ${SFML_DEPENDENCIES}) 330 | endif() 331 | 332 | endif() 333 | 334 | # handle errors 335 | if(NOT SFML_VERSION_OK) 336 | # SFML version not ok 337 | set(FIND_SFML_ERROR "SFML found but version too low (requested: ${SFML_FIND_VERSION}, found: ${SFML_VERSION_MAJOR}.${SFML_VERSION_MINOR}.${SFML_VERSION_PATCH})") 338 | set(SFML_FOUND FALSE) 339 | elseif(SFML_STATIC_LIBRARIES AND FIND_SFML_DEPENDENCIES_NOTFOUND) 340 | set(FIND_SFML_ERROR "SFML found but some of its dependencies are missing (${FIND_SFML_DEPENDENCIES_NOTFOUND})") 341 | set(SFML_FOUND FALSE) 342 | elseif(NOT SFML_FOUND) 343 | # include directory or library not found 344 | set(FIND_SFML_ERROR "Could NOT find SFML (missing: ${FIND_SFML_MISSING})") 345 | endif() 346 | if (NOT SFML_FOUND) 347 | if(SFML_FIND_REQUIRED) 348 | # fatal error 349 | message(FATAL_ERROR ${FIND_SFML_ERROR}) 350 | elseif(NOT SFML_FIND_QUIETLY) 351 | # error but continue 352 | message("${FIND_SFML_ERROR}") 353 | endif() 354 | endif() 355 | 356 | # handle success 357 | if(SFML_FOUND) 358 | message(STATUS "Found SFML ${SFML_VERSION_MAJOR}.${SFML_VERSION_MINOR}.${SFML_VERSION_PATCH} in ${SFML_INCLUDE_DIR}") 359 | endif() 360 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | with import {}; { 2 | raytracerEnv = stdenv.mkDerivation { 3 | name = "raytracer-env"; 4 | buildInputs = [ stdenv 5 | gcc7 6 | imagemagick 7 | sfml 8 | cmake 9 | ]; 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /plot_points.gnuplot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env gnuplot 2 | 3 | set mouse 4 | set datafile separator "," 5 | splot 'points.csv' using 1:3:2 with points palette pointsize 2 pointtype 7 6 | splot 'points.csv' with vectors palette pointsize 2 pointtype 7 -------------------------------------------------------------------------------- /scene.txt: -------------------------------------------------------------------------------- 1 | e 0.0 0.0 -200.0 2 | s 0.0 0.0 200.0 100.0 3 | s 100.0 0.0 100.0 100.0 4 | w 0 0 -1 500 #FFFFFF 5 | w 0 0 1 500 #FF0000 6 | w 1 0 0 500 #00FF00 7 | w -1 0 0 500 #0000FF 8 | w 0 1 0 500 #FFFF00 9 | w 0 -1 0 500 #FF00FF 10 | //t 0.0 200.0 10.0 200.0 -100.0 10.0 -200.0 -100.0 10.0 11 | -------------------------------------------------------------------------------- /src/arguments.cpp: -------------------------------------------------------------------------------- 1 | #include "./arguments.hpp" 2 | 3 | #include 4 | #include 5 | 6 | Arguments::Arguments(int argc, char **argv): 7 | m_argc(argc), 8 | m_argv(argv), 9 | m_threadCount(1), 10 | m_width(800), 11 | m_height(600) { 12 | } 13 | 14 | std::string Arguments::sceneFile() const { 15 | return m_sceneFile; 16 | } 17 | 18 | std::string Arguments::outputFile() const { 19 | return m_outputFile; 20 | } 21 | 22 | size_t Arguments::threadCount() const { 23 | return m_threadCount; 24 | } 25 | 26 | size_t Arguments::width() const { 27 | return m_width; 28 | } 29 | 30 | size_t Arguments::height() const { 31 | return m_height; 32 | } 33 | 34 | bool Arguments::verify() { 35 | int i = 1; 36 | 37 | for (; i < m_argc; ++i) { 38 | if (m_argv[i][0] == '-') { 39 | if (m_argv[i] == std::string("-j")) { 40 | // TODO(#83): Arguments option parsing doesn't check if the parameter of option is available 41 | m_threadCount = std::stoul(m_argv[++i]); 42 | } else if (m_argv[i] == std::string("-w")) { 43 | m_width = std::stoul(m_argv[++i]); 44 | } else if (m_argv[i] == std::string("-h")) { 45 | m_height = std::stoul(m_argv[++i]); 46 | } else { 47 | std::cerr << "Unexpected option: " 48 | << m_argv[i] 49 | << std::endl; 50 | return false; 51 | } 52 | } else { 53 | break; 54 | } 55 | } 56 | 57 | if (i < m_argc) { 58 | m_sceneFile = m_argv[i++]; 59 | } else { 60 | std::cerr << "Expected scene file" 61 | << std::endl; 62 | return false; 63 | } 64 | 65 | if (i < m_argc) { 66 | m_outputFile = m_argv[i++]; 67 | } 68 | 69 | return true; 70 | } 71 | 72 | void Arguments::help() const { 73 | std::cerr << "./ray-tracer " 74 | << "[-j ] " 75 | << "[-w ] " 76 | << "[-h ]" 77 | << " " 78 | << "[output-file] " 79 | << std::endl; 80 | } 81 | -------------------------------------------------------------------------------- /src/arguments.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ARGUMENTS_HPP_ 2 | #define ARGUMENTS_HPP_ 3 | 4 | #include 5 | 6 | class Arguments { 7 | public: 8 | Arguments(int argc, char **argv); 9 | 10 | std::string sceneFile() const; 11 | std::string outputFile() const; 12 | size_t threadCount() const; 13 | size_t width() const; 14 | size_t height() const; 15 | 16 | bool verify(); 17 | 18 | void help() const; 19 | 20 | private: 21 | const int m_argc; 22 | char **m_argv; 23 | 24 | std::string m_sceneFile; 25 | std::string m_outputFile; 26 | size_t m_threadCount; 27 | size_t m_width; 28 | size_t m_height; 29 | }; 30 | 31 | #endif // ARGUMENTS_HPP_ 32 | -------------------------------------------------------------------------------- /src/color.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "color.hpp" 7 | 8 | static int digit_hex_to_dec(char c) { 9 | if ('0' <= c && c <= '9') { 10 | return c - '0'; 11 | } 12 | 13 | if ('A' <= c && c <= 'F') { 14 | return c - 'A' + 10; 15 | } 16 | 17 | return 0; 18 | } 19 | 20 | static bool is_hex_digit(char c) { 21 | return ('0' <= c && c <= '9') || ('A' <= c && c <= 'F'); 22 | } 23 | 24 | static bool is_color_hex_code(const std::string &str) { 25 | if (str.size() != 7) { 26 | return false; 27 | } 28 | 29 | if (str[0] != '#') { 30 | return false; 31 | } 32 | 33 | for (size_t i = 1; i < str.size(); ++i) { 34 | if (!is_hex_digit(str[i])) { 35 | return false; 36 | } 37 | } 38 | 39 | return true; 40 | } 41 | 42 | std::unique_ptr color_from_hex(const std::string &str) { 43 | if (!is_color_hex_code(str)) { 44 | return nullptr; 45 | } 46 | 47 | std::array codes; 48 | std::transform(str.begin() + 1, str.end(), codes.begin(), digit_hex_to_dec); 49 | 50 | const Color hex_color = { 51 | static_cast(codes[0] * 16 + codes[1]) / 255.0f, 52 | static_cast(codes[2] * 16 + codes[3]) / 255.0f, 53 | static_cast(codes[4] * 16 + codes[5]) / 255.0f 54 | }; 55 | 56 | return std::make_unique(hex_color); 57 | } 58 | -------------------------------------------------------------------------------- /src/color.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_COLOR_HPP_ 2 | #define SRC_COLOR_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include "./vec.hpp" 8 | 9 | // TODO(#101): color poorly aligns with image formats 10 | using Color = vec; 11 | 12 | std::unique_ptr color_from_hex(const std::string &str); 13 | 14 | #endif // SRC_COLOR_HPP_ 15 | -------------------------------------------------------------------------------- /src/display.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "display.hpp" 6 | 7 | Display::Display(size_t width, size_t height): 8 | width(width), 9 | height(height), 10 | data(new Color[width * height]) { 11 | } 12 | 13 | Display::Display(const Display &display): 14 | width(display.width), 15 | height(display.height), 16 | data(new Color[width * height]) { 17 | std::copy(display.data, 18 | display.data + width * height, 19 | data); 20 | } 21 | 22 | Display::Display(Display &&display): 23 | width(display.width), 24 | height(display.height), 25 | data(display.data) { 26 | } 27 | 28 | Display::~Display() { 29 | delete[] data; 30 | } 31 | 32 | void Display::put(size_t row, size_t col, const Color &c) { 33 | data[row * width + col] = c; 34 | } 35 | 36 | Color Display::pixel(size_t row, size_t col) const { 37 | return data[row * width + col]; 38 | } 39 | 40 | void save_as_ppm(const Display &display, 41 | const std::string &filepath) { 42 | std::ofstream fout( 43 | filepath, 44 | std::ofstream::out | std::ostream::binary); 45 | 46 | fout << "P6" << std::endl; 47 | fout << display.width << " " << display.height << std::endl; 48 | fout << 255 << std::endl; 49 | 50 | for (size_t row = 0; row < display.height; ++row) { 51 | for (size_t col = 0; col < display.width; ++col) { 52 | for (size_t k = 0; k < 3; ++k) { 53 | const float x = 255.0f * display.pixel(row, col).v[k]; 54 | fout.put(static_cast(x)); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/display.hpp: -------------------------------------------------------------------------------- 1 | #ifndef DISPLAY_HPP_ 2 | #define DISPLAY_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include "color.hpp" 8 | 9 | struct Display { 10 | Display(size_t width, size_t height); 11 | Display(const Display &display); 12 | Display(Display &&display); 13 | ~Display(); 14 | 15 | void put(size_t row, size_t col, const Color &c); 16 | Color pixel(size_t row, size_t col) const; 17 | 18 | const size_t width; 19 | const size_t height; 20 | Color *data; 21 | }; 22 | 23 | void save_as_ppm(const Display &display, 24 | const std::string &filename); 25 | 26 | #endif // DISPLAY_HPP_ 27 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include // NOLINT 19 | 20 | #include "./arguments.hpp" 21 | #include "./color.hpp" 22 | #include "./display.hpp" 23 | #include "./scene.hpp" 24 | #include "./sphere.hpp" 25 | #include "./vec.hpp" 26 | 27 | template 28 | void report_progress(const std::string &title, 29 | T work, T goal) { 30 | const float progress = 31 | static_cast(work) / static_cast(goal) * 100.0f; 32 | 33 | std::cout << "\r" << title << "... " 34 | << std::fixed << std::setprecision(1) 35 | << progress 36 | << std::left << std::setfill(' ') << std::setw(2) 37 | << "%" << std::flush; 38 | } 39 | 40 | void file_render_mode(const Arguments &arguments) { 41 | Display display(arguments.width(), arguments.height()); 42 | Scene scene = load_scene_from_file(arguments.sceneFile()); 43 | 44 | for (size_t row = 0; row < display.height; ++row) { 45 | render_row(scene, &display, row, trace); 46 | report_progress("Rendering", row, display.height - 1); 47 | } 48 | 49 | save_as_ppm(display, arguments.outputFile()); 50 | } 51 | 52 | void preview_mode(const Arguments &arguments) { 53 | // TODO(#102): Preview Mode is not implemented (use SDL please) 54 | std::cerr << "Preview Mode is not implemented" << std::endl; 55 | exit(1); 56 | } 57 | 58 | int main(int argc, char *argv[]) { 59 | Arguments arguments(argc, argv); 60 | 61 | if (!arguments.verify()) { 62 | arguments.help(); 63 | return -1; 64 | } 65 | 66 | if (!arguments.outputFile().empty()) { 67 | file_render_mode(arguments); 68 | } else { 69 | preview_mode(arguments); 70 | } 71 | 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /src/ray.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ray.hpp" 4 | 5 | const float WORLD_HORIZON_FACTOR = 10000.0f; 6 | const Color WORLD_SKYBOX_COLOR = Color{0.0f, 0.0f, 0.0f}; 7 | const float MAX_RAY_LENGTH = 600.0f; 8 | 9 | template 10 | static T sqr(T x) { 11 | return x * x; 12 | } 13 | 14 | // https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection 15 | static float disc(const v3f &o, const v3f &l, // line 16 | const v3f &c, float r) { // sphere 17 | return sqr(dot(l, o - c)) - sqr(len(o - c)) + sqr(r); 18 | } 19 | 20 | Ray void_ray(const Ray &ray) { 21 | return { 22 | ray.origin + WORLD_HORIZON_FACTOR * ray.dir, 23 | ray.dir, 24 | WORLD_SKYBOX_COLOR, 25 | true 26 | }; 27 | } 28 | 29 | Ray absorb_ray(const Ray &ray, const Color &color) { 30 | return { 31 | ray.origin, 32 | ray.dir, 33 | color, 34 | true 35 | }; 36 | } 37 | 38 | Ray collide_ray_with_sphere(const Ray &ray, const Sphere &sphere) { 39 | const v3f o = ray.origin; 40 | const v3f l = ray.dir; 41 | const v3f c = sphere.center; 42 | const float r = sphere.radius; 43 | 44 | const float d2 = disc(o, l, c, r); 45 | 46 | if (d2 <= 0.0f) { 47 | return void_ray(ray); 48 | } 49 | 50 | const float d1 = - dot(l, o - c); 51 | const v3f x1 = o + (d1 - sqrtf(d2)) * l; 52 | const v3f x2 = o + (d1 + sqrtf(d2)) * l; 53 | const v3f x = len(o - x1) < len(o - x2) ? x1 : x2; 54 | 55 | const v3f n = normalize(x - c); 56 | const v3f l1 = normalize(l - (2 * dot(l, n)) * n); 57 | 58 | return { 59 | x, 60 | l1, 61 | ray.color, 62 | ray.absorbed 63 | }; 64 | } 65 | 66 | Ray collide_ray_with_wall(const Ray &ray, const Wall &wall) { 67 | const float b = dot(vec_pop(wall.p), ray.dir); 68 | 69 | // They ray is moving along with the plane 70 | if (std::fabs(b) < 1e-6) { 71 | return void_ray(ray); 72 | } 73 | 74 | const float a = -dot(wall.p, vec_push(ray.origin, 1.0f)); 75 | const float t = a / b; 76 | 77 | // Line intersects the plane but direction points away from the plane 78 | if (t < 0.0f) { 79 | return void_ray(ray); 80 | } 81 | 82 | return { 83 | ray.origin + t * ray.dir, 84 | ray.dir, 85 | wall.c * (1.0f - std::min(MAX_RAY_LENGTH, t) / MAX_RAY_LENGTH), 86 | true 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /src/ray.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RAY_HPP_ 2 | #define RAY_HPP_ 3 | 4 | #include "vec.hpp" 5 | #include "color.hpp" 6 | #include "sphere.hpp" 7 | #include "wall.hpp" 8 | 9 | struct Ray { 10 | v3f origin; 11 | v3f dir; 12 | Color color; 13 | bool absorbed; 14 | }; 15 | 16 | inline Ray ray(const v3f &origin, const v3f &dir, const Color &color) { 17 | return {origin, dir, color, false}; 18 | } 19 | 20 | inline Ray closer_ray(Ray source, Ray r1, Ray r2) { 21 | if (len(source.origin - r1.origin) < len(source.origin - r2.origin)) { 22 | return r1; 23 | } else { 24 | return r2; 25 | } 26 | } 27 | 28 | Ray void_ray(const Ray &ray); 29 | Ray collide_ray_with_sphere(const Ray &ray, const Sphere &sphere); 30 | Ray collide_ray_with_wall(const Ray &ray, const Wall &wall); 31 | 32 | #endif // RAY_HPP_ 33 | -------------------------------------------------------------------------------- /src/scene.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "scene.hpp" 11 | #include "ray.hpp" 12 | 13 | std::ostream& operator<<(std::ostream& os, const Scene&scene) { 14 | os << "Scene{" << std::endl 15 | << " Eye:" << scene.eye << std::endl; 16 | 17 | for (const auto&sphere : scene.spheres) 18 | std::cout << " " << sphere << std::endl; 19 | 20 | for (const auto&wall : scene.walls) 21 | std::cout << " " << wall << std::endl; 22 | 23 | return os << "}" << std::endl; 24 | } 25 | 26 | const Scene load_scene_from_file(const std::string &filename) { 27 | std::ifstream infile(filename); 28 | Scene scene = {}; 29 | std::string type, line; 30 | 31 | while (std::getline(infile, line)) { 32 | std::istringstream iss(line); 33 | iss >> type; 34 | 35 | if (type == "e") { // eye 36 | float eye1, eye2, eye3; 37 | iss >> eye1 >> eye2 >> eye3; 38 | scene.eye = { eye1, eye2, eye3 }; 39 | } else if (type == "s") { // sphere 40 | float c1, c2, c3, radius; 41 | iss >> c1 >> c2 >> c3 >> radius; 42 | scene.spheres.push_back({ {c1, c2, c3}, radius }); 43 | } else if (type == "w") { // walls 44 | float plane1, plane2, plane3, plane4; 45 | std::string color_hex; 46 | iss >> plane1 >> plane2 >> plane3 >> plane4 >> color_hex; 47 | 48 | const auto hex_color = color_from_hex(color_hex); 49 | 50 | scene.walls.push_back({ 51 | {plane1, plane2, plane3, plane4}, 52 | hex_color != nullptr ? *hex_color : Color{1.0f, 1.0f, 1.0f} 53 | }); 54 | } else if (type == "t") { // triangle 55 | Triangle t; 56 | iss >> t.v1 >> t.v2 >> t.v3; 57 | scene.triangles.push_back(t); 58 | } 59 | } 60 | 61 | return scene; 62 | } 63 | 64 | static float color_factor(size_t steps, size_t step_count) { 65 | const float x = 66 | 1.0f - static_cast(steps) / static_cast(step_count); 67 | return x; 68 | } 69 | 70 | Color march(const Scene &scene, Ray start_ray) { 71 | vec3 ray = {start_ray.origin.v[0], start_ray.origin.v[1], 0.0f}; 72 | size_t step_count = 600; 73 | 74 | Color triangle_color = {1.0f, 0.0f, 0.0f}; 75 | 76 | for (size_t i = 0; i < step_count; ++i) { 77 | ray += start_ray.dir; 78 | 79 | for (const auto &sphere : scene.spheres) { 80 | if (is_ray_inside_of_sphere(sphere, ray)) { 81 | vec3 norm = normalize(ray - sphere.center); 82 | start_ray.dir = normalize(start_ray.dir - (2 * dot(start_ray.dir, norm)) * norm); 83 | } 84 | } 85 | 86 | for (const auto &wall : scene.walls) { 87 | if (is_ray_behind_wall(wall, ray)) { 88 | return wall.c * color_factor(i, step_count); 89 | } 90 | } 91 | 92 | for (const auto &triangle : scene.triangles) { 93 | if (ray_hits_triangle(triangle, ray)) { 94 | return triangle_color * color_factor(i, step_count); 95 | } 96 | } 97 | } 98 | 99 | return {0.0f, 0.0f, 0.0f}; 100 | } 101 | 102 | Color trace(const Scene &scene, Ray ray) { 103 | for (size_t i = 0; i < 10 && !ray.absorbed; ++i) { 104 | Ray next_ray = void_ray(ray); 105 | 106 | for (const auto &wall : scene.walls) { 107 | next_ray = closer_ray( 108 | ray, 109 | collide_ray_with_wall(ray, wall), 110 | next_ray); 111 | } 112 | 113 | // TODO(#105): rays are getting stuck inside of spheres 114 | for (const auto &sphere : scene.spheres) { 115 | next_ray = closer_ray( 116 | ray, 117 | collide_ray_with_sphere(ray, sphere), 118 | next_ray); 119 | 120 | if (is_ray_inside_of_sphere(sphere, next_ray.origin)) { 121 | next_ray.absorbed = true; 122 | next_ray.color = Color{1.0f, 0.0f, 0.0f}; 123 | } 124 | } 125 | 126 | ray = next_ray; 127 | // std::cout << ray.origin << "->" << ray.dir << std::endl; 128 | } 129 | 130 | // TODO(#89): trace() does not support triangles 131 | 132 | return ray.color; 133 | } 134 | 135 | Color debug_sphere(const Scene &scene, Ray ray) { 136 | for (const auto &sphere : scene.spheres) { 137 | Ray sphere_ray = collide_ray_with_sphere(ray, sphere); 138 | if (!sphere_ray.absorbed) { 139 | std::cout << sphere_ray.origin << ',' 140 | << 10.0f * sphere_ray.dir << std::endl; 141 | } 142 | } 143 | 144 | return Color{0.0f, 0.0f, 0.0f}; 145 | } 146 | 147 | void render_row(const Scene &scene, 148 | Display *display, 149 | size_t row, 150 | RenderPixel renderPixel) { 151 | assert(display); 152 | 153 | const float half_width = static_cast(display->width) * 0.5f; 154 | const float half_height = static_cast(display->height) * 0.5f; 155 | 156 | for (size_t col = 0; col < display->width; ++col) { 157 | const vec3 origin = { static_cast(col) - half_width, 158 | static_cast(row) - half_height, 159 | 0.0f }; 160 | display->put( 161 | row, col, 162 | renderPixel( 163 | scene, 164 | ray( 165 | origin, 166 | normalize(origin - scene.eye), 167 | Color{1.0f, 1.0f, 1.0f}))); 168 | } 169 | } 170 | 171 | // TODO(#103): parallel rendering is not implemented 172 | -------------------------------------------------------------------------------- /src/scene.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SCENE_HPP_ 2 | #define SCENE_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "sphere.hpp" 9 | #include "wall.hpp" 10 | #include "triangle.hpp" 11 | #include "display.hpp" 12 | #include "ray.hpp" 13 | 14 | struct Scene { 15 | vec3 eye; 16 | std::vector spheres; 17 | std::vector walls; 18 | std::vector triangles; 19 | }; 20 | 21 | using RenderPixel = std::function; 22 | 23 | std::ostream& operator<<(std::ostream& os, const Scene&scene); 24 | const Scene load_scene_from_file(const std::string &filename); 25 | Color march(const Scene &scene, Ray ray); 26 | Color trace(const Scene &scene, Ray ray); 27 | Color debug_sphere(const Scene &scene, Ray ray); 28 | 29 | void render_row(const Scene &scene, 30 | Display *display, 31 | size_t row, 32 | RenderPixel renderPixel); 33 | 34 | #endif // SCENE_HPP_ 35 | -------------------------------------------------------------------------------- /src/sphere.cpp: -------------------------------------------------------------------------------- 1 | #include "./sphere.hpp" 2 | 3 | std::ostream& operator<<(std::ostream& os, const Sphere&sphere) { 4 | return os << "Sphere{ center " 5 | << sphere.center 6 | << " radius " 7 | << sphere.radius 8 | << "}"; 9 | } 10 | 11 | bool is_ray_inside_of_sphere(const Sphere &sphere, const vec3 &ray) { 12 | return sqr_norm(sphere.center - ray) < sphere.radius * sphere.radius; 13 | } 14 | -------------------------------------------------------------------------------- /src/sphere.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SPHERE_HPP_ 2 | #define SPHERE_HPP_ 3 | 4 | #include "vec.hpp" 5 | 6 | struct Sphere { 7 | vec3 center; 8 | float radius; 9 | }; 10 | 11 | std::ostream& operator<<(std::ostream& os, const Sphere&sphere); 12 | bool is_ray_inside_of_sphere(const Sphere &sphere, const vec3 &ray); 13 | 14 | #endif // SPHERE_HPP_ 15 | -------------------------------------------------------------------------------- /src/triangle.cpp: -------------------------------------------------------------------------------- 1 | #include "./triangle.hpp" 2 | 3 | v3f cross_product(const v3f &v1, const v3f &v2) { 4 | // i , j , k 5 | // v1.v[0], v1.v[1], v1.v[2] 6 | // v2.v[0], v2.v[1], v2.v[2] 7 | // 8 | //////////////////////////////// 9 | // 10 | // i * v1.v[1], v1.v[2] 11 | // v2.v[1], v2.v[2] 12 | // 13 | // -j * v1.v[0], v1.v[2] 14 | // v2.v[0], v2.v[2] 15 | // 16 | // k * v1.v[0], v1.v[1], 17 | // v2.v[0], v2.v[1], 18 | // 19 | //////////////////////////////// 20 | // 21 | // i = v1.v[1] * v2.v[2] - v1.v[2] * v2.v[1] 22 | // j = v1.v[2] * v2.v[0] - v1.v[0] * v2.v[2] 23 | // k = v1.v[0] * v2.v[1] - v1.v[1] * v2.v[0] 24 | 25 | return v3f { 26 | v1.v[1] * v2.v[2] - v1.v[2] * v2.v[1], 27 | v1.v[2] * v2.v[0] - v1.v[0] * v2.v[2], 28 | v1.v[0] * v2.v[1] - v1.v[1] * v2.v[0] 29 | }; 30 | } 31 | 32 | plane plane_of_triangle(const Triangle &triangle) { 33 | const v3f ab = triangle.v2 - triangle.v1; 34 | const v3f bc = triangle.v3 - triangle.v1; 35 | const v3f xp = normalize(cross_product(ab, bc)); 36 | 37 | // xp.v[0] * v1.v[0] + xp.v[1] * v1.v[1] + xp.v[2] * v1.v[2] + d = 0 38 | const float d = - xp.v[0] * triangle.v1.v[0] - xp.v[1] * triangle.v1.v[1] - xp.v[2] * triangle.v1.v[2]; 39 | 40 | return plane {xp.v[0], xp.v[1], xp.v[2], d}; 41 | } 42 | 43 | float point_segment_distance(const v3f &p, const v3f &a, const v3f &b) { 44 | const v3f n = b - a; 45 | const v3f pa = a - p; 46 | const v3f c = n * (dot(pa, n) / dot(n, n)); 47 | const v3f d = pa - c; 48 | return sqrtf(dot(d, d)); 49 | } 50 | 51 | bool ray_hits_triangle(const Triangle &triangle, 52 | const v3f &ray) { 53 | const auto p = plane_of_triangle(triangle); 54 | const auto n = normalize(vec_pop(p)); 55 | 56 | const auto AB = triangle.v2 - triangle.v1; 57 | const auto BC = triangle.v3 - triangle.v2; 58 | const auto CA = triangle.v1 - triangle.v3; 59 | 60 | return dot(n, cross_product(AB, ray - triangle.v1)) > 0 61 | && dot(n, cross_product(BC, ray - triangle.v2)) > 0 62 | && dot(n, cross_product(CA, ray - triangle.v3)) > 0; 63 | } 64 | 65 | // The point is in the triangle if EACH of the DISTANCES is less than 66 | // or equal to the HEIGHT of the triangle RELATIVE to the opposite 67 | // vertex 68 | // 69 | // - HEIGHT of the triangle 70 | // - opposite vertex 71 | -------------------------------------------------------------------------------- /src/triangle.hpp: -------------------------------------------------------------------------------- 1 | #ifndef TRIANGLE_HPP_ 2 | #define TRIANGLE_HPP_ 3 | 4 | #include "vec.hpp" 5 | #include "wall.hpp" 6 | 7 | // TODO(#65): support for triangle meshes from obj files 8 | 9 | struct Triangle { 10 | v3f v1, v2, v3; 11 | }; 12 | 13 | plane plane_of_triangle(const Triangle &triangle); 14 | 15 | bool ray_hits_triangle(const Triangle &triangle, 16 | const v3f &ray); 17 | 18 | #endif // TRIANGLE_HPP_ 19 | -------------------------------------------------------------------------------- /src/vec.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SRC_VEC_HPP_ 2 | #define SRC_VEC_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template 9 | struct vec { 10 | T v[N]; 11 | }; 12 | 13 | template 14 | using vec3 = vec; 15 | 16 | using v3f = vec3; 17 | 18 | template 19 | inline std::ostream& operator<<(std::ostream& os, const vec &vec) { 20 | for (size_t i = 0; i < N - 1; i++) 21 | os << vec.v[i] << ","; 22 | os << vec.v[N-1]; 23 | 24 | return os; 25 | } 26 | 27 | template 28 | inline std::istream& operator>>(std::istream& is, vec &vec) { 29 | for (size_t i = 0; i < N; ++i) { 30 | is >> vec.v[i]; 31 | } 32 | return is; 33 | } 34 | 35 | template 36 | inline vec operator+(const vec &v1, const vec &v2) { 37 | vec result; 38 | 39 | for (size_t i = 0; i < N; ++i) { 40 | result.v[i] = v1.v[i] + v2.v[i]; 41 | } 42 | 43 | return result; 44 | } 45 | 46 | template 47 | inline vec operator-(const vec &v1, const vec &v2) { 48 | vec result; 49 | 50 | for (size_t i = 0; i < N; ++i) { 51 | result.v[i] = v1.v[i] - v2.v[i]; 52 | } 53 | 54 | return result; 55 | } 56 | 57 | template 58 | inline vec &operator+=(vec &v1, // NOLINT(runtime/references) 59 | const vec &v2) { 60 | for (size_t i = 0; i < N; ++i) { 61 | v1.v[i] += v2.v[i]; 62 | } 63 | 64 | return v1; 65 | } 66 | 67 | template 68 | inline vec operator*(const T &s, const vec &v) { 69 | vec result; 70 | 71 | for (size_t i = 0; i < N; ++i) { 72 | result.v[i] = v.v[i] * s; 73 | } 74 | 75 | return result; 76 | } 77 | 78 | template 79 | inline vec operator*(const vec &v, const T &s) { 80 | return s * v; 81 | } 82 | 83 | template 84 | inline T sqr_norm(const vec &v) { 85 | T acc = T(); 86 | 87 | for (size_t i = 0; i < N; ++i) { 88 | acc += v.v[i] * v.v[i]; 89 | } 90 | 91 | return acc; 92 | } 93 | 94 | template 95 | inline T dot(const vec &v1, const vec &v2) { 96 | T result = T(); 97 | 98 | for (size_t i = 0; i < N; ++i) { 99 | result += v1.v[i] * v2.v[i]; 100 | } 101 | 102 | return result; 103 | } 104 | 105 | inline vec3 normalize(const vec3 &v) { 106 | return 1.0f / sqrtf(sqr_norm(v)) * v; 107 | } 108 | 109 | template 110 | inline float len(const vec &v) { 111 | return sqrtf(sqr_norm(v)); 112 | } 113 | 114 | template 115 | inline vec vec_push(const vec &v1, const T &t) { 116 | vec v2; 117 | 118 | for (size_t i = 0; i < N; ++i) { 119 | v2.v[i] = v1.v[i]; 120 | } 121 | 122 | v2.v[N] = t; 123 | 124 | return v2; 125 | } 126 | 127 | template 128 | inline vec vec_pop(const vec &v) { 129 | vec result; 130 | std::copy(v.v, v.v + N - 1, result.v); 131 | return result; 132 | } 133 | #endif // SRC_VEC_HPP_ 134 | -------------------------------------------------------------------------------- /src/wall.cpp: -------------------------------------------------------------------------------- 1 | #include "./wall.hpp" 2 | 3 | std::ostream& operator<<(std::ostream& os, const Wall&wall) { 4 | return os << "Wall{ p" << wall.p << " c" << wall.c << "}"; 5 | } 6 | 7 | bool is_ray_behind_wall(const Wall &wall, const vec3 &ray) { 8 | return dot(wall.p, vec_push(ray, 1.0f)) < 0.0f; 9 | } 10 | -------------------------------------------------------------------------------- /src/wall.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WALL_H_ 2 | #define WALL_H_ 3 | 4 | #include "./color.hpp" 5 | 6 | using plane = vec; 7 | 8 | struct Wall { 9 | plane p; 10 | Color c; 11 | }; 12 | 13 | std::ostream& operator<<(std::ostream& os, const Wall&wall); 14 | bool is_ray_behind_wall(const Wall &wall, const vec3 &ray); 15 | 16 | #endif // WALL_H_ 17 | --------------------------------------------------------------------------------