ra 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b56b6d752ce23d6484526702a0702d865bfad49f0209a40a8e597adf7a3c1b13
4
- data.tar.gz: e45c8ddc28b1723f9e2a114386a2a0927026e9cf2cb54a38f438f3583d84710c
3
+ metadata.gz: 96608d9848ea1181d69813cd6a37cfcc5cb0ac04564048ed4b2a9d6f4af27414
4
+ data.tar.gz: a1bfbd9254cc6c56abab623bdf65d9a3282904c4b43039c3609934a2d8d273b1
5
5
  SHA512:
6
- metadata.gz: 0b2723e84ab6a64a4036615d298e733714838a754aecd59e0146eef97bd7b3c01374a3a9b4e145e9bb3ac437fe6499b41d819bf3fb79be92494bd2a2629c6f39
7
- data.tar.gz: 1f74e78ddc9fbd2c61e231601d08519a8279279418ce8df0e8d6528a183412e09bbbfd138cbb3cd15fcb96979cac30b312520b3694b2c1232d394a1598dcfa57
6
+ metadata.gz: b63eba61eb50a16fc55388b409b27dde40e092009f920ffe614fb15e29683ac58b129a2bf3848c0944b220ed2b6aca1cfb434a2441d050a601a801fb60c5e0ae
7
+ data.tar.gz: 004eb7ca2f72a3804f62332f1f909cd2875dbc46928ea6392e22b32a34ae5a3ea55117a6bda5dfd15b84b33d8f1c3d990517b10b1d64ed08497e3fe5b8619750
data/exe/ra CHANGED
@@ -8,8 +8,8 @@ require 'slop'
8
8
  config = Slop.parse(ARGV) do |options|
9
9
  options.banner = 'Usage: ra -w 2560 -h 2048 | convert - sample.avif'
10
10
 
11
- options.integer '-w', '--width', 'width', default: 1280
12
- options.integer '-h', '--height', 'height', default: 1024
11
+ options.integer '-w', '--width', 'width', default: 2560
12
+ options.integer '-h', '--height', 'height', default: 2048
13
13
  options.integer '-fov', 'degrees', default: 60
14
14
 
15
15
  options.on('--help', 'help') do
@@ -26,13 +26,13 @@ end
26
26
  earth = Ra::Pattern::Texture.new(path: File.join(File.dirname(__FILE__), '..', 'textures/earth.avif'))
27
27
 
28
28
  light_l = Ra::Light.new(
29
- position: Vector[+5, +7, -9, Ra::Tuple::POINT],
30
- intensity: Ra::Color.uniform(0.4),
29
+ position: Vector[+5, +3, -9, Ra::Tuple::POINT],
30
+ intensity: Ra::Color.uniform(0.5),
31
31
  )
32
32
 
33
33
  light_r = Ra::Light.new(
34
- position: Vector[-5, +7, -9, Ra::Tuple::POINT],
35
- intensity: Ra::Color.uniform(0.8),
34
+ position: Vector[-5, +3, -9, Ra::Tuple::POINT],
35
+ intensity: Ra::Color.uniform(0.5),
36
36
  )
37
37
 
38
38
  camera = Ra::Camera.new(
@@ -40,43 +40,53 @@ camera = Ra::Camera.new(
40
40
  h: config[:h],
41
41
  fov: config[:fov] * Math::PI / 180,
42
42
  transform: Ra::Transform.view(
43
- from: Vector[0, +1.5, -5.0, Ra::Tuple::POINT],
44
- to: Vector[0, 0, +1.0, Ra::Tuple::POINT],
43
+ from: Vector[0, +1.5, -4.0, Ra::Tuple::POINT],
44
+ to: Vector[0, 0, 0, Ra::Tuple::POINT],
45
45
  up: Vector[0, 1, 0, Ra::Tuple::VECTOR],
46
46
  ),
47
47
  )
48
48
 
49
49
  floor = Ra::Shape::Plane.new(
50
- material: Ra::Material.new(base: Ra::Pattern::Checkers.new(
51
- colors: [
52
- Ra::Color.hex('#e2e8f0'),
53
- Ra::Color.hex('#94a3b8'),
54
- ],
55
- )),
50
+ material: Ra::Material.new(
51
+ base: Ra::Pattern::Checkers.new(
52
+ colors: [
53
+ Ra::Color.hex('#e2e8f0'),
54
+ Ra::Color.hex('#1e293b'),
55
+ ],
56
+ ),
57
+ reflective: 0.2,
58
+ ),
59
+ transform: Ra::Transform
60
+ .rotate_y(Math::PI / 4)
61
+ .scale(0.5, 0.5, 0.5),
56
62
  )
57
63
 
58
64
  wall_l = Ra::Shape::Plane.new(
59
- material: Ra::Material.new(base: Ra::Pattern::Stripes.new(
60
- colors: [
61
- Ra::Color.hex('#94a3b8'),
62
- Ra::Color.hex('#475569'),
63
- ],
64
- )),
65
+ material: Ra::Material.new(
66
+ base: Ra::Pattern::Stripes.new(
67
+ colors: [
68
+ Ra::Color.hex('#f5f5f4'),
69
+ Ra::Color.hex('#e7e5e4'),
70
+ ],
71
+ ),
72
+ ),
65
73
  transform: Ra::Transform
66
- .translate(0, 0, +5.0)
74
+ .translate(0, 0, +3.0)
67
75
  .rotate_y(-Math::PI / 4)
68
76
  .rotate_x(Math::PI / 2),
69
77
  )
70
78
 
71
79
  wall_r = Ra::Shape::Plane.new(
72
- material: Ra::Material.new(base: Ra::Pattern::Stripes.new(
73
- colors: [
74
- Ra::Color.hex('#94a3b8'),
75
- Ra::Color.hex('#475569'),
76
- ],
77
- )),
80
+ material: Ra::Material.new(
81
+ base: Ra::Pattern::Stripes.new(
82
+ colors: [
83
+ Ra::Color.hex('#f5f5f4'),
84
+ Ra::Color.hex('#e7e5e4'),
85
+ ],
86
+ ),
87
+ ),
78
88
  transform: Ra::Transform
79
- .translate(0, 0, +5.0)
89
+ .translate(0, 0, +3.0)
80
90
  .rotate_y(Math::PI / 4)
81
91
  .rotate_x(Math::PI / 2),
82
92
  )
@@ -90,23 +100,31 @@ sphere = Ra::Shape::Sphere.new(
90
100
  )
91
101
 
92
102
  cube_l = Ra::Shape::Cube.new(
93
- material: Ra::Material.new(base: Ra::Pattern::Gradient.new(
94
- color_a: Ra::Color.hex('#f43f5e'),
95
- color_b: Ra::Color.hex('#8b5cf6'),
96
- )),
103
+ material: Ra::Material.new(
104
+ base: Ra::Pattern::Gradient.new(
105
+ color_a: Ra::Color.hex('#f43f5e'),
106
+ color_b: Ra::Color.hex('#8b5cf6'),
107
+ ),
108
+ reflective: 0.2,
109
+ ),
97
110
  transform: Ra::Transform
98
- .translate(+1.0, +0.3, -1.5)
99
- .scale(0.3, 0.3, 0.3),
111
+ .translate(+1.0, +0.3, -1.0)
112
+ .scale(0.3, 0.3, 0.3)
113
+ .rotate_y(-Math::PI / 8),
100
114
  )
101
115
 
102
116
  cube_r = Ra::Shape::Cube.new(
103
- material: Ra::Material.new(base: Ra::Pattern::Gradient.new(
104
- color_a: Ra::Color.hex('#84cc16'),
105
- color_b: Ra::Color.hex('#f97316'),
106
- )),
117
+ material: Ra::Material.new(
118
+ base: Ra::Pattern::Gradient.new(
119
+ color_a: Ra::Color.hex('#84cc16'),
120
+ color_b: Ra::Color.hex('#f97316'),
121
+ ),
122
+ reflective: 0.5,
123
+ ),
107
124
  transform: Ra::Transform
108
- .translate(-1.0, +0.3, -1.5)
109
- .scale(0.3, 0.3, 0.3),
125
+ .translate(-1.0, +0.3, -1.0)
126
+ .scale(0.3, 0.3, 0.3)
127
+ .rotate_y(+Math::PI / 8),
110
128
  )
111
129
 
112
130
  lights = [
data/lib/ra/engine.rb CHANGED
@@ -31,10 +31,7 @@ module Ra
31
31
  def draw(x:, y:, canvas:)
32
32
  ray = @camera.ray(x:, y:)
33
33
 
34
- intersections = @world.intersect(ray:)
35
- intersection = Intersection.hit(intersections:)
36
-
37
- canvas[x, y] = @world.color(intersection:) if intersection
34
+ canvas[x, y] = @world.color(ray:)
38
35
  end
39
36
  end
40
37
  end
@@ -38,8 +38,9 @@ module Ra
38
38
  point = ray.position(t:)
39
39
  eyev = -ray.direction
40
40
  normalv = shape.normal(point:)
41
+ reflectv = Tuple.reflect(ray.direction, normalv)
41
42
 
42
- Surface.new(shape:, eyev:, normalv:, point:)
43
+ Surface.new(shape:, eyev:, normalv:, reflectv:, point:)
43
44
  end
44
45
  end
45
46
  end
data/lib/ra/lighting.rb CHANGED
@@ -53,7 +53,7 @@ module Ra
53
53
 
54
54
  # @return [Ra::Vector]
55
55
  def reflectv
56
- @reflectv ||= -(lightv - (normalv * 2 * lightv.dot(normalv)))
56
+ @reflectv ||= -Tuple.reflect(lightv, normalv)
57
57
  end
58
58
 
59
59
  # @return [Ra::Vector]
data/lib/ra/material.rb CHANGED
@@ -11,17 +11,24 @@ module Ra
11
11
  # shininess: 200,
12
12
  # )
13
13
  class Material
14
- attr_accessor :base, :ambient, :diffuse, :specular, :shininess
14
+ attr_accessor :base, :ambient, :diffuse, :reflective, :specular, :shininess
15
15
 
16
16
  # @param base [Ra::Color, Ra::Pattern:::Base]
17
17
  # @param ambient [Float] between 0.0 and 1.0
18
18
  # @param diffuse [Float] between 0.0 and 1.0
19
+ # @param reflective [Float] between 0.0 and 1.0
19
20
  # @param specular [Float] between 0.0 and 1.0
20
21
  # @param shininess [Numeric]
21
- def initialize(base:, ambient: 0.0, diffuse: 0.8, specular: 0.2, shininess: 80)
22
+ def initialize(base:, ambient: 0.0, diffuse: 0.8, reflective: 0.0, specular: 0.2, shininess: 80)
23
+ raise ArgumentError, "ambient=#{ambient} must be between 0 and 1" unless ambient.between?(0, 1)
24
+ raise ArgumentError, "ambient=#{diffuse} must be between 0 and 1" unless diffuse.between?(0, 1)
25
+ raise ArgumentError, "ambient=#{reflective} must be between 0 and 1" unless reflective.between?(0, 1)
26
+ raise ArgumentError, "specular=#{specular} must be between 0 and 1" unless specular.between?(0, 1)
27
+
22
28
  @base = base
23
29
  @ambient = ambient
24
30
  @diffuse = diffuse
31
+ @reflective = reflective
25
32
  @specular = specular
26
33
  @shininess = shininess
27
34
  end
data/lib/ra/surface.rb CHANGED
@@ -3,15 +3,16 @@
3
3
  module Ra
4
4
  # A surface contains everything needed to apply lighting.
5
5
  class Surface
6
- attr_accessor :eyev, :normalv, :shape, :point
6
+ attr_accessor :eyev, :normalv, :reflectv, :shape, :point
7
7
 
8
8
  # @param eyev [Vector]
9
9
  # @param normalv [Vector]
10
10
  # @param shape [Ra::Shape]
11
11
  # @param point [Vector]
12
- def initialize(eyev:, normalv:, shape:, point:)
12
+ def initialize(eyev:, normalv:, reflectv:, shape:, point:)
13
13
  @eyev = eyev
14
14
  @normalv = normalv.dot(eyev).negative? ? -normalv : +normalv
15
+ @reflectv = reflectv
15
16
  @shape = shape
16
17
  @point = point
17
18
  end
data/lib/ra/tuple.rb CHANGED
@@ -22,5 +22,12 @@ module Ra
22
22
  Tuple::VECTOR,
23
23
  ]
24
24
  end
25
+
26
+ # @param vector [Vector]
27
+ # @param normal [Vector]
28
+ # @return [Vector]
29
+ def self.reflect(vector, normal)
30
+ vector - (normal * 2 * vector.dot(normal))
31
+ end
25
32
  end
26
33
  end
data/lib/ra/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ra
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/ra/world.rb CHANGED
@@ -24,21 +24,39 @@ module Ra
24
24
  Intersection.hit(intersections:)
25
25
  end
26
26
 
27
- # @param intersection [Ra::Intersection]
27
+ # @param ray [Ra::Ray]
28
28
  # @return [Ra::Color]
29
- def color(intersection:)
29
+ def color(ray:, remaining: 4)
30
+ intersection = intersection(ray:)
31
+ return unless intersection
32
+
30
33
  surface = intersection.surface
31
- point = surface.hpoint
32
34
 
33
35
  colors = @lights.map do |light|
34
- shadowed = shadowed?(point:, light:)
35
- lighting = Lighting.new(surface:, light:, shadowed:)
36
+ shadowed = shadowed?(point: surface.hpoint, light:)
37
+ lighting = Lighting.new(light:, shadowed:, surface:)
36
38
  lighting.color
37
39
  end
38
40
 
39
- colors.reduce(&:+)
41
+ colors.reduce(&:+) + reflect(surface:, remaining:)
42
+ end
43
+
44
+ # @param surface [Ra::Surface]
45
+ # @param remaining [Integer]
46
+ # @return [Ra::Color]
47
+ def reflect(surface:, remaining:)
48
+ return if remaining.zero?
49
+
50
+ material = surface.shape.material
51
+ return unless material.reflective.positive?
52
+
53
+ ray = Ray.new(origin: surface.hpoint, direction: surface.reflectv)
54
+
55
+ color = color(ray:, remaining: remaining.pred)
56
+ color * material.reflective if color
40
57
  end
41
58
 
59
+ # @param light [Ra::Light]
42
60
  # @param point [Ra::Point]
43
61
  def shadowed?(light:, point:)
44
62
  vector = light.position - point
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ra
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre