├── .gitignore
├── Path Tracing Demo Tests
├── Info.plist
└── MatrixTests.swift
├── Path Tracing Demo.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ └── contents.xcworkspacedata
├── Path Tracing Demo
├── AppDelegate.swift
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Base.lproj
│ └── Main.storyboard
├── ColorTextureChooserViewController.swift
├── ControlPanelViewController.swift
├── Extensions.swift
├── Geometry.swift
├── Info.plist
├── MaterialEditorViewController.swift
├── Matrix.swift
├── MeshImport.swift
├── Objects.swift
├── Pathtracing.swift
├── PriorityQueue.swift
├── RenderResultViewController.swift
├── ShaderChooserViewController.swift
├── ShaderEditorViewController.swift
├── ShaderEncoding.swift
├── Shading.swift
├── SpacePartitioning.swift
└── Textures.swift
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ##jazzy
21 | docs/
22 | makefile
23 |
24 | ## Other
25 | *.moved-aside
26 | *.xccheckout
27 | *.xcscmblueprint
28 |
29 | ## AppCode
30 | .idea/
31 |
32 | ## iOS Target
33 | PathTracing/
34 |
--------------------------------------------------------------------------------
/Path Tracing Demo Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Path Tracing Demo Tests/MatrixTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PathTracingTestsTests.swift
3 | // PathTracingTestsTests
4 | //
5 | // Created by Palle Klewitz on 31.07.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import XCTest
27 |
28 | class MatrixTests: XCTestCase
29 | {
30 |
31 | override func setUp()
32 | {
33 | super.setUp()
34 | // Put setup code here. This method is called before the invocation of each test method in the class.
35 | }
36 |
37 | override func tearDown()
38 | {
39 | // Put teardown code here. This method is called after the invocation of each test method in the class.
40 | super.tearDown()
41 | }
42 |
43 | func testMatrixTranspose()
44 | {
45 | let matA = Matrix(rows: [[1,2,0],[0,1,1]])
46 | let matB = Matrix(rows: [[1,5,0],[0,2,1],[1,4,1]])
47 | let matC = Matrix(rows: [[2,1],[0,0],[1,0],[1,-1]])
48 | let matD = Matrix(columns:[[1,2,0,-1]])
49 | let matE = Matrix(rows: [[1,-1,0]])
50 | let matF = Matrix(rows: [[3]])
51 |
52 | let matAT = matA.transposed
53 | let matBT = matB.transposed
54 | let matCT = matC.transposed
55 | let matDT = matD.transposed
56 | let matET = matE.transposed
57 | let matFT = matF.transposed
58 |
59 | XCTAssertEqual(matAT.rows, [[1,0],[2,1],[0,1]])
60 | XCTAssertEqual(matBT.rows, [[1,0,1],[5,2,4],[0,1,1]])
61 | XCTAssertEqual(matCT.rows, [[2,0,1,1],[1,0,0,-1]])
62 | XCTAssertEqual(matDT.rows, [[1,2,0,-1]])
63 | XCTAssertEqual(matET.rows, [[1],[-1],[0]])
64 | XCTAssertEqual(matFT.rows, [[3]])
65 | }
66 |
67 | func testMatrixMultiplication()
68 | {
69 | let matA = Matrix(rows: [[1,2,0],[0,1,1]])
70 | let matB = Matrix(rows: [[1,5,0],[0,2,1],[1,4,1]])
71 | let matC = Matrix(rows: [[2,1],[0,0],[1,0],[1,-1]])
72 | let matD = Matrix(columns:[[1,2,0,-1]])
73 | let matE = Matrix(rows: [[1,-1,0]])
74 | let matF = Matrix(rows: [[3]])
75 |
76 | let matAB = matA * matB
77 | XCTAssertEqual(matAB.rows, [[1,9,2],[1,6,2]])
78 |
79 | let matCA = matC * matA
80 | XCTAssertEqual(matCA.rows, [[2,5,1],[0,0,0],[1,2,0],[1,1,-1]])
81 |
82 | let matDE = matD * matE
83 | XCTAssertEqual(matDE.rows, [[1,-1,0],[2,-2,0,],[0,0,0],[-1,1,0]])
84 |
85 | let matDF = matD * matF
86 | XCTAssertEqual(matDF.rows, [[3],[6],[0],[-3]])
87 |
88 | let matEB = matE * matB
89 | XCTAssertEqual(matEB.rows, [[1,3,-1]])
90 |
91 | let matFE = matF * matE
92 | XCTAssertEqual(matFE.rows, [[3,-3,0]])
93 | }
94 |
95 | func testMatrixVectorMultiplication()
96 | {
97 | let vecA = Vector3D(x: 1, y: 2, z: 3)
98 | let res1 = Matrix3x3Identity * vecA
99 | XCTAssertEqual(vecA, res1)
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/Path Tracing Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Path Tracing Demo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 29.07.16.
6 | // Copyright © 2016 - 2018 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Cocoa
27 |
28 | @NSApplicationMain
29 | class AppDelegate: NSObject, NSApplicationDelegate
30 | {
31 | var scene: Scene3D?
32 | {
33 | didSet
34 | {
35 | guard let scene = self.scene else { return }
36 | self.pathTracer = PathTracer(withScene: scene)
37 | DispatchQueue.main.async
38 | {
39 | NotificationCenter.default.post(name: NSNotification.Name(rawValue: "RenderResultViewUpdatePathTracer"), object: nil)
40 | }
41 | }
42 | }
43 |
44 | var pathTracer: PathTracer?
45 |
46 | func applicationDidFinishLaunching(_ aNotification: Notification)
47 | {
48 |
49 | }
50 |
51 | func applicationWillTerminate(_ aNotification: Notification)
52 | {
53 |
54 | }
55 | }
56 |
57 | class UnifiedTitleBarWindowController: NSWindowController
58 | {
59 | override func windowDidLoad()
60 | {
61 | super.windowDidLoad()
62 | self.window?.titlebarAppearsTransparent = true
63 | }
64 | }
65 |
66 | var ApplicationDelegate: AppDelegate
67 | {
68 | return NSApplication.shared.delegate! as! AppDelegate
69 | }
70 |
--------------------------------------------------------------------------------
/Path Tracing Demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/Path Tracing Demo/ColorTextureChooserViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ColorTextureChooserViewController.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 09.08.16.
6 | // Copyright © 2016 - 2018 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Cocoa
27 |
28 | protocol ColorTextureChooserDelegate: class
29 | {
30 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange color: Color)
31 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange texture: Texture?)
32 | }
33 |
34 | class ColorTextureChooserViewController: NSViewController
35 | {
36 | var color: Color = .white()
37 | {
38 | didSet
39 | {
40 | colorWell.color = NSColor(calibratedRed: CGFloat(color.red),
41 | green: CGFloat(color.green),
42 | blue: CGFloat(color.blue),
43 | alpha: CGFloat(color.alpha))
44 | }
45 | }
46 | var texture: Texture? = nil
47 | {
48 | didSet
49 | {
50 | if let texture = self.texture
51 | {
52 | if texture is ImageTexture
53 | {
54 | textureTypeChooser.selectItem(at: 2)
55 | }
56 | else if texture is CheckerboardTexture
57 | {
58 | textureTypeChooser.selectItem(at: 1)
59 | }
60 | }
61 | else
62 | {
63 | textureTypeChooser.selectItem(at: 0)
64 | }
65 | }
66 | }
67 |
68 | @IBOutlet weak var colorWell: NSColorWell!
69 | @IBOutlet weak var textureTypeChooser: NSPopUpButton!
70 |
71 | weak var delegate: ColorTextureChooserDelegate? = nil
72 |
73 | override func viewDidLoad()
74 | {
75 | super.viewDidLoad()
76 | }
77 |
78 | @IBAction func didChangeColor(_ sender: AnyObject)
79 | {
80 | let color = colorWell.color
81 |
82 | let r = Float(color.redComponent)
83 | let g = Float(color.greenComponent)
84 | let b = Float(color.blueComponent)
85 | let a = Float(color.alphaComponent)
86 |
87 | guard r != self.color.red || g != self.color.green || b != self.color.blue || a != self.color.alpha else { return }
88 |
89 | self.color = Color(withRed: r, green: g, blue: b, alpha: a)
90 | delegate?.colorTextureChooser(chooser: self, didChange: self.color)
91 | }
92 |
93 | @IBAction func didChangeTextureType(_ sender: AnyObject)
94 | {
95 | switch textureTypeChooser.indexOfSelectedItem
96 | {
97 | case 0:
98 | texture = nil
99 | delegate?.colorTextureChooser(chooser: self, didChange: nil)
100 | break
101 | case 1:
102 | texture = CheckerboardTexture(horizontalTiles: 20, verticalTiles: 20)
103 | delegate?.colorTextureChooser(chooser: self, didChange: texture)
104 | break
105 | case 2:
106 | let openPanel = NSOpenPanel()
107 | openPanel.canChooseFiles = true
108 | openPanel.canChooseDirectories = false
109 | openPanel.allowsMultipleSelection = false
110 | guard openPanel.runModal() == .OK else { break }
111 | guard let url = openPanel.url else { break }
112 | guard let imageTexture = ImageTexture(contentsOf: url) else { break }
113 | texture = imageTexture
114 | delegate?.colorTextureChooser(chooser: self, didChange: texture)
115 | break
116 | default:
117 | break
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/Path Tracing Demo/ControlPanelViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ControlPanelViewController.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 08.08.16.
6 | // Copyright © 2016 - 2018 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Cocoa
27 | import QuartzCore
28 |
29 | class ControlPanelViewController: NSViewController, WavefrontModelImporterMaterialDataSource
30 | {
31 | @IBOutlet weak var txtCameraX: NSTextField!
32 | @IBOutlet weak var txtCameraY: NSTextField!
33 | @IBOutlet weak var txtCameraZ: NSTextField!
34 | @IBOutlet weak var txtCameraAlpha: NSTextField!
35 | @IBOutlet weak var txtCameraBeta: NSTextField!
36 | @IBOutlet weak var txtCameraGamma: NSTextField!
37 | @IBOutlet weak var txtCameraApertureSize: NSTextField!
38 | @IBOutlet weak var txtCameraFocalLength: NSTextField!
39 | @IBOutlet weak var txtCameraFov: NSTextField!
40 |
41 | @IBOutlet weak var txtPathTracingSamples: NSTextField!
42 | @IBOutlet weak var txtPathTracingRecursionDepth: NSTextField!
43 |
44 | @IBOutlet weak var txtRenderWidth: NSTextField!
45 | @IBOutlet weak var txtRenderHeight: NSTextField!
46 |
47 | @IBOutlet weak var btnStart: NSButton!
48 | @IBOutlet weak var btnStop: NSButton!
49 |
50 | @IBOutlet weak var piSceneImportProgress: NSProgressIndicator!
51 |
52 | @IBOutlet weak var cwAmbientColor: NSColorWell!
53 |
54 | private var objectLoader: WavefrontModelImporter!
55 |
56 | private lazy var importQueue: DispatchQueue = DispatchQueue(label: "pathtracing.meshimport")
57 | private lazy var importSemaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
58 | private var shaderIndex:[String: Shader] = [:]
59 |
60 | private var environmentTexture: Texture?
61 |
62 | private func loadCamera()
63 | {
64 | let x = txtCameraX.floatValue
65 | let y = txtCameraY.floatValue
66 | let z = txtCameraZ.floatValue
67 | let alpha = txtCameraAlpha.floatValue
68 | let beta = txtCameraBeta.floatValue
69 | let gamma = txtCameraGamma.floatValue
70 | let apertureSize: Float = txtCameraApertureSize.floatValue
71 | let focalDistance: Float = txtCameraFocalLength.floatValue
72 | let fieldOfView: Float = txtCameraFov.floatValue
73 |
74 | ApplicationDelegate.scene?.camera.location = Vector3D(x: x, y: y, z: z)
75 | ApplicationDelegate.scene?.camera.rotation = (alpha: alpha, beta: beta, gamma: gamma)
76 | ApplicationDelegate.scene?.camera.focalDistance = focalDistance
77 | ApplicationDelegate.scene?.camera.apertureSize = apertureSize
78 | ApplicationDelegate.scene?.camera.fieldOfView = fieldOfView
79 |
80 | // return Camera(location: Point3D(x: x, y: y, z: z),
81 | // rotation: (alpha: alpha, beta: beta, gamma: gamma),
82 | // apertureSize: apertureSize,
83 | // focalDistance: focalDistance,
84 | // fieldOfView: fieldOfView)
85 | }
86 |
87 | override func viewDidLoad()
88 | {
89 | super.viewDidLoad()
90 | }
91 |
92 | @IBAction func importScene(_ sender: AnyObject)
93 | {
94 | let openPanel = NSOpenPanel()
95 | openPanel.allowsMultipleSelection = false
96 | openPanel.canChooseDirectories = false
97 | openPanel.canChooseFiles = true
98 |
99 | guard openPanel.runModal() == .OK else { return }
100 | guard let url = openPanel.url else { return }
101 |
102 | piSceneImportProgress.isHidden = false
103 |
104 | DispatchQueue.global().async
105 | {
106 | self.materials = [:]
107 | self.objectLoader = WavefrontModelImporter()
108 | self.objectLoader.materialDataSource = self
109 | self.objectLoader.progress.addObserver(self, forKeyPath: "fractionCompleted", options: [], context: nil)
110 | guard let objects = try? self.objectLoader.import(from: url) else { return }
111 |
112 | let camera = Camera(location: Vector3DZero, rotation: (alpha: 0, beta: 0, gamma: 0), apertureSize: 0, focalDistance: 1, fieldOfView: 1)
113 |
114 | ApplicationDelegate.scene = Scene3D(objects: objects, camera: camera, environmentShader: EnvironmentShader(color: .black(), texture: self.environmentTexture))
115 |
116 | DispatchQueue.main.async
117 | {
118 | self.piSceneImportProgress.stopAnimation(self)
119 | self.piSceneImportProgress.isHidden = true
120 | }
121 | }
122 | }
123 |
124 | @IBAction func startRendering(_ sender: AnyObject)
125 | {
126 | ApplicationDelegate.scene?.objects.flatMap{$0.materials}.distinct().forEach{print($0)}
127 |
128 | loadCamera()
129 |
130 | let color = cwAmbientColor.color
131 | ApplicationDelegate.scene?.environmentShader.color = Color(withRed: Float(color.redComponent), green: Float(color.greenComponent), blue: Float(color.blueComponent), alpha: Float(color.alphaComponent))
132 | ApplicationDelegate.scene?.environmentShader.texture = environmentTexture
133 |
134 | ApplicationDelegate.pathTracer?.traceRays(width: txtRenderWidth.integerValue,
135 | height: txtRenderHeight.integerValue,
136 | rayDepth: txtPathTracingRecursionDepth.integerValue,
137 | samples: txtPathTracingSamples.integerValue)
138 | }
139 |
140 | @IBAction func stopRendering(_ sender: AnyObject)
141 | {
142 | ApplicationDelegate.pathTracer?.stop()
143 | }
144 |
145 | private var materials:[String:Material] = [:]
146 |
147 | func material(named name: String, in materialLibrary: String) -> Material
148 | {
149 | if materials[name] == nil
150 | {
151 | materials[name] = Material(withShader: DefaultShader(color: .white()), named: name)
152 | }
153 | return materials[name]! //force unwrapping, as the code above guarantees non-nil value.
154 | }
155 |
156 | @IBAction func saveScene(_ sender: AnyObject)
157 | {
158 | guard let scene = ApplicationDelegate.scene else { return }
159 | let savePanel = NSSavePanel()
160 | guard savePanel.runModal() == .OK else { return }
161 | guard let url = savePanel.url else { return }
162 | DispatchQueue.global().async
163 | {
164 | let materialURL = url.appendingPathExtension("material")
165 | let materialData = WavefrontModelExporter.exportMaterials(of: scene)
166 |
167 | do
168 | {
169 | try materialData.write(to: materialURL, options: [.atomic])
170 | }
171 | catch
172 | {
173 | print(error)
174 | DispatchQueue.main.async
175 | {
176 | let alert = NSAlert(error: error)
177 | alert.runModal()
178 | }
179 | }
180 | }
181 | }
182 |
183 | @IBAction func saveRender(_ sender: AnyObject)
184 | {
185 | guard let image = ApplicationDelegate.pathTracer?.result else { return }
186 | let savePanel = NSSavePanel()
187 | savePanel.allowedFileTypes = ["public.png"]
188 | guard savePanel.runModal() == .OK else { return }
189 | guard let url = savePanel.url else { return }
190 |
191 | DispatchQueue.global().async
192 | {
193 | guard let destination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypePNG, 1, nil)
194 | else
195 | {
196 | DispatchQueue.main.async
197 | {
198 | let alert = NSAlert()
199 | alert.alertStyle = .critical
200 | alert.messageText = "Image could not be saved."
201 | alert.informativeText = "Image destination could not be created."
202 | alert.runModal()
203 | }
204 | return
205 | }
206 | CGImageDestinationAddImage(destination, image, nil)
207 | guard CGImageDestinationFinalize(destination)
208 | else
209 | {
210 | DispatchQueue.main.async
211 | {
212 | let alert = NSAlert()
213 | alert.alertStyle = .critical
214 | alert.messageText = "Image could not be saved."
215 | alert.informativeText = "Image destination could not be finalized."
216 | alert.runModal()
217 | }
218 | return
219 | }
220 | }
221 | }
222 |
223 | private var lastReport = 0.0
224 |
225 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
226 | {
227 | let time = CACurrentMediaTime()
228 | guard time - lastReport > 0.016 else { return }
229 | lastReport = time
230 |
231 | DispatchQueue.main.async
232 | {
233 | guard let keyPath = keyPath else { return }
234 |
235 | switch keyPath
236 | {
237 | case "fractionCompleted":
238 | guard let progress = object as? Progress else { break }
239 | self.piSceneImportProgress.minValue = 0.0
240 | self.piSceneImportProgress.maxValue = 1.0
241 | self.piSceneImportProgress.doubleValue = progress.fractionCompleted
242 | break
243 | default:
244 | break
245 | }
246 | }
247 | }
248 |
249 | @IBAction func chooseEnvironmentTexture(_ sender: AnyObject)
250 | {
251 | let openPanel = NSOpenPanel()
252 | openPanel.canChooseFiles = true
253 | openPanel.canChooseDirectories = false
254 | openPanel.allowsMultipleSelection = false
255 | guard openPanel.runModal() == .OK else { return }
256 | guard let url = openPanel.url else { return }
257 | guard let imageTexture = ImageTexture(contentsOf: url) else { return }
258 | ApplicationDelegate.scene?.environmentShader.texture = imageTexture
259 | environmentTexture = imageTexture
260 | }
261 |
262 | }
263 |
--------------------------------------------------------------------------------
/Path Tracing Demo/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 09.08.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | extension Array
29 | {
30 | func peek(body: (Element) throws -> ()) rethrows -> [Element]
31 | {
32 | try self.forEach(body)
33 | return self
34 | }
35 |
36 | func limit(n: Int, offset: Int = 0) -> [Element]
37 | {
38 | return Array(self[offset..<(n + offset)])
39 | }
40 | }
41 |
42 | extension Array where Element: Comparable
43 | {
44 | func kthSmallestElement(_ k: Int) -> Element
45 | {
46 | //using quickselect algorithm
47 | let pivotIndex = Int(arc4random_uniform(UInt32(self.count)))
48 | let pivot = self[pivotIndex]
49 |
50 | let lower = self.filter{$0 < pivot}
51 | let upper = self.filter{$0 > pivot}
52 |
53 | if k <= lower.count
54 | {
55 | return lower.kthSmallestElement(k)
56 | }
57 | else if k > self.count - upper.count
58 | {
59 | return upper.kthSmallestElement(k - (self.count - upper.count))
60 | }
61 | else
62 | {
63 | return pivot
64 | }
65 | }
66 |
67 | func kthLargestElement(_ k: Int) -> Element
68 | {
69 | return kthSmallestElement(self.count - k - 1)
70 | }
71 | }
72 |
73 | extension Array where Element: Hashable
74 | {
75 | func distinct() -> [Element]
76 | {
77 | var set = Set()
78 | var result:[Element] = []
79 |
80 | for element in self
81 | {
82 | guard !set.contains(element) else { continue }
83 | result.append(element)
84 | set.insert(element)
85 | }
86 |
87 | return result
88 | }
89 | }
90 |
91 | extension Array where Element: Equatable
92 | {
93 | func distinct() -> [Element]
94 | {
95 | var result:[Element] = []
96 |
97 | outer: for i in 0 ..< self.count
98 | {
99 | for j in 0 ..< i
100 | {
101 | if self[i] == self[j]
102 | {
103 | continue outer
104 | }
105 | }
106 | result.append(self[i])
107 | }
108 |
109 | return result
110 | }
111 | }
112 |
113 | extension Array where Element: AnyObject
114 | {
115 | func distinct() -> [Element]
116 | {
117 | var result:[Element] = []
118 |
119 | outer: for i in 0 ..< self.count
120 | {
121 | for j in 0 ..< i
122 | {
123 | if self[i] === self[j]
124 | {
125 | continue outer
126 | }
127 | }
128 | result.append(self[i])
129 | }
130 |
131 | return result
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/Path Tracing Demo/Geometry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Geometry.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 29.07.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | //MARK: 3D Vectors
29 |
30 | struct Vector3D
31 | {
32 | var x: Float
33 | var y: Float
34 | var z: Float
35 |
36 | var abs: Float
37 | {
38 | return sqrtf(self * self)
39 | }
40 |
41 | var normalized: Vector3D
42 | {
43 | return self * (1 / abs)
44 | }
45 |
46 | @inline(__always)
47 | func rotated(alpha: Float, beta: Float, gamma: Float) -> Vector3D
48 | {
49 | let sinAlpha = sinf(alpha)
50 | let cosAlpha = cosf(alpha)
51 | let sinBeta = sinf(beta)
52 | let cosBeta = cosf(beta)
53 | let sinGamma = sinf(gamma)
54 | let cosGamma = cosf(gamma)
55 |
56 | let xx = self.x * cosAlpha * cosBeta
57 | let yx = self.y * (cosAlpha * sinBeta * sinGamma - sinAlpha * cosGamma)
58 | let zx = self.z * (cosAlpha * sinBeta * cosGamma + sinAlpha * sinGamma)
59 |
60 | let xy = self.x * sinAlpha * cosBeta
61 | let yy = self.y * (sinAlpha * sinBeta * sinGamma + cosAlpha * cosGamma)
62 | let zy = self.z * (sinAlpha * sinBeta * cosGamma - cosAlpha * sinGamma)
63 |
64 | let xz = self.x * -sinBeta
65 | let yz = self.y * cosBeta * sinGamma
66 | let zz = self.z * cosBeta * cosGamma
67 |
68 | return Vector3D(x: xx + yx + zx, y: xy + yy + zy, z: xz + yz + zz)
69 | }
70 | }
71 |
72 | extension Vector3D : CustomStringConvertible
73 | {
74 | var description: String
75 | {
76 | return "(x: \(x), y: \(y), z: \(z))"
77 | }
78 | }
79 |
80 | extension Vector3D : Equatable { }
81 |
82 | extension Vector3D : Hashable
83 | {
84 | var hashValue: Int
85 | {
86 | return x.hashValue ^ y.hashValue ^ z.hashValue
87 | }
88 | }
89 |
90 | typealias Point3D = Vector3D
91 |
92 | let Vector3DZero = Vector3D(x: 0, y: 0, z: 0)
93 | let Vector3DUnitX = Vector3D(x: 1, y: 0, z: 0)
94 | let Vector3DUnitY = Vector3D(x: 0, y: 1, z: 0)
95 | let Vector3DUnitZ = Vector3D(x: 0, y: 0, z: 1)
96 |
97 |
98 | struct Vertex3D
99 | {
100 | var point: Point3D
101 | var normal: Vector3D
102 | var textureCoordinate: TextureCoordinate
103 | }
104 |
105 | extension Vertex3D : Equatable { }
106 |
107 |
108 | //MARK: Barycentric Coordinates
109 |
110 | struct BarycentricPoint
111 | {
112 | let alpha: Float
113 | let beta: Float
114 | let gamma: Float
115 | }
116 |
117 | extension BarycentricPoint : Equatable { }
118 |
119 | extension BarycentricPoint : CustomStringConvertible
120 | {
121 | var description: String
122 | {
123 | return "(alpha: \(alpha), beta: \(beta), gamma: \(gamma))"
124 | }
125 | }
126 |
127 | //MARK: Rays
128 |
129 | struct Ray3D
130 | {
131 | let base: Point3D
132 | let direction: Vector3D
133 |
134 | @inline(__always)
135 | func point(`for` parameter: Float) -> Point3D
136 | {
137 | return base + (direction * parameter)
138 | }
139 |
140 | @inline(__always)
141 | func findIntersection(with triangle: Triangle3D) -> (rayParameter: Float, barycentric: BarycentricPoint)?
142 | {
143 | let bSubA = triangle.b.point - triangle.a.point
144 | let cSubA = triangle.c.point - triangle.a.point
145 | let nDir = -direction
146 | let right = base - triangle.a.point
147 |
148 | let det = bSubA.x * cSubA.y * nDir.z
149 | + cSubA.x * nDir.y * bSubA.z
150 | + nDir.x * bSubA.y * cSubA.z
151 | - nDir.x * cSubA.y * bSubA.z
152 | - bSubA.x * nDir.y * cSubA.z
153 | - cSubA.x * bSubA.y * nDir.z
154 |
155 | guard det != 0 else { return nil }
156 |
157 | //solve using cramers rule
158 |
159 | let det1 = right.x * cSubA.y * nDir.z
160 | + cSubA.x * nDir.y * right.z
161 | + nDir.x * right.y * cSubA.z
162 | - nDir.x * cSubA.y * right.z
163 | - right.x * nDir.y * cSubA.z
164 | - cSubA.x * right.y * nDir.z
165 |
166 | let beta = det1 / det
167 | guard beta >= 0 && beta <= 1.0 else { return nil }
168 |
169 | let det2 = bSubA.x * right.y * nDir.z
170 | + right.x * nDir.y * bSubA.z
171 | + nDir.x * bSubA.y * right.z
172 | - nDir.x * right.y * bSubA.z
173 | - bSubA.x * nDir.y * right.z
174 | - right.x * bSubA.y * nDir.z
175 |
176 | let gamma = det2 / det
177 | let alpha = 1.0 - gamma - beta
178 | guard gamma >= 0 && gamma <= 1.0 && alpha >= 0.0 else { return nil }
179 |
180 | let det3 = bSubA.x * cSubA.y * right.z
181 | + cSubA.x * right.y * bSubA.z
182 | + right.x * bSubA.y * cSubA.z
183 | - right.x * cSubA.y * bSubA.z
184 | - bSubA.x * right.y * cSubA.z
185 | - cSubA.x * bSubA.y * right.z
186 |
187 | let rayParameter = det3 / det
188 | guard rayParameter >= 0 else { return nil }
189 |
190 | return (rayParameter: rayParameter,
191 | barycentric: BarycentricPoint(
192 | alpha: alpha,
193 | beta: beta,
194 | gamma: gamma))
195 | }
196 | }
197 |
198 | extension Ray3D : CustomStringConvertible
199 | {
200 | var description: String
201 | {
202 | return "Ray3D (base: \(base), direction: \(direction))"
203 | }
204 | }
205 |
206 | extension Ray3D : Equatable { }
207 |
208 |
209 | enum RayType
210 | {
211 | case camera
212 | case diffuse
213 | case reflective
214 | case refractive
215 | }
216 |
217 |
218 | //MARK: Triangles
219 |
220 | //private let defaultShader = DiffuseShader(color: Color(withRed: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
221 | //private let defaultShader = EmissionShader(color: Color(withRed: 1.0, green: 1.0, blue: 1.0, alpha: 1.0), strhe)
222 | let defaultShader = DefaultShader(color: Color(withRed: 1.0, green: 1.0, blue: 1.0, alpha: 1.0))
223 | let defaultMaterial = Material(withShader: DefaultShader(color: .white()), named: "Default Material")
224 |
225 | struct Triangle3D
226 | {
227 | let a: Vertex3D
228 | let b: Vertex3D
229 | let c: Vertex3D
230 | var material: Material
231 |
232 | var points:[Point3D]
233 | {
234 | return [a.point, b.point, c.point]
235 | }
236 |
237 | init(a: Vertex3D, b: Vertex3D, c: Vertex3D, material: Material = defaultMaterial)
238 | {
239 | self.a = a
240 | self.b = b
241 | self.c = c
242 | self.material = material
243 | }
244 |
245 | init(a: Point3D, b: Point3D, c: Point3D, material: Material = defaultMaterial)
246 | {
247 | let normal = (b-a)⨯(c-a)
248 | self.a = Vertex3D(point: a, normal: normal, textureCoordinate: TextureCoordinate(u: 0, v: 0))
249 | self.b = Vertex3D(point: b, normal: normal, textureCoordinate: TextureCoordinate(u: 1, v: 0))
250 | self.c = Vertex3D(point: c, normal: normal, textureCoordinate: TextureCoordinate(u: 0, v: 1))
251 | self.material = material
252 | }
253 |
254 | var normal: Vector3D
255 | {
256 | return ((b.point-a.point)⨯(c.point-a.point)).normalized
257 | }
258 |
259 | func translated(to point: Point3D) -> Triangle3D
260 | {
261 | return Triangle3D(
262 | a: Vertex3D(point: a.point + point, normal: a.normal, textureCoordinate: a.textureCoordinate),
263 | b: Vertex3D(point: b.point + point, normal: b.normal, textureCoordinate: b.textureCoordinate),
264 | c: Vertex3D(point: c.point + point, normal: c.normal, textureCoordinate: c.textureCoordinate),
265 | material: material)
266 | }
267 |
268 | func scaled(_ factor: Float) -> Triangle3D
269 | {
270 | return Triangle3D(
271 | a: Vertex3D(point: a.point * factor, normal: a.normal, textureCoordinate: a.textureCoordinate),
272 | b: Vertex3D(point: b.point * factor, normal: b.normal, textureCoordinate: b.textureCoordinate),
273 | c: Vertex3D(point: c.point * factor, normal: c.normal, textureCoordinate: c.textureCoordinate),
274 | material: material)
275 | }
276 | }
277 |
278 | extension Triangle3D : CustomStringConvertible
279 | {
280 | var description: String
281 | {
282 | return "Triangle3D (\(a), \(b), \(c))"
283 | }
284 | }
285 |
286 | extension Triangle3D : Equatable { }
287 |
288 | //MARK: Operator Declarations
289 |
290 | infix operator ⨯ : MultiplicationPrecedence
291 | infix operator <-> : MultiplicationPrecedence
292 | infix operator ∠ : MultiplicationPrecedence
293 |
294 | //MARK: Vector to Vector Real Arithmetic
295 |
296 | @inline(__always)
297 | func ⨯ (left: Vector3D, right: Vector3D) -> Vector3D
298 | {
299 | return Vector3D(
300 | x: left.y * right.z - left.z * right.y,
301 | y: left.z * right.x - left.x * right.z,
302 | z: left.x * right.y - left.y * right.x)
303 | }
304 |
305 | @inline(__always)
306 | func * (left: Vector3D, right: Vector3D) -> Float
307 | {
308 | return left.x * right.x + left.y * right.y + left.z * right.z
309 | }
310 |
311 | @inline(__always)
312 | func ∠ (left: Vector3D, right: Vector3D) -> Float
313 | {
314 | return abs(acosf(left.normalized * right.normalized))
315 | }
316 |
317 | @inline(__always)
318 | func + (left: Vector3D, right: Vector3D) -> Vector3D
319 | {
320 | return Vector3D(
321 | x: left.x + right.x,
322 | y: left.y + right.y,
323 | z: left.z + right.z)
324 | }
325 |
326 | @inline(__always)
327 | func - (left: Vector3D, right: Vector3D) -> Vector3D
328 | {
329 | return Vector3D(
330 | x: left.x - right.x,
331 | y: left.y - right.y,
332 | z: left.z - right.z)
333 | }
334 |
335 | @inline(__always)
336 | func <-> (left: Point3D, right: Point3D) -> Float
337 | {
338 | return (left-right).abs
339 | }
340 |
341 | //MARK: Vector to Ray Real Arithmetic
342 |
343 | @inline(__always)
344 | func <-> (left: Ray3D, right: Point3D) -> Float
345 | {
346 | return ((right - left.base) ⨯ left.direction).abs / left.direction.abs
347 | }
348 |
349 | @inline(__always)
350 | func <-> (left: Point3D, right: Ray3D) -> Float
351 | {
352 | return right <-> left
353 | }
354 |
355 | //MARK: Vector to Scalar Real Arithmetic
356 |
357 | @inline(__always)
358 | func * (left: Vector3D, right: Float) -> Vector3D
359 | {
360 | return Vector3D(
361 | x: left.x * right,
362 | y: left.y * right,
363 | z: left.z * right)
364 | }
365 |
366 | @inline(__always)
367 | func * (left: Float, right: Vector3D) -> Vector3D
368 | {
369 | return right * left
370 | }
371 |
372 | //MARK: Single Vector Operations
373 |
374 | @inline(__always)
375 | prefix func - (right: Vector3D) -> Vector3D
376 | {
377 | return Vector3D(
378 | x: -right.x,
379 | y: -right.y,
380 | z: -right.z)
381 | }
382 |
383 | //MARK: Equatables
384 |
385 | @inline(__always)
386 | func == (left: Vector3D, right: Vector3D) -> Bool
387 | {
388 | return left.x == right.x && left.y == right.y && left.z == right.z
389 | }
390 |
391 | @inline(__always)
392 | func == (left: BarycentricPoint, right: BarycentricPoint) -> Bool
393 | {
394 | return left.alpha == right.alpha && left.beta == right.beta && left.gamma == right.gamma
395 | }
396 |
397 | @inline(__always)
398 | func == (left: Ray3D, right: Ray3D) -> Bool
399 | {
400 | return left.base == right.base && left.direction == right.direction
401 | }
402 |
403 | @inline(__always)
404 | func == (left: Triangle3D, right: Triangle3D) -> Bool
405 | {
406 | return left.a == right.a && left.b == right.b && left.c == right.c
407 | }
408 |
409 | @inline(__always)
410 | func == (left: Vertex3D, right: Vertex3D) -> Bool
411 | {
412 | return left.point == right.point && left.normal == right.normal
413 | }
414 |
415 |
--------------------------------------------------------------------------------
/Path Tracing Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSApplicationCategoryType
24 | public.app-category.graphics-design
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSHumanReadableCopyright
28 | Copyright © 2016 Palle Klewitz. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
29 |
30 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
31 |
32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 | NSMainStoryboardFile
34 | Main
35 | NSPrincipalClass
36 | NSApplication
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Path Tracing Demo/MaterialEditorViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MaterialEditorViewController.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 09.08.16.
6 | // Copyright © 2016 - 2018 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import AppKit
27 | import Cocoa
28 |
29 | class MaterialEditorViewController: NSViewController
30 | {
31 | @IBOutlet weak var materialList: NSTableView!
32 | @IBOutlet weak var shaderContainerView: NSView!
33 |
34 | fileprivate lazy var materials:[Material] = ApplicationDelegate.scene?.objects.flatMap{$0.materials}.distinct().sorted{$0.name < $1.name} ?? []
35 |
36 | override func viewDidLoad()
37 | {
38 | super.viewDidLoad()
39 | materialList.delegate = self
40 | materialList.dataSource = self
41 | NotificationCenter.default.addObserver(self, selector: #selector(reload(notification:)), name: Notification.Name(rawValue: "RenderResultViewUpdatePathTracer") as NSNotification.Name, object: nil)
42 | }
43 |
44 | override func viewDidAppear()
45 | {
46 | childViewControllers.flatMap{$0 as? ShaderChooserViewController}.forEach{$0.delegate = self}
47 | }
48 |
49 | @objc func reload(notification: NSNotification?)
50 | {
51 | materials = ApplicationDelegate.scene?.objects.flatMap{$0.materials}.distinct().sorted{$0.name < $1.name} ?? []
52 | self.materialList.reloadData()
53 | }
54 | }
55 |
56 | extension MaterialEditorViewController: NSTableViewDataSource
57 | {
58 | func numberOfRows(in tableView: NSTableView) -> Int
59 | {
60 | return materials.count
61 | }
62 | }
63 |
64 | extension MaterialEditorViewController: NSTableViewDelegate
65 | {
66 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
67 | {
68 | var cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("MaterialCell"), owner: self)
69 | if cell == nil
70 | {
71 | cell = NSTextField(frame: NSRect(x: 0, y: 0, width: tableView.frame.width, height: 27))
72 | cell?.identifier = NSUserInterfaceItemIdentifier("MaterialCell")
73 | if let textCell = cell as? NSTextField
74 | {
75 | textCell.isEditable = false
76 | textCell.isSelectable = false
77 | textCell.isBezeled = false
78 | textCell.drawsBackground = false
79 | }
80 | }
81 | (cell as? NSTextField)?.stringValue = materials[row].name
82 | return cell
83 | }
84 |
85 | func tableViewSelectionDidChange(_ notification: Notification)
86 | {
87 | let selectedIndex = materialList.selectedRow
88 | guard selectedIndex >= 0 else { return }
89 | guard let shaderChooserViewController = (childViewControllers.flatMap{$0 as? ShaderChooserViewController}.first) else { return }
90 | shaderChooserViewController.shader = materials[selectedIndex].shader
91 | }
92 | }
93 |
94 | extension MaterialEditorViewController: ShaderChooserDelegate
95 | {
96 | func shaderChooserDidChangeShader(chooser: ShaderChooserViewController)
97 | {
98 | let selectedIndex = materialList.selectedRow
99 | guard selectedIndex >= 0 else { return }
100 | self.materials[selectedIndex].shader = chooser.shader
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Path Tracing Demo/Matrix.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Matrix.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 31.07.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 | #if !os(watchOS)
28 | import Accelerate
29 | #endif
30 |
31 | //MARK: Matrix Declarations
32 |
33 | struct Matrix
34 | {
35 | var rows:[[Float]]
36 |
37 | var columns:[[Float]]
38 | {
39 | var columns = Array>(repeating: Array(repeating: 0.0, count: height), count: width)
40 | for i in 0 ..< height
41 | {
42 | for j in 0 ..< width
43 | {
44 | columns[j][i] = rows[i][j]
45 | }
46 | }
47 | return columns
48 | }
49 |
50 | var width:Int
51 | {
52 | return rows.first?.count ?? 0
53 | }
54 |
55 | var height:Int
56 | {
57 | return rows.count
58 | }
59 |
60 | var determinant: Float
61 | {
62 | guard ((3...4) ~= width) && height == 3 else { fatalError("Determinant not yet implemented for matrices (A|b) where A is not a 3x3-matrix.") }
63 | return rows[0][0] * rows[1][1] * rows[2][2]
64 | + rows[0][1] * rows[1][2] * rows[2][0]
65 | + rows[0][2] * rows[1][0] * rows[2][1]
66 | - rows[0][2] * rows[1][1] * rows[2][0]
67 | - rows[0][0] * rows[1][2] * rows[2][1]
68 | - rows[0][1] * rows[1][0] * rows[2][2]
69 | }
70 |
71 | var transposed: Matrix
72 | {
73 | return Matrix(columns: rows)
74 | }
75 |
76 | init(rows:[[Float]])
77 | {
78 | self.rows = rows
79 | }
80 |
81 | init(columns:[[Float]])
82 | {
83 | var rows = Array>(repeating: Array(repeating: 0.0, count: columns.count), count: columns.first?.count ?? 0)
84 | for i in 0 ..< columns.count
85 | {
86 | for j in 0 ..< (columns.first?.count ?? 0)
87 | {
88 | rows[j][i] = columns[i][j]
89 | }
90 | }
91 | self.rows = rows
92 | }
93 |
94 | init(vectors: [Vector3D])
95 | {
96 | self.init(columns: vectors.map{[$0.x, $0.y, $0.z]})
97 | }
98 |
99 | init(vectors: Vector3D...)
100 | {
101 | self.init(columns: vectors.map{[$0.x, $0.y, $0.z]})
102 | }
103 |
104 | init(data: [Float], width: Int)
105 | {
106 | let height = data.count / width
107 | var rows:[[Float]] = []
108 | for rowIndex in 0 ..< height
109 | {
110 | var row:[Float] = []
111 | for columnIndex in 0 ..< width
112 | {
113 | row += [data[columnIndex+rowIndex*width]]
114 | }
115 | rows += [row]
116 | }
117 | self.rows = rows
118 | }
119 |
120 | init(data: [Float], height: Int)
121 | {
122 | self.init(data: data, width: data.count / height)
123 | }
124 |
125 | init(rotatingWithAlpha alpha: Float, beta: Float, gamma: Float)
126 | {
127 | let zRot = Matrix(rows: [[ cosf(alpha), -sinf(alpha), 0],
128 | [ sinf(alpha), cosf(alpha), 0],
129 | [ 0, 0, 1]])
130 |
131 | let yRot = Matrix(rows: [[ cosf(beta), 0, sinf(beta)],
132 | [ 0, 1, 0],
133 | [ -sinf(beta), 0, cosf(beta)]])
134 |
135 | let xRot = Matrix(rows: [[ 1, 0, 0],
136 | [ 0, cosf(gamma), -sinf(gamma)],
137 | [ 0, sinf(gamma), cosf(gamma)]])
138 | self.rows = (zRot * yRot * xRot).rows
139 | }
140 |
141 |
142 | func solve3x3() -> (x: Float, y: Float, z: Float)?
143 | {
144 | let det = determinant
145 | guard determinant != 0 else { return nil }
146 |
147 | // solve using Cramer's rule (only works for quadratic matrices)
148 | // implemented only for 3x3 matrices (with right side included)
149 |
150 | let det1 = rows[0][3] * rows[1][1] * rows[2][2]
151 | + rows[0][1] * rows[1][2] * rows[2][3]
152 | + rows[0][2] * rows[1][3] * rows[2][1]
153 | - rows[0][2] * rows[1][1] * rows[2][3]
154 | - rows[0][3] * rows[1][2] * rows[2][1]
155 | - rows[0][1] * rows[1][3] * rows[2][2]
156 |
157 | let det2 = rows[0][0] * rows[1][3] * rows[2][2]
158 | + rows[0][3] * rows[1][2] * rows[2][0]
159 | + rows[0][2] * rows[1][0] * rows[2][3]
160 | - rows[0][2] * rows[1][3] * rows[2][0]
161 | - rows[0][0] * rows[1][2] * rows[2][3]
162 | - rows[0][3] * rows[1][0] * rows[2][2]
163 |
164 | let det3 = rows[0][0] * rows[1][1] * rows[2][3]
165 | + rows[0][1] * rows[1][3] * rows[2][0]
166 | + rows[0][3] * rows[1][0] * rows[2][1]
167 | - rows[0][3] * rows[1][1] * rows[2][0]
168 | - rows[0][0] * rows[1][3] * rows[2][1]
169 | - rows[0][1] * rows[1][0] * rows[2][3]
170 |
171 | return (x: det1 / det, y: det2 / det, z: det3 / det)
172 | }
173 | }
174 |
175 | extension Matrix : CustomStringConvertible
176 | {
177 | var description: String
178 | {
179 | return rows
180 | .map
181 | {
182 | $0.map{String(format: "%5.2f", $0)}
183 | .joined(separator: ", ")
184 | }
185 | .map{"|\($0)|"}
186 | .joined(separator: "\n")
187 | }
188 |
189 | }
190 |
191 | extension Matrix : Equatable { }
192 |
193 | let Matrix3x3Identity = Matrix(rows: [[1,0,0],[0,1,0],[0,0,1]])
194 |
195 | //MARK: Operators
196 |
197 | func == (left: Matrix, right: Matrix) -> Bool
198 | {
199 | return left.width == right.width &&
200 | left.height == right.height &&
201 | Array(left.rows.joined()) == Array(right.rows.joined())
202 | }
203 |
204 | #if os(watchOS)
205 | func * (left: Matrix, right: Matrix) -> Matrix
206 | {
207 | assert(left.width == right.height)
208 | var resultBuffer = Array<[Float]>(repeating: Array(repeating: 0, count: right.width), count: left.height)
209 |
210 | for row in 0 ..< left.height
211 | {
212 | for column in 0 ..< right.width
213 | {
214 | var sum: Float = 0
215 | for i in 0 ..< left.width
216 | {
217 | sum += left.rows[row][i] * right.columns[column][i]
218 | }
219 | resultBuffer[row][column] = sum
220 | }
221 | }
222 |
223 | return Matrix(rows: resultBuffer)
224 | }
225 | #else
226 | func * (left: Matrix, right: Matrix) -> Matrix
227 | {
228 | assert(left.width == right.height)
229 | var resultBuffer = Array(repeating: 0, count: left.height * right.width)
230 |
231 | vDSP_mmul(left.rows.flatMap{$0}, 1, right.rows.flatMap{$0}, 1, &resultBuffer, 1, vDSP_Length(left.height), vDSP_Length(right.width), vDSP_Length(left.width))
232 |
233 | return Matrix(data: resultBuffer, width: right.width)
234 | }
235 | #endif
236 |
237 | func * (left: Matrix, right: Vector3D) -> Vector3D
238 | {
239 | //print("Transforming Vector: \(right) using:\n\(left)")
240 | if left.width == 3 && left.height == 3
241 | {
242 | let resultX = left.rows[0][0] * right.x + left.rows[0][1] * right.y + left.rows[0][2] * right.z
243 | let resultY = left.rows[1][0] * right.x + left.rows[1][1] * right.y + left.rows[1][2] * right.z
244 | let resultZ = left.rows[2][0] * right.x + left.rows[2][1] * right.y + left.rows[2][2] * right.z
245 | let result = Vector3D(x: resultX, y: resultY, z: resultZ)
246 | //print("Result: \(result)")
247 | return result
248 | }
249 | else if left.width == 4 && left.height == 4
250 | {
251 | let resultX = left.rows[0][0] * right.x + left.rows[0][1] * right.y + left.rows[0][2] * right.z + left.rows[0][3]
252 | let resultY = left.rows[1][0] * right.x + left.rows[1][1] * right.y + left.rows[1][2] * right.z + left.rows[1][3]
253 | let resultZ = left.rows[2][0] * right.x + left.rows[2][1] * right.y + left.rows[2][2] * right.z + left.rows[2][3]
254 | let resultW = left.rows[3][0] * right.x + left.rows[3][1] * right.y + left.rows[3][2] * right.z + left.rows[3][3]
255 | let result = Vector3D(x: resultX / resultW, y: resultY / resultW, z: resultZ / resultW)
256 | //print("Result: \(result)")
257 | return result
258 | }
259 | fatalError("Incompatible matrix size.")
260 | }
261 |
262 | extension Matrix
263 | {
264 |
265 | // mutating func swapRow(_ row: Int, with otherRow: Int)
266 | // {
267 | // (rows[row], rows[otherRow]) = (rows[otherRow], rows[row])
268 | // }
269 | //
270 | // mutating func addRow(_ row: Int, to target: Int, withFactor factor: Float = 1.0)
271 | // {
272 | // //rows[target] = rows[target].enumerated().map{$1+self.rows[row][$0]*factor}
273 | // var result = rows[target]
274 | // vDSP_vsma(rows[row], 1, [factor], result, 1, &result, 1, vDSP_Length(rows[target].count))
275 | // rows[target] = result
276 | // }
277 | //
278 | // mutating func multiplyRow(_ row: Int, with factor: Float)
279 | // {
280 | // //rows[row] = rows[row].map{$0*factor}
281 | // var result = rows[row]
282 | // vDSP_vsmul(result, 1, [factor], &result, 1, vDSP_Length(rows[row].count))
283 | // rows[row] = result
284 | // }
285 | }
286 |
--------------------------------------------------------------------------------
/Path Tracing Demo/MeshImport.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MeshImport.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 31.07.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | private extension String
29 | {
30 | var trimmed: String
31 | {
32 | return self.trimmingCharacters(in: CharacterSet.whitespaces)
33 | }
34 | }
35 |
36 | class WavefrontModelImporter: NSObject, ProgressReporting
37 | {
38 | weak var materialDataSource: WavefrontModelImporterMaterialDataSource?
39 |
40 | var progress: Progress = Progress()
41 |
42 | func `import`(from url: URL) throws -> [Object3D]
43 | {
44 | let data = try Data(contentsOf: url)
45 |
46 |
47 |
48 | let materialURL = url.deletingLastPathComponent()
49 | .appendingPathComponent(
50 | url.lastPathComponent
51 | .components(separatedBy: ".")
52 | .dropLast()
53 | .joined(separator: "."))
54 | .appendingPathExtension("material")
55 | print(materialURL)
56 |
57 | let materials:[String: Material]
58 |
59 | if
60 | let materialData = try? Data(contentsOf: materialURL, options: []),
61 | let encodedMaterials = NSKeyedUnarchiver.unarchiveObject(with: materialData) as? NSDictionary
62 | {
63 | var mutableMaterials:[String: Material] = [:]
64 | for key in (encodedMaterials.allKeys.flatMap{$0 as? String})
65 | {
66 | guard let shader = (encodedMaterials[key] as? ShaderDecoder)?.decoded else { continue }
67 | mutableMaterials[key] = Material(withShader: shader, named: key)
68 | }
69 | materials = mutableMaterials
70 | }
71 | else
72 | {
73 | materials = [:]
74 | }
75 |
76 | guard let dataString = String(data: data, encoding: String.Encoding.utf8) else { throw NSError(domain: "com.PK.pathtracingtests.wavefront.import", code: 1, userInfo: [:]) }
77 |
78 | var points:[Point3D] = []
79 | var normals:[Vector3D] = []
80 | var textureCoordinates:[TextureCoordinate] = []
81 | var faces:[Triangle3D] = []
82 | var objects:[Object3D] = []
83 | var currentMaterialLibrary: String? = nil
84 | var currentMaterial: Material? = nil
85 | var currentObjectName: String? = nil
86 |
87 | var processedCount = 0
88 | let lines = dataString.components(separatedBy: CharacterSet.newlines).map{$0.trimmed}.filter{!$0.isEmpty}.filter{$0[$0.startIndex] != "#"}
89 |
90 | progress.totalUnitCount = Int64(lines.count)
91 | progress.completedUnitCount = 0
92 |
93 | for line in lines
94 | {
95 | let lineComponents = line.components(separatedBy: CharacterSet.whitespaces)
96 |
97 | if lineComponents[0] == "v"
98 | {
99 | guard lineComponents.count >= 4 else { continue }
100 | let coordinates = lineComponents[1 ... 3].flatMap(Float.init)
101 | guard coordinates.count == 3 else { continue }
102 | points.append(Point3D(x: coordinates[0], y: coordinates[1], z: coordinates[2]))
103 | }
104 | else if lineComponents[0] == "vn"
105 | {
106 | guard lineComponents.count >= 4 else { continue }
107 | let coordinates = lineComponents[1 ... 3].flatMap(Float.init)
108 | guard coordinates.count == 3 else { continue }
109 | normals.append(Vector3D(x: coordinates[0], y: coordinates[1], z: coordinates[2]))
110 | }
111 | else if lineComponents[0] == "vt"
112 | {
113 | guard lineComponents.count >= 3 else { continue }
114 | let coordinates = lineComponents[1 ... 2].flatMap(Float.init)
115 | guard coordinates.count == 2 else { continue }
116 | textureCoordinates.append(TextureCoordinate(u: coordinates[0], v: coordinates[1]))
117 | }
118 | else if lineComponents[0] == "f"
119 | {
120 | let vertexStrings = lineComponents[1 ..< lineComponents.count]
121 | guard vertexStrings.count >= 3 else { continue }
122 | var vertexData:[(point: Point3D, normal: Vector3D?, texture: TextureCoordinate?)] = []
123 | for vertex in vertexStrings
124 | {
125 | let vertexComponents = vertex.components(separatedBy: "/").flatMap{Int($0)}
126 |
127 | let point: Point3D
128 | let normal: Vector3D?
129 | let textureCoordinate: TextureCoordinate?
130 |
131 | if vertexComponents.count == 3
132 | {
133 | point = points[vertexComponents[0]-1]
134 | textureCoordinate = textureCoordinates[vertexComponents[1]-1]
135 | normal = normals[vertexComponents[2]-1]
136 | }
137 | else if vertexComponents.count == 2
138 | {
139 | if vertex.contains("//")
140 | {
141 | point = points[vertexComponents[0]-1]
142 | normal = normals[vertexComponents[1]-1]
143 | textureCoordinate = nil
144 | }
145 | else
146 | {
147 | point = points[vertexComponents[0]-1]
148 | normal = nil
149 | textureCoordinate = textureCoordinates[vertexComponents[2]-1]
150 | }
151 | }
152 | else
153 | {
154 | point = points[vertexComponents[0]-1]
155 | normal = nil
156 | textureCoordinate = nil
157 | }
158 | vertexData.append((point: point, normal: normal, texture: textureCoordinate))
159 | }
160 |
161 | //TODO: Triangulation
162 |
163 | let vertexA = Vertex3D(point: vertexData[0].point,
164 | normal: vertexData[0].normal ?? ((vertexData[1].point - vertexData[0].point) ⨯ (vertexData[2].point - vertexData[0].point)),
165 | textureCoordinate: vertexData[0].texture ?? TextureCoordinate(u: 0, v: 0))
166 | let vertexB = Vertex3D(point: vertexData[1].point,
167 | normal: vertexData[1].normal ?? ((vertexData[2].point - vertexData[1].point) ⨯ (vertexData[0].point - vertexData[1].point)),
168 | textureCoordinate: vertexData[1].texture ?? TextureCoordinate(u: 1, v: 0))
169 | let vertexC = Vertex3D(point: vertexData[2].point,
170 | normal: vertexData[2].normal ?? ((vertexData[1].point - vertexData[2].point) ⨯ (vertexData[0].point - vertexData[2].point)),
171 | textureCoordinate: vertexData[2].texture ?? TextureCoordinate(u: 0, v: 1))
172 |
173 | let triangle: Triangle3D
174 | if let material = currentMaterial
175 | {
176 | triangle = Triangle3D(a: vertexA, b: vertexB, c: vertexC, material: material)
177 | }
178 | else
179 | {
180 | triangle = Triangle3D(a: vertexA, b: vertexB, c: vertexC)
181 | }
182 | faces.append(triangle)
183 | }
184 | else if lineComponents[0] == "o"
185 | {
186 | if lineComponents.count >= 2
187 | {
188 | currentObjectName = lineComponents[1]
189 | }
190 |
191 | guard !faces.isEmpty else { continue }
192 |
193 | var object = ExplicitTriangleMesh3D(triangles: faces)
194 | if let objectName = currentObjectName
195 | {
196 | object.name = objectName
197 | }
198 | objects.append(object)
199 | faces = []
200 | }
201 | else if lineComponents[0] == "mtllib"
202 | {
203 | guard lineComponents.count >= 2 else { continue }
204 | currentMaterialLibrary = lineComponents[1]
205 | }
206 | else if lineComponents[0] == "usemtl"
207 | {
208 | guard let materialLibrary = currentMaterialLibrary,
209 | lineComponents.count >= 2
210 | else
211 | {
212 | continue
213 | }
214 | if let material = materials[lineComponents[1]]
215 | {
216 | currentMaterial = material
217 | }
218 | else
219 | {
220 | currentMaterial = materialDataSource?.material(named: lineComponents[1], in: materialLibrary)
221 | }
222 | }
223 |
224 | processedCount += 1
225 | progress.completedUnitCount = Int64(processedCount)
226 | }
227 | if !faces.isEmpty
228 | {
229 | var object = ExplicitTriangleMesh3D(triangles: faces)
230 | if let objectName = currentObjectName
231 | {
232 | object.name = objectName
233 | }
234 | objects.append(object)
235 | }
236 |
237 | return objects
238 | }
239 | }
240 |
241 | protocol WavefrontModelImporterMaterialDataSource: class
242 | {
243 | func material(named name: String, `in` materialLibrary: String) -> Material
244 | }
245 |
246 | class WavefrontModelExporter
247 | {
248 | class func export(scene: Scene3D) -> (wavefrontData: Data, materialData: Data)
249 | {
250 | var dataString = "# Path Tracing Demo by PK\n"
251 |
252 | var offset = 0
253 |
254 | for object in scene.objects
255 | {
256 | dataString.append("o \(object.name)\n")
257 |
258 | let triangles = object.transformed
259 |
260 | let vertices = triangles
261 | .flatMap{[$0.a, $0.b, $0.c]}
262 |
263 | let vertexLocationString = vertices.map{$0.point}
264 | .map{"v \($0.x) \($0.y) \($0.z)"}
265 | .joined(separator: "\n")
266 |
267 | let vertexNormalString = vertices.map{$0.normal}
268 | .map{"vn \($0.x) \($0.y) \($0.z)"}
269 | .joined(separator: "\n")
270 |
271 | let vertexTextureCoordinateString = vertices.map{$0.textureCoordinate}
272 | .map{"vt \($0.u) \($0.v)"}
273 | .joined(separator: "\n")
274 |
275 | dataString.append(vertexLocationString)
276 | dataString.append("\n")
277 | dataString.append(vertexNormalString)
278 | dataString.append("\n")
279 | dataString.append(vertexTextureCoordinateString)
280 | dataString.append("\n")
281 |
282 | var currentMaterial = ""
283 |
284 | for (index, triangle) in triangles.enumerated()
285 | {
286 | if currentMaterial != triangle.material.name
287 | {
288 | currentMaterial = triangle.material.name
289 | dataString.append("usemtl \(currentMaterial)\n")
290 | dataString.append("s 1")
291 | }
292 | let first = index * 3 + offset
293 | let second = first + 1
294 | let third = second + 1
295 | dataString.append("f \(first)/\(first)/\(first) \(second)/\(second)/\(second) \(third)/\(third)/\(third)\n")
296 | }
297 |
298 | offset += triangles.count * 3
299 | }
300 |
301 | let materials = scene.objects.flatMap{$0.materials}.distinct()
302 |
303 | let materialShaders = NSMutableDictionary()
304 |
305 | for material in materials
306 | {
307 | materialShaders[material.name] = (material.shader as? ShaderEncoding)?.encoded
308 | }
309 |
310 | let encodedShaders:Data
311 | encodedShaders = NSKeyedArchiver.archivedData(withRootObject: materialShaders)
312 |
313 | guard let wavefrontData = dataString.data(using: .ascii) else { fatalError("unable to encode data string") }
314 | return (wavefrontData: wavefrontData, materialData: encodedShaders)
315 | }
316 |
317 | class func exportMaterials(of scene: Scene3D) -> Data
318 | {
319 | let materials = scene.objects.flatMap{$0.materials}.distinct()
320 |
321 | let materialShaders = NSMutableDictionary()
322 |
323 | for material in materials
324 | {
325 | materialShaders[material.name] = (material.shader as? ShaderEncoding)?.encoded
326 | }
327 |
328 | let encodedShaders:Data
329 | encodedShaders = NSKeyedArchiver.archivedData(withRootObject: materialShaders)
330 |
331 | return encodedShaders
332 | }
333 | }
334 |
335 |
336 |
--------------------------------------------------------------------------------
/Path Tracing Demo/Objects.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Objects.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 31.07.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | protocol Object3D : CustomStringConvertible
29 | {
30 | var location: Point3D { get set }
31 | var scale: Float { get set }
32 | var rotation: (alpha:Float, beta:Float, gamma:Float) { get set }
33 |
34 | var transformed:[Triangle3D] { get }
35 |
36 | var name: String { get }
37 |
38 | var materials:[Material] { get }
39 |
40 | nonmutating func translated(to point: Point3D) -> Object3D
41 | nonmutating func scaled(_ factor: Float) -> Object3D
42 |
43 | mutating func assignMaterial(_ material: Material)
44 | }
45 |
46 | struct ExplicitTriangleMesh3D : Object3D
47 | {
48 | var location: Point3D
49 | var scale: Float
50 | var rotation: (alpha:Float, beta:Float, gamma:Float)
51 | var triangles:[Triangle3D]
52 | var name: String
53 | var materials: [Material]
54 | {
55 | return triangles.map{$0.material}.distinct()
56 | }
57 |
58 | init(location: Point3D, scale: Float, rotation: (alpha:Float, beta:Float, gamma:Float), triangles: [Triangle3D], name: String = "unnamed")
59 | {
60 | self.location = location
61 | self.scale = scale
62 | self.rotation = rotation
63 | self.triangles = triangles
64 | self.name = name
65 | }
66 |
67 | init(triangles: [Triangle3D])
68 | {
69 | self.triangles = triangles
70 | location = Vector3DZero
71 | scale = 1.0
72 | rotation = (alpha: 0.0, beta: 0.0, gamma: 0.0)
73 | self.name = "unnamed"
74 | }
75 |
76 | var transformed:[Triangle3D]
77 | {
78 | let rotationMatrix = Matrix(rotatingWithAlpha: rotation.alpha, beta: rotation.beta, gamma: rotation.gamma)
79 | return triangles
80 | .map
81 | {
82 | // Applying rotation. The transformation matrix for normals is identical, because (A^-1)^T=A for rotation matrices
83 | Triangle3D(
84 | a: Vertex3D(point: rotationMatrix * $0.a.point, normal: rotationMatrix * $0.a.normal, textureCoordinate: $0.a.textureCoordinate),
85 | b: Vertex3D(point: rotationMatrix * $0.b.point, normal: rotationMatrix * $0.b.normal, textureCoordinate: $0.b.textureCoordinate),
86 | c: Vertex3D(point: rotationMatrix * $0.c.point, normal: rotationMatrix * $0.c.normal, textureCoordinate: $0.c.textureCoordinate),
87 | material: $0.material)
88 | }
89 | .map{$0.scaled(self.scale)}
90 | .map{$0.translated(to: self.location)}
91 | }
92 |
93 | func translated(to point: Point3D) -> Object3D
94 | {
95 | return ExplicitTriangleMesh3D(location: location + point, scale: scale, rotation: rotation, triangles: triangles)
96 | }
97 |
98 | func scaled(_ factor: Float) -> Object3D
99 | {
100 | return ExplicitTriangleMesh3D(location: location, scale: scale * factor, rotation: rotation, triangles: triangles)
101 | }
102 |
103 | var description: String
104 | {
105 | let triangleString = triangles.map{$0.description}.joined(separator: "\n\t- ")
106 | return "- ExplicitTriangleMesh3D: (location: \(location), scale: \(scale), rotation: \(rotation)\n\t- \(triangleString)"
107 | //return "Object3D"
108 | }
109 |
110 | mutating func assignMaterial(_ material: Material)
111 | {
112 | for i in 0 ..< triangles.count
113 | {
114 | triangles[i].material = material
115 | }
116 | }
117 | }
118 |
119 | //struct IndexedVertexMesh3D : Object3D
120 | //{
121 | // var location: Point3D
122 | // var scale: Float
123 | // var rotation: (alpha:Float, beta:Float, gamma:Float)
124 | // var vertices: [Point3D]
125 | // var triangles: [[Int]]
126 | //
127 | // var transformed: [Triangle3D]
128 | // {
129 | // return triangles
130 | // .map{[vertices[$0[0]], vertices[$0[1]], vertices[$0[2]]]}
131 | // .map{Triangle3D(a: $0[0], b: $0[1], c: $0[2])}
132 | // }
133 | //
134 | // func translated(to point: Point3D) -> Object3D
135 | // {
136 | // return IndexedVertexMesh3D(location: location + point, scale: scale, rotation: rotation, vertices: vertices, triangles: triangles)
137 | // }
138 | //
139 | // func scaled(_ factor: Float) -> Object3D
140 | // {
141 | // return IndexedVertexMesh3D(location: location, scale: scale * factor, rotation: rotation, vertices: vertices, triangles: triangles)
142 | // }
143 | //
144 | // var description: String
145 | // {
146 | // return "- SharedVertexMesh3D"
147 | // }
148 | //}
149 |
150 | class Camera
151 | {
152 | var location: Point3D
153 | var rotation: (alpha: Float, beta: Float, gamma: Float)
154 | var apertureSize: Float
155 | var focalDistance: Float
156 | var fieldOfView: Float
157 |
158 | init(location: Point3D, rotation: (alpha: Float, beta: Float, gamma: Float), apertureSize: Float, focalDistance: Float, fieldOfView: Float)
159 | {
160 | self.location = location
161 | self.rotation = rotation
162 | self.apertureSize = apertureSize
163 | self.focalDistance = focalDistance
164 | self.fieldOfView = fieldOfView
165 | }
166 | }
167 |
168 | struct Scene3D : CustomStringConvertible
169 | {
170 | var objects:[Object3D]
171 | var camera: Camera
172 | var environmentShader: EnvironmentShader
173 |
174 | var wavefrontString: String
175 | {
176 | let triangles = objects.map{$0.transformed}.joined()
177 | let vertices = triangles.map{[$0.a, $0.b, $0.c]}.joined()
178 | let vertexString = vertices.map{"v \($0.point.x) \($0.point.y) \($0.point.z)"}.joined(separator: "\n")
179 | let triangleString:String = triangles
180 | .enumerated()
181 | .map{$0.offset}
182 | .map{$0*3+1}
183 | .map{"f \($0) \($0+1) \($0+2)"}
184 | .joined(separator: "\n")
185 | return "# Path tracing tests by PK\n# Vertices\n\(vertexString)\n# Triangles\n\(triangleString)"
186 | }
187 |
188 | var description: String
189 | {
190 | return "Scene3D:\n\(objects.map{$0.description}.joined(separator: "\n"))"
191 | }
192 |
193 | var triangles: [Triangle3D]
194 | {
195 | //let rotationMatrix = Matrix(rotatingWithAlpha: -camera.rotation.alpha, beta: -camera.rotation.beta, gamma: -camera.rotation.gamma)
196 | return objects
197 | .flatMap{$0.transformed}
198 | // .map{ $0.translated(to: -camera.location) }
199 | // .map
200 | // {
201 | // Triangle3D(
202 | // a: Vertex3D(point: rotationMatrix * $0.a.point, normal: rotationMatrix * $0.a.normal, textureCoordinate: $0.a.textureCoordinate),
203 | // b: Vertex3D(point: rotationMatrix * $0.b.point, normal: rotationMatrix * $0.b.normal, textureCoordinate: $0.b.textureCoordinate),
204 | // c: Vertex3D(point: rotationMatrix * $0.c.point, normal: rotationMatrix * $0.c.normal, textureCoordinate: $0.c.textureCoordinate),
205 | // material: $0.material)
206 | // }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/Path Tracing Demo/Pathtracing.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Pathtracing.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 30.07.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 | import CoreGraphics
28 | #if !os(watchOS)
29 | import QuartzCore
30 | #endif
31 |
32 | #if os(watchOS)
33 | //Workaround for WatchOS, as QuartzCore is not available.
34 | func CACurrentMediaTime() -> Double
35 | {
36 | //TODO: implement
37 | return 0.0
38 | }
39 | #endif
40 |
41 | private extension Int
42 | {
43 | func loop(block: (Int) throws -> (T)) rethrows -> [T]
44 | {
45 | return try (0 ..< self).map(block)
46 | }
47 | }
48 |
49 | class PathTracer : PathTracingWorkerDelegate
50 | {
51 | let scene:Scene3D
52 |
53 | lazy var triangleStore: TriangleStore =
54 | {
55 | return OctreeTriangleStore(with: self.scene.triangles)
56 | }()
57 |
58 | private var workers: [LocalPathTracingWorker] = []
59 | private var context: CGContext!
60 | private(set) var result: CGImage?
61 |
62 | weak var delegate: PathTracerDelegate?
63 | private var managerQueue: DispatchQueue!
64 | //private var compositingQueue: DispatchQueue!
65 | private var workerQueue: DispatchQueue!
66 | private var tiles:[(location: (x: Int, y: Int), size: (width: Int, height: Int))] = []
67 | private var totalTileCount = 0
68 | private var height: Int = 0
69 | private var width: Int = 0
70 | private var startTime: Double = 0
71 |
72 | var useLogarithmicColor: Bool = false
73 |
74 | init(withScene scene: Scene3D)
75 | {
76 | self.scene = scene
77 | }
78 |
79 | /**
80 |
81 | Renders an image of a scene using path tracing.
82 | The image will have given width and height.
83 |
84 | The ray depth determines, how many bounces will be rendered per ray.
85 | Higher numbers increase realism but also increase render time.
86 |
87 | The tile size determines, how many pixels will be rendered by a single worker at once.
88 | Bigger tiles reduce overhead but results will not be available as fast.
89 |
90 | The number of workers can be used to control CPU utilization.
91 | One worker will only operate on a single thread, so if the system has 4 cores,
92 | 4 workers will provide maximum utilization.
93 |
94 | The number of samples determines, how often the same ray will be casted.
95 | If rough or diffuse materials are used, higher numbers will reduce noise,
96 | which will increase rendering time linearly.
97 |
98 | - parameter width: Width of the image, which will be rendered, in pixels
99 | - parameter height: Height of the image, which will be rendered, in pixels
100 |
101 | - parameter tileSize: Size of an area rendered by a single worker at once. Defaults to 32x32.
102 |
103 | - parameter workerCount: Number of workers rendering the image.
104 | Defaults to the number of available (logical) CPU cores on the machine.
105 |
106 | - parameter samples: Samples per (sub-)pixel
107 |
108 | */
109 | func traceRays(width: Int,
110 | height: Int,
111 | rayDepth: Int = 16,
112 | tileSize: (width: Int, height: Int) = (width: 32, height: 32),
113 | workerCount: Int = ProcessInfo.processInfo.processorCount,
114 | samples: Int = 1)
115 | {
116 | guard (workers.reduce(true){$0 && $1.idle}) else { fatalError("PathTracer not idle") }
117 |
118 | managerQueue = DispatchQueue(label: "pathtracer.tracerays.manager")
119 | //compositingQueue = DispatchQueue(label: "pathtracer.tracerays.compositor")
120 | workerQueue = DispatchQueue(label: "pathtracing.tracerays.workers", attributes: .concurrent)
121 | managerQueue.async
122 | {
123 | self.workers = (0 ..< workerCount).map
124 | { index in
125 | LocalPathTracingWorker(
126 | queue: self.workerQueue,
127 | totalSize: (width: width, height: height),
128 | rayDepth: rayDepth,
129 | triangles: self.triangleStore,
130 | samples: samples,
131 | camera: self.scene.camera,
132 | environmentShader: self.scene.environmentShader)
133 | }
134 |
135 | self.workers.forEach{$0.delegate = self}
136 | let horizontalTileCount = Int(ceil(Float(width / tileSize.width)))
137 | let verticalTileCount = Int(ceil(Float(height / tileSize.height))) + 1
138 |
139 | self.width = width
140 | self.height = height
141 |
142 | self.totalTileCount = horizontalTileCount * verticalTileCount
143 |
144 | let tileIndices = horizontalTileCount
145 | .loop{ x in verticalTileCount.loop{ y in (x, y)}}
146 | .flatMap{$0}
147 |
148 | self.tiles = tileIndices
149 | .sorted
150 | { (a, b) in
151 | let distAX = Float(a.0 - horizontalTileCount / 2 + 1)
152 | let distAY = Float(a.1 - verticalTileCount / 2)
153 | let distBX = Float(b.0 - horizontalTileCount / 2)
154 | let distBY = Float(b.1 - verticalTileCount / 2)
155 | let distA = sqrtf(distAX * distAX + distAY * distAY)
156 | let distB = sqrtf(distBX * distBX + distBY * distBY)
157 | return distA < distB
158 | }
159 | .map{ xy in (location: (x: xy.0 * tileSize.width, y: (xy.1 + 1) * tileSize.height), size: (width: tileSize.width, heigth: tileSize.height))}
160 |
161 | self.context = CGContext(
162 | data: nil,
163 | width: width,
164 | height: height,
165 | bitsPerComponent: 8,
166 | bytesPerRow: width * 4,
167 | space: CGColorSpaceCreateDeviceRGB(),
168 | bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)
169 |
170 | self.startTime = CACurrentMediaTime()
171 | self.workers.forEach{$0.render(region: self.tiles.removeFirst())}
172 | }
173 | }
174 |
175 | fileprivate final func worker(worker: LocalPathTracingWorker, didFinish region: (location: (x: Int, y: Int), size: (width: Int, height: Int)), result: [Color])
176 | {
177 | managerQueue.async
178 | {
179 | if !self.tiles.isEmpty
180 | {
181 | worker.render(region: self.tiles.removeFirst())
182 | }
183 | else if (self.workers.reduce(true){$0 && $1.idle})
184 | {
185 | self.workers = []
186 | }
187 | let rect = CGRect(
188 | x: region.location.x,
189 | y: self.height - region.location.y,
190 | width: region.size.width,
191 | height: region.size.height)
192 |
193 | var mutableResult: [Float]
194 | if self.useLogarithmicColor {
195 | mutableResult = result.map{$0.toLogColor()}.flatMap{[$0.red, $0.green, $0.blue, $0.alpha]}
196 | } else {
197 | mutableResult = result.flatMap{[$0.red, $0.green, $0.blue, $0.alpha]}
198 | }
199 |
200 | let ctx = CGContext(
201 | data: &mutableResult,
202 | width: region.size.width,
203 | height: region.size.height,
204 | bitsPerComponent: 32,
205 | bytesPerRow: region.size.width * 4 * 4,
206 | space: CGColorSpaceCreateDeviceRGB(),
207 | bitmapInfo: CGBitmapInfo.floatComponents.rawValue | CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue)
208 | guard let regionImage = ctx?.makeImage() else { fatalError("Image could not be created") }
209 |
210 | self.context.draw(regionImage, in: rect)
211 |
212 | guard let image = self.context.makeImage() else { return }
213 |
214 | if self.tiles.isEmpty && (self.workers.reduce(true){$0 && $1.idle})
215 | {
216 | let time = CACurrentMediaTime()
217 | print("Finished rendering. Duration: \(Float(time - self.startTime))s")
218 | DispatchQueue.main.async
219 | {
220 | self.result = image
221 | self.delegate?.pathTracingDidFinish(render: image)
222 | }
223 | }
224 | else
225 | {
226 | let progress = 1.0 - Float(self.tiles.count) / Float(self.totalTileCount)
227 | DispatchQueue.main.async
228 | {
229 | self.result = image
230 | self.delegate?.pathTracingDidUpdate(render: image, progress: progress)
231 | }
232 | }
233 | }
234 | }
235 |
236 | fileprivate func worker(worker: LocalPathTracingWorker, didPerformUpdateOf region: (location: (x: Int, y: Int), size: (width: Int, height: Int)), result: [Color])
237 | {
238 | managerQueue.async
239 | {
240 | var mutableResult: [Float]
241 | if self.useLogarithmicColor {
242 | mutableResult = result.map{$0.toLogColor()}.flatMap{[$0.red, $0.green, $0.blue, $0.alpha]}
243 | } else {
244 | mutableResult = result.flatMap{[$0.red, $0.green, $0.blue, $0.alpha]}
245 | }
246 |
247 | let ctx = CGContext(
248 | data: &mutableResult,
249 | width: region.size.width,
250 | height: region.size.height,
251 | bitsPerComponent: 32,
252 | bytesPerRow: region.size.width * 4 * 4,
253 | space: CGColorSpaceCreateDeviceRGB(),
254 | bitmapInfo: CGBitmapInfo.floatComponents.rawValue | CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue)
255 | guard let regionImage = ctx?.makeImage() else { fatalError("Image could not be created") }
256 |
257 | let rect = CGRect(
258 | x: region.location.x,
259 | y: self.height - region.location.y,
260 | width: region.size.width,
261 | height: region.size.height)
262 |
263 | self.context.draw(regionImage, in: rect)
264 |
265 | guard let image = self.context.makeImage() else { return }
266 |
267 | let progress = 1.0 - Float(self.tiles.count) / Float(self.totalTileCount)
268 | DispatchQueue.main.async
269 | {
270 | self.result = image
271 | self.delegate?.pathTracingDidUpdate(render: image, progress: progress)
272 | }
273 | }
274 | }
275 |
276 | func stop()
277 | {
278 | tiles.removeAll()
279 | workers.forEach{$0.stopRendering()}
280 | }
281 | }
282 |
283 | protocol PathTracerDelegate : class
284 | {
285 | func pathTracingDidUpdate(render: CGImage, progress: Float)
286 | func pathTracingDidFinish(render: CGImage)
287 | }
288 |
289 | protocol PathTracingWorker
290 | {
291 | func load(triangles: TriangleStore, rayDepth: Int, camera: Camera, ambientColor: Color)
292 |
293 | func render(region: (location: (x: Int, y: Int), size: (width: Int, height: Int)))
294 |
295 | func stopRendering()
296 | }
297 |
298 | private class LocalPathTracingWorker
299 | {
300 | private let queue: DispatchQueue
301 | private let totalSize: (width: Int, height: Int)
302 | private let triangles:TriangleStore
303 | weak var delegate: PathTracingWorkerDelegate?
304 | private(set) var idle: Bool = true
305 | private let samples: Int
306 | private let camera: Camera
307 | private let rayDepth: Int
308 | private var shouldStop: Bool = false
309 | private var environmentShader: EnvironmentShader
310 |
311 | init(queue: DispatchQueue,
312 | totalSize: (width: Int, height: Int),
313 | rayDepth: Int,
314 | triangles: TriangleStore,
315 | samples: Int,
316 | camera: Camera,
317 | environmentShader: EnvironmentShader)
318 | {
319 | self.queue = queue
320 | self.totalSize = totalSize
321 | self.triangles = triangles
322 | self.rayDepth = rayDepth
323 | self.samples = samples
324 | self.camera = camera
325 | self.environmentShader = environmentShader
326 | }
327 |
328 | fileprivate final func render(region: (location: (x: Int, y: Int), size: (width: Int, height: Int)))
329 | {
330 | idle = false
331 | queue.async
332 | {
333 | var lastReportTime = CACurrentMediaTime()
334 |
335 | var data = [Color](repeating: Color.black(), count: region.size.width * region.size.height)
336 | let horizontalScaleFactor = 1.0 / Float(self.totalSize.height) * Float(self.totalSize.width)
337 |
338 | let fovScalingFactor = tanf(self.camera.fieldOfView * 0.5)
339 |
340 | let rotationMatrix = Matrix(rotatingWithAlpha: self.camera.rotation.alpha, beta: self.camera.rotation.beta, gamma: self.camera.rotation.gamma)
341 |
342 | for y in region.location.y ..< (region.location.y + region.size.height)
343 | {
344 | for x in region.location.x ..< (region.location.x + region.size.width)
345 | {
346 | var color:Color = .black()
347 | for _ in 0 ..< self.samples
348 | {
349 | let angle = Float(drand48()) * 2.0 * Float.pi
350 | let radius = sqrtf(Float(drand48()))
351 |
352 | let baseOffsetX = (cosf(angle) * radius) * self.camera.apertureSize
353 | let baseOffsetY = (sinf(angle) * radius) * self.camera.apertureSize
354 | let rayOffsetX = baseOffsetX / self.camera.focalDistance
355 | let rayOffsetY = baseOffsetY / self.camera.focalDistance
356 | let offsetX = Float(-x) + Float(drand48()) - 0.5
357 | let offsetY = Float(y) + Float(drand48()) - 0.5
358 | let rayDirectionX = (offsetX / Float(self.totalSize.width) + 0.5) * horizontalScaleFactor * fovScalingFactor - rayOffsetX
359 | let rayDirectionY = (offsetY / Float(self.totalSize.height) - 0.5) * fovScalingFactor - rayOffsetY
360 |
361 | let rayBase = rotationMatrix * Vector3D(x: baseOffsetX, y: 0, z: baseOffsetY) + self.camera.location
362 | let rayDirection = rotationMatrix * Vector3D(x: rayDirectionX, y: 1, z: rayDirectionY).normalized
363 | let ray = Ray3D(base: rayBase,
364 | direction: rayDirection)
365 |
366 | let closestIntersection = self.triangles.nearestIntersectingTriangle(forRay: ray)
367 |
368 | if let intersection = closestIntersection
369 | {
370 | let sampleColor = intersection.triangle.material.shader.color(
371 | forTriangle: intersection.triangle,
372 | at: intersection.barycentricIntersection,
373 | point: ray.point(for: intersection.ray),
374 | rayDirection: ray.direction,
375 | sceneGeometry: self.triangles,
376 | environmentShader: self.environmentShader,
377 | previousColor: .white(),
378 | maximumRayDepth: self.rayDepth)
379 |
380 | color.red += sampleColor.red
381 | color.green += sampleColor.green
382 | color.blue += sampleColor.blue
383 | color.alpha += sampleColor.alpha
384 | }
385 | else
386 | {
387 | let sampleColor = self.environmentShader.environmentColor(for: ray.direction)
388 |
389 | color.red += sampleColor.red
390 | color.green += sampleColor.green
391 | color.blue += sampleColor.blue
392 | color.alpha += sampleColor.alpha
393 | }
394 | }
395 | let index = ((y - region.location.y) * region.size.width + (x - region.location.x))
396 |
397 | //TODO: Implement Alpha blending
398 | data[index] = (color * (1.0 / Float(self.samples)))
399 | }
400 | if self.shouldStop
401 | {
402 | self.shouldStop = false
403 | self.idle = true
404 | return
405 | }
406 | if CACurrentMediaTime() - lastReportTime >= 2.0
407 | {
408 | lastReportTime = CACurrentMediaTime()
409 | self.delegate?.worker(worker: self, didPerformUpdateOf: region, result: data)
410 | }
411 | }
412 |
413 | self.idle = true
414 | self.delegate?.worker(worker: self, didFinish: region, result: data)
415 | }
416 | }
417 |
418 | func stopRendering()
419 | {
420 | shouldStop = true
421 | }
422 | }
423 |
424 | private protocol PathTracingWorkerDelegate : class
425 | {
426 | func worker(worker: LocalPathTracingWorker, didFinish region: (location: (x: Int, y: Int), size: (width: Int, height: Int)), result: [Color])
427 | func worker(worker: LocalPathTracingWorker, didPerformUpdateOf region: (location: (x: Int, y: Int), size: (width: Int, height: Int)), result: [Color])
428 | }
429 |
--------------------------------------------------------------------------------
/Path Tracing Demo/PriorityQueue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PriorityQueue.swift
3 | // Path Tracing Demo
4 | //
5 | // Created by Palle Klewitz on 16.08.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | public struct BinomialHeapHandle
29 | {
30 | fileprivate init()
31 | {
32 | handle = (UInt64(arc4random()) << 32) | UInt64(arc4random())
33 | }
34 |
35 | fileprivate let handle: UInt64
36 | }
37 |
38 | extension BinomialHeapHandle: Equatable {}
39 |
40 | public func == (left: BinomialHeapHandle, right: BinomialHeapHandle) -> Bool
41 | {
42 | return left.handle == right.handle
43 | }
44 |
45 | extension BinomialHeapHandle: Hashable
46 | {
47 | public var hashValue: Int
48 | {
49 | return handle.hashValue
50 | }
51 | }
52 |
53 | public class BinomialHeap
54 | {
55 | fileprivate var roots:[BinomialHeapNode] = []
56 | private var handles:[UInt64: BinomialHeapNode] = [:]
57 |
58 | public private(set) var count: Int = 0
59 |
60 | public init()
61 | {
62 |
63 | }
64 |
65 | public func append(element: Element, withPriority priority: Priority) -> BinomialHeapHandle
66 | {
67 | let node = BinomialHeapNode(priority: priority, element: element)
68 | roots.append(node)
69 | cleanup()
70 | count += 1
71 |
72 | let handle = BinomialHeapHandle()
73 | handles[handle.handle] = node
74 | return handle
75 | }
76 |
77 | public func append(contentsOf collection: Array<(Priority, Element)>) -> [BinomialHeapHandle]
78 | {
79 | let nodes = collection.map{BinomialHeapNode(priority: $0.0, element: $0.1)}
80 | roots.append(contentsOf: nodes)
81 | cleanup()
82 | count += collection.count
83 |
84 | var handles = (0 ..< collection.count).map{_ in BinomialHeapHandle()}
85 |
86 | for i in 0 ..< collection.count
87 | {
88 | while self.handles[handles[i].handle] != nil
89 | {
90 | handles[i] = BinomialHeapHandle()
91 | }
92 |
93 | self.handles[handles[i].handle] = nodes[i]
94 | }
95 |
96 | return handles
97 | }
98 |
99 | public func append(contentsOf priorityQueue: BinomialHeap)
100 | {
101 | roots.append(contentsOf: priorityQueue.roots)
102 | cleanup()
103 | count += priorityQueue.count
104 | for (handle, element) in priorityQueue.handles
105 | {
106 | handles[handle] = element
107 | }
108 | }
109 |
110 | public func decreasePriority(of handle: BinomialHeapHandle, to newPriority: Priority) -> Bool
111 | {
112 | guard let node = handles[handle.handle], node.priority >= newPriority else { return false }
113 |
114 | node.priority = newPriority
115 | node.siftUp()
116 |
117 | return true
118 | }
119 |
120 | public func priority(of handle: BinomialHeapHandle) -> Priority?
121 | {
122 | return handles[handle.handle]?.priority
123 | }
124 |
125 | public func contains(_ handle: BinomialHeapHandle) -> Bool
126 | {
127 | return handles[handle.handle] != nil
128 | }
129 |
130 | public func element(`for` handle: BinomialHeapHandle) -> Element?
131 | {
132 | return handles[handle.handle]?.element
133 | }
134 |
135 | private func cleanup()
136 | {
137 | let maxRank = roots.map{$0.depth}.max() ?? 0
138 | var rankedRoots = [[BinomialHeapNode]](repeating: [], count: maxRank + 1)
139 | for root in roots
140 | {
141 | rankedRoots[root.depth].append(root)
142 | }
143 | var i = 0
144 | while i < rankedRoots.count
145 | {
146 | while rankedRoots[i].count >= 2
147 | {
148 | let node1 = rankedRoots[i].removeLast()
149 | let node2 = rankedRoots[i].removeLast()
150 | let merged = mergeNode(node1, with: node2)
151 | if rankedRoots.count > i + 1
152 | {
153 | rankedRoots[i + 1].append(merged)
154 | }
155 | else
156 | {
157 | rankedRoots.append([merged])
158 | }
159 | }
160 | i += 1
161 | }
162 | roots = rankedRoots.flatMap{$0}.sorted(by: {$0.priority < $1.priority})
163 | }
164 |
165 | private func mergeNode(_ node1: BinomialHeapNode, with node2: BinomialHeapNode) -> BinomialHeapNode
166 | {
167 | if node1.priority <= node2.priority
168 | {
169 | node1.children.append(node2)
170 | node1.depth = max(node1.depth, node2.depth + 1)
171 | node2.parent = node1
172 | return node1
173 | }
174 | else
175 | {
176 | node2.children.append(node1)
177 | node2.depth = max(node2.depth, node1.depth + 1)
178 | node1.parent = node2
179 | return node2
180 | }
181 | }
182 |
183 | public var min: Element!
184 | {
185 | return roots.first?.element
186 | }
187 |
188 | public var isEmpty: Bool
189 | {
190 | return roots.isEmpty
191 | }
192 |
193 | public func removeMin() -> Element?
194 | {
195 | guard roots.count > 0 else { return nil }
196 | let minNode = roots.first!
197 |
198 | roots.remove(at: 0)
199 | roots.append(contentsOf: minNode.children)
200 |
201 | cleanup()
202 |
203 | count -= 1
204 |
205 | return minNode.element
206 | }
207 | }
208 |
209 | fileprivate class BinomialHeapNode
210 | {
211 | var element: Element
212 | var priority: Priority
213 |
214 | weak var parent: BinomialHeapNode?
215 | var children:[BinomialHeapNode]
216 |
217 | var depth: Int
218 |
219 | init(priority: Priority, element: Element)
220 | {
221 | self.element = element
222 | self.priority = priority
223 | self.children = []
224 | depth = 0
225 | }
226 |
227 | fileprivate final func siftUp()
228 | {
229 | while let parent = self.parent, parent.priority > self.priority
230 | {
231 | let children = self.children
232 | self.children = parent.children
233 |
234 | self.children.remove(at: self.children.index(where: {$0 === self})!)
235 | self.children.append(parent)
236 |
237 | parent.children = children
238 |
239 | self.parent = parent.parent
240 | parent.parent = self
241 |
242 | let depth = parent.depth
243 | parent.depth = self.depth
244 | self.depth = depth
245 | }
246 | }
247 | }
248 |
249 | extension BinomialHeap: CustomStringConvertible
250 | {
251 | public var description: String
252 | {
253 | let description = "Binomial Heap (\(count) nodes)\n"
254 | let subnodeDescriptions = roots.map{$0.description}.joined(separator: "\n").replacingOccurrences(of: "\n", with: "\n\t")
255 | return "\(description)\n\t\(subnodeDescriptions)"
256 | }
257 | }
258 |
259 | extension BinomialHeapNode: CustomStringConvertible
260 | {
261 | fileprivate var description: String
262 | {
263 | let description = "- \(priority): \(element)"
264 | let subnodeDescriptions = children.map{$0.description}.joined(separator: "\n").replacingOccurrences(of: "\n", with: "\n\t")
265 | return "\(description)\n\t\(subnodeDescriptions)"
266 | }
267 | }
268 |
269 |
--------------------------------------------------------------------------------
/Path Tracing Demo/RenderResultViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 29.07.16.
6 | // Copyright © 2016 - 2018 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Cocoa
27 |
28 | class RenderResultViewController: NSViewController, PathTracerDelegate
29 | {
30 | @IBOutlet weak var imageView: NSImageView!
31 |
32 | override func viewDidLoad()
33 | {
34 | super.viewDidLoad()
35 | NotificationCenter.default.addObserver(self, selector: #selector(pathTracerUpdated(notification:)), name: Notification.Name(rawValue: "RenderResultViewUpdatePathTracer"), object: nil)
36 | }
37 |
38 | func pathTracingDidFinish(render: CGImage)
39 | {
40 | imageView.image = NSImage(cgImage: render, size: NSSize(width: render.width, height: render.height))
41 | self.view.window?.title = "Path Tracing Demo"
42 | }
43 |
44 | func pathTracingDidUpdate(render: CGImage, progress: Float)
45 | {
46 | imageView.image = NSImage(cgImage: render, size: NSSize(width: render.width, height: render.height))
47 | self.view.window?.title = "Path Tracing Demo - \(Int(progress * 100))% completed."
48 | }
49 |
50 | @objc func pathTracerUpdated(notification: NSNotification?)
51 | {
52 | ApplicationDelegate.pathTracer?.delegate = self
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Path Tracing Demo/ShaderChooserViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShaderChooserViewController.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 09.08.16.
6 | // Copyright © 2016 - 2018 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Cocoa
27 |
28 | private let DefaultShaderViewControllerIdentifier = "DefaultMaterialEditorViewController"
29 | private let DiffuseShaderViewControllerIdentifier = "DiffuseMaterialEditorViewController"
30 | private let EmissionShaderViewControllerIdentifier = "EmissionMaterialEditorViewController"
31 | private let ReflectionShaderViewControllerIdentifier = "ReflectionMaterialEditorViewController"
32 | private let RefractionShaderViewControllerIdentifier = "RefractionMaterialEditorViewController"
33 | private let AddShaderViewControllerIdentifier = "AddMaterialEditorViewController"
34 | private let MixShaderViewControllerIdentifier = "MixMaterialEditorViewController"
35 |
36 | private let shaderVCNames = [DefaultShaderViewControllerIdentifier, DiffuseShaderViewControllerIdentifier, EmissionShaderViewControllerIdentifier, ReflectionShaderViewControllerIdentifier, RefractionShaderViewControllerIdentifier, MixShaderViewControllerIdentifier, AddShaderViewControllerIdentifier]
37 |
38 | protocol ShaderChooserDelegate: class
39 | {
40 | func shaderChooserDidChangeShader(chooser: ShaderChooserViewController)
41 | }
42 |
43 | class ShaderChooserViewController: NSViewController
44 | {
45 | var shader: Shader = defaultShader
46 | {
47 | didSet
48 | {
49 | selectShader()
50 | loadShaderUI()
51 | }
52 | }
53 |
54 | @IBOutlet weak var shaderChooser: NSPopUpButton!
55 | @IBOutlet weak var shaderEditorView: NSView!
56 | weak var delegate: ShaderChooserDelegate?
57 |
58 | override func viewDidLoad()
59 | {
60 | super.viewDidLoad()
61 | didChooserShader(self)
62 | }
63 |
64 | @IBAction func didChooserShader(_ sender: AnyObject)
65 | {
66 | switch shaderChooser.indexOfSelectedItem
67 | {
68 | case 0:
69 | shader = DefaultShader(color: .white())
70 | break
71 | case 1:
72 | shader = DiffuseShader(color: .white())
73 | break
74 | case 2:
75 | shader = EmissionShader(strength: 1.0, color: .white())
76 | break
77 | case 3:
78 | shader = ReflectionShader(color: .white(), roughness: 0.0)
79 | break
80 | case 4:
81 | shader = RefractionShader(color: .white(), indexOfRefraction: 1.45, roughness: 0.0)
82 | break
83 | case 5:
84 | shader = MixShader(with: DefaultShader(color: .white()), and: DefaultShader(color: .white()), balance: 0.5)
85 | break
86 | case 6:
87 | shader = AddShader(with: DefaultShader(color: .white()), and: DefaultShader(color: .white()))
88 | break
89 | default:
90 | break
91 | }
92 | loadShaderUI()
93 | }
94 |
95 | private func selectShader()
96 | {
97 | if shader is DefaultShader
98 | {
99 | shaderChooser.selectItem(at: 0)
100 | }
101 | else if shader is DiffuseShader
102 | {
103 | shaderChooser.selectItem(at: 1)
104 | }
105 | else if shader is EmissionShader
106 | {
107 | shaderChooser.selectItem(at: 2)
108 | }
109 | else if shader is ReflectionShader
110 | {
111 | shaderChooser.selectItem(at: 3)
112 | }
113 | else if shader is RefractionShader
114 | {
115 | shaderChooser.selectItem(at: 4)
116 | }
117 | else if shader is MixShader
118 | {
119 | shaderChooser.selectItem(at: 5)
120 | }
121 | else if shader is AddShader
122 | {
123 | shaderChooser.selectItem(at: 6)
124 | }
125 | }
126 |
127 | private func loadShaderUI()
128 | {
129 | shaderEditorView.subviews.forEach{$0.removeFromSuperview()}
130 | childViewControllers.forEach{$0.removeFromParentViewController()}
131 | let identifier = shaderVCNames[shaderChooser.indexOfSelectedItem]
132 |
133 | guard let shaderEditorViewController = self.storyboard?.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(identifier)) as? ShaderEditorViewController
134 | else
135 | {
136 | fatalError("Shader ViewController cannot be instantiated or does not have matching type.")
137 | }
138 |
139 | shaderEditorViewController.view.frame = shaderEditorView.bounds
140 | shaderEditorView.addSubview(shaderEditorViewController.view)
141 |
142 | shaderEditorView.addConstraint(NSLayoutConstraint(item: shaderEditorView, attribute: .top, relatedBy: .equal, toItem: shaderEditorViewController.view, attribute: .top, multiplier: 1.0, constant: 0.0))
143 | shaderEditorView.addConstraint(NSLayoutConstraint(item: shaderEditorView, attribute: .bottom, relatedBy: .equal, toItem: shaderEditorViewController.view, attribute: .bottom, multiplier: 1.0, constant: 0.0))
144 | shaderEditorView.addConstraint(NSLayoutConstraint(item: shaderEditorView, attribute: .left, relatedBy: .equal, toItem: shaderEditorViewController.view, attribute: .left, multiplier: 1.0, constant: 0.0))
145 | shaderEditorView.addConstraint(NSLayoutConstraint(item: shaderEditorView, attribute: .right, relatedBy: .equal, toItem: shaderEditorViewController.view, attribute: .right, multiplier: 1.0, constant: 0.0))
146 |
147 | addChildViewController(shaderEditorViewController)
148 |
149 | shaderEditorViewController.shader = shader
150 |
151 | delegate?.shaderChooserDidChangeShader(chooser: self)
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/Path Tracing Demo/ShaderEditorViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShaderEditorViewController.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 09.08.16.
6 | // Copyright © 2016 - 2018 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Cocoa
27 |
28 | class ShaderEditorViewController: NSViewController
29 | {
30 | var shader: Shader = defaultShader
31 | {
32 | didSet
33 | {
34 | self.childViewControllers.flatMap{$0 as? ColorTextureChooserViewController}.forEach
35 | {
36 | $0.color = shader.color
37 | $0.texture = shader.texture
38 | }
39 | }
40 | }
41 | }
42 |
43 | class DefaultShaderEditorViewController: ShaderEditorViewController, ColorTextureChooserDelegate
44 | {
45 |
46 | required init?(coder: NSCoder)
47 | {
48 | super.init(coder: coder)
49 | }
50 |
51 | override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?)
52 | {
53 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
54 | }
55 |
56 | override func viewDidAppear()
57 | {
58 | self.childViewControllers.flatMap{$0 as? ColorTextureChooserViewController}.forEach
59 | {
60 | $0.delegate = self
61 | $0.color = shader.color
62 | $0.texture = shader.texture
63 | }
64 | }
65 |
66 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange color: Color)
67 | {
68 | (shader as? DefaultShader)?.color = color
69 | }
70 |
71 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange texture: Texture?)
72 | {
73 | (shader as? DefaultShader)?.texture = texture
74 | }
75 | }
76 |
77 |
78 | class DiffuseShaderEditorViewController: ShaderEditorViewController, ColorTextureChooserDelegate
79 | {
80 | required init?(coder: NSCoder)
81 | {
82 | super.init(coder: coder)
83 | }
84 |
85 | override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?)
86 | {
87 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
88 | }
89 |
90 | override func viewDidAppear()
91 | {
92 | self.childViewControllers.flatMap{$0 as? ColorTextureChooserViewController}.forEach
93 | {
94 | $0.delegate = self
95 | $0.color = shader.color
96 | $0.texture = shader.texture
97 | }
98 | }
99 |
100 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange color: Color)
101 | {
102 | (shader as? DiffuseShader)?.color = color
103 | }
104 |
105 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange texture: Texture?)
106 | {
107 | (shader as? DiffuseShader)?.texture = texture
108 | }
109 | }
110 |
111 | class EmissionShaderEditorViewController: ShaderEditorViewController, ColorTextureChooserDelegate, NSTextFieldDelegate
112 | {
113 | @IBOutlet weak var txtEmissionStrength: NSTextField!
114 |
115 | override var shader: Shader
116 | {
117 | didSet
118 | {
119 | txtEmissionStrength.floatValue = (shader as? EmissionShader)?.strength ?? 1.0
120 | }
121 | }
122 |
123 | required init?(coder: NSCoder)
124 | {
125 | super.init(coder: coder)
126 | }
127 |
128 | override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?)
129 | {
130 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
131 | }
132 |
133 | override func viewDidLoad()
134 | {
135 | super.viewDidLoad()
136 | txtEmissionStrength.delegate = self
137 | }
138 |
139 | override func viewDidAppear()
140 | {
141 | self.childViewControllers.flatMap{$0 as? ColorTextureChooserViewController}.forEach
142 | {
143 | $0.delegate = self
144 | $0.color = shader.color
145 | $0.texture = shader.texture
146 | }
147 | }
148 |
149 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange color: Color)
150 | {
151 | (shader as? EmissionShader)?.color = color
152 | }
153 |
154 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange texture: Texture?)
155 | {
156 | (shader as? EmissionShader)?.texture = texture
157 | }
158 |
159 | override func controlTextDidChange(_ obj: Notification)
160 | {
161 | (shader as? EmissionShader)?.strength = txtEmissionStrength.floatValue
162 | }
163 | }
164 |
165 | class ReflectionShaderEditorViewController: ShaderEditorViewController, ColorTextureChooserDelegate, NSTextFieldDelegate
166 | {
167 | @IBOutlet weak var txtRoughness: NSTextField!
168 |
169 | override var shader: Shader
170 | {
171 | didSet
172 | {
173 | txtRoughness.floatValue = (shader as? ReflectionShader)?.roughness ?? 0.0
174 | }
175 | }
176 |
177 | required init?(coder: NSCoder)
178 | {
179 | super.init(coder: coder)
180 | }
181 |
182 | override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?)
183 | {
184 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
185 | }
186 |
187 | override func viewDidLoad()
188 | {
189 | super.viewDidLoad()
190 | txtRoughness.delegate = self
191 | txtRoughness.floatValue = (shader as? RefractionShader)?.roughness ?? 0.0
192 | }
193 |
194 | override func viewDidAppear()
195 | {
196 | self.childViewControllers.flatMap{$0 as? ColorTextureChooserViewController}.forEach
197 | {
198 | $0.delegate = self
199 | $0.color = shader.color
200 | $0.texture = shader.texture
201 | }
202 | }
203 |
204 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange color: Color)
205 | {
206 | (shader as? ReflectionShader)?.color = color
207 | }
208 |
209 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange texture: Texture?)
210 | {
211 | (shader as? ReflectionShader)?.texture = texture
212 | }
213 |
214 | override func controlTextDidChange(_ obj: Notification)
215 | {
216 | (shader as? ReflectionShader)?.roughness = txtRoughness.floatValue
217 | }
218 | }
219 |
220 | class RefractionShaderEditorViewController: ShaderEditorViewController, ColorTextureChooserDelegate, NSTextFieldDelegate
221 | {
222 | @IBOutlet weak var txtIndexOfRefraction: NSTextField!
223 | @IBOutlet weak var txtRoughness: NSTextField!
224 | @IBOutlet weak var cwAttenuationColor: NSColorWell!
225 | @IBOutlet weak var txtAttenuationStrength: NSTextField!
226 |
227 | override var shader: Shader
228 | {
229 | didSet
230 | {
231 | txtIndexOfRefraction.floatValue = (shader as? RefractionShader)?.indexOfRefraction ?? 1.0
232 | txtRoughness.floatValue = (shader as? RefractionShader)?.roughness ?? 0.0
233 | txtAttenuationStrength.floatValue = (shader as? RefractionShader)?.absorptionStrength ?? 0.0
234 |
235 | if let attenuationColor = (shader as? RefractionShader)?.volumeColor
236 | {
237 | cwAttenuationColor.color = NSColor(calibratedRed: CGFloat(attenuationColor.red),
238 | green: CGFloat(attenuationColor.green),
239 | blue: CGFloat(attenuationColor.blue),
240 | alpha: CGFloat(attenuationColor.alpha))
241 | }
242 | }
243 | }
244 |
245 | required init?(coder: NSCoder)
246 | {
247 | super.init(coder: coder)
248 | }
249 |
250 | override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?)
251 | {
252 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
253 | }
254 |
255 | override func viewDidLoad()
256 | {
257 | super.viewDidLoad()
258 | txtIndexOfRefraction.delegate = self
259 | txtRoughness.delegate = self
260 | txtAttenuationStrength.delegate = self
261 | txtIndexOfRefraction.floatValue = (shader as? RefractionShader)?.indexOfRefraction ?? 1.0
262 | txtRoughness.floatValue = (shader as? RefractionShader)?.roughness ?? 0.0
263 | }
264 |
265 | override func viewDidAppear()
266 | {
267 | self.childViewControllers.flatMap{$0 as? ColorTextureChooserViewController}.forEach
268 | {
269 | $0.delegate = self
270 | $0.color = shader.color
271 | $0.texture = shader.texture
272 | }
273 | }
274 |
275 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange color: Color)
276 | {
277 | (shader as? RefractionShader)?.color = color
278 | }
279 |
280 | func colorTextureChooser(chooser: ColorTextureChooserViewController, didChange texture: Texture?)
281 | {
282 | (shader as? RefractionShader)?.texture = texture
283 | }
284 |
285 | override func controlTextDidChange(_ obj: Notification)
286 | {
287 | (shader as? RefractionShader)?.indexOfRefraction = txtIndexOfRefraction.floatValue
288 | (shader as? RefractionShader)?.roughness = txtRoughness.floatValue
289 | (shader as? RefractionShader)?.absorptionStrength = txtAttenuationStrength.floatValue
290 | }
291 |
292 | @IBAction func didChangeAttenuationColor(_ sender: AnyObject)
293 | {
294 | let color = cwAttenuationColor.color
295 |
296 | let r = Float(color.redComponent)
297 | let g = Float(color.greenComponent)
298 | let b = Float(color.blueComponent)
299 | let a = Float(color.alphaComponent)
300 |
301 | guard let currentAttenuationColor = (shader as? RefractionShader)?.volumeColor else { return }
302 | guard currentAttenuationColor.red != r ||
303 | currentAttenuationColor.green != g ||
304 | currentAttenuationColor.blue != b ||
305 | currentAttenuationColor.alpha != a
306 | else
307 | {
308 | return
309 | }
310 | (shader as? RefractionShader)?.volumeColor = Color(withRed: r, green: g, blue: b, alpha: a)
311 | }
312 | }
313 |
314 | class SubsurfaceScatteringShaderViewController: ShaderEditorViewController, NSTextFieldDelegate
315 | {
316 |
317 | }
318 |
319 | class AddShaderEditorViewController: ShaderEditorViewController, ShaderChooserDelegate
320 | {
321 | override func viewDidAppear()
322 | {
323 | self.childViewControllers.flatMap{$0 as? ShaderChooserViewController}.enumerated().forEach
324 | {
325 | $0.element.delegate = self
326 | if $0.offset == 0
327 | {
328 | $0.element.shader = (self.shader as? AddShader)?.shader1 ?? $0.element.shader
329 | }
330 | else
331 | {
332 | $0.element.shader = (self.shader as? AddShader)?.shader2 ?? $0.element.shader
333 | }
334 | }
335 |
336 | }
337 |
338 | func shaderChooserDidChangeShader(chooser: ShaderChooserViewController)
339 | {
340 | guard let index = self.childViewControllers.index(of: chooser) else { return }
341 | guard let addShader = (shader as? AddShader) else { return }
342 | if index == 0
343 | {
344 | addShader.shader1 = chooser.shader
345 | }
346 | else
347 | {
348 | addShader.shader2 = chooser.shader
349 | }
350 | }
351 | }
352 |
353 | class MixShaderEditorViewController: ShaderEditorViewController, ShaderChooserDelegate, NSTextFieldDelegate
354 | {
355 | @IBOutlet weak var txtBalance: NSTextField!
356 |
357 | override func viewDidAppear()
358 | {
359 | txtBalance.floatValue = (shader as? MixShader)?.balance ?? 0.0
360 | txtBalance.delegate = self
361 | self.childViewControllers.flatMap{$0 as? ShaderChooserViewController}.enumerated().forEach
362 | {
363 | $0.element.delegate = self
364 | if $0.offset == 0
365 | {
366 | $0.element.shader = (self.shader as? MixShader)?.shader1 ?? $0.element.shader
367 | }
368 | else
369 | {
370 | $0.element.shader = (self.shader as? MixShader)?.shader2 ?? $0.element.shader
371 | }
372 | }
373 |
374 | }
375 |
376 | func shaderChooserDidChangeShader(chooser: ShaderChooserViewController)
377 | {
378 | guard let index = self.childViewControllers.index(of: chooser) else { return }
379 | guard let mixShader = (shader as? MixShader) else { return }
380 | if index == 0
381 | {
382 | mixShader.shader1 = chooser.shader
383 | }
384 | else
385 | {
386 | mixShader.shader2 = chooser.shader
387 | }
388 | }
389 |
390 | override func controlTextDidChange(_ obj: Notification)
391 | {
392 | (shader as? MixShader)?.balance = txtBalance.floatValue
393 | }
394 | }
395 |
396 |
397 |
--------------------------------------------------------------------------------
/Path Tracing Demo/ShaderEncoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneEncoding.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 12.08.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | protocol TextureEncoder: NSCoding
29 | {
30 |
31 | }
32 |
33 | protocol TextureDecoder
34 | {
35 | var decoded: Texture { get }
36 | }
37 |
38 | protocol TextureEncoding
39 | {
40 | var encoded: TextureEncoder { get }
41 | }
42 |
43 | class ImageTextureEncoder: NSObject, TextureEncoder, TextureDecoder
44 | {
45 | private var texture: ImageTexture
46 |
47 | init(with texture: ImageTexture)
48 | {
49 | self.texture = texture
50 | }
51 |
52 | required init?(coder aDecoder: NSCoder)
53 | {
54 | let width = aDecoder.decodeInteger(forKey: "width")
55 | let height = aDecoder.decodeInteger(forKey: "height")
56 | var length: Int = 0
57 | guard let pixelData = aDecoder.decodeBytes(forKey: "pixel_data", returnedLength: &length) else { fatalError("pixel data could not be decoded") }
58 | guard width * height * 4 == length else { return nil }
59 |
60 | let pixelDataArray = (0.. Color
142 | {
143 | let red = coder.decodeFloat(forKey: "\(name)_red")
144 | let green = coder.decodeFloat(forKey: "\(name)_green")
145 | let blue = coder.decodeFloat(forKey: "\(name)_blue")
146 | let alpha = coder.decodeFloat(forKey: "\(name)_alpha")
147 |
148 | return Color(withRed: red, green: green, blue: blue, alpha: alpha)
149 | }
150 | }
151 |
152 | protocol ShaderEncoding
153 | {
154 | var encoded: ShaderEncoder { get }
155 | }
156 |
157 | protocol ShaderDecoder
158 | {
159 | var decoded: Shader { get }
160 | }
161 |
162 | class DefaultShaderEncoder: NSObject, ShaderDecoder, ShaderEncoder
163 | {
164 | private let shader: DefaultShader
165 |
166 | init(with shader: DefaultShader)
167 | {
168 | self.shader = shader
169 | }
170 |
171 | required init?(coder aDecoder: NSCoder)
172 | {
173 | let color = type(of: self).decodeColor(named: "shader_color", with: aDecoder)
174 | let texture = aDecoder.decodeObject(forKey: "texture") as? TextureDecoder
175 | shader = DefaultShader(color: color, texture: texture?.decoded)
176 | }
177 |
178 | func encode(with aCoder: NSCoder)
179 | {
180 | type(of: self).encode(color: shader.color, named: "shader_color", with: aCoder)
181 | aCoder.encode((shader.texture as? TextureEncoding)?.encoded, forKey: "texture")
182 | }
183 |
184 | var decoded: Shader
185 | {
186 | return shader
187 | }
188 | }
189 |
190 | extension DefaultShader: ShaderEncoding
191 | {
192 | var encoded: ShaderEncoder
193 | {
194 | return DefaultShaderEncoder(with: self)
195 | }
196 | }
197 |
198 | class DiffuseShaderEncoder: NSObject, ShaderDecoder, ShaderEncoder
199 | {
200 | private let shader: DiffuseShader
201 |
202 | init(with shader: DiffuseShader)
203 | {
204 | self.shader = shader
205 | }
206 |
207 | required init?(coder aDecoder: NSCoder)
208 | {
209 | let color = type(of: self).decodeColor(named: "shader_color", with: aDecoder)
210 | let texture = aDecoder.decodeObject(forKey: "texture") as? TextureDecoder
211 | shader = DiffuseShader(color: color, texture: texture?.decoded)
212 | }
213 |
214 | func encode(with aCoder: NSCoder)
215 | {
216 | type(of: self).encode(color: shader.color, named: "shader_color", with: aCoder)
217 | aCoder.encode((shader.texture as? TextureEncoding)?.encoded, forKey: "texture")
218 | }
219 |
220 | var decoded: Shader
221 | {
222 | return shader
223 | }
224 | }
225 |
226 | extension DiffuseShader: ShaderEncoding
227 | {
228 | var encoded: ShaderEncoder
229 | {
230 | return DiffuseShaderEncoder(with: self)
231 | }
232 | }
233 |
234 | class EmissionShaderEncoder: NSObject, ShaderDecoder, ShaderEncoder
235 | {
236 | private let shader: EmissionShader
237 |
238 | init(with shader: EmissionShader)
239 | {
240 | self.shader = shader
241 | }
242 |
243 | required init?(coder aDecoder: NSCoder)
244 | {
245 | let color = type(of: self).decodeColor(named: "shader_color", with: aDecoder)
246 | let texture = aDecoder.decodeObject(forKey: "texture") as? TextureDecoder
247 | let strength = aDecoder.decodeFloat(forKey: "strength")
248 | shader = EmissionShader(strength: strength, color: color, texture: texture?.decoded)
249 | }
250 |
251 | func encode(with aCoder: NSCoder)
252 | {
253 | type(of: self).encode(color: shader.color, named: "shader_color", with: aCoder)
254 | aCoder.encode((shader.texture as? TextureEncoding)?.encoded, forKey: "texture")
255 | aCoder.encode(shader.strength, forKey: "strength")
256 | }
257 |
258 | var decoded: Shader
259 | {
260 | return shader
261 | }
262 | }
263 |
264 | extension EmissionShader: ShaderEncoding
265 | {
266 | var encoded: ShaderEncoder
267 | {
268 | return EmissionShaderEncoder(with: self)
269 | }
270 | }
271 |
272 | class ReflectionShaderEncoder: NSObject, ShaderDecoder, ShaderEncoder
273 | {
274 | private let shader: ReflectionShader
275 |
276 | init(with shader: ReflectionShader)
277 | {
278 | self.shader = shader
279 | }
280 |
281 | required init?(coder aDecoder: NSCoder)
282 | {
283 | let color = type(of: self).decodeColor(named: "shader_color", with: aDecoder)
284 | let texture = aDecoder.decodeObject(forKey: "texture") as? TextureDecoder
285 | let roughness = aDecoder.decodeFloat(forKey: "roughness")
286 | shader = ReflectionShader(color: color, roughness: roughness, texture: texture?.decoded)
287 | }
288 |
289 | func encode(with aCoder: NSCoder)
290 | {
291 | type(of: self).encode(color: shader.color, named: "shader_color", with: aCoder)
292 | aCoder.encode((shader.texture as? TextureEncoding)?.encoded, forKey: "texture")
293 | aCoder.encode(shader.roughness, forKey: "roughness")
294 | }
295 |
296 | var decoded: Shader
297 | {
298 | return shader
299 | }
300 | }
301 |
302 | extension ReflectionShader: ShaderEncoding
303 | {
304 | var encoded: ShaderEncoder
305 | {
306 | return ReflectionShaderEncoder(with: self)
307 | }
308 | }
309 |
310 | class RefractionShaderEncoder: NSObject, ShaderDecoder, ShaderEncoder
311 | {
312 | private let shader: RefractionShader
313 |
314 | init(with shader: RefractionShader)
315 | {
316 | self.shader = shader
317 | }
318 |
319 | required init?(coder aDecoder: NSCoder)
320 | {
321 | let color = type(of: self).decodeColor(named: "shader_color", with: aDecoder)
322 | let texture = aDecoder.decodeObject(forKey: "texture") as? TextureDecoder
323 | let roughness = aDecoder.decodeFloat(forKey: "roughness")
324 | let ior = aDecoder.decodeFloat(forKey: "ior")
325 |
326 | let volumeColor: Color
327 |
328 | if aDecoder.containsValue(forKey: "volume_color_red")
329 | {
330 | volumeColor = type(of: self).decodeColor(named: "volume_color", with: aDecoder)
331 | }
332 | else
333 | {
334 | volumeColor = .white()
335 | }
336 |
337 | let absorptionStrength: Float
338 |
339 | if aDecoder.containsValue(forKey: "absorption_strength")
340 | {
341 | absorptionStrength = aDecoder.decodeFloat(forKey: "absorption_strength")
342 | }
343 | else
344 | {
345 | absorptionStrength = 0
346 | }
347 |
348 | shader = RefractionShader(color: color, texture: texture?.decoded, indexOfRefraction: ior, roughness: roughness, volumeColor: volumeColor, absorptionStrength: absorptionStrength)
349 | }
350 |
351 | func encode(with aCoder: NSCoder)
352 | {
353 | type(of: self).encode(color: shader.color, named: "shader_color", with: aCoder)
354 | aCoder.encode((shader.texture as? TextureEncoding)?.encoded, forKey: "texture")
355 | aCoder.encode(shader.roughness, forKey: "roughness")
356 | aCoder.encode(shader.indexOfRefraction, forKey: "ior")
357 | type(of: self).encode(color: shader.volumeColor, named: "volume_color", with: aCoder)
358 | aCoder.encode(shader.absorptionStrength, forKey: "absorption_strength")
359 | }
360 |
361 | var decoded: Shader
362 | {
363 | return shader
364 | }
365 | }
366 |
367 | extension RefractionShader: ShaderEncoding
368 | {
369 | var encoded: ShaderEncoder
370 | {
371 | return RefractionShaderEncoder(with: self)
372 | }
373 | }
374 |
375 | class MixShaderEncoder: NSObject, ShaderDecoder, ShaderEncoder
376 | {
377 | private let shader: MixShader
378 |
379 | init(with shader: MixShader)
380 | {
381 | self.shader = shader
382 | }
383 |
384 | required init?(coder aDecoder: NSCoder)
385 | {
386 | let balance = aDecoder.decodeFloat(forKey: "balance")
387 | let shader1 = aDecoder.decodeObject(forKey: "shader_1") as? ShaderDecoder
388 | let shader2 = aDecoder.decodeObject(forKey: "shader_2") as? ShaderDecoder
389 |
390 | shader = MixShader(with: shader1?.decoded ?? DefaultShader(color: .white()), and: shader2?.decoded ?? DefaultShader(color: .white()), balance: balance)
391 | }
392 |
393 | func encode(with aCoder: NSCoder)
394 | {
395 | aCoder.encode(shader.balance, forKey: "balance")
396 | aCoder.encode((shader.shader1 as? ShaderEncoding)?.encoded, forKey: "shader_1")
397 | aCoder.encode((shader.shader2 as? ShaderEncoding)?.encoded, forKey: "shader_2")
398 | }
399 |
400 | var decoded: Shader
401 | {
402 | return shader
403 | }
404 | }
405 |
406 | extension MixShader: ShaderEncoding
407 | {
408 | var encoded: ShaderEncoder
409 | {
410 | return MixShaderEncoder(with: self)
411 | }
412 | }
413 |
414 | class AddShaderEncoder: NSObject, ShaderDecoder, ShaderEncoder
415 | {
416 | private let shader: AddShader
417 |
418 | init(with shader: AddShader)
419 | {
420 | self.shader = shader
421 | }
422 |
423 | required init?(coder aDecoder: NSCoder)
424 | {
425 | guard
426 | let shader1 = aDecoder.decodeObject(forKey: "shader_1") as? ShaderDecoder,
427 | let shader2 = aDecoder.decodeObject(forKey: "shader_2") as? ShaderDecoder
428 | else
429 | {
430 | fatalError("cannot decode shader")
431 | }
432 | shader = AddShader(with: shader1.decoded, and: shader2.decoded)
433 | }
434 |
435 | func encode(with aCoder: NSCoder)
436 | {
437 | aCoder.encode((shader.shader1 as? ShaderEncoding)?.encoded, forKey: "shader_1")
438 | aCoder.encode((shader.shader2 as? ShaderEncoding)?.encoded, forKey: "shader_2")
439 | }
440 |
441 | var decoded: Shader
442 | {
443 | return shader
444 | }
445 | }
446 |
447 | extension AddShader: ShaderEncoding
448 | {
449 | var encoded: ShaderEncoder
450 | {
451 | return AddShaderEncoder(with: self)
452 | }
453 | }
454 |
--------------------------------------------------------------------------------
/Path Tracing Demo/Shading.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Shading.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 01.08.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | @inline(__always)
29 | private func random_normal() -> Float
30 | {
31 | let u1 = Float(drand48())
32 | let u2 = Float(drand48())
33 | let f1 = sqrtf(-2 * logf(u1))
34 | let f2 = 2 * Float.pi * u2
35 | return f1 * cosf(f2)
36 | }
37 |
38 | struct Color
39 | {
40 | var red: Float
41 | var green: Float
42 | var blue: Float
43 | var alpha: Float
44 |
45 | var red8: UInt8
46 | {
47 | get
48 | {
49 | return UInt8(min(max(red, 0.0), 1.0) * 255)
50 | }
51 |
52 | set (new)
53 | {
54 | self.red = Float(new) / 255.0
55 | }
56 | }
57 |
58 | var green8: UInt8
59 | {
60 | get
61 | {
62 | return UInt8(min(max(green, 0.0), 1.0) * 255)
63 | }
64 |
65 | set (new)
66 | {
67 | self.green = Float(new) / 255.0
68 | }
69 | }
70 |
71 | var blue8: UInt8
72 | {
73 | get
74 | {
75 | return UInt8(min(max(blue, 0.0), 1.0) * 255)
76 | }
77 |
78 | set (new)
79 | {
80 | self.blue = Float(new) / 255.0
81 | }
82 | }
83 |
84 | var alpha8: UInt8
85 | {
86 | get
87 | {
88 | return UInt8(min(max(alpha, 0.0), 1.0) * 255)
89 | }
90 |
91 | set (new)
92 | {
93 | self.alpha = Float(new) / 255
94 | }
95 | }
96 |
97 | var red16: UInt16
98 | {
99 | get
100 | {
101 | return UInt16(min(max(red, 0.0), 1.0) * 65535)
102 | }
103 |
104 | set (new)
105 | {
106 | self.red = Float(new) / 65535
107 | }
108 | }
109 |
110 | var green16: UInt16
111 | {
112 | get
113 | {
114 | return UInt16(min(max(green, 0.0), 1.0) * 65535)
115 | }
116 |
117 | set (new)
118 | {
119 | self.green = Float(new) / 65535
120 | }
121 | }
122 |
123 | var blue16: UInt16
124 | {
125 | get
126 | {
127 | return UInt16(min(max(blue, 0.0), 1.0) * 65535)
128 | }
129 |
130 | set (new)
131 | {
132 | self.blue = Float(new) / 65535
133 | }
134 | }
135 |
136 | var alpha16: UInt16
137 | {
138 | get
139 | {
140 | return UInt16(min(max(alpha, 0.0), 1.0) * 65535)
141 | }
142 |
143 | set (new)
144 | {
145 | self.alpha = Float(new) / 65535
146 | }
147 | }
148 |
149 | var premultipliedAlpha: Color
150 | {
151 | return Color(withRed: self.red / self.alpha, green: self.green / self.alpha, blue: self.blue / self.alpha, alpha: self.alpha)
152 | }
153 |
154 | func toLogColor(width: Float = 100, gain: Float = 1.0 / 7.0, offset: Float = 0) -> Color {
155 | func toLog(_ value: Float) -> Float {
156 | return log(value * width + 1) * gain + offset
157 | }
158 | return Color(withRed: toLog(red), green: toLog(green), blue: toLog(blue), alpha: alpha)
159 | }
160 |
161 | func toLinearColor(width: Float = 100, gain: Float = 1.0 / 7.0, offset: Float = 0) -> Color {
162 | func toLinear(_ value: Float) -> Float {
163 | return (exp((value - offset) / gain) - 1) / width
164 | }
165 | return Color(withRed: toLinear(red), green: toLinear(green), blue: toLinear(blue), alpha: alpha)
166 | }
167 |
168 | var brightness: Float
169 | {
170 | return sqrt(red * red + green * green + blue * blue) * 0.577350269
171 | }
172 |
173 | // var clamped: Color
174 | // {
175 | // return Color(withRed: max(min(red, 1.0), 0.0), green: max(min(green, 1.0), 0.0), blue: max(min(blue, 1.0), 0.0), alpha: max(min(alpha, 1.0), 0.0))
176 | // }
177 |
178 | init(withRed red: Float, green: Float, blue: Float, alpha: Float)
179 | {
180 | self.red = red
181 | self.green = green
182 | self.blue = blue
183 | self.alpha = alpha
184 | }
185 |
186 | init(withRed red: UInt8, green: UInt8, blue: UInt8, alpha: UInt8)
187 | {
188 | self.red = Float(red) / 255.0
189 | self.green = Float(green) / 255.0
190 | self.blue = Float(blue) / 255.0
191 | self.alpha = Float(alpha) / 255.0
192 | }
193 |
194 | init(withRed red: UInt16, green: UInt16, blue: UInt16, alpha: UInt16)
195 | {
196 | self.red = Float(red) / 65535.0
197 | self.green = Float(green) / 65535.0
198 | self.blue = Float(blue) / 65535.0
199 | self.alpha = Float(alpha) / 65535.0
200 | }
201 |
202 | static func white() -> Color
203 | {
204 | return Color(withRed: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
205 | }
206 |
207 | static func black() -> Color
208 | {
209 | return Color(withRed: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
210 | }
211 |
212 | static func clear() -> Color
213 | {
214 | return Color(withRed: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
215 | }
216 | }
217 |
218 | extension Color: CustomStringConvertible
219 | {
220 | var description: String
221 | {
222 | return "Color (r: \(red8), g: \(green8), b: \(blue8), a: \(alpha8))"
223 | }
224 | }
225 |
226 |
227 | func * (left: Color, right: Color) -> Color
228 | {
229 | return Color(withRed: left.red * right.red,
230 | green: left.green * right.green,
231 | blue: left.blue * right.blue,
232 | alpha: left.alpha * right.alpha)
233 | }
234 |
235 | func * (left: Color, right: Float) -> Color
236 | {
237 | return Color(withRed: left.red * right,
238 | green: left.green * right,
239 | blue: left.blue * right,
240 | alpha: left.alpha * right)
241 | }
242 |
243 | func * (left: Float, right: Color) -> Color
244 | {
245 | return Color(withRed: left * right.red,
246 | green: left * right.green,
247 | blue: left * right.blue,
248 | alpha: left * right.alpha)
249 | }
250 |
251 | func + (left: Color, right: Color) -> Color
252 | {
253 | return Color(withRed: left.red + right.red,
254 | green: left.green + right.green,
255 | blue: left.blue + right.blue,
256 | alpha: left.alpha + right.alpha)
257 | }
258 |
259 | class Material
260 | {
261 | var shader: Shader
262 | var name: String
263 |
264 | init(withShader shader: Shader, named name: String)
265 | {
266 | self.shader = shader
267 | self.name = name
268 | }
269 | }
270 |
271 | extension Material: CustomStringConvertible
272 | {
273 | var description: String
274 | {
275 | return "Material \(name): \(shader)"
276 | }
277 | }
278 |
279 | protocol Shader: class
280 | {
281 | var color: Color { get }
282 | var texture: Texture? { get }
283 |
284 | func color(forTriangle triangle: Triangle3D,
285 | at barycentricIntersectionCoordinates: BarycentricPoint,
286 | point: Point3D,
287 | rayDirection: Vector3D,
288 | sceneGeometry: TriangleStore,
289 | environmentShader: EnvironmentShader,
290 | previousColor: Color,
291 | maximumRayDepth: Int) -> Color
292 | }
293 |
294 | extension Shader
295 | {
296 | func textureColor(forTriangle triangle: Triangle3D, withIntersectionCoordinates intersectionCoordinates: BarycentricPoint, atAngle angle: Float = Float.pi) -> Color
297 | {
298 | let color:Color
299 | if let texture = self.texture
300 | {
301 | let textureCoordinate = triangle.a.textureCoordinate * intersectionCoordinates.alpha +
302 | triangle.b.textureCoordinate * intersectionCoordinates.beta +
303 | triangle.c.textureCoordinate * intersectionCoordinates.gamma
304 |
305 | color = texture.color(for: textureCoordinate, atAngle: angle)
306 | }
307 | else
308 | {
309 | color = self.color
310 | }
311 | return color
312 | }
313 | }
314 |
315 |
316 | class DefaultShader: Shader
317 | {
318 | var color: Color
319 | var texture: Texture?
320 |
321 | init(color: Color, texture: Texture? = nil)
322 | {
323 | self.color = color
324 | self.texture = texture
325 | }
326 |
327 | func color(forTriangle triangle: Triangle3D,
328 | at barycentricIntersectionCoordinates: BarycentricPoint,
329 | point: Point3D,
330 | rayDirection: Vector3D,
331 | sceneGeometry: TriangleStore,
332 | environmentShader: EnvironmentShader,
333 | previousColor: Color,
334 | maximumRayDepth: Int) -> Color
335 | {
336 | let normal = (triangle.a.normal * barycentricIntersectionCoordinates.alpha
337 | + triangle.b.normal * barycentricIntersectionCoordinates.beta
338 | + triangle.c.normal * barycentricIntersectionCoordinates.gamma).normalized
339 | let ray = rayDirection
340 | let brightness = 0.66667 * abs(normal * ray) + 0.33333
341 | let color = textureColor(forTriangle: triangle, withIntersectionCoordinates: barycentricIntersectionCoordinates)
342 | return color * brightness
343 | }
344 | }
345 |
346 | extension DefaultShader: CustomStringConvertible
347 | {
348 | var description: String
349 | {
350 | return "DefaultShader (color: \(color), texture: \(texture))"
351 | }
352 | }
353 |
354 |
355 | class DiffuseShader: Shader
356 | {
357 |
358 | var color: Color
359 | var texture: Texture?
360 |
361 | init(color: Color, texture: Texture? = nil)
362 | {
363 | self.color = color
364 | self.texture = texture
365 | }
366 |
367 | func color(forTriangle triangle: Triangle3D,
368 | at barycentricIntersectionCoordinates: BarycentricPoint,
369 | point: Point3D, rayDirection: Vector3D,
370 | sceneGeometry: TriangleStore,
371 | environmentShader: EnvironmentShader,
372 | previousColor: Color,
373 | maximumRayDepth: Int) -> Color
374 | {
375 | let color = self.textureColor(forTriangle: triangle, withIntersectionCoordinates: barycentricIntersectionCoordinates)
376 | guard (color * previousColor).brightness > 0.001 else { return .black() }
377 |
378 | let normal = triangle.a.normal * barycentricIntersectionCoordinates.alpha
379 | + triangle.b.normal * barycentricIntersectionCoordinates.beta
380 | + triangle.c.normal * barycentricIntersectionCoordinates.gamma
381 | var outgoingRayDirection = Vector3D(x: Float(drand48() * 2.0 - 1.0),
382 | y: Float(drand48() * 2.0 - 1.0),
383 | z: Float(drand48() * 2.0 - 1.0)).normalized
384 | if outgoingRayDirection * normal < 0
385 | {
386 | outgoingRayDirection = -outgoingRayDirection
387 | }
388 |
389 | let base = point + normal * 0.001
390 | let ray = Ray3D(base: base, direction: outgoingRayDirection)
391 |
392 | if let nextIntersection = sceneGeometry.nearestIntersectingTriangle(forRay: ray)
393 | {
394 | if maximumRayDepth > 0
395 | {
396 | let nextColor = nextIntersection.triangle.material.shader.color(
397 | forTriangle: nextIntersection.triangle,
398 | at: nextIntersection.barycentricIntersection,
399 | point: ray.point(for: nextIntersection.ray),
400 | rayDirection: outgoingRayDirection,
401 | sceneGeometry: sceneGeometry,
402 | environmentShader: environmentShader,
403 | previousColor: previousColor * color,
404 | maximumRayDepth: maximumRayDepth - 1)
405 | return nextColor * color
406 | }
407 | else
408 | {
409 | return Color(withRed: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
410 | }
411 | }
412 | else
413 | {
414 | return environmentShader.environmentColor(for: outgoingRayDirection) * color
415 | }
416 | }
417 | }
418 |
419 | extension DiffuseShader: CustomStringConvertible
420 | {
421 | var description: String
422 | {
423 | return "DiffuseShader (color: \(color), texture: \(texture))"
424 | }
425 | }
426 |
427 | class EmissionShader: Shader
428 | {
429 | var strength: Float
430 | var color: Color
431 | var texture: Texture?
432 | init(strength: Float, color: Color, texture: Texture? = nil)
433 | {
434 | self.strength = strength
435 | self.color = color
436 | self.texture = texture
437 | }
438 |
439 | func color(forTriangle triangle: Triangle3D,
440 | at barycentricIntersectionCoordinates: BarycentricPoint,
441 | point: Point3D,
442 | rayDirection: Vector3D,
443 | sceneGeometry: TriangleStore,
444 | environmentShader: EnvironmentShader,
445 | previousColor: Color,
446 | maximumRayDepth: Int) -> Color
447 | {
448 | let color = textureColor(forTriangle: triangle, withIntersectionCoordinates: barycentricIntersectionCoordinates)
449 | return color * strength
450 | }
451 | }
452 |
453 | extension EmissionShader: CustomStringConvertible
454 | {
455 | var description: String
456 | {
457 | return "EmissionShader (color: \(color), texture: \(texture), strength: \(strength))"
458 | }
459 | }
460 |
461 |
462 | class ReflectionShader: Shader
463 | {
464 | var color: Color
465 | var texture: Texture?
466 | var roughness: Float
467 |
468 | init(color: Color, roughness: Float, texture: Texture? = nil)
469 | {
470 | self.color = color
471 | self.roughness = roughness
472 | self.texture = texture
473 | }
474 |
475 | func color(forTriangle triangle: Triangle3D,
476 | at barycentricIntersectionCoordinates: BarycentricPoint,
477 | point: Point3D,
478 | rayDirection: Vector3D,
479 | sceneGeometry: TriangleStore,
480 | environmentShader: EnvironmentShader,
481 | previousColor: Color,
482 | maximumRayDepth: Int) -> Color
483 | {
484 | let color = self.textureColor(forTriangle: triangle, withIntersectionCoordinates: barycentricIntersectionCoordinates)
485 | guard (color * previousColor).brightness > 0.001 else { return .black() }
486 |
487 | var normal = (triangle.a.normal * barycentricIntersectionCoordinates.alpha
488 | + triangle.b.normal * barycentricIntersectionCoordinates.beta
489 | + triangle.c.normal * barycentricIntersectionCoordinates.gamma).normalized
490 |
491 | let toCamera = -rayDirection
492 |
493 | if rayDirection * normal < 0
494 | {
495 | normal = -normal
496 | }
497 |
498 | var outgoingRayDirection = (toCamera - 2 * (toCamera - (normal * toCamera) * normal)).normalized
499 | if roughness != 0.0
500 | {
501 | outgoingRayDirection = outgoingRayDirection.rotated(alpha: roughness * random_normal(), beta: roughness * random_normal(), gamma: roughness * random_normal())
502 |
503 | if (outgoingRayDirection * normal) * (rayDirection * normal) > 0
504 | {
505 | outgoingRayDirection = -outgoingRayDirection
506 | }
507 | }
508 |
509 | let base = point + outgoingRayDirection * 0.001
510 | let ray = Ray3D(base: base, direction: outgoingRayDirection)
511 |
512 | if let nextIntersection = sceneGeometry.nearestIntersectingTriangle(forRay: ray)
513 | {
514 | if maximumRayDepth > 0
515 | {
516 | let nextColor = nextIntersection.triangle.material.shader.color(
517 | forTriangle: nextIntersection.triangle,
518 | at: nextIntersection.barycentricIntersection,
519 | point: ray.point(for: nextIntersection.ray),
520 | rayDirection: outgoingRayDirection,
521 | sceneGeometry: sceneGeometry,
522 | environmentShader: environmentShader,
523 | previousColor: previousColor * color,
524 | maximumRayDepth: maximumRayDepth - 1)
525 | return nextColor * color
526 | }
527 | else
528 | {
529 | return Color(withRed: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
530 | }
531 | }
532 | else
533 | {
534 | return environmentShader.environmentColor(for: outgoingRayDirection) * color
535 | }
536 | }
537 | }
538 |
539 | extension ReflectionShader: CustomStringConvertible
540 | {
541 | var description: String
542 | {
543 | return "ReflectionShader (color: \(color), texture: \(texture), roughness: \(roughness))"
544 | }
545 | }
546 |
547 |
548 | class RefractionShader: Shader
549 | {
550 | var color: Color
551 | var texture: Texture?
552 |
553 | var volumeColor: Color
554 | var absorptionStrength: Float
555 |
556 | var indexOfRefraction: Float
557 | var roughness: Float
558 |
559 | init(color: Color, texture: Texture? = nil, indexOfRefraction: Float, roughness: Float, volumeColor: Color = .white(), absorptionStrength: Float = 0.0)
560 | {
561 | self.color = color
562 | self.texture = texture
563 | self.indexOfRefraction = indexOfRefraction
564 | self.roughness = roughness
565 | self.volumeColor = volumeColor
566 | self.absorptionStrength = absorptionStrength
567 | }
568 |
569 | func color(forTriangle triangle: Triangle3D,
570 | at barycentricIntersectionCoordinates: BarycentricPoint,
571 | point: Point3D,
572 | rayDirection: Vector3D,
573 | sceneGeometry: TriangleStore,
574 | environmentShader: EnvironmentShader,
575 | previousColor: Color,
576 | maximumRayDepth: Int) -> Color
577 | {
578 | let color = self.textureColor(forTriangle: triangle, withIntersectionCoordinates: barycentricIntersectionCoordinates)
579 | guard (color * previousColor).brightness > 0.001 else { return .black() }
580 |
581 | var normal = (triangle.a.normal * barycentricIntersectionCoordinates.alpha
582 | + triangle.b.normal * barycentricIntersectionCoordinates.beta
583 | + triangle.c.normal * barycentricIntersectionCoordinates.gamma).normalized
584 |
585 | let ior: Float
586 | let incoming: Bool
587 |
588 | if normal * rayDirection <= 0 //into the object
589 | {
590 | ior = self.indexOfRefraction
591 | incoming = true
592 | }
593 | else //out of the object
594 | {
595 | ior = 1 / self.indexOfRefraction
596 | normal = -normal
597 | incoming = false
598 | }
599 |
600 | let iorInv = 1.0 / ior
601 |
602 | let cosIncident = abs(normal * rayDirection)
603 | let sinIncidentSquared = 1 - cosIncident * cosIncident
604 |
605 | var transmissionRayDirection = ((iorInv * rayDirection) +
606 | (iorInv * cosIncident - sqrt(1.0 - iorInv * iorInv * sinIncidentSquared)) * normal)
607 | .normalized
608 |
609 | let cosOutgoing = abs(normal * transmissionRayDirection)
610 |
611 | let reflectanceCrossPolarizedSqrt = (cosIncident - ior * cosOutgoing) / (cosIncident + ior * cosOutgoing)
612 | let reflectanceCrossPolarized = reflectanceCrossPolarizedSqrt * reflectanceCrossPolarizedSqrt
613 |
614 | let reflectanceParallelPolarizedSqrt = (ior * cosIncident - cosOutgoing) / (ior * cosIncident + cosOutgoing)
615 | let reflectanceParallelPolarized = reflectanceParallelPolarizedSqrt * reflectanceParallelPolarizedSqrt
616 |
617 | var reflectance = (reflectanceCrossPolarized + reflectanceParallelPolarized) * 0.5
618 | reflectance = reflectance.isNaN ? 1.0 : reflectance
619 |
620 | let transmittance = 1.0 - reflectance
621 |
622 | let transmittedColor: Color
623 |
624 | if transmittance > 0.001
625 | {
626 | if roughness != 0.0
627 | {
628 | transmissionRayDirection = transmissionRayDirection.rotated(alpha: roughness * random_normal(), beta: roughness * random_normal(), gamma: roughness * random_normal())
629 |
630 | if (transmissionRayDirection * normal) * (rayDirection * normal) < 0
631 | {
632 | transmissionRayDirection = -transmissionRayDirection
633 | }
634 | }
635 |
636 | let base = point + transmissionRayDirection * 0.001
637 | let ray = Ray3D(base: base, direction: transmissionRayDirection)
638 |
639 | transmittedColor = getTransmittedColor(ray: ray, sceneGeometry: sceneGeometry, color: color, maximumRayDepth: maximumRayDepth, previousColor: color * previousColor * transmittance, environmentShader: environmentShader, incoming: incoming)
640 | }
641 | else
642 | {
643 | transmittedColor = .black()
644 | }
645 |
646 | let reflectedColor: Color
647 |
648 | if reflectance > 0.001
649 | {
650 | var reflectionRayDirection = (-rayDirection - 2 * (-rayDirection - (normal * -rayDirection) * normal)).normalized
651 |
652 | if roughness != 0.0
653 | {
654 | reflectionRayDirection = reflectionRayDirection.rotated(alpha: roughness * random_normal(), beta: roughness * random_normal(), gamma: roughness * random_normal())
655 |
656 | if (reflectionRayDirection * normal) * (rayDirection * normal) > 0
657 | {
658 | reflectionRayDirection = -reflectionRayDirection
659 | }
660 | }
661 |
662 | let reflectedBase = point + reflectionRayDirection * 0.001
663 | let reflectedRay = Ray3D(base: reflectedBase, direction: reflectionRayDirection)
664 |
665 | reflectedColor = getReflectedColor(ray: reflectedRay, sceneGeometry: sceneGeometry, color: color, maximumRayDepth: maximumRayDepth, previousColor: color * previousColor * reflectance, environmentShader: environmentShader, incoming: incoming)
666 | }
667 | else
668 | {
669 | reflectedColor = .black()
670 | }
671 |
672 | return transmittedColor * transmittance + reflectedColor * reflectance
673 | }
674 |
675 | @inline(__always)
676 | private func getTransmittedColor(ray: Ray3D, sceneGeometry: TriangleStore, color: Color, maximumRayDepth: Int, previousColor: Color, environmentShader: EnvironmentShader, incoming: Bool) -> Color
677 | {
678 | if let nextIntersection = sceneGeometry.nearestIntersectingTriangle(forRay: ray)
679 | {
680 | if maximumRayDepth > 0
681 | {
682 | let nextColor = nextIntersection.triangle.material.shader.color(
683 | forTriangle: nextIntersection.triangle,
684 | at: nextIntersection.barycentricIntersection,
685 | point: ray.point(for: nextIntersection.ray),
686 | rayDirection: ray.direction,
687 | sceneGeometry: sceneGeometry,
688 | environmentShader: environmentShader,
689 | previousColor: previousColor,
690 | maximumRayDepth: maximumRayDepth - 1)
691 |
692 | let volume: Color
693 |
694 | if incoming && absorptionStrength > 0
695 | {
696 | let volumeFactor = min(absorptionStrength * nextIntersection.ray, 1.0)
697 | volume = (Color.white() * (1.0 - volumeFactor)) + (volumeColor * volumeFactor)
698 | }
699 | else
700 | {
701 | volume = .white()
702 | }
703 |
704 | return nextColor * color * volume
705 | }
706 | else
707 | {
708 | return Color(withRed: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
709 | }
710 | }
711 | else
712 | {
713 | return environmentShader.environmentColor(for: ray.direction) * color
714 | }
715 | }
716 |
717 | @inline(__always)
718 | private func getReflectedColor(ray: Ray3D, sceneGeometry: TriangleStore, color: Color, maximumRayDepth: Int, previousColor: Color, environmentShader: EnvironmentShader, incoming: Bool) -> Color
719 | {
720 | if let nextIntersection = sceneGeometry.nearestIntersectingTriangle(forRay: ray)
721 | {
722 | if maximumRayDepth > 0
723 | {
724 | let nextColor = nextIntersection.triangle.material.shader.color(
725 | forTriangle: nextIntersection.triangle,
726 | at: nextIntersection.barycentricIntersection,
727 | point: ray.point(for: nextIntersection.ray),
728 | rayDirection: ray.direction,
729 | sceneGeometry: sceneGeometry,
730 | environmentShader: environmentShader,
731 | previousColor: previousColor,
732 | maximumRayDepth: maximumRayDepth - 1)
733 |
734 | let volume: Color
735 |
736 | if !incoming && absorptionStrength > 0
737 | {
738 | let volumeFactor = min(absorptionStrength * nextIntersection.ray, 1.0)
739 | volume = (Color.white() * (1.0 - volumeFactor)) + (volumeColor * volumeFactor)
740 | }
741 | else
742 | {
743 | volume = .white()
744 | }
745 |
746 | return nextColor * color * volume
747 | }
748 | else
749 | {
750 | return Color(withRed: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
751 | }
752 | }
753 | else
754 | {
755 | return environmentShader.environmentColor(for: ray.direction) * color
756 | }
757 | }
758 | }
759 |
760 | extension RefractionShader: CustomStringConvertible
761 | {
762 | var description: String
763 | {
764 | return "RefractionShader (color: \(color), texture: \(texture), index of refraction: \(indexOfRefraction), roughness: \(roughness), depth attenuation color: \(volumeColor), depth attenuation strength: \(absorptionStrength))"
765 | }
766 | }
767 |
768 | class SubsurfaceScatteringShader: Shader
769 | {
770 | var color: Color
771 | let texture: Texture?
772 | var strength: Float
773 |
774 |
775 | init(color: Color, strength: Float)
776 | {
777 | self.color = color
778 | self.texture = nil
779 | self.strength = strength
780 | }
781 |
782 | func color(forTriangle triangle: Triangle3D,
783 | at barycentricIntersectionCoordinates: BarycentricPoint,
784 | point: Point3D,
785 | rayDirection: Vector3D,
786 | sceneGeometry: TriangleStore,
787 | environmentShader: EnvironmentShader,
788 | previousColor: Color,
789 | maximumRayDepth: Int) -> Color
790 | {
791 |
792 | let color = self.textureColor(forTriangle: triangle, withIntersectionCoordinates: barycentricIntersectionCoordinates)
793 | guard (color * previousColor).brightness > 0.001 else { return .black() }
794 |
795 | let normal = triangle.a.normal * barycentricIntersectionCoordinates.alpha
796 | + triangle.b.normal * barycentricIntersectionCoordinates.beta
797 | + triangle.c.normal * barycentricIntersectionCoordinates.gamma
798 |
799 | var outgoingRayDirection = Vector3D(x: Float(drand48() * 2.0 - 1.0),
800 | y: Float(drand48() * 2.0 - 1.0),
801 | z: Float(drand48() * 2.0 - 1.0)).normalized
802 | if outgoingRayDirection * normal > 0
803 | {
804 | outgoingRayDirection = -outgoingRayDirection
805 | }
806 |
807 | var currentColor:Color = .white()
808 |
809 | for scatterCount in 1 ... maximumRayDepth
810 | {
811 | let base = point + outgoingRayDirection * 0.001
812 | let ray = Ray3D(base: base, direction: outgoingRayDirection)
813 | let nextScatterDistance = Float(drand48()) / strength
814 | if let nextIntersection = sceneGeometry.nearestIntersectingTriangle(forRay: ray), nextIntersection.ray < nextScatterDistance
815 | {
816 | let nextColor = nextIntersection.triangle.material.shader.color(
817 | forTriangle: nextIntersection.triangle,
818 | at: nextIntersection.barycentricIntersection,
819 | point: ray.point(for: nextIntersection.ray),
820 | rayDirection: ray.direction,
821 | sceneGeometry: sceneGeometry,
822 | environmentShader: environmentShader,
823 | previousColor: previousColor,
824 | maximumRayDepth: maximumRayDepth - scatterCount)
825 |
826 | return currentColor * (((1.0 - nextIntersection.ray) * .white()) + (nextIntersection.ray * color)) * nextColor
827 | }
828 |
829 | currentColor = currentColor * (((1.0 - nextScatterDistance * strength) * .white()) + (nextScatterDistance * strength * color))
830 |
831 | outgoingRayDirection = Vector3D(x: Float(drand48() * 2.0 - 1.0),
832 | y: Float(drand48() * 2.0 - 1.0),
833 | z: Float(drand48() * 2.0 - 1.0)).normalized
834 | }
835 |
836 | return color
837 | }
838 | }
839 |
840 | class AddShader: Shader
841 | {
842 | var shader1: Shader
843 | var shader2: Shader
844 | let color: Color = Color(withRed: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
845 | let texture: Texture? = nil
846 |
847 | init(with shader1: Shader, and shader2: Shader)
848 | {
849 | self.shader1 = shader1
850 | self.shader2 = shader2
851 | }
852 |
853 | func color(forTriangle triangle: Triangle3D,
854 | at barycentricIntersectionCoordinates: BarycentricPoint,
855 | point: Point3D,
856 | rayDirection: Vector3D,
857 | sceneGeometry: TriangleStore,
858 | environmentShader: EnvironmentShader,
859 | previousColor: Color,
860 | maximumRayDepth: Int) -> Color
861 | {
862 | let shader1Result = shader1.color(forTriangle: triangle,
863 | at: barycentricIntersectionCoordinates,
864 | point: point,
865 | rayDirection: rayDirection,
866 | sceneGeometry: sceneGeometry,
867 | environmentShader: environmentShader,
868 | previousColor: previousColor,
869 | maximumRayDepth: maximumRayDepth)
870 |
871 | let shader2Result = shader2.color(forTriangle: triangle,
872 | at: barycentricIntersectionCoordinates,
873 | point: point,
874 | rayDirection: rayDirection,
875 | sceneGeometry: sceneGeometry,
876 | environmentShader: environmentShader,
877 | previousColor: previousColor,
878 | maximumRayDepth: maximumRayDepth)
879 |
880 | return shader1Result + shader2Result
881 | }
882 | }
883 |
884 | extension AddShader: CustomStringConvertible
885 | {
886 | var description: String
887 | {
888 | let shader1Description = "\(shader1)".replacingOccurrences(of: "\n", with: "\n\t")
889 | let shader2Description = "\(shader2)".replacingOccurrences(of: "\n", with: "\n\t")
890 | return "AddShader:\n(\n\t\(shader1Description)\n\t\(shader2Description)\n)"
891 | }
892 | }
893 |
894 |
895 | class MixShader: Shader
896 | {
897 | var shader1: Shader
898 | var shader2: Shader
899 | var balance: Float
900 | let color: Color = Color(withRed: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
901 | let texture: Texture? = nil
902 |
903 | init(with shader1: Shader, and shader2: Shader, balance: Float)
904 | {
905 | self.shader1 = shader1
906 | self.shader2 = shader2
907 | self.balance = balance
908 | }
909 |
910 | func color(forTriangle triangle: Triangle3D,
911 | at barycentricIntersectionCoordinates: BarycentricPoint,
912 | point: Point3D,
913 | rayDirection: Vector3D,
914 | sceneGeometry: TriangleStore,
915 | environmentShader: EnvironmentShader,
916 | previousColor: Color,
917 | maximumRayDepth: Int) -> Color
918 | {
919 | let shader1Result = shader1.color(forTriangle: triangle,
920 | at: barycentricIntersectionCoordinates,
921 | point: point,
922 | rayDirection: rayDirection,
923 | sceneGeometry: sceneGeometry,
924 | environmentShader: environmentShader,
925 | previousColor: previousColor,
926 | maximumRayDepth: maximumRayDepth)
927 |
928 | let shader2Result = shader2.color(forTriangle: triangle,
929 | at: barycentricIntersectionCoordinates,
930 | point: point,
931 | rayDirection: rayDirection,
932 | sceneGeometry: sceneGeometry,
933 | environmentShader: environmentShader,
934 | previousColor: previousColor,
935 | maximumRayDepth: maximumRayDepth)
936 |
937 | return shader1Result * balance + shader2Result * (1.0 - balance)
938 |
939 | }
940 | }
941 |
942 | extension MixShader: CustomStringConvertible
943 | {
944 | var description: String
945 | {
946 | let shader1Description = "\(shader1)".replacingOccurrences(of: "\n", with: "\n\t")
947 | let shader2Description = "\(shader2)".replacingOccurrences(of: "\n", with: "\n\t")
948 | return "MixShader (mix: \(balance)):\n(\n\t\(shader1Description)\n\t\(shader2Description)\n)"
949 | }
950 | }
951 |
952 | class EnvironmentShader
953 | {
954 | var color: Color
955 | var texture: Texture?
956 | var strength: Float
957 |
958 | init(color: Color, texture: Texture? = nil, strength: Float = 1.0)
959 | {
960 | self.color = color
961 | self.texture = texture
962 | self.strength = strength
963 | }
964 |
965 | func environmentColor(`for` rayDirection: Vector3D) -> Color
966 | {
967 | if let texture = self.texture
968 | {
969 | let longitude = atanf(rayDirection.y / rayDirection.x) + (rayDirection.x < 0 ? Float.pi : 0)
970 | let latitude = asinf(rayDirection.z)
971 | let u = longitude / Float.pi * 0.5
972 | let v = latitude / Float.pi + 0.5
973 | return texture.color(for: TextureCoordinate(u: u, v: v), atAngle: 1.5707963268) * strength
974 | }
975 | else
976 | {
977 | return color * strength
978 | }
979 | }
980 | }
981 |
--------------------------------------------------------------------------------
/Path Tracing Demo/SpacePartitioning.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SpacePartitioning.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 01.08.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | protocol TriangleStore: class
29 | {
30 | func nearestIntersectingTriangle(forRay ray: Ray3D) -> (triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)?
31 | }
32 |
33 | fileprivate struct Volume
34 | {
35 | let x: Float
36 | let y: Float
37 | let z: Float
38 | let width: Float
39 | let height: Float
40 | let depth: Float
41 |
42 | init(x: Float, y: Float, z: Float, width: Float, height: Float, depth: Float)
43 | {
44 | self.x = x
45 | self.y = y
46 | self.z = z
47 | self.width = width
48 | self.height = height
49 | self.depth = depth
50 | }
51 |
52 | init(containing points: [Point3D])
53 | {
54 | let minX = points.min{$0.x < $1.x}?.x ?? 0
55 | let minY = points.min{$0.y < $1.y}?.y ?? 0
56 | let minZ = points.min{$0.z < $1.z}?.z ?? 0
57 | let maxX = points.max{$0.x < $1.x}?.x ?? 1
58 | let maxY = points.max{$0.y < $1.y}?.y ?? 1
59 | let maxZ = points.max{$0.z < $1.z}?.z ?? 1
60 |
61 | let dx = maxX - minX
62 | let dy = maxY - minY
63 | let dz = maxZ - minZ
64 |
65 | x = minX
66 | y = minY
67 | z = minZ
68 | width = dx
69 | height = dy
70 | depth = dz
71 | }
72 |
73 | var mid:Point3D
74 | {
75 | return Point3D(x: x + width * 0.5, y: y + height * 0.5, z: z + depth * 0.5)
76 | }
77 |
78 | var innerSphereRadius:Float
79 | {
80 | return min(min(width, height), depth) * 0.5
81 | }
82 |
83 | var maxX: Float
84 | {
85 | return x + width
86 | }
87 |
88 | var maxY: Float
89 | {
90 | return y + height
91 | }
92 |
93 | var maxZ: Float
94 | {
95 | return z + depth
96 | }
97 |
98 | var outerSphereRadius: Float
99 | {
100 | let mid = self.mid
101 | let dx = x - mid.x
102 | let dy = y - mid.y
103 | let dz = z - mid.z
104 | return sqrt(dx * dx + dy * dy + dz * dz)
105 | }
106 |
107 | nonmutating func contains(point: Point3D) -> Bool
108 | {
109 | guard point.x >= x && point.x <= (x + width) else { return false }
110 | guard point.y >= y && point.y <= (y + height) else { return false }
111 | guard point.z >= z && point.z <= (z + depth) else { return false }
112 | return true
113 | }
114 |
115 | nonmutating func intersects(ray: Ray3D, strict: Bool = false) -> Bool
116 | {
117 | let baseBFL = Point3D(x: x, y: y, z: z)
118 | let baseTBR = baseBFL + Vector3D(x: width, y: height, z: depth)
119 |
120 | let tA = baseBFL - ray.base
121 | let tB = baseTBR - ray.base
122 |
123 | let tDivA = Vector3D(x: tA.x / ray.direction.x, y: tA.y / ray.direction.y, z: tA.z / ray.direction.z)
124 | let tDivB = Vector3D(x: tB.x / ray.direction.x, y: tB.y / ray.direction.y, z: tB.z / ray.direction.z)
125 |
126 | let tMin = max(max(min(tDivA.x, tDivB.x), min(tDivA.y, tDivB.y)), min(tDivA.z, tDivB.z))
127 | let tMax = min(min(max(tDivA.x, tDivB.x), max(tDivA.y, tDivB.y)), max(tDivA.z, tDivB.z))
128 |
129 | guard tMax >= 0 else { return false }
130 | guard tMax >= tMin else { return false }
131 | if strict
132 | {
133 | guard tMin <= 1 else { return false }
134 | }
135 | return true
136 | }
137 |
138 | nonmutating func contains(_ triangle: Triangle3D) -> Bool
139 | {
140 | if (triangle.points.map(self.contains).reduce(false){$0 || $1})
141 | {
142 | return true
143 | }
144 | else if intersects(ray: Ray3D(base: triangle.a.point, direction: triangle.b.point - triangle.a.point), strict: true)
145 | {
146 | return true
147 | }
148 | else if intersects(ray: Ray3D(base: triangle.a.point, direction: triangle.c.point - triangle.a.point), strict: true)
149 | {
150 | return true
151 | }
152 | else if intersects(ray: Ray3D(base: triangle.b.point, direction: triangle.c.point - triangle.b.point), strict: true)
153 | {
154 | return true
155 | }
156 | else
157 | {
158 | let edges = [(nearBottomLeft, nearBottomRight),
159 | (nearBottomLeft, nearTopLeft),
160 | (nearBottomRight, nearTopRight),
161 | (nearTopLeft, nearTopRight),
162 | (nearBottomLeft, farBottomLeft),
163 | (nearBottomRight, farBottomRight),
164 | (nearTopLeft, farTopLeft),
165 | (nearTopRight, farTopRight),
166 | (farBottomLeft, farBottomRight),
167 | (farBottomLeft, farTopLeft),
168 | (farBottomRight, farTopRight),
169 | (farTopLeft, farTopRight)]
170 |
171 | for edge in edges
172 | {
173 | let ray = Ray3D(base: edge.0, direction: edge.1 - edge.0)
174 |
175 | if let intersection = ray.findIntersection(with: triangle), intersection.rayParameter <= 1.0
176 | {
177 | return true
178 | }
179 | }
180 |
181 | return false
182 | }
183 | }
184 |
185 | private var nearBottomLeft: Point3D
186 | {
187 | return Point3D(x: x, y: y, z: z)
188 | }
189 |
190 | private var nearBottomRight: Point3D
191 | {
192 | return Point3D(x: x + width, y: y, z: z)
193 | }
194 |
195 | private var nearTopLeft: Point3D
196 | {
197 | return Point3D(x: x, y: y + height, z: z)
198 | }
199 |
200 | private var nearTopRight: Point3D
201 | {
202 | return Point3D(x: x + width, y: y + height, z: z)
203 | }
204 |
205 | private var farBottomLeft: Point3D
206 | {
207 | return Point3D(x: x, y: y, z: z + depth)
208 | }
209 |
210 | private var farBottomRight: Point3D
211 | {
212 | return Point3D(x: x + width, y: y, z: z + depth)
213 | }
214 |
215 | private var farTopLeft: Point3D
216 | {
217 | return Point3D(x: x, y: y + height, z: z + depth)
218 | }
219 |
220 | private var farTopRight: Point3D
221 | {
222 | return Point3D(x: x + width, y: y + height, z: z + depth)
223 | }
224 | }
225 |
226 | private protocol OctreeNodeType
227 | {
228 | var volume: Volume { get }
229 |
230 | nonmutating func findTriangle(intersecting ray: Ray3D, betterThan bestIntersectionRay: Float?) -> (triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)?
231 |
232 | nonmutating func intersects(ray: Ray3D, betterThan bestIntersection: Float?) -> Bool
233 | }
234 |
235 | extension OctreeNodeType
236 | {
237 | @inline(__always)
238 | fileprivate nonmutating func intersects(ray: Ray3D, betterThan bestIntersection: Float? = nil) -> Bool
239 | {
240 | let baseBFL = Point3D(x: volume.x, y: volume.y, z: volume.z)
241 | let baseTBR = baseBFL + Vector3D(x: volume.width, y: volume.height, z: volume.depth)
242 |
243 | let tA = baseBFL - ray.base
244 | let tB = baseTBR - ray.base
245 |
246 | let tDivA = Vector3D(x: tA.x / ray.direction.x, y: tA.y / ray.direction.y, z: tA.z / ray.direction.z)
247 | let tDivB = Vector3D(x: tB.x / ray.direction.x, y: tB.y / ray.direction.y, z: tB.z / ray.direction.z)
248 |
249 | let tMax = min(min(max(tDivA.x, tDivB.x), max(tDivA.y, tDivB.y)), max(tDivA.z, tDivB.z))
250 |
251 | guard tMax >= 0 else { return false }
252 |
253 | let tMin = max(max(min(tDivA.x, tDivB.x), min(tDivA.y, tDivB.y)), min(tDivA.z, tDivB.z))
254 |
255 | guard tMax >= tMin && tMin < (bestIntersection ?? Float.infinity) else { return false }
256 | return true
257 | }
258 |
259 | }
260 |
261 | fileprivate class OctreeEmptyLeaf : OctreeNodeType
262 | {
263 | fileprivate let volume: Volume = Volume(x: 0, y: 0, z: 0, width: 0, height: 0, depth: 0)
264 |
265 | fileprivate func findTriangle(intersecting ray: Ray3D, betterThan bestIntersectionRay: Float?) -> (triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)?
266 | {
267 | return nil
268 | }
269 |
270 | }
271 |
272 | fileprivate class OctreeLeaf : OctreeNodeType
273 | {
274 | fileprivate let volume: Volume
275 | fileprivate let triangles:ContiguousArray
276 |
277 | init(volume: Volume, triangles: ContiguousArray)
278 | {
279 | self.volume = volume
280 | self.triangles = triangles
281 | }
282 |
283 | fileprivate func findTriangle(intersecting ray: Ray3D, betterThan bestIntersectionRay: Float?) -> (triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)?
284 | {
285 | guard self.intersects(ray: ray, betterThan: bestIntersectionRay) else { return nil }
286 |
287 | var bestIntersection:(triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)? = nil
288 |
289 | for triangle in triangles
290 | {
291 | guard let intersection = ray.findIntersection(with: triangle) else { continue }
292 | guard intersection.rayParameter > 0 else { continue }
293 | if bestIntersection == nil || intersection.rayParameter < bestIntersection!.ray
294 | {
295 | bestIntersection = (triangle: triangle, ray: intersection.rayParameter, barycentricIntersection: intersection.barycentric)
296 | }
297 | }
298 |
299 | return bestIntersection
300 | }
301 | }
302 |
303 | fileprivate class OctreeNode : OctreeNodeType
304 | {
305 | fileprivate let volume: Volume
306 |
307 | fileprivate let lll: OctreeNodeType
308 | fileprivate let gll: OctreeNodeType
309 | fileprivate let lgl: OctreeNodeType
310 | fileprivate let ggl: OctreeNodeType
311 | fileprivate let llg: OctreeNodeType
312 | fileprivate let glg: OctreeNodeType
313 | fileprivate let lgg: OctreeNodeType
314 | fileprivate let ggg: OctreeNodeType
315 |
316 | fileprivate convenience init(with triangles: ContiguousArray)
317 | {
318 | self.init(with: triangles, maxBounds: Volume(containing: triangles.flatMap{[$0.a.point, $0.b.point, $0.c.point]}))
319 | }
320 |
321 | private init(with triangles: ContiguousArray, maxBounds: Volume)
322 | {
323 |
324 | let v = Volume(containing: triangles.flatMap{[$0.a.point, $0.b.point, $0.c.point]})
325 |
326 | self.volume = Volume(x: max(v.x, maxBounds.x),
327 | y: max(v.y, maxBounds.y),
328 | z: max(v.z, maxBounds.z),
329 | width: min(maxBounds.maxX, v.maxX) - max(v.x, maxBounds.x),
330 | height: min(maxBounds.maxY, v.maxY) - max(v.y, maxBounds.y),
331 | depth: min(maxBounds.maxZ, v.maxZ) - max(v.z, maxBounds.z))
332 |
333 | let minX = volume.x
334 | let minY = volume.y
335 | let minZ = volume.z
336 |
337 | let points = triangles.flatMap{[$0.a.point, $0.b.point, $0.c.point]}
338 |
339 | let mid = volume.mid
340 |
341 | let splitX = (points.map{$0.x}.kthSmallestElement(points.count / 2) * 0.6667 + mid.x * 0.3333)
342 | let splitY = (points.map{$0.y}.kthSmallestElement(points.count / 2) * 0.6667 + mid.y * 0.3333)
343 | let splitZ = (points.map{$0.z}.kthSmallestElement(points.count / 2) * 0.6667 + mid.z * 0.3333)
344 |
345 | // let splitX = minX + volume.width * 0.5
346 | // let splitY = minY + volume.height * 0.5
347 | // let splitZ = minZ + volume.depth * 0.5
348 |
349 | let lowerWidth = splitX - minX
350 | let lowerHeight = splitY - minY
351 | let lowerDepth = splitZ - minZ
352 |
353 | let upperWidth = volume.width - lowerWidth
354 | let upperHeight = volume.height - lowerHeight
355 | let upperDepth = volume.depth - lowerDepth
356 |
357 | let lllBounds = Volume(
358 | x: minX,
359 | y: minY,
360 | z: minZ,
361 | width: lowerWidth,
362 | height: lowerHeight,
363 | depth: lowerDepth)
364 | let llgBounds = Volume(
365 | x: minX,
366 | y: minY,
367 | z: splitZ,
368 | width: lowerWidth,
369 | height: lowerHeight,
370 | depth: upperDepth)
371 | let lglBounds = Volume(
372 | x: minX,
373 | y: splitY,
374 | z: minZ,
375 | width: lowerWidth,
376 | height: upperHeight,
377 | depth: lowerDepth)
378 | let lggBounds = Volume(
379 | x: minX,
380 | y: splitY,
381 | z: splitZ,
382 | width: lowerWidth,
383 | height: upperHeight,
384 | depth: upperDepth)
385 | let gllBounds = Volume(
386 | x: splitX,
387 | y: minY,
388 | z: minZ,
389 | width: upperWidth,
390 | height: lowerHeight,
391 | depth: lowerDepth)
392 | let glgBounds = Volume(
393 | x: splitX,
394 | y: minY,
395 | z: splitZ,
396 | width: upperWidth,
397 | height: lowerHeight,
398 | depth: upperDepth)
399 | let gglBounds = Volume(
400 | x: splitX,
401 | y: splitY,
402 | z: minZ,
403 | width: upperWidth,
404 | height: upperHeight,
405 | depth: lowerDepth)
406 | let gggBounds = Volume(
407 | x: splitX,
408 | y: splitY,
409 | z: splitZ,
410 | width: upperWidth,
411 | height: upperHeight,
412 | depth: upperDepth)
413 |
414 | var lllBucket:ContiguousArray = []
415 | var llgBucket:ContiguousArray = []
416 | var lglBucket:ContiguousArray = []
417 | var lggBucket:ContiguousArray = []
418 | var gllBucket:ContiguousArray = []
419 | var glgBucket:ContiguousArray = []
420 | var gglBucket:ContiguousArray = []
421 | var gggBucket:ContiguousArray = []
422 |
423 | for triangle in triangles
424 | {
425 | if lllBounds.contains(triangle)
426 | {
427 | lllBucket.append(triangle)
428 | }
429 | if llgBounds.contains(triangle)
430 | {
431 | llgBucket.append(triangle)
432 | }
433 | if lglBounds.contains(triangle)
434 | {
435 | lglBucket.append(triangle)
436 | }
437 | if lggBounds.contains(triangle)
438 | {
439 | lggBucket.append(triangle)
440 | }
441 | if gllBounds.contains(triangle)
442 | {
443 | gllBucket.append(triangle)
444 | }
445 | if glgBounds.contains(triangle)
446 | {
447 | glgBucket.append(triangle)
448 | }
449 | if gglBounds.contains(triangle)
450 | {
451 | gglBucket.append(triangle)
452 | }
453 | if gggBounds.contains(triangle)
454 | {
455 | gggBucket.append(triangle)
456 | }
457 | }
458 |
459 | let buckets = [lllBucket, llgBucket, lglBucket, lggBucket, gllBucket, glgBucket, gglBucket, gggBucket]
460 | let bounds = [lllBounds, llgBounds, lglBounds, lggBounds, gllBounds, glgBounds, gglBounds, gggBounds]
461 | var distinctPoints = Array(repeating: false, count: 8)
462 |
463 | for i in 0 ..< 8
464 | {
465 | var distinct = false
466 | let points = buckets[i].flatMap{[$0.a.point, $0.b.point, $0.c.point]}.filter{bounds[i].contains(point: $0)}
467 |
468 | let minX = points.min{$0.x < $1.x}?.x ?? 0
469 | let maxX = points.max{$0.x < $1.x}?.x ?? 0
470 | distinct = distinct || (minX != maxX)
471 |
472 | let minY = points.min{$0.y < $1.y}?.y ?? 0
473 | let maxY = points.max{$0.y < $1.y}?.y ?? 0
474 | distinct = distinct || (minY != maxY)
475 |
476 | let minZ = points.min{$0.z < $1.z}?.z ?? 0
477 | let maxZ = points.max{$0.z < $1.z}?.z ?? 0
478 | distinct = distinct || (minZ != maxZ)
479 |
480 | distinctPoints[i] = distinct
481 | }
482 |
483 | let innerNodeThreshold = 4
484 |
485 | if distinctPoints[0] && lllBucket.count > innerNodeThreshold && lllBucket.count < triangles.count
486 | {
487 | lll = OctreeNode(with: lllBucket, maxBounds: lllBounds)
488 | }
489 | else if !lllBucket.isEmpty
490 | {
491 | lll = OctreeLeaf(volume: lllBounds, triangles: lllBucket)
492 | }
493 | else
494 | {
495 | lll = OctreeEmptyLeaf()
496 | }
497 |
498 | if distinctPoints[1] && llgBucket.count > innerNodeThreshold && llgBucket.count < triangles.count
499 | {
500 | llg = OctreeNode(with: llgBucket, maxBounds: llgBounds)
501 | }
502 | else if !llgBucket.isEmpty
503 | {
504 | llg = OctreeLeaf(volume: llgBounds, triangles: llgBucket)
505 | }
506 | else
507 | {
508 | llg = OctreeEmptyLeaf()
509 | }
510 |
511 | if distinctPoints[2] && lglBucket.count > innerNodeThreshold && lglBucket.count < triangles.count
512 | {
513 | lgl = OctreeNode(with: lglBucket, maxBounds: lglBounds)
514 | }
515 | else if !lglBucket.isEmpty
516 | {
517 | lgl = OctreeLeaf(volume: lglBounds, triangles: lglBucket)
518 | }
519 | else
520 | {
521 | lgl = OctreeEmptyLeaf()
522 | }
523 |
524 | if distinctPoints[3] && lggBucket.count > innerNodeThreshold && lggBucket.count < triangles.count
525 | {
526 | lgg = OctreeNode(with: lggBucket, maxBounds: lggBounds)
527 | }
528 | else if !lggBucket.isEmpty
529 | {
530 | lgg = OctreeLeaf(volume: lggBounds, triangles: lggBucket)
531 | }
532 | else
533 | {
534 | lgg = OctreeEmptyLeaf()
535 | }
536 |
537 | if distinctPoints[4] && gllBucket.count > innerNodeThreshold && gllBucket.count < triangles.count
538 | {
539 | gll = OctreeNode(with: gllBucket, maxBounds: gllBounds)
540 | }
541 | else if !gllBucket.isEmpty
542 | {
543 | gll = OctreeLeaf(volume: gllBounds, triangles: gllBucket)
544 | }
545 | else
546 | {
547 | gll = OctreeEmptyLeaf()
548 | }
549 |
550 | if distinctPoints[5] && glgBucket.count > innerNodeThreshold && glgBucket.count < triangles.count
551 | {
552 | glg = OctreeNode(with: glgBucket, maxBounds: glgBounds)
553 | }
554 | else if !glgBucket.isEmpty
555 | {
556 | glg = OctreeLeaf(volume: glgBounds, triangles: glgBucket)
557 | }
558 | else
559 | {
560 | glg = OctreeEmptyLeaf()
561 | }
562 |
563 | if distinctPoints[6] && gglBucket.count > innerNodeThreshold && gglBucket.count < triangles.count
564 | {
565 | ggl = OctreeNode(with: gglBucket, maxBounds: gglBounds)
566 | }
567 | else if !gglBucket.isEmpty
568 | {
569 | ggl = OctreeLeaf(volume: gglBounds, triangles: gglBucket)
570 | }
571 | else
572 | {
573 | ggl = OctreeEmptyLeaf()
574 | }
575 |
576 | if distinctPoints[7] && gggBucket.count > innerNodeThreshold && gggBucket.count < triangles.count
577 | {
578 | ggg = OctreeNode(with: gggBucket, maxBounds: gggBounds)
579 | }
580 | else if !gggBucket.isEmpty
581 | {
582 | ggg = OctreeLeaf(volume: gggBounds, triangles: gggBucket)
583 | }
584 | else
585 | {
586 | ggg = OctreeEmptyLeaf()
587 | }
588 | }
589 |
590 | fileprivate func findTriangle(intersecting ray: Ray3D, betterThan bestIntersectionRay: Float? = nil) -> (triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)?
591 | {
592 | guard intersects(ray: ray, betterThan: bestIntersectionRay) else { return nil }
593 |
594 | var bestIntersection:(triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)? = nil
595 |
596 | if let subnodeIntersection = lll.findTriangle(intersecting: ray, betterThan: bestIntersection?.ray)
597 | {
598 | if bestIntersection == nil || bestIntersection!.ray > subnodeIntersection.ray
599 | {
600 | bestIntersection = subnodeIntersection
601 | }
602 | }
603 | if let subnodeIntersection = llg.findTriangle(intersecting: ray, betterThan: bestIntersection?.ray)
604 | {
605 | if bestIntersection == nil || bestIntersection!.ray > subnodeIntersection.ray
606 | {
607 | bestIntersection = subnodeIntersection
608 | }
609 | }
610 | if let subnodeIntersection = lgl.findTriangle(intersecting: ray, betterThan: bestIntersection?.ray)
611 | {
612 | if bestIntersection == nil || bestIntersection!.ray > subnodeIntersection.ray
613 | {
614 | bestIntersection = subnodeIntersection
615 | }
616 | }
617 | if let subnodeIntersection = lgg.findTriangle(intersecting: ray, betterThan: bestIntersection?.ray)
618 | {
619 | if bestIntersection == nil || bestIntersection!.ray > subnodeIntersection.ray
620 | {
621 | bestIntersection = subnodeIntersection
622 | }
623 | }
624 | if let subnodeIntersection = gll.findTriangle(intersecting: ray, betterThan: bestIntersection?.ray)
625 | {
626 | if bestIntersection == nil || bestIntersection!.ray > subnodeIntersection.ray
627 | {
628 | bestIntersection = subnodeIntersection
629 | }
630 | }
631 | if let subnodeIntersection = glg.findTriangle(intersecting: ray, betterThan: bestIntersection?.ray)
632 | {
633 | if bestIntersection == nil || bestIntersection!.ray > subnodeIntersection.ray
634 | {
635 | bestIntersection = subnodeIntersection
636 | }
637 | }
638 | if let subnodeIntersection = ggl.findTriangle(intersecting: ray, betterThan: bestIntersection?.ray)
639 | {
640 | if bestIntersection == nil || bestIntersection!.ray > subnodeIntersection.ray
641 | {
642 | bestIntersection = subnodeIntersection
643 | }
644 | }
645 | if let subnodeIntersection = ggg.findTriangle(intersecting: ray, betterThan: bestIntersection?.ray)
646 | {
647 | if bestIntersection == nil || bestIntersection!.ray > subnodeIntersection.ray
648 | {
649 | bestIntersection = subnodeIntersection
650 | }
651 | }
652 |
653 | return bestIntersection
654 | }
655 | }
656 |
657 | extension OctreeEmptyLeaf: CustomStringConvertible
658 | {
659 | fileprivate var description: String
660 | {
661 | return "none"
662 | }
663 | }
664 |
665 | extension OctreeLeaf: CustomStringConvertible
666 | {
667 | fileprivate var description: String
668 | {
669 | return "Leaf \(volume):\n\t\(triangles.map{$0.description}.joined(separator: "\n\t"))"
670 | }
671 | }
672 |
673 | extension OctreeNode: CustomStringConvertible
674 | {
675 | fileprivate var description: String
676 | {
677 | var descriptions = [String](repeating: "", count: 8)
678 |
679 | for (index, (node, name)) in [(lll, "lll"), (llg, "llg"), (lgl, "lgl"), (lgg, "lgg"), (gll, "gll"), (glg, "glg"), (ggl, "ggl"), (ggg, "ggg")].enumerated()
680 | {
681 | var description:String
682 |
683 | description = "\(node)"
684 |
685 | descriptions[index] = "\t\(name):\n\t\t\(description.replacingOccurrences(of: "\n", with: "\n\t"))"
686 | }
687 |
688 | let combinedDescriptions = descriptions.joined(separator: "\n")
689 |
690 | return "Inner Node (\(volume)):\n\(combinedDescriptions)"
691 | }
692 | }
693 |
694 | class OctreeTriangleStore : TriangleStore, CustomStringConvertible
695 | {
696 | private let root: OctreeNode
697 |
698 | init(with triangles: [Triangle3D])
699 | {
700 | root = OctreeNode(with: ContiguousArray(triangles))
701 | }
702 |
703 | func nearestIntersectingTriangle(forRay ray: Ray3D) -> (triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)?
704 | {
705 | return root.findTriangle(intersecting: ray)
706 | }
707 |
708 | var description: String
709 | {
710 | return "Octree:\n\(root)"
711 | }
712 | }
713 |
714 |
715 | //Currently unused (slower than octree. Bug?)
716 | //class BSPTriangleStore: TriangleStore, CustomStringConvertible
717 | //{
718 | // private enum BSPTreeNode
719 | // {
720 | // case inner(InnerNode)
721 | // case leaf([Triangle3D])
722 | // case none
723 | // }
724 | //
725 | // private enum SplittingPlaneOrientation
726 | // {
727 | // case xy
728 | // case xz
729 | // case yz
730 | //
731 | // var pointComparator: (Point3D, Point3D) -> Bool
732 | // {
733 | // switch self
734 | // {
735 | // case .xy:
736 | // return {$0.z < $1.z}
737 | // case .xz:
738 | // return {$0.y < $1.y}
739 | // case .yz:
740 | // return {$0.x < $1.x}
741 | // }
742 | // }
743 | //
744 | // var triangleComparator: (Triangle3D, Triangle3D) -> Bool
745 | // {
746 | // let pc = pointComparator
747 | // return { a, b -> Bool in
748 | // var bb = true
749 | // //∀pb∈B ∃pa∈A: pa Bool
799 | // {
800 | // guard point.x >= x && point.x <= (x + width) else { return false }
801 | // guard point.y >= y && point.y <= (y + height) else { return false }
802 | // guard point.z >= z && point.z <= (z + depth) else { return false }
803 | // return true
804 | // }
805 | //
806 | // private func intersects(ray: Ray3D, strict: Bool = false) -> Bool
807 | // {
808 | // let baseBFL = Point3D(x: x, y: y, z: z)
809 | // let baseTBR = baseBFL + Vector3D(x: width, y: height, z: depth)
810 | //
811 | // let tA = baseBFL - ray.base
812 | // let tB = baseTBR - ray.base
813 | //
814 | // var rayDirection = ray.direction
815 | // if rayDirection.x == 0
816 | // {
817 | // rayDirection.x = nextafterf(rayDirection.x, 1)
818 | // }
819 | //
820 | // if rayDirection.y == 0
821 | // {
822 | // rayDirection.y = nextafterf(rayDirection.y, 1)
823 | // }
824 | //
825 | // if rayDirection.z == 0
826 | // {
827 | // rayDirection.z = nextafterf(rayDirection.z, 1)
828 | // }
829 | //
830 | // let tDivA = Vector3D(x: tA.x / rayDirection.x, y: tA.y / rayDirection.y, z: tA.z / ray.direction.z)
831 | // let tDivB = Vector3D(x: tB.x / rayDirection.x, y: tB.y / rayDirection.y, z: tB.z / ray.direction.z)
832 | //
833 | // let tMin = max(max(min(tDivA.x, tDivB.x), min(tDivA.y, tDivB.y)), min(tDivA.z, tDivB.z))
834 | // let tMax = min(min(max(tDivA.x, tDivB.x), max(tDivA.y, tDivB.y)), max(tDivA.z, tDivB.z))
835 | //
836 | // guard tMax >= 0 else { return false }
837 | // guard tMax >= tMin else { return false }
838 | // if strict
839 | // {
840 | // guard tMin <= 1 else { return false }
841 | // }
842 | // return true
843 | // }
844 | //
845 | // fileprivate var description: String
846 | // {
847 | // return "x: \(x), y: \(y), z: \(z), w: \(width), h: \(height), d: \(depth)"
848 | // }
849 | // }
850 | //
851 | // private class InnerNode: CustomStringConvertible
852 | // {
853 | // private let volume: Volume
854 | // private let lower:BSPTreeNode
855 | // private let upper:BSPTreeNode
856 | //
857 | // init(triangles:[Triangle3D], volume: Volume, nextSplit: SplittingPlaneOrientation)
858 | // {
859 | // self.volume = volume
860 | //
861 | // let sortedTriangles = triangles.sorted(by: nextSplit.triangleComparator)
862 | // let lowerTriangles = sortedTriangles.limit(n: sortedTriangles.count / 2)
863 | // let upperTriangles = sortedTriangles.limit(n: sortedTriangles.count - lowerTriangles.count, offset: lowerTriangles.count)
864 | //
865 | // let lowerPoints = lowerTriangles.flatMap{[$0.a.point, $0.b.point, $0.c.point]}
866 | // let lowerMinX = lowerPoints.min{$0.x < $1.x}?.x ?? 0
867 | // let lowerMinY = lowerPoints.min{$0.y < $1.y}?.y ?? 0
868 | // let lowerMinZ = lowerPoints.min{$0.z < $1.z}?.z ?? 0
869 | // let lowerMaxX = lowerPoints.max{$0.x < $1.x}?.x ?? 0
870 | // let lowerMaxY = lowerPoints.max{$0.y < $1.y}?.y ?? 0
871 | // let lowerMaxZ = lowerPoints.max{$0.z < $1.z}?.z ?? 0
872 | //
873 | // let upperPoints = upperTriangles.flatMap{[$0.a.point, $0.b.point, $0.c.point]}
874 | // let upperMinX = upperPoints.min{$0.x < $1.x}?.x ?? 0
875 | // let upperMinY = upperPoints.min{$0.y < $1.y}?.y ?? 0
876 | // let upperMinZ = upperPoints.min{$0.z < $1.z}?.z ?? 0
877 | // let upperMaxX = upperPoints.max{$0.x < $1.x}?.x ?? 0
878 | // let upperMaxY = upperPoints.max{$0.y < $1.y}?.y ?? 0
879 | // let upperMaxZ = upperPoints.max{$0.z < $1.z}?.z ?? 0
880 | //
881 | // let lowerDX = lowerMaxX - lowerMinX
882 | // let lowerDY = lowerMaxY - lowerMinY
883 | // let lowerDZ = lowerMaxZ - lowerMinZ
884 | //
885 | // let upperDX = upperMaxX - upperMinX
886 | // let upperDY = upperMaxY - upperMinY
887 | // let upperDZ = upperMaxZ - upperMinZ
888 | //
889 | // let lowerVolume = Volume(x: lowerMinX, y: lowerMinY, z: lowerMinZ, width: lowerDX, height: lowerDY, depth: lowerDZ)
890 | // let upperVolume = Volume(x: upperMinX, y: upperMinY, z: upperMinZ, width: upperDX, height: upperDY, depth: upperDZ)
891 | //
892 | // let capacity = 4
893 | //
894 | // if lowerPoints.count < capacity ||
895 | // (lowerMinX == lowerMaxX &&
896 | // lowerMinY == lowerMaxY &&
897 | // lowerMinZ == lowerMaxZ) ||
898 | // lowerTriangles.count == triangles.count
899 | // {
900 | // lower = .leaf(lowerTriangles)
901 | // }
902 | // else
903 | // {
904 | // let nextSplit: SplittingPlaneOrientation
905 | //
906 | // if lowerDX > lowerDY && lowerDX > lowerDZ
907 | // {
908 | // nextSplit = .yz
909 | // }
910 | // else if lowerDY > lowerDX && lowerDY > lowerDZ
911 | // {
912 | // nextSplit = .xz
913 | // }
914 | // else
915 | // {
916 | // nextSplit = .xy
917 | // }
918 | //
919 | //
920 | // lower = .inner(InnerNode(triangles: lowerTriangles,
921 | // volume: lowerVolume,
922 | // nextSplit: nextSplit))
923 | // }
924 | // if upperPoints.count < capacity ||
925 | // (upperMinX == upperMaxX &&
926 | // upperMinY == upperMaxY &&
927 | // upperMinZ == upperMaxZ) ||
928 | // upperTriangles.count == triangles.count
929 | // {
930 | // upper = .leaf(upperTriangles)
931 | // }
932 | // else
933 | // {
934 | // let nextSplit: SplittingPlaneOrientation
935 | //
936 | // if upperDX > upperDY && upperDX > upperDZ
937 | // {
938 | // nextSplit = .yz
939 | // }
940 | // else if upperDY > upperDX && upperDY > upperDZ
941 | // {
942 | // nextSplit = .xz
943 | // }
944 | // else
945 | // {
946 | // nextSplit = .xy
947 | // }
948 | //
949 | // upper = .inner(InnerNode(triangles: upperTriangles,
950 | // volume: upperVolume,
951 | // nextSplit: nextSplit))
952 | // }
953 | // }
954 | //
955 | // convenience init(triangles:[Triangle3D])
956 | // {
957 | // let points = triangles.flatMap{[$0.a.point, $0.b.point, $0.c.point]}
958 | // let minX = points.min{$0.x < $1.x}?.x ?? 0
959 | // let minY = points.min{$0.y < $1.y}?.y ?? 0
960 | // let minZ = points.min{$0.z < $1.z}?.z ?? 0
961 | // let maxX = points.max{$0.x < $1.x}?.x ?? 1
962 | // let maxY = points.max{$0.y < $1.y}?.y ?? 1
963 | // let maxZ = points.max{$0.z < $1.z}?.z ?? 1
964 | //
965 | // let dx = maxX - minX
966 | // let dy = maxY - minY
967 | // let dz = maxZ - minZ
968 | //
969 | // let nextSplit: SplittingPlaneOrientation
970 | //
971 | // if dx > dy && dx > dz
972 | // {
973 | // nextSplit = .yz
974 | // }
975 | // else if dy > dx && dy > dz
976 | // {
977 | // nextSplit = .xz
978 | // }
979 | // else
980 | // {
981 | // nextSplit = .xy
982 | // }
983 | //
984 | // let volume = Volume(
985 | // x: minX,
986 | // y: minY,
987 | // z: minZ,
988 | // width: maxX - minX,
989 | // height: maxY - minY,
990 | // depth: maxZ - minZ)
991 | //
992 | // self.init(triangles: triangles, volume: volume, nextSplit: nextSplit)
993 | // }
994 | //
995 | // private final func intersects(ray: Ray3D, betterThan bestIntersection: Float? = nil) -> Bool
996 | // {
997 | // let baseBFL = Point3D(x: volume.x, y: volume.y, z: volume.z)
998 | // let baseTBR = baseBFL + Vector3D(x: volume.width, y: volume.height, z: volume.depth)
999 | //
1000 | // let tA = baseBFL - ray.base
1001 | // let tB = baseTBR - ray.base
1002 | //
1003 | // let rayDirection = ray.direction + Vector3D(x: nextafterf(0, 1) * 4.0, y: nextafterf(0, 1) * 4.0, z: nextafterf(0, 1) * 4.0)
1004 | //
1005 | // let tDivA = Vector3D(x: tA.x / rayDirection.x, y: tA.y / rayDirection.y, z: tA.z / ray.direction.z)
1006 | // let tDivB = Vector3D(x: tB.x / rayDirection.x, y: tB.y / rayDirection.y, z: tB.z / ray.direction.z)
1007 | //
1008 | // let tMax = min(min(max(tDivA.x, tDivB.x), max(tDivA.y, tDivB.y)), max(tDivA.z, tDivB.z))
1009 | //
1010 | // guard tMax >= 0 else { return false }
1011 | //
1012 | // let tMin = max(max(min(tDivA.x, tDivB.x), min(tDivA.y, tDivB.y)), min(tDivA.z, tDivB.z))
1013 | //
1014 | // guard tMax >= tMin && tMin < (bestIntersection ?? Float.infinity) else { return false }
1015 | // return true
1016 | // }
1017 | //
1018 | // fileprivate final func nearestIntersectingTriangle(forRay ray: Ray3D, betterThan bestIntersection: Float? = nil) -> (triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)?
1019 | // {
1020 | // guard intersects(ray: ray, betterThan: bestIntersection) else { return nil }
1021 | // var bestIntersection: (triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)? = nil
1022 | // for subnode in [lower, upper]
1023 | // {
1024 | // if case let .inner(innerNode) = subnode
1025 | // {
1026 | // guard let subnodeIntersection = innerNode.nearestIntersectingTriangle(forRay: ray, betterThan: nil) else { continue }
1027 | // if bestIntersection == nil || bestIntersection!.ray > subnodeIntersection.ray
1028 | // {
1029 | // bestIntersection = subnodeIntersection
1030 | // }
1031 | // }
1032 | // else if case let .leaf(triangles) = subnode
1033 | // {
1034 | // for triangle in triangles
1035 | // {
1036 | // guard let intersection = ray.findIntersection(with: triangle) else { continue }
1037 | // guard intersection.rayParameter > 0 else { continue }
1038 | // if bestIntersection == nil || intersection.rayParameter < bestIntersection!.ray
1039 | // {
1040 | // bestIntersection = (triangle: triangle, ray: intersection.rayParameter, barycentricIntersection: intersection.barycentric)
1041 | // }
1042 | // }
1043 | // }
1044 | // }
1045 | // return bestIntersection
1046 | // }
1047 | //
1048 | // fileprivate var description: String
1049 | // {
1050 | // let lowerDescription:String
1051 | // if case let .inner(innerNode) = lower
1052 | // {
1053 | // lowerDescription = innerNode.description.replacingOccurrences(of: "\n", with: "\n\t")
1054 | // }
1055 | // else if case let .leaf(triangles) = lower
1056 | // {
1057 | // lowerDescription = "\tLeaf (\(triangles.count) triangles)"
1058 | // }
1059 | // else
1060 | // {
1061 | // lowerDescription = "None"
1062 | // }
1063 | // let upperDescription:String
1064 | // if case let .inner(innerNode) = upper
1065 | // {
1066 | // upperDescription = innerNode.description.replacingOccurrences(of: "\n", with: "\n\t")
1067 | // }
1068 | // else if case let .leaf(triangles) = upper
1069 | // {
1070 | // upperDescription = "\tLeaf (\(triangles.count) triangles)"
1071 | // }
1072 | // else
1073 | // {
1074 | // upperDescription = "None"
1075 | // }
1076 | // return "Inner \(volume)\n\t\(lowerDescription)\n\t\(upperDescription)"
1077 | // }
1078 | // }
1079 | //
1080 | // private let root:InnerNode
1081 | //
1082 | // init(with triangles: [Triangle3D])
1083 | // {
1084 | // root = InnerNode(triangles: triangles)
1085 | // }
1086 | //
1087 | // final func nearestIntersectingTriangle(forRay ray: Ray3D) -> (triangle: Triangle3D, ray: Float, barycentricIntersection: BarycentricPoint)?
1088 | // {
1089 | // return root.nearestIntersectingTriangle(forRay: ray)
1090 | // }
1091 | //
1092 | // var description: String
1093 | // {
1094 | // return "BHVTree:\n\(root.description)"
1095 | // }
1096 | //}
1097 |
--------------------------------------------------------------------------------
/Path Tracing Demo/Textures.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Textures.swift
3 | // PathTracingTests
4 | //
5 | // Created by Palle Klewitz on 05.08.16.
6 | // Copyright © 2016 Palle Klewitz.
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is furnished
13 | // to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all
16 | // copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
23 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 | //
25 |
26 | import Foundation
27 |
28 | #if os(iOS)
29 | import UIKit
30 | #elseif os(watchOS)
31 | import UIKit
32 | #elseif os(OSX)
33 | import Cocoa
34 | #endif
35 |
36 |
37 | struct TextureCoordinate
38 | {
39 | var u: Float
40 | var v: Float
41 | }
42 |
43 | @inline(__always)
44 | func * (left: TextureCoordinate, right: Float) -> TextureCoordinate
45 | {
46 | return TextureCoordinate(u: left.u * right, v: left.v * right)
47 | }
48 |
49 | @inline(__always)
50 | func * (left: Float, right: TextureCoordinate) -> TextureCoordinate
51 | {
52 | return right * left
53 | }
54 |
55 | func + (left: TextureCoordinate, right: TextureCoordinate) -> TextureCoordinate
56 | {
57 | return TextureCoordinate(u: left.u + right.u, v: left.v + right.v)
58 | }
59 |
60 | protocol Texture
61 | {
62 | func color(`for` textureCoordinate: TextureCoordinate, atAngle incidentAngle: Float) -> Color
63 | }
64 |
65 | class CheckerboardTexture: Texture
66 | {
67 | var horizontalTiles: Int
68 | var verticalTiles: Int
69 |
70 | init(horizontalTiles: Int, verticalTiles: Int)
71 | {
72 | self.horizontalTiles = horizontalTiles
73 | self.verticalTiles = verticalTiles
74 | }
75 |
76 | func color(for textureCoordinate: TextureCoordinate, atAngle incidentAngle: Float) -> Color
77 | {
78 | if fmodf(textureCoordinate.u * Float(horizontalTiles), 2.0) <= 1.0
79 | {
80 | return fmodf(textureCoordinate.v * Float(verticalTiles), 2.0) <= 1.0 ? .black() : .white()
81 | }
82 | else
83 | {
84 | return fmodf(textureCoordinate.v * Float(verticalTiles), 2.0) <= 1.0 ? .white() : .black()
85 | }
86 | }
87 | }
88 |
89 | extension CheckerboardTexture: CustomStringConvertible
90 | {
91 | var description: String
92 | {
93 | return "CheckerboardTexture (\(horizontalTiles)x\(verticalTiles))"
94 | }
95 | }
96 |
97 | class ImageTexture: Texture
98 | {
99 | var pixelData: [Float]
100 | var width: Int
101 | var height: Int
102 |
103 | init?(with image: CGImage)
104 | {
105 | self.width = image.width
106 | self.height = image.height
107 |
108 | pixelData = [Float](repeating: 0, count: width * height * 4)
109 | guard let ctx = CGContext(
110 | data: &pixelData,
111 | width: width,
112 | height: height,
113 | bitsPerComponent: 32,
114 | bytesPerRow: width * 4 * 4,
115 | space: CGColorSpaceCreateDeviceRGB(),
116 | bitmapInfo: CGBitmapInfo.floatComponents.rawValue | CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue)
117 | else
118 | {
119 | return nil
120 | }
121 | ctx.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height))
122 | print("Max: \(pixelData.max() ?? 0), min: \(pixelData.min() ?? 0)")
123 | }
124 |
125 | init(with data: [Float], width: Int, height: Int)
126 | {
127 | self.pixelData = data
128 | self.width = width
129 | self.height = height
130 | }
131 |
132 | convenience init?(contentsOf fileURL: URL)
133 | {
134 | guard let data = try? Data(contentsOf: fileURL) else { return nil }
135 | #if os(iOS)
136 | guard let image = UIImage(data: data) else { return nil }
137 | guard let cgImage = image.cgImage else { return nil }
138 | #elseif os(watchOS)
139 | guard let image = UIImage(data: data) else { return nil }
140 | guard let cgImage = image.cgImage else { return nil }
141 | #elseif os(OSX)
142 | guard let image = NSImage(data: data) else { return nil }
143 | guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return nil }
144 | #endif
145 | self.init(with: cgImage)
146 | }
147 |
148 | @inline(__always)
149 | private func rangeLimited(_ textureCoordinate: TextureCoordinate) -> TextureCoordinate
150 | {
151 | let u = textureCoordinate.u - floorf(textureCoordinate.u)
152 | let v = textureCoordinate.v - floorf(textureCoordinate.v)
153 |
154 | return TextureCoordinate(u: u, v: v)
155 | }
156 |
157 | func color(for textureCoordinate: TextureCoordinate, atAngle incidentAngle: Float) -> Color
158 | {
159 | let limited = rangeLimited(textureCoordinate)
160 | let x = limited.u * Float(width-1)
161 | let y = limited.v * Float(height-1)
162 |
163 | let lx = floorf(x)
164 | let gx = ceilf(x)
165 | let ly = floorf(y)
166 | let gy = ceilf(y)
167 |
168 | let fgx = x - lx
169 | let flx = 1 - fgx
170 | let fgy = y - ly
171 | let fly = 1 - fgy
172 |
173 | let indexLL = (Int(lx) + width * Int(ly)) * 4
174 | let indexGL = (Int(gx) + width * Int(ly)) * 4
175 | let indexLG = (Int(lx) + width * Int(gy)) * 4
176 | let indexGG = (Int(gx) + width * Int(gy)) * 4
177 |
178 | let colorLL = Color(withRed: self.pixelData[indexLL],
179 | green: self.pixelData[indexLL+1],
180 | blue: self.pixelData[indexLL+2],
181 | alpha: self.pixelData[indexLL+3])
182 |
183 | let colorGL = Color(withRed: self.pixelData[indexGL],
184 | green: self.pixelData[indexGL+1],
185 | blue: self.pixelData[indexGL+2],
186 | alpha: self.pixelData[indexGL+3])
187 |
188 | let colorLG = Color(withRed: self.pixelData[indexLG],
189 | green: self.pixelData[indexLG+1],
190 | blue: self.pixelData[indexLG+2],
191 | alpha: self.pixelData[indexLG+3])
192 |
193 | let colorGG = Color(withRed: self.pixelData[indexGG],
194 | green: self.pixelData[indexGG+1],
195 | blue: self.pixelData[indexGG+2],
196 | alpha: self.pixelData[indexGG+3])
197 |
198 | //Bilinear interpolation
199 | let linearLower = colorLL * flx + colorGL * fgx
200 | let linearUpper = colorLG * flx + colorGG * fgx
201 | return linearLower * fly + linearUpper * fgy
202 | }
203 | }
204 |
205 | extension ImageTexture: CustomStringConvertible
206 | {
207 | var description: String
208 | {
209 | return "ImageTexture (w: \(width), h: \(height))"
210 | }
211 | }
212 |
213 |
214 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Path Tracing Demo
2 |
3 | 
4 |
5 | ## Features
6 | ### Shaders
7 | - Emission
8 | - Diffuse
9 | - Reflection
10 | - Refraction
11 | - Mix
12 | - Add
13 |
14 | ### Refraction
15 | - Fresnel glass
16 | - Depth attenuation
17 |
18 | ### Other Features
19 | - Physically accurate depth of field
20 | - Image based lighting
21 | - Texturing
22 | - Wavefront .obj import
23 | - HDR textures and render targets
24 |
25 | ## License
26 |
27 | Copyright (c) 2016 - 2018 Palle Klewitz.
28 |
29 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
30 |
31 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
32 |
33 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 |
--------------------------------------------------------------------------------