File: raytrace.ml

package info (click to toggle)
js-of-ocaml 5.9.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 32,020 kB
  • sloc: ml: 91,250; javascript: 57,289; ansic: 315; makefile: 271; lisp: 23; sh: 6; perl: 4
file content (513 lines) | stat: -rw-r--r-- 14,572 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
module Color = struct
  type t =
    { red : float
    ; green : float
    ; blue : float
    }

  let make r g b = { red = r; green = g; blue = b }

  (*
  let print ch c =
    let r = truncate (c.red *. 255.) in
    let g = truncate (c.green *. 255.) in
    let b = truncate (c.blue *. 255.) in
    Format.fprintf ch "rgb(%d,%d,%d)" r g b
*)

  let limit c =
    { red =
        (let red = c.red in
         if red <= 0. then 0. else if red > 1.0 then 1.0 else red)
    ; green =
        (let green = c.green in
         if green <= 0. then 0. else if green > 1.0 then 1.0 else green)
    ; blue =
        (let blue = c.blue in
         if blue <= 0. then 0. else if blue > 1.0 then 1.0 else blue)
    }

  let add c1 c2 =
    { red = c1.red +. c2.red; green = c1.green +. c2.green; blue = c1.blue +. c2.blue }

  let add_scalar c1 s =
    limit { red = c1.red +. s; green = c1.green +. s; blue = c1.blue +. s }

  let subtract c1 c2 =
    { red = c1.red -. c2.red; green = c1.green -. c2.green; blue = c1.blue -. c2.blue }

  let multiply c1 c2 =
    { red = c1.red *. c2.red; green = c1.green *. c2.green; blue = c1.blue *. c2.blue }

  let multiply_scalar c1 s =
    { red = c1.red *. s; green = c1.green *. s; blue = c1.blue *. s }

  let divide_factor c1 f =
    { red = c1.red /. f; green = c1.green /. f; blue = c1.blue /. f }

  let distance c1 c2 =
    abs_float (c1.red -. c2.red)
    +. abs_float (c1.green -. c2.green)
    +. abs_float (c1.blue -. c2.blue)

  let blend c1 c2 w = add (multiply_scalar c1 (1. -. w)) (multiply_scalar c2 w)

  let brightness c =
    let r = truncate (c.red *. 255.) in
    let g = truncate (c.green *. 255.) in
    let b = truncate (c.blue *. 255.) in
    ((r * 77) + (g * 150) + (b * 29)) lsr 8
end

module Vector = struct
  type t =
    { x : float
    ; mutable y : float
    ; z : float
    }

  let make x y z = { x; y; z }

  (*
  let print ch v = Format.fprintf ch "%f %f %f" v.x v.y v.z
*)

  let magnitude v = sqrt ((v.x *. v.x) +. (v.y *. v.y) +. (v.z *. v.z))

  let normalize v =
    let m = magnitude v in
    { x = v.x /. m; y = v.y /. m; z = v.z /. m }

  let cross v w =
    { x = (v.y *. w.z) -. (v.z *. w.y)
    ; y = (v.z *. w.x) -. (v.x *. w.z)
    ; z = (v.x *. w.y) -. (v.y *. w.x)
    }

  let dot v w = (v.x *. w.x) +. (v.y *. w.y) +. (v.z *. w.z)

  let add v w = { x = v.x +. w.x; y = v.y +. w.y; z = v.z +. w.z }

  let subtract v w = { x = v.x -. w.x; y = v.y -. w.y; z = v.z -. w.z }

  let multiply_vector v w = { x = v.x *. w.x; y = v.y *. w.y; z = v.z *. w.z }

  let multiply_scalar v w = { x = v.x *. w; y = v.y *. w; z = v.z *. w }
end

module Light = struct
  type t =
    { position : Vector.t
    ; color : Color.t
    ; intensity : float
    }

  let make p c i = { position = p; color = c; intensity = i }
end

module Ray = struct
  type t =
    { position : Vector.t
    ; direction : Vector.t
    }

  let make p d = { position = p; direction = d }
end

module Intersection_info = struct
  type 'a t =
    { shape : 'a
    ; distance : float
    ; position : Vector.t
    ; normal : Vector.t
    ; color : Color.t
    }
end

module Camera = struct
  type t =
    { position : Vector.t
    ; look_at : Vector.t
    ; equator : Vector.t
    ; up : Vector.t
    ; screen : Vector.t
    }

  let make pos look_at up =
    { position = pos
    ; look_at
    ; up
    ; equator = Vector.cross (Vector.normalize look_at) up
    ; screen = Vector.add pos look_at
    }

  let get_ray c vx vy =
    let pos =
      Vector.subtract
        c.screen
        (Vector.subtract
           (Vector.multiply_scalar c.equator vx)
           (Vector.multiply_scalar c.up vy))
    in
    pos.Vector.y <- pos.Vector.y *. -1.;
    let dir = Vector.subtract pos c.position in
    Ray.make pos (Vector.normalize dir)
end

module Background = struct
  type t =
    { color : Color.t
    ; ambience : float
    }

  let make c a = { color = c; ambience = a }
end

module Material = struct
  type t =
    { reflection : float
    ; transparency : float
    ; gloss : float
    ; has_texture : bool
    ; get_color : float -> float -> Color.t
    }

  let wrap_up t =
    let t = mod_float t 2.0 in
    if t < -1. then t +. 2.0 else if t >= 1. then t -. 2.0 else t

  let solid color reflection transparency gloss =
    { reflection
    ; transparency
    ; gloss
    ; has_texture = false
    ; get_color = (fun _ _ -> color)
    }

  let chessboard color_even color_odd reflection transparency gloss density =
    { reflection
    ; transparency
    ; gloss
    ; has_texture = true
    ; get_color =
        (fun u v ->
          let t = wrap_up (u *. density) *. wrap_up (v *. density) in
          if t < 0. then color_even else color_odd)
    }
end

module Shape = struct
  type shape =
    | Sphere of Vector.t * float
    | Plane of Vector.t * float

  type t =
    { shape : shape
    ; material : Material.t
    }

  let make shape material = { shape; material }

  let dummy =
    make
      (Sphere (Vector.make 0. 0. 0., 0.))
      (Material.solid (Color.make 0. 0. 0.) 0. 0. 0.)

  let position s =
    match s.shape with
    | Sphere (p, _) -> p
    | Plane (p, _) -> p

  let intersect s ray =
    match s.shape with
    | Sphere (position, radius) ->
        let dst = Vector.subtract ray.Ray.position position in
        let b = Vector.dot dst ray.Ray.direction in
        let c = Vector.dot dst dst -. (radius *. radius) in
        let d = (b *. b) -. c in
        if d > 0.
        then
          let dist = -.b -. sqrt d in
          let pos =
            Vector.add ray.Ray.position (Vector.multiply_scalar ray.Ray.direction dist)
          in
          Some
            { Intersection_info.shape = s
            ; distance = dist
            ; position = pos
            ; normal = Vector.normalize (Vector.subtract pos position)
            ; color = s.material.Material.get_color 0. 0.
            }
        else None
    | Plane (position, d) ->
        let vd = Vector.dot position ray.Ray.direction in
        if vd = 0.
        then None
        else
          let t = -.(Vector.dot position ray.Ray.position +. d) /. vd in
          if t <= 0.
          then None
          else
            let pos =
              Vector.add ray.Ray.position (Vector.multiply_scalar ray.Ray.direction t)
            in
            Some
              { Intersection_info.shape = s
              ; distance = t
              ; position = pos
              ; normal = position
              ; color =
                  (if s.material.Material.has_texture
                   then
                     let vu =
                       Vector.make
                         position.Vector.y
                         position.Vector.z
                         (-.position.Vector.x)
                     in
                     let vv = Vector.cross vu position in
                     let u = Vector.dot pos vu in
                     let v = Vector.dot pos vv in
                     s.material.Material.get_color u v
                   else s.material.Material.get_color 0. 0.)
              }
end

module Scene = struct
  type t =
    { camera : Camera.t
    ; shapes : Shape.t array
    ; lights : Light.t array
    ; background : Background.t
    }

  let make c s l b = { camera = c; shapes = s; lights = l; background = b }
end

module Engine = struct
  type t =
    { pixel_width : int
    ; pixel_height : int
    ; canvas_width : int
    ; canvas_height : int
    ; render_diffuse : bool
    ; render_shadows : bool
    ; render_highlights : bool
    ; render_reflections : bool
    ; ray_depth : int
    }

  let check_number = ref 0

  let get_reflection_ray p n v =
    let c1 = -.Vector.dot n v in
    let r1 = Vector.add (Vector.multiply_scalar n (2. *. c1)) v in
    Ray.make p r1

  let rec ray_trace options info ray scene depth =
    let old_color =
      Color.multiply_scalar
        info.Intersection_info.color
        scene.Scene.background.Background.ambience
    in
    let color = ref old_color in
    let shininess =
      10. ** (info.Intersection_info.shape.Shape.material.Material.gloss +. 1.)
    in
    let lights = scene.Scene.lights in
    for i = 0 to Array.length lights - 1 do
      let light = lights.(i) in
      let v =
        Vector.normalize
          (Vector.subtract light.Light.position info.Intersection_info.position)
      in
      (if options.render_diffuse
       then
         let l = Vector.dot v info.Intersection_info.normal in
         if l > 0.
         then
           color :=
             Color.add
               !color
               (Color.multiply
                  info.Intersection_info.color
                  (Color.multiply_scalar light.Light.color l)));
      (if depth <= options.ray_depth
       then
         if options.render_reflections
            && info.Intersection_info.shape.Shape.material.Material.reflection > 0.
         then
           let reflection_ray =
             get_reflection_ray
               info.Intersection_info.position
               info.Intersection_info.normal
               ray.Ray.direction
           in
           let col =
             match
               test_intersection reflection_ray scene info.Intersection_info.shape
             with
             | Some ({ Intersection_info.distance = d; _ } as info) when d > 0. ->
                 ray_trace options info reflection_ray scene (depth + 1)
             | _ -> scene.Scene.background.Background.color
           in
           color :=
             Color.blend
               !color
               col
               info.Intersection_info.shape.Shape.material.Material.reflection);
      let shadow_info = ref None in
      if options.render_shadows
      then (
        let shadow_ray = Ray.make info.Intersection_info.position v in
        shadow_info := test_intersection shadow_ray scene info.Intersection_info.shape;
        match !shadow_info with
        | Some info ->
            (*XXX This looks wrong! *)
            let va = Color.multiply_scalar !color 0.5 in
            let db =
              0.5
              *. (info.Intersection_info.shape.Shape.material.Material.transparency ** 0.5)
            in
            color := Color.add_scalar va db
        | None -> ());
      if options.render_highlights
         && !shadow_info <> None
         && info.Intersection_info.shape.Shape.material.Material.gloss > 0.
      then
        (*XXX This looks wrong! *)
        let shape_position = Shape.position info.Intersection_info.shape in
        let lv = Vector.normalize (Vector.subtract shape_position light.Light.position) in
        let e =
          Vector.normalize
            (Vector.subtract scene.Scene.camera.Camera.position shape_position)
        in
        let h = Vector.normalize (Vector.subtract e lv) in
        let gloss_weight =
          max (Vector.dot info.Intersection_info.normal h) 0. ** shininess
        in
        color := Color.add (Color.multiply_scalar light.Light.color gloss_weight) !color
    done;
    Color.limit !color

  and test_intersection ray scene exclude =
    let best = ref None in
    let dist = ref 2000. in
    let shapes = scene.Scene.shapes in
    for i = 0 to Array.length shapes - 1 do
      let shape = shapes.(i) in
      if shape != exclude
      then
        match Shape.intersect shape ray with
        | Some { Intersection_info.distance = d; _ } as v when d >= 0. && d < !dist ->
            best := v;
            dist := d
        | _ -> ()
    done;
    !best

  let get_pixel_color options ray scene =
    match test_intersection ray scene Shape.dummy with
    | Some info -> ray_trace options info ray scene 0
    | None -> scene.Scene.background.Background.color

  let set_pixel _options x y color =
    if x == y then check_number := !check_number + Color.brightness color;
    ( (*
    let pxw = options.pixel_width in
    let pxh = options.pixel_height in
    Format.eprintf "%d %d %d %d %d %a@." (x * pxw) (y * pxh) pxw pxh !check_number Color.print color;
*) )

  let render_scene options scene _canvas =
    check_number := 0;
    (*XXX canvas *)
    let canvas_height = options.canvas_height in
    let canvas_width = options.canvas_width in
    for y = 0 to canvas_height - 1 do
      for x = 0 to canvas_width - 1 do
        let yp = (float y /. float canvas_height *. 2.) -. 1. in
        let xp = (float x /. float canvas_width *. 2.) -. 1. in
        let ray = Camera.get_ray scene.Scene.camera xp yp in
        let color = get_pixel_color options ray scene in
        set_pixel options x y color
      done
    done;
    assert (!check_number = 2321)

  let make
      canvas_width
      canvas_height
      pixel_width
      pixel_height
      render_diffuse
      render_shadows
      render_highlights
      render_reflections
      ray_depth =
    { canvas_width = canvas_width / pixel_width
    ; canvas_height = canvas_height / pixel_height
    ; pixel_width
    ; pixel_height
    ; render_diffuse
    ; render_shadows
    ; render_highlights
    ; render_reflections
    ; ray_depth
    }
end

let render_scene () =
  let camera =
    Camera.make
      (Vector.make 0. 0. (-15.))
      (Vector.make (-0.2) 0. 5.)
      (Vector.make 0. 1. 0.)
  in
  let background = Background.make (Color.make 0.5 0.5 0.5) 0.4 in
  let sphere =
    Shape.make
      (Shape.Sphere (Vector.make (-1.5) 1.5 2., 1.5))
      (Material.solid (Color.make 0. 0.5 0.5) 0.3 0. 2.)
  in
  let sphere1 =
    Shape.make
      (Shape.Sphere (Vector.make 1. 0.25 1., 0.5))
      (Material.solid (Color.make 0.9 0.9 0.9) 0.1 0. 1.5)
  in
  let plane =
    Shape.make
      (Shape.Plane (Vector.normalize (Vector.make 0.1 0.9 (-0.5)), 1.2))
      (Material.chessboard (Color.make 1. 1. 1.) (Color.make 0. 0. 0.) 0.2 0. 1.0 0.7)
  in
  let light = Light.make (Vector.make 5. 10. (-1.)) (Color.make 0.8 0.8 0.8) 10. in
  let light1 = Light.make (Vector.make (-3.) 5. (-15.)) (Color.make 0.8 0.8 0.8) 100. in
  let scene =
    Scene.make camera [| plane; sphere; sphere1 |] [| light; light1 |] background
  in
  let image_width = 100 in
  let image_height = 100 in
  let pixel_size = 5, 5 in
  let render_diffuse = true in
  let render_shadows = true in
  let render_highlights = true in
  let render_reflections = true in
  let ray_depth = 2 in
  let engine =
    Engine.make
      image_width
      image_height
      (fst pixel_size)
      (snd pixel_size)
      render_diffuse
      render_shadows
      render_highlights
      render_reflections
      ray_depth
  in
  Engine.render_scene engine scene None

let _ =
  for _ = 0 to 99 do
    render_scene ()
  done