Skip to content

Commit

Permalink
Plane added
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesmcm committed Nov 10, 2019
1 parent 7c7fc15 commit 55192d5
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 29 deletions.
2 changes: 2 additions & 0 deletions .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
align = more // For pretty alignment.
maxColumn = 100 // For my wide 30" display.
Binary file added output/plane.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions src/main/scala/raytracer/Demo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -114,5 +114,31 @@ object Demo {
stringToFile("scene4.ppm", canvas.toPPM)

}
def planeScene(): Unit = {
val floorMaterial: Material = Material.defaultMaterial().setColour(Colour(1, 0.9, 0.9)).setSpecular(0)
val floor: Plane = Plane().setMaterial(floorMaterial)

val middleMaterial: Material = Material.defaultMaterial().setColour(
Colour(0.1, 1, 0.5)).setDiffuse(0.7).setSpecular(0.3)
val middleSphere: Sphere = Sphere.unitSphere().setTransform(
Translation(-0.5, 1, 0.5)).setMaterial(middleMaterial)

val rightMaterial: Material = middleMaterial.setColour(Colour(0.5, 1, 0.1))
val rightSphere: Sphere = Sphere.unitSphere().setTransform(
Translation(1.5, 0.5, -0.5)*Scaling(0.5, 0.5, 0.5)).setMaterial(rightMaterial)

val leftMaterial: Material = middleMaterial.setColour(Colour(1, 0.8, 0.1))
val leftSphere: Sphere = Sphere.unitSphere().setTransform(
Translation(-1.5, 0.33, -0.75)*Scaling(0.33, 0.33, 0.33)).setMaterial(leftMaterial)

val world: World = World(List(Light.pointLight(Point(-10, 10, -10), Colour(1, 1, 1))),
List(floor, leftSphere, rightSphere, middleSphere))

val camera: Camera = Camera(800, 600, math.Pi/3).setTransform(viewTransform(Point(0, 1.5, -5),
Point(0, 1, 0), Vector(0, 1, 0)))

val canvas: Canvas = camera.render(world)
stringToFile("scene5.ppm", canvas.toPPM)
}

}
3 changes: 2 additions & 1 deletion src/main/scala/raytracer/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ object Main extends App {
// drawClock(200)
// castSphereSilhouette(100)
// Demo.lightSphere(2000)
Demo.firstScene()
// Demo.firstScene()
Demo.planeScene()


}
Expand Down
53 changes: 53 additions & 0 deletions src/main/scala/raytracer/Plane.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright (C) 2019 James McMurray
//
// raytracer_challenge 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 3 of the License, or
// (at your option) any later version.
//
// raytracer_challenge 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 raytracer_challenge If not, see <http://www.gnu.org/licenses/>.

package raytracer

class Plane(val transform: Matrix, val material: Material) extends SpaceObject {
type T = Plane

def constructor(t: Matrix, m: Material): T = new Plane(t, m)

final override def equals(that: Any): Boolean = {
that match {
case that: Plane => transform === that.transform && material === that.material
case _ => false
}
}

final def ===(that: Plane): Boolean = {
transform === that.transform && material === that.material
}

final override def hashCode: Int = (transform, material).##


def localIntersect(r: Ray): Seq[Intersection] = {
if (math.abs(r.direction.y) < EPSILON) (List[Intersection]()) else {
val t: Double = -r.origin.y / r.direction.y
List(new Intersection(t, this))
}
}


def localNormalAt(p: RTTuple): RTTuple = {
Vector(0, 1, 0)
}
}


object Plane {
def apply(): Plane = new Plane(Matrix.getIdentityMatrix(4), Material.defaultMaterial())
}
61 changes: 33 additions & 28 deletions src/main/scala/raytracer/Sphere.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,71 +15,76 @@

package raytracer

import cats.implicits._

class Sphere(val origin: RTTuple, val radius: Double, val transform: Matrix, val material: Material) extends SpaceObject {
class Sphere(val transform: Matrix, val material: Material) extends SpaceObject {
// Note origin and radius always assumed as unit sphere
// Use setTransform for transformations
type T = Sphere
def constructor(t: Matrix, m: Material): T = new Sphere(t, m)

final override def equals(that: Any): Boolean = {
that match {
case that: Sphere => origin === that.origin && radius === that.radius && transform === that.transform && material === that.material
case that: Sphere => transform === that.transform && material === that.material
case _ => false
}
}

final def ===(that: Sphere): Boolean = {
origin === that.origin && radius === that.radius && transform === that.transform && material === that.material
transform === that.transform && material === that.material
}

final override def hashCode: Int = (origin, radius, transform, material).##
final override def hashCode: Int = (transform, material).##


def intersect(r: Ray): Seq[Intersection] = {
val (discriminant, a ,b): (Double, Double, Double) = getDiscriminant(r.transform(transform.inverse))
def localIntersect(r: Ray): Seq[Intersection] = {
val (discriminant, a, b): (Double, Double, Double) = getDiscriminant(r)
discriminant match {
case x if x < 0 => List()
case _ => List(-b - math.sqrt(discriminant), -b + math.sqrt(discriminant)).map(_/(2*a)).map(
case _ => List(-b - math.sqrt(discriminant), -b + math.sqrt(discriminant)).map(_ / (2 * a)).map(
(t: Double) => new Intersection(t, this))
}
}

def getDiscriminant(r: Ray): (Double, Double, Double) = {
val sphere_to_ray: RTTuple = r.origin - origin
val sphere_to_ray: RTTuple = r.origin - Point(0, 0, 0)

val a: Double = r.direction dot r.direction
val b: Double = 2 * (r.direction dot sphere_to_ray)
val c: Double = (sphere_to_ray dot sphere_to_ray) - 1

((b*b) - 4 * a * c, a, b)
}

def setTransform(m: Matrix): Sphere = {
new Sphere(origin, radius, m, material)
((b * b) - 4 * a * c, a, b)
}

def setMaterial(m: Material): Sphere = {
new Sphere(origin, radius, transform, m)
}

def normalAt(p: RTTuple): RTTuple = {
transform.inverse.transpose.tupleMult(transform.inverse.tupleMult(p) - origin).forceVector().normalise()
def localNormalAt(p: RTTuple): RTTuple = {
(p - Point(0, 0, 0))
}

}

object Sphere {
def unitSphere(): Sphere = new Sphere(Point(0,0,0), 1.0,
Matrix.getIdentityMatrix(4), Material.defaultMaterial())
def unitSphere(): Sphere = new Sphere(Matrix.getIdentityMatrix(4), Material.defaultMaterial())
}

// TODO: Move me
abstract class SpaceObject(){
abstract class SpaceObject() {
type T <: SpaceObject
val material: Material
val transform: Matrix

def normalAt(p: RTTuple): RTTuple
def intersect(r: Ray): Seq[Intersection]
def setMaterial(m: Material): SpaceObject
def setTransform(m: Matrix): SpaceObject
def constructor(t: Matrix, m: Material): T

def localNormalAt(p: RTTuple): RTTuple
def normalAt(p: RTTuple): RTTuple = {
val localPoint: RTTuple = transform.inverse.tupleMult(p)

transform.inverse.transpose.tupleMult(localNormalAt(localPoint)).forceVector().normalise()
}

def localIntersect(r: Ray): Seq[Intersection]
def intersect(r: Ray): Seq[Intersection] = {
localIntersect(r.transform(transform.inverse))
}

def setMaterial(m: Material): T = constructor(transform, m)

def setTransform(m: Matrix): T = constructor(m, material)
}
60 changes: 60 additions & 0 deletions src/test/scala/raytracer/PlaneTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (C) 2019 James McMurray
//
// raytracer_challenge 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 3 of the License, or
// (at your option) any later version.
//
// raytracer_challenge 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 raytracer_challenge If not, see <http://www.gnu.org/licenses/>.

package raytracer

import org.scalatest.FunSuite

class PlaneTest extends FunSuite {
test("Plane.test_normal") {
val p: Plane = Plane()
val n1: RTTuple = p.localNormalAt(Point(0, 0, 0))
val n2: RTTuple = p.localNormalAt(Point(10, 0, -10))
val n3: RTTuple = p.localNormalAt(Point(-5, 0, 150))
assert(n1 === Vector(0, 1, 0) && n2 === Vector(0, 1, 0) &&
n3 === Vector(0, 1, 0))
}

test("Plane.test_parallel") {
val p: Plane = Plane()
val r: Ray = Ray(Point(0, 10, 0), Vector(0, 0, 1))

val xs: Seq[Intersection] = p.localIntersect(r)
assert(xs.isEmpty)
}
test("Plane.test_coplanar") {
val p: Plane = Plane()
val r: Ray = Ray(Point(0, 0, 0), Vector(0, 0, 1))

val xs: Seq[Intersection] = p.localIntersect(r)
assert(xs.isEmpty)
}
test("Plane.test_intersect_above") {
val p: Plane = Plane()
val r: Ray = Ray(Point(0, 1, 0), Vector(0, -1, 0))

val xs: Seq[Intersection] = p.localIntersect(r)
assert(xs.length === 1 && xs(0).t === 1 && xs(0).shape === p)
}
test("Plane.test_intersect_below") {
val p: Plane = Plane()
val r: Ray = Ray(Point(0, -1, 0), Vector(0, 1, 0))

val xs: Seq[Intersection] = p.localIntersect(r)
assert(xs.length === 1 && xs(0).t === 1 && xs(0).shape === p)
}


}
9 changes: 9 additions & 0 deletions src/test/scala/raytracer/SphereTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,13 @@ class SphereTest extends FunSuite {

assert(s.material === m)
}
test("Sphere.test_superclass") {
val m: Material = Material.defaultMaterial().setAmbient(1)
val s: Sphere = Sphere.unitSphere().setMaterial(m)

assert(s match {
case x: SpaceObject => true
case _ => false
})
}
}

0 comments on commit 55192d5

Please sign in to comment.