In looking at doing this raytracer code better/faster, I started rewriting it to conform with my personal style of writing code.
Here is a sample of orignal code that I rewrote to simplify it (to me).
class Sphere < Thing
@radius2 : Float64
def initialize(@center : Vector, radius : Float64, @_surface : Surface)
@radius2 = radius*radius
end
def normal(pos)
Vector.norm(Vector.minus(pos, @center))
end
def surface
@_surface
end
def intersect(ray)
eo = Vector.minus(@center, ray.start)
v = Vector.dot(eo, ray.dir)
dist = 0.0
if (v >= 0)
disc = @radius2 - (Vector.dot(eo, eo) - v * v)
if (disc >= 0)
dist = v - Math.sqrt(disc)
end
end
if (dist == 0)
return nil
end
Intersection.new(self, ray, dist)
end
end
class Plane < Thing
def initialize(@_norm : Vector, @offset : Float64, @_surface : Surface)
end
def normal(pos)
return @_norm
end
def intersect(ray)
denom = Vector.dot(@_norm, ray.dir)
return nil if (denom > 0)
dist = (Vector.dot(@_norm, ray.start) + @offset) / (-denom)
return Intersection.new(self, ray, dist)
end
def surface
@_surface
end
end
abstract class Surface
end
class ShinySurface < Surface
def diffuse(pos)
return Color_white
end
def specular(pos)
return Color_grey
end
def reflect(pos)
return 0.7
end
def roughness
return 250
end
end
I like to read/write code horizontally (as oneliners for single concepts) when possible. It creates code noise (to me) to write a simple oneline concept as a 3 line definition. So hereâs how I rewrote the above code to make it cleaner and simpler.
class Sphere < Thing
@radius2 : Float64
def initialize(@center : Vector, radius : Float64, @_surface : Surface)
@radius2 = radius*radius
end
def normal(pos); Vector.norm(Vector.minus(pos, @center)) end
def surface; @_surface end
def intersect(ray)
eo = Vector.minus(@center, ray.start)
v = Vector.dot(eo, ray.dir)
dist = 0.0
if (v >= 0)
disc = @radius2 - (Vector.dot(eo, eo) - v * v)
dist = v - Math.sqrt(disc) if (disc >= 0)
end
return nil if (dist == 0)
Intersection.new(self, ray, dist)
end
end
class Plane < Thing
def initialize(@_norm : Vector, @offset : Float64, @_surface : Surface) end
def normal(pos); @_norm end
def surface; @_surface end
def intersect(ray)
denom = Vector.dot(@_norm, ray.dir)
return nil if denom > 0
dist = (Vector.dot(@_norm, ray.start) + @offset) / (-denom)
Intersection.new(self, ray, dist)
end
end
abstract class Surface end
class ShinySurface < Surface
def diffuse(pos); Color_white end
def specular(pos); Color_grey end
def reflect(pos); 0.7 end
def roughness; 250 end
end
class CheckerboardSurface < Surface
def diffuse(pos)
return Color_white if ((pos.z).floor + (pos.x).floor) % 2 != 0
Color_black
end
def specular(pos); Color_white end
def roughness; 250 end
def reflect(pos)
return 0.1 if ((pos.z).floor + (pos.x).floor) % 2 != 0
0.7
end
end
There was a big discussion about this too in Ruby, which with 3.0.0 now allows
endless
methods (oneline method definitions that donât need the end
keyword).
I think adding this to Crystal would have many benefits too.
- It would allow compatibility with Ruby 3+ going forward.
- It would make source code much shorter, and easier to read/write/maintain.
- It would be simpler to read for newbies coming to the language.
- It could possibly encourage more concise thinking and refactoring.
- It wouldnât affect any legacy code.
The only possilbe âconâ I can think of (beside having to change the parser to
accommodate the change), is the emotional resistance to it (see Rubyâs discussions).
Here is what those code examples would look like using endless
methods, using Rubyâs syntax. Too me, this is not only more beautiful
code, but is concise, very easy to understand, and easier to write and read, and eliminates allot of unnecessary syntactical code noise.
class Sphere < Thing
@radius2 : Float64
def initialize(@center : Vector, radius : Float64, @_surface : Surface) = @radius2 = radius*radius
def normal(pos) = Vector.norm(Vector.minus(pos, @center))
def surface = @_surface
def intersect(ray)
eo = Vector.minus(@center, ray.start)
v = Vector.dot(eo, ray.dir)
dist = 0.0
if (v >= 0)
disc = @radius2 - (Vector.dot(eo, eo) - v * v)
dist = v - Math.sqrt(disc) if (disc >= 0)
end
return nil if (dist == 0)
Intersection.new(self, ray, dist)
end
end
class Plane < Thing
def initialize(@_norm : Vector, @offset : Float64, @_surface : Surface) end
def normal(pos) = @_norm
def surface = @_surface
def intersect(ray)
denom = Vector.dot(@_norm, ray.dir)
return nil if denom > 0
dist = (Vector.dot(@_norm, ray.start) + @offset) / (-denom)
Intersection.new(self, ray, dist)
end
end
abstract class Surface end
class ShinySurface < Surface
def diffuse(pos) = Color_white
def specular(pos) = Color_grey
def reflect(pos) = 0.7
def roughness = 250
end
class CheckerboardSurface < Surface
def diffuse(pos)
return Color_white if ((pos.z).floor + (pos.x).floor) % 2 != 0
Color_black
end
def specular(pos) = Color_white
def roughness = 250
def reflect(pos) = ((pos.z).floor + (pos.x).floor) % 2 != 0 ? 0.1 : 0.7
end