47 Commits

Author SHA1 Message Date
Manoël Trapier
0e188a0594 Update build badge from travis to github action.
(free for OSS project) Travis is dead!
2022-02-28 11:43:31 +00:00
Godzil
c1f41cb6f6 Trying to fix github actions. 2022-02-28 11:37:41 +00:00
Godzil
50143076ab Fix a typo in README 2022-02-28 11:29:44 +00:00
Manoël Trapier
c33cdabdec Add some actions! 2022-02-28 11:04:51 +00:00
Godzil
ad16643111 Googletest...
This "master" to "main" is already none-sense, but renaming the principal branch on an existing repo to break everything.
Thanks a lot.
2022-02-28 10:55:52 +00:00
Godzil
45f5f8098e Force cmake 3.11 because of changes in add_executable
Remove Ubuntu 18.04 arm target as it use an old cmake.
2021-03-10 15:22:26 +00:00
Godzil
28b619c566 Try to change (again) the travis file. May work this time? 2021-03-10 14:08:08 +00:00
Godzil
4f512405b2 Travis works, but always is unpredictable at time.. 2021-03-10 12:26:52 +00:00
Godzil
fe80488c5e Update travis file to more up to date practice for Mac OS X build using homebrew 2021-03-10 12:17:28 +00:00
Godzil
f8f4d7b21a Add sample output from the dragon scene 2021-03-10 11:58:09 +00:00
Godzil
5651570c2b Slight changes in octree/bvh 2020-03-17 00:44:58 +00:00
Godzil
71c236abb0 Better to fill the function I suppose. 2020-03-17 00:31:30 +00:00
Godzil
c0fc061834 Don't set the focal/aperture on the constructor and use a dedicated method for that. 2020-03-17 00:28:56 +00:00
Godzil
61ce9d3543 Add possibility to play with focal length and aperture to the camera. Not enabled by default. 2020-03-17 00:23:16 +00:00
Godzil
9849c16f66 Couple of small optimisation
Add option to build for gprof
Do not build by default with the renderstat (they have a reasonable impact on performances)
Separated created ray and castedray in the stats
Trying to force some simple function to be inlined
2020-03-13 18:22:35 +00:00
Godzil
aacd4f6c9e Make BVH and Octree to not flatten the world before parsing
It may help a lot on some scene that already use groups.

So each exiting group have their own BVH or Octree in.

It does help a lot on already organised scene like the Christmas balls.
2020-03-13 01:07:28 +00:00
Godzil
5b6b627a43 Small but effective optimisations.
Allow inlining the function helps a bit!
2020-03-13 01:04:56 +00:00
Manoël Trapier
4f2e327533 Update for a proper testing size and don’t get a timeout 2020-03-12 19:39:32 +00:00
Godzil
c858b4dcde A new scene and some optimisations. 2020-03-12 17:45:29 +00:00
Godzil
7a43a98816 Adding BVH optimisation. 2020-03-12 07:21:44 +00:00
Godzil
47e31714f3 That file was supposed to be there XD 2020-03-12 00:14:50 +00:00
Godzil
424d58c59b Octree Optimiser seems to work.
Well.. There are a couple of weird things happening, need to investigate, but it's not that easy when scene start to be complicated :/
2020-03-12 00:14:00 +00:00
Godzil
5996e38e6e Fix my own stupidity.. 2020-03-12 00:11:48 +00:00
Godzil
b00bb75189 Huge speed up by changing how Intersect are shared. 2020-03-12 00:11:26 +00:00
Godzil
0aa949c60b Continue working work optimiser. 2020-03-11 19:35:08 +00:00
Godzil
420203e95d Add a tool to debug the normal vector in objfiles by rendering them. 2020-03-11 16:59:02 +00:00
Godzil
c667ce26d7 Fix a bug in objfile where OBJ file were not the parent of the base group. 2020-03-11 16:57:49 +00:00
Godzil
13cc2c0ff9 Add more useful dump info for shapes 2020-03-11 16:56:38 +00:00
Godzil
1c4018e47a Cosmetics. 2020-03-11 16:56:03 +00:00
Godzil
083a5b600f typo 2020-03-11 16:54:46 +00:00
Godzil
3a2d21b787 Starting to work on the World Optimiser sequence. 2020-03-11 09:09:30 +00:00
Godzil
441d758845 Add locking mechanism to prevent updating transform/parent 2020-03-10 13:55:27 +00:00
Godzil
5da0c10182 Started working on "world optimiser" base class.
The idea is to reorganise objets to take use of the them to prevent most object to be intersect if they are not likely to be on the ray path.
2020-03-10 09:18:15 +00:00
Godzil
b89f9ec331 Add a way to remove shapes from groups 2020-03-10 09:16:39 +00:00
Godzil
add3d7c861 A bit of cleanup 2020-03-10 09:15:38 +00:00
Godzil
4e241a1871 Trying to make the dump a bit more usefull and slightly less cluttered 2020-03-09 17:41:11 +00:00
Godzil
15a861802a OBJFile now use group instead of it own list.
Change the group function to find group by name, and now name group when needed, and change tests to use group name instead of number.

Also now the default group is the base group, not a separate group.

Bonus: Add a destructor to cleanup memory
Added a function to get base group. OBJFile will still behave as a valid shape, but now we can skip it in the generated world by adding the base group instead.

Code is now cleaner.
2020-03-09 16:03:27 +00:00
Godzil
66a8b98aeb Add missing code \o/ 2020-03-09 15:59:52 +00:00
Godzil
0f945b69cc World default group now have a name!
Fancy!
2020-03-09 15:58:59 +00:00
Godzil
314da11005 Add support for naming groups 2020-03-09 15:58:32 +00:00
Godzil
8437ab8753 Move ShapeType into the Shape object. 2020-03-09 15:57:23 +00:00
Godzil
d514219ae6 Due to the way material are working now, we need to set by hand (for now) that the material is set. 2020-03-09 14:17:08 +00:00
Godzil
efa47f28ca Fix some potential buffer overflow issues.
Not critical, but better to avoid them!
2020-03-09 14:16:05 +00:00
Godzil
e653855556 Change World to use a base group instead of storing all the world object.
Makes the code simpler and avoid some potential issues. Add to change a bit some test to make them pass properly.
2020-03-09 13:47:42 +00:00
Godzil
1510de3b36 Change CSG to derive the intersect function and do the calculation there instead of the local one.
Also use the bounding box to reject ray that don't it that CSG element.
2020-03-09 13:45:57 +00:00
Godzil
8550d4068f Correct how filterIntersections work, seems c++ don't like how thing were done. 2020-03-09 13:44:10 +00:00
Godzil
cd93b67274 Revert some debug changes that should have not been commited. 2020-03-09 13:42:21 +00:00
71 changed files with 2261 additions and 467 deletions

44
.github/workflows/cmake.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: CMake
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.allow_failure }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-latest ]
allow_failure: [false]
# include:
# - os: macos-latest
# allow_failure: true
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Test round 1
working-directory: ${{github.workspace}}/build
run: make test
- name: Test round 2
working-directory: ${{github.workspace}}/build/tests
run: make test

View File

@@ -1,17 +1,13 @@
dist: bionic
language: c
addons:
apt:
packages:
- lcov
os:
- linux
- osx
compiler:
- clang
- gcc
- imagemagick
homebrew:
packages:
- imagemagick
script:
- mkdir build
@@ -22,10 +18,58 @@ script:
- cd tests
- ./testMyRays
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink python@2 ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install imagemagick ; fi
jobs:
include:
- os: linux
dist: bionic
arch: amd64
compiler: gcc
- os: linux
dist: bionic
arch: amd64
compiler: clang
- os: linux
dist: focal
arch: amd64
compiler: gcc
- os: linux
dist: focal
arch: amd64
compiler: clang
- os: osx
compiler: clang
osx_image: xcode12.2
- os: linux
dist: focal
arch: arm64
compiler: gcc
- os: osx
compiler: clang
osx_image: xcode10.3
- os: osx
compiler: clang
osx_image: xcode11.6
- os: osx
compiler: clang
osx_image: xcode12
- stage: "Coverage"
os: linux
dist: bionic
arch: amd64
compiler: gcc
script:
- mkdir coverage
@@ -36,7 +80,8 @@ jobs:
after_success:
- bash <(curl -s https://codecov.io/bash)
before_install:
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew unlink python@2 ; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install imagemagick ; fi
allow_failures:
- os: linux
arch: arm64
- os: osx

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.1)
cmake_minimum_required(VERSION 3.11)
include(ExternalProject)
@@ -12,11 +12,17 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/external/covera
option(PACKAGE_TESTS "Build the tests" ON)
option(ENABLE_COVERAGE "Build for code coverage" OFF)
option(SHOW_STATS "Show rendering stat" ON)
option(SHOW_STATS "Show rendering stat" OFF)
if (SHOW_STATS)
add_compile_options(-DRENDER_STATS)
endif()
option(USE_GPROF "Enable profiling" OFF)
if (USE_GPROF)
add_compile_options(-pg)
add_link_options(-pg)
endif()
option(USE_LUA "Enable the use of Lua" ON)
if (USE_LUA)
add_compile_options(-DENABLE_LUA_SUPPORT)

View File

@@ -1,4 +1,4 @@
[![codecov](https://codecov.io/gh/Godzil/DoRayMe/branch/master/graph/badge.svg)](https://codecov.io/gh/Godzil/DoRayMe) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/66339747e4a843719cba29cf5e31ff90)](https://www.codacy.com/manual/Godzil/DoRayMe?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=Godzil/DoRayMe&amp;utm_campaign=Badge_Grade) [![Coverage Status](https://coveralls.io/repos/github/Godzil/DoRayMe/badge.svg?branch=master)](https://coveralls.io/github/Godzil/DoRayMe?branch=master) [![Build Status](https://travis-ci.org/Godzil/DoRayMe.svg?branch=master)](https://travis-ci.org/Godzil/DoRayMe)
[![codecov](https://codecov.io/gh/Godzil/DoRayMe/branch/master/graph/badge.svg)](https://codecov.io/gh/Godzil/DoRayMe) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/66339747e4a843719cba29cf5e31ff90)](https://www.codacy.com/manual/Godzil/DoRayMe?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=Godzil/DoRayMe&amp;utm_campaign=Badge_Grade) [![Coverage Status](https://coveralls.io/repos/github/Godzil/DoRayMe/badge.svg?branch=master)](https://coveralls.io/github/Godzil/DoRayMe?branch=master) [![CMake](https://github.com/Godzil/DoRayMe/actions/workflows/cmake.yml/badge.svg)](https://github.com/Godzil/DoRayMe/actions/workflows/cmake.yml)
DoRayMe
=======
@@ -80,7 +80,7 @@ Examples outputs
###### Spherical mapping:
![Spherical mapping](output/uvmap_checkeredsphere.png)
######Planar mapping:
###### Planar mapping:
![Planar mapping](output/uvmap_checkeredplane.png)
###### Cylindrical mapping:
@@ -96,4 +96,7 @@ Examples outputs
![Image mapping](output/uvmap_earth.png)
###### Skybox:
![Skybox](output/uvmap_skybox.png)
![Skybox](output/uvmap_skybox.png)
###### Large OBJ file:
![Dragon](output/dragon_scene.png)

BIN
output/dragon_scene.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

685
samplescenes/orrery.yml Normal file
View File

@@ -0,0 +1,685 @@
# ======================================================
# orrery.yml
#
# This file describes the title image for the "Texture
# Mapping" bonus chapter at:
#
# http://www.raytracerchallenge.com/bonus/texture-mapping.html
#
# It requires several additional resources, provided as a
# separate download. The resources were found on the following
# sites:
#
# * https://www.bittbox.com/freebies/free-hi-resolution-wood-textures
# : the wooden texture for the table
# * https://astrogeology.usgs.gov/search/map/Mercury/Messenger/Global/Mercury_MESSENGER_MDIS_Basemap_LOI_Mosaic_Global_166m
# : the map of Mercury
# * http://planetpixelemporium.com/planets.html
# : maps of Earth, Mars, Jupiter, Saturn, Uranus, and Neptune
# * https://hdrihaven.com/hdri/?c=indoor&h=artist_workshop
# : the "artist workshop" environment map
#
# by Jamis Buck <jamis@jamisbuck.org>
# ======================================================
- add: camera
width: 800
height: 400
field-of-view: 1.2
from: [ 2, 4, -10]
to: [ -1, -1, 0 ]
up: [ 0, 1, 0 ]
# The scene as shown in the bonus chapter is rendered using an area light,
# precisely as described in the "Rendering soft shadows" bonus chapter,
# here: http://www.raytracerchallenge.com/bonus/area-light.html
#
# if you haven't implemented area lights, you can replace this with a point
# light located at [0, 2.5, -10].
- add: light
corner: [-5, 0, -10 ]
uvec: [ 10, 0, 0 ]
vvec: [ 0, 5, 0 ]
usteps: 10
vsteps: 5
jitter: true
intensity: [ 1, 1, 1 ]
# -------------------------------------------
# some common textures
# -------------------------------------------
- define: GOLD
value:
color: [ 1, 0.8, 0.1 ]
ambient: 0.1
diffuse: 0.6
specular: 0.3
shininess: 15
- define: SILVER
value:
color: [ 1, 1, 1 ]
ambient: 0.1
diffuse: 0.7
specular: 0.3
shininess: 15
# -----------------------------------------------
# CSG definition for the gears used to construct
# the orrery.
#
# NOTCH is a helper object used to create the
# teeth for the gears.
#
# GEAR is the actual gear object itself.
# -----------------------------------------------
- define: NOTCH
value:
add: csg
operation: difference
left:
type: cube
transform:
- [ scale, 1, 0.25, 1 ]
- [ translate, 1, 0, 1 ]
- [ rotate-y, 0.7854 ]
- [ scale, 1, 1, 0.1 ]
right:
type: cylinder
min: -0.26
max: 0.26
closed: true
transform:
- [ scale, 0.8, 1, 0.8 ]
- define: GEAR
value:
add: csg
operation: difference
left:
type: cylinder
min: -0.025
max: 0.025
closed: true
right:
type: group
children:
# center hole
- add: cylinder
min: -0.06
max: 0.06
closed: true
transform:
- [ scale, 0.1, 1, 0.1 ]
# crescents
- add: csg
operation: difference
left:
type: cylinder
min: -0.06
max: 0.06
closed: true
transform:
- [ scale, 0.7, 1, 0.7 ]
right:
type: cube
transform:
- [ scale, 1, 0.1, 0.2 ]
# teeth
- add: NOTCH
- add: NOTCH
transform:
- [ rotate-y, 0.31415 ]
- add: NOTCH
transform:
- [ rotate-y, 0.6283 ]
- add: NOTCH
transform:
- [ rotate-y, 0.94245 ]
- add: NOTCH
transform:
- [ rotate-y, 1.2566 ]
- add: NOTCH
transform:
- [ rotate-y, 1.57075 ]
- add: NOTCH
transform:
- [ rotate-y, 1.8849 ]
- add: NOTCH
transform:
- [ rotate-y, 2.19905 ]
- add: NOTCH
transform:
- [ rotate-y, 2.5132 ]
- add: NOTCH
transform:
- [ rotate-y, 2.82735 ]
- add: NOTCH
transform:
- [ rotate-y, 3.1415 ]
- add: NOTCH
transform:
- [ rotate-y, -0.31415 ]
- add: NOTCH
transform:
- [ rotate-y, -0.6283 ]
- add: NOTCH
transform:
- [ rotate-y, -0.94245 ]
- add: NOTCH
transform:
- [ rotate-y, -1.2566 ]
- add: NOTCH
transform:
- [ rotate-y, -1.57075 ]
- add: NOTCH
transform:
- [ rotate-y, -1.8849 ]
- add: NOTCH
transform:
- [ rotate-y, -2.19905 ]
- add: NOTCH
transform:
- [ rotate-y, -2.5132 ]
- add: NOTCH
transform:
- [ rotate-y, -2.82735 ]
# mechanism: top plate
- add: csg
operation: difference
material: GOLD
transform:
- [ rotate-y, -1 ]
left:
type: cylinder
min: -1.51
max: -1.5
closed: true
right:
type: group
children:
- add: cylinder
min: -1.52
max: -1.49
closed: true
transform:
- [ scale, 0.1, 1, 0.1 ]
- add: csg
operation: difference
left:
type: cylinder
min: -1.52
max: -1.49
closed: true
transform:
- [ scale, 0.75, 1, 0.75 ]
right:
type: cube
transform:
- [ scale, 1, 0.1, 0.2 ]
- [ translate, 0, -1.5, 0 ]
# mechanism: gear
- add: GEAR
material: SILVER
transform:
- [ scale, 0.5, 0.5, 0.5 ]
- [ translate, 0.4, -1.45, -0.4 ]
# mechanism: gear
- add: GEAR
material: SILVER
transform:
- [ rotate-y, 0.8 ]
- [ scale, 0.4, 0.4, 0.4 ]
- [ translate, -0.4, -1.45, 0.2 ]
# sun
- add: group
children:
- add: sphere
shadow: false
material:
color: [1, 1, 0]
ambient: 0.1
diffuse: 0.6
specular: 0 # count on the skybox reflection being the specular highlight
reflective: 0.2
- add: group
material: GOLD
children:
- add: cylinder
min: -4
max: -0.5
transform:
- [ scale, 0.025, 1, 0.025 ]
# base
- add: sphere
transform:
- [ translate, 0, -4, 0 ]
material:
pattern:
type: map
mapping: spherical
uv_pattern:
type: checkers
width: 16
height: 8
colors:
- [ 0, 0, 0 ]
- [ 0.5, 0.5, 0.5 ]
diffuse: 0.6
specular: 0 # count on the skybox reflection being the specular highlight
ambient: 0.1
reflective: 0.2
# table
- add: cube
transform:
- [ scale, 5, 0.1, 5 ]
- [ translate, 0, -4, 0 ]
material:
diffuse: 0.9
ambient: 0.1
specular: 0
pattern:
type: map
mapping: planar
uv_pattern:
type: image
file: res/wood.ppm
transform:
- [ scale, 0.5, 0.5, 0.5 ]
# mechanism: gear-plate between top & mercury
- add: GEAR
material: SILVER
transform:
- [ rotate-y, -0.4 ]
- [ scale, 0.9, 0.9, 0.9 ]
- [ translate, 0, -1.75, 0 ]
# mercury
- add: group
transform:
- [ translate, 2, 0, 0 ]
- [ rotate-y, 0.7 ]
children:
- add: sphere
transform:
- [ scale, 0.25, 0.25, 0.25 ]
material:
pattern:
type: map
mapping: spherical
uv_pattern:
type: image
file: res/mercury-small.ppm
- add: group
material: GOLD
children:
- add: cylinder
min: -2
max: 0
transform:
- [ scale, 0.025, 1, 0.025 ]
- add: sphere
transform:
- [ scale, 0.025, 0.025, 0.025 ]
- [ translate, 0, -2, 0 ]
- add: cylinder
min: 0
max: 2
transform:
- [ scale, 0.025, 1, 0.025 ]
- [ rotate-z, 1.5708 ]
- [ translate, 0, -2, 0 ]
# mechanism: gear-plate between mercury & venus
- add: GEAR
material: SILVER
transform:
- [ rotate-y, 1.3 ]
- [ translate, 0, -2.05, 0 ]
# venus
- add: group
transform:
- [ translate, 3, 0, 0 ]
- [ rotate-y, 0.3 ]
children:
- add: sphere
transform:
- [ scale, 0.25, 0.25, 0.25 ]
material:
color: [ 1, 1, 0.8 ]
- add: group
material: GOLD
children:
- add: cylinder
min: -2.1
max: 0
transform:
- [ scale, 0.025, 1, 0.025 ]
- add: sphere
transform:
- [ scale, 0.025, 0.025, 0.025 ]
- [ translate, 0, -2.1, 0 ]
- add: cylinder
min: 0
max: 3
transform:
- [ scale, 0.025, 1, 0.025 ]
- [ rotate-z, 1.5708 ]
- [ translate, 0, -2.1, 0 ]
# mechanism: gear-plate between venus & earth
- add: GEAR
material: SILVER
transform:
- [ scale, 0.9, 0.9, 0.9 ]
- [ rotate-y, -2.2 ]
- [ translate, 0, -2.15, 0 ]
# earth
- add: group
transform:
- [ translate, 4, 0, 0 ]
- [ rotate-y, 2 ]
children:
- add: sphere
transform:
- [ scale, 0.25, 0.25, 0.25 ]
material:
pattern:
type: map
mapping: spherical
uv_pattern:
type: image
file: res/earthmap-small.ppm
- add: group
material: GOLD
children:
- add: cylinder
min: -2.2
max: 0
transform:
- [ scale, 0.025, 1, 0.025 ]
- add: sphere
transform:
- [ scale, 0.025, 0.025, 0.025 ]
- [ translate, 0, -2.2, 0 ]
- add: cylinder
min: 0
max: 4
transform:
- [ scale, 0.025, 1, 0.025 ]
- [ rotate-z, 1.5708 ]
- [ translate, 0, -2.2, 0 ]
# mechanism: gear-plate between earth & mars
- add: GEAR
material: SILVER
transform:
- [ scale, 0.8, 0.8, 0.8 ]
- [ rotate-y, 1.7 ]
- [ translate, 0, -2.25, 0 ]
# mars
- add: group
transform:
- [ translate, 5, 0, 0 ]
- [ rotate-y, -2 ]
children:
- add: sphere
transform:
- [ scale, 0.25, 0.25, 0.25 ]
material:
pattern:
type: map
mapping: spherical
uv_pattern:
type: image
file: res/mars-small.ppm
- add: group
material: GOLD
children:
- add: cylinder
min: -2.3
max: 0
transform:
- [ scale, 0.025, 1, 0.025 ]
- add: sphere
transform:
- [ scale, 0.025, 0.025, 0.025 ]
- [ translate, 0, -2.3, 0 ]
- add: cylinder
min: 0
max: 5
transform:
- [ scale, 0.025, 1, 0.025 ]
- [ rotate-z, 1.5708 ]
- [ translate, 0, -2.3, 0 ]
# mechanism: gear-plate between mars & jupiter
- add: GEAR
material: SILVER
transform:
- [ rotate-y, -0.9 ]
- [ translate, 0, -2.35, 0 ]
# jupiter
- add: group
transform:
- [ translate, 6.5, 0, 0 ]
- [ rotate-y, -0.75 ]
children:
- add: sphere
transform:
- [ scale, 0.67, 0.67, 0.67 ]
material:
pattern:
type: map
mapping: spherical
uv_pattern:
type: image
file: res/jupitermap-small.ppm
- add: group
material: GOLD
children:
- add: cylinder
min: -2.4
max: 0
transform:
- [ scale, 0.025, 1, 0.025 ]
- add: sphere
transform:
- [ scale, 0.025, 0.025, 0.025 ]
- [ translate, 0, -2.4, 0 ]
- add: cylinder
min: 0
max: 6.5
transform:
- [ scale, 0.025, 1, 0.025 ]
- [ rotate-z, 1.5708 ]
- [ translate, 0, -2.4, 0 ]
# mechanism: gear-plate between jupiter & saturn
- add: GEAR
material: SILVER
transform:
- [ scale, 0.95, 0.95, 0.95 ]
- [ rotate-y, -1.1 ]
- [ translate, 0, -2.45, 0 ]
# saturn
- add: group
transform:
- [ translate, 8, 0, 0 ]
- [ rotate-y, -2.5 ]
children:
- add: sphere
transform:
- [ scale, 0.5, 0.5, 0.5 ]
material:
pattern:
type: map
mapping: spherical
uv_pattern:
type: image
file: res/saturnmap-small.ppm
# rings
- add: csg
operation: difference
transform:
- [ rotate-z, 0.2 ]
material:
pattern:
type: rings
colors:
- [ 1, 1, 0.5 ]
- [ 1, 1, 0 ]
transform:
- [ scale, 0.05, 1, 0.05 ]
left:
type: cylinder
min: -0.01
max: 0.01
closed: true
transform:
- [ scale, 1.2, 1, 1.2 ]
right:
type: cylinder
min: -0.02
max: 0.02
closed: true
transform:
- [ scale, 0.75, 1, 0.75 ]
- add: group
material: GOLD
children:
- add: cylinder
min: -2.5
max: 0
transform:
- [ scale, 0.025, 1, 0.025 ]
- add: sphere
transform:
- [ scale, 0.025, 0.025, 0.025 ]
- [ translate, 0, -2.5, 0 ]
- add: cylinder
min: 0
max: 8
transform:
- [ scale, 0.025, 1, 0.025 ]
- [ rotate-z, 1.5708 ]
- [ translate, 0, -2.5, 0 ]
# mechanism: gear-plate between saturn & uranus
- add: GEAR
material: SILVER
transform:
- [ scale, 0.9, 0.9, 0.9 ]
- [ rotate-y, 1 ]
- [ translate, 0, -2.55, 0 ]
# uranus
- add: group
transform:
- [ translate, 9, 0, 0 ]
- [ rotate-y, -3 ]
children:
- add: sphere
transform:
- [ scale, 0.4, 0.4, 0.4 ]
material:
pattern:
type: map
mapping: spherical
uv_pattern:
type: image
file: res/uranusmap-small.ppm
- add: group
material: GOLD
children:
- add: cylinder
min: -2.6
max: 0
transform:
- [ scale, 0.025, 1, 0.025 ]
- add: sphere
transform:
- [ scale, 0.025, 0.025, 0.025 ]
- [ translate, 0, -2.6, 0 ]
- add: cylinder
min: 0
max: 9
transform:
- [ scale, 0.025, 1, 0.025 ]
- [ rotate-z, 1.5708 ]
- [ translate, 0, -2.6, 0 ]
# mechanism: gear-plate between uranus & neptune
- add: GEAR
material: SILVER
transform:
- [ rotate-y, -1 ]
- [ translate, 0, -2.65, 0 ]
# neptune
- add: group
transform:
- [ translate, 10, 0, 0 ]
- [ rotate-y, -1.25 ]
children:
- add: sphere
transform:
- [ scale, 0.4, 0.4, 0.4 ]
material:
pattern:
type: map
mapping: spherical
uv_pattern:
type: image
file: res/neptunemap-small.ppm
- add: group
material: GOLD
children:
- add: cylinder
min: -2.7
max: 0
transform:
- [ scale, 0.025, 1, 0.025 ]
- add: sphere
transform:
- [ scale, 0.025, 0.025, 0.025 ]
- [ translate, 0, -2.7, 0 ]
- add: cylinder
min: 0
max: 10
transform:
- [ scale, 0.025, 1, 0.025 ]
- [ rotate-z, 1.5708 ]
- [ translate, 0, -2.7, 0 ]
# outer sphere as the surrounding environment
- add: sphere
transform:
- [ scale, 1000, 1000, 1000 ]
material:
pattern:
type: map
mapping: spherical
uv_pattern:
type: image
file: res/artist_workshop.ppm
transform:
- [ rotate-y, -2.7 ]
diffuse: 0
specular: 0
ambient: 1

View File

@@ -12,7 +12,7 @@ file(GLOB RAY_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h ${CMAKE_CURRENT_SO
${CMAKE_CURRENT_SOURCE_DIR}/uvpattern/*.h)
file(GLOB RAY_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shapes/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/worldbuilder/*.cpp)
${CMAKE_CURRENT_SOURCE_DIR}/worldbuilder/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/worldoptimiser/*.cpp)
target_include_directories(rayonnement PUBLIC include pattern)

View File

@@ -15,10 +15,11 @@
#include <stdio.h>
#include <renderstat.h>
Camera::Camera(uint32_t hsize, uint32_t vsize, double fov) : verticalSize(vsize), horizontalSize(hsize), fieldOfView(fov)
Camera::Camera(uint32_t hsize, uint32_t vsize, double fov) : verticalSize(vsize),
horizontalSize(hsize), fieldOfView(fov), focalDistance(1), apertureSize(0), rayCount(1)
{
double aspectRatio = (double)hsize / (double)vsize;
double halfView = tan(fov / 2.0);
double halfView = tan(fov / 2.0) * this->focalDistance;
if (aspectRatio >= 1)
{
@@ -42,7 +43,7 @@ void Camera::setTransform(Matrix transform)
this->inverseTransform = transform.inverse();
}
Ray Camera::rayForPixel(uint32_t pixelX, uint32_t pixelY)
Ray Camera::rayForPixel(uint32_t pixelX, uint32_t pixelY, double horzOffset, double vertOffset)
{
double xOffset = ((double)pixelX + 0.5) * this->pixelSize;
double yOffset = ((double)pixelY + 0.5) * this->pixelSize;
@@ -50,10 +51,11 @@ Ray Camera::rayForPixel(uint32_t pixelX, uint32_t pixelY)
double worldX = this->halfWidth - xOffset;
double worldY = this->halfHeight - yOffset;
Tuple pixel = this->inverseTransform * Point(worldX, worldY, -1);
Tuple origin = this->inverseTransform * Point(0, 0, 0);
Tuple pixel = this->inverseTransform * Point(worldX, worldY, -this->focalDistance);
Tuple origin = this->inverseTransform * Point(horzOffset, vertOffset, 0);
Tuple direction = (pixel - origin).normalise();
stats.addCastedRay();
return Ray(origin, direction);
}
@@ -69,9 +71,25 @@ Canvas Camera::render(World world, uint32_t depth)
{
for (x = 0 ; x < this->horizontalSize ; x++)
{
Ray r = this->rayForPixel(x, y);
Tuple colour = world.colourAt(r, depth);
Tuple colour;
if (this->apertureSize > 0)
{
int i;
for (i = 0 ; i < this->rayCount ; i++)
{
double horz = frandclip(-this->apertureSize/2, this->apertureSize / 2);
double vert = frandclip(-this->apertureSize/2, this->apertureSize / 2);
Ray r = this->rayForPixel(x, y, horz, vert);
colour = colour + world.colourAt(r, depth);
}
colour = colour / this->rayCount;
}
else
{
Ray r = this->rayForPixel(x, y);
colour = world.colourAt(r, depth);
}
stats.addPixel();
image.putPixel(x, y, colour);
}

View File

@@ -21,6 +21,10 @@ private:
double halfWidth;
double halfHeight;
public:
double focalDistance;
double apertureSize;
uint32_t rayCount;
uint32_t verticalSize;
uint32_t horizontalSize;
double fieldOfView;
@@ -30,8 +34,13 @@ public:
public:
Camera(uint32_t hsize, uint32_t vsize, double fov);
void setFocal(double focal, double aperture, uint32_t rayCount) {
this->focalDistance = focal;
this->apertureSize = aperture;
this->rayCount = rayCount;
}
void setTransform(Matrix transform);
Ray rayForPixel(uint32_t pixelX, uint32_t pixelY);
Ray rayForPixel(uint32_t pixelX, uint32_t pixelY, double horzOffset = 0, double vertOffset = 0);
Canvas render(World w, uint32_t depth = 5);
};

View File

@@ -17,7 +17,7 @@
class Cone : public Shape {
protected:
Intersect localIntersect(Ray r);
void localIntersect(Ray r, Intersect &xs);
Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
@@ -29,7 +29,7 @@ public:
double minCap;
double maxCap;
Cone() : minCap(-INFINITY), maxCap(INFINITY), isClosed(false), Shape(SHAPE_CONE) { stats.addCone(); };
Cone() : minCap(-INFINITY), maxCap(INFINITY), isClosed(false), Shape(Shape::CONE) { stats.addCone(); };
BoundingBox getLocalBounds();
bool haveFiniteBounds() { return !(isinf(this->minCap) || isinf(this->maxCap)); };

View File

@@ -28,13 +28,13 @@ protected:
BoundingBox bounds;
protected:
Intersect localIntersect(Ray r);
void localIntersect(Ray r, Intersect &xs);
Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
BoundingBox getLocalBounds();
bool intersectionAllowed(bool leftHit, bool inLeft, bool inRight);
Intersect filterIntersections(Intersect &xs);
void filterIntersections(Intersect &xs, Intersect &ret);
void updateBoundingBox();
BoundingBox getBounds();
@@ -42,12 +42,14 @@ protected:
public:
CSG(OperationType operation, Shape *left, Shape *right);
Intersect intersect(Ray r);
void intersect(Ray &r, Intersect &xs);
bool includes(Shape *b);
void updateTransform();
void lock();
void dumpMe(FILE *fp);
};

View File

@@ -19,12 +19,12 @@ class Cube : public Shape {
protected:
void checkAxis(double axeOrigin, double axeDirection, double *axeMin, double *axeMax);
Intersect localIntersect(Ray r);
void localIntersect(Ray r, Intersect &xs);
Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
public:
Cube() : Shape(SHAPE_CUBE) { stats.addCube(); };
Cube() : Shape(Shape::CUBE) { stats.addCube(); };
void dumpMe(FILE *fp);
};

View File

@@ -18,7 +18,7 @@
class Cylinder : public Shape {
protected:
Intersect localIntersect(Ray r);
void localIntersect(Ray r, Intersect &xs);
Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
@@ -30,7 +30,7 @@ public:
double minCap;
double maxCap;
Cylinder() : minCap(-INFINITY), maxCap(INFINITY), isClosed(false), Shape(SHAPE_CYLINDER) { stats.addCylinder(); };
Cylinder() : minCap(-INFINITY), maxCap(INFINITY), isClosed(false), Shape(Shape::CYLINDER) { stats.addCylinder(); };
BoundingBox getLocalBounds();
bool haveFiniteBounds() { return !(isinf(this->minCap) || isinf(this->maxCap)); };

View File

@@ -25,8 +25,10 @@ private:
Shape* *unboxableObjectList;
uint32_t unboxableObjectCount;
char name[32 + 1];
protected:
Intersect localIntersect(Ray r);
void localIntersect(Ray r, Intersect &xs);
Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
BoundingBox bounds;
@@ -35,9 +37,13 @@ public:
bool isEmpty();
void addObject(Shape *s);
Shape *operator[](const int p) { return this->objectList[p]; }
void removeObject(Shape *s);
Intersect intersect(Ray r);
Shape *operator[](const int p) { return this->getObject(p); };
Shape *getObject(const int p) { return this->objectList[p]; };
Shape *getUnboxable(const int p) { return this->unboxableObjectList[p]; };
void intersect(Ray &r, Intersect &xs);
BoundingBox getLocalBounds();
BoundingBox getBounds();
@@ -46,7 +52,16 @@ public:
bool includes(Shape *b);
Group();
uint32_t getObjectCount() { return this->objectCount; };
uint32_t getUnboxableCount() { return this->unboxableObjectCount; };
Group(const char *name = nullptr);
void lock();
void setBounds(BoundingBox &bb) { this->bounds | bb; };
const char *getName() { return this->name; };
void dumpMe(FILE * fp);
};

View File

@@ -25,31 +25,7 @@ struct Computation
object(object), t(t), hitPoint(point), eyeVector(eyev), normalVector(normalv), inside(inside),
overHitPoint(overHitP), underHitPoint(underHitP), reflectVector(reflectV), n1(n1), n2(n2), material(objMat) { };
double schlick()
{
/* Find the cos of the angle betzeen the eye and normal vector */
double cos = this->eyeVector.dot(this->normalVector);
double r0;
/* Total internal reflection can only occur when n1 > n2 */
if (this->n1 > this->n2)
{
double n, sin2_t;
n = this->n1 / this->n2;
sin2_t = (n * n) * (1.0 - (cos * cos));
if (sin2_t > 1.0)
{
return 1.0;
}
/* Compute the cos of theta */
cos = sqrt(1.0 - sin2_t);
}
r0 = ((this->n1 - this->n2) / (this->n1 + this->n2));
r0 = r0 * r0;
return r0 + (1 - r0) * ((1 - cos)*(1 - cos)*(1 - cos)*(1 - cos)*(1 - cos));
};
double schlick();
Shape *object;
double t;
@@ -73,7 +49,6 @@ class Intersection
public:
double t;
Shape *object;
double u, v;
public:

View File

@@ -22,6 +22,7 @@ double min3(double a, double b, double c);
double max3(double a, double b, double c);
double frand();
double frandclip(double min, double max);
static double modulo(double a, double b)
{

View File

@@ -19,6 +19,8 @@
#undef minor
#endif
#define FastGet4(_x, _y) (this->data[4 * (_x) + (_y)])
class Matrix
{
protected:
@@ -46,7 +48,12 @@ public:
bool isInvertible() { return this->determinant() != 0; }
Matrix operator*(const Matrix &b) const;
Tuple operator*(const Tuple &b) const;
Tuple operator*(const Tuple &b) const {
return Tuple(b.x * FastGet4(0, 0) + b.y * FastGet4(0, 1) + b.z * FastGet4(0, 2) + b.w * FastGet4(0, 3),
b.x * FastGet4(1, 0) + b.y * FastGet4(1, 1) + b.z * FastGet4(1, 2) + b.w * FastGet4(1, 3),
b.x * FastGet4(2, 0) + b.y * FastGet4(2, 1) + b.z * FastGet4(2, 2) + b.w * FastGet4(2, 3),
b.x * FastGet4(3, 0) + b.y * FastGet4(3, 1) + b.z * FastGet4(3, 2) + b.w * FastGet4(3, 3));
}
};
class Matrix4: public Matrix

View File

@@ -18,9 +18,9 @@
class OBJFile : public Shape
{
private:
uint32_t allocatedFaceGroupCount;
Group* *faceGroupList;
uint32_t faceGroupCount;
Group *baseGroup;
Group *currentGroup;
uint32_t allocatedVertexCount;
Point* *vertexList;
@@ -31,7 +31,7 @@ private:
uint32_t vertexNormalCount;
private:
Intersect localIntersect(Ray r);
void localIntersect(Ray r, Intersect &xs);
Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
public:
@@ -51,22 +51,30 @@ public:
OBJFile();
OBJFile(const char *filepath);
~OBJFile();
int parseOBJFile(const char *content);
/* OBJ file expect the first vertice to be 1 and not 0 */
Point vertices(uint32_t i) { return *this->vertexList[i - 1]; };
Vector verticesNormal(uint32_t i) { return *this->vertexNormalList[i - 1]; };
Group *groups(uint32_t i) { return this->faceGroupList[i]; };
Intersect intersect(Ray r);
Group *groups(const char *groupName);
void intersect(Ray &r, Intersect &xs);
BoundingBox getLocalBounds();
BoundingBox getBounds();
Shape *getBaseGroup() { return this->baseGroup; };
bool includes(Shape *b);
void updateBoundingBox();
void updateTransform();
void lock();
void dumpMe(FILE * fp);
};
#define OBJ_DEFAULT_GROUP "_DefaultObjGroup_"
#endif /* DORAYME_OBJFILE_H */

View File

@@ -14,11 +14,11 @@
class Plane : public Shape
{
protected:
Intersect localIntersect(Ray r);
void localIntersect(Ray r, Intersect &xs);
Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
public:
Plane() : Shape(SHAPE_PLANE) { stats.addPlane(); };
Plane() : Shape(Shape::PLANE) { stats.addPlane(); };
BoundingBox getLocalBounds();
bool haveFiniteBounds() { return false; };
};

View File

@@ -27,7 +27,8 @@ private:
uint64_t csgCount; /* Total number of CSG */
uint64_t pixelCount; /* Total number of rendered pixels */
uint64_t rayCount; /* Total number of rays */
uint64_t rayCount; /* Total number of rays object created */
uint64_t rayCasted; /* Total number of rays actually casted */
uint64_t lightRayEmitedCount; /* Total number of ray launched for light tests */
uint64_t reflectionRayCount; /* Total number of reflection ray launched */
uint64_t refractedRayCount; /* Total number of refracted ray launched */
@@ -43,7 +44,8 @@ public:
RenderStats() : coneCount(0), cylinderCount(0), cubeCount(0), groupCount(0), lightCount(0), planeCount(0), sphereCount(0), triangleCount(0),
pixelCount(0), rayCount(0), lightRayEmitedCount(0), reflectionRayCount(0), refractedRayCount(0),
intersectCount(0), intersectionCount(0), reallocCallCount(0), mallocCallCount(0), smoothTriangleCount(0),
discardedIntersectCount(0), maxDepthAttained(UINT64_MAX), maxIntersectOnARay(0), objfileCount(0), csgCount(0) {};
discardedIntersectCount(0), maxDepthAttained(UINT64_MAX), maxIntersectOnARay(0), objfileCount(0),
csgCount(0), rayCasted(0) {};
#ifdef RENDER_STATS
void addCone();
void addCylinder();
@@ -59,6 +61,7 @@ public:
void printStats();
void addPixel();
void addRay();
void addCastedRay();
void addLightRay();
void addReflectRay();
void addRefractRay();
@@ -82,6 +85,7 @@ public:
static void printStats() {};
static void addPixel() {};
static void addRay() {};
static void addCastedRay() {};
static void addLightRay() {};
static void addReflectRay() {};
static void addRefractRay() {};

View File

@@ -19,32 +19,37 @@ class Shape;
#include <material.h>
#include <boundingbox.h>
enum ShapeType
{
SHAPE_NONE,
SHAPE_SPHERE,
SHAPE_PLANE,
SHAPE_CUBE,
SHAPE_CYLINDER,
SHAPE_CONE,
SHAPE_GROUP,
SHAPE_TRIANGLE,
SHAPE_OBJFILE,
SHAPE_SMOOTHTRIANGLE,
SHAPE_CSG,
};
/* Base class for all object that can be presented in the world */
class Shape
{
public:
enum ShapeType
{
NONE,
SPHERE,
PLANE,
CUBE,
CYLINDER,
CONE,
GROUP,
TRIANGLE,
OBJFILE,
SMOOTHTRIANGLE,
CSG,
};
protected:
ShapeType type;
Matrix localTransformMatrix;
bool locked;
uint64_t objectId;
protected:
virtual Intersect localIntersect(Ray r) = 0;
virtual void localIntersect(Ray r, Intersect &xs) = 0;
virtual Tuple localNormalAt(Tuple point, Intersection *hit) = 0;
static uint64_t newObjectId();
public:
Matrix transformMatrix;
Matrix inverseTransform;
@@ -56,11 +61,16 @@ public:
bool materialSet;
public:
Shape(ShapeType = SHAPE_NONE);
Shape(ShapeType = Shape::NONE);
virtual Intersect intersect(Ray r);
ShapeType getType() { return this->type; };
virtual void intersect(Ray &r, Intersect &xs) { this->localIntersect(this->invTransform(r), xs); };
Tuple normalAt(Tuple point, Intersection *hit = nullptr);
uint64_t getObjectId() { return this->objectId; };
void setObjectId(uint64_t oid) { this->objectId = oid; };
/* Bounding box points are always world value */
virtual BoundingBox getLocalBounds();
virtual BoundingBox getBounds();
@@ -72,14 +82,25 @@ public:
virtual void dumpMe(FILE *fp);
/* When an object is locked, the matrix transformation and bounding box can't be updated. This is
* usefull to move object between group without changing the real hierarchy between them.
* It will also not change the parent member.
* This is supposed to be used only before a render is going to start so we can optimise the
* way the object are stored to prefer lots of un-needed intersections.
*/
virtual void lock() { this->locked = true; };
Tuple worldToObject(Tuple point) { return this->inverseTransform * point; };
Tuple objectToWorld(Tuple point) { return this->transformMatrix * point; };
Tuple normalToWorld(Tuple normalVector);
void setParent(Shape *parent) { if (!this->locked) { this->parent = parent; };};
void setTransform(Matrix transform);
void setMaterial(Material material) { this->material = material; this->materialSet = true; };
Ray transform(Ray r) { return Ray(this->transformMatrix * r.origin, this->transformMatrix * r.direction); };
Ray invTransform(Ray r) { return Ray(this->inverseTransform * r.origin, this->inverseTransform * r.direction); };
Material *getMaterial();
Ray transform(Ray &r) { return Ray(this->transformMatrix * r.origin, this->transformMatrix * r.direction); };
Ray invTransform(Ray &r) { return Ray(this->inverseTransform * r.origin, this->inverseTransform * r.direction); };
bool operator==(const Shape &b) const { return this->material == b.material &&
this->type == b.type &&

View File

@@ -23,6 +23,8 @@ protected:
public:
SmoothTriangle(Point p1, Point p2, Point p3, Vector n1, Vector n2, Vector n3);
void dumpMe(FILE *fp);
};
#endif /* DORAYME_SMOOTHTRIANGLE_H */

View File

@@ -18,11 +18,11 @@
class Sphere : public Shape
{
protected:
Intersect localIntersect(Ray r);
void localIntersect(Ray r, Intersect &xs);
Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
public:
Sphere() : Shape(SHAPE_SPHERE) { stats.addSphere(); };
Sphere() : Shape(Shape::SPHERE) { stats.addSphere(); };
/* All sphere are at (0, 0, 0) and radius 1 in the object space */

View File

@@ -16,7 +16,7 @@
class TestShape : public Shape
{
private:
Intersect localIntersect(Ray r);
void localIntersect(Ray r, Intersect &xs);
Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
public:

View File

@@ -15,7 +15,7 @@
class Triangle : public Shape
{
protected:
Intersect localIntersect(Ray r);
void localIntersect(Ray r, Intersect &xs);
Tuple localNormalAt(Tuple point, Intersection *hit = nullptr);
public:

View File

@@ -39,16 +39,23 @@ public:
this->z * b, this->w * b); };
Tuple operator/(const double &b) const { return Tuple(this->x / b, this->y / b,
this->z / b, this->w / b); };
void fixPoint();
void fixVector();
bool isRepresentable();
void set(double nX, double nY, double nZ) { this->x = nX; this->y = nY; this->z = nZ; };
double magnitude();
Tuple normalise();
double dot(const Tuple &b);
Tuple cross(const Tuple &b) const;
double dot(const Tuple &b) {
return this->x * b.x + this->y * b.y + this->z * b.z + this->w * b.w;
}
Tuple cross(const Tuple &b) const {
return Tuple(this->y * b.z - this->z * b.y,
this->z * b.x - this->x * b.z,
this->x * b.y - this->y * b.x,
0);
}
Tuple reflect(const Tuple &normal);
};

View File

@@ -15,6 +15,8 @@
#include <intersect.h>
#include <ray.h>
#include <stdio.h>
#include <group.h>
#include <worldoptimiser.h>
#ifdef ENABLE_LUA_SUPPORT
extern "C" {
@@ -25,15 +27,13 @@ extern "C" {
class World
{
public:
uint32_t objectCount;
uint32_t lightCount;
private:
uint32_t allocatedObjectCount;
uint32_t allocatedLightCount;
Light* *lightList;
Shape* *objectList;
Group worldGroup;
#ifdef ENABLE_LUA_SUPPORT
lua_State *L;
@@ -50,9 +50,12 @@ public:
bool lightIsIn(Light &l);
bool objectIsIn(Shape &s);
Shape *getObject(int i) { return this->objectList[i]; };
Shape *getObject(int i) { return this->worldGroup[i]; };
Light *getLight(int i) { return this->lightList[i]; };
uint32_t getObjectCount() { return this->worldGroup.getObjectCount(); };
uint32_t getLightCount() { return this->lightCount; };
Tuple shadeHit(Computation comps, uint32_t depthCount = 4);
Tuple colourAt(Ray r, uint32_t depthCount = 4);
bool isShadowed(Tuple point, Tuple lightPosition);
@@ -60,7 +63,9 @@ public:
Colour reflectColour(Computation comps, uint32_t depthCount = 4);
Colour refractedColour(Computation comps, uint32_t depthCount = 4);
Intersect intersect(Ray r);
void intersect(Ray &r, Intersect &xs);
void finalise(WorldOptimiser &opt);
void dumpMe(FILE *fp);
};

View File

@@ -0,0 +1,51 @@
/*
* DoRayMe - a quick and dirty Raytracer
* World optimiser header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_WORLDOPTIMISER_H
#define DORAYME_WORLDOPTIMISER_H
#include <group.h>
/* World Optimiser subclasses will created move objects around to try to optimise the raytrace of the world, to
* have as least as possible object to intersect per ray.
* This class is abstract to we can implement different type and change at runtime or build time
*/
class WorldOptimiser
{
protected:
Group *root;
void moveInfiniteObjects(Shape *s = nullptr);
void moveAllObjects(Shape *s = nullptr);
public:
void setRoot(Group *root) { this->root = root; };
virtual void run() = 0;
};
class NoWorldOptimisation : public WorldOptimiser
{
public:
void run() {};
};
class BVHOptimisation : public WorldOptimiser
{
private:
void makeTree(Group *leaf, int depth = 0);
public:
void run();
};
class OctreeOptimisation : public WorldOptimiser
{
private:
void makeTree(Group *leaf, int depth = 0);
public:
void run();
};
#endif /* DORAYME_WORLDOPTIMISER_H */

View File

@@ -24,9 +24,17 @@ Intersect::Intersect()
{
this->allocated = MIN_ALLOC;
this->list = (Intersection **)calloc(sizeof(Intersection *), MIN_ALLOC);
stats.addMalloc();
stats.addIntersect();
this->num = 0;
if (this->list != nullptr)
{
stats.addMalloc();
stats.addIntersect();
this->num = 0;
}
else
{
printf("ABORT: Allocation error [%s]!\n", __FUNCTION__);
exit(-1);
}
}
Intersect::~Intersect()
@@ -34,12 +42,17 @@ Intersect::~Intersect()
int i;
for(i = 0; i < this->num; i++)
{
delete this->list[i];
if (this->list[i] != nullptr)
{
delete this->list[i];
this->list[i] = nullptr;
}
}
/* Free stuff */
if (this->list != nullptr)
{
free(this->list);
this->list = nullptr;
}
}

View File

@@ -10,6 +10,32 @@
#include <shape.h>
#include <list.h>
double Computation::schlick()
{
/* Find the cos of the angle betzeen the eye and normal vector */
double cos = this->eyeVector.dot(this->normalVector);
double r0;
/* Total internal reflection can only occur when n1 > n2 */
if (this->n1 > this->n2)
{
double n, sin2_t;
n = this->n1 / this->n2;
sin2_t = (n * n) * (1.0 - (cos * cos));
if (sin2_t > 1.0)
{
return 1.0;
}
/* Compute the cos of theta */
cos = sqrt(1.0 - sin2_t);
}
r0 = ((this->n1 - this->n2) / (this->n1 + this->n2));
r0 = r0 * r0;
return r0 + (1 - r0) * ((1 - cos)*(1 - cos)*(1 - cos)*(1 - cos)*(1 - cos));
};
Computation Intersection::prepareComputation(Ray r, Intersect *xs)
{
double n1 = 1.0;
@@ -42,7 +68,7 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs)
Tuple reflectV = r.direction.reflect(normalV);
/* If the hit object is not transparent, there is no need to do that. I think .*/
if ((xs != nullptr) && (xs->hit().object->material.transparency > 0))
if ((xs != nullptr) && (xs->hit().object->getMaterial()->transparency > 0))
{
List containers;
int j;
@@ -54,7 +80,7 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs)
{
if (!containers.isEmpty())
{
n1 = containers.last()->material.refractiveIndex;
n1 = containers.last()->getMaterial()->refractiveIndex;
}
}
@@ -71,7 +97,7 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs)
{
if (!containers.isEmpty())
{
n2 = containers.last()->material.refractiveIndex;
n2 = containers.last()->getMaterial()->refractiveIndex;
}
/* End the loop */
@@ -80,10 +106,7 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs)
}
}
Shape *s = this->object;
/* For now don't get root group material */
while((!s->materialSet) && (s->parent != nullptr)) { s = s->parent; }
Material *m = this->object->getMaterial();
return Computation(this->object,
this->t,
@@ -96,5 +119,5 @@ Computation Intersection::prepareComputation(Ray r, Intersect *xs)
n1,
n2,
underHitP,
&s->material);
}
m);
}

View File

@@ -68,4 +68,9 @@ double max3(double a, double b, double c)
double frand()
{
return rand() / ((double) RAND_MAX);
}
double frandclip(double min, double max)
{
return (frand() * (max - min)) + min;
}

View File

@@ -101,15 +101,18 @@ Matrix Matrix::operator*(const Matrix &b) const
return ret;
}
//#define FastGet4(_x, _y) (this->data[4 * (_x) + (_y)])
/* TODO: Check if we can optimise this function. It is called a lot */
/*
Tuple Matrix::operator*(const Tuple &b) const
{
return Tuple(b.x * this->get(0, 0) + b.y * this->get(0, 1) + b.z * this->get(0, 2) + b.w * this->get(0, 3),
b.x * this->get(1, 0) + b.y * this->get(1, 1) + b.z * this->get(1, 2) + b.w * this->get(1, 3),
b.x * this->get(2, 0) + b.y * this->get(2, 1) + b.z * this->get(2, 2) + b.w * this->get(2, 3),
b.x * this->get(3, 0) + b.y * this->get(3, 1) + b.z * this->get(3, 2) + b.w * this->get(3, 3));
return Tuple(b.x * FastGet4(0, 0) + b.y * FastGet4(0, 1) + b.z * FastGet4(0, 2) + b.w * FastGet4(0, 3),
b.x * FastGet4(1, 0) + b.y * FastGet4(1, 1) + b.z * FastGet4(1, 2) + b.w * FastGet4(1, 3),
b.x * FastGet4(2, 0) + b.y * FastGet4(2, 1) + b.z * FastGet4(2, 2) + b.w * FastGet4(2, 3),
b.x * FastGet4(3, 0) + b.y * FastGet4(3, 1) + b.z * FastGet4(3, 2) + b.w * FastGet4(3, 3));
}
*/
Matrix Matrix::identity()
{
int i;

View File

@@ -85,20 +85,32 @@ void RenderStats::addRay()
this->rayCount++;
};
void RenderStats::addCastedRay()
{
#pragma omp atomic
this->rayCasted++;
};
void RenderStats::addLightRay()
{
this->addCastedRay();
#pragma omp atomic
this->lightRayEmitedCount++;
};
void RenderStats::addReflectRay()
{
this->addCastedRay();
#pragma omp atomic
this->reflectionRayCount++;
};
void RenderStats::addRefractRay()
{
this->addCastedRay();
#pragma omp atomic
this->refractedRayCount++;
};
@@ -172,7 +184,8 @@ void RenderStats::printStats()
printf("CSG : %lld\n", this->csgCount);
printf("==================================================\n");
printf("Pixel rendered : %lld\n", this->pixelCount);
printf("Ray casted : %lld\n", this->rayCount);
printf("Ray created : %lld\n", this->rayCount);
printf("Ray casted : %lld\n", this->rayCasted);
printf("Light Ray casted : %lld\n", this->lightRayEmitedCount);
printf("Reflection ray casted : %lld\n", this->reflectionRayCount);
printf("Refraction ray casted : %lld\n", this->refractedRayCount);
@@ -181,7 +194,7 @@ void RenderStats::printStats()
printf("Malloc called : %lld\n", this->mallocCallCount);
printf("Realloc called : %lld\n", this->reallocCallCount);
printf("Bounding box missed : %lld\n", this->discardedIntersectCount);
printf("Min depth atteined : %lld\n", this->maxDepthAttained);
printf("Min depth attained : %lld\n", this->maxDepthAttained);
printf("Max intersect count : %lld\n", this->maxIntersectOnARay);
printf("==================================================\n");
};

View File

@@ -51,10 +51,8 @@ void Cone::intersectCaps(Ray r, Intersect &xs)
}
}
Intersect Cone::localIntersect(Ray r)
void Cone::localIntersect(Ray r, Intersect &xs)
{
Intersect ret;
double A = (r.direction.x * r.direction.x) -
(r.direction.y * r.direction.y) +
(r.direction.z * r.direction.z);
@@ -70,7 +68,7 @@ Intersect Cone::localIntersect(Ray r)
if ((fabs(A) <= getEpsilon()) && (fabs(B) >= getEpsilon()))
{
double t = -C / (2*B);
ret.add(Intersection(t, this));
xs.add(Intersection(t, this));
}
else if (fabs(A) >= getEpsilon())
{
@@ -83,20 +81,18 @@ Intersect Cone::localIntersect(Ray r)
double y0 = r.origin.y + t0 * r.direction.y;
if ((this->minCap < y0) && (y0 < this->maxCap))
{
ret.add(Intersection(t0, this));
xs.add(Intersection(t0, this));
}
double y1 = r.origin.y + t1 * r.direction.y;
if ((this->minCap < y1) && (y1 < this->maxCap))
{
ret.add(Intersection(t1, this));
xs.add(Intersection(t1, this));
}
}
}
this->intersectCaps(r, ret);
return ret;
this->intersectCaps(r, xs);
}
Tuple Cone::localNormalAt(Tuple point, Intersection *hit)

View File

@@ -13,36 +13,35 @@
#include <math_helper.h>
CSG::CSG(OperationType operation, Shape *left, Shape *right) : Shape(SHAPE_CSG), operation(operation), left(left), right(right)
CSG::CSG(OperationType operation, Shape *left, Shape *right) : Shape(Shape::CSG), operation(operation), left(left), right(right)
{
stats.addCsg();
this->left->parent = this;
this->right->parent = this;
this->left->setParent(this);
this->right->setParent(this);
this->bounds | this->left->getBounds();
this->bounds | this->right->getBounds();
}
Intersect CSG::localIntersect(Ray r)
void CSG::localIntersect(Ray r, Intersect &xs)
{
int i;
Intersect leftxs = this->left->intersect(r);
Intersect rightxs = this->right->intersect(r);
for(i = 0; i < rightxs.count(); i++)
{
leftxs.add(rightxs[i]);
}
Intersect ret = this->filterIntersections(leftxs);
return ret;
this->intersect(r, xs);
}
Intersect CSG::intersect(Ray r)
void CSG::intersect(Ray &r, Intersect &xs)
{
return localIntersect(r);
int i;
Intersect tmp = Intersect();
if (this->bounds.intesectMe(r))
{
this->left->intersect(r, tmp);
this->right->intersect(r, tmp);
this->filterIntersections(tmp, xs);
}
}
Tuple CSG::localNormalAt(Tuple point, Intersection *hit)
@@ -101,13 +100,11 @@ bool CSG::intersectionAllowed(bool leftHit, bool inLeft, bool inRight)
return false;
}
Intersect CSG::filterIntersections(Intersect &xs)
void CSG::filterIntersections(Intersect &xs, Intersect &ret)
{
bool inl = false;
bool inr = false;
Intersect ret = Intersect();
int i;
for(i = 0; i < xs.count(); i++)
@@ -128,8 +125,19 @@ Intersect CSG::filterIntersections(Intersect &xs)
inr = !inr;
}
}
}
return ret;
void CSG::lock()
{
Shape::lock();
if(this->left)
{
this->left->lock();
}
if(this->right)
{
this->right->lock();
}
}
void CSG::dumpMe(FILE *fp)

View File

@@ -36,10 +36,8 @@ void Cube::checkAxis(double axeOrigin, double axeDirection, double *axeMin, doub
}
}
Intersect Cube::localIntersect(Ray r)
void Cube::localIntersect(Ray r, Intersect &xs)
{
Intersect ret;
double xtMin, xtMax, ytMin, ytMax, ztMin, ztMax;
double tMin, tMax;
@@ -52,11 +50,9 @@ Intersect Cube::localIntersect(Ray r)
if (tMin <= tMax)
{
ret.add(Intersection(tMin, this));
ret.add(Intersection(tMax, this));
xs.add(Intersection(tMin, this));
xs.add(Intersection(tMax, this));
}
return ret;
}
Tuple Cube::localNormalAt(Tuple point, Intersection *hit)

View File

@@ -25,7 +25,7 @@ bool Cylinder::checkCap(Ray r, double t)
void Cylinder::intersectCaps(Ray r, Intersect &xs)
{
/* Caps only mattter is the cylinder is closed, and might possibly be
/* Caps only matter if the cylinder is closed, and might possibly be
* intersected by the ray
*/
if ((this->isClosed) && (fabs(r.direction.y) > getEpsilon()))
@@ -51,10 +51,8 @@ void Cylinder::intersectCaps(Ray r, Intersect &xs)
}
}
Intersect Cylinder::localIntersect(Ray r)
void Cylinder::localIntersect(Ray r, Intersect &xs)
{
Intersect ret;
double A = r.direction.x * r.direction.x + r.direction.z * r.direction.z;
/* Ray is parallel to the Y axis */
@@ -74,20 +72,18 @@ Intersect Cylinder::localIntersect(Ray r)
double y0 = r.origin.y + t0 * r.direction.y;
if ((this->minCap < y0) && (y0 < this->maxCap))
{
ret.add(Intersection(t0, this));
xs.add(Intersection(t0, this));
}
double y1 = r.origin.y + t1 * r.direction.y;
if ((this->minCap < y1) && (y1 < this->maxCap))
{
ret.add(Intersection(t1, this));
xs.add(Intersection(t1, this));
}
}
}
this->intersectCaps(r, ret);
return ret;
this->intersectCaps(r, xs);
}
Tuple Cylinder::localNormalAt(Tuple point, Intersection *hit)

View File

@@ -11,10 +11,11 @@
#include <group.h>
#include <math_helper.h>
#include <renderstat.h>
#include <string.h>
#define MIN_ALLOC (2)
Group::Group() : Shape(SHAPE_GROUP)
Group::Group(const char *name) : Shape(Shape::GROUP)
{
stats.addGroup();
this->allocatedObjectCount = MIN_ALLOC;
@@ -25,11 +26,18 @@ Group::Group() : Shape(SHAPE_GROUP)
this->unboxableObjectList = (Shape **)calloc(sizeof(Shape **), MIN_ALLOC);
this->unboxableObjectCount = 0;
if (name != nullptr)
{
strncpy(this->name, name, 32);
}
else
{
strncpy(this->name, "untitled", 32);
}
}
Intersect Group::intersect(Ray r)
void Group::intersect(Ray &r, Intersect &xs)
{
Intersect ret;
int i, j;
if (this->objectCount > 0)
{
@@ -37,14 +45,7 @@ Intersect Group::intersect(Ray r)
{
for (i = 0 ; i < this->objectCount ; i++)
{
Intersect xs = this->objectList[i]->intersect(r);
if (xs.count() > 0)
{
for (j = 0 ; j < xs.count() ; j++)
{
ret.add(xs[j]);
}
}
this->objectList[i]->intersect(r, xs);
}
}
}
@@ -54,17 +55,9 @@ Intersect Group::intersect(Ray r)
{
for(i = 0; i < this->unboxableObjectCount; i++)
{
Intersect xs = this->unboxableObjectList[i]->intersect(r);
if (xs.count() > 0)
{
for(j = 0; j < xs.count(); j++)
{
ret.add(xs[j]);
}
}
this->unboxableObjectList[i]->intersect(r, xs);
}
}
return ret;
}
bool Group::includes(Shape *b)
@@ -81,7 +74,6 @@ bool Group::includes(Shape *b)
}
}
/* We are force to do them all the time */
if (this->unboxableObjectCount > 0)
{
int i;
@@ -96,9 +88,9 @@ bool Group::includes(Shape *b)
return false;
}
Intersect Group::localIntersect(Ray r)
void Group::localIntersect(Ray r, Intersect &xs)
{
return Intersect();
this->intersect(r, xs);
}
Tuple Group::localNormalAt(Tuple point, Intersection *hit)
@@ -117,7 +109,7 @@ void Group::addObject(Shape *s)
this->objectList = (Shape **)realloc(this->objectList, sizeof(Shape **) * this->allocatedObjectCount);
}
s->parent = this;
s->setParent(this);
s->updateTransform();
this->objectList[this->objectCount++] = s;
@@ -133,13 +125,44 @@ void Group::addObject(Shape *s)
this->unboxableObjectList = (Shape **)realloc(this->unboxableObjectList, sizeof(Shape **) * this->allocatedUnboxableObjectCount);
}
s->parent = this;
s->setParent(this);
s->updateTransform();
this->unboxableObjectList[this->unboxableObjectCount++] = s;
}
}
void Group::removeObject(Shape *s)
{
int i;
if (s->haveFiniteBounds())
{
for (i = 0; i < this->objectCount; i++)
{
if (this->objectList[i] == s)
{
this->objectCount --;
this->objectList[i] = this->objectList[this->objectCount];
this->objectList[this->objectCount] = nullptr;
break;
}
}
}
else
{
for (i = 0; i < this->unboxableObjectCount; i++)
{
if (this->unboxableObjectList[i] == s)
{
this->unboxableObjectCount --;
this->unboxableObjectList[i] = this->unboxableObjectList[this->unboxableObjectCount];
this->unboxableObjectList[this->unboxableObjectCount] = nullptr;
break;
}
}
}
}
bool Group::isEmpty()
{
return (this->objectCount == 0) && (this->unboxableObjectCount == 0);
@@ -204,6 +227,7 @@ void Group::dumpMe(FILE *fp)
{
int i;
fprintf(fp, "\"Type\": \"Group\",\n");
fprintf(fp, "\"Name\": \"%s\",\n", this->name);
if (this->objectCount > 0)
{
fprintf(fp, "\"Objects\": {\n");
@@ -228,4 +252,28 @@ void Group::dumpMe(FILE *fp)
fprintf(fp, "},\n");
}
Shape::dumpMe(fp);
}
void Group::lock()
{
Shape::lock();
/* Now notify included object they have to lock */
int i;
if (this->objectCount > 0)
{
for (i = 0 ; i < this->objectCount ; i++)
{
this->objectList[i]->lock();
}
}
if (this->unboxableObjectCount > 0)
{
for(i = 0; i < this->unboxableObjectCount; i++)
{
this->unboxableObjectList[i]->lock();
}
}
}

View File

@@ -17,32 +17,64 @@
#include <math_helper.h>
#include <group.h>
#include <triangle.h>
#include <sphere.h>
#include <smoothtriangle.h>
#include <transformation.h>
#include <cone.h>
#include <cylinder.h>
#define MIN_ALLOC (2)
#define DEFAULT_GROUP (0)
OBJFile::OBJFile() : Shape(SHAPE_OBJFILE), ignoredLines(0)
//#define DEBUG_NORMAL
OBJFile::OBJFile() : Shape(Shape::OBJFILE), ignoredLines(0)
{
stats.addOBJFile();
this->allocatedFaceGroupCount = MIN_ALLOC;
this->faceGroupList = (Group **)calloc(sizeof(Group **), MIN_ALLOC);
this->faceGroupCount = 0;
this->allocatedVertexCount = MIN_ALLOC;
this->vertexList = (Point **)calloc(sizeof(Point **), MIN_ALLOC);
this->vertexCount = 0;
this->allocatedVertexNormalCount = MIN_ALLOC;
this->vertexNormalList = (Vector **)calloc(sizeof(Vector **), MIN_ALLOC);
this->vertexNormalCount = 0;
/* There is always a default group */
this->addGroup(new Group());
/* The base group */
this->baseGroup = new Group(OBJ_DEFAULT_GROUP);
this->currentGroup = this->baseGroup;
this->baseGroup->parent = this;
};
OBJFile::~OBJFile()
{
int i;
if (vertexCount > 0)
{
for(i = 0; i < vertexCount; i++)
{
delete this->vertexList[i];
this->vertexList[i] = nullptr;
}
}
free(this->vertexList);
this->vertexList = nullptr;
if (vertexNormalCount > 0)
{
for(i = 0; i < vertexNormalCount; i++)
{
delete this->vertexNormalList[i];
this->vertexNormalList[i] = nullptr;
}
}
free(this->vertexNormalList);
this->vertexNormalList = nullptr;
/* It is not our responsibility to clear the group object as this object may be destroyed before the
* render is done
*/
}
OBJFile::OBJFile(const char *filepath) : OBJFile()
{
FILE *fp;
@@ -53,7 +85,8 @@ OBJFile::OBJFile(const char *filepath) : OBJFile()
{
fseek(fp, 0, SEEK_END);
fileSize = ftell(fp);
fileBuff = (char *)calloc(fileSize, 1);
/* Add one byte to the size to make sure it is null terminated */
fileBuff = (char *)calloc(fileSize + 1, 1);
fseek(fp, 0, SEEK_SET);
fileSize = fread(fileBuff, 1, fileSize, fp);
fclose(fp);
@@ -70,18 +103,13 @@ OBJFile::OBJFile(const char *filepath) : OBJFile()
void OBJFile::addGroup(Group *group)
{
if ((this->faceGroupCount + 1) > this->allocatedFaceGroupCount)
{
this->allocatedFaceGroupCount *= 2;
this->faceGroupList = (Group **)realloc(this->faceGroupList, sizeof(Group **) * this->allocatedFaceGroupCount);
}
this->baseGroup->addObject(group);
group->parent = this;
group->setParent(this);
group->updateTransform();
this->faceGroupList[this->faceGroupCount++] = group;
this->bounds | group->getBounds();
this->currentGroup = group;
}
void OBJFile::addVertex(Point *vertex)
@@ -106,49 +134,45 @@ void OBJFile::addVertexNormal(Vector *vertexNormal)
this->vertexNormalList[this->vertexNormalCount++] = vertexNormal;
}
Intersect OBJFile::intersect(Ray r)
Group *OBJFile::groups(const char *groupName)
{
Intersect ret;
int i, j;
if (this->faceGroupCount > 0)
if (strncmp(groupName, this->baseGroup->getName(), strlen(groupName)) == 0)
{
if (this->bounds.intesectMe(r))
return this->baseGroup;
}
int i;
for(i = 0; i < this->baseGroup->getObjectCount(); i++)
{
Shape *cur = (*this->baseGroup)[i];
if (cur->getType() == Shape::GROUP)
{
for (i = 0 ; i < this->faceGroupCount ; i++)
Group *curGrp = (Group *)cur;
if (strncmp(groupName, curGrp->getName(), strlen(groupName)) == 0)
{
Intersect xs = this->faceGroupList[i]->intersect(r);
if (xs.count() > 0)
{
for (j = 0 ; j < xs.count() ; j++)
{
ret.add(xs[j]);
}
}
return curGrp;
}
}
}
return ret;
/* Not found */
return nullptr;
}
void OBJFile::intersect(Ray &r, Intersect &xs)
{
this->baseGroup->intersect(r, xs);
}
bool OBJFile::includes(Shape *b)
{
int i;
if (this->faceGroupCount > 0)
{
for (i = 0 ; i < this->faceGroupCount ; i++)
{
if (this->faceGroupList[i] == b)
{
return true;
}
}
}
return false;
return this->baseGroup->includes(b);
}
Intersect OBJFile::localIntersect(Ray r)
void OBJFile::localIntersect(Ray r, Intersect &xs)
{
return Intersect();
this->intersect(r, xs);
}
Tuple OBJFile::localNormalAt(Tuple point, Intersection *hit)
@@ -172,14 +196,7 @@ void OBJFile::updateBoundingBox()
int i;
this->bounds.reset();
for(i = 0; i < this->faceGroupCount; i++)
{
if (this->faceGroupList[i]->haveFiniteBounds())
{
BoundingBox objB = this->faceGroupList[i]->getBounds();
this->bounds | objB;
}
}
this->bounds | this->baseGroup->getBounds();
}
void OBJFile::updateTransform()
@@ -187,10 +204,8 @@ void OBJFile::updateTransform()
int i;
Shape::updateTransform();
for (i = 0 ; i < this->faceGroupCount ; i++)
{
this->faceGroupList[i]->updateTransform();
}
this->baseGroup->updateTransform();
/* Once the full stack being notified of the changes, let's update the
* bounding box
@@ -203,14 +218,22 @@ void OBJFile::dumpMe(FILE * fp)
int i;
fprintf(fp, "\"Type\": \"OBJFile\",\n");
fprintf(fp, "\"Objects\": {\n");
for(i = 0; i < this->faceGroupCount; i++)
this->baseGroup->dumpMe(fp);
fprintf(fp, "},\n");
fprintf(fp, "\"Vertices\": {\n");
for(i = 1; i < this->vertexCount + 1; i++)
{
fprintf(fp, "\"%d\": {\n", i);
this->faceGroupList[i]->dumpMe(fp);
fprintf(fp, "},\n");
fprintf(fp, "\"v[%d]\": { \"x\": %f, \"y\": %f, \"z\": %f },\n", i,
this->vertices(i).x, this->vertices(i).y, this->vertices(i).z);
}
fprintf(fp, "},\n");
fprintf(fp, "\"NormalVertices\": {\n");
for(i = 1; i < this->vertexNormalCount + 1; i++)
{
fprintf(fp, "\"vn[%d]\": { \"x\": %f, \"y\": %f, \"z\": %f },\n", i,
this->verticesNormal(i).x, this->verticesNormal(i).y, this->verticesNormal(i).z);
}
fprintf(fp, "},\n");
Shape::dumpMe(fp);
}
@@ -221,7 +244,7 @@ int OBJFile::parseOBJFile(const char *content)
/* I don't think we will handle lines of more than 512 characters... */
char lineBuff[MAX_LINE_LENGTH];
uint32_t currentLineNum = 1;
uint32_t totalLength = strlen(content);
/* Need to process line by line */
const char *bufferPos = content;
const char *lineNewline;
@@ -249,6 +272,12 @@ int OBJFile::parseOBJFile(const char *content)
this->parseLine(lineBuff, currentLineNum);
bufferPos += lineLength + 1;
if ((bufferPos - content) >= totalLength)
{
/* We are past the length of the buffer, don't need to continue */
break;
}
currentLineNum++;
}
return 0;
@@ -334,6 +363,58 @@ static int parseFaceVertex(char *buf, uint32_t &v, uint32_t &vt, uint32_t &vn)
return ret;
}
#ifdef DEBUG_NORMAL
Shape *makeVector(Point pos, Vector verNorm, Colour c, double scale = 1)
{
Group *ret = new Group("Vector");
Sphere *sp = new Sphere();
Colour c2 = c;
sp->material.colour = c2;
sp->material.ambient = 1;
sp->material.refractiveIndex = 0;
sp->material.reflective = 0;
sp->material.specular = 0;
sp->materialSet = true;
sp->setTransform(translation(pos.x, pos.y, pos.z) * scaling(0.1, 0.1, 0.1));
ret->addObject(sp);
double theta = atan2(verNorm.x, verNorm.z);
double radius = verNorm.magnitude();
double phi = acos(verNorm.y / radius);
sp = new Sphere();
c2 = c;
c2.x /=3; c2.y /=3; c2.y /=3;
sp->material.colour = c2;
sp->material.ambient = 1;
sp->material.refractiveIndex = 0;
sp->material.transparency = 0;
sp->material.specular = 0;
sp->materialSet = true;
sp->setTransform(translation(pos.x, pos.y, pos.z) * translation(verNorm.x, verNorm.y, verNorm.z) * scaling(0.1, 0.1, 0.1));
ret->addObject(sp);
c2 = c;
c2.x /=2; c2.y /=3; c2.y /=2;
Cone *cn = new Cone();
cn->minCap = 0;
cn->maxCap = 1;
cn->material.colour = c2;
cn->material.ambient = 1;
cn->material.refractiveIndex = 0;
cn->material.reflective = 0;
cn->material.specular = 0;
cn->materialSet = true;
cn->setTransform(translation(pos.x, pos.y, pos.z) * rotationY(theta) * rotationX(phi) * scaling(0.1, radius, 0.1));
ret->addObject(cn);
return ret;
}
#endif
/* Actually execute the line */
int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine)
{
@@ -385,6 +466,17 @@ int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine)
}
else
{
#ifdef DEBUG_NORMAL
this->currentGroup->addObject(makeVector(this->vertices(v[1]),
this->verticesNormal(vn[1]),
Colour(1, 0, 1)));
this->currentGroup->addObject(makeVector(this->vertices(v[2]),
this->verticesNormal(vn[2]),
Colour(0.5, 0, 0.5)));
this->currentGroup->addObject(makeVector(this->vertices(v[3]),
this->verticesNormal(vn[3]),
Colour(0.5, 0, 1)));
#endif
t = new SmoothTriangle(this->vertices(v[1]),
this->vertices(v[2]),
this->vertices(v[3]),
@@ -392,11 +484,29 @@ int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine)
this->verticesNormal(vn[2]),
this->verticesNormal(vn[3]));
}
this->faceGroupList[this->faceGroupCount - 1]->addObject(t);
/* Set the object id to the OBJ one */
t->setObjectId(this->getObjectId());
this->currentGroup->addObject(t);
ret = 0;
}
else if (argc > 4)
{
#ifdef DEBUG_NORMAL
if (vn[1] != INT32_MAX)
{
for(i = 2; i < (argc); i++)
{
this->currentGroup->addObject(makeVector(this->vertices(v[i]),
this->verticesNormal(vn[i]),
Colour(1, 1, 0)));
}
this->currentGroup->addObject(makeVector(this->vertices(v[1]),
this->verticesNormal(vn[1]),
Colour(0, 1, 0)));
}
#endif
for(i = 2; i < (argc - 1); i++)
{
@@ -416,7 +526,8 @@ int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine)
this->verticesNormal(vn[i]),
this->verticesNormal(vn[i + 1]));
}
this->faceGroupList[this->faceGroupCount - 1]->addObject(t);
t->setObjectId(this->getObjectId());
this->currentGroup->addObject(t);
}
ret = 0;
}
@@ -429,7 +540,7 @@ int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine)
{
if (argc == 2)
{
this->addGroup(new Group());
this->addGroup(new Group(argv[1]));
}
else
{
@@ -437,4 +548,11 @@ int OBJFile::execLine(int argc, char *argv[], uint32_t currentLine)
}
}
return ret;
}
void OBJFile::lock()
{
Shape::lock();
this->baseGroup->lock();
}

View File

@@ -12,22 +12,20 @@
#include <plane.h>
#include <math_helper.h>
Intersect Plane::localIntersect(Ray r)
void Plane::localIntersect(Ray r, Intersect &xs)
{
double t;
Intersect ret = Intersect();
if (fabs(r.direction.y) < getEpsilon())
{
/* With a direction == 0, the ray can't intersect the plane */
return ret;
}
else
{
t = -r.origin.y / r.direction.y;
t = -r.origin.y / r.direction.y;
ret.add(Intersection(t, this));
return ret;
xs.add(Intersection(t, this));
}
}
Tuple Plane::localNormalAt(Tuple point, Intersection *hit)

View File

@@ -15,18 +15,26 @@
Shape::Shape(ShapeType type)
{
this->objectId = Shape::newObjectId();
this->locked = false;
this->parent = nullptr;
this->dropShadow = true;
this->type = type;
this->localTransformMatrix = Matrix4().identity();
this->updateTransform();
this->materialSet = false;
this->updateTransform();
}
Intersect Shape::intersect(Ray r)
uint64_t Shape::newObjectId()
{
return this->localIntersect(this->invTransform(r));
};
static uint64_t id = 0;
uint64_t ret;
ret = id++;
return ret;
}
Tuple Shape::normalToWorld(Tuple normalVector)
{
@@ -51,6 +59,8 @@ Tuple Shape::normalAt(Tuple point, Intersection *hit)
void Shape::updateTransform()
{
if (this->locked) return;
this->transformMatrix = this->localTransformMatrix;
if (this->parent != nullptr)
{
@@ -63,6 +73,8 @@ void Shape::updateTransform()
void Shape::setTransform(Matrix transform)
{
if (this->locked) return;
this->localTransformMatrix = transform;
this->updateTransform();
}
@@ -89,13 +101,36 @@ BoundingBox Shape::getBounds()
return ret;
}
Material *Shape::getMaterial()
{
Shape *s = this;
while((!s->materialSet) && (s->parent != nullptr))
{
s = s->parent;
}
return &s->material;
}
void Shape::dumpMe(FILE *fp)
{
fprintf(fp, "\"Material\": {\n");
this->material.dumpMe(fp);
fprintf(fp, "},\n");
if (this->materialSet)
{
fprintf(fp, "\"Material\": {\n");
this->material.dumpMe(fp);
fprintf(fp, "},\n");
}
fprintf(fp, "\"DropShadow\": %d,\n", this->dropShadow);
fprintf(fp, "\"BoundingBox\": {\n");
this->getBounds().dumpMe(fp);
fprintf(fp, "},\n");
fprintf(fp, "\"Locked\": %d,\n", this->locked);
fprintf(fp, "\"MaterialSet\": %d,\n", this->materialSet);
if (this->haveFiniteBounds())
{
fprintf(fp, "\"BoundingBox\": {\n");
this->getBounds().dumpMe(fp);
fprintf(fp, "},\n");
}
fprintf(fp, "\"id\": %ld,\n", this->getObjectId());
if (this->parent)
{
fprintf(fp, "\"parentId\": %ld,\n", this->parent->getObjectId());
}
}

View File

@@ -16,7 +16,7 @@
SmoothTriangle::SmoothTriangle(Point p1, Point p2, Point p3, Vector n1, Vector n2, Vector n3) : Triangle(p1, p2, p3),
n1(n1), n2(n2), n3(n3)
{
this->type = SHAPE_SMOOTHTRIANGLE;
this->type = Shape::SMOOTHTRIANGLE;
stats.addSmoothTriangle();
}
@@ -25,4 +25,18 @@ Tuple SmoothTriangle::localNormalAt(Tuple point, Intersection *hit)
return (this->n2 * hit->u +
this->n3 * hit->v +
this->n1 * (1 - hit->u - hit->v)).normalise();
}
void SmoothTriangle::dumpMe(FILE *fp)
{
Tuple t = this->n1;
fprintf(fp, "\"n1\": { \"x\": %f, \"y\": %f, \"z\": %f}, \n",
t.x, t.y, t.z);
t = this->n2;
fprintf(fp, "\"n2\": { \"x\": %f, \"y\": %f, \"z\": %f}, \n",
t.x, t.y, t.z);
t = this->n3;
fprintf(fp, "\"n3\": { \"x\": %f, \"y\": %f, \"z\": %f}, \n",
t.x, t.y, t.z);
Triangle::dumpMe(fp);
}

View File

@@ -13,9 +13,8 @@
#include <tuple.h>
#include <intersect.h>
Intersect Sphere::localIntersect(Ray r)
void Sphere::localIntersect(Ray r, Intersect &xs)
{
Intersect ret;
double a, b, c, discriminant;
Tuple sphere_to_ray = r.origin - Point(0, 0, 0);
@@ -28,11 +27,9 @@ Intersect Sphere::localIntersect(Ray r)
if (discriminant >= 0)
{
ret.add(Intersection((-b - sqrt(discriminant)) / (2 * a), this));
ret.add(Intersection((-b + sqrt(discriminant)) / (2 * a), this));
xs.add(Intersection((-b - sqrt(discriminant)) / (2 * a), this));
xs.add(Intersection((-b + sqrt(discriminant)) / (2 * a), this));
}
return ret;
}
Tuple Sphere::localNormalAt(Tuple point, Intersection *hit)

View File

@@ -13,10 +13,9 @@ TestShape::TestShape() : localRay(Point(0, 0, 0), Vector(0, 0, 0))
{
}
Intersect TestShape::localIntersect(Ray r)
void TestShape::localIntersect(Ray r, Intersect &xs)
{
this->localRay = r;
return Intersect();
}
Tuple TestShape::localNormalAt(Tuple point, Intersection *hit)

View File

@@ -12,7 +12,7 @@
#include <math_helper.h>
#include <renderstat.h>
Triangle::Triangle(Point p1, Point p2, Point p3) : Shape(SHAPE_TRIANGLE), p1(p1), p2(p2), p3(p3)
Triangle::Triangle(Point p1, Point p2, Point p3) : Shape(Shape::TRIANGLE), p1(p1), p2(p2), p3(p3)
{
stats.addTriangle();
@@ -21,15 +21,13 @@ Triangle::Triangle(Point p1, Point p2, Point p3) : Shape(SHAPE_TRIANGLE), p1(p1)
this->normal = e2.cross(e1).normalise();
}
Intersect Triangle::localIntersect(Ray r)
void Triangle::localIntersect(Ray r, Intersect &xs)
{
Intersect ret;
Tuple dirCrossE2 = r.direction.cross(this->e2);
double determinant = this->e1.dot(dirCrossE2);
if (fabs(determinant) < getEpsilon())
{
return ret;
return;
}
double f = 1.0 / determinant;
@@ -40,18 +38,16 @@ Intersect Triangle::localIntersect(Ray r)
if ((u < 0) || (u > 1))
{
return ret;
return;
}
if ((v < 0) || ((u + v) > 1))
{
return ret;
return;
}
double t = f * this->e2.dot(originCrossE1);
ret.add(Intersection(t, this, u, v));
return ret;
xs.add(Intersection(t, this, u, v));
}
Tuple Triangle::localNormalAt(Tuple point, Intersection *hit)
@@ -73,6 +69,8 @@ BoundingBox Triangle::getLocalBounds()
void Triangle::dumpMe(FILE *fp)
{
fprintf(fp, "\"Type\": \"Triangle\",\n");
/* World points*/
Tuple t = this->transformMatrix * this->p1;
fprintf(fp, "\"p1\": { \"x\": %f, \"y\": %f, \"z\": %f}, \n",
t.x, t.y, t.z);
@@ -82,5 +80,16 @@ void Triangle::dumpMe(FILE *fp)
t = this->transformMatrix * this->p3;
fprintf(fp, "\"p3\": { \"x\": %f, \"y\": %f, \"z\": %f}, \n",
t.x, t.y, t.z);
/* Local points */
t = this->p1;
fprintf(fp, "\"lp1\": { \"x\": %f, \"y\": %f, \"z\": %f}, \n",
t.x, t.y, t.z);
t = this->p2;
fprintf(fp, "\"lp2\": { \"x\": %f, \"y\": %f, \"z\": %f}, \n",
t.x, t.y, t.z);
t = this->p3;
fprintf(fp, "\"lp3\": { \"x\": %f, \"y\": %f, \"z\": %f}, \n",
t.x, t.y, t.z);
Shape::dumpMe(fp);
}

View File

@@ -26,7 +26,7 @@ Tuple Tuple::normalise()
return Tuple(this->x / mag, this->y / mag, this->z / mag, this->w / mag);
}
/*
double Tuple::dot(const Tuple &b)
{
return this->x * b.x + this->y * b.y + this->z * b.z + this->w * b.w;
@@ -39,30 +39,12 @@ Tuple Tuple::cross(const Tuple &b) const
this->x * b.y - this->y * b.x,
0);
}
*/
Tuple Tuple::reflect(const Tuple &normal)
{
return *this - normal * 2 * this->dot(normal);
}
void Tuple::fixPoint()
{
if (isnan(this->x) || isnan(this->y) || isnan(this->z))
{
/* w is probably broken, so fix it */
this->w = 1;
}
}
void Tuple::fixVector()
{
if (isnan(this->x) || isnan(this->y) || isnan(this->z))
{
/* w is probably broken, so fix it */
this->w = 0;
}
}
bool Tuple::isRepresentable()
{
return !(isnan(this->x) || isnan(this->y) || isnan(this->z) ||

View File

@@ -22,16 +22,12 @@ extern "C" {
#define MIN_ALLOC (2)
World::World() : objectCount(0), lightCount(0)
World::World() : lightCount(0), worldGroup("World")
{
this->allocatedLightCount = MIN_ALLOC;
this->lightList = (Light **)calloc(sizeof(Light *), MIN_ALLOC);
this->lightCount = 0;
this->allocatedObjectCount = MIN_ALLOC;
this->objectList = (Shape **)calloc(sizeof(Shape *), MIN_ALLOC);
this->objectCount = 0;
#ifdef ENABLE_LUA_SUPPORT
this->L = luaL_newstate(); /* opens Lua */
luaL_openlibs(L); /* opens the basic library */
@@ -45,12 +41,10 @@ World::~World()
void World::addObject(Shape *s)
{
if ((this->objectCount + 1) > this->allocatedObjectCount)
{
this->allocatedObjectCount *= 2;
this->objectList = (Shape **)realloc(this->objectList, sizeof(Shape **) * this->allocatedObjectCount);
}
this->objectList[this->objectCount++] = s;
/* Cheaty but need to be done for now */
s->materialSet = true;
this->worldGroup.addObject(s);
}
void World::addLight(Light *l)
@@ -78,33 +72,12 @@ bool World::lightIsIn(Light &l)
bool World::objectIsIn(Shape &s)
{
int i;
for(i = 0; i < this->objectCount; i++)
{
if (*this->objectList[i] == s)
{
return true;
}
}
return false;
return this->worldGroup.includes(&s);
}
Intersect World::intersect(Ray r)
void World::intersect(Ray &r, Intersect &xs)
{
Intersect ret;
int i, j;
for(i = 0; i < this->objectCount; i++)
{
Intersect xs = this->objectList[i]->intersect(r);
for(j = 0; j < xs.count(); j++)
{
ret.add(xs[j]);
}
}
return ret;
this->worldGroup.intersect(r, xs);
}
Tuple World::shadeHit(Computation comps, uint32_t depthCount)
@@ -135,7 +108,8 @@ Tuple World::shadeHit(Computation comps, uint32_t depthCount)
Tuple World::colourAt(Ray r, uint32_t depthCount)
{
Intersect allHits = this->intersect(r);
Intersect allHits;
this->intersect(r, allHits);
Intersection hit = allHits.hit();
stats.setMaxDepth(depthCount);
@@ -158,7 +132,8 @@ bool World::isShadowed(Tuple point, Tuple lightPosition)
Ray r = Ray(point, direction);
stats.addLightRay();
Intersect xs = this->intersect(r);
Intersect xs;
this->intersect(r, xs);
int i;
for(i = 0; i < xs.count(); i++)
@@ -214,6 +189,16 @@ Colour World::refractedColour(Computation comps, uint32_t depthCount)
return Colour(hitColour.x, hitColour.y, hitColour.z);
}
void World::finalise(WorldOptimiser &opt)
{
/* First lock everything */
this->worldGroup.lock();
/* Now run the optimiser */
opt.setRoot(&this->worldGroup);
opt.run();
}
void World::dumpMe(FILE *fp)
{
int i;
@@ -230,12 +215,7 @@ void World::dumpMe(FILE *fp)
fprintf(fp, "},\n");
fprintf(fp, "\"Objects\": {\n");
for(i = 0; i < this->objectCount; i++)
{
fprintf(fp, "\"%d\": {\n", i);
this->objectList[i]->dumpMe(fp);
fprintf(fp, "},\n");
}
this->worldGroup.dumpMe(fp);
fprintf(fp, "},\n");
/* JSON Closing */

124
source/worldoptimiser.cpp Normal file
View File

@@ -0,0 +1,124 @@
/*
* DoRayMe - a quick and dirty Raytracer
* WorldOptimiser implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <shape.h>
#include <group.h>
#include <objfile.h>
#include <world.h>
#include <worldoptimiser.h>
/* This function is meant to move all infinite object to the root group */
void WorldOptimiser::moveInfiniteObjects(Shape *s)
{
if (s == nullptr)
{
s = this->root;
}
if (s->getType() == Shape::OBJFILE)
{
/* Special case */
OBJFile *obj = (OBJFile *)s;
s = obj->getBaseGroup();
}
if (s->getType() == Shape::GROUP)
{
int i;
Group *grp = (Group *)s;
if (grp->getUnboxableCount() > 0)
{
for(i = 0; i < grp->getUnboxableCount(); i++)
{
Shape *shp = grp->getUnboxable(i);
if (this->root != s)
{
if (shp->getType() == Shape::GROUP)
{
/* Issue a warning if it is a group */
printf("WARNING: The group '%s' in '%s' have infinite bounds, all items part of it will not be optimised."
"That may affect performances!",
((Group *)shp)->getName(),
grp->getName());
}
this->root->addObject(shp);
grp->removeObject(shp);
/* We remove an object from that list, so need to do some stuffs. */
i -= 1;
}
}
}
/* Now let's traverse the rest of that group */
if (grp->getObjectCount() > 0)
{
for(i = 0; i < grp->getObjectCount(); i++)
{
Shape *shp = grp->getObject(i);
this->moveInfiniteObjects(shp);
}
}
}
/* If it is not a group, there is nothing to be done there. */
}
void WorldOptimiser::moveAllObjects(Shape *s)
{
if (s == nullptr)
{
s = this->root;
}
if (s->getType() == Shape::OBJFILE)
{
/* Special case */
OBJFile *obj = (OBJFile *)s;
s = obj->getBaseGroup();
}
/* We should be here only when it is a group, but better being safe. */
if (s->getType() == Shape::GROUP)
{
int i;
Group *grp = (Group *)s;
/* Now let's traverse the rest of that group */
if (grp->getObjectCount() > 0)
{
for(i = 0; i < grp->getObjectCount(); i++)
{
Shape *shp = grp->getObject(i);
switch(shp->getType())
{
default:
/* Don't move if we are on the same leaf */
if (this->root != s)
{
/* It is not a group type object so, move it! */
this->root->addObject(shp);
grp->removeObject(shp);
/* We remove an object from that list, so need to do some stuffs. */
i -= 1;
}
break;
case Shape::GROUP:
case Shape::OBJFILE:
this->moveAllObjects(shp);
}
}
}
}
/* If it is not a group, there is nothing to be done there. */
}

View File

@@ -0,0 +1,144 @@
/*
* DoRayMe - a quick and dirty Raytracer
* BVH world optimiser implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <worldoptimiser.h>
#include <cube.h>
#include <objfile.h>
#include <transformation.h>
void BVHOptimisation::makeTree(Group *leaf, int depth)
{
/* Let's take the bounding box of the root */
BoundingBox rootBB = leaf->getBounds();
double dx = (rootBB.max.x - rootBB.min.x);
double dy = (rootBB.max.y - rootBB.min.y);
double dz = (rootBB.max.z - rootBB.min.z);
/* Take the mid value for each axes */
Tuple midMin = rootBB.min;
Tuple midMax = rootBB.max;
BoundingBox SlicesBB[2];
int sliceIdx;
Group *Slices[2];
double largestSide = max3(dx, dy, dz);
int i;
if (largestSide == dx)
{
midMin.x = rootBB.min.x + dx / 2.0;
midMax.x = rootBB.min.x + dx / 2.0;
}
else if (largestSide == dy)
{
midMin.y = rootBB.min.y + dy / 2.0;
midMax.y = rootBB.min.y + dy / 2.0;
}
else
{
midMin.z = rootBB.min.z + dx / 2.0;
midMax.z = rootBB.min.z + dx / 2.0;
}
/* Split the main bounding box into 8 boxes */
SlicesBB[0] | rootBB.min;
SlicesBB[0] | midMax;
SlicesBB[1] | rootBB.max;
SlicesBB[1] | midMin;
for (sliceIdx = 0 ; sliceIdx < 2 ; sliceIdx++)
{
Slices[sliceIdx] = nullptr;
}
for (i = 0 ; i < leaf->getObjectCount(); i++)
{
Shape *shp = leaf->getObject(i);
BoundingBox objBB = shp->getBounds();
for (sliceIdx = 0 ; sliceIdx < 2 ; sliceIdx++)
{
if (SlicesBB[sliceIdx].fitsIn(objBB))
{
if (Slices[sliceIdx] == nullptr)
{
char name[32];
snprintf(name, 32, "%d_Slice %d", depth, sliceIdx);
Slices[sliceIdx] = new Group(name);
Slices[sliceIdx]->setBounds(SlicesBB[sliceIdx]);
}
Slices[sliceIdx]->addObject(shp);
leaf->removeObject(shp);
i -= 1;
break;
}
}
if (shp->getType() == Shape::GROUP)
{
this->makeTree((Group *)shp, depth + 1);
}
else if (shp->getType() == Shape::OBJFILE)
{
this->makeTree((Group *)((OBJFile *)shp)->getBaseGroup(), depth + 1);
}
}
/* Now add the quadrant to the root and recurse in it */
for (sliceIdx = 0 ; sliceIdx < 2 ; sliceIdx++)
{
if (Slices[sliceIdx] != nullptr)
{
this->makeTree(Slices[sliceIdx], depth + 1);
Slices[sliceIdx]->updateBoundingBox();
leaf->addObject(Slices[sliceIdx]);
#if 0
Cube *cb = new Cube();
double sx = SlicesBB[sliceIdx].max.x - SlicesBB[sliceIdx].min.x;
double sy = SlicesBB[sliceIdx].max.y - SlicesBB[sliceIdx].min.y;
double sz = SlicesBB[sliceIdx].max.z - SlicesBB[sliceIdx].min.z;
cb->setTransform(translation(SlicesBB[sliceIdx].min.x, SlicesBB[sliceIdx].min.y,
SlicesBB[sliceIdx].min.z) * scaling(sx, sy, sz));
cb->material.colour = Colour(0.01, 0.01, 0);
cb->materialSet = true;
cb->dropShadow = false;
cb->material.ambient = 0.1;
cb->material.reflective = 0;
cb->material.transparency = 0.95;
cb->material.refractiveIndex = 1;
cb->material.specular = 0;
leaf->addObject(cb);
printf("%s: %d objs\n", Slices[sliceIdx]->getName(),
Slices[sliceIdx]->getObjectCount());
#endif
}
}
}
void BVHOptimisation::run()
{
/* First let's clear our hands */
this->moveInfiniteObjects();
/* Then let's have some fun! */
//this->moveAllObjects();
/* Now.. The fun start ! */
makeTree(this->root, 0);
}

View File

@@ -0,0 +1,141 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Octree world optimiser implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <worldoptimiser.h>
#include <cube.h>
#include <objfile.h>
#include <transformation.h>
void OctreeOptimisation::makeTree(Group *leaf, int depth)
{
/* Let's take the bounding box of the root */
BoundingBox rootBB = leaf->getBounds();
/* Take the mid value for each axes */
double midX = (rootBB.max.x - rootBB.min.x) / 2.0 + rootBB.min.x;
double midY = (rootBB.max.y - rootBB.min.y) / 2.0 + rootBB.min.y;
double midZ = (rootBB.max.z - rootBB.min.z) / 2.0 + rootBB.min.z;
BoundingBox octantBB[8];
int octantIdx;
Group *octants[8];
int i;
/* Split the main bounding box into 8 boxes */
octantBB[0] | Point(rootBB.min.x, rootBB.min.y, rootBB.min.z);
octantBB[0] | Point(midX, midY, midZ);
octantBB[1] | Point(midX, rootBB.min.y, rootBB.min.z);
octantBB[1] | Point(rootBB.max.x, midY, midZ);
octantBB[2] | Point(rootBB.min.x, midY, rootBB.min.z);
octantBB[2] | Point(midX, rootBB.max.y, midZ);
octantBB[3] | Point(midX, midY, rootBB.min.z);
octantBB[3] | Point(rootBB.max.x, rootBB.max.y, midZ);
octantBB[4] | Point(rootBB.min.x, midY, midZ);
octantBB[4] | Point(midX, rootBB.max.y, rootBB.max.z);
octantBB[5] | Point(midX, midY, midZ);
octantBB[5] | Point(rootBB.max.x, rootBB.max.y, rootBB.max.z);
octantBB[6] | Point(rootBB.min.x, rootBB.min.y, midZ);
octantBB[6] | Point(midX, midY, rootBB.max.z);
octantBB[7] | Point(midX, rootBB.min.y, midZ);
octantBB[7] | Point(rootBB.max.x, midY, rootBB.max.z);
for (octantIdx = 0 ; octantIdx < 8 ; octantIdx++)
{
octants[octantIdx] = nullptr;
}
for (i = 0 ; i < leaf->getObjectCount(); i++)
{
Shape *shp = leaf->getObject(i);
BoundingBox objBB = shp->getBounds();
//if ((shp->getType() != Shape::GROUP) && (shp->getType() != Shape::OBJFILE))
for (octantIdx = 0 ; octantIdx < 8 ; octantIdx++)
{
if (octantBB[octantIdx].fitsIn(objBB))
{
if (octants[octantIdx] == nullptr)
{
char name[32];
snprintf(name, 32, "%d_Quadrant %d", depth, octantIdx);
octants[octantIdx] = new Group(name);
octants[octantIdx]->setBounds(octantBB[octantIdx]);
}
octants[octantIdx]->addObject(shp);
leaf->removeObject(shp);
i -= 1;
break;
}
}
if (shp->getType() == Shape::GROUP)
{
this->makeTree((Group *)shp, depth + 1);
}
if (shp->getType() == Shape::OBJFILE)
{
this->makeTree((Group *)((OBJFile *)shp)->getBaseGroup(), depth + 1);
}
}
/* Now add the quadrant to the root and recurse in it */
for (octantIdx = 0 ; octantIdx < 8 ; octantIdx++)
{
if (octants[octantIdx] != nullptr)
{
this->makeTree(octants[octantIdx], depth + 1);
octants[octantIdx]->updateBoundingBox();
leaf->addObject(octants[octantIdx]);
#if 0
Cube *cb = new Cube();
double sx = octantBB[octantIdx].max.x - octantBB[octantIdx].min.x;
double sy = octantBB[octantIdx].max.y - octantBB[octantIdx].min.y;
double sz = octantBB[octantIdx].max.z - octantBB[octantIdx].min.z;
cb->setTransform(translation(octantBB[octantIdx].min.x, octantBB[octantIdx].min.y,
octantBB[octantIdx].min.z) * scaling(sx, sy, sz));
cb->material.colour = Colour(0.01, 0.01, 0);
cb->materialSet = true;
cb->dropShadow = false;
cb->material.ambient = 0.1;
cb->material.reflective = 0;
cb->material.transparency = 0.95;
cb->material.refractiveIndex = 1;
cb->material.specular = 0;
leaf->addObject(cb);
printf("%s: %d objs\n", octants[octantIdx]->getName(),
octants[octantIdx]->getObjectCount());
#endif
}
}
}
void OctreeOptimisation::run()
{
/* First let's clear our hands */
this->moveInfiniteObjects();
/* Then let's have some fun! */
//this->moveAllObjects();
/* Now.. The fun start ! */
makeTree(this->root, 0);
}

View File

@@ -128,7 +128,7 @@ file(DOWNLOAD
)
add_custom_command(
TARGET uvmap_skybox POST_BUILD
COMMAND unzip -o ${CMAKE_SOURCE_DIR}/external/LancellottiChapel.zip -d LancellottiChapel
COMMAND unzip -qq -o ${CMAKE_SOURCE_DIR}/external/LancellottiChapel.zip -d LancellottiChapel
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/external/
)
add_custom_command(
@@ -138,6 +138,26 @@ add_custom_command(
${CMAKE_CURRENT_BINARY_DIR}/
)
# Dragon scene
add_executable(dragon_scene)
target_sources(dragon_scene PRIVATE dragon_scene.cpp)
file(DOWNLOAD
http://raytracerchallenge.com/bonus/assets/dragon.zip
${CMAKE_SOURCE_DIR}/external/dragon.zip
EXPECTED_HASH MD5=308b0f2aca1d48d24e6fc4584dfdf345
)
add_custom_command(
TARGET dragon_scene POST_BUILD
COMMAND unzip -qq -o ${CMAKE_SOURCE_DIR}/external/dragon.zip
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/external/
)
add_custom_command(
TARGET dragon_scene POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_SOURCE_DIR}/external/dragon.obj
${CMAKE_CURRENT_BINARY_DIR}/
)
add_test(NAME Chapter05_Test COMMAND $<TARGET_FILE:ch5_test>)
add_test(NAME Chapter06_Test COMMAND $<TARGET_FILE:ch6_test>)
add_test(NAME Chapter07_Test COMMAND $<TARGET_FILE:ch7_test>)
@@ -160,6 +180,7 @@ add_test(NAME UVMap_AlignCheckPlane COMMAND $<TARGET_FILE:uvmap_aligncheckplane>
add_test(NAME UVMap_CheckeredCube COMMAND $<TARGET_FILE:uvmap_checkeredcube>)
add_test(NAME UVMap_Earth COMMAND $<TARGET_FILE:uvmap_earth>)
add_test(NAME UVMap_Skybox COMMAND $<TARGET_FILE:uvmap_skybox>)
add_test(NAME Dragon_Sceme COMMAND $<TARGET_FILE:dragon_scene>)
add_test(NAME Test_Rendering COMMAND $<TARGET_FILE:test_render>)
add_test(NAME Triangle_RenderTest COMMAND $<TARGET_FILE:triangle_rendertest>)
add_test(NAME ChristmasBall_Rendering COMMAND $<TARGET_FILE:christmasball_render>)

View File

@@ -24,6 +24,8 @@
#include <transformation.h>
#include <worldoptimiser.h>
int main()
{
World w = World();
@@ -198,6 +200,9 @@ int main()
/* ----------------------------- */
BVHOptimisation opt;
w.finalise(opt);
/* Set the camera */
Camera camera = Camera(400, 200, 0.785);
camera.setTransform(viewTransform(Point(8, 6, -8),

View File

@@ -36,47 +36,62 @@ int main()
/* ----------------------------- */
/* Floor */
Plane p = Plane();
Material planesMaterial = Material();
CheckersPattern checkered = CheckersPattern(Colour(0.35, 0.35, 0.35), Colour(0.4, 0.4, 0.4));
p.material.pattern = &checkered;
p.material.ambient = 1;
p.material.diffuse = 0;
p.material.specular = 0;
planesMaterial.pattern = &checkered;
planesMaterial.ambient = 1;
planesMaterial.diffuse = 0;
planesMaterial.specular = 0;
Plane p = Plane();
p.setMaterial(planesMaterial);
p.material.reflective = 0.1;
w.addObject(&p);
Plane p2 = Plane();
p2.setTransform(translation(0, 0, -10) * rotationX(M_PI/2));
p2.material.pattern = &checkered;
p2.material.ambient = 1;
p2.material.diffuse = 0;
p2.material.specular = 0;
p2.setMaterial(planesMaterial);
w.addObject(&p2);
Material lowPoly = Material();
lowPoly.colour = Colour(1, 0.3, 0.2);
lowPoly.shininess = 5;
lowPoly.specular = 0.4;
OBJFile teapot = OBJFile("teapot-low.obj");
teapot.setTransform(translation(7, 0, 3) * rotationY(M_PI*23/22) * rotationX(-M_PI/2) * scaling(0.3, 0.3, 0.3));
teapot.material.colour = Colour(1, 0.3, 0.2);
teapot.material.shininess = 5;
teapot.material.specular = 0.4;
w.addObject(&teapot);
teapot.setMaterial(lowPoly);
w.addObject(teapot.getBaseGroup());
FILE *fpOut = fopen("lowpoly_teapot.json", "wt");
if (fpOut)
{
teapot.dumpMe(fpOut);
fclose(fpOut);
}
OBJFile teapot2 = OBJFile("teapot-lowtri.obj");
teapot2.setTransform(translation(-7, 0, 3) * rotationY(-M_PI*46/22) * rotationX(-M_PI/2) * scaling(0.3, 0.3, 0.3));
teapot2.material.colour = Colour(1, 0.3, 0.2);
teapot2.material.shininess = 5;
teapot2.material.specular = 0.4;
w.addObject(&teapot2);
teapot2.setMaterial(lowPoly);;
w.addObject(teapot2.getBaseGroup());
Material highPoly = Material();
highPoly.colour = Colour(0.3, 1, 0.2);
highPoly.shininess = 5;
highPoly.specular = 0.4;
highPoly.reflective = 0.5;
OBJFile teapot3= OBJFile("teapot.obj");
teapot3.setTransform(translation(0, 0, -5) * rotationY(-M_PI) * rotationX(-M_PI/2) * scaling(0.4, 0.4, 0.4));
teapot3.material.colour = Colour(0.3, 1, 0.2);
teapot3.material.shininess = 5;
teapot3.material.specular = 0.4;
teapot3.material.reflective = 0.5;
w.addObject(&teapot3);
teapot3.setMaterial(highPoly);
w.addObject(teapot3.getBaseGroup());
/* ----------------------------- */
BVHOptimisation opt;
w.finalise(opt);
/* Set the camera */
Camera camera = Camera(80, 40, M_PI/2);
camera.setTransform(viewTransform(Point(0, 7, 13),

View File

@@ -32,7 +32,8 @@ int main()
double worldX = -(wallSize / 2) + pixelSize * x;
Point position = Point(worldX, worldY, wallDistance);
Ray r = Ray(cameraOrigin, (position - cameraOrigin).normalise());
Intersect xs = s.intersect(r);
Intersect xs;
s.intersect(r, xs);
if (!xs.hit().nothing())
{

View File

@@ -36,7 +36,8 @@ int main()
double worldX = -(wallSize / 2) + pixelSize * x;
Point position = Point(worldX, worldY, wallDistance);
Ray r = Ray(cameraOrigin, (position - cameraOrigin).normalise());
Intersect xs = s.intersect(r);
Intersect xs;
s.intersect(r, xs);
Intersection hit = xs.hit();

View File

@@ -34,13 +34,9 @@ Shape *fir_branch()
Group *ret = new Group();
double length = 2;
double radius = 0.025;
#if 0
int segments = 20;
int perSegment = 24;
#else
int segments = 5;
int perSegment = 24;
#endif
Cylinder *branch = new Cylinder();
branch->minCap = 0;
branch->maxCap = length;
@@ -50,11 +46,12 @@ Shape *fir_branch()
branch->material.ambient = 0.2;
branch->material.specular = 0;
branch->material.diffuse = 0.6;
branch->materialSet = true;
ret->addObject(branch);
double seq_size = length / (segments - 1);
double theta = 2.1 * M_PI / perSegment;
double maxLenght = 20 * radius;
double maxLength = 20 * radius;
int y, i;
Triangle *needle;
@@ -73,7 +70,7 @@ Shape *fir_branch()
* around the branch */
double yAngle = i * theta + frand() * theta;
/* How long is the needle? */
double needleLenght = maxLenght / 2 * (1 + frand());
double needleLenght = maxLength / 2 * (1 + frand());
/* How much is the needle offset fomr the center of the branch? */
double ofs = radius / 2;
Point p1 = Point(ofs, yBase, ofs);
@@ -81,14 +78,14 @@ Shape *fir_branch()
Point p3 = Point(0, yTip, needleLenght);
needle = new Triangle(p1, p2, p3);
needle->setTransform(rotationY(yAngle));
needle->material.colour = Colour((1.0 / (double)perSegment)*i /*0.26*/, 0.36, /*0.16*/ (1. / (double)segments) * y);
needle->material.colour = Colour(0.26, 0.36, 0.16);
needle->material.specular = 0.1;
needle->materialSet = true;
subGroup->addObject(needle);
}
ret->addObject(subGroup);
}
return ret;
}
@@ -108,6 +105,7 @@ int main()
light1Sphere.material.ambient = 0.6;
light1Sphere.material.diffuse = 0;
light1Sphere.material.specular = 0;
light1Sphere.materialSet = true;
w.addObject(&light1Sphere);
Light light2 = Light(POINT_LIGHT, Point(10, 10, -10), Colour(0.6, 0.6, 0.6));
@@ -119,6 +117,7 @@ int main()
light2Sphere.material.ambient = 0.6;
light2Sphere.material.diffuse = 0;
light2Sphere.material.specular = 0;
light2Sphere.materialSet = true;
w.addObject(&light2Sphere);
Light light3 = Light(POINT_LIGHT, Point(-2, 1, -6), Colour(0.2, 0.1, 0.1));
@@ -130,6 +129,7 @@ int main()
light3Sphere.material.ambient = 0.6;
light3Sphere.material.diffuse = 0;
light3Sphere.material.specular = 0;
light3Sphere.materialSet = true;
w.addObject(&light3Sphere);
Light light4 = Light(POINT_LIGHT, Point(-1, -2, -6), Colour(0.1, 0.2, 0.1));
@@ -141,6 +141,7 @@ int main()
light4Sphere.material.ambient = 0.6;
light4Sphere.material.diffuse = 0;
light4Sphere.material.specular = 0;
light4Sphere.materialSet = true;
w.addObject(&light4Sphere);
Light light5 = Light(POINT_LIGHT, Point(3, -1, -6), Colour(0.2, 0.2, 0.2));
@@ -152,6 +153,7 @@ int main()
light5Sphere.material.ambient = 0.6;
light5Sphere.material.diffuse = 0;
light5Sphere.material.specular = 0;
light5Sphere.materialSet = true;
w.addObject(&light5Sphere);
/* ----------------------------- */
@@ -162,6 +164,7 @@ int main()
theBall.material.specular = 0;
theBall.material.diffuse = 0.5;
theBall.material.reflective = 0.5;
theBall.materialSet = true;
w.addObject(&theBall);
Cylinder crown = Cylinder();
@@ -175,6 +178,7 @@ int main()
crown.material.specular = 0.8;
crown.material.shininess = 20;
crown.material.reflective = 0.05;
crown.materialSet = true;
w.addObject(&crown);
/* ----------------------------- */
@@ -183,7 +187,7 @@ int main()
s = fir_branch();
s->setTransform(translation(-1, -1, 0) * rotationY(0.349) * rotationX(-1.5708) * translation(0, -0.5, 0));
w.addObject(s);
/*
s = fir_branch();
s->setTransform(translation(-1, 1, 0) * rotationY(0.349) * rotationX(-1.5708) * translation(0, -0.5, 0));
w.addObject(s);
@@ -223,16 +227,16 @@ int main()
s = fir_branch();
s->setTransform(translation(1.5, -0.5, 0) * rotationY(-0.1754) * rotationX(0.087) * rotationX(-1.5708) * translation(0, -0.5, 0));
w.addObject(s);
*/
/* ----------------------------- */
/*
FILE *fpOut = fopen("christmas_worlddump.json", "wt");
if (fpOut)
{
w.dumpMe(fpOut);
fclose(fpOut);
}
*/
/* ----------------------------- */

View File

@@ -46,7 +46,7 @@ TEST(ConeTest, Intersecting_a_cone_with_a_ray)
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cone.intersect(r);
Intersect xs; cone.intersect(r, xs);
/* Temporary lower the precision */
set_equal_precision(0.00001);
@@ -64,7 +64,7 @@ TEST(ConeTest, Intersecting_a_cone_with_a_ray_parall_to_one_of_its_halves)
Cone cone = Cone();
Tuple direction = Vector(0, 1, 1).normalise();
Ray r = Ray(Point(0, 0, -1), direction);
Intersect xs = cone.intersect(r);
Intersect xs; cone.intersect(r, xs);
ASSERT_EQ(xs.count(), 1);
/* Temporary lower the precision */
@@ -102,7 +102,8 @@ TEST(ConeTest, Intersecting_a_cone_end_cap)
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cone.intersect(r);
Intersect xs;
cone.intersect(r, xs);
ASSERT_EQ(xs.count(), Counts[i]);
}

View File

@@ -18,8 +18,8 @@
class CSGTest : public CSG
{
public:
Intersect doLocalIntersect(Ray r) {
return this->localIntersect(r);
void doLocalIntersect(Ray r, Intersect &xs) {
return this->localIntersect(r, xs);
};
Tuple doLocalNormalAt(Tuple point, Intersection *hit = nullptr) {
return this->localNormalAt(point, hit);
@@ -31,8 +31,8 @@ public:
return this->intersectionAllowed(leftHit, inLeft, inRight);
};
Intersect doFilterIntersections(Intersect &xs) {
return this->filterIntersections(xs);
void doFilterIntersections(Intersect &xs, Intersect &ret) {
this->filterIntersections(xs, ret);
}
CSGTest(OperationType operation, Shape *left, Shape *right) : CSG(operation, left, right) {};
@@ -186,7 +186,8 @@ TEST(CSGTest, Filtering_a_list_of_intersections)
for(i = 0; i < testCount; i++)
{
c.setOperation(testList[i]);
Intersect result = c.doFilterIntersections(xs);
Intersect result = Intersect();
c.doFilterIntersections(xs, result);
ASSERT_EQ(result.count(), 2);
ASSERT_EQ(result[0], xs[testResults[i][0]]);
@@ -201,7 +202,7 @@ TEST(CSGTest, A_ray_misses_a_csg_object)
CSGTest c = CSGTest(CSG::UNION, &s1, &s2);
Ray r = Ray(Point(0, 2, -5), Vector(0, 0, 1));
Intersect xs = c.doLocalIntersect(r);
Intersect xs; c.doLocalIntersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}
@@ -216,7 +217,7 @@ TEST(CSGTest, A_ray_hits_a_csg_object)
CSGTest c = CSGTest(CSG::UNION, &s1, &s2);
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Intersect xs = c.doLocalIntersect(r);
Intersect xs; c.doLocalIntersect(r, xs);
ASSERT_EQ(xs.count(), 2);
ASSERT_TRUE(double_equal(xs[0].t, 4));

View File

@@ -43,7 +43,7 @@ TEST(CubeTest, A_ray_intersects_a_cube)
for(i = 0; i < 7; i++)
{
Ray r = Ray(Origins[i], Directions[i]);
Intersect xs = c.intersect(r);
Intersect xs; c.intersect(r, xs);
ASSERT_EQ(xs.count(), 2);
EXPECT_EQ(xs[0].t, t1[i]);
@@ -77,7 +77,7 @@ TEST(CubeTest, A_ray_miss_a_cube)
for(i = 0; i < 6; i++)
{
Ray r = Ray(Origins[i], Directions[i]);
Intersect xs = c.intersect(r);
Intersect xs; c.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}

View File

@@ -34,7 +34,7 @@ TEST(CylinderTest, A_ray_miss_a_cylinder)
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cyl.intersect(r);
Intersect xs; cyl.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}
@@ -65,7 +65,7 @@ TEST(CylinderTest, A_ray_hit_a_cylinder)
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cyl.intersect(r);
Intersect xs; cyl.intersect(r, xs);
/* Temporary lower the precision */
set_equal_precision(0.00001);
@@ -142,7 +142,7 @@ TEST(CylinderTest, Intersecting_a_constrained_cylinder)
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cyl.intersect(r);
Intersect xs; cyl.intersect(r, xs);
ASSERT_EQ(xs.count(), Counts[i]);
}
@@ -184,7 +184,7 @@ TEST(CylinderTest, Intersecting_the_caps_of_a_close_cylinder)
Tuple direction = Directions[i].normalise();
Ray r = Ray(Origins[i], direction);
Intersect xs = cyl.intersect(r);
Intersect xs; cyl.intersect(r, xs);
ASSERT_EQ(xs.count(), Counts[i]);
}
}

136
tests/dragon_scene.cpp Normal file
View File

@@ -0,0 +1,136 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for OBJ File using teapots in chapter 15.
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <plane.h>
#include <cube.h>
#include <sphere.h>
#include <material.h>
#include <colour.h>
#include <canvas.h>
#include <camera.h>
#include <objfile.h>
#include <pattern.h>
#include <strippattern.h>
#include <gradientpattern.h>
#include <checkerspattern.h>
#include <ringpattern.h>
#include <transformation.h>
#include <cylinder.h>
int main()
{
World w = World();
/* Add lights */
/* Light light1 = Light(POINT_LIGHT, Point(10, 100, 10), Colour(.1, .1, .1));
w.addLight(&light1);
Light light2 = Light(POINT_LIGHT, Point(10, 100, -10), Colour(.1, .1, .2));
w.addLight(&light2);
Light light3 = Light(POINT_LIGHT, Point(-10, 100, 10), Colour(.1, .1, .1));
w.addLight(&light3);
Light light4 = Light(POINT_LIGHT, Point(-10, 100, -10), Colour(.1, .1, .1));
w.addLight(&light4);*/
Light light1 = Light(POINT_LIGHT, Point(0, 100, 0), Colour(.3, .3, .3));
w.addLight(&light1);
Point lightPos = Point(3.8, 6.8, 4.5);
Light mouthLight = Light(POINT_LIGHT, lightPos, Colour(0.5, 0.5, 0.5));
w.addLight(&mouthLight);
Light mainLight = Light(POINT_LIGHT, Point(8, 10, 16), Colour(1, 1, 1));
w.addLight(&mainLight);
/* ----------------------------- */
#if 0
/* Spot light */
Sphere spot = Sphere();
spot.dropShadow = false;
spot.setTransform(translation(lightPos.x, lightPos.y, lightPos.z) * scaling(0.2, 0.2, 0.2));
w.addObject(&spot);
Cylinder X = Cylinder();
X.minCap = 0;
X.maxCap = 4;
Cylinder Y = Cylinder();
Y.minCap = 0;
Y.maxCap = 4;
Cylinder Z = Cylinder();
Z.minCap = 0;
Z.maxCap = 4;
Z.materialSet = Y.materialSet = X.materialSet = true;
X.material.ambient = 1;
X.material.specular = 0;
X.material.diffuse = 0;
Z.material = Y.material = X.material;
Z.dropShadow = Y.dropShadow = X.dropShadow = false;
X.material.colour = Colour(1, 0, 0);
Y.material.colour = Colour(0, 1, 0);
Z.material.colour = Colour(0, 0, 1);
Y.setTransform(translation(lightPos.x, lightPos.y, lightPos.z) * scaling(0.1, 1, 0.1));
X.setTransform(translation(lightPos.x, lightPos.y, lightPos.z) * rotationZ(M_PI/2) * scaling(0.1, 1, 0.1));
Z.setTransform(translation(lightPos.x, lightPos.y, lightPos.z) * rotationX(M_PI/2) * scaling(0.1, 1, 0.1));
w.addObject(&X);
w.addObject(&Y);
w.addObject(&Z);
#endif
/* Floor */
Material floorMaterial;
floorMaterial.colour = Colour(0.2, 0.3, 0.3);
floorMaterial.reflective = 0.3;
floorMaterial.specular = 0.4;
floorMaterial.shininess = 5;
floorMaterial.ambient = 0;
floorMaterial.diffuse = 0.8;
/* Let's use a cube for the floor */
Cube floor = Cube();
floor.setMaterial(floorMaterial);
floor.setTransform(translation(0, -0.1, 0) * scaling(10, 0.1, 10));
w.addObject(&floor);
Material dragonMat;
dragonMat.colour = Colour(0.23, 0.75, 0.39);
dragonMat.reflective = 0.9;
dragonMat.transparency = 0.99;
dragonMat.specular = 0.9;
dragonMat.shininess = 50;
dragonMat.refractiveIndex = 1.6;
dragonMat.ambient = 0;
dragonMat.diffuse = 0.7;
Shape *dragon = OBJFile("dragon.obj").getBaseGroup();
dragon->setTransform(rotationY(M_PI * 0.75) * scaling(2, 2, 2));
dragon->setMaterial(dragonMat);
w.addObject(dragon);
/* ----------------------------- */
OctreeOptimisation opt;
w.finalise(opt);
/* Set the camera */
Camera camera = Camera(40, 30, M_PI/2);
camera.setTransform(viewTransform(Point(-5, 10, 13),
Point(0, 1, 0),
Vector(0, 1, 0)));
/* Now render it */
Canvas image = camera.render(w, 5);
image.SaveAsPNG("ch15_teapot_objfile.png");
return 0;
}

View File

@@ -39,7 +39,7 @@ TEST(GroupTest, Intersecting_a_ray_with_an_empty_group)
{
Group g = Group();
Ray r = Ray(Point(0, 0, 0), Vector(0, 0, 1));
Intersect xs = g.intersect(r);
Intersect xs; g.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}
@@ -58,7 +58,7 @@ TEST(GroupTest, Intersecting_a_ray_with_an_nonempty_group)
g.addObject(&s3);
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Intersect xs = g.intersect(r);
Intersect xs; g.intersect(r, xs);
ASSERT_EQ(xs.count(), 4);
EXPECT_EQ(xs[0].object, &s2);
EXPECT_EQ(xs[1].object, &s2);
@@ -77,7 +77,7 @@ TEST(GroupTest, Intersecting_a_transformed_group)
g.addObject(&s);
Ray r = Ray(Point(10, 0, -50), Vector(0, 0, 1));
Intersect xs = g.intersect(r);
Intersect xs; g.intersect(r, xs);
ASSERT_EQ(xs.count(), 2);
}
@@ -97,4 +97,36 @@ TEST(GroupTest, Group_bounding_box)
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}
}
TEST(GroupTest, Removing_an_object)
{
Group g = Group();
Sphere s1 = Sphere();
Sphere s2 = Sphere();
Sphere s3 = Sphere();
s1.setTransform(translation(-1, 0, 0));
s2.setTransform(scaling(0.5, 0.5, 0.5));
g.addObject(&s1);
g.addObject(&s2);
ASSERT_EQ(g.getObjectCount(), 2);
ASSERT_EQ(*(g[0]), s1);
ASSERT_EQ(*(g[1]), s2);
g.removeObject(&s1);
ASSERT_EQ(g.getObjectCount(), 1);
ASSERT_EQ(*(g[0]), s2);
ASSERT_EQ(g[1], nullptr);
g.removeObject(&s3);
ASSERT_EQ(g.getObjectCount(), 1);
g.removeObject(&s2);
ASSERT_EQ(g.getObjectCount(), 0);
ASSERT_EQ(g[0], nullptr);
}

View File

@@ -57,7 +57,7 @@ TEST(IntersectTest, Intersect_sets_the_object_on_the_intersection)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Sphere s = Sphere();
Intersect xs = s.intersect(r);
Intersect xs; s.intersect(r, xs);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].object, (Shape *)&s);

View File

@@ -55,7 +55,7 @@ TEST(OBJFileTest, Parsing_triangle_faces)
OBJFile parser = OBJFile();
parser.parseOBJFile(file);
Group *g0 = parser.groups(0);
Group *g0 = parser.groups(OBJ_DEFAULT_GROUP);
Triangle *t1 = (Triangle *)(*g0)[0];
Triangle *t2 = (Triangle *)(*g0)[1];
@@ -82,7 +82,7 @@ TEST(OBJFileTest, Triangulating_polygons)
OBJFile parser = OBJFile();
parser.parseOBJFile(file);
Group *g0 = parser.groups(0);
Group *g0 = parser.groups(OBJ_DEFAULT_GROUP);
Triangle *t1 = (Triangle *)(*g0)[0];
Triangle *t2 = (Triangle *)(*g0)[1];
@@ -105,9 +105,12 @@ TEST(OBJFileTest, Triangle_in_groups)
{
OBJFile parser = OBJFile("triangles.obj");
/* TODO: Add group name search/test */
Group *g1 = parser.groups(1);
Group *g2 = parser.groups(2);
Group *g1 = parser.groups("FirstGroup");
Group *g2 = parser.groups("SecondGroup");
/* The groups must exists */
ASSERT_NE(g1, nullptr);
ASSERT_NE(g2, nullptr);
Triangle *t1 = (Triangle *)(*g1)[0];
Triangle *t2 = (Triangle *)(*g2)[0];
@@ -151,10 +154,10 @@ TEST(OBJFileTest, Faces_with_normal)
OBJFile parser = OBJFile();
parser.parseOBJFile(file);
Group *g0 = parser.groups(0);
Group *g0 = parser.groups(OBJ_DEFAULT_GROUP);
SmoothTriangle *t1 = (SmoothTriangle *)(*g0)[0];
SmoothTriangle *t2 = (SmoothTriangle *)(*g0)[1];
SmoothTriangle *t1 = (SmoothTriangle *)g0->getObject(0);
SmoothTriangle *t2 = (SmoothTriangle *)g0->getObject(1);
ASSERT_EQ(t1->p1, parser.vertices(1));
ASSERT_EQ(t1->p2, parser.vertices(2));
@@ -171,6 +174,4 @@ TEST(OBJFileTest, Faces_with_normal)
ASSERT_EQ(t2->n1, parser.verticesNormal(3));
ASSERT_EQ(t2->n2, parser.verticesNormal(1));
ASSERT_EQ(t2->n3, parser.verticesNormal(2));
}

View File

@@ -31,7 +31,7 @@ TEST(PlaneTest, Intersect_with_a_ray_parallel_to_the_plane)
Plane p = Plane();
Ray r = Ray(Point(0, 10, 0), Vector(0, 0, 1));
Intersect xs = p.intersect(r);
Intersect xs; p.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}
@@ -41,7 +41,7 @@ TEST(PlaneTest, Intersect_with_a_coplanar_ray)
Plane p = Plane();
Ray r = Ray(Point(0, 0, 0), Vector(0, 0, 1));
Intersect xs = p.intersect(r);
Intersect xs; p.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}
@@ -51,7 +51,7 @@ TEST(PlaneTest, A_ray_intersecting_a_plane_from_above)
Plane p = Plane();
Ray r = Ray(Point(0, 1, 0), Vector(0, -1, 0));
Intersect xs = p.intersect(r);
Intersect xs; p.intersect(r, xs);
ASSERT_EQ(xs.count(), 1);
ASSERT_EQ(xs[0].t, 1);
@@ -63,7 +63,7 @@ TEST(PlaneTest, A_ray_intersecting_a_plane_from_below)
Plane p = Plane();
Ray r = Ray(Point(0, -1, 0), Vector(0, 1, 0));
Intersect xs = p.intersect(r);
Intersect xs; p.intersect(r, xs);
ASSERT_EQ(xs.count(), 1);
ASSERT_EQ(xs[0].t, 1);

View File

@@ -53,7 +53,7 @@ TEST(ShapeTest, Intersecting_a_scaled_shape_with_a_ray)
TestShape s = TestShape();
s.setTransform(scaling(2, 2, 2));
Intersect xs = s.intersect(r);
Intersect xs; s.intersect(r, xs);
ASSERT_EQ(s.localRay.origin, Point(0, 0, -2.5));
ASSERT_EQ(s.localRay.direction, Vector(0, 0, 0.5));
@@ -65,7 +65,7 @@ TEST(ShapeTest, Intersecting_a_translated_shape_with_a_ray)
TestShape s = TestShape();
s.setTransform(translation(5, 0, 0));
Intersect xs = s.intersect(r);
Intersect xs; s.intersect(r, xs);
ASSERT_EQ(s.localRay.origin, Point(-5, 0, -5));
ASSERT_EQ(s.localRay.direction, Vector(0, 0, 1));
@@ -198,6 +198,28 @@ TEST(ShapeTest, Test_the_bouding_box_of_the_translated_shape)
BoundingBox res = t.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}
TEST(ShapeTest, A_lock_shape_can_t_have_transformation_changed)
{
TestShape t = TestShape();
t.setTransform(translation(10, 0, 0));
BoundingBox b = BoundingBox(Point(9, -1, -1), Point(11, 1, 1));
BoundingBox res = t.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
t.lock();
t.setTransform(translation(-10, -10,-10));
res = t.getBounds();
ASSERT_EQ(res.min, b.min);
ASSERT_EQ(res.max, b.max);
}

View File

@@ -14,9 +14,9 @@ class SmoothTriTest : public SmoothTriangle
{
public:
SmoothTriTest(Point p1, Point p2, Point p3, Vector n1, Vector n2, Vector n3) : SmoothTriangle(p1, p2, p3, n1, n2, n3) {};
Intersect doLocalIntersect(Ray ray)
void doLocalIntersect(Ray ray, Intersect &xs)
{
return this->localIntersect(ray);
this->localIntersect(ray, xs);
};
Tuple doLocalNormalAt(Tuple point, Intersection *hit)
@@ -48,7 +48,7 @@ TEST(SmoothTriangleTest, An_intersection_with_a_smooth_triangle_stores_u_v)
{
Ray r = Ray(Point(-0.2, 0.3, -2), Vector(0, 0, 1));
Intersect xs = tri.doLocalIntersect(r);
Intersect xs; tri.doLocalIntersect(r, xs);
ASSERT_TRUE(double_equal(xs[0].u, 0.45));
ASSERT_TRUE(double_equal(xs[0].v, 0.25));

View File

@@ -17,7 +17,7 @@ TEST(SphereTest, A_ray_intersect_a_sphere_at_two_points)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Sphere s = Sphere();
Intersect xs = s.intersect(r);
Intersect xs; s.intersect(r, xs);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, 4.0);
@@ -28,7 +28,7 @@ TEST(SphereTest, A_ray_intersect_a_sphere_at_a_tangent)
{
Ray r = Ray(Point(0, 1, -5), Vector(0, 0, 1));
Sphere s = Sphere();
Intersect xs = s.intersect(r);
Intersect xs; s.intersect(r, xs);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, 5.0);
@@ -39,7 +39,7 @@ TEST(SphereTest, A_ray_miss_a_sphere)
{
Ray r = Ray(Point(0, 2, -5), Vector(0, 0, 1));
Sphere s = Sphere();
Intersect xs = s.intersect(r);
Intersect xs; s.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}
@@ -48,7 +48,7 @@ TEST(SphereTest, A_ray_originate_inside_a_sphere)
{
Ray r = Ray(Point(0, 0, 0), Vector(0, 0, 1));
Sphere s = Sphere();
Intersect xs = s.intersect(r);
Intersect xs; s.intersect(r, xs);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, -1.0);
@@ -59,7 +59,7 @@ TEST(SphereTest, A_sphere_is_behind_a_ray)
{
Ray r = Ray(Point(0, 0, 5), Vector(0, 0, 1));
Sphere s = Sphere();
Intersect xs = s.intersect(r);
Intersect xs; s.intersect(r, xs);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, -6.0);
@@ -89,7 +89,7 @@ TEST(SphereTest, Intersecting_a_scaled_sphere_with_a_ray)
s.setTransform(scaling(2, 2, 2));
Intersect xs = s.intersect(r);
Intersect xs; s.intersect(r, xs);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, 3.0);
@@ -103,7 +103,7 @@ TEST(SphereTest, Intersecting_a_translated_sphere_with_a_ray)
s.setTransform(translation(5, 0, 0));
Intersect xs = s.intersect(r);
Intersect xs; s.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}

View File

@@ -44,7 +44,7 @@ TEST(TriangleTest, Intersecting_a_ray_parallel_to_the_triangle)
Triangle t = Triangle(Point(0, 1, 0), Point(-1, 0, 0), Point(1, 0, 0));
Ray r = Ray(Point(0, -1, -2), Vector(0, 1, 0));
Intersect xs = t.intersect(r);
Intersect xs; t.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}
@@ -54,7 +54,7 @@ TEST(TriangleTest, A_ray_miss_the_p1_p3_edge)
Triangle t = Triangle(Point(0, 1, 0), Point(-1, 0, 0), Point(1, 0, 0));
Ray r = Ray(Point(1, 1, -2), Vector(0, 0, 1));
Intersect xs = t.intersect(r);
Intersect xs; t.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}
@@ -64,7 +64,7 @@ TEST(TriangleTest, A_ray_miss_the_p1_p2_edge)
Triangle t = Triangle(Point(0, 1, 0), Point(-1, 0, 0), Point(1, 0, 0));
Ray r = Ray(Point(-1, 1, -2), Vector(0, 0, 1));
Intersect xs = t.intersect(r);
Intersect xs; t.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}
@@ -74,7 +74,7 @@ TEST(TriangleTest, A_ray_miss_the_p2_p3_edge)
Triangle t = Triangle(Point(0, 1, 0), Point(-1, 0, 0), Point(1, 0, 0));
Ray r = Ray(Point(0, -1, -2), Vector(0, 0, 1));
Intersect xs = t.intersect(r);
Intersect xs; t.intersect(r, xs);
ASSERT_EQ(xs.count(), 0);
}
@@ -84,7 +84,7 @@ TEST(TriangleTest, A_ray_strikes_a_triangle)
Triangle t = Triangle(Point(0, 1, 0), Point(-1, 0, 0), Point(1, 0, 0));
Ray r = Ray(Point(0, .5, -2), Vector(0, 0, 1));
Intersect xs = t.intersect(r);
Intersect xs; t.intersect(r, xs);
ASSERT_EQ(xs.count(), 1);
EXPECT_EQ(xs[0].t, 2);

View File

@@ -22,8 +22,8 @@ TEST(WorldTest, Creating_a_world)
{
World w;
ASSERT_EQ(w.lightCount, 0);
ASSERT_EQ(w.objectCount, 0);
ASSERT_EQ(w.getLightCount(), 0);
ASSERT_EQ(w.getObjectCount(), 0);
}
TEST(WorldTest, The_default_world)
@@ -41,9 +41,12 @@ TEST(WorldTest, The_default_world)
s2.setTransform(scaling(0.5, 0.5,0.5));
Shape *obj0 = w.getObject(0);
Shape *obj1 = w.getObject(1);
ASSERT_TRUE(w.lightIsIn(l));
ASSERT_TRUE(w.objectIsIn(s1));
ASSERT_TRUE(w.objectIsIn(s2));
ASSERT_EQ(*obj0, s1);
ASSERT_EQ(*obj1, s2);
}
TEST(WorldTest, Intersect_a_world_with_a_ray)
@@ -51,7 +54,8 @@ TEST(WorldTest, Intersect_a_world_with_a_ray)
World w = DefaultWorld();
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Intersect xs = w.intersect(r);
Intersect xs;
w.intersect(r, xs);
ASSERT_EQ(xs.count(), 4);
ASSERT_EQ(xs[0].t, 4);