├── .gitignore ├── LICENSE.md ├── Main.lean ├── README.md ├── Render └── Vec3.lean ├── c ├── Makefile └── render.c ├── lake-manifest.json ├── lakefile.lean ├── lean-toolchain ├── old ├── Algebra.lean ├── ArrayExtra.lean ├── NatExtra.lean └── vec.lean └── output ├── test10.2.ppm ├── test12.2.ppm ├── test13.bigger.png ├── test13.bigger.ppm ├── test13.png ├── test13.ppm ├── test7.ppm └── test9.5.ppm /.gitignore: -------------------------------------------------------------------------------- 1 | /.lake/ 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Main.lean: -------------------------------------------------------------------------------- 1 | /- 2 | Copyright (c) 2021 Kyle Miller. All rights reserved. 3 | Released under Apache 2.0 license as described in the file LICENSE. 4 | Authors: Kyle Miller 5 | -/ 6 | import Render.Vec3 7 | 8 | def Float.pi : Float := 3.1415926535897932385 9 | @[inline] def Float.infinity : Float := 1e100 -- fix this 10 | 11 | /-- Uniform at random in [0, 1)-/ 12 | def randomFloat {gen} [RandomGen gen] (g : gen) : Float × gen := 13 | let (n, g') := RandomGen.next g 14 | let (lo, hi) := RandomGen.range g 15 | (Float.ofNat (n - lo) / Float.ofNat (hi - lo + 1), g') 16 | 17 | def IO.randFloat (lo := 0.0) (hi := 1.0) : IO Float := do 18 | let gen ← IO.stdGenRef.get 19 | let (r, gen) := randomFloat gen 20 | IO.stdGenRef.set gen 21 | return lo + (hi - lo) * r 22 | 23 | def IO.randVec3 (lo := 0.0) (hi := 1.0) : IO (Vec3 Float) := 24 | return ⟨← IO.randFloat lo hi, ← IO.randFloat lo hi, ← IO.randFloat lo hi⟩ 25 | 26 | def IO.randVec3InUnitSphere : IO (Vec3 Float) := do 27 | for _ in [0:100] do -- 7e-33 probability of failure 28 | let p ← IO.randVec3 (-1.0) (1.0) 29 | if p.lengthSquared < 1.0 then 30 | return p 31 | return ⟨1, 0, 0⟩ 32 | 33 | def IO.randVec3InUnitDisk : IO (Vec3 Float) := do 34 | for _ in [0:100] do -- 2e-67 probability of failure 35 | let p := Vec3.mk (← IO.randFloat (-1.0) (1.0)) (← IO.randFloat (-1.0) (1.0)) 0.0 36 | if p.lengthSquared < 1.0 then 37 | return p 38 | return ⟨0, 0, 0⟩ 39 | 40 | structure Ray (α : Type _) where 41 | origin : Vec3 α 42 | dir : Vec3 α 43 | 44 | def Ray.at [Add α] [Mul α] (r : Ray α) (t : α) : Vec3 α := r.origin + t * r.dir 45 | 46 | structure Camera where 47 | origin : Vec3 Float 48 | lowerLeftCorner : Vec3 Float 49 | horizontal : Vec3 Float 50 | vertical : Vec3 Float 51 | (u v w : Vec3 Float) /- right, up, back -/ 52 | lensRadius : Float 53 | 54 | /-- 55 | vfov is the vertical field of view (in degrees) 56 | -/ 57 | def Camera.default 58 | (lookFrom lookAt vup : Vec3 Float) 59 | (vfov : Float) 60 | (aspectRatio : Float) 61 | (aperture : Float) 62 | (focusDist : Float) : 63 | Camera := 64 | let theta := vfov / 180. * Float.pi 65 | let h := Float.tan (theta / 2) 66 | let viewportHeight := 2.0 * h 67 | let viewportWidth := aspectRatio * viewportHeight 68 | 69 | let w := (lookFrom - lookAt).normalized 70 | let u := (vup.cross w).normalized 71 | let v := w.cross u 72 | 73 | let origin := lookFrom 74 | let horizontal := focusDist * viewportWidth * u 75 | let vertical := focusDist * viewportHeight * v 76 | let lowerLeftCorner := origin - horizontal/2.0 - vertical/2.0 - focusDist * w 77 | 78 | Camera.mk origin lowerLeftCorner horizontal vertical u v w (aperture / 2.0) 79 | 80 | def Camera.getRay (c : Camera) (s t : Float) : IO (Ray Float) := do 81 | let rd := c.lensRadius * (← IO.randVec3InUnitDisk) 82 | let offset := rd.x * c.u + rd.y * c.v 83 | return { origin := c.origin + offset 84 | dir := c.lowerLeftCorner + s*c.horizontal + t*c.vertical - c.origin - offset } 85 | 86 | inductive MaterialResponse 87 | | emit (c : Color Float) 88 | | scatter (albedo : Color Float) (scattered : Ray Float) 89 | 90 | def MaterialResponse.absorb : MaterialResponse := MaterialResponse.emit Color.black 91 | 92 | inductive Material 93 | | lambertian (albedo : Color Float) 94 | | metal (albedo : Color Float) (fuzz : Float := 0.0) 95 | | dielectric (indexOfRefraction : Float) 96 | | sky 97 | 98 | structure HitRecord where 99 | p : Vec3 Float 100 | t : Float 101 | material : Material 102 | normal : Vec3 Float 103 | frontFace : Bool 104 | 105 | @[inline] 106 | def HitRecord.withNormal 107 | (p : Vec3 Float) (t : Float) (m : Material) 108 | (dir : Vec3 Float) (outwardNormal : Vec3 Float) : HitRecord := 109 | let frontFace : Bool := dir.dot outwardNormal < 0.0 110 | { p := p 111 | t := t 112 | material := m 113 | normal := if frontFace then outwardNormal else -outwardNormal 114 | frontFace := frontFace } 115 | 116 | @[inline] 117 | def Vec3.refract (uv : Vec3 Float) (n : Vec3 Float) (etai_over_etat : Float) : Vec3 Float := 118 | let cosTheta := min (- uv.dot n) 1.0 119 | let rOutPerp := etai_over_etat * (uv + cosTheta * n) 120 | let rOutParallel := (-Float.sqrt (1.0 - rOutPerp.lengthSquared).abs) * n 121 | rOutPerp + rOutParallel 122 | 123 | def HitRecord.scatter (hitrec : HitRecord) (r : Ray Float) : IO MaterialResponse := 124 | match hitrec.material with 125 | | .lambertian albedo => do 126 | let mut scatterDir := hitrec.normal + (← IO.randVec3InUnitSphere).normalized 127 | if scatterDir.nearZero then 128 | scatterDir := hitrec.normal 129 | return .scatter albedo { origin := hitrec.p, dir := scatterDir } 130 | | .metal albedo fuzz => do 131 | let reflected := r.dir.normalized.reflect hitrec.normal 132 | let scattered := { origin := hitrec.p, dir := reflected + fuzz * (← IO.randVec3InUnitSphere) } 133 | if scattered.dir.dot hitrec.normal > 0.0 then 134 | return .scatter albedo scattered 135 | else 136 | return .absorb 137 | | .dielectric indexOfRefraction => do 138 | let refractionRatio := if hitrec.frontFace then 1.0/indexOfRefraction else indexOfRefraction 139 | let unitDirection := r.dir.normalized 140 | let cosTheta := min (-unitDirection.dot hitrec.normal) 1.0 141 | let sinTheta := Float.sqrt (1.0 - cosTheta * cosTheta) 142 | let cannotRefract : Bool := refractionRatio * sinTheta > 1.0 143 | 144 | -- Schlick's approximation 145 | let reflectance := 146 | let r0' := (1 - refractionRatio) / (1 + refractionRatio) 147 | let r0 := r0' * r0' 148 | r0 + (1 - r0) * Float.pow (1 - cosTheta) 5 149 | 150 | let direction : Vec3 Float := 151 | if cannotRefract || reflectance > (← IO.randFloat) then 152 | Vec3.reflect unitDirection hitrec.normal 153 | else 154 | Vec3.refract unitDirection hitrec.normal refractionRatio 155 | 156 | let scattered := { origin := hitrec.p, dir := direction } 157 | return .scatter Color.white scattered 158 | | .sky => do 159 | let unit : Vec3 Float := r.dir.normalized 160 | let t := 0.5 * (unit.y + 1.0) 161 | return .emit <| (1.0 - t) * Color.white + t * (Color.mk 0.5 0.7 1.0) 162 | 163 | inductive Hittable 164 | | sphere (center : Vec3 Float) (radius : Float) (mat : Material) 165 | 166 | def Hittable.hit (r : Ray Float) (tmin : Float) (hitrec : HitRecord) : (obj : Hittable) → HitRecord 167 | | sphere center radius mat => Id.run do 168 | let oc := r.origin - center 169 | let a := r.dir.lengthSquared 170 | let halfb := Vec3.dot oc r.dir 171 | let c := oc.lengthSquared - radius * radius 172 | let discr := halfb*halfb - a*c 173 | if discr < 0.0 then 174 | return hitrec 175 | let sqrtd := discr.sqrt 176 | -- Find the nearest root that lies in the acceptable range 177 | let mut root := (-halfb - sqrtd) / a 178 | if root < tmin || hitrec.t < root then 179 | root := (-halfb + sqrtd) / a 180 | if root < tmin || hitrec.t < root then 181 | return hitrec 182 | let t := root 183 | let p := r.at t 184 | let outwardNormal := (p - center) / radius 185 | return HitRecord.withNormal p t mat r.dir outwardNormal 186 | 187 | def hitList (hs : Array Hittable) (r : Ray Float) (tmin tmax : Float) : HitRecord := Id.run do 188 | let mut hitrec : HitRecord := 189 | { p := ⟨0, 0, 0⟩ 190 | t := tmax 191 | material := Material.sky 192 | normal := ⟨0, 0, 0⟩ 193 | frontFace := true } 194 | for obj in hs do 195 | hitrec := obj.hit r tmin hitrec 196 | return hitrec 197 | 198 | def rayColor (hs : Array Hittable) (r : Ray Float) : 199 | (depth : Nat) → (acc : Color Float) → IO (Color Float) 200 | | 0, _ => return Color.black -- exceeded ray bounce limit, no more light gathered 201 | | depth + 1, acc => do 202 | match ← (hitList hs r 0.001 Float.infinity).scatter r with 203 | | .emit c => return acc * c 204 | | .scatter albedo scattered => rayColor hs scattered depth (albedo * acc) 205 | 206 | def Float.clampToUInt8 (x : Float) : UInt8 := 207 | Float.toUInt8 <| min 255 <| max 0 x 208 | 209 | def IO.FS.Handle.writeColor (handle : IO.FS.Handle) (c : Color Float) : IO Unit := do 210 | let r := Float.clampToUInt8 (256 * c.r.sqrt) 211 | let g := Float.clampToUInt8 (256 * c.g.sqrt) 212 | let b := Float.clampToUInt8 (256 * c.b.sqrt) 213 | handle.putStrLn s!"{r} {g} {b}" 214 | 215 | def randomScene : IO (Array Hittable) := do 216 | let mut world : Array Hittable := #[] 217 | 218 | -- Ground 219 | world := world.push <| .sphere ⟨0, -1000, 0⟩ 1000 (.lambertian ⟨0.5, 0.5, 0.5⟩) 220 | 221 | for a' in [0:22] do 222 | let a := Float.ofNat a' - 11 223 | for b' in [0:22] do 224 | let b := Float.ofNat b' - 11 225 | let center : Vec3 Float := ⟨a + 0.9 * (← IO.randFloat), 0.2, b + 0.9 * (← IO.randFloat)⟩ 226 | if Vec3.length (center - Vec3.mk 4 0.2 0) > 0.9 then 227 | let chooseMat ← IO.randFloat 228 | if chooseMat < 0.9 then 229 | let albedo : Color Float := (← IO.randVec3) * (← IO.randVec3) 230 | world := world.push <| .sphere center 0.2 (.lambertian albedo) 231 | else if chooseMat < 0.95 then 232 | let albedo : Color Float ← IO.randVec3 0.5 1 233 | let fuzz ← IO.randFloat 0 0.5 234 | world := world.push <| .sphere center 0.2 (.metal albedo fuzz) 235 | else 236 | world := world.push <| .sphere center 0.2 (.dielectric 1.5) 237 | 238 | world := world.push <| .sphere ⟨0, 1, 0⟩ 1 (.dielectric 1.5) 239 | world := world.push <| .sphere ⟨-4, 1, 0⟩ 1 (.lambertian ⟨0.4, 0.2, 0.1⟩) 240 | world := world.push <| .sphere ⟨4, 1, 0⟩ 1 (.metal ⟨0.7, 0.6, 0.5⟩) 241 | 242 | return world 243 | 244 | def writeTestImage (filename : String) : IO Unit := do 245 | let width : Nat := 500 246 | let height : Nat := width * 2 / 3 247 | let aspectRatio : Float := (Float.ofNat width) / (Float.ofNat height) 248 | let numThreads := 10 249 | let samplesPerPixel := 8 250 | let maxDepth := 30 251 | 252 | IO.println s!"{numThreads} threads, {width}x{height} pixels, {numThreads*samplesPerPixel} total samples per pixel, max depth {maxDepth}." 253 | 254 | -- Set the seed to something specific for determinism 255 | IO.stdGenRef.set mkStdGen 256 | 257 | let world ← randomScene 258 | 259 | let lookFrom : Vec3 Float := ⟨13, 2, 3⟩ 260 | let lookAt : Vec3 Float := ⟨0, 0, 0⟩ 261 | let vup : Vec3 Float := ⟨0, 1, 0⟩ 262 | let distToFocus := 10 263 | let aperture := 0.1 264 | let cam := Camera.default lookFrom lookAt vup 20.0 aspectRatio aperture distToFocus 265 | 266 | let renderTask (showProgress := false) : IO (Array (Color Float)) := do 267 | let width' := Float.ofNat width 268 | let height' := Float.ofNat height 269 | let mut pixels : Array (Color Float) := Array.empty 270 | for line in [0:height] do 271 | if showProgress then 272 | IO.println s!"line {line+1} of {height}" 273 | let j := height - line - 1 274 | let j' := Float.ofNat j 275 | for i in [0:width] do 276 | let i' := Float.ofNat i 277 | let mut pixelColor := Color.black 278 | for _ in [0:samplesPerPixel] do 279 | let u := (i' + (← IO.randFloat)) / width' 280 | let v := (j' + (← IO.randFloat)) / height' 281 | let ray ← cam.getRay u v 282 | pixelColor := pixelColor + (← rayColor world ray maxDepth Color.white) 283 | pixels := pixels.push pixelColor 284 | return pixels 285 | 286 | IO.println s!"Starting {numThreads} threads." 287 | let mut tasks := Array.empty 288 | for i in [0:numThreads] do 289 | tasks := tasks.push (← IO.asTask (renderTask (i = 0))) 290 | 291 | let mut pixels : Array (Color Float) := Array.mkArray (height * width) Color.black 292 | 293 | for t in tasks do 294 | let pixels' ← IO.ofExcept (← IO.wait t) 295 | let pixels'' := pixels 296 | for i in [0:height*width] do 297 | pixels := pixels.set! i (pixels'[i]! + pixels''[i]!) 298 | 299 | IO.println s!"Writing to {filename}" 300 | 301 | IO.FS.withFile filename IO.FS.Mode.write fun handle => do 302 | handle.putStrLn "P3" 303 | handle.putStrLn s!"{width} {height} 255" 304 | let divisor := Float.ofNat (samplesPerPixel * numThreads) 305 | for i in [0:height*width] do 306 | handle.writeColor <| pixels[i]! / divisor 307 | 308 | def main : List String → IO Unit 309 | | [] => writeTestImage "out.ppm" 310 | | (x::_) => writeTestImage x 311 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple raytracer in Lean 4 2 | 3 | [Lean 4](https://github.com/leanprover/lean4) is a dependently typed programming language, and 4 | it can be used both as a proof assistant and for practical programs. 5 | 6 | This repository implements the ray tracer described in 7 | [_Ray Tracing in One Weekend_](https://raytracing.github.io/books/RayTracingInOneWeekend.html). 8 | Code for writing PPM files originally came from 9 | [TOTBWF/lean4-raytrace](https://github.com/TOTBWF/lean4-raytrace). 10 | 11 | The raytracer uses `Task`s to render in parallel. Part of a raytracer is using supersampling 12 | to better estimate the amount of light entering each pixel, so it is trivial to parallelize: 13 | the entire image is rendered multiple times, and the results are averaged together. 14 | 15 | ![final test image](https://github.com/kmill/lean4-raytracer/blob/master/output/test13.png?raw=true) 16 | 17 | (8 minutes 10 seconds with 10 threads on an Apple M2 Max. 500x333 pixels, 80 total samples per pixel, max depth 30.) 18 | 19 | ![final test image, higher resolution](https://github.com/kmill/lean4-raytracer/blob/master/output/test13.bigger.png?raw=true) 20 | 21 | (2 hours with 16 threads on an Intel Xeon E5-2665. 800x533 pixels, 480 total samples per pixel, max depth 50.) 22 | 23 | ## Running the code 24 | 25 | Assuming you already have Lean 4 setup, this builds an executable and runs it: 26 | ``` 27 | $ lake build && time lake exe render test.ppm 28 | ``` 29 | The rendering settings are hard-coded in `writeTestImage` in `render.lean`. 30 | 31 | ## C benchmark 32 | 33 | The `c` folder contains an implementation of the raytracer in C, hand translated from Lean using C idioms. 34 | This is not meant to be a fair comparison, and I spent more time thinking about optimizing the C version. 35 | The only purpose here is to get some idea of the relative speed of my Lean code. 36 | 37 | The first test image in C took 5.9 seconds with the same configuration, 38 | and the second test image took 1 minute 19 seconds (but with Apple M2 Max). 39 | -------------------------------------------------------------------------------- /Render/Vec3.lean: -------------------------------------------------------------------------------- 1 | /- 2 | Copyright (c) 2021 Kyle Miller. All rights reserved. 3 | Released under Apache 2.0 license as described in the file LICENSE. 4 | Authors: Kyle Miller 5 | -/ 6 | 7 | structure Vec3 (α : Type _) := 8 | (x y z : α) 9 | 10 | namespace Vec3 11 | 12 | @[inline] def map (f : α → β) (v : Vec3 α) : Vec3 β := ⟨f v.x, f v.y, f v.z⟩ 13 | 14 | @[inline] def map₂ (f : α → β → γ) (v : Vec3 α) (w : Vec3 β) : Vec3 γ := 15 | ⟨f v.x w.x, f v.y w.y, f v.z w.z⟩ 16 | 17 | instance [Add α] : Add (Vec3 α) where 18 | add := map₂ (. + .) 19 | 20 | instance [Sub α] : Sub (Vec3 α) where 21 | sub := map₂ (. - .) 22 | 23 | instance [Mul α] : Mul (Vec3 α) where 24 | mul := map₂ (. * .) 25 | 26 | instance [Div α] : Div (Vec3 α) where 27 | div := map₂ (. / .) 28 | 29 | instance [Neg α] : Neg (Vec3 α) where 30 | neg := map (- .) 31 | 32 | -- Scalar multiplication 33 | @[default_instance] 34 | instance [Mul α] : HMul α (Vec3 α) (Vec3 α) where 35 | hMul c := map (c * .) 36 | 37 | -- Scalar division 38 | @[default_instance] 39 | instance [Div α] : HDiv (Vec3 α) α (Vec3 α) where 40 | hDiv v c := map (. / c) v 41 | 42 | @[inline] def sum [Add α] (v : Vec3 α) : α := v.x + v.y + v.z 43 | 44 | @[inline] def lengthSquared [Add α] [Mul α] (v : Vec3 α) : α := (v * v).sum 45 | 46 | @[inline] def length (v : Vec3 Float) : Float := v.lengthSquared.sqrt 47 | 48 | @[inline] def normalized (v : Vec3 Float) : Vec3 Float := v / v.length 49 | 50 | @[inline] def dot [Add α] [Mul α] (v w : Vec3 α) : α := (v * w).sum 51 | 52 | @[inline] def cross [Sub α] [Mul α] (v w : Vec3 α) : Vec3 α := 53 | ⟨v.y*w.z - v.z*w.y, v.z*w.x - v.x*w.z, v.x*w.y - v.y*w.x⟩ 54 | 55 | /-- Reflect v over plane with normal vector n. -/ 56 | @[inline] def reflect (v n : Vec3 Float) : Vec3 Float := v - 2 * v.dot n * n 57 | 58 | @[inline] def nearZero (v : Vec3 Float) (ε2 : Float := 1e-16) : Bool := 59 | v.lengthSquared < ε2 60 | 61 | end Vec3 62 | 63 | abbrev Color (α : Type _) := Vec3 α 64 | 65 | namespace Color 66 | 67 | def mk (r g b : α) : Color α := ⟨r, g, b⟩ 68 | @[inline] def r (v : Color α) : α := v.x 69 | @[inline] def g (v : Color α) : α := v.y 70 | @[inline] def b (v : Color α) : α := v.z 71 | 72 | @[inline] def white : Color Float := Color.mk 1.0 1.0 1.0 73 | @[inline] def black : Color Float := Color.mk 0.0 0.0 0.0 74 | 75 | instance : Inhabited (Color Float) := ⟨black⟩ 76 | 77 | end Color 78 | -------------------------------------------------------------------------------- /c/Makefile: -------------------------------------------------------------------------------- 1 | render: render.c 2 | gcc -o render render.c -O3 -Wall -Wextra -lm -lpthread 3 | 4 | render.s: render.c 5 | gcc -S render.c -O3 6 | -------------------------------------------------------------------------------- /c/render.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Halts the program, printing an error message. 11 | #define error(...) do { \ 12 | if (errno) { perror("ERROR"); } \ 13 | fprintf(stderr, "ERROR %s:%d: ", __FILE__, __LINE__); \ 14 | fprintf(stderr, __VA_ARGS__); \ 15 | fprintf(stderr, "\n"); \ 16 | raise(SIGABRT); \ 17 | exit(22); \ 18 | } while(0) 19 | 20 | inline double fmin(double x, double y) { return x <= y ? x : y; } 21 | inline double fmax(double x, double y) { return x <= y ? y : x; } 22 | 23 | typedef struct Vec3 { 24 | double x, y, z; 25 | } Vec3; 26 | 27 | Vec3 Vec3_add(Vec3 v, Vec3 w) { return (Vec3){v.x + w.x, v.y + w.y, v.z + w.z}; } 28 | Vec3 Vec3_sub(Vec3 v, Vec3 w) { return (Vec3){v.x - w.x, v.y - w.y, v.z - w.z}; } 29 | Vec3 Vec3_mul(Vec3 v, Vec3 w) { return (Vec3){v.x * w.x, v.y * w.y, v.z * w.z}; } 30 | Vec3 Vec3_div(Vec3 v, Vec3 w) { return (Vec3){v.x / w.x, v.y / w.y, v.z / w.z}; } 31 | Vec3 Vec3_scale(double c, Vec3 w) { return (Vec3){c * w.x, c * w.y, c * w.z}; } 32 | 33 | double Vec3_sum(Vec3 v) { return v.x + v.y + v.z; } 34 | double Vec3_dot(Vec3 v, Vec3 w) { return Vec3_sum(Vec3_mul(v, w)); } 35 | double Vec3_length_squared(Vec3 v) { return Vec3_dot(v, v); } 36 | double Vec3_length(Vec3 v) { return sqrt(Vec3_length_squared(v)); } 37 | Vec3 Vec3_normalized(Vec3 v) { return Vec3_scale(1 / Vec3_length(v), v); } 38 | 39 | Vec3 Vec3_cross(Vec3 v, Vec3 w) { 40 | return (Vec3){v.y*w.z - v.z*w.y, v.z*w.x - v.x*w.z, v.x*w.y - v.y*w.x}; 41 | } 42 | 43 | /* Reflect v over plane with normal (unit) vector n. */ 44 | Vec3 Vec3_reflect(Vec3 v, Vec3 n) { 45 | return Vec3_sub(v, Vec3_scale(2 * Vec3_dot(v, n), n)); 46 | } 47 | 48 | bool Vec3_near_zero(Vec3 v) { return Vec3_length(v) < 1e-8; } 49 | 50 | typedef Vec3 Color; 51 | 52 | const Color Color_black = (Color){0, 0, 0}; 53 | const Color Color_white = (Color){1, 1, 1}; 54 | 55 | /* The random number generator in Lean 4, which appears to be 56 | from ranlib. Barry W. Brown, James Lovato, Kathy Russell , 57 | based on L'Ecuyer and Cote, ACM TOMS 17:98-111 (1991). 58 | 59 | Copying the Lean implementation. */ 60 | 61 | typedef struct Rand { 62 | int32_t s1, s2; 63 | } Rand; 64 | 65 | /* s and q are seeds to the random number generator. */ 66 | void Rand_init(Rand *r, int32_t s, int32_t q) { 67 | r->s1 = (s % 2147483562) + 1; 68 | r->s2 = (q % 2147483398) + 1; 69 | } 70 | 71 | /* Returns a random integer in [1, 2147483562] inclusive. */ 72 | int32_t Rand_next(Rand *r) { 73 | int32_t s1 = r->s1; 74 | int32_t s2 = r->s2; 75 | int32_t k = s1 / 53668; 76 | int32_t s1_ = 40014 * (s1 - k * 53668) - k * 12211; 77 | int32_t s1__ = s1_ < 0 ? s1_ + 2147483563 : s1_; 78 | int32_t k_ = s2 / 52774; 79 | int32_t s2_ = 40692 * (s2 - k_ * 52774) - k_ * 3791; 80 | int32_t s2__ = s2_ < 0 ? s2_ + 2147483399 : s2_; 81 | int32_t z = s1__ - s2__; 82 | int32_t z_ = z < 1 ? z + 2147483562 : z % 2147483562; 83 | r->s1 = s1__; 84 | r->s2 = s2__; 85 | return z_; 86 | } 87 | 88 | /* Split a random number generator, initializing r_new. */ 89 | void Rand_split(Rand *r, Rand *r_new) { 90 | int32_t newS1 = r->s1 == 2147483562 ? 1 : r->s1 + 1; 91 | int32_t newS2 = r->s2 == 1 ? 2147483398 : r->s2 - 1; 92 | Rand_next(r); 93 | r_new->s1 = r->s1; 94 | r_new->s2 = newS2; 95 | r->s1 = newS1; 96 | } 97 | 98 | /* Uniform at random double in range [0, 1). */ 99 | double Rand_unif(Rand *r) { 100 | int32_t i = Rand_next(r); 101 | return (double)(i - 1) / 2147483562; 102 | } 103 | 104 | /* Uniform at random double in range [lo, hi). */ 105 | double Rand_unif_range(Rand *r, double lo, double hi) { 106 | return (hi - lo) * Rand_unif(r) + lo; 107 | } 108 | 109 | Vec3 Rand_Vec3_range(Rand *r, double lo, double hi) { 110 | double x, y, z; 111 | x = Rand_unif_range(r, lo, hi); 112 | y = Rand_unif_range(r, lo, hi); 113 | z = Rand_unif_range(r, lo, hi); 114 | return (Vec3){x, y, z}; 115 | } 116 | 117 | /* Gives a vector with length less than 1 uniformly at random. */ 118 | Vec3 Rand_Vec3_in_unit_sphere(Rand *r) { 119 | for (int i = 0; i < 100; i++) { // 7e-33 probability of failure 120 | Vec3 v = Rand_Vec3_range(r, -1, 1); 121 | if (Vec3_length_squared(v) < 1.0) { 122 | return v; 123 | } 124 | } 125 | return (Vec3){1, 0, 0}; 126 | } 127 | 128 | /* Gives a vector in the XY unit disk with length less than 1 uniformly at random. */ 129 | Vec3 Rand_Vec3_in_unit_disk(Rand *r) { 130 | double x, y; 131 | for (int i = 0; i < 100; i++) { // 2e-67 probability of failure 132 | x = Rand_unif_range(r, -1, 1); 133 | y = Rand_unif_range(r, -1, 1); 134 | Vec3 v = (Vec3){x, y, 0}; 135 | if (Vec3_length_squared(v) < 1.0) { 136 | return v; 137 | } 138 | } 139 | return (Vec3){0, 0, 0}; 140 | } 141 | 142 | typedef struct Ray { 143 | Vec3 origin, dir; 144 | } Ray; 145 | 146 | Vec3 Ray_at(Ray const *r, double t) { 147 | return Vec3_add(r->origin, Vec3_scale(t, r->dir)); 148 | } 149 | 150 | typedef struct Camera { 151 | Vec3 origin, lower_left_corner, horizontal, vertical; 152 | Vec3 u, v, w; /* right, up, back */ 153 | double lens_radius; 154 | } Camera; 155 | 156 | void Camera_default(Camera *cam, 157 | Vec3 look_from, Vec3 look_at, Vec3 vup, 158 | double vfov, 159 | double aspect_ratio, 160 | double aperture, 161 | double focus_dist) { 162 | double theta = vfov / 180 * M_PI; 163 | double h = tan(theta / 2); 164 | double viewport_height = 2.0 * h; 165 | double viewport_width = aspect_ratio * viewport_height; 166 | 167 | Vec3 w = Vec3_normalized(Vec3_sub(look_from, look_at)); 168 | Vec3 u = Vec3_normalized(Vec3_cross(vup, w)); 169 | Vec3 v = Vec3_cross(w, u); 170 | 171 | cam->origin = look_from; 172 | cam->horizontal = Vec3_scale(focus_dist * viewport_width, u); 173 | cam->vertical = Vec3_scale(focus_dist * viewport_height, v); 174 | cam->lower_left_corner = Vec3_sub(cam->origin, Vec3_add(Vec3_add(Vec3_scale(0.5, cam->horizontal), 175 | Vec3_scale(0.5, cam->vertical)), 176 | Vec3_scale(focus_dist, w))); 177 | cam->u = u; 178 | cam->v = v; 179 | cam->w = w; 180 | cam->lens_radius = aperture / 2; 181 | } 182 | 183 | void Camera_get_ray(Camera const *cam, Rand *rand, double s, double t, Ray *ray) { 184 | Vec3 rd = Vec3_scale(cam->lens_radius, Rand_Vec3_in_unit_disk(rand)); 185 | Vec3 offset = Vec3_add(Vec3_scale(rd.x, cam->u), Vec3_scale(rd.y, cam->v)); 186 | ray->origin = Vec3_add(cam->origin, offset); 187 | ray->dir = Vec3_sub(Vec3_add(cam->lower_left_corner, 188 | Vec3_add(Vec3_scale(s, cam->horizontal), 189 | Vec3_scale(t, cam->vertical))), 190 | ray->origin); 191 | } 192 | 193 | typedef struct HitRecord { 194 | Vec3 p; 195 | double t; 196 | Vec3 normal; 197 | bool front_face; 198 | } HitRecord; 199 | 200 | /* Given a HitRecord with p and t set, set normal and front_face */ 201 | void HitRecord_set_normal(HitRecord *hit, Ray const *ray, Vec3 outward_normal) { 202 | hit->front_face = Vec3_dot(ray->dir, outward_normal) < 0.0; 203 | hit->normal = hit->front_face ? outward_normal : Vec3_scale(-1, outward_normal); 204 | } 205 | 206 | void HitRecord_init_from(HitRecord *dest, HitRecord const *src) { 207 | dest->p = src->p; 208 | dest->t = src->t; 209 | dest->normal = src->normal; 210 | dest->front_face = src->front_face; 211 | } 212 | 213 | enum MaterialResponse_type { ABSORB, SCATTER }; 214 | 215 | typedef struct MaterialResponse { 216 | enum MaterialResponse_type type; 217 | union { 218 | struct {} absorb; 219 | struct { 220 | Color albedo; 221 | Ray scattered; 222 | } scatter; 223 | }; 224 | } MaterialResponse; 225 | 226 | typedef struct Material_lambertian { 227 | Color albedo; 228 | } Material_lambertian; 229 | 230 | typedef struct Material_metal { 231 | Color albedo; 232 | double fuzz; 233 | } Material_metal; 234 | 235 | typedef struct Material_dielectric { 236 | double index_of_refraction; 237 | } Material_dielectric; 238 | 239 | enum Material_type { LAMBERTIAN, METAL, DIELECTRIC }; 240 | 241 | typedef struct Material { 242 | enum Material_type type; 243 | union { 244 | Material_lambertian lambertian; 245 | Material_metal metal; 246 | Material_dielectric dielectric; 247 | }; 248 | } Material; 249 | 250 | void Material_make_lambertian(Material *mat, Color albedo) { 251 | mat->type = LAMBERTIAN; 252 | mat->lambertian.albedo = albedo; 253 | } 254 | void Material_make_metal(Material *mat, Color albedo, double fuzz) { 255 | mat->type = METAL; 256 | mat->metal.albedo = albedo; 257 | mat->metal.fuzz = fuzz; 258 | } 259 | void Material_make_dielectric(Material *mat, double index_of_refraction) { 260 | mat->type = DIELECTRIC; 261 | mat->dielectric.index_of_refraction = index_of_refraction; 262 | } 263 | 264 | void Material_lambertian_scatter(Material_lambertian const *mat, 265 | Rand *rand, Ray const *ray, HitRecord const *hitrec, 266 | MaterialResponse *response) { 267 | (void)ray; 268 | Vec3 scatter_dir = Vec3_add(hitrec->normal, Vec3_normalized(Rand_Vec3_in_unit_sphere(rand))); 269 | if (Vec3_near_zero(scatter_dir)) { 270 | scatter_dir = hitrec->normal; 271 | } 272 | response->type = SCATTER; 273 | response->scatter.albedo = mat->albedo; 274 | response->scatter.scattered.origin = hitrec->p; 275 | response->scatter.scattered.dir = scatter_dir; 276 | } 277 | 278 | void Material_metal_scatter(Material_metal const *mat, 279 | Rand *rand, Ray const *ray, HitRecord const *hitrec, 280 | MaterialResponse *response) { 281 | Vec3 reflected = Vec3_reflect(Vec3_normalized(ray->dir), hitrec->normal); 282 | Vec3 scattered_dir = Vec3_add(reflected, Vec3_scale(mat->fuzz, Rand_Vec3_in_unit_sphere(rand))); 283 | if (Vec3_dot(scattered_dir, hitrec->normal) > 0.0) { 284 | response->type = SCATTER; 285 | response->scatter.albedo = mat->albedo; 286 | response->scatter.scattered.origin = hitrec->p; 287 | response->scatter.scattered.dir = scattered_dir; 288 | } else { 289 | response->type = ABSORB; 290 | } 291 | } 292 | 293 | Vec3 Vec3_refract(Vec3 uv, Vec3 n, double etai_over_etat) { 294 | double cos_theta = fmin(-Vec3_dot(uv, n), 1.0); 295 | Vec3 r_out_perp = Vec3_scale(etai_over_etat, 296 | Vec3_add(uv, Vec3_scale(cos_theta, n))); 297 | Vec3 r_out_parallel = Vec3_scale(-sqrt(fabs(1.0 - Vec3_length_squared(r_out_perp))), 298 | n); 299 | return Vec3_add(r_out_perp, r_out_parallel); 300 | } 301 | 302 | void Material_dielectric_scatter(Material_dielectric const *mat, 303 | Rand *rand, Ray const *ray, HitRecord const *hitrec, 304 | MaterialResponse *response) { 305 | double refraction_ratio = hitrec->front_face ? 1.0 / mat->index_of_refraction : mat->index_of_refraction; 306 | Vec3 unit_dir = Vec3_normalized(ray->dir); 307 | double cos_theta = fmin(-Vec3_dot(unit_dir, hitrec->normal), 1.0); 308 | double sin_theta = sqrt(1.0 - cos_theta * cos_theta); 309 | bool cannot_refract = refraction_ratio * sin_theta > 1.0; 310 | 311 | /* Schlick's approximation */ 312 | double r0_ = (1 - refraction_ratio) / (1 + refraction_ratio); 313 | double r0 = r0_ * r0_; 314 | double reflectance = r0 + (1 - r0) * pow(1 - cos_theta, 5); 315 | 316 | Vec3 direction; 317 | if (cannot_refract || reflectance > Rand_unif(rand)) { 318 | direction = Vec3_reflect(unit_dir, hitrec->normal); 319 | } else { 320 | direction = Vec3_refract(unit_dir, hitrec->normal, refraction_ratio); 321 | } 322 | response->type = SCATTER; 323 | response->scatter.albedo = Color_white; 324 | response->scatter.scattered.origin = hitrec->p; 325 | response->scatter.scattered.dir = direction; 326 | } 327 | 328 | void Material_scatter(Material const *mat, 329 | Rand *rand, Ray const *ray, HitRecord const *hitrec, 330 | MaterialResponse *response) { 331 | switch (mat->type) { 332 | case LAMBERTIAN: 333 | Material_lambertian_scatter(&mat->lambertian, rand, ray, hitrec, response); 334 | return; 335 | case METAL: 336 | Material_metal_scatter(&mat->metal, rand, ray, hitrec, response); 337 | return; 338 | case DIELECTRIC: 339 | Material_dielectric_scatter(&mat->dielectric, rand, ray, hitrec, response); 340 | return; 341 | default: 342 | error("Unknown material type"); 343 | } 344 | } 345 | 346 | enum Hittable_type { SPHERE }; 347 | 348 | typedef struct Hittable_sphere { 349 | Vec3 center; 350 | double radius; 351 | Material const *mat; 352 | } Hittable_sphere; 353 | 354 | typedef struct Hittable { 355 | enum Hittable_type type; 356 | union { 357 | Hittable_sphere sphere; 358 | }; 359 | } Hittable; 360 | 361 | bool Hittable_sphere_hit(Hittable_sphere const *sphere, 362 | Ray const *ray, double tmin, double tmax, 363 | HitRecord *hitrec, Material const **mat) { 364 | Vec3 oc = Vec3_sub(ray->origin, sphere->center); 365 | double a = Vec3_length_squared(ray->dir); 366 | double halfb = Vec3_dot(oc, ray->dir); 367 | double c = Vec3_length_squared(oc) - sphere->radius * sphere->radius; 368 | double discr = halfb * halfb - a * c; 369 | if (discr < 0.0) { 370 | return false; 371 | } 372 | double sqrtd = sqrt(discr); 373 | /* Find the nearest root that lies in the acceptable range */ 374 | double root = (-halfb - sqrtd) / a; 375 | if (root < tmin || tmax < root) { 376 | root = (-halfb + sqrtd) / a; 377 | if (root < tmin || tmax < root) { 378 | return false; 379 | } 380 | } 381 | double t = root; 382 | Vec3 p = Ray_at(ray, t); 383 | Vec3 outward_normal = Vec3_scale(1/sphere->radius, Vec3_sub(p, sphere->center)); 384 | *mat = sphere->mat; 385 | hitrec->p = p; 386 | hitrec->t = t; 387 | HitRecord_set_normal(hitrec, ray, outward_normal); 388 | return true; 389 | } 390 | 391 | void make_sphere(Hittable *obj, Vec3 center, double radius, Material const *mat) { 392 | obj->type = SPHERE; 393 | obj->sphere.center = center; 394 | obj->sphere.radius = radius; 395 | obj->sphere.mat = mat; 396 | } 397 | 398 | bool Hittable_hit(Hittable const *obj, 399 | Ray const *ray, double tmin, double tmax, 400 | HitRecord *hitrec, Material const **mat) { 401 | switch (obj->type) { 402 | case SPHERE: 403 | return Hittable_sphere_hit(&obj->sphere, ray, tmin, tmax, hitrec, mat); 404 | default: 405 | error("Unknown hittable type"); 406 | } 407 | } 408 | 409 | bool hit_list(int nobj, Hittable const *obj_list, 410 | Ray const *ray, double tmin, double tmax, 411 | HitRecord *hitrec, Material const **mat) { 412 | hitrec->p = ray->origin; 413 | hitrec->t = tmax; 414 | HitRecord curr_hitrec; 415 | Material const *curr_mat = NULL; 416 | bool did_hit = false; 417 | for (int i = 0; i < nobj; i++) { 418 | if (Hittable_hit(&obj_list[i], ray, tmin, hitrec->t, &curr_hitrec, &curr_mat)) { 419 | did_hit = true; 420 | HitRecord_init_from(hitrec, &curr_hitrec); 421 | *mat = curr_mat; 422 | } 423 | } 424 | return did_hit; 425 | } 426 | 427 | Vec3 ray_color(int nobj, Hittable const *obj_list, 428 | Ray const *ray, Rand *rand, 429 | int depth) { 430 | if (depth <= 0) { 431 | return Color_black; 432 | } 433 | HitRecord hitrec; 434 | Material const *mat = NULL; 435 | if (hit_list(nobj, obj_list, ray, 0.001, INFINITY, &hitrec, &mat)) { 436 | MaterialResponse response; 437 | Material_scatter(mat, rand, ray, &hitrec, &response); 438 | switch (response.type) { 439 | case ABSORB: 440 | return Color_black; 441 | case SCATTER: 442 | return Vec3_mul(response.scatter.albedo, 443 | ray_color(nobj, obj_list, &response.scatter.scattered, rand, depth - 1)); 444 | default: 445 | error("Unknown material response type"); 446 | } 447 | } else { 448 | Vec3 unit = Vec3_normalized(ray->dir); 449 | double t = 0.5 * (unit.y + 1.0); 450 | return Vec3_add(Vec3_scale(1.0 - t, Color_white), 451 | Vec3_scale(t, (Color){0.5, 0.7, 1.0})); 452 | } 453 | } 454 | 455 | /* Takes a floating-point number with [0,1) mapped to [0,256). Clamps result to 0-255. */ 456 | uint8_t clamp_to_uint8_t(double f) { 457 | int i = (int)(256*f); 458 | if (i < 0) { 459 | return 0; 460 | } else if (i > 255) { 461 | return 255; 462 | } else { 463 | return i; 464 | } 465 | } 466 | 467 | /* Write the color with x^(1/2) gamma encoding */ 468 | void write_color(FILE *f, Color c) { 469 | fprintf(f, "%d %d %d\n", 470 | clamp_to_uint8_t(sqrt(c.x)), 471 | clamp_to_uint8_t(sqrt(c.y)), 472 | clamp_to_uint8_t(sqrt(c.z))); 473 | } 474 | 475 | void random_scene(Rand *rand, int *nobj, Hittable **world) { 476 | Material *mats = malloc(1000 * sizeof(Material)); 477 | int n_mat = 0; 478 | Hittable *objs = malloc(1000 * sizeof(Hittable)); 479 | int n_obj = 0; 480 | 481 | Material *mat_glass = &mats[n_mat++]; 482 | Material_make_dielectric(mat_glass, 1.5); 483 | 484 | // Ground 485 | Material *ground_mat = &mats[n_mat++]; 486 | Material_make_lambertian(ground_mat, (Color){0.5, 0.5, 0.5}); 487 | make_sphere(&objs[n_obj++], (Vec3){0, -1000, 0}, 1000, ground_mat); 488 | 489 | for (int a = -11; a < 11; a++) { 490 | for (int b = -11; b < 11; b++) { 491 | Vec3 center = (Vec3) {a + 0.9 * Rand_unif(rand), 0.2, b + 0.9 * Rand_unif(rand)}; 492 | if (Vec3_length(Vec3_sub(center, (Vec3){4, 0.2, 0})) > 0.9) { 493 | double choose_mat = Rand_unif(rand); 494 | if (choose_mat < 0.9) { 495 | Color albedo = Vec3_mul(Rand_Vec3_range(rand, 0, 1), Rand_Vec3_range(rand, 0, 1)); 496 | Material *mat = &mats[n_mat++]; 497 | Material_make_lambertian(mat, albedo); 498 | make_sphere(&objs[n_obj++], center, 0.2, mat); 499 | } else if (choose_mat < 0.95) { 500 | Color albedo = Rand_Vec3_range(rand, 0.5, 1); 501 | double fuzz = Rand_unif_range(rand, 0, 0.5); 502 | Material *mat = &mats[n_mat++]; 503 | Material_make_metal(mat, albedo, fuzz); 504 | make_sphere(&objs[n_obj++], center, 0.2, mat); 505 | } else { 506 | make_sphere(&objs[n_obj++], center, 0.2, mat_glass); 507 | } 508 | } 509 | } 510 | } 511 | 512 | // 3 big spheres 513 | make_sphere(&objs[n_obj++], (Vec3){0, 1, 0}, 1, mat_glass); 514 | 515 | Material *mat_lambert = &mats[n_mat++]; 516 | Material_make_lambertian(mat_lambert, (Color){0.4, 0.2, 0.1}); 517 | make_sphere(&objs[n_obj++], (Vec3){-4, 1, 0}, 1, mat_lambert); 518 | 519 | Material *mat_metal = &mats[n_mat++]; 520 | Material_make_metal(mat_metal, (Color){0.7, 0.6, 0.5}, 0); 521 | make_sphere(&objs[n_obj++], (Vec3){4, 1, 0}, 1, mat_metal); 522 | 523 | printf("%d objects, %d materials\n", n_obj, n_mat); 524 | 525 | *nobj = n_obj; 526 | *world = objs; 527 | } 528 | 529 | struct RenderData { 530 | int width, height; 531 | int samples_per_pixel; 532 | int max_depth; 533 | Color *pixels; 534 | Camera *cam; 535 | Hittable *world; 536 | int nobj; 537 | Rand rand; 538 | bool show_progress; 539 | }; 540 | 541 | void *render_task(void *arg) { 542 | struct RenderData const *rd = arg; 543 | Rand rand = rd->rand; 544 | 545 | for (int line = 0; line < rd->height; line++) { 546 | if (rd->show_progress) { 547 | printf("line %d of %d\n", line+1, rd->height); 548 | } 549 | int j = rd->height - line - 1; 550 | for (int i = 0; i < rd->width; i++) { 551 | Color pixel_color = Color_black; 552 | for (int s = 0; s < rd->samples_per_pixel; s++) { 553 | double u = (i + Rand_unif(&rand)) / rd->width; 554 | double v = (j + Rand_unif(&rand)) / rd->height; 555 | Ray ray; 556 | Camera_get_ray(rd->cam, &rand, u, v, &ray); 557 | Color rc = ray_color(rd->nobj, rd->world, &ray, &rand, rd->max_depth); 558 | //Color rc = (Color){(double)i/width, (double)j/height, 0.25}; 559 | pixel_color = Vec3_add(pixel_color, rc); 560 | } 561 | rd->pixels[line * rd->width + i] = pixel_color; 562 | } 563 | } 564 | 565 | return arg; 566 | } 567 | 568 | void write_test_image(const char *filename) { 569 | double aspect_ratio = 3.0 / 2.0; 570 | int width = 500; 571 | int height = (int)(width / aspect_ratio); 572 | int samples_per_pixel = 8; 573 | int max_depth = 30; 574 | int num_threads = 10; 575 | 576 | printf("%d threads, %dx%d pixels, %d total samples per pixel, max depth %d.\n", 577 | num_threads, width, height, samples_per_pixel*num_threads, max_depth); 578 | 579 | Rand rand; 580 | Rand_init(&rand, 0, 0); 581 | 582 | Hittable *world; 583 | int nobj; 584 | random_scene(&rand, &nobj, &world); 585 | 586 | Vec3 look_from = (Vec3){13, 2, 3}; 587 | Vec3 look_at = (Vec3){0, 0, 0}; 588 | Vec3 vup = (Vec3){0, 1, 0}; 589 | double dist_to_focus = 10; 590 | double aperture = 0.1; 591 | Camera cam; 592 | Camera_default(&cam, 593 | look_from, look_at, vup, 594 | 20.0, aspect_ratio, aperture, dist_to_focus); 595 | 596 | printf("Starting %d threads.\n", num_threads); 597 | 598 | pthread_t threads[num_threads]; 599 | 600 | for (int i = 0; i < num_threads; i++) { 601 | struct RenderData *rd = malloc(sizeof(struct RenderData)); 602 | rd->width = width; 603 | rd->height = height; 604 | rd->samples_per_pixel = samples_per_pixel; 605 | rd->max_depth = max_depth; 606 | rd->pixels = malloc(height * width * sizeof(Color)); 607 | rd->cam = &cam; 608 | rd->world = world; 609 | rd->nobj = nobj; 610 | Rand_split(&rand, &rd->rand); 611 | rd->show_progress = (i == 0); 612 | pthread_create(&threads[i], NULL, &render_task, (void*)rd); 613 | } 614 | 615 | Color *pixels = malloc(height * width * sizeof(Color)); 616 | for (int i = 0; i < height * width; i++) { 617 | pixels[i] = Color_black; 618 | } 619 | 620 | for (int i = 0; i < num_threads; i++) { 621 | struct RenderData *rd = NULL; 622 | pthread_join(threads[i], (void **)&rd); 623 | Color *new_pixels = rd->pixels; 624 | for (int i = 0; i < height * width; i++) { 625 | pixels[i] = Vec3_add(pixels[i], new_pixels[i]); 626 | } 627 | free(new_pixels); 628 | free(rd); 629 | } 630 | 631 | printf("Writing to %s\n", filename); 632 | FILE *f = fopen(filename, "w"); 633 | fprintf(f, "P3\n%d %d 255\n", width, height); 634 | for (int i = 0; i < width * height; i++) { 635 | write_color(f, Vec3_scale(1.0 / (samples_per_pixel * num_threads), pixels[i])); 636 | } 637 | fclose(f); 638 | free(pixels); 639 | } 640 | 641 | int main(int argc, char **argv) { 642 | char *filename = "out.ppm"; 643 | if (argc > 1) { 644 | filename = argv[1]; 645 | } 646 | write_test_image(filename); 647 | return 0; 648 | } 649 | -------------------------------------------------------------------------------- /lake-manifest.json: -------------------------------------------------------------------------------- 1 | {"version": 7, 2 | "packagesDir": ".lake/packages", 3 | "packages": [], 4 | "name": "render", 5 | "lakeDir": ".lake"} 6 | -------------------------------------------------------------------------------- /lakefile.lean: -------------------------------------------------------------------------------- 1 | import Lake 2 | 3 | open Lake DSL 4 | 5 | package render { 6 | -- add package configuration options here 7 | } 8 | 9 | lean_lib Render { 10 | -- add library configuration options here 11 | } 12 | 13 | @[default_target] 14 | lean_exe render { 15 | root := `Main 16 | } 17 | -------------------------------------------------------------------------------- /lean-toolchain: -------------------------------------------------------------------------------- 1 | leanprover/lean4:v4.8.0-rc1 2 | -------------------------------------------------------------------------------- /old/Algebra.lean: -------------------------------------------------------------------------------- 1 | class Zero (α : Type _) where 2 | zero : α 3 | 4 | class One (α : Type _) where 5 | one : α 6 | 7 | instance [OfNat α 0] : Zero α where 8 | zero := 0 9 | 10 | instance [OfNat α 1] : One α where 11 | one := 1 12 | 13 | instance [Zero α] : OfNat α (natLit! 0) where 14 | ofNat := Zero.zero 15 | instance [One α] : OfNat α (natLit! 1) where 16 | ofNat := One.one -------------------------------------------------------------------------------- /old/ArrayExtra.lean: -------------------------------------------------------------------------------- 1 | namespace Array 2 | 3 | -- TODO add unsafe version 4 | 5 | def foldlM₂ {m : Type _ → Type _} [Monad m] (f : γ → α → β → m γ) (init : γ) (as : Array α) (bs : Array β) (h' : as.size = bs.size) (start := 0) (stop := as.size) : m γ := 6 | let fold (stop : Nat) (h : stop ≤ as.size) := 7 | let rec loop (i : Nat) (j : Nat) (c : γ) : m γ := do 8 | if hlt : j < stop then 9 | match i with 10 | | 0 => pure c 11 | | i'+1 => 12 | loop i' (j+1) (← f c (as.get ⟨j, Nat.ltOfLtOfLe hlt h⟩) (bs.get ⟨j, by { rw ←h'; exact Nat.ltOfLtOfLe hlt h }⟩)) 13 | else 14 | pure c 15 | loop (stop - start) start init 16 | if h : stop ≤ as.size then 17 | fold stop h 18 | else 19 | fold as.size (Nat.leRefl _) 20 | 21 | def mapM₂ {m : Type _ → Type _} [Monad m] (f : α → β → m γ) (as : Array α) (bs : Array β) (h : as.size = bs.size) : m (Array γ) := 22 | foldlM₂ (fun cs a b => do let c ← f a b; pure (cs.push c)) (mkEmpty as.size) as bs h 23 | 24 | @[inline] 25 | def map₂ (f : α → β → γ) (as : Array α) (bs : Array β) (h : as.size = bs.size): Array γ := 26 | Id.run <| mapM₂ f as bs h 27 | 28 | end Array -------------------------------------------------------------------------------- /old/NatExtra.lean: -------------------------------------------------------------------------------- 1 | theorem Nat.zeroDiv {n : Nat} : 0 / n = 0 := by 2 | rw Nat.divDef 3 | have key : ¬ (0 < n ∧ n ≤ 0) by 4 | intro h 5 | have h' := Nat.ltOfLtOfLe h.1 h.2 6 | exact Nat.notLtZero 0 h' 7 | rw ifNeg key 8 | 9 | theorem Nat.div_le_div_of_mul_aux (k n m : Nat) : n / k ≤ (n + m) / k := by { 10 | induction m with 11 | | zero => { exact Nat.leRefl _ } 12 | | succ m ih => { exact sorry } 13 | } 14 | 15 | theorem Nat.div_le_div_of_mul {k n m : Nat} (h : n ≤ m) : n / k ≤ m / k := by { 16 | have h' := Nat.div_le_div_of_mul_aux k n (m - n); 17 | exact sorry 18 | } 19 | 20 | theorem Nat.div_le_of_le_mul {k n m : Nat} (h : k ≤ n * m) : k / m ≤ n := by { 21 | induction k with 22 | | zero => { rw Nat.zeroDiv; exact Nat.zeroLe _ } 23 | | succ k ih => { 24 | have hk : k ≤ n * m := Nat.leTrans (Nat.leSucc _) h; 25 | have ih' := ih (Nat.leTrans (Nat.leSucc _) h); 26 | exact sorry 27 | } 28 | } 29 | 30 | theorem Nat.div_lt_of_lt_mul {k n m : Nat} (h : k < n * m) : k / m < n := by { 31 | exact sorry 32 | } -------------------------------------------------------------------------------- /old/vec.lean: -------------------------------------------------------------------------------- 1 | import render.ArrayExtra 2 | import render.NatExtra 3 | import render.Algebra 4 | 5 | def Function.leftInverse (g : β → α) (f : α → β) : Prop := ∀ x, g (f x) = x 6 | def Function.rightInverse (g : β → α) (f : α → β) : Prop := Function.leftInverse f g 7 | 8 | structure Equiv (α : Sort u) (β : Sort v) where 9 | toFun : α → β 10 | invFun : β → α 11 | leftInv : Function.leftInverse invFun toFun 12 | rightInv : Function.rightInverse invFun toFun 13 | 14 | infix:25 " ≃ " => Equiv 15 | 16 | def Equiv.symm (f : α ≃ β) : β ≃ α where 17 | toFun := f.invFun 18 | invFun := f.toFun 19 | leftInv := f.rightInv 20 | rightInv := f.leftInv 21 | 22 | /-- An equivalence "is" a function. -/ 23 | instance : CoeFun (α ≃ β) (λ _ => α → β) where 24 | coe := Equiv.toFun 25 | 26 | /-- A finite type with a specific enumeration. -/ 27 | class Enumerable (α : Type u) where 28 | card : Nat 29 | enum : α ≃ Fin card 30 | 31 | instance : Enumerable (Fin n) where 32 | card := n 33 | enum := { toFun := id 34 | invFun := id 35 | leftInv := λ _ => rfl 36 | rightInv := λ _ => rfl } 37 | 38 | section CartesianProduct 39 | 40 | theorem cartEncodeProp {i j m n : Nat} (hi : i < m) (hj : j < n) : i * n + j < m * n := by 41 | cases m with 42 | | zero => apply False.elim; exact Nat.notLtZero _ hi 43 | | succ m => { 44 | rw Nat.succMul; 45 | exact Nat.ltOfLeOfLt (Nat.addLeAddRight (Nat.mulLeMulRight _ (Nat.leOfLtSucc hi)) _) (Nat.addLtAddLeft hj _) 46 | } 47 | 48 | def cartDecode {n m : Nat} : Fin (n * m) → Fin n × Fin m 49 | | ⟨k, h⟩ => 50 | (⟨k / m, Nat.div_lt_of_lt_mul h⟩, 51 | ⟨k % m, Nat.modLt _ (by { cases m; apply False.elim; rw Nat.mulZero at h; exact Nat.notLtZero _ h; apply Nat.succPos})⟩) 52 | 53 | instance [Enumerable α] [Enumerable β] : Enumerable (α × β) where 54 | card := Enumerable.card α * Enumerable.card β 55 | enum := { 56 | toFun := λ (a, b) => 57 | let ⟨i, hi⟩ := Enumerable.enum a 58 | let ⟨j, hj⟩ := Enumerable.enum b 59 | ⟨i * Enumerable.card β + j, cartEncodeProp hi hj⟩ 60 | invFun := λ n => 61 | let (i, j) := cartDecode n 62 | (Enumerable.enum.symm i, Enumerable.enum.symm j) 63 | leftInv := sorry 64 | rightInv := sorry 65 | } 66 | 67 | end CartesianProduct 68 | 69 | 70 | 71 | instance : Enumerable Bool where 72 | card := 2 73 | enum := { 74 | toFun := fun 75 | | false => 0 76 | | true => 1 77 | invFun := fun 78 | | ⟨0, _⟩ => false 79 | | ⟨1, _⟩ => true 80 | | ⟨n+2, h⟩ => False.elim (Nat.notSuccLeZero _ (Nat.leOfSuccLeSucc (Nat.leOfSuccLeSucc h))) 81 | leftInv := by 82 | intro 83 | | true => rfl 84 | | false => rfl 85 | rightInv := by 86 | intro 87 | | ⟨0, _⟩ => rfl 88 | | ⟨1, _⟩ => rfl 89 | | ⟨n+2, h⟩ => exact False.elim (Nat.notSuccLeZero _ (Nat.leOfSuccLeSucc (Nat.leOfSuccLeSucc h))) 90 | } 91 | 92 | instance : Enumerable Empty where 93 | card := 0 94 | enum := { 95 | toFun := fun t => nomatch t 96 | invFun := fun 97 | | ⟨n, h⟩ => False.elim (Nat.notSuccLeZero _ h) 98 | leftInv := fun t => nomatch t 99 | rightInv := fun t => nomatch t 100 | } 101 | 102 | def Enumerable.listOf.aux (α : Type u) [Enumerable α] : Nat -> Nat -> List α 103 | | lo, 0 => [] 104 | | lo, (left+1) => 105 | if h : lo < Enumerable.card α then 106 | Enumerable.enum.symm ⟨lo, h⟩ :: aux α (lo + 1) left 107 | else [] -- Shouldn't happen, but makes the definition easy. 108 | 109 | /-- Create a list of every term in the Enumerable type in order. -/ 110 | def Enumerable.listOf (α : Type u) [Enumerable α] : List α := 111 | Enumerable.listOf.aux α 0 (Enumerable.card α) 112 | 113 | structure Vec (ι : Type u) [Enumerable ι] (α : Type v) where 114 | array : Array α 115 | hasSize : array.size = Enumerable.card ι 116 | 117 | namespace Vec 118 | variable {ι : Type _} [Enumerable ι] 119 | 120 | def fill (a : α) : Vec ι α where 121 | array := Array.mkArray (Enumerable.card ι) a 122 | hasSize := Array.sizeMkArrayEq .. 123 | 124 | def empty [Inhabited α] : Vec ι α := 125 | fill Inhabited.default 126 | 127 | @[inline] def translateIdx (v : Vec ι α) (i : ι) : Fin v.array.size := 128 | let ⟨n, h⟩ := Enumerable.enum i 129 | ⟨n, by rw v.hasSize; exact h⟩ 130 | 131 | /-- Get the value associated to a particular index. -/ 132 | @[inline] def get (v : Vec ι α) (i : ι) : α := 133 | v.array.get (v.translateIdx i) 134 | 135 | /-- support `v[i]` notation. -/ 136 | @[inline] def getOp (self : Vec ι α) (idx : ι) : α := self.get idx 137 | 138 | /-- Set the value associated to a particular index. -/ 139 | @[inline] def set (v : Vec ι α) (i : ι) (a : α) : Vec ι α where 140 | array := v.array.set (v.translateIdx i) a 141 | hasSize := by rw [Array.sizeSetEq, v.hasSize] 142 | 143 | @[inline] def forIn {α : Type u} {β : Type v} {m : Type v → Type w} [Monad m] (as : Vec ι α) (b : β) (f : α → β → m (ForInStep β)) : m β := 144 | as.array.forIn b f 145 | 146 | def pure (x : α) : Vec ι α := fill x 147 | 148 | @[inline] def of (f : ι → α) : Vec ι α where 149 | array := do 150 | let mut a := Array.empty 151 | for i in Enumerable.listOf ι do 152 | a := a.push $ f i 153 | return a 154 | hasSize := sorry -- need to define differently to be able to easily prove this 155 | 156 | macro "vec" xs:Lean.explicitBinders " => " b:term : term => Lean.expandExplicitBinders `Vec.of xs b 157 | 158 | def reindex (v : Vec ι α) (ι' : Type _) [Enumerable ι'] (h : Enumerable.card ι = Enumerable.card ι') : Vec ι' α where 159 | array := v.array 160 | hasSize := by rw [←h, v.hasSize] 161 | 162 | def bind [Enumerable κ] (f : α → Vec κ β) (v : Vec ι α) : Vec (ι × κ) β where 163 | array := Id.run <| Array.foldlM (λ w a => w ++ (f a).array) Array.empty v.array 164 | hasSize := sorry 165 | 166 | @[inline] 167 | def map (v : Vec ι α) (f : α → β) : Vec ι β where 168 | array := v.array.map f 169 | hasSize := sorry 170 | 171 | @[inline] 172 | def map₂ (v : Vec ι α) (w : Vec ι β) (f : α → β → γ) : Vec ι γ where 173 | array := Array.map₂ f v.array w.array (by { rw [v.hasSize, ← w.hasSize] }) 174 | hasSize := sorry 175 | 176 | instance [Enumerable ι] [Add α] : Add (Vec ι α) where 177 | add v w := Vec.map₂ v w (. + .) 178 | instance [Enumerable ι] [Sub α] : Sub (Vec ι α) where 179 | sub v w := Vec.map₂ v w (. - .) 180 | instance [Enumerable ι] [Mul α] : Mul (Vec ι α) where 181 | mul v w := Vec.map₂ v w (. * .) 182 | instance [Enumerable ι] [Div α] : Div (Vec ι α) where 183 | div v w := Vec.map₂ v w (. / .) 184 | instance [Enumerable ι] [Neg α] : Neg (Vec ι α) where 185 | neg v := Vec.map v (- .) 186 | 187 | -- Scalar multiplication 188 | instance [Enumerable ι] [Mul α] : HMul α (Vec ι α) (Vec ι α) where 189 | hMul c v := v.map (c * .) 190 | -- Scalar division 191 | instance [Enumerable ι] [Div α] : HDiv (Vec ι α) α (Vec ι α) where 192 | hDiv v c := v.map (. / c) 193 | -- Zero vector 194 | instance [Enumerable ι] [Zero α] : Zero (Vec ι α) where 195 | zero := vec i => 0 196 | 197 | def lengthSquared [Add α] [Mul α] [Zero α] (v : Vec ι α) : α := 198 | Id.run <| v.array.foldlM (λ x a => x + a * a) 0 199 | 200 | def length (v : Vec ι Float) : Float := Float.sqrt <| v.lengthSquared 201 | 202 | def normalized (v : Vec ι Float) : Vec ι Float := v / v.length 203 | 204 | def dot [Add α] [Mul α] [Zero α] (v w : Vec ι α) : α := 205 | Id.run <| Array.foldlM₂ (λ x a b => x + a * b) 0 v.array w.array (by { rw [v.hasSize, ← w.hasSize] }) 206 | 207 | end Vec 208 | 209 | theorem List.toArraySizeEq (x : List α) : x.toArray.size = x.length := sorry 210 | 211 | def List.toDenseVec (x : List α) : Vec (Fin x.length) α where 212 | array := x.toArray 213 | hasSize := List.toArraySizeEq .. 214 | 215 | syntax "![" sepBy(term, ", ") "]" : term 216 | 217 | macro_rules 218 | | `(![ $elems,* ]) => `(List.toDenseVec [ $elems,* ]) 219 | 220 | instance [Repr α] [Enumerable ι] : Repr (Vec ι α) where 221 | reprPrec v _ := 222 | if v.array.size == 0 then 223 | "![]" 224 | else 225 | Std.Format.bracketFill "![" (@Std.Format.joinSep _ ⟨repr⟩ (v.array.toList) ("," ++ Std.Format.line)) "]" 226 | 227 | example : Vec (Fin 3) Nat := ![2,22,222] 228 | 229 | abbrev Vec3 (α : Type _) := Vec (Fin 3) α 230 | 231 | namespace Vec3 232 | 233 | def mk (x y z : α) : Vec3 α where 234 | array := #[x, y, z] 235 | hasSize := rfl 236 | 237 | @[inline] def x (v : Vec3 α) : α := v[⟨0, rfl⟩] 238 | @[inline] def y (v : Vec3 α) : α := v[⟨1, rfl⟩] 239 | @[inline] def z (v : Vec3 α) : α := v[⟨2, rfl⟩] 240 | 241 | def cross [Sub α] [Mul α] (v w : Vec3 α) : Vec3 α := 242 | ![v.y*w.z - v.z*w.y, v.z*w.x - v.x*w.z, v.x*w.y - v.y*w.x] 243 | 244 | end Vec3 245 | 246 | inductive ColorChannel 247 | | R | G | B 248 | 249 | open ColorChannel 250 | 251 | instance : Enumerable ColorChannel where 252 | card := 3 253 | enum := { 254 | toFun := fun 255 | | R => 0 256 | | G => 1 257 | | B => 2 258 | invFun := fun 259 | | ⟨0, _⟩ => R 260 | | ⟨1, _⟩ => G 261 | | ⟨2, _⟩ => B 262 | | ⟨n+3, h⟩ => False.elim (Nat.notSuccLeZero _ (Nat.leOfSuccLeSucc (Nat.leOfSuccLeSucc (Nat.leOfSuccLeSucc h)))) 263 | leftInv := by 264 | intro 265 | | R => rfl 266 | | G => rfl 267 | | B => rfl 268 | rightInv := by 269 | intro 270 | | ⟨0, _⟩ => rfl 271 | | ⟨1, _⟩ => rfl 272 | | ⟨2, _⟩ => rfl 273 | | ⟨n+3, h⟩ => exact False.elim (Nat.notSuccLeZero _ (Nat.leOfSuccLeSucc (Nat.leOfSuccLeSucc (Nat.leOfSuccLeSucc h)))) 274 | } 275 | 276 | abbrev Color α := Vec ColorChannel α 277 | 278 | def Color.mk (r g b : α) : Color α where 279 | array := #[r, g, b] 280 | hasSize := rfl 281 | 282 | namespace Color 283 | 284 | @[inline] def r (v : Color α) : α := v[R] 285 | @[inline] def g (v : Color α) : α := v[G] 286 | @[inline] def b (v : Color α) : α := v[B] 287 | 288 | end Color -------------------------------------------------------------------------------- /output/test13.bigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmill/lean4-raytracer/205143b32c006199c914cf6f340004137aaaa72a/output/test13.bigger.png -------------------------------------------------------------------------------- /output/test13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmill/lean4-raytracer/205143b32c006199c914cf6f340004137aaaa72a/output/test13.png --------------------------------------------------------------------------------