Skip to content

Commit

Permalink
Chapter 14: World rendered but not working.
Browse files Browse the repository at this point in the history
  • Loading branch information
sraaphorst committed Jan 19, 2023
1 parent ecda557 commit 6c20bbf
Show file tree
Hide file tree
Showing 10 changed files with 330 additions and 0 deletions.
91 changes: 91 additions & 0 deletions src/main/kotlin/apps/ch14_world.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package apps

// By Sebastian Raaphorst, 2023.
// From https://forum.raytracerchallenge.com/thread/13/groups-scene-description

import light.PointLight
import material.Material
import math.Color
import math.Matrix
import math.Tuple
import scene.Camera
import scene.World
import shapes.*
import java.io.File
import kotlin.math.PI
import kotlin.system.measureTimeMillis

fun main() {
val transforms = listOf(0, PI / 3, 2 * PI / 3, PI, 4 * PI / 3, 5 * PI / 3)
.map(Matrix::rotateY)

val leg = run {
val sphere = Sphere(Matrix.translate(0, 0, -1) * Matrix.scale(0.25, 0.25, 0.25))
val cylinder = Cylinder(0, 1, false,
Matrix.translate(0, 0, -1) * Matrix.rotateY(-PI / 6) *
Matrix.rotateZ(-PI / 2) * Matrix.scale(0.25, 1, 0.25))
Group(children = listOf(sphere, cylinder))
}

val cap = run {
val trans = Matrix.rotateX(-PI / 4 ) * Matrix.scale(0.24606, 1.37002, 0.24606)
val cones = transforms.map {
Cone(-1, 0, false, it * trans)
}
Group(children = cones)
}

val wacky = run {
val legs = transforms.map(leg::withTransformation)
val cap1 = cap.withTransformation(Matrix.translate(0, 1, 0))
val cap2 = cap.withTransformation(Matrix.rotateX(PI) * Matrix.translate(0, 1, 0))
Group(children = legs + listOf(cap1, cap2))
}

val backdrop = run {
val t = Matrix.translate(0, 0, 100) * Matrix.rotateX(PI / 2)
val m = Material(Color.WHITE, ambient = 1, diffuse = 0, specular = 0)
Plane(t, m)
}

val wacky1 = run {
val t = Matrix.translate(-2.8, 0, 0) * Matrix.rotateX(0.4363) * Matrix.rotateY(PI / 18)
val m = Material(Color(0.9, 0.2, 0.4), ambient = 0.2, diffuse = 0.8, specular = 0.7, shininess = 20)
wacky.withTransformation(t).withMaterial(m)
}

val wacky2 = run {
val t = Matrix.rotateY(PI / 18)
val m = Material(Color(0.2, 0.9, 0.6), ambient = 0.2, diffuse = 0.8, specular = 0.7, shininess = 20)
wacky.withTransformation(t).withMaterial(m)
}

val wacky3 = run {
val t = Matrix.translate(2.8, 0, 0) * Matrix.rotateX(-0.4363) * Matrix.rotateY(-PI / 18)
val m = Material(Color(0.2, 0.3, 1), ambient = 0.2, diffuse = 0.8, specular = 0.7, shininess = 20)
wacky.withTransformation(t).withMaterial(m)
}

val world = run {
val color = Color(0.25, 0.25, 0.25)
val light1 = PointLight(Tuple.point(10_000, 10_000, -10_000), color)
val light2 = PointLight(Tuple.point(-10_000, 10_000, -10_000), color)
val light3 = PointLight(Tuple.point(10_000, -10_000, -10_000), color)
val light4 = PointLight(Tuple.point(-10_000, -10_000, -10_000), color)
World(listOf(backdrop, wacky1, wacky2, wacky3), listOf(light1, light2, light3, light4))
}

val camera = run {
val from = Tuple.point(0, 0, -9)
val to = Tuple.PZERO
val t = from.viewTransformationFrom(to, Tuple.VY)
Camera(2400, 1200, 0.9, t)
}

val elapsed = measureTimeMillis {
val canvas = camera.render(world)
canvas.toPPMFile(File("output/ch14_world.ppm"))
}
println("Time elapsed: ${elapsed / 1000.0} s")
}

205 changes: 205 additions & 0 deletions src/main/kotlin/apps/ch14_world.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# ======================================================
# group.yml
#
# This file describes the scene illustrated at the start
# of chapter 14, "Groups", in "The Ray Tracer
# Challenge"
#
# This scene description assumes:
#
# 1. Your ray tracer supports multiple light sources.
# If it does not, you can omit the extra light
# sources and bump up the existing light's intensity
# to [1, 1, 1].
# 2. Child objects in a group inherit their default
# material from the parent group. If you haven't
# implemented this optional feature, you'll need
# arrange other means of texturing the child
# elements (or accept that all elements of the
# scene will be white).
#
# by Jamis Buck <[email protected]>
# ======================================================

# ======================================================
# the camera
# ======================================================

- add: camera
width: 300
height: 100
field-of-view: 0.9
from: [0, 0, -9]
to: [0, 0, 0]
up: [0, 1, 0]

# ======================================================
# light sources
# ======================================================

- add: light
at: [ 10000, 10000, -10000 ]
intensity: [ 0.25, 0.25, 0.25 ]
- add: light
at: [ -10000, 10000, -10000 ]
intensity: [ 0.25, 0.25, 0.25 ]
- add: light
at: [ 10000, -10000, -10000 ]
intensity: [ 0.25, 0.25, 0.25 ]
- add: light
at: [ -10000, -10000, -10000 ]
intensity: [ 0.25, 0.25, 0.25 ]

# ======================================================
# These describe groups that will be reused within
# the scene. (You can think of these as functions that
# return a new instance of the given shape each time they
# are referenced.)
# ======================================================

- define: leg
value:
add: group
children:
- add: sphere
transform:
- [ scale, 0.25, 0.25, 0.25 ]
- [ translate, 0, 0, -1 ]
- add: cylinder
min: 0
max: 1
closed: false
transform:
- [ scale, 0.25, 1, 0.25 ]
- [ rotate-z, -1.5708 ]
- [ rotate-y, -0.5236 ]
- [ translate, 0, 0, -1 ]

- define: cap
value:
add: group
children:
- add: cone
min: -1
max: 0
closed: false
transform:
- [ scale, 0.24606, 1.37002, 0.24606 ]
- [ rotate-x, -0.7854 ]
- add: cone
min: -1
max: 0
closed: false
transform:
- [ scale, 0.24606, 1.37002, 0.24606 ]
- [ rotate-x, -0.7854 ]
- [ rotate-y, 1.0472 ]
- add: cone
min: -1
max: 0
closed: false
transform:
- [ scale, 0.24606, 1.37002, 0.24606 ]
- [ rotate-x, -0.7854 ]
- [ rotate-y, 2.0944 ]
- add: cone
min: -1
max: 0
closed: false
transform:
- [ scale, 0.24606, 1.37002, 0.24606 ]
- [ rotate-x, -0.7854 ]
- [ rotate-y, 3.1416 ]
- add: cone
min: -1
max: 0
closed: false
transform:
- [ scale, 0.24606, 1.37002, 0.24606 ]
- [ rotate-x, -0.7854 ]
- [ rotate-y, 4.1888 ]
- add: cone
min: -1
max: 0
closed: false
transform:
- [ scale, 0.24606, 1.37002, 0.24606 ]
- [ rotate-x, -0.7854 ]
- [ rotate-y, 5.236 ]

- define: wacky
value:
add: group
children:
- add: leg
- add: leg
transform:
- [ rotate-y, 1.0472 ]
- add: leg
transform:
- [ rotate-y, 2.0944 ]
- add: leg
transform:
- [ rotate-y, 3.1416 ]
- add: leg
transform:
- [ rotate-y, 4.1888 ]
- add: leg
transform:
- [ rotate-y, 5.236 ]
- add: cap
transform:
- [ translate, 0, 1, 0 ]
- add: cap
transform:
- [ translate, 0, 1, 0 ]
- [ rotate-x, 3.1416 ]

# ======================================================
# Construct the scene itself
# ======================================================

# a white backdrop
- add: plane
transform:
- [ rotate-x, 1.5708 ]
- [ translate, 0, 0, 100 ]
material:
color: [ 1, 1, 1 ]
ambient: 1
diffuse: 0
specular: 0

- add: wacky
transform:
- [ rotate-y, 0.1745 ]
- [ rotate-x, 0.4363 ]
- [ translate, -2.8, 0, 0 ]
material:
color: [ 0.9, 0.2, 0.4 ]
ambient: 0.2
diffuse: 0.8
specular: 0.7
shininess: 20

- add: wacky
transform:
- [ rotate-y, 0.1745 ]
material:
color: [ 0.2, 0.9, 0.6 ]
ambient: 0.2
diffuse: 0.8
specular: 0.7
shininess: 20

- add: wacky
transform:
- [ rotate-y, -0.1745 ]
- [ rotate-x, -0.4363 ]
- [ translate, 2.8, 0, 0 ]
material:
color: [ 0.2, 0.3, 1.0 ]
ambient: 0.2
diffuse: 0.8
specular: 0.7
shininess: 20
3 changes: 3 additions & 0 deletions src/main/kotlin/shapes/Cone.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class Cone(minimum: Number = Double.NEGATIVE_INFINITY,
override fun withParent(parent: Shape?): Shape =
Cone(minimum, maximum, closed, transformation, material, castsShadow, parent)

override fun withMaterial(material: Material): Shape =
Cone(minimum, maximum, closed, transformation, material, castsShadow, parent)

override fun localIntersect(rayLocal: Ray): List<Intersection> {
val a = rayLocal.direction.x * rayLocal.direction.x -
rayLocal.direction.y * rayLocal.direction.y +
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/shapes/Cube.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class Cube(transformation: Matrix = Matrix.I,
override fun withParent(parent: Shape?): Shape =
Cube(transformation, material, castsShadow, parent)

override fun withMaterial(material: Material): Shape =
Cube(transformation, material, castsShadow, parent)

override fun localIntersect(rayLocal: Ray): List<Intersection> =
bounds.intersects(rayLocal).map { Intersection(it, this) }

Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/shapes/Cylinder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class Cylinder(minimum: Number = Double.NEGATIVE_INFINITY,
override fun withParent(parent: Shape?): Shape =
Cylinder(minimum, maximum, closed, transformation, material, castsShadow, parent)

override fun withMaterial(material: Material): Shape =
Cylinder(minimum, maximum, closed, transformation, material, castsShadow, parent)

override fun localIntersect(rayLocal: Ray): List<Intersection> {
val a = rayLocal.direction.x * rayLocal.direction.x + rayLocal.direction.z * rayLocal.direction.z

Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/shapes/Group.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ class Group(transformation: Matrix = Matrix.I,
override fun withParent(parent: Shape?): Shape =
Group(transformation, children, castsShadow, parent)

fun withTransformation(transformation: Matrix): Shape {
if (!transformation.isTransformation())
throw IllegalArgumentException("Shapes must have 4x4 transformation matrices:\n" +
"\tShape: ${javaClass.name}\nTransformation:\n${transformation.show()}")
return Group(transformation, children, castsShadow, parent)
}

override fun withMaterial(material: Material): Shape =
Group(transformation, children.map { it.withMaterial(material) }, castsShadow, parent)

// Only process children if the ray intersects the bounding box for this group.
override fun localIntersect(rayLocal: Ray): List<Intersection> =
if (bounds.intersects(rayLocal).isNotEmpty())
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/shapes/Plane.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Plane(transformation: Matrix = Matrix.I,
override fun withParent(parent: Shape?): Shape =
Plane(transformation, material, castsShadow, parent)

override fun withMaterial(material: Material): Shape =
Plane(transformation, material, castsShadow, parent)

override fun localIntersect(rayLocal: Ray): List<Intersection> {
if (rayLocal.direction.y.absoluteValue < DEFAULT_PRECISION)
return emptyList()
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/shapes/Shape.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ abstract class Shape(val transformation: Matrix,
// This method should only be invoked by Groups containing the object.
internal abstract fun withParent(parent: Shape? = null): Shape

// This method creates a copy of the object with the specified material.
abstract fun withMaterial(material: Material): Shape

// Convert a point from world space to object space.
// Later on, we use parent here.
internal fun worldToLocal(tuple: Tuple): Tuple =
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/shapes/Sphere.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class Sphere(transformation: Matrix = Matrix.I,
override fun withParent(parent: Shape?): Shape =
Sphere(transformation, material, castsShadow, parent)

override fun withMaterial(material: Material): Shape =
Sphere(transformation, material, castsShadow, parent)

override fun localIntersect(rayLocal: Ray): List<Intersection> {
val sphereToRay = rayLocal.origin - Tuple.PZERO

Expand Down
6 changes: 6 additions & 0 deletions src/test/kotlin/shapes/ShapeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ class ShapeTest {
return s
}

override fun withMaterial(material: Material): Shape {
val s = TestShape(transformation, material, parent)
s.savedRay = savedRay
return s
}

override fun localIntersect(rayLocal: Ray): List<Intersection> {
savedRay = rayLocal
return emptyList()
Expand Down

0 comments on commit 6c20bbf

Please sign in to comment.