├── .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 | ![Rendered Image](http://i.imgur.com/wbiIbH9.png "rendered image") 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 | --------------------------------------------------------------------------------