95 Commits

Author SHA1 Message Date
Godzil
dac74007ea Add sample from Chapter 12 :) 2020-02-22 17:39:25 +00:00
Godzil
b251b632ac Add a parameter for shapes to not drop shadow. 2020-02-22 17:38:25 +00:00
Godzil
81e323fdf4 Added CUBES! 2020-02-22 17:30:15 +00:00
Godzil
c9021974f6 Add a world generator based on another raytracer file format I made in the past and add a crude tool to run it.
it does not render properly, there are some major differences between both engine especially in the material definition. Will need more work, but is not urgent.
2020-02-22 15:16:25 +00:00
Godzil
4d4c4a7453 Add preliminary support for material emissivity.
Not yet sure it work as I intended.
2020-02-22 15:14:09 +00:00
Godzil
935c8ebff7 Add support for multiple lights 2020-02-22 15:12:06 +00:00
Godzil
4cdf7a4264 Correct default canvas size for ch11_test 2020-02-22 01:29:42 +00:00
Godzil
51a6bbebb9 Refraction is fully there, with magic fresnel! 2020-02-22 01:27:48 +00:00
Godzil
e45dbad59e Added some proper test scenes for chapter 11. 2020-02-22 00:50:55 +00:00
Godzil
3db0aaaeac Refraction seems to work. Still need to do a nice scene. 2020-02-21 22:50:12 +00:00
Godzil
df52cb36db Working on refraction & transparency.
Lots of work left to do!
2020-02-21 18:59:14 +00:00
Godzil
89dd74fa7c Finally! We have reflections! 2020-02-21 17:39:45 +00:00
Godzil
7337ae4837 Finally! We have reflections! 2020-02-21 17:21:06 +00:00
Godzil
9fffb68026 Remove nanogui dependencies for now. If the need of a gui come, will add back but for now it just add unnecessary checkout time 2020-02-21 12:08:54 +00:00
Godzil
7687581e83 Finishing touch for patterns! 2020-02-21 12:05:30 +00:00
Godzil
75cf59cc1a Adding support for pattern.
Still a bit more work to be done there.
2020-02-21 09:36:34 +00:00
Godzil
9d0db6a635 Added planes! 2020-02-21 00:26:48 +00:00
Godzil
66c1582a5f Shape is now an abstract class and can't be instanciated.
Change derived shape to only deal with local calculation they don't need anymore to deal with how they've been transformed.
2020-02-21 00:02:30 +00:00
Godzil
2a8fe61388 Working on adding test for the shape object. 2020-02-20 18:06:29 +00:00
Godzil
f8c60da05e Updating readme 2020-02-20 17:48:09 +00:00
Godzil
de315d06f9 Just to be sure. 2020-02-20 17:47:26 +00:00
Godzil
cf5597ad6d Adding shadows! 2020-02-20 17:46:03 +00:00
Manoël Trapier
5198888df6 Doh 2020-02-20 16:49:02 +00:00
Godzil
10ae695f01 Trying to fixing some weird things about coverall, also add some real more coverage. 2020-02-20 16:47:00 +00:00
Godzil
d4fae2dbe2 Revert the canvas size to the one from the chapter 2020-02-20 16:20:20 +00:00
Godzil
daef0c078f Revert the canvas size to the one from the chapter 2020-02-20 16:13:58 +00:00
Godzil
a477b137e7 Add a super special Camera from a well known constructor. Can take picture up to Infinite TeraPixel! 2020-02-20 16:08:47 +00:00
Godzil
ba1ae34855 Canvas: use Tuple instead of Colour for put Pixel, add two constructor that may be usefull in the future. 2020-02-20 16:07:39 +00:00
Godzil
bc047cd89e Add viewTransform transformation 2020-02-20 15:11:40 +00:00
Godzil
863bb2a34b Name consistency 2020-02-20 14:48:11 +00:00
Godzil
14c3044acf Cosmetics 2020-02-20 13:27:12 +00:00
Godzil
4b4d2b7819 Wow travis detected (by luck?) that uninitialised variable where one test failed in one specific configuration but no other one..
And thanks valgrind to give me a hint!
2020-02-20 02:01:35 +00:00
Godzil
9e1f448e0f testS.... 2020-02-20 01:53:28 +00:00
Godzil
457f5d04e4 Also run the unit test directly. 2020-02-20 01:49:37 +00:00
Godzil
999419dfe1 World is on the verge of working! 2020-02-20 01:41:53 +00:00
Godzil
be6b472472 Simplify hit search as now the list is ordered. 2020-02-20 01:40:50 +00:00
Godzil
aa078f4d46 Fix in world. 2020-02-20 00:47:02 +00:00
Godzil
dbaa6eea2c Fix Intersect and add sort the list each time we add something to it. 2020-02-20 00:46:19 +00:00
Godzil
a82b67faa4 Working on worlds.
It's currently crashing.
2020-02-19 18:05:48 +00:00
Godzil
efe46e2864 Fix function prototype 2020-02-19 15:45:19 +00:00
Godzil
60d639f3a7 Start working on world (domination) 2020-02-18 17:31:21 +00:00
Godzil
ddaefafa1a Don't fully understand coveralls behaviour. Let's try building with gcc to see it is works better. 2020-02-18 12:24:05 +00:00
Godzil
9f764018d3 Add a couple of test for code that wasn't tested before. 2020-02-18 12:20:40 +00:00
Godzil
6200e5ed56 It generally works better when we build before trying to run..... 2020-02-18 12:10:38 +00:00
Godzil
be245523c9 This should be the proper way. 2020-02-18 12:07:37 +00:00
Godzil
11a00a6e74 Testing travis job feature. 2020-02-18 11:50:32 +00:00
Godzil
af96d52c5a Renaming Object to Shape (part 2) 2020-02-18 11:43:05 +00:00
Godzil
5a4f9f4dc4 Renaming Object to Shape (part 1) 2020-02-18 11:40:55 +00:00
Godzil
df4ec9794a Update travis badge 2020-02-17 23:47:08 +00:00
Godzil
db9f2c0203 Do I really know SH script? I wonder sometimes 😣 2020-02-17 23:33:48 +00:00
Godzil
88fcd48481 This test wasn't support to be there at first. 2020-02-17 23:29:50 +00:00
Godzil
5e295c06b2 Seems that coveralls don't like the OS X build, so let's only push when build from linux 2020-02-17 23:28:38 +00:00
Godzil
4dd7a3af39 Add Chapter 5 and 6 executable to the test list 2020-02-17 23:22:05 +00:00
Godzil
1aab8f6619 Remove the test 🤨 2020-02-17 23:21:28 +00:00
Godzil
27ec4d5567 Trying to find which version of gcov is installed on travis-ci. 2020-02-17 23:13:09 +00:00
Godzil
a64b1288a5 Trying to make travis having the proper gcov version. 2020-02-17 23:06:02 +00:00
Godzil
77907499a4 Rework a bit the base Cmakelist to make it clearer. 2020-02-17 23:05:37 +00:00
Godzil
cf00e62c5a Update coverall script to use my fork that properly support AppleClang. 2020-02-17 23:05:08 +00:00
Godzil
5086a5c82f Use file/glob instead of set to add source files 2020-02-17 23:02:38 +00:00
Godzil
b84ed7496b Trying to make gcov/coveralls to work... 2020-02-17 22:32:33 +00:00
Godzil
b4bd8bd4b7 CoverallS... 2020-02-17 22:19:10 +00:00
Godzil
c1e7496d21 Fix a glib problem on some distros. 2020-02-17 22:18:59 +00:00
Godzil
674831b370 Travis don't like tabs! (he is right :)) 2020-02-17 22:09:31 +00:00
Godzil
8cc2272b50 Add a really really light readme. 2020-02-17 22:04:00 +00:00
Godzil
2e2d8c143c Add travis, coverall and other things. 2020-02-17 21:56:59 +00:00
Godzil
a8ca88640b Add GPLv2 strict license 2020-02-17 21:28:55 +00:00
Godzil
9bdfb26f7e Add Chapter 06 test! 2020-02-17 19:15:58 +00:00
Godzil
5ebed12f4f Add support for point light and materials.
Add material to objects.
2020-02-17 19:12:57 +00:00
Godzil
73d60fb7e4 Fix a copy mistake. 2020-02-17 19:09:31 +00:00
Godzil
4a9379c0b2 Naming consistency 2020-02-17 17:57:55 +00:00
Godzil
aeb4669162 Add vector reflection via a normal vector. 2020-02-17 15:56:08 +00:00
Godzil
a8194169c5 Add function to calculate sphere normal vector on given point on the sphere. 2020-02-17 15:39:14 +00:00
Godzil
656ff52204 Add Chapter 5 "putting it together" example. 2020-02-17 14:30:21 +00:00
Godzil
00b283053e Add transformation to objects. 2020-02-17 14:15:55 +00:00
Godzil
b799e5f819 Update a comment. 2020-02-17 13:57:07 +00:00
Godzil
cabe7ff147 Change Ray to use the generic Tuple instead of Point/Vector (but you still should use Point/Vector for initialisation) 2020-02-17 13:54:07 +00:00
Godzil
17aebe6538 Move the cross product to Tuple instead of just vector (to ease some stuff later, but this is invalid on Points) 2020-02-17 13:53:30 +00:00
Godzil
a1087a9871 Add default size to matrix (4 as it is the most common) 2020-02-17 13:52:28 +00:00
Godzil
c4418683c6 Add the hit function to get the closest non negative hit, and add some mecanisme to test that properly and report when no valid hit occur. 2020-02-17 12:24:15 +00:00
Godzil
1900d1f45d Change the Intersection to a class, and stop using memory allocation for it (and pointer)
A bit more clean (on the code side)
2020-02-17 11:48:29 +00:00
Godzil
513cd9d7eb Update glfw, googletest and lodepng submodules to their latest version 2020-02-17 11:19:53 +00:00
Godzil
01a0de09ab Add 3 new type of object: Sphere, Object and Intersect.
Add Intersect object as a way to report where a ray intersect another object and which one it is.
Add an Object base class for all object that can be rendered.
Add the Sphere object.
2020-02-17 11:16:20 +00:00
Godzil
8faf1db3be More Ray work. 2020-02-15 23:45:19 +00:00
Godzil
79af9fbc97 Also rename the test file. 2020-02-15 23:20:37 +00:00
Godzil
1e2588441f Rename tuple file to remove the plural.
Also add empty shell ray.
2020-02-15 23:18:04 +00:00
Godzil
1f4b14c896 Add a function to convert degree to radian. 2020-02-14 23:29:09 +00:00
Godzil
dee4b9ae91 Now we can transform! 2020-02-14 23:28:56 +00:00
Godzil
514bd649c1 Correct how google test is used.
I think it was working before because the other computer had gtest system installed.

Bonus: it is now also integrated with CTest
2020-02-14 22:19:39 +00:00
Godzil
0621ca86e1 Change width to size, as it is more correct.
Add calculation of determinant, submatric, minorm cofactor and inverse of a matrix.
2020-02-14 19:19:36 +00:00
Godzil
9f2a41e6f3 Change default precision to float instead of double (need to investigate there)
Also add a way to dynamically change the precision (needed for some test that don't use non full precision result matrix)
2020-02-14 19:18:43 +00:00
Godzil
95c7616646 Moving some functions around to keep the header a bit more tidy 2020-02-14 17:52:47 +00:00
Godzil
c4c216647d First batch of matrix related functions 2020-02-14 17:49:51 +00:00
Godzil
fac2212661 Canvas implementation and add build of LodePNG 2020-02-14 16:04:28 +00:00
Godzil
a9321b5051 Update an header 2020-02-14 15:08:50 +00:00
Godzil
f3678992c5 Add Colour implementation 2020-02-14 15:08:40 +00:00
96 changed files with 6800 additions and 62 deletions

6
.gitmodules vendored
View File

@@ -1,12 +1,12 @@
[submodule "external/googletest"] [submodule "external/googletest"]
path = external/googletest path = external/googletest
url = https://github.com/google/googletest.git url = https://github.com/google/googletest.git
[submodule "external/nanogui"]
path = external/nanogui
url = https://github.com/Godzil/nanogui.git
[submodule "external/glfw"] [submodule "external/glfw"]
path = external/glfw path = external/glfw
url = https://github.com/glfw/glfw.git url = https://github.com/glfw/glfw.git
[submodule "external/lodepng"] [submodule "external/lodepng"]
path = external/lodepng path = external/lodepng
url = https://github.com/lvandeve/lodepng url = https://github.com/lvandeve/lodepng
[submodule "external/coveralls-cmake"]
path = external/coveralls-cmake
url = https://github.com/Godzil/coveralls-cmake.git

36
.travis.yml Normal file
View File

@@ -0,0 +1,36 @@
dist: bionic
language: c
addons:
apt:
packages:
- lcov
os:
- linux
- osx
compiler:
- clang
- gcc
script:
- mkdir build
- cd build
- cmake ..
- make
- make test
- ./tests/testMyRays
jobs:
include:
- stage: "Coverage"
os: linux
compiler: gcc
script:
- mkdir coverage
- cd coverage
- cmake .. -DCOVERALLS=ON -DCMAKE_BUILD_TYPE=Debug
- cmake --build .
- cmake --build . --target coveralls
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@@ -6,18 +6,36 @@ project(DoRayMe)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
option(COVERALLS "Generate coveralls data" OFF)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/external/coveralls-cmake/cmake)
#Add external projects that directly need to be builded option(PACKAGE_TESTS "Build the tests" ON)
ExternalProject_Add(googletest
SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/googletest" if (COVERALLS)
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/external/googletest" include(Coveralls)
CONFIGURE_COMMAND "" coveralls_turn_on_coverage()
BUILD_COMMAND "" endif()
INSTALL_COMMAND ""
TEST_COMMAND ""
) # LodePNG don't make a .a or .so, so let's build a library here
add_library(LodePNG STATIC)
set(LODEPNG_INCLUDE_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/external/lodepng)
target_sources(LodePNG PRIVATE external/lodepng/lodepng.cpp external/lodepng/lodepng.h)
# Main app # Main app
add_subdirectory(source) add_subdirectory(source)
# Unit Tests
add_subdirectory(tests) if(PACKAGE_TESTS OR COVERALLS)
enable_testing()
include(GoogleTest)
add_subdirectory("${PROJECT_SOURCE_DIR}/external/googletest" "external/googletest")
add_subdirectory(tests)
endif()
if (COVERALLS)
# Create the coveralls target.
coveralls_setup(
"${COVERAGE_SRCS}" # The source files.
ON # If we should upload.
) # (Optional) Alternate project cmake module path.
endif()

340
COPYING Normal file
View File

@@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

14
LICENSE Normal file
View File

@@ -0,0 +1,14 @@
Copyright (C) 2020 Manoel <Godzil> Trapier
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

View File

@@ -0,0 +1,51 @@
[![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)
DoRayMe
=======
A Quick and dirty raytracer.
This raytracer is made following the book "[The Ray Tracer Challenge](https://pragprog.com/book/jbtracer/the-ray-tracer-challenge)" by Jamis Buck.
It is writen in C++ with no STL and use [LodePNG](https://github.com/lvandeve/lodepng) to output PNG file.
Examples outputs
----------------
From chapter 05:
![Chapter 5 rendering test](output/ch5_test.png)
From Chapter 06:
![Chapter 6 rendering test](output/ch6_test.png)
From Chapter 07:
![Chapter 7 rendering test](output/ch7_test.png)
From Chapter 08:
![Chapter 8 rendering test](output/ch8_test.png)
From Chapter 09:
![Chapter 9 rendering test](output/ch9_test.png)
From Chapter 10:
![Chapter 10 rendering test](output/ch10_test.png)
From Chapter 11:
![Chapter 11 reflections rendering test](output/ch11_reflection.png)
![Chapter 11 refraction rendering test](output/ch11_refraction.png)
![Chapter 11 rendering test](output/ch11_test.png)
From Chapter 12:
![Chapter 12 rendering test](output/ch12_test.png)

1
external/coveralls-cmake vendored Submodule

2
external/glfw vendored

1
external/nanogui vendored

Submodule external/nanogui deleted from 16bc6b1d3a

BIN
output/ch10_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

BIN
output/ch11_reflection.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

BIN
output/ch11_refraction.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

BIN
output/ch11_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

BIN
output/ch5_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 B

BIN
output/ch6_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
output/ch7_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
output/ch8_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
output/ch9_test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -1,16 +1,22 @@
# To simplify testing, the app is build in two passes, # To simplify testing, the app is build in two passes,
# First most is build as a library # First most is build as a library
add_library(rayonnement STATIC math_helper.cpp) add_library(rayonnement STATIC)
set(RAY_HEADERS include/tuples.h include/math_helper.h) file(GLOB RAY_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/*.h ${CMAKE_CURRENT_SOURCE_DIR}/pattern/*.h)
set(RAY_SOURCES tuples.cpp)
target_include_directories(rayonnement PUBLIC include) file(GLOB RAY_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shapes/*.cpp
${CMAKE_CURRENT_SOURCE_DIR}/worldbuilder/*.cpp)
target_include_directories(rayonnement PUBLIC include pattern)
target_sources(rayonnement PRIVATE ${RAY_HEADERS} ${RAY_SOURCES}) target_sources(rayonnement PRIVATE ${RAY_HEADERS} ${RAY_SOURCES})
target_link_libraries(rayonnement LodePNG)
# Second we build the main executable # Second we build the main executable
add_executable(dorayme main.cpp) add_executable(dorayme main.cpp)
target_include_directories(rayonnement PUBLIC include) target_include_directories(rayonnement PUBLIC include ${LODEPNG_INCLUDE_FOLDER})
target_link_libraries(dorayme rayonnement) target_link_libraries(dorayme rayonnement)
if (COVERALLS)
set(COVERAGE_SRCS ${RAY_HEADERS} ${RAY_SOURCES} ${COVERAGE_SRCS} PARENT_SCOPE)
endif()

74
source/camera.cpp Normal file
View File

@@ -0,0 +1,74 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Camera implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <matrix.h>
#include <stdint.h>
#include <math.h>
#include <ray.h>
#include <camera.h>
Camera::Camera(uint32_t hsize, uint32_t vsize, double fov) : verticalSize(vsize), horizontalSize(hsize), fieldOfView(fov)
{
double aspectRatio = (double)hsize / (double)vsize;
double halfView = tan(fov / 2.0);
if (aspectRatio >= 1)
{
this->halfWidth = halfView;
this->halfHeight = halfView / aspectRatio;
}
else
{
this->halfWidth = halfView * aspectRatio;
this->halfHeight = halfView;
}
this->pixelSize = (this->halfWidth * 2) / this->horizontalSize;
this->setTransform(Matrix4().identity());
}
void Camera::setTransform(Matrix transform)
{
this->transformMatrix = transform;
this->inverseTransform = transform.inverse();
}
Ray Camera::rayForPixel(uint32_t pixelX, uint32_t pixelY)
{
double xOffset = ((double)pixelX + 0.5) * this->pixelSize;
double yOffset = ((double)pixelY + 0.5) * this->pixelSize;
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 direction = (pixel - origin).normalise();
return Ray(origin, direction);
}
Canvas Camera::render(World world, uint32_t depth)
{
uint32_t x, y;
Canvas image = Canvas(this->horizontalSize, this->verticalSize);
for(y = 0; y < this->verticalSize; y++)
{
for(x = 0; x < this->horizontalSize; x++)
{
Ray r = this->rayForPixel(x, y);
Tuple colour = world.colourAt(r, depth);
image.putPixel(x, y, colour);
}
}
return image;
}

70
source/canvas.cpp Normal file
View File

@@ -0,0 +1,70 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Canvas implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <canvas.h>
#include <lodepng.h>
#define BPP (24)
#define BytePP (BPP / 8)
#define MIN(_a, _b) ((_a)<(_b)?(_a):(_b))
#define MAX(_a, _b) ((_a)>(_b)?(_a):(_b))
Canvas::Canvas(uint32_t width, uint32_t height) : width(width), height(height)
{
this->bitmap = (uint8_t *)calloc(4, width * height);
this->stride = BytePP * width;
}
Canvas::Canvas(const Canvas &b)
{
this->width = b.width;
this->height = b.height;
this->stride = b.stride;
this->bitmap = (uint8_t *)calloc(4, b.width * b.height);
memcpy(this->bitmap, b.bitmap, 4 * b.width * b.height);
}
Canvas::Canvas(const Canvas *b)
{
this->width = b->width;
this->height = b->height;
this->stride = b->stride;
this->bitmap = (uint8_t *)calloc(4, b->width * b->height);
memcpy(this->bitmap, b->bitmap, 4 * b->width * b->height);
}
Canvas::~Canvas()
{
if (this->bitmap != nullptr)
{
free(this->bitmap);
}
}
void Canvas::putPixel(uint32_t x, uint32_t y, Tuple colour)
{
uint32_t offset = y * this->stride + x * BytePP;
this->bitmap[offset + 0] = MAX(MIN(colour.x * 255, 255), 0);
this->bitmap[offset + 1] = MAX(MIN(colour.y * 255, 255), 0);
this->bitmap[offset + 2] = MAX(MIN(colour.z * 255, 255), 0);
}
Colour Canvas::getPixel(uint32_t x, uint32_t y)
{
uint32_t offset = y * this->stride + x * BytePP;
return Colour(this->bitmap[offset + 0] / 255.0, this->bitmap[offset + 1] / 255.0, this->bitmap[offset + 2] / 255.0);
}
bool Canvas::SaveAsPNG(const char *filename)
{
uint32_t ret = lodepng_encode24_file(filename, this->bitmap, this->width, this->height);
return ret == 0;
}

10
source/colour.cpp Normal file
View File

@@ -0,0 +1,10 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Colour implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include "colour.h"

38
source/include/camera.h Normal file
View File

@@ -0,0 +1,38 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Camera header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_CAMERA_H
#define DORAYME_CAMERA_H
#include <matrix.h>
#include <stdint.h>
#include <ray.h>
#include <canvas.h>
#include <world.h>
class Camera
{
private:
double halfWidth;
double halfHeight;
public:
uint32_t verticalSize;
uint32_t horizontalSize;
double fieldOfView;
double pixelSize;
Matrix transformMatrix;
Matrix inverseTransform;
public:
Camera(uint32_t hsize, uint32_t vsize, double fov);
void setTransform(Matrix transform);
Ray rayForPixel(uint32_t pixelX, uint32_t pixelY);
Canvas render(World w, uint32_t depth = 5);
};
#endif /* DORAYME_CAMERA_H */

34
source/include/canvas.h Normal file
View File

@@ -0,0 +1,34 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Canvas header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_CANVAS_H
#define DORAYME_CANVAS_H
#include <stdint.h>
#include <colour.h>
class Canvas
{
private:
uint8_t *bitmap;
uint32_t stride;
public:
uint32_t width, height;
Canvas(uint32_t width, uint32_t height);
Canvas(const Canvas *c);
Canvas(const Canvas &c);
~Canvas();
void putPixel(uint32_t x, uint32_t y, Tuple c);
Colour getPixel(uint32_t x, uint32_t y);
bool SaveAsPNG(const char *filename);
};
#endif /* DORAYME_CANVAS_H */

32
source/include/colour.h Normal file
View File

@@ -0,0 +1,32 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Colour header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_COLOUR_H
#define DORAYME_COLOUR_H
#include <tuple.h>
class Colour : public Tuple
{
public:
Colour(double red, double green, double blue) : Tuple(red, green, blue, 0) {};
double red() { return this->x; };
double green() { return this->y; };
double blue() { return this->z; };
double red(double v) { this->x = v; return v; };
double green(double v) { this->y = v; return v; };
double blue(double v) { this->z = v; return v; };
using Tuple::operator*;
Colour operator*(const Colour &b) const { return Colour(this->x * b.x,
this->y * b.y,
this->z * b.z); };
};
#endif /* DORAYME_COLOUR_H */

28
source/include/cube.h Normal file
View File

@@ -0,0 +1,28 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Cube header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_CUBE_H
#define DORAYME_CUBE_H
#include <shape.h>
#include <ray.h>
#include <intersect.h>
class Cube : public Shape {
private:
void checkAxis(double axeOrigine, double axeDirection, double *axeMin, double *axeMax);
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);
public:
Cube() : Shape(SHAPE_CUBE) {};
};
#endif /* DORAYME_CUBE_H */

View File

@@ -0,0 +1,30 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Intersect header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_INTERSECT_H
#define DORAYME_INTERSECT_H
#include <stdint.h>
#include <intersection.h>
class Intersect
{
private:
Intersection **list;
uint32_t num;
uint32_t allocated;
public:
Intersect();
~Intersect();
void add(Intersection i);
int count() { return this->num; };
Intersection operator[](const int p) { return *this->list[p]; }
Intersection hit();
};
#endif /* DORAYME_INTERSECT_H */

View File

@@ -0,0 +1,82 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Intersection header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_INTERSECTION_H
#define DORAYME_INTERSECTION_H
#include <stdlib.h>
#include <ray.h>
class Shape;
class Intersect;
struct Computation
{
Computation(Shape *object, double t, Tuple point, Tuple eyev, Tuple normalv, Tuple overHitP,
bool inside, Tuple reflectV = Vector(0, 0, 0), double n1 = 1.0, double n2 = 1.0,
Tuple underHitP = Point(0, 0, 0)) :
object(object), t(t), hitPoint(point), eyeVector(eyev), normalVector(normalv), inside(inside),
overHitPoint(overHitP), underHitPoint(underHitP), reflectVector(reflectV), n1(n1), n2(n2) { };
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));
};
Shape *object;
double t;
Tuple hitPoint;
Tuple overHitPoint;
Tuple underHitPoint;
Tuple eyeVector;
Tuple normalVector;
Tuple reflectVector;
double n1;
double n2;
bool inside;
};
class Intersection
{
public:
double t;
Shape *object;
public:
Intersection(double t, Shape *object) : t(t), object(object) { };
bool nothing() { return (this->object == nullptr); };
Computation prepareComputation(Ray r, Intersect *xs = nullptr);
bool operator==(const Intersection &b) const { return ((this->t == b.t) && (this->object == b.object)); };
};
#endif /* DORAYME_INTERSECTION_H */

36
source/include/light.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Light header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_LIGHT_H
#define DORAYME_LIGHT_H
#include <tuple.h>
#include <colour.h>
enum LightType
{
POINT_LIGHT = 0,
};
class Light
{
public:
Colour intensity;
Tuple position;
LightType type;
public:
Light(LightType type = POINT_LIGHT, Tuple position=Point(0, 0, 0),
Colour intensity=Colour(1, 1, 1)) : type(type), position(position), intensity(intensity) { };
bool operator==(const Light &b) const { return this->intensity == b.intensity &&
this->position == b.position &&
this->type == b.type; };
};
#endif /* DORAYME_LIGHT_H */

112
source/include/list.h Normal file
View File

@@ -0,0 +1,112 @@
/*
* DoRayMe - a quick and dirty Raytracer
* List header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_LIST_H
#define DORAYME_LIST_H
#include <shape.h>
struct ChainList
{
Shape *shape;
ChainList *next;
};
class List
{
private:
ChainList *head;
ChainList *tail;
uint32_t count;
public:
List() : head(nullptr), tail(nullptr), count(0) { };
~List()
{
ChainList *p = this->head;
if (p == nullptr) { return; }
/* clear up the list */
}
Shape *last()
{
ChainList *p = this->tail;
if (p == nullptr) { return nullptr; }
return p->shape;
}
void remove(Shape *s)
{
ChainList *p = this->head;
if (p == nullptr) { return; }
if ((p->next == nullptr) && (p->shape == s))
{
/* First element */
this->tail = nullptr;
free(this->head);
this->head = nullptr;
this->count = 0;
return;
}
while(p->next != nullptr)
{
if (p->next->shape == s)
{
ChainList *found = p->next;
p->next = p->next->next;
free(found);
if (p->next == NULL) { this->tail = p; }
this->count --;
return;
}
p = p->next;
}
}
void append(Shape *s)
{
ChainList *theNew = (ChainList *)calloc(1, sizeof(ChainList));
theNew->shape = s;
ChainList *p = this->tail;
this->tail = theNew;
if (p != nullptr) { p->next = theNew; }
else { this->head = theNew; } /* If the tail is empty, it mean the list IS empty. */
this->count ++;
}
bool isEmpty()
{
return (this->count == 0);
}
bool doesInclude(Shape *s)
{
ChainList *p = this->head;
while(p != nullptr)
{
if (p->shape == s) { return true; }
p = p->next;
}
return false;
}
};
#endif //DORAYME_LIST_H

52
source/include/material.h Normal file
View File

@@ -0,0 +1,52 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Material header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_MATERIAL_H
#define DORAYME_MATERIAL_H
#include <tuple.h>
#include <colour.h>
#include <pattern.h>
#include <light.h>
class Shape;
class Material
{
public:
Colour colour;
double ambient;
double diffuse;
double specular;
double shininess;
double reflective;
double transparency;
double emissive;
double refractiveIndex;
Pattern *pattern;
public:
Material() : colour(Colour(1, 1, 1)), ambient(0.1), diffuse(0.9), specular(0.9), shininess(200),
reflective(0.0), transparency(0.0), emissive(0), refractiveIndex(1.0), pattern(nullptr) {};
Colour lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, Shape *hitObject, bool inShadow = false);
bool operator==(const Material &b) const { return double_equal(this->ambient, b.ambient) &&
double_equal(this->diffuse, b.diffuse) &&
double_equal(this->specular, b.specular) &&
double_equal(this->shininess, b.shininess) &&
double_equal(this->reflective, b.reflective) &&
double_equal(this->transparency, b.transparency) &&
double_equal(this->emissive, b.emissive) &&
double_equal(this->refractiveIndex, b.refractiveIndex) &&
(this->colour == b.colour); };
};
#endif /* DORAYME_MATERIAL_H */

View File

@@ -10,6 +10,15 @@
#ifndef DORAYME_MATH_HELPER_H #ifndef DORAYME_MATH_HELPER_H
#define DORAYME_MATH_HELPER_H #define DORAYME_MATH_HELPER_H
#include <math.h>
void set_equal_precision(double v);
double getEpsilon();
bool double_equal(double a, double b); bool double_equal(double a, double b);
#endif //DORAYME_MATH_HELPER_H double deg_to_rad(double deg);
double min3(double a, double b, double c);
double max3(double a, double b, double c);
#endif /* DORAYME_MATH_HELPER_H */

75
source/include/matrix.h Normal file
View File

@@ -0,0 +1,75 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Matrix header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_MATRIX_H
#define DORAYME_MATRIX_H
#include <tuple.h>
/* Some **** linux distro seems to define "minor" as a macro
* and wreak havoc.
* Let's make sure we are clean here
*/
#ifdef minor
#undef minor
#endif
class Matrix
{
protected:
/* 4x4 is the default */
double data[4*4];
int size;
public:
Matrix(int size = 4);
Matrix(double values[], int size);
double get(int x, int y) const { return this->data[this->size * x + y]; };
void set(int x, int y, double v) { this->data[this->size * x + y] = v; };
Matrix identity();
Matrix transpose();
double determinant();
Matrix submatrix(int row, int column);
Matrix inverse();
double minor(int row, int column) { return this->submatrix(row, column).determinant(); }
double cofactor(int row, int column) { return (((column+row)&1)?-1:1) * this->minor(row, column); }
bool operator==(const Matrix &b) const;
bool operator!=(const Matrix &b) const;
bool isInvertible() { return this->determinant() != 0; }
Matrix operator*(const Matrix &b) const;
Tuple operator*(const Tuple &b) const;
};
class Matrix4: public Matrix
{
public:
Matrix4() : Matrix(4) { };
Matrix4(double values[]) : Matrix(values, 4) { };
};
class Matrix3 : public Matrix
{
public:
Matrix3() : Matrix(3) { };
Matrix3(double values[]) : Matrix(values, 3) { };
};
class Matrix2 : public Matrix
{
private:
using Matrix::data;
public:
Matrix2() : Matrix(2) { };
Matrix2(double values[]) : Matrix(values, 2) { };
};
#endif /* DORAYME_MATRIX_H */

36
source/include/pattern.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Pattern header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_PATTERN_H
#define DORAYME_PATTERN_H
#include <colour.h>
#include <tuple.h>
#include <matrix.h>
class Shape;
class Pattern
{
public:
Colour a;
Colour b;
Matrix transformMatrix;
Matrix inverseTransform;
public:
Pattern(Colour a, Colour b);
virtual Colour patternAt(Tuple point) = 0;
void setTransform(Matrix transform);
Colour patternAtObject(Shape *object, Tuple point);
};
#endif /* DORAYME_PATTERN_H */

22
source/include/plane.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Plane header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_PLANE_H
#define DORAYME_PLANE_H
class Plane : public Shape
{
private:
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);
public:
Plane() : Shape(SHAPE_PLANE) { };
};
#endif //DORAYME_PLANE_H

25
source/include/ray.h Normal file
View File

@@ -0,0 +1,25 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Ray header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_RAY_H
#define DORAYME_RAY_H
#include <tuple.h>
class Ray
{
public:
Tuple direction;
Tuple origin;
Ray(Tuple origin, Tuple direction) : origin(origin), direction(direction) { };
Tuple position(double t) { return this->origin + this->direction * t; };
};
#endif /* DORAYME_RAY_H */

62
source/include/shape.h Normal file
View File

@@ -0,0 +1,62 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Object header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_SHAPE_H
#define DORAYME_SHAPE_H
class Shape;
#include <ray.h>
#include <tuple.h>
#include <matrix.h>
#include <intersect.h>
#include <material.h>
enum ShapeType
{
SHAPE_NONE,
SHAPE_SPHERE,
SHAPE_PLANE,
SHAPE_CUBE,
SHAPE_CONE,
};
/* Base class for all object that can be presented in the world */
class Shape
{
private:
ShapeType type;
private:
virtual Intersect localIntersect(Ray r) = 0;
virtual Tuple localNormalAt(Tuple point) = 0;
public:
Matrix transformMatrix;
Matrix inverseTransform;
Material material;
bool dropShadow;
public:
Shape(ShapeType = SHAPE_NONE);
Intersect intersect(Ray r);
Tuple normalAt(Tuple point);
void setTransform(Matrix transform);
void setMaterial(Material material) { this->material = material; };
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 &&
this->transformMatrix == b.transformMatrix; };
};
#endif /* DORAYME_SHAPE_H */

34
source/include/sphere.h Normal file
View File

@@ -0,0 +1,34 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Sphere header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_SPHERE_H
#define DORAYME_SPHERE_H
#include <shape.h>
#include <ray.h>
#include <intersect.h>
class Sphere : public Shape
{
private:
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);
public:
Sphere() : Shape(SHAPE_SPHERE) { };
/* All sphere are at (0, 0, 0) and radius 1 in the object space */
};
/* Mostly for test purposes */
class GlassSphere : public Sphere
{
public:
GlassSphere() : Sphere() { this->material.transparency = 1.0; this->material.refractiveIndex = 1.5; };
};
#endif /* DORAYME_SPHERE_H */

View File

@@ -0,0 +1,28 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Test shape header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_TESTSHAPE_H
#define DORAYME_TESTSHAPE_H
#include <shape.h>
#include <ray.h>
#include <tuple.h>
class TestShape : public Shape
{
private:
Intersect localIntersect(Ray r);
Tuple localNormalAt(Tuple point);
public:
Ray localRay;
TestShape();
};
#endif //DORAYME_TESTSHAPE_H

View File

@@ -0,0 +1,26 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Transformation header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_TRANSFORMATION_H
#define DORAYME_TRANSFORMATION_H
#include <matrix.h>
Matrix translation(double x, double y, double z);
Matrix scaling(double x, double y, double z);
Matrix rotationX(double angle);
Matrix rotationY(double angle);
Matrix rotationZ(double angle);
Matrix shearing(double Xy, double Xx, double Yx, double Yz, double Zx, double Zy);
Matrix viewTransform(Tuple from, Tuple to, Tuple up);
#endif /* DORAYME_TRANSFORMATION_H */

View File

@@ -6,8 +6,8 @@
* Copyright (c) 2020 986-Studio. * Copyright (c) 2020 986-Studio.
* *
*/ */
#ifndef DORAYME_TUPLES_H #ifndef DORAYME_TUPLE_H
#define DORAYME_TUPLES_H #define DORAYME_TUPLE_H
#include <math_helper.h> #include <math_helper.h>
@@ -41,6 +41,8 @@ public:
double magnitude(); double magnitude();
Tuple normalise(); Tuple normalise();
double dot(const Tuple &b); double dot(const Tuple &b);
Tuple cross(const Tuple &b) const;
Tuple reflect(const Tuple &normal);
}; };
class Point: public Tuple class Point: public Tuple
@@ -53,7 +55,6 @@ class Vector: public Tuple
{ {
public: public:
Vector(double x, double y, double z) : Tuple(x, y, z, 0.0) {}; Vector(double x, double y, double z) : Tuple(x, y, z, 0.0) {};
Vector cross(const Vector &b) const;
}; };
#endif /* DORAYME_TUPLES_H */ #endif /* DORAYME_TUPLE_H */

56
source/include/world.h Normal file
View File

@@ -0,0 +1,56 @@
/*
* DoRayMe - a quick and dirty Raytracer
* World header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_WORLD_H
#define DORAYME_WORLD_H
#include <stdint.h>
#include <light.h>
#include <shape.h>
#include <intersect.h>
#include <ray.h>
class World
{
public:
uint32_t objectCount;
uint32_t lightCount;
private:
uint32_t allocatedObjectCount;
uint32_t allocatedLightCount;
Light* *lightList;
Shape* *objectList;
public:
World();
~World();
void addObject(Shape *s);
void addLight(Light *l);
/* Some debug things */
bool lightIsIn(Light &l);
bool objectIsIn(Shape &s);
Shape *getObject(int i) { return this->objectList[i]; };
Light *getLight(int i) { return this->lightList[i]; };
Tuple shadeHit(Computation comps, uint32_t depthCount = 4);
Tuple colourAt(Ray r, uint32_t depthCount = 4);
bool isShadowed(Tuple point, uint32_t light = 0);
Colour reflectColour(Computation comps, uint32_t depthCount = 4);
Colour refractedColour(Computation comps, uint32_t depthCount = 4);
Intersect intersect(Ray r);
};
#endif /* DORAYME_WORLD_H */

View File

@@ -0,0 +1,53 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Worldbuilder header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_WORLDBUILDER_H
#define DORAYME_WORLDBUILDER_H
#include <world.h>
#include <camera.h>
/* Let's keep a single header for now, will see later */
class DefaultWorld : public World
{
public:
DefaultWorld();
};
/* Not implemented yet */
class Hw3File : public World
{
private:
Matrix transformStack[50];
uint32_t transStackCount;
public:
double currentAmbient;
double currentShininess;
double currentSpecular;
double currentDiffuse;
double currentEmission;
double currentReflective;
double currentTransparency;
double currentRefIndex;
Colour currentColour;
Matrix cam;
double camFoV;
public:
Matrix getTransformMatrix();
void popTransformMatrix();
void pushTransformMatrix();
void applyTransformMatrix(Matrix t);
Hw3File(const char *filename);
};
#endif /* DORAYME_WORLDBUILDER_H */

66
source/intersect.cpp Normal file
View File

@@ -0,0 +1,66 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Intersect implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <stdlib.h>
#include <math_helper.h>
#include <intersect.h>
#include <float.h>
#define MIN_ALLOC (2)
Intersect::Intersect()
{
this->allocated = MIN_ALLOC;
this->list = (Intersection **)calloc(sizeof(Intersection *), MIN_ALLOC);
this->num = 0;
}
Intersect::~Intersect()
{
/* Free stuff */
}
void Intersect::add(Intersection i)
{
Intersection *x;
int j, k;
if ((this->num + 1) > this->allocated)
{
this->allocated *= 2;
this->list = (Intersection **)realloc(this->list, sizeof(Intersection *) * this->allocated);
}
this->list[this->num++] = new Intersection(i.t, i.object);
/* Now sort.. */
for(j = 1; j < (this->num); j++)
{
x = this->list[j];
k = j;
while( (k > 0) && (this->list[k - 1]->t) > x->t )
{
this->list[k] = this->list[k - 1];
k--;
}
this->list[k] = x;
}
}
Intersection Intersect::hit()
{
int i;
for(i = 0; i < this->num; i++)
{
if (this->list[i]->t >= 0)
return *this->list[i];
}
return Intersection(0, nullptr);
}

82
source/intersection.cpp Normal file
View File

@@ -0,0 +1,82 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Intersection implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <intersection.h>
#include <shape.h>
#include <list.h>
Computation Intersection::prepareComputation(Ray r, Intersect *xs)
{
double n1 = 1.0;
double n2 = 1.0;
Tuple hitP = r.position(this->t);
Tuple normalV = this->object->normalAt(hitP);
Tuple eyeV = -r.direction;
bool inside = false;
if (normalV.dot(eyeV) < 0)
{
inside = true;
normalV = -normalV;
}
Tuple overHitP = hitP + normalV * getEpsilon();
Tuple underHitP = hitP - normalV * getEpsilon();
Tuple reflectV = r.direction.reflect(normalV);
if (xs != nullptr)
{
List containers;
int j, k;
for(j = 0; j < xs->count(); j++)
{
Intersection i = (*xs)[j];
if (*this == i)
{
if (!containers.isEmpty())
{
n1 = containers.last()->material.refractiveIndex;
}
}
if (containers.doesInclude(i.object))
{
containers.remove(i.object);
}
else
{
containers.append(i.object);
}
if (*this == i)
{
if (!containers.isEmpty())
{
n2 = containers.last()->material.refractiveIndex;
}
/* End the loop */
break;
}
}
}
return Computation(this->object,
this->t,
hitP,
eyeV,
normalV,
overHitP,
inside,
reflectV,
n1,
n2,
underHitP);
}

View File

@@ -11,7 +11,62 @@
#include <float.h> #include <float.h>
#include <math_helper.h> #include <math_helper.h>
static double current_precision = FLT_EPSILON;
void set_equal_precision(double v)
{
current_precision = v;
}
double getEpsilon()
{
return current_precision;
}
bool double_equal(double a, double b) bool double_equal(double a, double b)
{ {
return fabs(a - b) < DBL_EPSILON; return fabs(a - b) < current_precision;
} }
double deg_to_rad(double deg)
{
return deg * M_PI / 180.;
}
double min3(double a, double b, double c)
{
if (a <= b)
{
if (c < a) return c;
return a;
}
if (b <= a)
{
if (c < b) return c;
return b;
}
if (c <= a)
{
if (b < c) return b;
}
return c;
}
double max3(double a, double b, double c)
{
if (a >= b)
{
if (c > a) return c;
return a;
}
if (b >= a)
{
if (c > b) return c;
return b;
}
if (c >= a)
{
if (b > c) return b;
}
return c;
}

201
source/matrix.cpp Normal file
View File

@@ -0,0 +1,201 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Matrix implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <stdio.h>
#include <matrix.h>
#include <tuple.h>
#include <math_helper.h>
Matrix::Matrix(int width)
{
int i;
this->size = width;
for(i = 0; i < width*width; i++)
{
this->data[i] = 0;
}
};
Matrix::Matrix(double values[], int width)
{
int x, y;
this->size = width;
for(y = 0; y < this->size; y++)
{
for (x = 0 ; x < this->size ; x++)
{
this->data[this->size * x + y] = values[this->size * x + y];
}
}
};
bool Matrix::operator==(const Matrix &b) const
{
int i;
if (this->size != b.size)
{
/* If they are not the same size don't even bother */
return false;
}
for(i = 0; i < this->size*this->size; i++)
{
if (!double_equal(this->data[i], b.data[i]))
{
return false;
}
}
return true;
}
bool Matrix::operator!=(const Matrix &b) const
{
int i;
if (this->size != b.size)
{
/* If they are not the same size don't even bother */
return true;
}
for(i = 0; i < this->size*this->size; i++)
{
if (!double_equal(this->data[i], b.data[i]))
{
return true;
}
}
return false;
}
Matrix Matrix::operator*(const Matrix &b) const
{
int x, y, k;
Matrix ret = Matrix(this->size);
if (this->size == b.size)
{
for (y = 0 ; y < this->size ; y++)
{
for (x = 0 ; x < this->size ; x++)
{
double v = 0;
for (k = 0 ; k < this->size ; k++)
{
v += this->get(x, k) * b.get(k, y);
}
ret.set(x, y, v);
}
}
}
return ret;
}
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));
}
Matrix Matrix::identity()
{
int i;
for(i = 0; i < this->size; i++)
{
this->set(i, i, 1);
}
return *this;
}
Matrix Matrix::transpose()
{
int x, y;
Matrix ret = Matrix(this->size);
for (y = 0 ; y < this->size ; y++)
{
for (x = 0 ; x < this->size ; x++)
{
ret.set(y, x, this->get(x, y));
}
}
return ret;
}
Matrix Matrix::submatrix(int row, int column)
{
int i, j;
int x = 0, y = 0;
Matrix ret = Matrix(this->size - 1);
for (i = 0 ; i < this->size ; i++)
{
if (i == row)
{
/* Skip that row */
continue;
}
y = 0;
for (j = 0 ; j < this->size ; j++)
{
if (j == column)
{
/* skip that column */
continue;
}
ret.set(x, y, this->get(i, j));
y++;
}
x++;
}
return ret;
}
double Matrix::determinant()
{
double det = 0;
if (this->size == 2)
{
det = this->data[0] * this->data[3] - this->data[1] * this->data[2];
}
else
{
int col;
for(col = 0; col < this->size; col++)
{
det = det + this->get(0, col) * this->cofactor(0, col);
}
}
return det;
}
Matrix Matrix::inverse()
{
Matrix ret = Matrix(this->size);
if (this->isInvertible())
{
int row, col;
double c;
for (row = 0; row < this->size; row++)
{
for (col = 0; col < this->size; col++)
{
c = this->cofactor(row, col);
ret.set(col, row, c / this->determinant());
}
}
}
return ret;
}

31
source/pattern.cpp Normal file
View File

@@ -0,0 +1,31 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Pattern implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <pattern.h>
#include <shape.h>
Pattern::Pattern(Colour a, Colour b): a(a), b(b)
{
this->transformMatrix = Matrix4().identity();
this->inverseTransform = this->transformMatrix.inverse();
};
Colour Pattern::patternAtObject(Shape *object, Tuple worldPoint)
{
Tuple objectPoint = object->inverseTransform * worldPoint;
Tuple patternPoint = this->inverseTransform * objectPoint;
return this->patternAt(patternPoint);
}
void Pattern::setTransform(Matrix transform)
{
this->transformMatrix = transform;
this->inverseTransform = transform.inverse();
}

View File

@@ -0,0 +1,25 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Checkers Pattern header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_CHECKERSPATTERN_H
#define DORAYME_CHECKERSPATTERN_H
class CheckersPattern : public Pattern
{
public:
CheckersPattern(Colour a, Colour b) : Pattern(a, b) { };
Colour patternAt(Tuple point)
{
double value = floor(point.x) + floor(point.y) + floor(point.z);
return (fmod(value, 2) == 0)?this->a:this->b;
}
};
#endif /* DORAYME_CHECKERSPATTERN_H */

View File

@@ -0,0 +1,30 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Gradient Pattern header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_GRADIENTPATTERN_H
#define DORAYME_GRADIENTPATTERN_H
#include <pattern.h>
class GradientPattern : public Pattern
{
public:
GradientPattern(Colour a, Colour b) : Pattern(a, b) { };
Colour patternAt(Tuple point)
{
Tuple distance = this->b - this->a;
double fraction = point.x - floor(point.x);
Tuple ret = this->a + distance * fraction;
return Colour(ret.x, ret.y, ret.z);
}
};
#endif /* DORAYME_GRADIENTPATTERN_H */

View File

@@ -0,0 +1,30 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Ring Pattern header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_RINGSUPPORT_H
#define DORAYME_RINGSUPPORT_H
#include <pattern.h>
class RingPattern : public Pattern
{
public:
RingPattern(Colour a, Colour b) : Pattern(a, b) { };
Colour patternAt(Tuple point)
{
double squared = (point.x * point.x) + (point.z * point.z);
double value = floor(sqrt(squared));
return (fmod(value, 2) == 0)?this->a:this->b;
}
};
#endif /* DORAYME_RINGSUPPORT_H */

View File

@@ -0,0 +1,32 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Strip Pattern header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_STRIPPATTERN_H
#define DORAYME_STRIPPATTERN_H
#include <pattern.h>
#include <stdio.h>
class StripPattern : public Pattern
{
public:
StripPattern(Colour a, Colour b) : Pattern(a, b) { };
Colour patternAt(Tuple point)
{
if (fmod(floor(point.x), 2) == 0)
{
return this->a;
}
return this->b;
}
};
#endif /* DORAYME_STRIPPATTERN_H */

View File

@@ -0,0 +1,27 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Strip Pattern header
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#ifndef DORAYME_TESTPATTERN_H
#define DORAYME_TESTPATTERN_H
#include <pattern.h>
#include <stdio.h>
class TestPattern : public Pattern
{
public:
TestPattern() : Pattern(Colour(0, 0, 0), Colour(1, 1, 1)) { };
Colour patternAt(Tuple point)
{
return Colour(point.x, point.y, point.z);
}
};
#endif /* DORAYME_TESTPATTERN_H */

76
source/shapes/cube.cpp Normal file
View File

@@ -0,0 +1,76 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Cube implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <tuple.h>
#include <ray.h>
#include <shape.h>
#include <cube.h>
#include <math_helper.h>
void Cube::checkAxis(double axeOrigin, double axeDirection, double *axeMin, double *axeMax)
{
double tMinNumerator = (-1 - axeOrigin);
double tMaxNumerator = (1 - axeOrigin);
if (fabs(axeDirection) >= getEpsilon())
{
*axeMin = tMinNumerator / axeDirection;
*axeMax = tMaxNumerator / axeDirection;
}
else
{
*axeMin = tMinNumerator * INFINITY;
*axeMax = tMaxNumerator * INFINITY;
}
if (*axeMin > *axeMax)
{
double swap = *axeMax;
*axeMax = *axeMin;
*axeMin = swap;
}
}
Intersect Cube::localIntersect(Ray r)
{
Intersect ret;
double xtMin, xtMax, ytMin, ytMax, ztMin, ztMax;
double tMin, tMax;
this->checkAxis(r.origin.x, r.direction.x, &xtMin, &xtMax);
this->checkAxis(r.origin.y, r.direction.y, &ytMin, &ytMax);
this->checkAxis(r.origin.z, r.direction.z, &ztMin, &ztMax);
tMin = max3(xtMin, ytMin, ztMin);
tMax = min3(xtMax, ytMax, ztMax);
if (tMin <= tMax)
{
ret.add(Intersection(tMin, this));
ret.add(Intersection(tMax, this));
}
return ret;
}
Tuple Cube::localNormalAt(Tuple point)
{
double maxC = max3(fabs(point.x), fabs(point.y), fabs(point.z));
if (maxC == fabs(point.x))
{
return Vector(point.x, 0, 0);
}
else if (maxC == fabs(point.y))
{
return Vector(0, point.y, 0);
}
return Vector(0, 0, point.z);
}

8
source/shapes/light.cpp Normal file
View File

@@ -0,0 +1,8 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Light implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/

View File

@@ -0,0 +1,69 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Material implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <tuple.h>
#include <material.h>
#include <colour.h>
#include <shape.h>
Colour Material::lighting(Light light, Tuple point, Tuple eyeVector, Tuple normalVector, Shape *hitObject, bool inShadow)
{
Colour pointColor = this->colour;
if (this->pattern != nullptr)
{
pointColor = this->pattern->patternAtObject(hitObject, point);
}
Tuple lightVector = (light.position - point).normalise();
Tuple reflectVector = Tuple(0, 0, 0, 0);
Tuple effectiveColour = pointColor * light.intensity;
Tuple ambientColour = Colour(0, 0, 0);
Tuple emissiveColour = Colour(0, 0, 0);
Tuple diffuseColour = Colour(0, 0, 0);
Tuple specularColour = Colour(0, 0, 0);
Tuple finalColour = Colour(0, 0, 0);
double lightDotNormal, reflectDotEye;
ambientColour = effectiveColour * this->ambient;
emissiveColour = pointColor * this->emissive;
if (!inShadow)
{
lightDotNormal = lightVector.dot(normalVector);
if (lightDotNormal < 0)
{
diffuseColour = Colour(0, 0, 0);
specularColour = Colour(0, 0, 0);
}
else
{
diffuseColour = effectiveColour * this->diffuse * lightDotNormal;
reflectVector = -lightVector.reflect(normalVector);
reflectDotEye = reflectVector.dot(eyeVector);
if (reflectDotEye < 0)
{
specularColour = Colour(0, 0, 0);
}
else
{
double factor = pow(reflectDotEye, this->shininess);
specularColour = light.intensity * this->specular * factor;
}
}
}
finalColour = emissiveColour + ambientColour + diffuseColour + specularColour;
return Colour(finalColour.x, finalColour.y, finalColour.z);
}

36
source/shapes/plane.cpp Normal file
View File

@@ -0,0 +1,36 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Plane implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <tuple.h>
#include <ray.h>
#include <shape.h>
#include <plane.h>
#include <math_helper.h>
Intersect Plane::localIntersect(Ray r)
{
double t;
Intersect ret = Intersect();
if (fabs(r.direction.y) < getEpsilon())
{
/* With a direction == 0, the ray can't intersect the plane */
return ret;
}
t = -r.origin.y / r.direction.y;
ret.add(Intersection(t, this));
return ret;
}
Tuple Plane::localNormalAt(Tuple point)
{
return Vector(0, 1, 0);
}

8
source/shapes/ray.cpp Normal file
View File

@@ -0,0 +1,8 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Ray implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/

47
source/shapes/shape.cpp Normal file
View File

@@ -0,0 +1,47 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Object implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <ray.h>
#include <shape.h>
#include <matrix.h>
#include <tuple.h>
#include <intersect.h>
Shape::Shape(ShapeType type)
{
this->dropShadow = true;
this->type = type;
this->transformMatrix = Matrix4().identity();
this->inverseTransform = this->transformMatrix.inverse();
}
Intersect Shape::intersect(Ray r)
{
return this->localIntersect(this->invTransform(r));
};
Tuple Shape::normalAt(Tuple point)
{
Tuple local_point = this->inverseTransform * point;
Tuple local_normal = this->localNormalAt(local_point);
Tuple world_normal = this->inverseTransform.transpose() * local_normal;
/* W may get wrong, so hack it. This is perfectly normal as we are using a 4x4 matrix instead of a 3x3 */
world_normal.w = 0;
return world_normal.normalise();
}
void Shape::setTransform(Matrix transform)
{
this->transformMatrix = transform;
this->inverseTransform = transform.inverse();
}

41
source/shapes/sphere.cpp Normal file
View File

@@ -0,0 +1,41 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Sphere implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <math.h>
#include <sphere.h>
#include <ray.h>
#include <tuple.h>
#include <intersect.h>
Intersect Sphere::localIntersect(Ray r)
{
Intersect ret;
double a, b, c, discriminant;
Tuple sphere_to_ray = r.origin - Point(0, 0, 0);
a = r.direction.dot(r.direction);
b = 2 * r.direction.dot(sphere_to_ray);
c = sphere_to_ray.dot(sphere_to_ray) - 1;
discriminant = b * b - 4 * a * c;
if (discriminant >= 0)
{
ret.add(Intersection((-b - sqrt(discriminant)) / (2 * a), this));
ret.add(Intersection((-b + sqrt(discriminant)) / (2 * a), this));
}
return ret;
}
Tuple Sphere::localNormalAt(Tuple point)
{
return (point - Point(0, 0, 0)).normalise();
}

View File

@@ -0,0 +1,25 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Test shape implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <shape.h>
#include <testshape.h>
TestShape::TestShape() : localRay(Point(0, 0, 0), Vector(0, 0, 0))
{
}
Intersect TestShape::localIntersect(Ray r)
{
this->localRay = r;
return Intersect();
}
Tuple TestShape::localNormalAt(Tuple point)
{
return Vector(point.x, point.y, point.z);
}

99
source/transformation.cpp Normal file
View File

@@ -0,0 +1,99 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Transformation implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <math.h>
#include <transformation.h>
Matrix translation(double x, double y, double z)
{
Matrix ret = Matrix4().identity();
ret.set(0, 3, x);
ret.set(1, 3, y);
ret.set(2, 3, z);
return ret;
}
Matrix scaling(double x, double y, double z)
{
Matrix ret = Matrix4();
ret.set(0, 0, x);
ret.set(1, 1, y);
ret.set(2, 2, z);
ret.set(3, 3, 1);
return ret;
}
Matrix rotationX(double angle)
{
Matrix ret = Matrix4().identity();
ret.set(1, 1, cos(angle));
ret.set(1, 2, -sin(angle));
ret.set(2, 1, sin(angle));
ret.set(2, 2, cos(angle));
return ret;
}
Matrix rotationY(double angle)
{
Matrix ret = Matrix4().identity();
ret.set(0, 0, cos(angle));
ret.set(0, 2, sin(angle));
ret.set(2, 0, -sin(angle));
ret.set(2, 2, cos(angle));
return ret;
}
Matrix rotationZ(double angle)
{
Matrix ret = Matrix4().identity();
ret.set(0, 0, cos(angle));
ret.set(0, 1, -sin(angle));
ret.set(1, 0, sin(angle));
ret.set(1, 1, cos(angle));
return ret;
}
Matrix shearing(double Xy, double Xz, double Yx, double Yz, double Zx, double Zy)
{
Matrix ret = Matrix4().identity();
ret.set(0, 1, Xy);
ret.set(0, 2, Xz);
ret.set(1, 0, Yx);
ret.set(1, 2, Yz);
ret.set(2, 0, Zx);
ret.set(2, 1, Zy);
return ret;
}
Matrix viewTransform(Tuple from, Tuple to, Tuple up)
{
Tuple forward = (to - from).normalise();
Tuple left = forward.cross(up.normalise());
Tuple true_up = left.cross(forward);
double orientationValues[] = { left.x, left.y, left.z, 0,
true_up.x, true_up.y, true_up.z, 0,
-forward.x, -forward.y, -forward.z, 0,
0, 0, 0, 1 };
Matrix orientation = Matrix4(orientationValues);
return orientation * translation(-from.x, -from.y, -from.z);
}

View File

@@ -6,7 +6,7 @@
* Copyright (c) 2020 986-Studio. * Copyright (c) 2020 986-Studio.
* *
*/ */
#include <tuples.h> #include <tuple.h>
#include <math.h> #include <math.h>
@@ -27,9 +27,15 @@ double Tuple::dot(const Tuple &b)
return this->x * b.x + this->y * b.y + this->z * b.z + this->w * b.w; return this->x * b.x + this->y * b.y + this->z * b.z + this->w * b.w;
} }
Vector Vector::cross(const Vector &b) const Tuple Tuple::cross(const Tuple &b) const
{ {
return Vector(this->y * b.z - this->z * b.y, return Tuple(this->y * b.z - this->z * b.y,
this->z * b.x - this->x * b.z, this->z * b.x - this->x * b.z,
this->x * b.y - this->y * b.x); this->x * b.y - this->y * b.x,
0);
}
Tuple Tuple::reflect(const Tuple &normal)
{
return *this - normal * 2 * this->dot(normal);
} }

195
source/world.cpp Normal file
View File

@@ -0,0 +1,195 @@
/*
* DoRayMe - a quick and dirty Raytracer
* World implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <shape.h>
#define MIN_ALLOC (2)
World::World() : objectCount(0), lightCount(0)
{
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;
};
World::~World()
{
/* We need to do some cleanup... */
}
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;
}
void World::addLight(Light *l)
{
if ((this->lightCount + 1) > this->allocatedLightCount)
{
this->allocatedLightCount *= 2;
this->lightList = (Light **)realloc(this->lightList, sizeof(Light **) * this->allocatedLightCount);
}
this->lightList[this->lightCount++] = l;
}
bool World::lightIsIn(Light &l)
{
int i;
for(i = 0; i < this->lightCount; i++)
{
if (*this->lightList[i] == l)
{
return true;
}
}
return false;
}
bool World::objectIsIn(Shape &s)
{
int i;
for(i = 0; i < this->objectCount; i++)
{
if (*this->objectList[i] == s)
{
return true;
}
}
return false;
}
Intersect World::intersect(Ray r)
{
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;
}
Tuple World::shadeHit(Computation comps, uint32_t depthCount)
{
uint32_t lightIndex;
Tuple surface = Colour(0, 0, 0);
for(lightIndex = 0; lightIndex < this->lightCount; lightIndex++)
{
bool isThereAnObstacle = this->isShadowed(comps.overHitPoint, lightIndex);
surface = surface + comps.object->material.lighting(*this->lightList[lightIndex], comps.overHitPoint, comps.eyeVector,
comps.normalVector, comps.object, isThereAnObstacle);
}
Tuple reflected = this->reflectColour(comps, depthCount);
Tuple refracted = this->refractedColour(comps, depthCount);
if ((comps.object->material.reflective > 0) && (comps.object->material.transparency > 0))
{
double reflectance = comps.schlick();
return surface + reflected * reflectance + refracted * (1 - reflectance);
}
return surface + reflected + refracted;
}
Tuple World::colourAt(Ray r, uint32_t depthCount)
{
Intersect allHits = this->intersect(r);
Intersection hit = allHits.hit();
if (hit.nothing())
{
return Colour(0, 0, 0);
}
else
{
return this->shadeHit(hit.prepareComputation(r, &allHits), depthCount);
}
}
bool World::isShadowed(Tuple point, uint32_t light)
{
Tuple v = this->lightList[light]->position - point;
double distance = v.magnitude();
Tuple direction = v.normalise();
Ray r = Ray(point, direction);
Intersect xs = this->intersect(r);
int i;
for(i = 0; i < xs.count(); i++)
{
Intersection h = xs[i];
if (h.t < 0) continue;
if ((h.object->dropShadow == true) && (h.t < distance))
{
return true;
}
}
return false;
}
Colour World::reflectColour(Computation comps, uint32_t depthCount)
{
if ((depthCount == 0) || (comps.object->material.reflective == 0))
{
return Colour(0, 0, 0);
}
/* So it is reflective, even just a bit. Let'sr reflect the ray! */
Ray reflectedRay = Ray(comps.overHitPoint, comps.reflectVector);
Tuple hitColour = this->colourAt(reflectedRay, depthCount - 1);
hitColour = hitColour * comps.object->material.reflective;
return Colour(hitColour.x, hitColour.y, hitColour.z);
}
Colour World::refractedColour(Computation comps, uint32_t depthCount)
{
double nRatio = comps.n1 / comps.n2;
double cos_i = comps.eyeVector.dot(comps.normalVector);
double sin2_t = (nRatio*nRatio) * (1 - cos_i * cos_i);
if ((sin2_t > 1 ) || (depthCount == 0) || (comps.object->material.transparency == 0))
{
return Colour(0, 0, 0);
}
double cos_t = sqrt(1.0 - sin2_t);
Tuple direction = comps.normalVector * (nRatio * cos_i - cos_t) - comps.eyeVector * nRatio;
Ray refractedRay = Ray(comps.underHitPoint, direction);
Tuple hitColour = this->colourAt(refractedRay, depthCount - 1) * comps.object->material.transparency;
return Colour(hitColour.x, hitColour.y, hitColour.z);
}

View File

@@ -0,0 +1,34 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Default World builder implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <worldbuilder.h>
#include <world.h>
#include <sphere.h>
#include <light.h>
#include <material.h>
#include <transformation.h>
DefaultWorld::DefaultWorld()
{
Light *l = new Light(POINT_LIGHT, Point(-10, 10, -10), Colour(1, 1, 1));
Sphere *s1 = new Sphere();
Sphere *s2 = new Sphere();
Material s1Mat = Material();
s1Mat.colour = Colour(0.8, 1.0, 0.6);
s1Mat.diffuse = 0.7;
s1Mat.specular = 0.2;
s1->setMaterial(s1Mat);
s2->setTransform(scaling(0.5, 0.5,0.5));
this->addLight(l);
this->addObject(s1);
this->addObject(s2);
}

View File

@@ -0,0 +1,346 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Hw3file implementation
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
/* Don't build for now */
/* This file is parsing a text format from another raytracer I made in the past. */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <worldbuilder.h>
#include <sphere.h>
#include <matrix.h>
#include <transformation.h>
#define IS_CMD(_cmd) (strncmp(buffer, _cmd, sizeof(_cmd)) == 0)
typedef void (*cmdFunc)(Hw3File *w, uint32_t curLine, double argv[15]);
typedef struct cmd_def
{
const char *name;
int8_t numparam; /* -1 == one string, else >0 = number of argv */
cmdFunc f;
} cmd_def;
static void cmdCamera (Hw3File *w, uint32_t curLine, double argv[15])
{
w->cam = viewTransform(Point(argv[0], argv[1], argv[2]),
Point(argv[3], argv[4], argv[5]),
Vector(argv[6], argv[7], argv[8]));
w->camFoV = deg_to_rad(argv[9]);
}
/* 0: X, 1: Y, 2: Z
* 3: Radius
*/
static void cmdSphere (Hw3File *w, uint32_t curLine, double argv[15])
{
printf("Adding a sphere...\n");
/* Instanciate a sphere */
Sphere *shape = new Sphere();
Matrix pos = translation(argv[0], argv[1], argv[2]) * scaling(argv[3], argv[3], argv[3]);
shape->setTransform(w->getTransformMatrix() * pos);
shape->material.ambient = w->currentAmbient;
shape->material.reflective = w->currentReflective;
shape->material.shininess = w->currentShininess;
shape->material.specular = w->currentSpecular;
shape->material.diffuse = w->currentDiffuse;
shape->material.colour = w->currentColour;
shape->material.transparency = w->currentTransparency;
shape->material.refractiveIndex = w->currentRefIndex;
w->addObject(shape);
}
static void cmdCube (Hw3File *w, uint32_t curLine, double argv[15])
{
}
static void cmdTrans (Hw3File *w, uint32_t curLine, double argv[15])
{
Matrix m = translation(argv[0], argv[1], argv[2]);
w->applyTransformMatrix(m);
}
static void cmdRotate (Hw3File *w, uint32_t curLine, double argv[15])
{
Matrix m = Matrix4().identity();
if (argv[2] != 0)
{
m = m * rotationZ(argv[3]);
}
if (argv[1] != 0)
{
m = m * rotationY(argv[3]);
}
if (argv[0] != 0)
{
m = m * rotationX(argv[3]);
}
w->applyTransformMatrix(m);
}
static void cmdScale (Hw3File *w, uint32_t curLine, double argv[15])
{
Matrix m = scaling(argv[0], argv[1], argv[2]);
w->applyTransformMatrix(m);
}
static void cmdPushT (Hw3File *w, uint32_t curLine, double argv[15])
{
w->pushTransformMatrix();
}
static void cmdPopT (Hw3File *w, uint32_t curLine, double argv[15])
{
w->popTransformMatrix();
}
static void cmdLPoint (Hw3File *w, uint32_t curLine, double argv[15])
{
/* create point light */
Light *l = new Light(POINT_LIGHT, Point(argv[0], argv[1], argv[2]), Colour(argv[3], argv[4], argv[5]));
w->addLight(l);
}
static void cmdAmbient (Hw3File *w, uint32_t curLine, double argv[15])
{
//w->currentAmbient = (argv[0] + argv[1] + argv[2]) / 3;
w->currentColour = Colour(argv[0], argv[1], argv[2]);
}
static void cmdColour (Hw3File *w, uint32_t curLine, double argv[15])
{
w->currentColour = Colour(argv[0], argv[1], argv[2]);
}
static void cmdDiffuse (Hw3File *w, uint32_t curLine, double argv[15])
{
w->currentDiffuse = (argv[0] + argv[1] + argv[2]) / 3;
}
static void cmdSpecular (Hw3File *w, uint32_t curLine, double argv[15])
{
w->currentSpecular = (argv[0] + argv[1] + argv[2]) / 3;
}
static void cmdShine (Hw3File *w, uint32_t curLine, double argv[15])
{
w->currentReflective = argv[0];
}
static void cmdEmission (Hw3File *w, uint32_t curLine, double argv[15])
{
w->currentEmission = (argv[0] + argv[1] + argv[2]) / 3;
}
#if 0
static void cmdLDire (Hw3File *w, uint32_t curLine, double argv[15])
{
/* create directional light */
light *cur = new light();
cur->type = LIGHT_DIRECTIONAL;
cur->attenuation = sc->attenuation;
cur->position.set(argv[0], argv[1], argv[2]);
cur->lightcolor.set(argv[3], argv[4], argv[5]);
sc->l[sc->l_count] = cur;
sc->l_count++;
}
static void cmdAtten (Hw3File *w, uint32_t curLine, double argv[15])
{
sc->attenuation = Vector(argv[0], argv[1], argv[2]);
}
static void cmdMaxVerts (Hw3File *w, uint32_t curLine, double argv[15])
{
sc->vertexCount = (uint32_t) argv[0];
sc->vertex = new point[sc->vertexCount];
sc->curVertex = 0;
}
static void cmdMaxVertN (Hw3File *w, uint32_t curLine, double argv[15])
{
/* ignore for now */
}
static void cmdVertex (Hw3File *w, uint32_t curLine, double argv[15])
{
sc->vertex[sc->curVertex].set(argv[0], argv[1], argv[2]);
sc->curVertex++;
}
static void cmdVertexN (Hw3File *w, uint32_t curLine, double argv[15])
{
/* ignore for now */
}
static void cmdTri (Hw3File *w, uint32_t curLine, double argv[15])
{
/* arg are vertex numbers */
triangle *tr = new triangle(sc->vertex[(int)argv[0]], sc->vertex[(int)argv[1]], sc->vertex[(int)argv[2]], sc->getMatrix());
tr->ambient = sc->ambient;
tr->diffuse = sc->diffuse;
tr->specular = sc->specular;
tr->emission = sc->emission;
tr->shininess = sc->shininess;
tr->sourceLine = curLine;
sc->o[sc->o_count] = tr;
sc->o_count++;
}
#endif
static cmd_def commandList[] =
{
/* Camera */
{"camera", 10, cmdCamera},
/* Geometry */
{"sphere", 4, cmdSphere},
//{ "maxverts", 1, cmdMaxVerts },
//{ "maxvertnorms", 1, cmdMaxVertN },
//{ "vertex", 3, cmdVertex },
//{ "vertexnormal", 6, cmdVertexN },
//{ "tri", 3, cmdTri },
//{ "trinormal", 3, cmdTriN },
//{ "cube", 1, cmdCube },
/* transformation */
{"translate", 3, cmdTrans},
{"rotate", 4, cmdRotate},
{"scale", 3, cmdScale},
{"pushTransform", 0, cmdPushT},
{"popTransform", 0, cmdPopT},
/* Lights */
//{ "directional", 6, cmdLDire },
{"point", 6, cmdLPoint},
//{ "attenuation", 3, cmdAtten },
/* Materials */
{"color", 3, cmdColour},
{"ambient", 3, cmdAmbient},
{"diffuse", 3, cmdDiffuse},
{"specular", 3, cmdSpecular},
{"shininess", 1, cmdShine},
{"emission", 3, cmdEmission},
};
#define CMD_COUNT (sizeof(commandList) / sizeof(cmd_def))
Hw3File::Hw3File(const char *filename) : transStackCount(0), currentColour(Colour(1, 1, 1)),
currentEmission(0), currentShininess(200), currentAmbient(0.1),
currentDiffuse(0.9), currentSpecular(0.9), camFoV(0)
{
FILE *fp;
this->popTransformMatrix();
fp = fopen(filename, "rt");
if (fp != NULL)
{
char buffer[512];
int line = 0;
while(!feof(fp))
{
uint32_t i;
line++;
memset(buffer, 0, 512);
fgets(buffer, 512, fp);
if ((buffer[0] == '#') || (strlen(buffer) < 2))
{
continue; /* Ingore empty line or commented lines */
}
for (i = 0; i < CMD_COUNT; i++)
{
if (strncmp(buffer, commandList[i].name, strlen(commandList[i].name)) == 0)
{
char first[512];
double value[15];
if (commandList[i].numparam != 0)
{
size_t j;
int k = 0, l = 0;
char buff[512];
for(j = strlen(commandList[i].name); j < strlen(buffer); j++)
{
if (!isspace(buffer[j]))
{
buff[l++] = buffer[j];
}
else
{
buff[l] = 0;
l = 0;
if (k == 0)
{
strcpy(first, buff);
}
if (strlen(buff) > 0)
{
value[k++] = atof(buff);
}
}
}
if (k != abs(commandList[i].numparam))
{
printf("line %d malformed: given %d parameter, expected %d\n%s", line, k, abs(commandList[i].numparam), buffer);
goto exit;
}
}
commandList[i].f(this, line, value);
break;
}
}
}
}
exit:
return;
}
Matrix Hw3File::getTransformMatrix()
{
return this->transformStack[this->transStackCount];
}
void Hw3File::popTransformMatrix()
{
if (this->transStackCount == 0)
{
this->transformStack[0] = Matrix4().identity();
}
else
{
this->transformStack[this->transStackCount] = Matrix4().identity();
this->transStackCount --;
}
}
void Hw3File::pushTransformMatrix()
{
this->transStackCount ++;
this->transformStack[this->transStackCount] = this->transformStack[this->transStackCount - 1];
}
void Hw3File::applyTransformMatrix(Matrix t)
{
this->transformStack[this->transStackCount] = this->transformStack[this->transStackCount] * t;
}

View File

@@ -3,10 +3,78 @@ project(DoRayTested)
set(THREADS_PREFER_PTHREAD_FLAG ON) set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
set(TESTS_SRC tuples_test.cpp) set(TESTS_SRC math_test.cpp tuple_test.cpp colour_test.cpp canvas_test.cpp matrix_test.cpp transformation_test.cpp
ray_test.cpp intersect_test.cpp sphere_test.cpp light_test.cpp material_test.cpp world_test.cpp camera_test.cpp
shape_test.cpp plane_test.cpp pattern_test.cpp cube_test.cpp)
add_executable(testMyRays) add_executable(testMyRays)
target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) target_include_directories(testMyRays PUBLIC ${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR})
target_include_directories(testMyRays PUBLIC ../source/include) target_include_directories(testMyRays PUBLIC ../source/include)
target_sources(testMyRays PRIVATE ${TESTS_SRC}) target_sources(testMyRays PRIVATE ${TESTS_SRC})
target_link_libraries(testMyRays gtest gtest_main rayonnement Threads::Threads) target_link_libraries(testMyRays gtest gtest_main rayonnement Threads::Threads)
gtest_discover_tests(testMyRays
WORKING_DIRECTORY ${PROJECT_DIR}
PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${PROJECT_DIR}"
)
add_executable(hw3render)
target_include_directories(hw3render PUBLIC ../source/include)
target_sources(hw3render PRIVATE hw3render.cpp)
target_link_libraries(hw3render rayonnement)
add_executable(ch5_test)
target_include_directories(ch5_test PUBLIC ../source/include)
target_sources(ch5_test PRIVATE ch5_test.cpp)
target_link_libraries(ch5_test rayonnement)
add_executable(ch6_test)
target_include_directories(ch6_test PUBLIC ../source/include)
target_sources(ch6_test PRIVATE ch6_test.cpp)
target_link_libraries(ch6_test rayonnement)
add_executable(ch7_test)
target_include_directories(ch7_test PUBLIC ../source/include)
target_sources(ch7_test PRIVATE ch7_test.cpp)
target_link_libraries(ch7_test rayonnement)
add_executable(ch9_test)
target_include_directories(ch9_test PUBLIC ../source/include)
target_sources(ch9_test PRIVATE ch9_test.cpp)
target_link_libraries(ch9_test rayonnement)
add_executable(ch10_test)
target_include_directories(ch10_test PUBLIC ../source/include)
target_sources(ch10_test PRIVATE ch10_test.cpp)
target_link_libraries(ch10_test rayonnement)
add_executable(ch11_reflection)
target_include_directories(ch11_reflection PUBLIC ../source/include)
target_sources(ch11_reflection PRIVATE ch11_reflection.cpp)
target_link_libraries(ch11_reflection rayonnement)
add_executable(ch11_refraction)
target_include_directories(ch11_refraction PUBLIC ../source/include)
target_sources(ch11_refraction PRIVATE ch11_refraction.cpp)
target_link_libraries(ch11_refraction rayonnement)
add_executable(ch11_test)
target_include_directories(ch11_test PUBLIC ../source/include)
target_sources(ch11_test PRIVATE ch11_test.cpp)
target_link_libraries(ch11_test rayonnement)
add_executable(ch12_test)
target_include_directories(ch12_test PUBLIC ../source/include)
target_sources(ch12_test PRIVATE ch12_test.cpp)
target_link_libraries(ch12_test rayonnement)
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>)
add_test(NAME Chapter09_Test COMMAND $<TARGET_FILE:ch9_test>)
add_test(NAME Chapter10_Test COMMAND $<TARGET_FILE:ch10_test>)
add_test(NAME Chapter11_Reflection COMMAND $<TARGET_FILE:ch11_reflection>)
add_test(NAME Chapter11_Refraction COMMAND $<TARGET_FILE:ch11_refraction>)
add_test(NAME Chapter11_Test COMMAND $<TARGET_FILE:ch11_test>)
add_test(NAME Chapter12_Test COMMAND $<TARGET_FILE:ch12_test>)

111
tests/camera_test.cpp Normal file
View File

@@ -0,0 +1,111 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Camera unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <camera.h>
#include <math.h>
#include <math_helper.h>
#include <matrix.h>
#include <tuple.h>
#include <ray.h>
#include <world.h>
#include <canvas.h>
#include <colour.h>
#include <worldbuilder.h>
#include <transformation.h>
#include <stdint.h>
#include <gtest/gtest.h>
TEST(CameraTest, Constructing_a_camera)
{
uint32_t hsize = 160;
uint32_t vsize = 120;
double field_of_view = M_PI / 2;
Camera c = Camera(hsize, vsize, field_of_view);
ASSERT_EQ(c.horizontalSize, 160);
ASSERT_EQ(c.verticalSize, 120);
ASSERT_TRUE(double_equal(c.fieldOfView, M_PI / 2));
ASSERT_EQ(c.transformMatrix, Matrix4().identity());
}
TEST(CameraTest, Pixel_size_for_a_horizontal_canvas)
{
Camera c = Camera(200, 125, M_PI / 2);
ASSERT_TRUE(double_equal(c.pixelSize, 0.01));
}
TEST(CameraTest, Pixel_size_for_a_vertical_canvas)
{
Camera c = Camera(125, 200, M_PI / 2);
ASSERT_TRUE(double_equal(c.pixelSize, 0.01));
}
TEST(CameraTest, Constructing_a_ray_through_the_center_of_the_canvas)
{
Camera c = Camera(201, 101, M_PI / 2);
Ray r = c.rayForPixel(100, 50);
ASSERT_EQ(r.origin, Point(0, 0, 0));
ASSERT_EQ(r.direction, Vector(0, 0, -1));
}
TEST(CameraTest, Constructing_a_ray_through_a_corner_of_the_canvas)
{
Camera c = Camera(201, 101, M_PI / 2);
Ray r = c.rayForPixel(0, 0);
ASSERT_EQ(r.origin, Point(0, 0, 0));
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(r.direction, Vector(0.66519, 0.33259, -0.66851));
set_equal_precision(FLT_EPSILON);
}
TEST(CameraTest, Constructing_a_ray_when_the_camera_is_transformed)
{
Camera c = Camera(201, 101, M_PI / 2);
c.setTransform(rotationY(M_PI / 4) * translation(0, -2, 5));
Ray r = c.rayForPixel(100, 50);
ASSERT_EQ(r.origin, Point(0, 2, -5));
ASSERT_EQ(r.direction, Vector(sqrt(2)/2, 0, -sqrt(2)/2));
}
TEST(CameraTest, Rendering_a_world_with_a_camera)
{
World w = DefaultWorld();
Camera c = Camera(11, 11, M_PI / 2);
Tuple from = Point(0, 0, -5);
Tuple to = Point(0, 0, 0);
Tuple up = Vector(0, 1, 0);
c.setTransform(viewTransform(from, to, up));
Canvas image = c.render(w);
/* Temporary lower the precision */
/* We need to lower a lot as Canvas is not keeping the
* floating point value, but a value between 0 and 255 per channel,
* as it is storing the actual frame buffer, so there is a more big different
* between the value.
*/
set_equal_precision(0.005);
Colour col = image.getPixel(5, 5);
ASSERT_EQ(col, Colour(0.38066, 0.47583, 0.2855));
set_equal_precision(FLT_EPSILON);
}

71
tests/canvas_test.cpp Normal file
View File

@@ -0,0 +1,71 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Canvas unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <colour.h>
#include <canvas.h>
#include <math.h>
#include <gtest/gtest.h>
TEST(CanvasTest, Creating_a_canvas)
{
Canvas c = Canvas(10, 20);
int x, y;
ASSERT_EQ(c.width, 10);
ASSERT_EQ(c.height, 20);
for(y = 0; y < 20; y++)
{
for(x = 0; x < 10; x++)
{
ASSERT_EQ(c.getPixel(x, y), Colour(0, 0, 0));
}
}
}
TEST(CanvasTest, Test_Writing_pixels_to_a_canvas_Test)
{
Canvas c = Canvas(10, 20);
Colour red = Colour(1, 0, 0);
c.putPixel(2, 3, red);
ASSERT_EQ(c.getPixel(2, 3), red);
}
TEST(CanvasTest, Save_a_PNG_file)
{
Canvas c = Canvas(5, 3);
Colour c1 = Colour(1.5, 0, 0);
Colour c2 = Colour(0, 0.5, 0);
Colour c3 = Colour(-0.5, 0, 1);
c.putPixel(0, 0, c1);
c.putPixel(2, 1, c2);
c.putPixel(4, 2, c3);
ASSERT_TRUE(c.SaveAsPNG("Save_a_PNG_file.png"));
}
TEST(CanvasTest, Create_a_canvas_from_another_using_reference)
{
Canvas c = Canvas(100, 100);
Canvas copy = Canvas(c);
ASSERT_EQ(c.width, copy.width);
}
TEST(CanvasTest, Create_a_canvas_from_another_using_pointer)
{
Canvas c = Canvas(100, 100);
Canvas copy = Canvas(&c);
ASSERT_EQ(c.width, copy.width);
}

94
tests/ch10_test.cpp Normal file
View File

@@ -0,0 +1,94 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for chapter 10
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <plane.h>
#include <material.h>
#include <colour.h>
#include <canvas.h>
#include <camera.h>
#include <pattern.h>
#include <strippattern.h>
#include <gradientpattern.h>
#include <checkerspattern.h>
#include <ringpattern.h>
#include <transformation.h>
int main()
{
/* First we need to construct the world */
Plane floor = Plane();
floor.material.specular = 0;
floor.material.pattern = new RingPattern(Colour(1, 0.9, 0.9), Colour(1, 0.2, 0.2));
Plane wall = Plane();
wall.material.specular = 0;
wall.material.pattern = new StripPattern(Colour(1, 0.9, 0.9), Colour(1, 0.2, 0.2));
wall.material.pattern->setTransform(translation(0, 0, 1) * rotationY(M_PI/4));
wall.setTransform(translation(0, 0, 5) * rotationX(M_PI/2));
Sphere middle = Sphere();
middle.setTransform(translation(-0.7, 1, 0.6));
middle.material.diffuse = 0.7;
middle.material.specular = 0.3;
middle.material.pattern = new StripPattern(Colour(0.1, 1, 0.5), Colour(0, 0.2, 0.2));
middle.material.pattern->setTransform((rotationZ(M_PI/4) * rotationY(M_PI/5) * scaling(0.2, 0.2, 0.2)));
Sphere right = Sphere();
right.setTransform(translation(1.5, 0.5, -0.5) * scaling(0.5, 0.5, 0.5));
right.material.diffuse = 0.7;
right.material.specular = 0.3;
right.material.pattern = new StripPattern(Colour(0.5, 1, 0.1), Colour(0, 0, 0));
right.material.pattern->setTransform((scaling(0.1, 0.1, 0.1)));
Sphere left = Sphere();
left.setTransform(translation(-1.5, 0.33, -0.75) * scaling(0.33, 0.33, 0.33));
left.material.diffuse = 0.7;
left.material.specular = 0.3;
left.material.pattern = new GradientPattern(Colour(1, 0.8, 0.1), Colour(0.1, 0.1, 1));
left.material.pattern->setTransform(translation(1.5, 0, 0) * scaling(2.1, 2, 2) * rotationY(-M_PI/4));
Sphere fourth = Sphere();
fourth.setTransform(translation(.5, 0.25, 0.4) * scaling(0.3, 0.3, 0.3));
fourth.material.diffuse = 0.7;
fourth.material.specular = 0.3;
fourth.material.pattern = new CheckersPattern(Colour(0.1, 0.8, 0.1), Colour(0.8, 1, 0.8));
fourth.material.pattern->setTransform( scaling(0.2, 0.2, 0.2));
World w = World();
w.addObject(&floor);
w.addObject(&wall);
w.addObject(&middle);
w.addObject(&left);
w.addObject(&right);
w.addObject(&fourth);
/* Add light */
Light light = Light(POINT_LIGHT, Point(-10, 10, -10), Colour(1, 1, 1));
w.addLight(&light);
/* Set the camera */
Camera camera = Camera(100, 50, M_PI / 3);
camera.setTransform(viewTransform(Point(0, 1.5, -5),
Point(0, 1, 0),
Vector(0, 1, 0)));
/* Now render it */
Canvas image = camera.render(w);
image.SaveAsPNG("ch10_test.png");
return 0;
}

117
tests/ch11_reflection.cpp Normal file
View File

@@ -0,0 +1,117 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for reflection in chapter 11.
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <plane.h>
#include <material.h>
#include <colour.h>
#include <canvas.h>
#include <camera.h>
#include <pattern.h>
#include <strippattern.h>
#include <gradientpattern.h>
#include <checkerspattern.h>
#include <ringpattern.h>
#include <transformation.h>
int main()
{
/* First we need to construct the world */
Plane floor = Plane();
floor.material.specular = 0;
floor.material.pattern = new RingPattern(Colour(1, 0.9, 0.9), Colour(1, 0.2, 0.2));
floor.material.reflective = 0.1;
Plane wall = Plane();
wall.material.specular = 0;
wall.material.pattern = new StripPattern(Colour(1, 0.9, 0.9), Colour(1, 0.2, 0.2));
wall.material.pattern->setTransform(translation(0, 0, 1) * rotationY(M_PI/4));
wall.setTransform(translation(0, 0, 5) * rotationX(M_PI/2));
Sphere middle = Sphere();
middle.setTransform(translation(-0.7, 1, 0.6));
middle.material.diffuse = 0.7;
middle.material.specular = 0.3;
middle.material.pattern = new StripPattern(Colour(0.1, 1, 0.5), Colour(0, 0.2, 0.2));
middle.material.pattern->setTransform((rotationZ(M_PI/4) * rotationY(M_PI/5) * scaling(0.2, 0.2, 0.2)));
Sphere right = Sphere();
right.setTransform(translation(1.5, 0.5, -0.5) * scaling(0.5, 0.5, 0.5));
right.material.diffuse = 0.7;
right.material.specular = 0.3;
right.material.pattern = new StripPattern(Colour(0.5, 1, 0.1), Colour(0, 0, 0));
right.material.pattern->setTransform((scaling(0.1, 0.1, 0.1)));
right.material.reflective = 0.1;
Sphere left = Sphere();
left.setTransform(translation(-1.5, 0.33, -0.75) * scaling(0.33, 0.33, 0.33));
left.material.diffuse = 0.7;
left.material.specular = 0.3;
left.material.pattern = new GradientPattern(Colour(1, 0.8, 0.1), Colour(0.1, 0.1, 1));
left.material.pattern->setTransform(translation(1.5, 0, 0) * scaling(2.1, 2, 2) * rotationY(-M_PI/4));
Sphere fourth = Sphere();
fourth.setTransform(translation(.5, 0.25, 0.4) * scaling(0.3, 0.3, 0.3));
fourth.material.diffuse = 0.7;
fourth.material.specular = 0.3;
fourth.material.pattern = new CheckersPattern(Colour(0.1, 0.8, 0.1), Colour(0.8, 1, 0.8));
fourth.material.pattern->setTransform( scaling(0.2, 0.2, 0.2));
fourth.material.reflective = 0.4;
World w = World();
w.addObject(&floor);
w.addObject(&wall);
w.addObject(&middle);
w.addObject(&left);
w.addObject(&right);
w.addObject(&fourth);
/* Add some more reflective spheres */
Sphere ref1 = Sphere();
ref1.setTransform(translation(1, 1, .4) * scaling(0.2, 0.2, 0.2));
ref1.material.reflective = 1;
ref1.material.colour = Colour(0.3, 0.7, 0.6);
w.addObject(&ref1);
Sphere ref2 = Sphere();
ref2.setTransform(translation(1.5, 2, -.8) * scaling(0.2, 0.2, 0.2));
ref2.material.reflective = 1;
ref2.material.specular = 0.5;
ref2.material.colour = Colour(0.3, 0.3, 0.3);
w.addObject(&ref2);
Sphere ref3 = Sphere();
ref3.setTransform(translation(-2, 1.678, .4) * scaling(0.4, 0.4, 0.4));
ref3.material.reflective = 1;
ref3.material.specular = 0.5;
w.addObject(&ref3);
/* Add light */
Light light = Light(POINT_LIGHT, Point(-10, 10, -10), Colour(1, 1, 1));
w.addLight(&light);
/* Set the camera */
Camera camera = Camera(100, 50, M_PI / 3);
camera.setTransform(viewTransform(Point(0, 1.5, -5),
Point(0, 1, 0),
Vector(0, 1, 0)));
/* Now render it */
Canvas image = camera.render(w);
image.SaveAsPNG("ch11_reflection.png");
return 0;
}

71
tests/ch11_refraction.cpp Normal file
View File

@@ -0,0 +1,71 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for reflection in chapter 11.
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <plane.h>
#include <material.h>
#include <colour.h>
#include <canvas.h>
#include <camera.h>
#include <pattern.h>
#include <strippattern.h>
#include <gradientpattern.h>
#include <checkerspattern.h>
#include <ringpattern.h>
#include <transformation.h>
int main()
{
World w = World();
/* First we need to construct the world */
Plane floor = Plane();
floor.material.pattern = new CheckersPattern(Colour(1, 1, 1), Colour(0, 0, 0));
floor.setTransform(translation(0, -10, 0));
w.addObject(&floor);
Sphere glassBall = Sphere();
glassBall.material.shininess = 300;
glassBall.material.transparency = 1;
glassBall.material.reflective = 1;
glassBall.material.refractiveIndex = 1.52;
glassBall.material.diffuse = 0.1;
w.addObject(&glassBall);
Sphere airBall = Sphere();
airBall.setTransform(scaling(0.5, 0.5, 0.5));
airBall.material.shininess = 300;
airBall.material.transparency = 1;
airBall.material.reflective = 1;
airBall.material.refractiveIndex = 1.0009;
airBall.material.diffuse = 0.1;
w.addObject(&airBall);
/* Add light */
Light light = Light(POINT_LIGHT, Point(20, 10, 0), Colour(0.7, 0.7, 0.7));
w.addLight(&light);
/* Set the camera */
Camera camera = Camera(100, 100, M_PI / 3);
camera.setTransform(viewTransform(Point(0, 2.5, 0),
Point(0, 0, 0),
Vector(1, 0, 0)));
/* Now render it */
Canvas image = camera.render(w);
image.SaveAsPNG("ch11_refraction.png");
return 0;
}

160
tests/ch11_test.cpp Normal file
View File

@@ -0,0 +1,160 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for reflection in chapter 11.
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <plane.h>
#include <material.h>
#include <colour.h>
#include <canvas.h>
#include <camera.h>
#include <pattern.h>
#include <strippattern.h>
#include <gradientpattern.h>
#include <checkerspattern.h>
#include <ringpattern.h>
#include <transformation.h>
int main()
{
World w = World();
Material wallMaterial = Material();
wallMaterial.pattern = new StripPattern(Colour(0.45, 0.45, 0.45), Colour(0.55, 0.55, 0.55));
wallMaterial.pattern->setTransform( scaling(0.25, 0.25, 0.25) * rotationY(1.5708));
wallMaterial.ambient = 0;
wallMaterial.diffuse = 0.4;
wallMaterial.specular = 0;
wallMaterial.reflective = 0.3;
/* ----------------------------- */
/* The flood */
Plane floor = Plane();
floor.setTransform(rotationY(0.31415));
floor.material.pattern = new CheckersPattern(Colour(0.35, 0.35, 0.35), Colour(0.65, 0.65, 0.65));
floor.material.specular = 0;
floor.material.reflective = 0.4;
w.addObject(&floor);
/* the ceiling */
Plane ceiling = Plane();
ceiling.setTransform(translation(0, 5, 0));
ceiling.material.colour = Colour(0.8, 0.8, 0.8);
ceiling.material.ambient = 0.3;
ceiling.material.specular = 0;
w.addObject(&ceiling);
/* West wall */
Plane westWall = Plane();
westWall.setTransform( translation(-5, 0, 0) * rotationZ(1.5708) * rotationY(1.5708));
westWall.setMaterial(wallMaterial);
w.addObject(&westWall);
/* east wall */
Plane eastWall = Plane();
eastWall.setTransform( translation(5, 0, 0) * rotationZ(1.5708) * rotationY(1.5708));
eastWall.setMaterial(wallMaterial);
w.addObject(&eastWall);
/* north wall */
Plane northWall = Plane();
northWall.setTransform( translation(0, 0, 5) * rotationX(1.5708));
northWall.setMaterial(wallMaterial);
w.addObject(&northWall);
/* south wall */
Plane southWall = Plane();
southWall.setTransform( translation(0, 0, -5) * rotationX(1.5708));
southWall.setMaterial(wallMaterial);
w.addObject(&southWall);
/* ----------------------------- */
/* Background balls */
Sphere bg1 = Sphere();
bg1.setTransform(translation(4.6, 0.4, 1) * scaling(0.4, 0.4, 0.4));
bg1.material.colour = Colour(0.8, 0.5, 0.3);
bg1.material.shininess = 50;
w.addObject(&bg1);
Sphere bg2 = Sphere();
bg2.setTransform(translation(4.7, 0.3, 0.4) * scaling(0.3, 0.3, 0.3));
bg2.material.colour = Colour(0.9, 0.4, 0.5);
bg2.material.shininess = 50;
w.addObject(&bg2);
Sphere bg3 = Sphere();
bg3.setTransform(translation(-1, 0.5, 4.5) * scaling(0.5, 0.5, 0.5));
bg3.material.colour = Colour(0.4, 0.9, 0.6);
bg3.material.shininess = 50;
w.addObject(&bg3);
Sphere bg4 = Sphere();
bg4.setTransform(translation(-1.7, 0.3, 4.7) * scaling(0.3, 0.3, 0.3));
bg4.material.colour = Colour(0.4, 0.6, 0.9);
bg4.material.shininess = 50;
w.addObject(&bg4);
/* Forground balls */
/* Red sphere */
Sphere redBall = Sphere();
redBall.setTransform(translation(-0.6, 1, 0.6));
redBall.material.colour = Colour(1, 0.3, 0.2);
redBall.material.shininess = 5;
redBall.material.specular = 0.4;
w.addObject(&redBall);
/* blue glass ball */
Sphere blueGlassBall = Sphere();
blueGlassBall.setTransform(translation(0.6, 0.7, -0.6) * scaling(0.7, 0.7, 0.7));
blueGlassBall.material.colour = Colour(0, 0, 0.2);
blueGlassBall.material.ambient = 0;
blueGlassBall.material.diffuse = 0.4;
blueGlassBall.material.specular = 0.9;
blueGlassBall.material.shininess = 300;
blueGlassBall.material.transparency = 0.9;
blueGlassBall.material.refractiveIndex = 1.5;
w.addObject(&blueGlassBall);
/* green glass ball */
Sphere greenGlassBall = Sphere();
greenGlassBall.setTransform(translation(-0.7, 0.5, -0.8) * scaling(0.5, 0.5, 0.5));
greenGlassBall.material.colour = Colour(0, 0.2, 0);
greenGlassBall.material.ambient = 0;
greenGlassBall.material.diffuse = 0.4;
greenGlassBall.material.specular = 0.9;
greenGlassBall.material.shininess = 300;
greenGlassBall.material.transparency = 0.9;
greenGlassBall.material.refractiveIndex = 1.5;
w.addObject(&greenGlassBall);
/* ----------------------------- */
/* Add light */
Light light = Light(POINT_LIGHT, Point(-4.9, 4.9, -1), Colour(1, 1, 1));
w.addLight(&light);
/* Set the camera */
Camera camera = Camera(400, 200, 1.152);
camera.setTransform(viewTransform(Point(-2.6, 1.5, -3.9),
Point(-0.6, 1, -0.8),
Vector(0, 1, 0)));
/* Now render it */
Canvas image = camera.render(w);
image.SaveAsPNG("ch11_test.png");
return 0;
}

213
tests/ch12_test.cpp Normal file
View File

@@ -0,0 +1,213 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for reflection in chapter 12.
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <plane.h>
#include <cube.h>
#include <material.h>
#include <colour.h>
#include <canvas.h>
#include <camera.h>
#include <pattern.h>
#include <strippattern.h>
#include <gradientpattern.h>
#include <checkerspattern.h>
#include <ringpattern.h>
#include <transformation.h>
int main()
{
World w = World();
/* Add light */
Light light = Light(POINT_LIGHT, Point(0, 6.9, -5), Colour(1, 1, 0.9));
w.addLight(&light);
/* ----------------------------- */
/* The floor / ceiling */
Cube floor = Cube();
floor.setTransform( scaling(20, 7, 20) * translation(0, 1, 0));
floor.material.pattern = new CheckersPattern(Colour(0, 0, 0), Colour(0.25, 0.25, 0.25));
floor.material.pattern->setTransform(scaling(0.07, 0.07, 0.07));
floor.material.ambient = 0.25;
floor.material.diffuse = 0.7;
floor.material.specular = 0.9;
floor.material.shininess = 300;
floor.material.reflective = 0.1;
w.addObject(&floor);
/* Walls */
Cube walls = Cube();
walls.setTransform(scaling(10, 10, 10));
walls.material.pattern = new CheckersPattern(Colour( 0.4863, 0.3765, 0.2941), Colour(0.3725, 0.2902, 0.2275 ));
walls.material.pattern->setTransform(scaling(0.05, 20, 0.05));
walls.material.ambient = 0.1;
walls.material.diffuse = 0.7;
walls.material.specular = 0.9;
walls.material.shininess = 300;
walls.material.reflective = 0.1;
w.addObject(&walls);
/* Table top */
Cube tableTop = Cube();
tableTop.setTransform(translation(0, 3.1, 0) * scaling(3, 0.1, 2));
tableTop.material.pattern = new StripPattern(Colour(0.5529, 0.4235, 0.3255), Colour(0.6588, 0.5098, 0.4000 ));
tableTop.material.pattern->setTransform(scaling(0.05, 0.05, 0.05) * rotationY(0.1));
tableTop.material.ambient = 0.1;
tableTop.material.diffuse = 0.7;
tableTop.material.specular = 0.9;
tableTop.material.shininess = 300;
tableTop.material.reflective = 0.2;
w.addObject(&tableTop);
/* Leg 1 */
Cube leg1 = Cube();
leg1.setTransform(translation(2.7, 1.5, -1.7) * scaling(0.1, 1.5, 0.1));
leg1.material.colour = Colour(0.5529, 0.4235, 0.3255);
leg1.material.ambient = 0.2;
leg1.material.diffuse = 0.7;
w.addObject(&leg1);
/* Leg 2 */
Cube leg2 = Cube();
leg2.setTransform(translation(2.7, 1.5, 1.7) * scaling(0.1, 1.5, 0.1));
leg2.material.colour = Colour(0.5529, 0.4235, 0.3255);
leg2.material.ambient = 0.2;
leg2.material.diffuse = 0.7;
w.addObject(&leg2);
/* Leg 3 */
Cube leg3 = Cube();
leg3.setTransform(translation(-2.7, 1.5, -1.7) * scaling(0.1, 1.5, 0.1));
leg3.material.colour = Colour(0.5529, 0.4235, 0.3255);
leg3.material.ambient = 0.2;
leg3.material.diffuse = 0.7;
w.addObject(&leg3);
/* Leg 4 */
Cube leg4 = Cube();
leg4.setTransform(translation(-2.7, 1.5, 1.7) * scaling(0.1, 1.5, 0.1));
leg4.material.colour = Colour(0.5529, 0.4235, 0.3255);
leg4.material.ambient = 0.2;
leg4.material.diffuse = 0.7;
w.addObject(&leg4);
/* ----------------------------- */
/* Glass cube */
Cube glassCube = Cube();
glassCube.setTransform(translation(0, 3.45001, 0) * rotationY(0.2) * scaling(0.25, 0.25, 0.25));
glassCube.dropShadow = false;
glassCube.material.colour = Colour(1, 1, 0.8);
glassCube.material.ambient = 0;
glassCube.material.diffuse = 0.3;
glassCube.material.specular = 0.9;
glassCube.material.shininess = 300;
glassCube.material.reflective = 0.7;
glassCube.material.transparency = 0.7;
glassCube.material.refractiveIndex = 1.5;
w.addObject(&glassCube);
/* Little cube 1 */
Cube lilCube1 = Cube();
lilCube1.setTransform(translation(1, 3.25, -0.9) *
rotationY(-0.4) *
scaling(0.15, 0.15, 0.15));
lilCube1.material.colour = Colour(1, 0.5, 0.5);
lilCube1.material.reflective = 0.6;
lilCube1.material.diffuse = 0.4;
w.addObject(&lilCube1);
/* Little cube 2 */
Cube lilCube2 = Cube();
lilCube2.setTransform(translation(-1.5, 3.27, 0.3) *
rotationY(0.4) *
scaling(0.15, 0.07, 0.15));
lilCube2.material.colour = Colour(1, 1, 0.5);
w.addObject(&lilCube2);
/* Little cube 3 */
Cube lilCube3 = Cube();
lilCube3.setTransform(translation(0, 3.25, 1) *
rotationY(0.4) *
scaling(0.2, 0.05, 0.05));
lilCube3.material.colour = Colour(0.5, 1, 0.5);
w.addObject(&lilCube3);
/* Little cube 4 */
Cube lilCube4 = Cube();
lilCube4.setTransform(translation(-0.6, 3.4, -1) *
rotationY(0.8) *
scaling(0.05, 0.2, 0.05));
lilCube4.material.colour = Colour(0.5, 0.5, 1);
w.addObject(&lilCube4);
/* Little cube 5 */
Cube lilCube5 = Cube();
lilCube5.setTransform(translation(2, 3.4, 1) *
rotationY(0.8) *
scaling(0.05, 0.2, 0.05));
lilCube5.material.colour = Colour(0.5, 1, 1);
w.addObject(&lilCube5);
/* ----------------------------- */
/* Frame 1 */
Cube frame1 = Cube();
frame1.setTransform(translation(-10, 4, 1) * scaling(0.05, 1, 1));
frame1.material.colour = Colour(0.7098, 0.2471, 0.2196);
frame1.material.diffuse = 0.6;
w.addObject(&frame1);
/* Frame 2 */
Cube frame2 = Cube();
frame2.setTransform(translation(-10, 3.4, 2.7) * scaling(0.05, 0.4, 0.4));
frame2.material.colour = Colour(0.2667, 0.2706, 0.6902);
frame2.material.diffuse = 0.6;
w.addObject(&frame2);
/* Frame 3 */
Cube frame3 = Cube();
frame3.setTransform(translation(-10, 4.6, 2.7) * scaling(0.05, 0.4, 0.4));
frame3.material.colour = Colour(0.3098, 0.5961, 0.3098);
frame3.material.diffuse = 0.6;
w.addObject(&frame3);
/* ----------------------------- */
/* Mirror */
Cube mirror = Cube();
mirror.setTransform(translation(-2, 3.5, 9.95) * scaling(4.8, 1.4, 0.06));
mirror.material.colour = Colour(0, 0, 0);
mirror.material.diffuse = 0;
mirror.material.ambient = 0;
mirror.material.specular = 1;
mirror.material.shininess = 300;
mirror.material.reflective = 1;
w.addObject(&mirror);
/* ----------------------------- */
/* Set the camera */
Camera camera = Camera(400, 200, 0.785);
camera.setTransform(viewTransform(Point(8, 6, -8),
Point(0, 3, 0),
Vector(0, 1, 0)));
/* Now render it */
Canvas image = camera.render(w, 4);
image.SaveAsPNG("ch12_test.png");
return 0;
}

46
tests/ch5_test.cpp Normal file
View File

@@ -0,0 +1,46 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for chapter 5 "Put it together".
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <ray.h>
#include <sphere.h>
#include <colour.h>
#include <canvas.h>
#include <transformation.h>
int main()
{
int x, y;
Canvas c = Canvas(100, 100);
Sphere s = Sphere();
Colour red = Colour(1, 0, 0);
Point cameraOrigin = Point(0, 0, -5);
double wallDistance = 10;
double wallSize = 7;
double pixelSize = wallSize / c.width;
for(y = 0; y < c.height; y++)
{
double worldY = (wallSize / 2) - pixelSize * y;
for(x = 0; x < c.width; x++)
{
double worldX = -(wallSize / 2) + pixelSize * x;
Point position = Point(worldX, worldY, wallDistance);
Ray r = Ray(cameraOrigin, (position - cameraOrigin).normalise());
Intersect xs = s.intersect(r);
if (!xs.hit().nothing())
{
c.putPixel(x, y, red);
}
}
}
c.SaveAsPNG("ch5_test.png");
return 0;
}

57
tests/ch6_test.cpp Normal file
View File

@@ -0,0 +1,57 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for chapter 6 "Put it together".
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <ray.h>
#include <sphere.h>
#include <colour.h>
#include <canvas.h>
#include <transformation.h>
int main()
{
int x, y;
Canvas c = Canvas(100, 100);
Sphere s = Sphere();
s.material.colour = Colour(1, 0.2, 1);
Light light = Light(POINT_LIGHT, Point(-10, 10, -10), Colour(1, 1, 1));
Point cameraOrigin = Point(0, 0, -5);
double wallDistance = 10;
double wallSize = 7;
double pixelSize = wallSize / c.width;
for(y = 0; y < c.height; y++)
{
double worldY = (wallSize / 2) - pixelSize * y;
for(x = 0; x < c.width; x++)
{
double worldX = -(wallSize / 2) + pixelSize * x;
Point position = Point(worldX, worldY, wallDistance);
Ray r = Ray(cameraOrigin, (position - cameraOrigin).normalise());
Intersect xs = s.intersect(r);
Intersection hit = xs.hit();
if (!hit.nothing())
{
Tuple hitPoint = r.position(hit.t);
Tuple hitNormalVector = hit.object->normalAt(hitPoint);
Tuple eye = -r.direction;
Colour pixelColour = hit.object->material.lighting(light, hitPoint, eye, hitNormalVector, hit.object);
c.putPixel(x, y, pixelColour);
}
}
}
c.SaveAsPNG("ch6_test.png");
return 0;
}

83
tests/ch7_test.cpp Normal file
View File

@@ -0,0 +1,83 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for chapter 7 "Put it together".
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <material.h>
#include <colour.h>
#include <canvas.h>
#include <camera.h>
#include <transformation.h>
int main()
{
/* First we need to construct the world */
Sphere floor = Sphere();
floor.setTransform(scaling(10, 0.01, 10));
floor.material.colour = Colour(1, 0.9, 0.9);
floor.material.specular = 0;
Sphere left_wall = Sphere();
left_wall.setTransform(translation(0, 0, 5) *
rotationY(-M_PI / 4) * rotationX((M_PI / 2)) *
scaling(10, 0.01, 10));
left_wall.material = floor.material;
Sphere right_wall = Sphere();
right_wall.setTransform(translation(0, 0, 5) *
rotationY(M_PI / 4) * rotationX((M_PI / 2)) *
scaling(10, 0.01, 10));
right_wall.material = floor.material;
Sphere middle = Sphere();
middle.setTransform(translation(-0.5, 1, 0.5));
middle.material.colour = Colour(0.1, 1, 0.5);
middle.material.diffuse = 0.7;
middle.material.specular = 0.3;
Sphere right = Sphere();
right.setTransform(translation(1.5, 0.5, -0.5) * scaling(0.5, 0.5, 0.5));
right.material.colour = Colour(0.5, 1, 0.1);
right.material.diffuse = 0.7;
right.material.specular = 0.3;
Sphere left = Sphere();
left.setTransform(translation(-1.5, 0.33, -0.75) * scaling(0.33, 0.33, 0.33));
left.material.colour = Colour(1, 0.8, 0.1);
left.material.diffuse = 0.7;
left.material.specular = 0.3;
World w = World();
w.addObject(&floor);
w.addObject(&left_wall);
w.addObject(&right_wall);
w.addObject(&middle);
w.addObject(&left);
w.addObject(&right);
/* Add light */
Light light = Light(POINT_LIGHT, Point(-10, 10, -10), Colour(1, 1, 1));
w.addLight(&light);
/* Set the camera */
Camera camera = Camera(100, 50, M_PI / 3);
camera.setTransform(viewTransform(Point(0, 1.5, -5),
Point(0, 1, 0),
Vector(0, 1, 0)));
/* Now render it */
Canvas image = camera.render(w);
image.SaveAsPNG("ch7_test.png");
return 0;
}

69
tests/ch9_test.cpp Normal file
View File

@@ -0,0 +1,69 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Render test for chapter 9.
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <plane.h>
#include <material.h>
#include <colour.h>
#include <canvas.h>
#include <camera.h>
#include <transformation.h>
int main()
{
/* First we need to construct the world */
Plane floor = Plane();
floor.material.colour = Colour(1, 0.9, 0.9);
floor.material.specular = 0;
Sphere middle = Sphere();
middle.setTransform(translation(-0.5, 1, 0.5));
middle.material.colour = Colour(0.1, 1, 0.5);
middle.material.diffuse = 0.7;
middle.material.specular = 0.3;
Sphere right = Sphere();
right.setTransform(translation(1.5, 0.5, -0.5) * scaling(0.5, 0.5, 0.5));
right.material.colour = Colour(0.5, 1, 0.1);
right.material.diffuse = 0.7;
right.material.specular = 0.3;
Sphere left = Sphere();
left.setTransform(translation(-1.5, 0.33, -0.75) * scaling(0.33, 0.33, 0.33));
left.material.colour = Colour(1, 0.8, 0.1);
left.material.diffuse = 0.7;
left.material.specular = 0.3;
World w = World();
w.addObject(&floor);
w.addObject(&middle);
w.addObject(&left);
w.addObject(&right);
/* Add light */
Light light = Light(POINT_LIGHT, Point(-10, 10, -10), Colour(1, 1, 1));
w.addLight(&light);
/* Set the camera */
Camera camera = Camera(100, 50, M_PI / 3);
camera.setTransform(viewTransform(Point(0, 1.5, -5),
Point(0, 1, 0),
Vector(0, 1, 0)));
/* Now render it */
Canvas image = camera.render(w);
image.SaveAsPNG("ch9_test.png");
return 0;
}

51
tests/colour_test.cpp Normal file
View File

@@ -0,0 +1,51 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Colour unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <colour.h>
#include <math.h>
#include <gtest/gtest.h>
TEST(ColourTest, Color_is_tuple)
{
Colour c = Colour(-0.5, 0.4, 1.7);
ASSERT_EQ(c.red(), -0.5);
ASSERT_EQ(c.green(), 0.4);
ASSERT_EQ(c.blue(), 1.7);
}
TEST(ColourTest, Adding_colours)
{
Colour c1 = Colour(0.9, 0.6, 0.75);
Colour c2 = Colour(0.7, 0.1, 0.25);
ASSERT_EQ(c1 + c2, Colour(1.6, 0.7, 1.0));
}
TEST(ColourTest, Substracting_colours)
{
Colour c1 = Colour(0.9, 0.6, 0.75);
Colour c2 = Colour(0.7, 0.1, 0.25);
ASSERT_EQ(c1 - c2, Colour(0.2, 0.5, 0.5));
}
TEST(ColourTest, Multiplying_colour_by_a_scalar)
{
Colour c = Colour(0.2, 0.3, 0.4);
ASSERT_EQ(c * 2, Colour(0.4, 0.6, 0.8));
}
TEST(ColourTest, Multiplying_colours)
{
Colour c1 = Colour(1, 0.2, 0.4);
Colour c2 = Colour(0.9, 1, 0.1);
ASSERT_EQ(c1 * c2, Colour(0.9, 0.2, 0.04));
}

117
tests/cube_test.cpp Normal file
View File

@@ -0,0 +1,117 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Cube unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <intersect.h>
#include <intersection.h>
#include <cube.h>
#include <transformation.h>
#include <gtest/gtest.h>
TEST(CubeTest, A_ray_intersects_a_cube)
{
Cube c = Cube();
Point Origins[] = {
Point(5, 0.5, 0),
Point(-5, 0.5, 0),
Point(0.5, 5, 0),
Point(0.5, -5, 0),
Point(0.5, 0, 5),
Point(0.5, 0, -5),
Point(0, 0.5, 0),
};
Vector Directions[] = {
Vector(-1, 0, 0),
Vector(1, 0, 0),
Vector(0, -1, 0),
Vector(0, 1, 0),
Vector(0, 0, -1),
Vector(0, 0, 1),
Vector(0, 0, 1),
};
double t1[] = { 4, 4, 4, 4, 4, 4, -1 };
double t2[] = { 6, 6, 6, 6, 6, 6, 1 };
int i;
for(i = 0; i < 7; i++)
{
Ray r = Ray(Origins[i], Directions[i]);
Intersect xs = c.intersect(r);
ASSERT_EQ(xs.count(), 2);
EXPECT_EQ(xs[0].t, t1[i]);
EXPECT_EQ(xs[1].t, t2[i]);
}
}
TEST(CubeTest, A_ray_miss_a_cube)
{
Cube c = Cube();
Point Origins[] = {
Point(-2, 0, 0),
Point(0, -2, 0),
Point(0, 0, -2),
Point(2, 0, 2),
Point(0, 2, 2),
Point(2, 2, 0),
};
Vector Directions[] = {
Vector(0.2673, 0.5345, 0.8018),
Vector(0.8018, 0.2673, 0.5345),
Vector(0.5345, 0.8018, 0.2673),
Vector(0, 0, -1),
Vector(0, -1, 0),
Vector(-1, 0, 0),
};
int i;
for(i = 0; i < 6; i++)
{
Ray r = Ray(Origins[i], Directions[i]);
Intersect xs = c.intersect(r);
ASSERT_EQ(xs.count(), 0);
}
}
TEST(CubeTest, The_normal_on_the_surface_of_a_cube)
{
Cube c = Cube();
Point HitPoints[] = {
Point(1, 0.5, -0.8),
Point(-1, -0.2, 0.9),
Point(-0.4, 1, -0.1),
Point(0.3, -1, -0.7),
Point(-0.6, 0.3, 1),
Point(0.4, 0.4, -1),
Point(1, 1, 1),
Point(-1, -1, -1),
};
Vector ExpectedNormals[] = {
Vector(1, 0, 0),
Vector(-1, 0, 0),
Vector(0, 1, 0),
Vector(0, -1, 0),
Vector(0, 0, 1),
Vector(0, 0, -1),
Vector(1, 0, 0),
Vector(-1, 0, 0),
};
int i;
for(i = 0; i < 8; i++)
{
ASSERT_EQ(c.normalAt(HitPoints[i]), ExpectedNormals[i]);
}
}

42
tests/hw3render.cpp Normal file
View File

@@ -0,0 +1,42 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Renderer using hw3 files as world builder.
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <stdio.h>
#include <world.h>
#include <worldbuilder.h>
#include <light.h>
#include <sphere.h>
#include <material.h>
#include <colour.h>
#include <canvas.h>
#include <camera.h>
#include <transformation.h>
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("usage: %s file.hw3\n", argv[0]);
return -1;
}
Hw3File world = Hw3File(argv[1]);
/* Set the camera resolution */
Camera cam = Camera(640, 480, world.camFoV);
cam.setTransform(world.cam);
/* Now render it */
Canvas image = cam.render(world);
image.SaveAsPNG("hw3render.png");
return 0;
}

305
tests/intersect_test.cpp Normal file
View File

@@ -0,0 +1,305 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Intersect unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <intersect.h>
#include <intersection.h>
#include <sphere.h>
#include <plane.h>
#include <transformation.h>
#include <gtest/gtest.h>
TEST(IntersectTest, Creating_an_intersect_and_do_some_check)
{
Intersect i;
ASSERT_EQ(i.count(), 0);
i.add(Intersection(1.0, nullptr));
i.add(Intersection(4.2, nullptr));
ASSERT_EQ(i.count(), 2);
ASSERT_EQ(i[0].t, 1.0);
ASSERT_EQ(i[1].t, 4.2);
}
TEST(IntersectTest, An_intersection_encapsulate_t_and_object)
{
Sphere s = Sphere();
Intersection i = Intersection(3.5, &s);
ASSERT_EQ(i.t, 3.5);
ASSERT_EQ(i.object, (Shape *)&s);
}
TEST(IntersectTest, Aggregating_intersections)
{
Sphere s = Sphere();
Intersection i1 = Intersection(1, &s);
Intersection i2 = Intersection(2, &s);
Intersect xs = Intersect();
xs.add(i1);
xs.add(i2);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, 1);
ASSERT_EQ(xs[1].t, 2);
}
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);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].object, (Shape *)&s);
ASSERT_EQ(xs[1].object, (Shape *)&s);
}
TEST(IntersectTest, The_hit_when_all_intersection_have_positive_t)
{
Sphere s = Sphere();
Intersect xs = Intersect();
Intersection i1 = Intersection(1, &s);
Intersection i2 = Intersection(2, &s);
xs.add(i1);
xs.add(i2);
Intersection i = xs.hit();
ASSERT_EQ(i, i1);
}
TEST(IntersectTest, The_hit_when_some_intersection_have_negative_t)
{
Sphere s = Sphere();
Intersect xs = Intersect();
Intersection i1 = Intersection(-1, &s);
Intersection i2 = Intersection(2, &s);
Intersection i3 = Intersection(12, &s);
xs.add(i1);
xs.add(i2);
xs.add(i3);
Intersection i = xs.hit();
ASSERT_EQ(i, i2);
}
TEST(IntersectTest, The_hit_when_all_intersection_have_negative_t)
{
Sphere s = Sphere();
Intersect xs = Intersect();
Intersection i1 = Intersection(-2, &s);
Intersection i2 = Intersection(-1, &s);
xs.add(i1);
xs.add(i2);
Intersection i = xs.hit();
ASSERT_TRUE(i.nothing());
}
TEST(IntersectTest, The_hit_is_always_the_lowest_nonnegative_intersection)
{
Sphere s = Sphere();
Intersect xs = Intersect();
Intersection i1 = Intersection(5, &s);
Intersection i2 = Intersection(7, &s);
Intersection i3 = Intersection(-3, &s);
Intersection i4 = Intersection(2, &s);
xs.add(i1);
xs.add(i2);
xs.add(i3);
xs.add(i4);
Intersection i = xs.hit();
ASSERT_EQ(i, i4);
}
TEST(IntersectTest, Precomputing_the_state_of_an_intersection)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Sphere shape = Sphere();
Intersection i = Intersection(4, &shape);
Computation comps = i.prepareComputation(r);
ASSERT_EQ(comps.t, i.t);
ASSERT_EQ(comps.object, i.object);
ASSERT_EQ(comps.hitPoint, Point(0, 0, -1));
ASSERT_EQ(comps.eyeVector, Vector(0, 0, -1));
ASSERT_EQ(comps.normalVector, Vector(0, 0, -1));
}
TEST(IntersectTest, The_hit_when_an_intersection_occurs_on_the_outside)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Sphere shape = Sphere();
Intersection i = Intersection(4, &shape);
Computation comps = i.prepareComputation(r);
ASSERT_EQ(comps.inside, false);
}
TEST(IntersectTest, The_hit_when_an_intersection_occurs_on_the_inside)
{
Ray r = Ray(Point(0, 0, 0), Vector(0, 0, 1));
Sphere shape = Sphere();
Intersection i = Intersection(1, &shape);
Computation comps = i.prepareComputation(r);
ASSERT_EQ(comps.hitPoint, Point(0, 0, 1));
ASSERT_EQ(comps.eyeVector, Vector(0, 0, -1));
ASSERT_EQ(comps.inside, true);
/* Normal vector would have been (0, 0, 1); but is inverted ! */
ASSERT_EQ(comps.normalVector, Vector(0, 0, -1));
}
TEST(IntersectTest, The_hit_should_offset_the_point)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Sphere shape = Sphere();
shape.setTransform(translation(0, 0, 1));
Intersection i = Intersection(5, &shape);
Computation comps = i.prepareComputation(r);
/* Normal vector would have been (0, 0, 1); but is inverted ! */
ASSERT_LT(comps.overHitPoint.z, -getEpsilon() / 2);
ASSERT_GT(comps.hitPoint.z, comps.overHitPoint.z);
}
TEST(IntersectTest, Precomputing_the_reflection_vector)
{
Plane s = Plane();
Ray r = Ray(Point(0, 1, -1), Vector(0, -sqrt(2) / 2, sqrt(2) / 2));
Intersection i = Intersection(sqrt(2), &s);
Computation comps = i.prepareComputation(r);
ASSERT_EQ(comps.reflectVector, Vector(0, sqrt(2) / 2, sqrt(2) / 2));
}
TEST(IntersectTest, Finding_n1_and_n2_at_various_intersections)
{
int i;
double n1_res[6] = { 1.0, 1.5, 2.0, 2.5, 2.5, 1.5 };
double n2_res[6] = { 1.5, 2.0, 2.5, 2.5, 1.5, 1.0 };
GlassSphere A = GlassSphere();
A.setTransform(scaling(2, 2, 2));
A.material.refractiveIndex = 1.5;
GlassSphere B = GlassSphere();
B.setTransform(translation(0, 0, -0.25));
B.material.refractiveIndex = 2.0;
GlassSphere C = GlassSphere();
C.setTransform(translation(0, 0, 0.25));
C.material.refractiveIndex = 2.5;
Ray r = Ray(Point(0, 0, -4), Vector(0, 0, 1));
Intersect xs = Intersect();
xs.add(Intersection(2.0, &A));
xs.add(Intersection(2.75, &B));
xs.add(Intersection(3.25, &C));
xs.add(Intersection(4.75, &B));
xs.add(Intersection(5.25, &C));
xs.add(Intersection(6, &A));
for(i = 0; i < xs.count(); i++)
{
Intersection inter = xs[i];
Computation comps = inter.prepareComputation(r, &xs);
ASSERT_EQ(comps.n1, n1_res[i]);
ASSERT_EQ(comps.n2, n2_res[i]);
}
}
TEST(IntersectTest, The_under_point_is_offset_below_the_surface)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
GlassSphere shape = GlassSphere();
shape.setTransform(translation(0, 0, 1));
Intersection i = Intersection(5, &shape);
Intersect xs = Intersect();
xs.add(i);
Computation comps = i.prepareComputation(r, &xs);
ASSERT_TRUE(double_equal(comps.underHitPoint.z, getEpsilon() / 2));
ASSERT_LT(comps.hitPoint.z, comps.underHitPoint.z);
}
TEST(IntersectTest, The_Schlick_approximation_under_total_internal_reflection)
{
GlassSphere shape = GlassSphere();
Ray r = Ray(Point(0, 0, sqrt(2)/2), Vector(0, 1, 0));
Intersect xs = Intersect();
xs.add(Intersection(-sqrt(2)/2, &shape));
xs.add(Intersection(sqrt(2)/2, &shape));
Computation comps = xs[1].prepareComputation(r, &xs);
double reflectance = comps.schlick();
ASSERT_EQ(reflectance, 1.0);
}
TEST(IntersectTest, The_Schlick_approximation_with_a_perpendicular_viewing_angle)
{
GlassSphere shape = GlassSphere();
Ray r = Ray(Point(0, 0, 0), Vector(0, 1, 0));
Intersect xs = Intersect();
xs.add(Intersection(-1, &shape));
xs.add(Intersection(1, &shape));
Computation comps = xs[1].prepareComputation(r, &xs);
double reflectance = comps.schlick();
ASSERT_TRUE(double_equal(reflectance, 0.04));
}
TEST(IntersectTest, The_Schlick_approximation_with_small_angle_and_n2_gt_n1)
{
GlassSphere shape = GlassSphere();
Ray r = Ray(Point(0, 0.99, -2), Vector(0, 0, 1));
Intersect xs = Intersect();
xs.add(Intersection(1.8589, &shape));
Computation comps = xs[0].prepareComputation(r, &xs);
double reflectance = comps.schlick();
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_TRUE(double_equal(reflectance, 0.48873));
set_equal_precision(FLT_EPSILON);
}

24
tests/light_test.cpp Normal file
View File

@@ -0,0 +1,24 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Light unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <light.h>
#include <math.h>
#include <colour.h>
#include <tuple.h>
#include <gtest/gtest.h>
TEST(LightTest, A_point_lighthas_a_position_and_intensity)
{
Colour intensity = Colour(1, 1, 1);
Point position = Point(0, 0, 0);
Light light = Light(POINT_LIGHT, position, intensity);
ASSERT_EQ(light.position, position);
ASSERT_EQ(light.intensity, intensity);
}

124
tests/material_test.cpp Normal file
View File

@@ -0,0 +1,124 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Material unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <material.h>
#include <math.h>
#include <colour.h>
#include <testshape.h>
#include <gtest/gtest.h>
TEST(MaterialTest, The_default_material)
{
Material m = Material();
ASSERT_EQ(m.colour, Colour(1, 1, 1));
ASSERT_EQ(m.ambient, 0.1);
ASSERT_EQ(m.diffuse, 0.9);
ASSERT_EQ(m.specular, 0.9);
ASSERT_EQ(m.shininess, 200.0);
}
// This is used by the next tests
static Material m = Material();
static Point position = Point(0, 0, 0);
TEST(MaterialTest, Lighting_with_the_eye_between_the_light_and_the_surface)
{
TestShape t = TestShape();
Vector eyev = Vector(0, 0, -1);
Vector normalv = Vector(0, 0, -1);
Light light = Light(POINT_LIGHT, Point(0, 0, -10), Colour(1, 1, 1));
Colour result = m.lighting(light, position, eyev, normalv, &t);
ASSERT_EQ(result, Colour(1.9, 1.9, 1.9));
}
TEST(MaterialTest, Lighting_with_the_eye_between_the_light_and_the_surface_eye_offset_by_45)
{
TestShape t = TestShape();
Vector eyev = Vector(0, -sqrt(2)/2, -sqrt(2)/2);
Vector normalv = Vector(0, 0, -1);
Light light = Light(POINT_LIGHT, Point(0, 0, -10), Colour(1, 1, 1));
Colour result = m.lighting(light, position, eyev, normalv, &t);
ASSERT_EQ(result, Colour(1.0, 1.0, 1.0));
}
TEST(MaterialTest, Lighting_with_the_eye_opposite_surface_light_offset_45)
{
TestShape t = TestShape();
Vector eyev = Vector(0, 0, -1);
Vector normalv = Vector(0, 0, -1);
Light light = Light(POINT_LIGHT, Point(0, 10, -10), Colour(1, 1, 1));
Colour result = m.lighting(light, position, eyev, normalv, &t);
set_equal_precision(0.0001);
ASSERT_EQ(result, Colour(0.7364, 0.7364, 0.7364));
set_equal_precision(FLT_EPSILON);
}
TEST(MaterialTest, Lighting_with_the_eye_in_the_path_of_the_reflection_vector)
{
TestShape t = TestShape();
Vector eyev = Vector(0, -sqrt(2)/2, -sqrt(2)/2);
Vector normalv = Vector(0, 0, -1);
Light light = Light(POINT_LIGHT, Point(0, 10, -10), Colour(1, 1, 1));
Colour result = m.lighting(light, position, eyev, normalv, &t);
set_equal_precision(0.0001);
ASSERT_EQ(result, Colour(1.6364, 1.6364, 1.6364));
set_equal_precision(FLT_EPSILON);
}
TEST(MaterialTest, Lighting_with_the_light_behind_the_surface)
{
TestShape t = TestShape();
Vector eyev = Vector(0, 0, -1);
Vector normalv = Vector(0, 0, -1);
Light light = Light(POINT_LIGHT, Point(0, 0, 10), Colour(1, 1, 1));
Colour result = m.lighting(light, position, eyev, normalv, &t);
ASSERT_EQ(result, Colour(0.1, 0.1, 0.1));
}
TEST(MaterialTest, Lighting_with_the_surface_in_shadow)
{
TestShape t = TestShape();
Vector eyev = Vector(0, 0, -1);
Vector normalv = Vector(0, 0, -1);
Light light = Light(POINT_LIGHT, Point(0, 0, -10), Colour(1, 1, 1));
bool inShadow = true;
Colour result = m.lighting(light, position, eyev, normalv, &t, inShadow);
ASSERT_EQ(result, Colour(0.1, 0.1, 0.1));
}
TEST(MaterialTest, Reflectivity_for_the_default_material)
{
Material m = Material();
ASSERT_EQ(m.reflective, 0);
}
TEST(MaterialTest, Transparency_and_refractive_index_for_the_default_material)
{
Material m = Material();
ASSERT_EQ(m.transparency, 0.0);
ASSERT_EQ(m.refractiveIndex, 1.0);
}

97
tests/math_test.cpp Normal file
View File

@@ -0,0 +1,97 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Math helper unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <math_helper.h>
#include <gtest/gtest.h>
TEST(MathTest, Default_epsilon)
{
ASSERT_EQ(getEpsilon(), FLT_EPSILON);
}
TEST(MathTest, Check_setting_epsilon)
{
set_equal_precision(0.00001);
ASSERT_EQ(getEpsilon(), 0.00001);
set_equal_precision(FLT_EPSILON);
}
TEST(MathTest, Check_double_equal_is_working)
{
ASSERT_TRUE(double_equal(0 - DBL_EPSILON, 0));
ASSERT_FALSE(double_equal(0 - FLT_EPSILON, 0));
ASSERT_TRUE(double_equal(0 - FLT_EPSILON/2.0, 0));
ASSERT_FALSE(double_equal(0 - 0.001, 0));
ASSERT_FALSE(double_equal(0 - 0.00001, 0));
ASSERT_FALSE(double_equal(0 - 0.000001, 0));
set_equal_precision(0.00001);
ASSERT_TRUE(double_equal(0 - FLT_EPSILON*2, 0));
ASSERT_FALSE(double_equal(0 - 0.001, 0));
ASSERT_FALSE(double_equal(0 - 0.00001, 0));
ASSERT_TRUE(double_equal(0 - 0.000001, 0));
set_equal_precision(FLT_EPSILON);
}
TEST(MathTest, Check_that_deg_to_rad_is_working)
{
double angle180 = deg_to_rad(180);
double angle90 = deg_to_rad(90);
double angle270 = deg_to_rad(270);
ASSERT_EQ(angle180, M_PI);
ASSERT_EQ(angle90, M_PI / 2.);
ASSERT_EQ(angle270, M_PI * 1.5);
}
TEST(MathTest, Min_is_working)
{
ASSERT_EQ(min3(1, 2, 3), 1);
ASSERT_EQ(min3(1, 3, 2), 1);
ASSERT_EQ(min3(2, 1, 3), 1);
ASSERT_EQ(min3(2, 3, 1), 1);
ASSERT_EQ(min3(3, 1, 2), 1);
ASSERT_EQ(min3(3, 2, 1), 1);
ASSERT_EQ(min3(1, 2, 2), 1);
ASSERT_EQ(min3(2, 1, 2), 1);
ASSERT_EQ(min3(2, 2, 1), 1);
ASSERT_EQ(min3(3, 2, 2), 2);
ASSERT_EQ(min3(2, 3, 2), 2);
ASSERT_EQ(min3(2, 2, 3), 2);
ASSERT_EQ(min3(2, 2, 2), 2);
}
TEST(MathTest, Max_is_working)
{
ASSERT_EQ(max3(1, 2, 3), 3);
ASSERT_EQ(max3(1, 3, 2), 3);
ASSERT_EQ(max3(2, 1, 3), 3);
ASSERT_EQ(max3(2, 3, 1), 3);
ASSERT_EQ(max3(3, 1, 2), 3);
ASSERT_EQ(max3(3, 2, 1), 3);
ASSERT_EQ(max3(1, 2, 2), 2);
ASSERT_EQ(max3(2, 1, 2), 2);
ASSERT_EQ(max3(2, 2, 1), 2);
ASSERT_EQ(max3(3, 2, 2), 3);
ASSERT_EQ(max3(2, 3, 2), 3);
ASSERT_EQ(max3(2, 2, 3), 3);
ASSERT_EQ(max3(2, 2, 2), 2);
}

407
tests/matrix_test.cpp Normal file
View File

@@ -0,0 +1,407 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Matric unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <matrix.h>
#include <tuple.h>
#include <math.h>
#include <gtest/gtest.h>
TEST(MatrixTest, Constructing_and_inspecting_a_4x4_Matrix)
{
double values[] = {1, 2, 3, 4,
5.5, 6.5, 7.5, 8.5,
9, 10, 11, 12,
13.5, 14.5, 15.5, 16.5};
Matrix4 m = Matrix4(values);
ASSERT_EQ(m.get(0, 0), 1);
ASSERT_EQ(m.get(0, 3), 4);
ASSERT_EQ(m.get(1, 0), 5.5);
ASSERT_EQ(m.get(1, 2), 7.5);
ASSERT_EQ(m.get(2, 2), 11);
ASSERT_EQ(m.get(3, 0), 13.5);
ASSERT_EQ(m.get(3, 2), 15.5);
}
TEST(MatrixTest, Change_a_single_value_and_check_it)
{
double values[] = {-3, 5,
1, -2};
Matrix2 m = Matrix2(values);
ASSERT_EQ(m.get(0, 0), -3);
ASSERT_EQ(m.get(0, 1), 5);
ASSERT_EQ(m.get(1, 0), 1);
ASSERT_EQ(m.get(1, 1), -2);
m.set(0, 0, 12);
ASSERT_EQ(m.get(0, 0), 12);
}
TEST(MatrixTest, A_3x3_matrix_ought_to_be_representable)
{
double values[] = {-3, 5, 0,
1, -2, -7,
0, 1, 1};
Matrix3 m = Matrix3(values);
ASSERT_EQ(m.get(0, 0), -3);
ASSERT_EQ(m.get(1, 1), -2);
ASSERT_EQ(m.get(2, 2), 1);
}
TEST(MatrixTest, A_2x2_matrix_ought_to_be_representable)
{
double values[] = {-3, 5,
1, -2};
Matrix2 m = Matrix2(values);
ASSERT_EQ(m.get(0, 0), -3);
ASSERT_EQ(m.get(0, 1), 5);
ASSERT_EQ(m.get(1, 0), 1);
ASSERT_EQ(m.get(1, 1), -2);
}
TEST(MatrixTest, Matrix_equality_with_identical_matrix)
{
double values1[] = {1, 2, 3, 4,
5, 6, 7, 8,
9, 8, 7, 6,
5, 4, 3, 2};
double values2[] = {1, 2, 3, 4,
5, 6, 7, 8,
9, 8, 7, 6,
5, 4, 3, 2};
Matrix4 A = Matrix4(values1);
Matrix4 B = Matrix4(values2);
ASSERT_EQ(A, B);
}
TEST(MatrixTest, Matrix_equality_with_different_matrix)
{
double values1[] = {1, 2, 3, 4,
5, 6, 7, 8,
9, 8, 7, 6,
5, 4, 3, 2};
double values2[] = {2, 3, 4, 5,
6, 7, 8, 9,
8, 7, 6, 5,
4, 3, 2, 1};
Matrix4 A = Matrix4(values1);
Matrix4 B = Matrix4(values2);
ASSERT_NE(A, B);
}
TEST(MatrixTest, Multiplying_two_matrices)
{
double values1[] = {1, 2, 3, 4,
5, 6, 7, 8,
9, 8, 7, 6,
5, 4, 3, 2};
double values2[] = {-2, 1, 2, 3,
3, 2, 1, -1,
4, 3, 6, 5,
1, 2, 7, 8};
double results[] = {20, 22, 50, 48,
44, 54, 114, 108,
40, 58, 110, 102,
16, 26, 46, 42};
Matrix4 A = Matrix4(values1);
Matrix4 B = Matrix4(values2);
ASSERT_EQ(A * B, Matrix4(results));
}
TEST(MatrixTest, A_matrix_multiplyed_by_a_tuple)
{
double valuesA[] = {1, 2, 3, 4,
2, 4, 4, 2,
8, 6, 4, 1,
0, 0, 0, 1};
Matrix4 A = Matrix4(valuesA);
Tuple b = Tuple(1, 2, 3, 1);
ASSERT_EQ(A * b, Tuple(18, 24, 33, 1));
}
TEST(MatrixTest, Multiplying_a_matrix_by_the_identity_matrix)
{
double valuesA[] = {0, 1, 2, 4,
1, 2, 4, 8,
2, 4, 8, 16,
4, 8, 16, 32};
Matrix4 A = Matrix4(valuesA);
Matrix ident = Matrix4().identity();
ASSERT_EQ(A * ident, A);
}
TEST(MatrixTest, Multiplying_the_identity_matrix_by_a_tuple)
{
Tuple a = Tuple(1, 2, 3, 4);
Matrix ident = Matrix4().identity();
ASSERT_EQ(ident * a, a);
}
TEST(MatrixTest, Transposing_a_matrix)
{
double valuesA[] = {0, 9, 3, 0,
9, 8, 0, 8,
1, 8, 5, 3,
0, 0, 5, 8};
double results[] = {0, 9, 1, 0,
9, 8, 8, 0,
3, 0, 5, 5,
0, 8, 3, 8};
Matrix A = Matrix4(valuesA);
ASSERT_EQ(A.transpose(), Matrix4(results));
}
TEST(MatrixTest, Transposing_this_identity_matrix)
{
Matrix ident = Matrix4().identity();
ASSERT_EQ(ident.transpose(), ident);
}
TEST(MatrixTest, Calculating_the_determinant_of_a_2x2_matrix)
{
double valuesA[] = { 1, 5,
-3, 2 };
Matrix2 A = Matrix2(valuesA);
ASSERT_EQ(A.determinant(), 17);
}
TEST(MatrixTest, A_submatrix_of_a_3x3_matrix_is_a_2x2_matrix)
{
double valuesA[] = { 1, 5, 0,
-3, 2, 7,
0, 6, -3 };
double results[] = { -3, 2,
0, 6 };
Matrix3 A = Matrix3(valuesA);
ASSERT_EQ(A.submatrix(0, 2), Matrix2(results));
}
TEST(MatrixTest, A_submatrix_of_a_4x4_matrix_is_a_3x3_matrix)
{
double valuesA[] = { -6, 1, 1, 6,
-8, 5, 8, 6,
-1, 0, 8, 2,
-7, 1, -1, 1 };
double results[] = { -6, 1, 6,
-8, 8, 6,
-7,-1, 1 };
Matrix4 A = Matrix4(valuesA);
ASSERT_EQ(A.submatrix(2, 1), Matrix3(results));
}
TEST(MatrixTest, Calculate_a_minor_of_a_3x3_matrix)
{
double valuesA[] = { 3, 5, 0,
2, -1, -7,
6, -1, 5 };
Matrix3 A = Matrix3(valuesA);
Matrix B = A.submatrix(1, 0);
ASSERT_EQ(B.determinant(), 25);
ASSERT_EQ(A.minor(1, 0), 25);
}
TEST(MatrixTest, Calculating_a_cofactor_of_a_3x3_matrix)
{
double valuesA[] = { 3, 5, 0,
2, -1, -7,
6, -1, 5 };
Matrix3 A = Matrix3(valuesA);
ASSERT_EQ(A.minor(0, 0), -12);
ASSERT_EQ(A.cofactor(0, 0), -12);
ASSERT_EQ(A.minor(1, 0), 25);
ASSERT_EQ(A.cofactor(1, 0), -25);
}
TEST(MatrixTest, Calculating_the_determinant_of_a_3x3_matrix)
{
double valuesA[] = { 1, 2, 6,
-5, 8, -4,
2, 6, 4 };
Matrix A = Matrix3(valuesA);
ASSERT_EQ(A.cofactor(0, 0), 56);
ASSERT_EQ(A.cofactor(0, 1), 12);
ASSERT_EQ(A.minor(0, 2), -46);
ASSERT_EQ(A.determinant(), -196);
}
TEST(MatrixTest, Calculating_the_determinant_of_a_4x4_matrix)
{
double valuesA[] = { -2, -8, 3, 5,
-3, 1, 7, 3,
1, 2, -9, 6,
-6, 7, 7, -9 };
Matrix A = Matrix4(valuesA);
ASSERT_EQ(A.cofactor(0, 0), 690);
ASSERT_EQ(A.cofactor(0, 1), 447);
ASSERT_EQ(A.cofactor(0, 2), 210);
ASSERT_EQ(A.minor(0, 3), -51);
ASSERT_EQ(A.determinant(), -4071);
}
TEST(MatrixTest, Testing_an_invertible_matrix_for_invertibility)
{
double valuesA[] = { 6, 4, 4, 4,
5, 5, 7, 6,
4, -9, 3, -7,
9, 1, 7, -6 };
Matrix A = Matrix4(valuesA);
ASSERT_EQ(A.determinant(), -2120);
ASSERT_TRUE(A.isInvertible());
}
TEST(MatrixTest, Testing_an_noninvertible_matrix_for_invertibility)
{
double valuesA[] = { -4, 2, -2, -3,
9, 6, 2, 6,
0, -5, 1, -5,
0, 0, 0, 0 };
Matrix A = Matrix4(valuesA);
ASSERT_EQ(A.determinant(), 0);
ASSERT_FALSE(A.isInvertible());
}
TEST(MatrixTest, Calculating_the_inverse_of_a_matrix)
{
double valuesA[] = { -5, 2, 6, -8,
1, -5, 1, 8,
7, 7, -6, -7,
1, -3, 7, 4 };
double results[] = { 0.21805, 0.45113, 0.24060, -0.04511,
-0.80827, -1.45677, -0.44361, 0.52068,
-0.07895, -0.22368, -0.05263, 0.19737,
-0.52256, -0.81391, -0.30075, 0.30639 };
Matrix A = Matrix4(valuesA);
Matrix B = A.inverse();
ASSERT_EQ(A.determinant(), 532);
ASSERT_EQ(A.cofactor(2, 3), -160);
ASSERT_NEAR(B.get(3, 2), -160./532., DBL_EPSILON);
ASSERT_EQ(A.cofactor(3, 2), 105);
ASSERT_NEAR(B.get(2, 3), 105./532., DBL_EPSILON);
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(B, Matrix4(results));
/* Revert to default */
set_equal_precision(FLT_EPSILON);
}
TEST(MatrixTest, Calculating_the_inverse_of_another_matrix)
{
double valuesA[] = { 8, -5, 9, 2,
7, 5, 6, 1,
-6, 0, 9, 6,
-3, 0, -9, -4 };
double results[] = { -0.15385, -0.15385, -0.28205, -0.53846,
-0.07692, 0.12308, 0.02564, 0.03077,
0.35897, 0.35897, 0.43590, 0.92308,
-0.69231, -0.69231, -0.76923, -1.92308 };
Matrix A = Matrix4(valuesA);
Matrix B = A.inverse();
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(B, Matrix4(results));
/* Revert to default */
set_equal_precision(FLT_EPSILON);
}
TEST(MatrixTest, Calculating_the_inverse_of_third_matrix)
{
double valuesA[] = { 9, 3, 0, 9,
-5, -2, -6, -3,
-4, 9, 6, 4,
-7, 6, 6, 2 };
double results[] = { -0.04074, -0.07778, 0.14444, -0.22222,
-0.07778, 0.03333, 0.36667, -0.33333,
-0.02901, -0.14630, -0.10926, 0.12963,
0.17778, 0.06667, -0.26667, 0.33333 };
Matrix A = Matrix4(valuesA);
Matrix B = A.inverse();
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(B, Matrix4(results));
/* Revert to default */
set_equal_precision(FLT_EPSILON);
}
TEST(MatrixTest, Multiplying_a_product_by_its_inverse)
{
double valuesA[] = { 3, -9, 7, 3,
3, -8, 2, -9,
-4, 4, 4, 1,
-6, 5, -1, 1 };
double valuesB[] = { 8, 2, 2, 2,
3, -1, 7, 0,
7, 0, 5, 4,
6, -2, 0, 5 };
Matrix A = Matrix4(valuesA);
Matrix B = Matrix4(valuesB);
Matrix C = A * B;
ASSERT_EQ(C * B.inverse(), A);
}

211
tests/pattern_test.cpp Normal file
View File

@@ -0,0 +1,211 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Pattern unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <pattern.h>
#include <strippattern.h>
#include <gradientpattern.h>
#include <ringpattern.h>
#include <checkerspattern.h>
#include <testpattern.h>
#include <transformation.h>
#include <colour.h>
#include <sphere.h>
#include <gtest/gtest.h>
#include <material.h>
Colour black = Colour(0, 0, 0);
Colour white = Colour(1, 1, 1);
TEST(PatternTest, Creating_a_stripe_pattern)
{
StripPattern p = StripPattern(white, black);
ASSERT_EQ(p.a, white);
ASSERT_EQ(p.b, black);
}
TEST(PatternTest, A_strip_pattern_is_constant_in_y)
{
StripPattern p = StripPattern(white, black);
ASSERT_EQ(p.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(p.patternAt(Point(0, 1, 0)), white);
ASSERT_EQ(p.patternAt(Point(0, 2, 0)), white);
}
TEST(PatternTest, A_strip_pattern_is_constant_in_z)
{
StripPattern p = StripPattern(white, black);
ASSERT_EQ(p.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(p.patternAt(Point(0, 0, 1)), white);
ASSERT_EQ(p.patternAt(Point(0, 0, 2)), white);
}
TEST(PatternTest, A_strip_pattern_alternate_in_x)
{
StripPattern p = StripPattern(white, black);
ASSERT_EQ(p.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(p.patternAt(Point(0.9, 0, 0)), white);
ASSERT_EQ(p.patternAt(Point(1, 0, 0)), black);
ASSERT_EQ(p.patternAt(Point(-0.1, 0, 0)), black);
ASSERT_EQ(p.patternAt(Point(-1, 0, 0)), black);
ASSERT_EQ(p.patternAt(Point(-1.1, 0, 0)), white);
}
TEST(PatternTest, Lightning_with_a_pattern_applied)
{
Sphere s = Sphere();
Material m;
StripPattern p = StripPattern(white, black);
m.pattern = &p;
m.ambient = 1;
m.diffuse = 0;
m.specular = 0;
Tuple eyev = Vector(0, 0, -1);
Tuple normalv = Vector(0, 0, -1);
Light light = Light(POINT_LIGHT, Point(0, 0, -10), Colour(1, 1, 1));
Colour c1 = m.lighting(light, Point(0, 9, 0), eyev, normalv, &s, false);
Colour c2 = m.lighting(light, Point(1, 1, 0), eyev, normalv, &s, false);
ASSERT_EQ(c1, Colour(1, 1, 1));
ASSERT_EQ(c2, Colour(0, 0, 0));
}
TEST(PatternTest, Stripe_with_an_object_transformation)
{
Sphere s = Sphere();
s.setTransform(scaling(2, 2, 2));
StripPattern pattern = StripPattern(white, black);
Colour c = pattern.patternAtObject(&s, Point(1.5, 0, 0));
ASSERT_EQ(c, white);
}
TEST(PatternTest, Stripe_with_a_pattern_transformation)
{
Sphere s = Sphere();
StripPattern pattern = StripPattern(white, black);
pattern.setTransform(scaling(2, 2, 2));
Colour c = pattern.patternAtObject(&s, Point(1.5, 0, 0));
ASSERT_EQ(c, white);
}
TEST(PatternTest, Stripe_with_both_an_object_and_a_pattern_transformation)
{
Sphere s = Sphere();
s.setTransform(scaling(2, 2, 2));
StripPattern pattern = StripPattern(white, black);
pattern.setTransform(translation(0.5, 0, 0));
Colour c = pattern.patternAtObject(&s, Point(2.5, 0, 0));
ASSERT_EQ(c, white);
}
TEST(PatternTest, The_default_pattern_transformation)
{
TestPattern pattern = TestPattern();
ASSERT_EQ(pattern.transformMatrix, Matrix4().identity());
}
TEST(PatternTest, Assigning_a_transformation)
{
TestPattern pattern = TestPattern();
pattern.setTransform(translation(1, 2, 3));
ASSERT_EQ(pattern.transformMatrix, translation(1, 2, 3));
}
TEST(PatternTest, A_pattern_with_an_object_transformation)
{
Sphere s = Sphere();
s.setTransform(scaling(2, 2, 2));
TestPattern pattern = TestPattern();
Colour c = pattern.patternAtObject(&s, Point(2, 3, 4));
ASSERT_EQ(c, Colour(1, 1.5, 2));
}
TEST(PatternTest, A_pattern_with_a_pattern_transformation)
{
Sphere s = Sphere();
TestPattern pattern = TestPattern();
pattern.setTransform(scaling(2, 2, 2));
Colour c = pattern.patternAtObject(&s, Point(2, 3, 4));
ASSERT_EQ(c, Colour(1, 1.5, 2));
}
TEST(PatternTest, A_pattern_with_an_object_and_a_pattern_transformation)
{
Sphere s = Sphere();
s.setTransform(scaling(2, 2, 2));
TestPattern pattern = TestPattern();
pattern.setTransform(translation(0.5, 1, 1.5));
Colour c = pattern.patternAtObject(&s, Point(2.5, 3, 3.5));
ASSERT_EQ(c, Colour(0.75, 0.5, 0.25));
}
TEST(PatternTest, A_gradient_linearly_interpolates_betweens_colors)
{
GradientPattern pattern = GradientPattern(white, black);
ASSERT_EQ(pattern.patternAt(Point(0.25, 0, 0)), Colour(0.75, 0.75, 0.75));
ASSERT_EQ(pattern.patternAt(Point(0.5, 0, 0)), Colour(0.5, 0.5, 0.5));
ASSERT_EQ(pattern.patternAt(Point(0.75, 0, 0)), Colour(0.25, 0.25, 0.25));
}
TEST(PatternTest, A_ring_should_extend_in_both_x_and_z)
{
RingPattern pattern = RingPattern(white, black);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(1, 0, 0)), black);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 1)), black);
/* 0.708 is just bit more than sqrt(2)/2 */
ASSERT_EQ(pattern.patternAt(Point(0.708, 0, 0.708)), black);
}
TEST(PatternTest, Checkers_should_repeat_in_x)
{
CheckersPattern pattern = CheckersPattern(white, black);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(0.99, 0, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(1.01, 0, 0)), black);
}
TEST(PatternTest, Checkers_should_repeat_in_y)
{
CheckersPattern pattern = CheckersPattern(white, black);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(0, 0.99, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(0, 1.01, 0)), black);
}
TEST(PatternTest, Checkers_should_repeat_in_z)
{
CheckersPattern pattern = CheckersPattern(white, black);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 0)), white);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 0.99)), white);
ASSERT_EQ(pattern.patternAt(Point(0, 0, 1.01)), black);
}

71
tests/plane_test.cpp Normal file
View File

@@ -0,0 +1,71 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Plane unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <ray.h>
#include <shape.h>
#include <plane.h>
#include <material.h>
#include <transformation.h>
#include <gtest/gtest.h>
TEST(PlaneTest, The_normal_of_a_plane_is_constant_everywhere)
{
Plane p = Plane();
Tuple n1 = p.normalAt(Point(0, 0, 0));
Tuple n2 = p.normalAt(Point(10, 0, -10));
Tuple n3 = p.normalAt(Point(-5, 0, 0150));
ASSERT_EQ(n1, Vector(0, 1, 0));
ASSERT_EQ(n2, Vector(0, 1, 0));
ASSERT_EQ(n3, Vector(0, 1, 0));
}
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);
ASSERT_EQ(xs.count(), 0);
}
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);
ASSERT_EQ(xs.count(), 0);
}
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);
ASSERT_EQ(xs.count(), 1);
ASSERT_EQ(xs[0].t, 1);
ASSERT_EQ(xs[0].object, &p);
}
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);
ASSERT_EQ(xs.count(), 1);
ASSERT_EQ(xs[0].t, 1);
ASSERT_EQ(xs[0].object, &p);
}

65
tests/ray_test.cpp Normal file
View File

@@ -0,0 +1,65 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Ray unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <ray.h>
#include <transformation.h>
#include <shape.h>
#include <testshape.h>
#include <gtest/gtest.h>
TEST(RayTest, Creating_a_ray_and_querying_it)
{
Point origin = Point(1, 2, 3);
Vector direction = Vector(4, 5, 6);
Ray r = Ray(origin, direction);
ASSERT_EQ(r.origin, origin);
ASSERT_EQ(r.direction, direction);
}
TEST(RayTest, Computing_a_point_from_a_distance)
{
Ray r = Ray(Point(2, 3, 4), Vector(1, 0, 0));
ASSERT_EQ(r.position(0), Point(2, 3, 4));
ASSERT_EQ(r.position(1), Point(3, 3, 4));
ASSERT_EQ(r.position(-1), Point(1, 3, 4));
ASSERT_EQ(r.position(2.5), Point(4.5, 3, 4));
}
TEST(RayTest, Translating_a_ray)
{
Ray r = Ray(Point(1, 2, 3), Vector(0, 1, 0));
Matrix m = translation(3, 4, 5);
TestShape o = TestShape();
o.setTransform(m);
Ray r2 = o.transform(r);
ASSERT_EQ(r2.origin, Point(4, 6, 8));
ASSERT_EQ(r2.direction, Vector(0, 1, 0));
}
TEST(RayTest, Scaling_a_ray)
{
Ray r = Ray(Point(1, 2, 3), Vector(0, 1, 0));
Matrix m = scaling(2, 3, 4);
TestShape o = TestShape();
o.setTransform(m);
Ray r2 = o.transform(r);
ASSERT_EQ(r2.origin, Point(2, 6, 12));
ASSERT_EQ(r2.direction, Vector(0, 3, 0));
}

98
tests/shape_test.cpp Normal file
View File

@@ -0,0 +1,98 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Shape unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <shape.h>
#include <testshape.h>
#include <matrix.h>
#include <transformation.h>
#include <gtest/gtest.h>
TEST(ShapeTest, The_default_transformation)
{
TestShape s = TestShape();
ASSERT_EQ(s.transformMatrix, Matrix4().identity());
}
TEST(ShapeTest, Assigning_a_transformation)
{
TestShape s = TestShape();
s.setTransform(translation(2, 3, 4));
ASSERT_EQ(s.transformMatrix, translation(2, 3, 4));
}
TEST(ShapeTest, The_default_material)
{
TestShape s = TestShape();
ASSERT_EQ(s.material, Material());
}
TEST(ShapeTest, Assigning_a_material)
{
TestShape s = TestShape();
Material m = Material();
m.ambient = 1;
s.material = m;
ASSERT_EQ(s.material, m);
}
TEST(ShapeTest, Intersecting_a_scaled_shape_with_a_ray)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
TestShape s = TestShape();
s.setTransform(scaling(2, 2, 2));
Intersect xs = s.intersect(r);
ASSERT_EQ(s.localRay.origin, Point(0, 0, -2.5));
ASSERT_EQ(s.localRay.direction, Vector(0, 0, 0.5));
}
TEST(ShapeTest, Intersecting_a_translated_shape_with_a_ray)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
TestShape s = TestShape();
s.setTransform(translation(5, 0, 0));
Intersect xs = s.intersect(r);
ASSERT_EQ(s.localRay.origin, Point(-5, 0, -5));
ASSERT_EQ(s.localRay.direction, Vector(0, 0, 1));
}
TEST(ShapeTest, Computing_the_normal_on_a_translated_shape)
{
TestShape s = TestShape();
s.setTransform(translation(0, 1, 0));
Tuple n = s.normalAt(Point(0, 1.70711, -0.70711));
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(n, Vector(0, 0.70711, -0.70711));
set_equal_precision(FLT_EPSILON);
}
TEST(ShapeTest, Computing_the_normal_on_a_tranformed_shape)
{
TestShape s = TestShape();
s.setTransform(scaling(1, 0.5, 1) * rotationZ(M_PI / 5));
Tuple n = s.normalAt(Point(0, sqrt(2)/2, -sqrt(2)/2));
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(n, Vector(0, 0.97014, -0.24254));
set_equal_precision(FLT_EPSILON);
}

210
tests/sphere_test.cpp Normal file
View File

@@ -0,0 +1,210 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Sphere unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <ray.h>
#include <sphere.h>
#include <material.h>
#include <transformation.h>
#include <gtest/gtest.h>
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);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, 4.0);
ASSERT_EQ(xs[1].t, 6.0);
}
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);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, 5.0);
ASSERT_EQ(xs[1].t, 5.0);
}
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);
ASSERT_EQ(xs.count(), 0);
}
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);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, -1.0);
ASSERT_EQ(xs[1].t, 1.0);
}
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);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, -6.0);
ASSERT_EQ(xs[1].t, -4.0);
}
TEST(SphereTest, A_sphere_default_transformation)
{
Sphere s = Sphere();
ASSERT_EQ(s.transformMatrix, Matrix4().identity());
}
TEST(SphereTest, Changing_a_sphere_transformation)
{
Sphere s = Sphere();
Matrix t = translation(2, 3, 4);
s.setTransform(t);
ASSERT_EQ(s.transformMatrix, t);
}
TEST(SphereTest, Intersecting_a_scaled_sphere_with_a_ray)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Sphere s = Sphere();
s.setTransform(scaling(2, 2, 2));
Intersect xs = s.intersect(r);
ASSERT_EQ(xs.count(), 2);
ASSERT_EQ(xs[0].t, 3.0);
ASSERT_EQ(xs[1].t, 7.0);
}
TEST(SphereTest, Intersecting_a_translated_sphere_with_a_ray)
{
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Sphere s = Sphere();
s.setTransform(translation(5, 0, 0));
Intersect xs = s.intersect(r);
ASSERT_EQ(xs.count(), 0);
}
TEST(SphereTest, The_normal_on_a_sphere_at_a_point_on_the_X_axis)
{
Sphere s = Sphere();
Tuple n = s.normalAt(Point(1, 0, 0));
ASSERT_EQ(n, Vector(1, 0, 0));
}
TEST(SphereTest, The_normal_on_a_sphere_at_a_point_on_the_Y_axis)
{
Sphere s = Sphere();
Tuple n = s.normalAt(Point(0, 1, 0));
ASSERT_EQ(n, Vector(0, 1, 0));
}
TEST(SphereTest, The_normal_on_a_sphere_at_a_point_on_the_Z_axis)
{
Sphere s = Sphere();
Tuple n = s.normalAt(Point(0, 0, 1));
ASSERT_EQ(n, Vector(0, 0, 1));
}
TEST(SphereTest, The_normal_on_a_sphere_at_a_nonaxial_point)
{
Sphere s = Sphere();
Tuple n = s.normalAt(Point(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3));
ASSERT_EQ(n, Vector(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3));
}
TEST(SphereTest, The_normal_is_a_normalise_vector)
{
Sphere s = Sphere();
Tuple n = s.normalAt(Point(sqrt(3)/3, sqrt(3)/3, sqrt(3)/3));
ASSERT_EQ(n, n.normalise());
}
TEST(SphereTest, Computing_the_normal_on_a_translated_sphere)
{
Sphere s = Sphere();
s.setTransform(translation(0, 1, 0));
Tuple n = s.normalAt(Point(0, 1.70711, -0.70711));
set_equal_precision(0.0001);
ASSERT_EQ(n, Vector(0, 0.70711, -0.70711));
/* Revert to default */
set_equal_precision(FLT_EPSILON);
}
TEST(SphereTest, Computing_the_normal_on_a_tranformed_sphere)
{
Sphere s = Sphere();
s.setTransform(scaling(1, 0.5, 1) * rotationZ(M_PI / 5));
Tuple n = s.normalAt(Point(0, sqrt(2)/2, -sqrt(2)/2));
set_equal_precision(0.0001);
ASSERT_EQ(n, Vector(0, 0.97014, -0.24254));
/* Revert to default */
set_equal_precision(FLT_EPSILON);
}
TEST(SphereTest, A_sphere_have_a_default_material)
{
Sphere s = Sphere();
Material m = Material();
ASSERT_EQ(s.material, m);
}
TEST(SphereTest, A_sphere_may_be_assigned_a_material)
{
Sphere s = Sphere();
Material m = Material();
m.ambient = 1;
s.setMaterial(m);
ASSERT_EQ(s.material, m);
}
TEST(SphereTest, A_helper_for_producing_a_sphere_with_a_glassy_material)
{
GlassSphere s = GlassSphere();
ASSERT_EQ(s.transformMatrix, Matrix4().identity());
ASSERT_EQ(s.material.transparency, 1.0);
ASSERT_EQ(s.material.refractiveIndex, 1.5);
}

View File

@@ -0,0 +1,248 @@
/*
* DoRayMe - a quick and dirty Raytracer
* Transformations unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <transformation.h>
#include <tuple.h>
#include <math.h>
#include <gtest/gtest.h>
TEST(TransformationTest, Multiplying_by_a_translation_matrix)
{
Matrix transform = translation(5, -3, 2);
Point p = Point(-3, 4, 5);
ASSERT_EQ(transform * p, Point(2, 1, 7));
}
TEST(TransformationTest, Multiplying_by_the_inverse_of_a_translation_matrix)
{
Matrix transform = translation(5, -3, 2);
Matrix inv = transform.inverse();
Point p = Point(-3, 4, 5);
ASSERT_EQ(inv * p, Point(-8, 7, 3));
}
TEST(TransformationTest, Translation_does_not_affect_vectors)
{
Matrix transform = translation(5, -3, 2);
Vector v = Vector(-3, 4, 5);
ASSERT_EQ(transform * v, Vector(-3, 4, 5));
}
TEST(TransformationTest, A_scaling_matrix_applied_to_a_point)
{
Matrix transform = scaling(2, 3, 4);
Point p = Point(-4, 6, 8);
ASSERT_EQ(transform * p, Point(-8, 18, 32));
}
TEST(TransformationTest, A_scaling_matrix_applied_to_a_vector)
{
Matrix transform = scaling(2, 3, 4);
Vector v = Vector(-4, 6, 8);
ASSERT_EQ(transform * v, Vector(-8, 18, 32));
}
TEST(TransformationTest, Multiplaying_by_the_inverse_of_a_scaling_matrix)
{
Matrix transform = scaling(2, 3, 4);
Matrix inv = transform.inverse();
Vector v = Vector(-4, 6, 8);
ASSERT_EQ(inv * v, Vector(-2, 2, 2));
}
TEST(TransformationTest, Reflexion_is_scaling_by_a_negative_value)
{
Matrix transform = scaling(-1, 1, 1);
Point p = Point(2, 3, 4);
ASSERT_EQ(transform * p, Point(-2, 3, 4));
}
TEST(TransformationTest, Rotating_a_point_around_the_X_axis)
{
Point p = Point(0, 1, 0);
Matrix half_quarter = rotationX(M_PI / 4.);
Matrix full_quarter = rotationX(M_PI / 2.);
ASSERT_EQ(half_quarter * p, Point(0, sqrt(2)/2, sqrt(2)/2));
ASSERT_EQ(full_quarter * p, Point(0, 0, 1));
}
TEST(TransformationTest, The_inverse_of_an_x_rotation_rotates_in_the_opposite_direction)
{
Point p = Point(0, 1, 0);
Matrix half_quarter = rotationX(M_PI / 4.);
Matrix inv = half_quarter.inverse();
ASSERT_EQ(inv * p, Point(0, sqrt(2)/2, -sqrt(2)/2));
}
TEST(TransformationTest, Rotating_a_point_around_the_Y_axis)
{
Point p = Point(0, 0, 1);
Matrix half_quarter = rotationY(M_PI / 4.);
Matrix full_quarter = rotationY(M_PI / 2.);
ASSERT_EQ(half_quarter * p, Point(sqrt(2)/2, 0, sqrt(2)/2));
ASSERT_EQ(full_quarter * p, Point(1, 0, 0));
}
TEST(TransformationTest, Rotating_a_point_around_the_Z_axis)
{
Point p = Point(0, 1, 0);
Matrix half_quarter = rotationZ(M_PI / 4.);
Matrix full_quarter = rotationZ(M_PI / 2.);
ASSERT_EQ(half_quarter * p, Point(-sqrt(2)/2, sqrt(2)/2, 0));
ASSERT_EQ(full_quarter * p, Point(-1, 0, 0));
}
TEST(TransformationTest, A_shearing_transformation_moves_x_in_proportion_to_y)
{
Matrix transform = shearing(1, 0, 0, 0, 0, 0);
Point p = Point(2, 3, 4);
ASSERT_EQ(transform * p, Point(5, 3, 4));
}
TEST(TransformationTest, A_shearing_transformation_moves_x_in_proportion_to_z)
{
Matrix transform = shearing(0, 1, 0, 0, 0, 0);
Point p = Point(2, 3, 4);
ASSERT_EQ(transform * p, Point(6, 3, 4));
}
TEST(TransformationTest, A_shearing_transformation_moves_y_in_proportion_to_x)
{
Matrix transform = shearing(0, 0, 1, 0, 0, 0);
Point p = Point(2, 3, 4);
ASSERT_EQ(transform * p, Point(2, 5, 4));
}
TEST(TransformationTest, A_shearing_transformation_moves_y_in_proportion_to_z)
{
Matrix transform = shearing(0, 0, 0, 1, 0, 0);
Point p = Point(2, 3, 4);
ASSERT_EQ(transform * p, Point(2, 7, 4));
}
TEST(TransformationTest, A_shearing_transformation_moves_z_in_proportion_to_x)
{
Matrix transform = shearing(0, 0, 0, 0, 1, 0);
Point p = Point(2, 3, 4);
ASSERT_EQ(transform * p, Point(2, 3, 6));
}
TEST(TransformationTest, A_shearing_transformation_moves_z_in_proportion_to_y)
{
Matrix transform = shearing(0, 0, 0, 0, 0, 1);
Point p = Point(2, 3, 4);
ASSERT_EQ(transform * p, Point(2, 3, 7));
}
TEST(TransformationTest, Individual_trnasformations_are_applied_in_sequence)
{
Point p = Point(1, 0, 1);
Matrix A = rotationX(M_PI / 2.);
Matrix B = scaling(5, 5, 5);
Matrix C = translation(10, 5, 7);
Tuple p2 = A * p;
ASSERT_EQ(p2, Point(1, -1, 0));
Tuple p3 = B * p2;
ASSERT_EQ(p3, Point(5, -5, 0));
Tuple p4 = C * p3;
ASSERT_EQ(p4, Point(15, 0, 7));
}
TEST(TransformationTest, Chained_transformation_must_be_applied_in_reverse_order)
{
Point p = Point(1, 0, 1);
Matrix A = rotationX(M_PI / 2.);
Matrix B = scaling(5, 5, 5);
Matrix C = translation(10, 5, 7);
Matrix T = C * B * A;
ASSERT_EQ(T * p, Point(15, 0, 7));
}
TEST(TransformationTest, The_transformation_matrix_for_the_default_orientation)
{
Tuple from = Point(0, 0, 0);
Tuple to = Point(0, 0, -1);
Tuple up = Vector(0, 1, 0);
Matrix t = viewTransform(from, to, up);
ASSERT_EQ(t, Matrix4().identity());
}
TEST(TransformationTest, A_view_transformation_matrix_looking_in_positive_z_direction)
{
Tuple from = Point(0, 0, 0);
Tuple to = Point(0, 0, 1);
Tuple up = Vector(0, 1, 0);
Matrix t = viewTransform(from, to, up);
ASSERT_EQ(t, scaling(-1, 1, -1));
}
TEST(TransformationTest, The_view_transformation_move_the_world)
{
Tuple from = Point(0, 0, 8);
Tuple to = Point(0, 0, 0);
Tuple up = Vector(0, 1, 0);
Matrix t = viewTransform(from, to, up);
ASSERT_EQ(t, translation(0, 0, -8));
}
TEST(TransformationTest, An_arbitrary_view_transformation)
{
Tuple from = Point(1, 3, 2);
Tuple to = Point(4, -2, 8);
Tuple up = Vector(1, 1, 0);
Matrix t = viewTransform(from, to, up);
double values[] = {-0.50709, 0.50709, 0.67612, -2.36643,
0.76772, 0.60609, 0.12122, -2.82843,
-0.35857, 0.59761, -0.71714, 0.00000,
0.00000, 0.00000, 0.00000, 1.00000};
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(t, Matrix4(values));
set_equal_precision(FLT_EPSILON);
}

View File

@@ -1,16 +1,16 @@
/* /*
* DoRayMe - a quick and dirty Raytracer * DoRayMe - a quick and dirty Raytracer
* Tuples tests * Tuples unit tests
* *
* Created by Manoël Trapier * Created by Manoël Trapier
* Copyright (c) 2020 986-Studio. * Copyright (c) 2020 986-Studio.
* *
*/ */
#include <tuples.h> #include <tuple.h>
#include <math.h> #include <math.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
TEST(TuplesTests, Tuple_With_w_equal_1_and_is_point) TEST(TupleTest, Tuple_With_w_equal_1_and_is_point)
{ {
Tuple a = Tuple(4.3, -4.2, 3.1, 1.0); Tuple a = Tuple(4.3, -4.2, 3.1, 1.0);
@@ -22,7 +22,7 @@ TEST(TuplesTests, Tuple_With_w_equal_1_and_is_point)
ASSERT_FALSE(a.isVector()); ASSERT_FALSE(a.isVector());
} }
TEST(TuplesTests, Tuple_With_w_equal_0_and_is_vector) TEST(TupleTest, Tuple_With_w_equal_0_and_is_vector)
{ {
Tuple a = Tuple(4.3, -4.2, 3.1, 0.0); Tuple a = Tuple(4.3, -4.2, 3.1, 0.0);
@@ -34,21 +34,21 @@ TEST(TuplesTests, Tuple_With_w_equal_0_and_is_vector)
ASSERT_TRUE(a.isVector()); ASSERT_TRUE(a.isVector());
} }
TEST(TuplesTests, Point_create_tuples_with_w_equal_1) TEST(TupleTest, Point_create_tuples_with_w_equal_1)
{ {
Tuple a = Point(4, -4, 3); Tuple a = Point(4, -4, 3);
ASSERT_EQ(a, Tuple(4, -4, 3, 1)); ASSERT_EQ(a, Tuple(4, -4, 3, 1));
} }
TEST(TuplesTests, Vector_create_tuples_with_w_equal_0) TEST(TupleTest, Vector_create_tuples_with_w_equal_0)
{ {
Tuple a = Vector(4, -4, 3); Tuple a = Vector(4, -4, 3);
ASSERT_EQ(a, Tuple(4, -4, 3, 0)); ASSERT_EQ(a, Tuple(4, -4, 3, 0));
} }
TEST(TuplesTests, Adding_two_tuples) TEST(TupleTest, Adding_two_tuples)
{ {
Tuple a1 = Tuple(3, -2, 5, 1); Tuple a1 = Tuple(3, -2, 5, 1);
Tuple a2 = Tuple(-2, 3, 1, 0); Tuple a2 = Tuple(-2, 3, 1, 0);
@@ -56,7 +56,7 @@ TEST(TuplesTests, Adding_two_tuples)
ASSERT_EQ(a1 + a2, Tuple(1, 1, 6, 1)); ASSERT_EQ(a1 + a2, Tuple(1, 1, 6, 1));
} }
TEST(TuplesTests, Substracting_two_points) TEST(TupleTest, Substracting_two_points)
{ {
Point p1 = Point(3, 2, 1); Point p1 = Point(3, 2, 1);
Point p2 = Point(5, 6, 7); Point p2 = Point(5, 6, 7);
@@ -64,7 +64,7 @@ TEST(TuplesTests, Substracting_two_points)
ASSERT_EQ(p1 - p2, Vector(-2, -4, -6)); ASSERT_EQ(p1 - p2, Vector(-2, -4, -6));
} }
TEST(TuplesTests, Substracting_a_vector_from_a_point) TEST(TupleTest, Substracting_a_vector_from_a_point)
{ {
Point p = Point(3, 2, 1); Point p = Point(3, 2, 1);
Vector v = Vector(5, 6, 7); Vector v = Vector(5, 6, 7);
@@ -72,7 +72,7 @@ TEST(TuplesTests, Substracting_a_vector_from_a_point)
ASSERT_EQ(p - v, Point(-2, -4, -6)); ASSERT_EQ(p - v, Point(-2, -4, -6));
} }
TEST(TuplesTests, Substracting_two_vectors) TEST(TupleTest, Substracting_two_vectors)
{ {
Vector v1 = Vector(3, 2, 1); Vector v1 = Vector(3, 2, 1);
Vector v2 = Vector(5, 6, 7); Vector v2 = Vector(5, 6, 7);
@@ -80,7 +80,7 @@ TEST(TuplesTests, Substracting_two_vectors)
ASSERT_EQ(v1 - v2, Vector(-2, -4, -6)); ASSERT_EQ(v1 - v2, Vector(-2, -4, -6));
} }
TEST(TuplesTests, Substracting_a_vector_from_zero_vector) TEST(TupleTest, Substracting_a_vector_from_zero_vector)
{ {
Vector zero = Vector(0, 0, 0); Vector zero = Vector(0, 0, 0);
Vector v = Vector(1, -2, 3); Vector v = Vector(1, -2, 3);
@@ -88,84 +88,84 @@ TEST(TuplesTests, Substracting_a_vector_from_zero_vector)
ASSERT_EQ(zero - v, Vector(-1, 2, -3)); ASSERT_EQ(zero - v, Vector(-1, 2, -3));
} }
TEST(TuplesTests, Negating_a_tuple) TEST(TupleTest, Negating_a_tuple)
{ {
Tuple a = Tuple(1, -2, 3, -4); Tuple a = Tuple(1, -2, 3, -4);
ASSERT_EQ(-a, Tuple(-1, 2, -3, 4)); ASSERT_EQ(-a, Tuple(-1, 2, -3, 4));
} }
TEST(TuplesTests, Multiplying_a_tuple_by_a_scalar) TEST(TupleTest, Multiplying_a_tuple_by_a_scalar)
{ {
Tuple a = Tuple(1, -2, 3, -4); Tuple a = Tuple(1, -2, 3, -4);
ASSERT_EQ(a * 3.5, Tuple(3.5, -7, 10.5, -14)); ASSERT_EQ(a * 3.5, Tuple(3.5, -7, 10.5, -14));
} }
TEST(TuplesTests, Multiplying_a_tuple_by_a_fraction) TEST(TupleTest, Multiplying_a_tuple_by_a_fraction)
{ {
Tuple a = Tuple(1, -2, 3, -4); Tuple a = Tuple(1, -2, 3, -4);
ASSERT_EQ(a * 0.5, Tuple(0.5, -1, 1.5, -2)); ASSERT_EQ(a * 0.5, Tuple(0.5, -1, 1.5, -2));
} }
TEST(TuplesTests, Dividing_a_tuple_by_a_scalar) TEST(TupleTest, Dividing_a_tuple_by_a_scalar)
{ {
Tuple a = Tuple(1, -2, 3, -4); Tuple a = Tuple(1, -2, 3, -4);
ASSERT_EQ(a / 2, Tuple(0.5, -1, 1.5, -2)); ASSERT_EQ(a / 2, Tuple(0.5, -1, 1.5, -2));
} }
TEST(TuplesTests, Computing_the_magnitude_of_vector_1_0_0) TEST(TupleTest, Computing_the_magnitude_of_vector_1_0_0)
{ {
Vector v = Vector(1, 0, 0); Vector v = Vector(1, 0, 0);
ASSERT_EQ(v.magnitude(), 1); ASSERT_EQ(v.magnitude(), 1);
} }
TEST(TuplesTests, Computing_the_magnitude_of_vector_0_1_0) TEST(TupleTest, Computing_the_magnitude_of_vector_0_1_0)
{ {
Vector v = Vector(0, 1, 0); Vector v = Vector(0, 1, 0);
ASSERT_EQ(v.magnitude(), 1); ASSERT_EQ(v.magnitude(), 1);
} }
TEST(TuplesTests, Computing_the_magnitude_of_vector_0_0_1) TEST(TupleTest, Computing_the_magnitude_of_vector_0_0_1)
{ {
Vector v = Vector(0, 0, 1); Vector v = Vector(0, 0, 1);
ASSERT_EQ(v.magnitude(), 1); ASSERT_EQ(v.magnitude(), 1);
} }
TEST(TuplesTests, Computing_the_magnitude_of_vector_1_2_3) TEST(TupleTest, Computing_the_magnitude_of_vector_1_2_3)
{ {
Vector v = Vector(1, 2, 3); Vector v = Vector(1, 2, 3);
ASSERT_EQ(v.magnitude(), sqrt(14)); ASSERT_EQ(v.magnitude(), sqrt(14));
} }
TEST(TuplesTests, Computing_the_magnitude_of_vector_n1_n2_n3) TEST(TupleTest, Computing_the_magnitude_of_vector_n1_n2_n3)
{ {
Vector v = Vector(-1, -2, -3); Vector v = Vector(-1, -2, -3);
ASSERT_EQ(v.magnitude(), sqrt(14)); ASSERT_EQ(v.magnitude(), sqrt(14));
} }
TEST(TuplesTests, Nomilise_vector_4_0_0_give_1_0_0) TEST(TupleTest, Nomilise_vector_4_0_0_give_1_0_0)
{ {
Vector v = Vector(4, 0, 0); Vector v = Vector(4, 0, 0);
ASSERT_EQ(v.normalise(), Vector(1, 0, 0)); ASSERT_EQ(v.normalise(), Vector(1, 0, 0));
} }
TEST(TuplesTests, Nomilise_vector_1_2_3) TEST(TupleTest, Nomilise_vector_1_2_3)
{ {
Vector v = Vector(1, 2, 3); Vector v = Vector(1, 2, 3);
ASSERT_EQ(v.normalise(), Vector(1 / sqrt(14), 2 / sqrt(14), 3 / sqrt(14))); ASSERT_EQ(v.normalise(), Vector(1 / sqrt(14), 2 / sqrt(14), 3 / sqrt(14)));
} }
TEST(TuplesTests, Dot_product_of_two_tuples) TEST(TupleTest, Dot_product_of_two_tuples)
{ {
Vector a = Vector(1, 2, 3); Vector a = Vector(1, 2, 3);
Vector b = Vector(2, 3, 4); Vector b = Vector(2, 3, 4);
@@ -173,7 +173,7 @@ TEST(TuplesTests, Dot_product_of_two_tuples)
ASSERT_EQ(a.dot(b), 20); ASSERT_EQ(a.dot(b), 20);
} }
TEST(TuplesTests, Cross_product_of_two_vector) TEST(TupleTest, Cross_product_of_two_vector)
{ {
Vector a = Vector(1, 2, 3); Vector a = Vector(1, 2, 3);
Vector b = Vector(2, 3, 4); Vector b = Vector(2, 3, 4);
@@ -181,3 +181,23 @@ TEST(TuplesTests, Cross_product_of_two_vector)
ASSERT_EQ(a.cross(b), Vector(-1, 2, -1)); ASSERT_EQ(a.cross(b), Vector(-1, 2, -1));
ASSERT_EQ(b.cross(a), Vector(1, -2, 1)); ASSERT_EQ(b.cross(a), Vector(1, -2, 1));
} }
TEST(TupleTest, Reflecting_a_vector_approaching_at_45)
{
Vector v = Vector(1, -1, 0); /* Vector */
Vector n = Vector(0, 1, 0); /* Normal */
Tuple r = v.reflect(n);
ASSERT_EQ(r, Vector(1, 1, 0));
}
TEST(TupleTest, Reflecting_a_vector_off_a_slanted_surface)
{
Vector v = Vector(0, -1, 0); /* Vector */
Vector n = Vector(sqrt(2)/2, sqrt(2)/2, 0); /* Normal */
Tuple r = v.reflect(n);
ASSERT_EQ(r, Vector(1, 0, 0));
}

429
tests/world_test.cpp Normal file
View File

@@ -0,0 +1,429 @@
/*
* DoRayMe - a quick and dirty Raytracer
* World unit tests
*
* Created by Manoël Trapier
* Copyright (c) 2020 986-Studio.
*
*/
#include <world.h>
#include <light.h>
#include <sphere.h>
#include <material.h>
#include <transformation.h>
#include <worldbuilder.h>
#include <testpattern.h>
#include <math.h>
#include <gtest/gtest.h>
#include <plane.h>
TEST(WorldTest, Creating_a_world)
{
World w;
ASSERT_EQ(w.lightCount, 0);
ASSERT_EQ(w.objectCount, 0);
}
TEST(WorldTest, The_default_world)
{
World w = DefaultWorld();
Light l = Light(POINT_LIGHT, Point(-10, 10, -10), Colour(1, 1, 1));
Sphere s1 = Sphere();
Sphere s2 = Sphere();
Material s1Mat = Material();
s1Mat.colour = Colour(0.8, 1.0, 0.6);
s1Mat.diffuse = 0.7;
s1Mat.specular = 0.2;
s1.setMaterial(s1Mat);
s2.setTransform(scaling(0.5, 0.5,0.5));
ASSERT_TRUE(w.lightIsIn(l));
ASSERT_TRUE(w.objectIsIn(s1));
ASSERT_TRUE(w.objectIsIn(s2));
};
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);
ASSERT_EQ(xs.count(), 4);
ASSERT_EQ(xs[0].t, 4);
ASSERT_EQ(xs[1].t, 4.5);
ASSERT_EQ(xs[2].t, 5.5);
ASSERT_EQ(xs[3].t, 6);
}
TEST(WorldTest, Shading_an_intersection)
{
World w = DefaultWorld();
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Shape *s = w.getObject(0);
Intersection i = Intersection(4, s);
Computation comps = i.prepareComputation(r);
Tuple c = w.shadeHit(comps);
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(c, Colour(0.38066, 0.47583, 0.2855));
set_equal_precision(FLT_EPSILON);
}
TEST(WorldTest, The_when_ray_miss)
{
World w = DefaultWorld();
Ray r = Ray(Point(0, 0, -5), Vector(0, 1, 0));
Tuple c = w.colourAt(r);
ASSERT_EQ(c, Colour(0, 0, 0));
}
TEST(WorldTest, The_when_ray_hit)
{
World w = DefaultWorld();
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Tuple c = w.colourAt(r);
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(c, Colour(0.38066, 0.47583, 0.2855));
set_equal_precision(FLT_EPSILON);
}
TEST(WorldTest, The_colour_with_an_intersection_behind_the_ray)
{
World w = DefaultWorld();
Shape *outer = w.getObject(0);
outer->material.ambient = 1;
Shape *inner = w.getObject(1);
inner->material.ambient = 1;
Ray r = Ray(Point(0, 0, 0.75), Vector(0, 0, -1));
Tuple c = w.colourAt(r);
ASSERT_EQ(c, inner->material.colour);
}
TEST(WorldTest, There_is_no_shadow_when_nothing_is_collinear_with_point_and_light)
{
World w = DefaultWorld();
Tuple p = Point(0, 10, 0);
ASSERT_FALSE(w.isShadowed(p));
}
TEST(WorldTest, The_shadow_when_an_object_is_between_the_point_and_the_light)
{
World w = DefaultWorld();
Tuple p = Point(10, -10, 10);
ASSERT_TRUE(w.isShadowed(p));
}
TEST(WorldTest, There_is_no_shadow_whne_an_object_is_behing_the_light)
{
World w = DefaultWorld();
Tuple p = Point(-20, 20, -20);
ASSERT_FALSE(w.isShadowed(p));
}
TEST(WorldTest, There_is_no_shadow_when_an_object_is_behing_the_point)
{
World w = DefaultWorld();
Tuple p = Point(-2, 2, -2);
ASSERT_FALSE(w.isShadowed(p));
}
TEST(WorldTest, Shade_hit_is_given_an_intersection_in_shadow)
{
World w = World();
Light l = Light(POINT_LIGHT, Point(0, 0, -10), Colour(1, 1, 1));
w.addLight(&l);
Sphere s1 = Sphere();
w.addObject(&s1);
Sphere s2 = Sphere();
s2.setTransform(translation(0, 0, 10));
w.addObject(&s2);
Ray r = Ray(Point(0, 0, 5), Vector(0, 0, 1));
Intersection i = Intersection(4, &s2);
Computation comps = i.prepareComputation(r);
Tuple c = w.shadeHit(comps);
ASSERT_EQ(c, Colour(0.1, 0.1, 0.1));
};
TEST(WorldTest, The_reflected_colour_for_a_non_reflective_material)
{
World w = DefaultWorld();
Ray r = Ray(Point(0, 0, 0), Vector(0, 0, 1));
Shape *shape = w.getObject(1); /* The second object */
shape->material.ambient = 1; /* We use this to get a predictable colour */
Intersection i = Intersection(1, shape);
Computation comps = i.prepareComputation(r);
Colour colour = w.reflectColour(comps);
ASSERT_EQ(colour, Colour(0, 0, 0));
}
TEST(WorldTest, The_reflected_colour_for_a_reflective_material)
{
World w = DefaultWorld();
Plane shape = Plane();
shape.material.reflective = 0.5;
shape.setTransform(translation(0, -1, 0));
w.addObject(&shape);
Ray r = Ray(Point(0, 0, -3), Vector(0, -sqrt(2)/2, sqrt(2)/2));
Intersection i = Intersection(sqrt(2), &shape);
Computation comps = i.prepareComputation(r);
Colour colour = w.reflectColour(comps);
/* Temporary lower the precision */
set_equal_precision(0.00002);
ASSERT_EQ(colour, Colour(0.19032, 0.2379, 0.14274));
set_equal_precision(FLT_EPSILON);
}
TEST(WorldTest, Shade_hit_with_a_reflective_material)
{
World w = DefaultWorld();
Plane shape = Plane();
shape.material.reflective = 0.5;
shape.setTransform(translation(0, -1, 0));
w.addObject(&shape);
Ray r = Ray(Point(0, 0, -3), Vector(0, -sqrt(2)/2, sqrt(2)/2));
Intersection i = Intersection(sqrt(2), &shape);
Computation comps = i.prepareComputation(r);
Tuple colour = w.shadeHit(comps);
/* Temporary lower the precision */
set_equal_precision(0.00005);
ASSERT_EQ(colour, Colour(0.87677, 0.92436, 0.82918));
set_equal_precision(FLT_EPSILON);
}
TEST(WorldTest, Colour_at_with_mutually_reflective_surfaces)
{
World w = World();
Light l = Light(POINT_LIGHT, Point(0, 0, 0), Colour(1, 1, 1));
w.addLight(&l);
Plane lower = Plane();
lower.material.reflective = 1;
lower.setTransform(translation(0, -1, 0));
Plane higher = Plane();
higher.material.reflective = 1;
higher.setTransform(translation(0, 1, 0));
w.addObject(&lower);
w.addObject(&higher);
Ray r = Ray(Point(0, 0, 0), Vector(0, 1, 0));
/* It should just exit, we don't care about the actual colour */
w.colourAt(r);
SUCCEED();
}
TEST(WorldTest, The_reflected_colour_at_the_maximum_recursion_depth)
{
World w = DefaultWorld();
Plane shape = Plane();
shape.material.reflective = 0.5;
shape.setTransform(translation(0, -1, 0));
w.addObject(&shape);
Ray r = Ray(Point(0, 0, -3), Vector(0, -sqrt(2)/2, sqrt(2)/2));
Intersection i = Intersection(sqrt(2), &shape);
Computation comps = i.prepareComputation(r);
Tuple colour = w.reflectColour(comps, 0);
/* Temporary lower the precision */
ASSERT_EQ(colour, Colour(0, 0, 0));
}
TEST(WorldTest, The_refracted_colour_with_an_opaque_surface)
{
World w = DefaultWorld();
Shape *shape = w.getObject(0);
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Intersect xs = Intersect();
xs.add(Intersection(4, shape));
xs.add(Intersection(6, shape));
Computation comps = xs[0].prepareComputation(r, &xs);
Colour c = w.refractedColour(comps, 5);
ASSERT_EQ(c, Colour(0, 0, 0));
}
TEST(WorldTest, The_refracted_colour_at_the_maximum_recursive_depth)
{
World w = DefaultWorld();
Shape *shape = w.getObject(0);
shape->material.transparency = 1.0;
shape->material.refractiveIndex = 1.5;
Ray r = Ray(Point(0, 0, -5), Vector(0, 0, 1));
Intersect xs = Intersect();
xs.add(Intersection(4, shape));
xs.add(Intersection(6, shape));
Computation comps = xs[0].prepareComputation(r, &xs);
Colour c = w.refractedColour(comps, 0);
ASSERT_EQ(c, Colour(0, 0, 0));
}
TEST(WorldTest, The_refracted_colour_under_total_internal_reflection)
{
World w = DefaultWorld();
Shape *shape = w.getObject(0);
shape->material.transparency = 1.0;
shape->material.refractiveIndex = 1.5;
Ray r = Ray(Point(0, 0, sqrt(2)/2), Vector(0, 1, 0));
Intersect xs = Intersect();
xs.add(Intersection(-sqrt(2)/2, shape));
xs.add(Intersection(sqrt(2)/2, shape));
Computation comps = xs[1].prepareComputation(r, &xs);
Colour c = w.refractedColour(comps, 5);
ASSERT_EQ(c, Colour(0, 0, 0));
}
TEST(WorldTest, The_refracted_coloud_with_a_refracted_ray)
{
World w = DefaultWorld();
Shape *A = w.getObject(0);
A->material.ambient = 1.0;
A->material.pattern = new TestPattern();
Shape *B = w.getObject(1);
B->material.transparency = 1.0;
B->material.refractiveIndex = 1.5;
Ray r = Ray(Point(0, 0, 0.1), Vector(0, 1, 0));
Intersect xs = Intersect();
xs.add(Intersection(-0.9899, A));
xs.add(Intersection(-0.4899, B));
xs.add(Intersection(0.4899, B));
xs.add(Intersection(0.9899, A));
Computation comps = xs[2].prepareComputation(r, &xs);
Colour c = w.refractedColour(comps, 5);
/* Temporary lower the precision */
set_equal_precision(0.00005);
ASSERT_EQ(c, Colour(0, 0.99888, 0.04725));
set_equal_precision(FLT_EPSILON);
}
TEST(WorldTest, Shade_hit_with_a_transparent_material)
{
World w = DefaultWorld();
Plane floor = Plane();
floor.setTransform(translation(0, -1, 0));
floor.material.transparency = 0.5;
floor.material.refractiveIndex = 1.5;
w.addObject(&floor);
Sphere ball = Sphere();
ball.material.colour = Colour(1, 0, 0);
ball.material.ambient = 0.5;
ball.setTransform(translation(0, -3.5, -0.5));
w.addObject(&ball);
Ray r = Ray(Point(0, 0, -3), Vector(0, -sqrt(2)/2, sqrt(2)/2));
Intersect xs = Intersect();
xs.add(Intersection(sqrt(2), &floor));
Computation comps = xs[0].prepareComputation(r, &xs);
Tuple c = w.shadeHit(comps, 5);
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(c, Colour(0.93642, 0.68642, 0.68642));
set_equal_precision(FLT_EPSILON);
}
TEST(WorldTest, Shade_hit_with_a_reflective_transparent_material)
{
World w = DefaultWorld();
Ray r = Ray(Point(0, 0, -3), Vector(0, -sqrt(2)/2, sqrt(2)/2));
Plane floor = Plane();
floor.setTransform(translation(0, -1, 0));
floor.material.transparency = 0.5;
floor.material.reflective = 0.5;
floor.material.refractiveIndex = 1.5;
w.addObject(&floor);
Sphere ball = Sphere();
ball.material.colour = Colour(1, 0, 0);
ball.material.ambient = 0.5;
ball.setTransform(translation(0, -3.5, -0.5));
w.addObject(&ball);
Intersect xs = Intersect();
xs.add(Intersection(sqrt(2), &floor));
Computation comps = xs[0].prepareComputation(r, &xs);
Tuple c = w.shadeHit(comps, 5);
/* Temporary lower the precision */
set_equal_precision(0.00001);
ASSERT_EQ(c, Colour(0.93391, 0.69643, 0.69243));
set_equal_precision(FLT_EPSILON);
}