├── .gitignore ├── .lldbinit ├── README.md ├── build.sh └── code ├── Main.swift └── Math.swift /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | 3 | *.swp 4 | 5 | temp 6 | 7 | .lvimrc 8 | 9 | *.ppm 10 | 11 | *.mp4 12 | -------------------------------------------------------------------------------- /.lldbinit: -------------------------------------------------------------------------------- 1 | target create ./build/main 2 | breakpoint set --line 442 3 | run 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | This program was made to accompany _Graphics like Pixar using Swift_, a lightning talk given at _try! Swift Tokyo_ in 2019. 4 | 5 | This is a simple ray tracer, or more specifically, path tracer, built using Swift. 6 | 7 | Much of the code is derived from Pete Shirley's Ray Tracing Minibooks, with a few added extras. 8 | 9 | # Overview 10 | 11 | For each pixel, `ns` rays are stochastically fired into a simple scene. 12 | 13 | The final pixel color is just the average color of each ray, which is calculated using the material properties of whatever sphere the ray happens to intersect with. 14 | 15 | To make things interesting, a simple looping camera animation was adding using Rodrigues rotation. Single frames are output as `ppm` files, and converted to an `mp4` using `ffmpeg` after the program has run. 16 | 17 | # Algorithms 18 | 19 | - Line sphere intersection 20 | - Path tracing 21 | - Lamberian scattering 22 | - Fresnel equations 23 | - Schlick approximation 24 | - Perlin noise 25 | - Rodrigues rotation formula 26 | 27 | # Requirements 28 | 29 | - Xcode 10 30 | - Swift 4.2 31 | - ffmpeg 32 | 33 | # Installation 34 | 35 | Run `build.sh` and everything should just work! 36 | 37 | # Performance 38 | 39 | The default render settings are quite slow. The whole program is single threaded, and not optimized at all. 40 | 41 | To reduce the rendering time, try the following: 42 | 43 | - Reducing the output image resolution. 44 | - Reduce the number of rays per pixel. 45 | - Reduce the frames drawn, either the duration of the animation or framerate. 46 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CODE_PATH=code 4 | BUILD_PATH=build 5 | ASSETS_PATH=data 6 | 7 | # OPTIMIZE_SWITCHES="" 8 | OPTIMIZE_SWITCHES="-O" 9 | 10 | swiftc -g $OPTIMIZE_SWITCHES $CODE_PATH/Math.swift $CODE_PATH/main.swift -o $BUILD_PATH/main 11 | 12 | rm ./temp/* 13 | rm ./out.mp4 14 | $BUILD_PATH/main 15 | # open temp/out_001.ppm 16 | ffmpeg -i ./temp/out_%03d.ppm -c:v libx264 -crf 18 -preset slow -pix_fmt yuv420p -c:a copy out.mp4 17 | 18 | exit 0 19 | -------------------------------------------------------------------------------- /code/Main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: Perlin Noise 4 | 5 | var PERLIN_N:Int = (1 << 8) 6 | 7 | func permuteAxis(_ axis: inout [Int], _ i: Int) { 8 | let tar = Int(drand48f()*Float(i + 1)) 9 | let tmp = axis[i] 10 | axis[i] = axis[tar] 11 | axis[tar] = tmp 12 | } 13 | 14 | // NOTE: (Kapsy) This is an incomplete implementation! 15 | class Perlin { 16 | var permX: [Int] 17 | var permY: [Int] 18 | var permZ: [Int] 19 | 20 | var randFloat: [Float] 21 | 22 | init() { 23 | let N = PERLIN_N 24 | 25 | self.permX = [Int](repeating: 0, count: PERLIN_N) 26 | self.permY = [Int](repeating: 0, count: PERLIN_N) 27 | self.permZ = [Int](repeating: 0, count: PERLIN_N) 28 | 29 | self.randFloat = [Float](repeating: 0.0, count: PERLIN_N) 30 | 31 | for i in 0.. Float { 47 | 48 | let p1 = p0*20.0 49 | 50 | let u = Float(p1.x - floor(p1.x)) 51 | let v = Float(p1.y - floor(p1.y)) 52 | let w = Float(p1.z - floor(p1.z)) 53 | 54 | let i = Int(floor(p1.x)) 55 | let j = Int(floor(p1.y)) 56 | let k = Int(floor(p1.z)) 57 | 58 | // Crazy Swift multi dimensional array syntax. 59 | var c:[[[Float]]] = Array(repeating: 60 | Array(repeating: 61 | Array(repeating: 0.0, 62 | count: 2), 63 | count: 2), 64 | count: 2) 65 | 66 | for di in 0..<2 { 67 | for dj in 0..<2 { 68 | for dk in 0..<2 { 69 | c[di][dj][dk] = 70 | per.randFloat[per.permX[(i+di) & (PERLIN_N - 1)] ^ 71 | per.permY[(j+dj) & (PERLIN_N - 1)] ^ 72 | per.permZ[(k+dk) & (PERLIN_N - 1)]] 73 | }}} 74 | 75 | var accum = Float(0.0) 76 | 77 | for i in 0..<2 { 78 | for j in 0..<2 { 79 | for k in 0..<2 { 80 | let I = (Float(i)*u + (1.0 - Float(i))*(1.0 - u)) 81 | let J = (Float(j)*v + (1.0 - Float(j))*(1.0 - v)) 82 | let K = (Float(k)*w + (1.0 - Float(k))*(1.0 - w)) 83 | 84 | accum += I*J*K*Float(c[i][j][k]) 85 | }}} 86 | 87 | assert(accum <= 1.0) 88 | 89 | return accum 90 | } 91 | 92 | // MARK: Texture 93 | 94 | enum TextureType { 95 | 96 | case plain 97 | case checker 98 | case perlin 99 | } 100 | 101 | class Texture { 102 | 103 | var type: TextureType = .plain 104 | var albedo = V3(0) 105 | var perlin: Perlin? 106 | } 107 | 108 | // MARK: Material 109 | 110 | enum MaterialType { 111 | 112 | case lambertian 113 | case metal 114 | case dielectric 115 | } 116 | 117 | class Material { 118 | 119 | var type: MaterialType 120 | var texture: Texture 121 | var fuzz = Float(0) // For metal only, leaving in same class for now. 122 | var refIndex = Float(1.1) // For dielectrics only, leaving here for now. 123 | 124 | init(type: MaterialType, texture: Texture) { 125 | self.type = type 126 | self.texture = texture 127 | } 128 | } 129 | 130 | func getAlbedo(_ texture: Texture, _ u: Float, _ v: Float, _ p: V3) -> V3 { 131 | 132 | var res = V3(0) 133 | 134 | switch texture.type { 135 | 136 | case .checker: 137 | let selector = Float(sin(10.0*p.x)*sin(10.0*p.z)) 138 | if selector > 0.0 { 139 | res = V3(0,0,0) 140 | } else { 141 | res = V3(1.0,1.0,1.0) 142 | } 143 | 144 | case .plain: 145 | res = texture.albedo 146 | 147 | case .perlin: 148 | if let perlin = texture.perlin { 149 | res = V3(1.0)*getNoise(perlin, p) 150 | } 151 | } 152 | 153 | return res 154 | } 155 | 156 | func schlick(_ cos: Float, _ refIndex: Float) -> Float { 157 | var r0 = (1.0 - refIndex)/(1.0 + refIndex); 158 | r0 = r0*r0; 159 | r0 = r0 + (1.0 - r0)*pow((1.0 - cos), 5.0); 160 | 161 | return r0 162 | } 163 | 164 | func reflect(_ v: V3, _ N: V3) -> V3 { 165 | return (v - 2*dot(v, N)*N) 166 | } 167 | 168 | // MARK: Sphere 169 | 170 | class Sphere 171 | { 172 | var center: V3 173 | var rad: Float 174 | var material: Material 175 | 176 | init(center: V3, rad: Float, material: Material) { 177 | self.center = center 178 | self.rad = rad 179 | self.material = material 180 | } 181 | } 182 | 183 | var globalSpheres: [Sphere] = [] 184 | 185 | enum PrimativeType { 186 | case sphere 187 | case triangle 188 | } 189 | 190 | // MARK: Camera 191 | 192 | struct Camera { 193 | 194 | var origin = V3(0) 195 | var lowerLeft = V3(0) 196 | var horiz = V3(0) 197 | var vert = V3(0) 198 | 199 | var w = V3(0) 200 | var u = V3(0) 201 | var v = V3(0) 202 | 203 | var lensRad = Float(0) 204 | } 205 | 206 | // MARK: Ray 207 | 208 | class HitRecord { 209 | var dist = Float(0) 210 | var primRef:Sphere? = nil 211 | var primType:PrimativeType = .sphere 212 | } 213 | 214 | struct Ray { 215 | 216 | var A: V3 217 | var B: V3 218 | 219 | // NOTE: (Kapsy) For C union like behavior. 220 | var orig: V3 { get { return A } } 221 | var dir: V3 { get { return B } } 222 | 223 | init(_ A: V3, _ B: V3) { 224 | self.A = A 225 | self.B = B 226 | } 227 | 228 | init() { 229 | self.A = V3(0) 230 | self.B = V3(0) 231 | } 232 | } 233 | 234 | func getRayTemp(_ c: inout Camera, _ s: Float, _ t: Float) -> Ray { 235 | 236 | let res = Ray(c.origin, c.lowerLeft + s*c.horiz + t*c.vert - c.origin) 237 | return res 238 | } 239 | 240 | func getRay(_ c: inout Camera, _ s: Float, _ t: Float) -> Ray { 241 | 242 | // NOTE: (Kapsy) Random in unit disk. 243 | var rand = V3(0) 244 | repeat { 245 | rand = 2.0*V3(drand48f(), drand48f(), 0) - V3(1,1,0) 246 | } while dot(rand, rand) >= 1.0 247 | 248 | let rd = c.lensRad*rand; 249 | let offset = c.u*rd.x + c.v*rd.y 250 | 251 | let res = Ray(c.origin + offset, c.lowerLeft + s*c.horiz + t*c.vert - c.origin - offset) 252 | return res 253 | } 254 | 255 | func pointAt(_ ray: inout Ray, _ t: Float) -> V3 { 256 | let res = ray.A + t*ray.B 257 | return res 258 | } 259 | 260 | func traverseSpheres(_ ray: inout Ray, _ hit: HitRecord) { 261 | 262 | let tnear = Float(0.001) 263 | var tfar = Float.greatestFiniteMagnitude 264 | 265 | for sphere in globalSpheres { 266 | 267 | let rad = sphere.rad 268 | let center = sphere.center 269 | let oc = ray.orig - center 270 | 271 | let a = dot(ray.dir, ray.dir) 272 | let b = dot(oc, ray.dir) 273 | let c = dot(oc, oc) - rad*rad 274 | let discriminant = b*b - a*c; 275 | 276 | if discriminant > 0.0 { 277 | var t = (-b - sqrt(discriminant))/a 278 | if (tnear < t && t < tfar) 279 | { 280 | tfar = t 281 | 282 | hit.dist = t; 283 | hit.primRef = sphere 284 | hit.primType = .sphere; 285 | } 286 | 287 | t = (-b + sqrt(discriminant))/a 288 | if (tnear < t && t < tfar) 289 | { 290 | tfar = t 291 | 292 | hit.dist = t; 293 | hit.primRef = sphere 294 | hit.primType = .sphere; 295 | } 296 | } 297 | } 298 | } 299 | 300 | var MAX_DEPTH = Int(10) 301 | 302 | func getColorForRay(_ ray: inout Ray, _ depth: Int) -> V3 { 303 | 304 | var res = V3() 305 | 306 | let hit = HitRecord() 307 | hit.dist = Float.greatestFiniteMagnitude 308 | 309 | traverseSpheres(&ray, hit) 310 | 311 | if hit.dist < Float.greatestFiniteMagnitude { 312 | 313 | var p = V3(0) 314 | var N = V3(0) 315 | var mat: Material? = nil; 316 | 317 | switch hit.primType { 318 | 319 | case .sphere: 320 | 321 | if let sphere = hit.primRef { 322 | 323 | let rad = sphere.rad 324 | let center = sphere.center 325 | 326 | p = pointAt(&ray, hit.dist) 327 | N = (p - center)/rad 328 | mat = sphere.material 329 | } 330 | 331 | case .triangle: 332 | break 333 | } 334 | 335 | if let _mat = mat { 336 | 337 | switch _mat.type { 338 | 339 | case .lambertian: 340 | 341 | let rand = randomInUnitSphere() 342 | let target = p + N + rand 343 | let albedo = getAlbedo(_mat.texture, 0, 0, p) 344 | var scattered = Ray(p, target - p) 345 | 346 | if depth < MAX_DEPTH { 347 | res = albedo*getColorForRay(&scattered, depth+1) 348 | } else { 349 | res = V3(0) 350 | } 351 | 352 | case .metal: 353 | 354 | let v = unit(ray.dir) 355 | let reflected = v - 2*dot(v, N)*N 356 | let bias = N*1e-4 357 | 358 | var scattered = Ray(p + bias, reflected + _mat.fuzz*randomInUnitSphere()) 359 | 360 | let albedo = getAlbedo(_mat.texture, 0, 0, p) 361 | 362 | // NOTE: (Kapsy) Direction between normal and reflection should never be more than 90 deg. 363 | let result = (dot(scattered.dir, N) > 0.0) 364 | if (depth < MAX_DEPTH && result) { 365 | res = albedo*getColorForRay(&scattered, depth+1) 366 | } else { 367 | res = V3(0.0) 368 | } 369 | 370 | case .dielectric: 371 | 372 | var scattered = Ray() 373 | 374 | var outwardNormal = V3(0) 375 | var niOverNt = Float(0) 376 | let reflected = reflect(ray.dir, N) 377 | 378 | var reflectProb = Float(0) 379 | var cos = Float(0) 380 | 381 | if dot(ray.dir, N) > 0.0 { 382 | outwardNormal = -N 383 | niOverNt = _mat.refIndex 384 | cos = _mat.refIndex*dot(ray.dir, N)/length(ray.dir) 385 | } 386 | else 387 | { 388 | outwardNormal = N 389 | niOverNt = 1.0/_mat.refIndex 390 | cos = -dot(ray.dir, N)/length(ray.dir) 391 | } 392 | 393 | var refracted = V3(0.0) 394 | 395 | let bias = outwardNormal*1e-2 396 | p = p - bias 397 | 398 | let uv = unit(ray.dir) 399 | let dt = dot(uv, outwardNormal) 400 | let discriminant = 1.0 - niOverNt*niOverNt*(1.0 - dt*dt) 401 | 402 | // NOTE: (Kapsy) Approximate reflection/refraction probability. 403 | if discriminant > 0.0 { 404 | refracted = niOverNt*(uv - outwardNormal*dt) - outwardNormal*sqrt(discriminant) 405 | reflectProb = schlick(cos, _mat.refIndex) 406 | } else { 407 | scattered = Ray(p, reflected) 408 | reflectProb = 1.0 409 | } 410 | 411 | if drand48f() < reflectProb { 412 | scattered = Ray(p, reflected) 413 | } else { 414 | scattered = Ray(p, refracted) 415 | } 416 | 417 | if depth < MAX_DEPTH { 418 | res = getColorForRay(&scattered, depth+1) 419 | } else { 420 | res = V3 (0.0) 421 | } 422 | } 423 | } 424 | 425 | } else { 426 | 427 | // NOTE: (Kapsy) Draw our psuedo sky background. 428 | let rdir = ray.dir 429 | 430 | let t = (unit(rdir).y + 1.0)*0.5 431 | let cola = V3(1.0) 432 | let colb = (1.0/255.0)*V3(255.0, 128.0, 0.0) 433 | 434 | res = (1.0 - t)*cola + t*colb 435 | } 436 | 437 | return res 438 | } 439 | 440 | func main() { 441 | 442 | // MARK: Init spheres 443 | 444 | let perlinTexture = Texture() 445 | perlinTexture.albedo = V3(1,1,1) 446 | perlinTexture.perlin = Perlin() 447 | perlinTexture.type = .perlin 448 | let sphere0Mat = Material(type: .lambertian, texture: perlinTexture) 449 | let sphere0 = Sphere(center: V3(0, 0.32, 0), rad: 0.34, material: sphere0Mat) 450 | globalSpheres.append(sphere0) 451 | 452 | let glassTexture = Texture() 453 | glassTexture.albedo = V3(1) 454 | let sphere1Mat = Material(type: .dielectric, texture: glassTexture) 455 | let sphere1 = Sphere(center: V3(0.53, 0.3, -0.33), rad: -0.23, material: sphere1Mat) 456 | globalSpheres.append(sphere1) 457 | 458 | let whiteTexture = Texture() 459 | whiteTexture.albedo = V3(1,0.97,0.97) 460 | let sphere2Mat = Material(type: .metal, texture: whiteTexture) 461 | sphere2Mat.fuzz = 0.24 462 | let sphere2 = Sphere(center: V3(-0.7, 0.3, 0), rad: 0.24, material: sphere2Mat) 463 | globalSpheres.append(sphere2) 464 | 465 | let groundTexture = Texture() 466 | groundTexture.albedo = V3(0.2,0.5,0.3) 467 | groundTexture.type = .checker 468 | let sphere3Mat = Material(type: .lambertian, texture: groundTexture) 469 | sphere3Mat.refIndex = 1.3 470 | let sphere3 = Sphere(center: V3(0, -99.99, 0), rad: 100.0, material: sphere3Mat) 471 | globalSpheres.append(sphere3) 472 | 473 | let greenTexture = Texture() 474 | greenTexture.albedo = V3(0,1.3,0) 475 | let sphere4Mat = Material(type: .lambertian, texture: greenTexture) 476 | let sphere4 = Sphere(center: V3(0.0, 0.3, 0.5), rad: 0.13, material: sphere4Mat) 477 | globalSpheres.append(sphere4) 478 | 479 | let redTexture = Texture() 480 | redTexture.albedo = V3(2,0.3,0.3) 481 | let sphere5Mat = Material(type: .lambertian, texture: redTexture) 482 | let sphere5 = Sphere(center: V3(0.1, 0.3, -0.6), rad: 0.16, material: sphere5Mat) 483 | globalSpheres.append(sphere5) 484 | 485 | let purpleTexture = Texture() 486 | purpleTexture.albedo = V3(1,0,1) 487 | let sphere6Mat = Material(type: .metal, texture: purpleTexture) 488 | sphere6Mat.fuzz = 0.2 489 | let sphere6 = Sphere(center: V3(0.68, 0.33, 0.79), rad: 0.33, material: sphere6Mat) 490 | globalSpheres.append(sphere6) 491 | 492 | let blueTexture = Texture() 493 | blueTexture.albedo = V3(0.2,0.2,3) 494 | let sphere7Mat = Material(type: .lambertian, texture: blueTexture) 495 | let sphere7 = Sphere(center: V3(-0.5, 0.3, -0.9), rad: 0.13, material: sphere7Mat) 496 | globalSpheres.append(sphere7) 497 | 498 | let purple2Texture = Texture() 499 | purple2Texture.albedo = V3(1,1,1) 500 | let sphere8Mat = Material(type: .dielectric, texture: purple2Texture) 501 | let sphere8 = Sphere(center: V3(-0.6, 0.24, 0.6), rad: 0.18, material: sphere8Mat) 502 | globalSpheres.append(sphere8) 503 | 504 | let metalTexture = Texture() 505 | metalTexture.albedo = V3(0,1,1) 506 | let sphere9Mat = Material(type: .metal, texture: metalTexture) 507 | sphere9Mat.fuzz = 0.3 508 | let sphere9 = Sphere(center: V3(0.5, 0.3, -0.9), rad: 0.10, material: sphere9Mat) 509 | globalSpheres.append(sphere9) 510 | 511 | let frameRate = Float(25) 512 | // NOTE: (Kapsy) Uncomment for animation preview renders. 513 | //let frameRate = Float(0.5) 514 | 515 | let durationSeconds = Float(12) 516 | let frameCount = frameRate*durationSeconds 517 | 518 | // NOTE: (Kapsy) Camera rotation step 519 | let omega = (2*Float.pi)/frameCount 520 | let k = V3 (0,1,0) 521 | var ellipsephase = Float(0) 522 | 523 | // NOTE: (Kapsy) Image plane dimensions 524 | let nx = Int(600) 525 | let ny = Int(300) 526 | 527 | //// let nx = Int(200) 528 | //// let ny = Int(100) 529 | 530 | // NOTE: (Kapsy) Primary rays per pixel 531 | let ns = Int(30) 532 | 533 | var lookFrom = V3(0.001,0.39,-1.0) 534 | let lookAt = V3(0.0, 0.3, 0.0) 535 | 536 | for f in 0..= 0) && (ir <= 255)) 603 | assert((ig >= 0) && (ig <= 255)) 604 | assert((ib >= 0) && (ib <= 255)) 605 | 606 | let pixel = "\(ir) \(ig) \(ib)\n".data(using: .ascii)! 607 | data.append(pixel) 608 | } 609 | } 610 | 611 | let outPath = "temp/out_\(String(format: "%03d", f + 1)).ppm" 612 | 613 | FileManager.default.createFile(atPath: outPath, contents: data) 614 | 615 | // NOTE: (Kapsy) Rodrigues Rotation formula 616 | var v = lookFrom 617 | v = v*cos(omega) + cross(k, v)*sin(omega) + k*dot(k, v)*(1.0 - cos(omega)) 618 | lookFrom = v 619 | 620 | ellipsephase += omega 621 | } 622 | } 623 | 624 | main() 625 | -------------------------------------------------------------------------------- /code/Math.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // MARK: Number functions 4 | 5 | func assertNaN(_ value: Float) { 6 | assert(!value.isNaN) 7 | } 8 | 9 | func filterNaN(_ value: Float) -> Float { 10 | return value.isNaN ? 0.0 : value 11 | } 12 | 13 | func drand48f() -> Float { 14 | return Float(drand48()) 15 | } 16 | 17 | func clamp01(_ a: Float) -> Float { 18 | if a > 1.0 { 19 | return 1.0 20 | } else if a < 0.0 { 21 | return 0.0 22 | } 23 | return a 24 | } 25 | 26 | // MARK: V3 27 | 28 | struct V3 { 29 | var x = Float(0) 30 | var y = Float(0) 31 | var z = Float(0) 32 | 33 | // NOTE: (Kapsy) For C union like behavior. 34 | var r: Float { get { return x } set(a) { self.x = a } } 35 | var g: Float { get { return y } set(a) { self.y = a } } 36 | var b: Float { get { return z } set(a) { self.z = a } } 37 | 38 | 39 | init() {} 40 | 41 | init(_ val: Float) { 42 | self.x = val 43 | self.y = val 44 | self.z = val 45 | } 46 | 47 | init(_ x: Float, _ y: Float, _ z: Float) { 48 | self.x = x 49 | self.y = y 50 | self.z = z 51 | } 52 | } 53 | 54 | func +(a: V3, b: V3) -> V3 { 55 | let res = V3(a.x + b.x, a.y + b.y, a.z + b.z) 56 | return res 57 | } 58 | 59 | func +=(a: inout V3, b: V3) { 60 | a = a + b 61 | } 62 | 63 | func -(a: V3, b: V3) -> V3 { 64 | let res = V3(a.x - b.x, a.y - b.y, a.z - b.z) 65 | return res 66 | } 67 | 68 | func -=(a: inout V3, b: V3) { 69 | a = a - b 70 | } 71 | 72 | func *(a: V3, b: V3) -> V3 { 73 | let res = V3(a.x*b.x, a.y*b.y, a.z*b.z) 74 | return res 75 | } 76 | 77 | func *=(a: inout V3, b: V3) { 78 | a = a * b 79 | } 80 | 81 | func /(a: V3, b: Float) -> V3 { 82 | let res = V3(a.x/b, a.y/b, a.z/b) 83 | return res 84 | } 85 | 86 | func /=(a: inout V3, b: Float) { 87 | a = a/b 88 | } 89 | 90 | func *(a: Float, b: V3) -> V3 { 91 | let res = V3(a*b.x, a*b.y, a*b.z) 92 | return res 93 | } 94 | 95 | func *(a: V3, b: Float) -> V3 { 96 | let res = b*a 97 | return res 98 | } 99 | 100 | prefix func -(a: V3) -> V3 { 101 | let res = V3(-a.x, -a.y, -a.z) 102 | return res 103 | } 104 | 105 | func dot(_ a: V3, _ b: V3) -> Float { 106 | let res = a.x*b.x + a.y*b.y + a.z*b.z 107 | return res 108 | } 109 | 110 | func cross(_ a: V3, _ b: V3) -> V3 { 111 | let res = V3(a.y*b.z - a.z*b.y, 112 | a.z*b.x - a.x*b.z, 113 | a.x*b.y - a.y*b.x); 114 | return res 115 | } 116 | 117 | func length(_ a: V3) -> Float { 118 | let res = sqrt(dot(a, a)) 119 | return res 120 | } 121 | 122 | func unit(_ a: V3) -> V3 { 123 | let res = a/length(a) 124 | return res 125 | } 126 | 127 | func squaredLen(_ v: V3) -> Float { 128 | return dot(v, v) 129 | } 130 | 131 | func randomInUnitSphere() -> V3 { 132 | var v = V3(0) 133 | 134 | repeat { 135 | v = 2.0*V3(drand48f(), drand48f(), drand48f()) - V3(1) 136 | } while squaredLen(v) >= 1.0 137 | 138 | return (v); 139 | } 140 | --------------------------------------------------------------------------------