Skip to content

Commit

Permalink
Merge pull request #16 from quephird/make_material_a_property_modifier
Browse files Browse the repository at this point in the history
Make material a property modifier
  • Loading branch information
quephird committed Oct 22, 2022
2 parents 4f80aee + f841ce3 commit ae570a5
Show file tree
Hide file tree
Showing 21 changed files with 212 additions and 191 deletions.
76 changes: 50 additions & 26 deletions README.md
Expand Up @@ -25,7 +25,8 @@ struct QuickStart: ScintillaApp {
point(0, 2, -2),
point(0, 0, 0),
vector(0, 1, 0)))
Sphere(.solidColor(Color(1, 0, 0)))
Sphere()
.material(.solidColor(Color(1, 0, 0)))
}
}
```
Expand Down Expand Up @@ -66,7 +67,7 @@ All shapes also have the following property modifiers for setting/updating the u
This means that you can chain operations together in a logical manner and not have to explicitly `let` out a transformation matrix and then pass it in to the shape's constructor, like this:

```swift
Cube(.basicMaterial())
Cube()
.shear(1, 1, 0, 1, 0, 0)
.scale(1, 2, 3)
.rotateX(PI/3)
Expand Down Expand Up @@ -99,9 +100,10 @@ struct MyWorld: ScintillaApp {
point(0, 0, -5),
point(0, 0, 0),
vector(0, 1, 0)))
ImplicitSurface(.solidColor(Color(0.2, 1, 0.5)), ((-2, -2, -2), (2, 2, 2))) { x, y, z in
ImplicitSurface(((-2, -2, -2), (2, 2, 2))) { x, y, z in
x*x + y*y + z*z + sin(4*x) + sin(4*y) + sin(4*z) - 1
}
.material(.solidColor(Color(0.2, 1, 0.5)))
}
}
```
Expand Down Expand Up @@ -172,14 +174,18 @@ There are three supported operations for combining various shapes:
The implementation for CSG takes advantage of so-called result builders, a feature of Swift that allows the programmer to list parameters to a function with minimal punctuation. Furthermore, Scintilla is responsible for nesting pairs of CSG operations so you don't have to, and so you can express the subtraction of three cylinders from a sphere like this:

```swift
Sphere(.solidColor(Color(0, 0, 1)))
Sphere()
.material(.solidColor(Color(0, 0, 1)))
.difference {
Cylinder(.solidColor(Color(0, 1, 0)))
Cylinder()
.material(.solidColor(Color(0, 1, 0)))
.scale(0.6, 0.6, 0.6)
Cylinder(.solidColor(Color(0, 1, 0)))
Cylinder()
.material(.solidColor(Color(0, 1, 0)))
.scale(0.6, 0.6, 0.6)
.rotateZ(PI/2)
Cylinder(.solidColor(Color(0, 1, 0)))
Cylinder()
.material(.solidColor(Color(0, 1, 0)))
.scale(0.6, 0.6, 0.6)
.rotateX(PI/2)
}
Expand All @@ -191,24 +197,30 @@ Sphere(.solidColor(Color(0, 0, 1)))
CSG(.difference,
CSG(.difference,
CSG(.difference,
Sphere(.solidColor(Color(0, 0, 1))),
Cylinder(.solidColor(Color(0, 1, 0)))
Sphere()
.material(.solidColor(Color(0, 0, 1)))),
Cylinder()
.material(.solidColor(Color(0, 1, 0)))
.scale(0.5, 0.5, 0.5)),
Cylinder(.solidColor(Color(0, 1, 0)))
Cylinder()
.material(.solidColor(Color(0, 1, 0)))
.scale(0.5, 0.5, 0.5)
.rotateZ(PI/2)),
Cylinder(.solidColor(Color(0, 1, 0)))
Cylinder()
.material(.solidColor(Color(0, 1, 0)))
.scale(0.5, 0.5, 0.5)
.rotateX(PI/2))
```

You can even use `for` loops in the middle of an expression to accomplish the same:

```swift
Sphere(.solidColor(Color(0, 0, 1)))
Sphere()
.material(.solidColor(Color(0, 0, 1)))
.difference {
for (thetaX, thetaZ) in [(0, 0), (0, PI/2), (PI/2, 0)] {
Cylinder(.solidColor(Color(0, 1, 0)))
Cylinder()
.material(.solidColor(Color(0, 1, 0)))
.scale(0.6, 0.6, 0.6)
.rotateX(thetaX)
.rotateZ(thetaZ)
Expand All @@ -219,14 +231,17 @@ Sphere(.solidColor(Color(0, 0, 1)))
You can also chain calls to `.union()`, `.intersection()`, and `.difference()` to create complex shapes:

```swift
Sphere(.solidColor(Color(0, 0, 1)))
Sphere()
.material(.solidColor(Color(0, 0, 1)))
.intersection {
Cube(.solidColor(Color(1, 0, 0)))
Cube()
.material(.solidColor(Color(1, 0, 0)))
.scale(0.8, 0.8, 0.8)
}
.difference {
for (thetaX, thetaZ) in [(0, 0), (0, PI/2), (PI/2, 0)] {
Cylinder(.solidColor(Color(0, 1, 0)))
Cylinder()
.material(.solidColor(Color(0, 1, 0)))
.scale(0.5, 0.5, 0.5)
.rotateX(thetaX)
.rotateZ(thetaZ)
Expand Down Expand Up @@ -258,7 +273,7 @@ The following diagram might make it clearer to understand what the parameters re
| | | * | | * |
| | * | | * | |
| |------|------|------|------|
| | | * | *|
| | | | * | *|
fullUVec | * | * | | |
| |------|------|------|------|
| | | * | * | |
Expand Down Expand Up @@ -307,10 +322,13 @@ World {
point(0, 3, -5),
point(0, 0, 0),
vector(0, 1, 0)))
Sphere(.solidColor(Color(1, 0, 0)))
Sphere()
.material(.solidColor(Color(1, 0, 0)))
.translate(-2, 0, 0)
Sphere(.solidColor(Color(0, 1, 0)))
Sphere(.solidColor(Color(0, 0, 1)))
Sphere()
.material(.solidColor(Color(0, 1, 0)))
Sphere()
.material(.solidColor(Color(0, 0, 1)))
.translate(2, 0, 0)
```

Expand All @@ -337,14 +355,17 @@ struct MyWorld: ScintillaApp {
point(0, 1, -2),
point(0, 0, 0),
vector(0, 1, 0)))
Sphere(.solidColor(Color(0, 0, 1)))
Sphere()
.material(.solidColor(Color(0, 0, 1)))
.intersection {
Cube(.solidColor(Color(1, 0, 0)))
Cube()
.material(.solidColor(Color(1, 0, 0)))
.scale(0.8, 0.8, 0.8)
}
.difference {
for (thetaX, thetaZ) in [(0, 0), (0, PI/2), (PI/2, 0)] {
Cylinder(.solidColor(Color(0, 1, 0)))
Cylinder()
.material(.solidColor(Color(0, 1, 0)))
.scale(0.5, 0.5, 0.5)
.rotateX(thetaX)
.rotateZ(thetaZ)
Expand Down Expand Up @@ -379,14 +400,17 @@ struct CSGExample: ScintillaApp {
point(0, 1.5, -2),
point(0, 0, 0),
vector(0, 1, 0)))
Sphere(.solidColor(Color(0, 0, 1)))
Sphere()
.material(.solidColor(Color(0, 0, 1)))
.intersection {
Cube(.solidColor(Color(1, 0, 0)))
Cube()
.material(.solidColor(Color(1, 0, 0)))
.scale(0.8, 0.8, 0.8)
}
.difference {
for (thetaX, thetaZ) in [(0, 0), (0, PI/2), (PI/2, 0)] {
Cylinder(.solidColor(Color(0, 1, 0)))
Cylinder()
.material(.solidColor(Color(0, 1, 0)))
.scale(0.5, 0.5, 0.5)
.rotateX(thetaX)
.rotateZ(thetaZ)
Expand Down
2 changes: 1 addition & 1 deletion Sources/ScintillaLib/CSG.swift
Expand Up @@ -16,7 +16,7 @@ public class CSG: Shape {
self.operation = operation
self.left = left
self.right = right
super.init(.basicMaterial())
super.init()
left.parent = .csg(self)
right.parent = .csg(self)
}
Expand Down
10 changes: 4 additions & 6 deletions Sources/ScintillaLib/Cone.swift
Expand Up @@ -12,25 +12,23 @@ public class Cone: Shape {
var maximum: Double
var isCapped: Bool

public override init( _ material: Material) {
public override init() {
self.minimum = -.infinity
self.maximum = .infinity
self.isCapped = false
super.init(material)
super.init()
}

public init(_ material: Material, _ minimum: Double, _ maximum: Double) {
public init(_ minimum: Double, _ maximum: Double) {
self.minimum = minimum
self.maximum = maximum
self.isCapped = false
super.init(material)
}

public init(_ material: Material, _ minimum: Double, _ maximum: Double, _ isCapped: Bool) {
public init(_ minimum: Double, _ maximum: Double, _ isCapped: Bool) {
self.minimum = minimum
self.maximum = maximum
self.isCapped = isCapped
super.init(material)
}

// A helper function to reduce duplication.
Expand Down
10 changes: 4 additions & 6 deletions Sources/ScintillaLib/Cylinder.swift
Expand Up @@ -12,25 +12,23 @@ public class Cylinder: Shape {
var maximum: Double
var isCapped: Bool

public override init( _ material: Material) {
public override init() {
self.minimum = -.infinity
self.maximum = .infinity
self.isCapped = false
super.init(material)
super.init()
}

public init(_ material: Material, _ minimum: Double, _ maximum: Double) {
public init(_ minimum: Double, _ maximum: Double) {
self.minimum = minimum
self.maximum = maximum
self.isCapped = false
super.init(material)
}

public init(_ material: Material, _ minimum: Double, _ maximum: Double, _ isCapped: Bool) {
public init(_ minimum: Double, _ maximum: Double, _ isCapped: Bool) {
self.minimum = minimum
self.maximum = maximum
self.isCapped = isCapped
super.init(material)
}

// A helper function to reduce duplication.
Expand Down
2 changes: 1 addition & 1 deletion Sources/ScintillaLib/Group.swift
Expand Up @@ -12,7 +12,7 @@ public class Group: Shape {

public init(@ShapeBuilder builder: () -> [Shape]) {
self.children = builder()
super.init(.basicMaterial())
super.init()
for child in children {
child.parent = .group(self)
}
Expand Down
10 changes: 4 additions & 6 deletions Sources/ScintillaLib/ImplicitSurface.swift
Expand Up @@ -14,22 +14,20 @@ let MAX_ITERATIONS_BISECTION = 100

public class ImplicitSurface: Shape {
var f: SurfaceFunction
var boundingBox: Cube = Cube(.basicMaterial())
var boundingBox: Cube = Cube()

public init(_ material: Material, _ f: @escaping SurfaceFunction) {
public init(_ f: @escaping SurfaceFunction) {
self.f = f
super.init(material)
}

public init(_ material: Material, _ boundingBox: BoundingBox, _ f: @escaping SurfaceFunction) {
public init(_ boundingBox: BoundingBox, _ f: @escaping SurfaceFunction) {
let ((xMin, yMin, zMin), (xMax, yMax, zMax)) = boundingBox
let (scaleX, scaleY, scaleZ) = ((xMax-xMin)/2, (yMax-yMin)/2, (zMax-zMin)/2)
let (translateX, translateY, translateZ) = ((xMax+xMin)/2, (yMax+yMin)/2, (zMax+zMin)/2)
self.boundingBox = Cube(.basicMaterial())
self.boundingBox = Cube()
.scale(scaleX, scaleY, scaleZ)
.translate(translateX, translateY, translateZ)
self.f = f
super.init(material)
}

override func localIntersect(_ localRay: Ray) -> [Intersection] {
Expand Down
11 changes: 8 additions & 3 deletions Sources/ScintillaLib/Shape.swift
Expand Up @@ -16,16 +16,15 @@ public class Shape {
self.inverseTransposeTransform = transform.inverse().transpose()
}
}
var material: Material
var material: Material = .basicMaterial()
var inverseTransform: Matrix4
var inverseTransposeTransform: Matrix4
var parent: Container?
var castsShadow: Bool

public init(_ material: Material) {
public init() {
self.id = Self.latestId
self.transform = .identity
self.material = material
self.inverseTransform = transform.inverse()
self.inverseTransposeTransform = transform.inverse().transpose()
self.castsShadow = true
Expand All @@ -44,6 +43,12 @@ public class Shape {
return CSG.makeCSG(.intersection, self, otherShapesBuilder)
}

public func material(_ material: Material) -> Self {
self.material = material

return self
}

public func castsShadow(_ castsShadow: Bool) -> Self {
self.castsShadow = castsShadow

Expand Down
13 changes: 3 additions & 10 deletions Sources/ScintillaLib/Torus.swift
Expand Up @@ -6,19 +6,12 @@
//

public class Torus: Shape {
var majorRadius: Double
var minorRadius: Double
var majorRadius: Double = 2.0
var minorRadius: Double = 1.0

public override init( _ material: Material) {
self.majorRadius = 2.0
self.minorRadius = 1.0
super.init(material)
}

public init(_ material: Material, _ majorRadius: Double, _ minorRadius: Double) {
public init(_ majorRadius: Double, _ minorRadius: Double) {
self.majorRadius = majorRadius
self.minorRadius = minorRadius
super.init(material)
}

override func localIntersect(_ localRay: Ray) -> [Intersection] {
Expand Down
12 changes: 6 additions & 6 deletions Tests/ScintillaLibTests/CSGTests.swift
Expand Up @@ -10,8 +10,8 @@ import XCTest

class CSGTests: XCTestCase {
func testFilterIntersections() throws {
let s1 = Sphere(.basicMaterial())
let s2 = Cube(.basicMaterial())
let s1 = Sphere()
let s2 = Cube()
let allIntersections = [
Intersection(1, s1),
Intersection(2, s2),
Expand All @@ -34,17 +34,17 @@ class CSGTests: XCTestCase {
}

func testLocalIntersectWithRayThatMisses() throws {
let s1 = Sphere(.basicMaterial())
let s2 = Cube(.basicMaterial())
let s1 = Sphere()
let s2 = Cube()
let csg = CSG(.union, s1, s2)
let ray = Ray(point(0, 2, -5), vector(0, 0, 1))
let allIntersections = csg.localIntersect(ray)
XCTAssertTrue(allIntersections.isEmpty)
}

func testLocalIntersectWithRayThatHits() throws {
let s1 = Sphere(.basicMaterial())
let s2 = Sphere(.basicMaterial())
let s1 = Sphere()
let s2 = Sphere()
.translate(0, 0, 0.5)
let csg = CSG(.union, s1, s2)
let ray = Ray(point(0, 0, -5), vector(0, 0, 1))
Expand Down

0 comments on commit ae570a5

Please sign in to comment.