Skip to content

Instantly share code, notes, and snippets.

@yhara
Created April 30, 2020 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yhara/ea0e66e0d8bdd114d2401dd133539fa3 to your computer and use it in GitHub Desktop.
Save yhara/ea0e66e0d8bdd114d2401dd133539fa3 to your computer and use it in GitHub Desktop.
# orig: http://qiita.com/doxas/items/477fda867da467116f8d
IMAGE_WIDTH = 512
IMAGE_HEIGHT = 512
IMAGE_DEPTH = 256
EPS = 0.0001
MAX_REF = 4
class Vec
def initialize(x: Float, y: Float, z: Float)
var @x = x
var @y = y
var @z = z
end
def x -> Float; @x; end
def y -> Float; @y; end
def z -> Float; @z; end
def add(b: Vec) -> Vec
Vec.new(@x + b.x, @y + b.y, @z + b.z)
end
def sub(b: Vec) -> Vec
Vec.new(@x - b.x, @y - b.y, @z - b.z)
end
def mul(t: Float) -> Vec
Vec.new(@x * t, @y * t, @z * t)
end
def multi(b: Vec) -> Vec
Vec.new(@x * b.x, @y * b.y, @z * b.z)
end
def dot(b: Vec) -> Float
@x * b.x + @y * b.y + @z * b.z
end
def cross(b: Vec) -> Vec
Vec.new(@y * b.z - @z * b.y,
@z * b.x - @x * b.z,
@x * b.y - @y * b.x)
end
def length -> Float
Math.sqrt(@x * @x + @y * @y + @z * @z)
end
def normalize -> Vec
len = self.length()
if len > 0.00000000000000001
r_len = 1.0 / len
@x = @x * r_len
@y = @y * r_len
@z = @z * r_len
end
self
end
def reflect(normal: Vec) -> Vec
self.add(normal.mul(-2.0 * normal.dot(self)))
end
end
LIGHT = Vec.new(0.577, 0.577, 0.577)
class Ray
def initialize(origin: Vec, dir: Vec)
@origin = origin
@dir = dir
end
def origin -> Vec; @origin; end
def dir -> Vec; @dir; end
end
class Isect
def initialize(hit: Int, hit_point: Vec, normal: Vec,
color: Vec, distance: Float, ray_dir: Vec)
var @hit = hit
var @hit_point = hit_point
var @normal = normal
var @color = color
var @distance = distance
var @ray_dir = ray_dir
end
def hit -> Int; @hit; end
def hit=(v: Int); @hit=v; end
def hit_point -> Vec; @hit_point; end
def hit_point=(v: Vec); @hit_point=v; end
def normal -> Vec; @normal; end
def normal=(v: Vec); @normal=v; end
def color -> Vec; @color; end
def color=(v: Vec); @color=v; end
def distance -> Float; @distance; end
def distance=(v: Float); @distance=v; end
def ray_dir -> Vec; @ray_dir; end
def ray_dir=(v: Vec); @ray_dir=v; end
end
class Sphere
def initialize(radius: Float, position: Vec, color: Vec)
@radius = radius
@position = position
@color = color
end
def radius -> Float; @radius; end
def position -> Vec; @position; end
def color -> Vec; @color; end
def intersect(ray: Ray, isect: Isect) -> Void
rs = ray.origin.sub(@position)
b = rs.dot(ray.dir)
c = rs.dot(rs) - @radius * @radius
d = b * b - c
t = -b - Math.sqrt(d)
if d > 0.0 && t > EPS && t < isect.distance
isect.hit_point = ray.origin.add(ray.dir.mul(t))
isect.normal = isect.hit_point.sub(@position).normalize
isect.color = @color.mul(Util.clamp(LIGHT.dot(isect.normal), 0.1, 1.0))
isect.distance = t
isect.hit = isect.hit + 1
isect.ray_dir = ray.dir
end
end
end
class Plane
def initialize(position: Vec, normal: Vec, color: Vec)
@position = position
@normal = normal
@color = color
end
def position -> Vec; @position; end
def normal -> Vec; @normal; end
def color -> Vec; @color; end
def intersect(ray: Ray, isect: Isect) -> Void
d = -(@position.dot(@normal))
v = ray.dir.dot(@normal)
t = -(ray.origin.dot(@normal) + d) / v
if t > EPS && t < isect.distance
isect.hit_point = ray.origin.add(ray.dir.mul(t))
isect.normal = @normal
d2 = Util.clamp(LIGHT.dot(isect.normal), 0.1, 1.0)
m = isect.hit_point.x % 2
n = isect.hit_point.z % 2
d3 = if (m > 1.0 && n > 1.0) || (m < 1.0 && n < 1.0)
d2*0.5
else
d2
end
abs = isect.hit_point.z.abs
f = 1.0 - (if abs < 25.0; abs; else 25.0; end) * 0.04
isect.color = @color.mul(d3 * f)
isect.distance = t
isect.hit = isect.hit + 1
isect.ray_dir = ray.dir
end
end
end
PLANE = Plane.new(Vec.new(0.0, -1.0, 0.0), Vec.new(0.0, 1.0, 0.0), Vec.new(1.0, 1.0, 1.0))
T = 10.0
SPHERE1 = Sphere.new(0.5, Vec.new( 0.0, -0.5, Math.sin(0.0)), Vec.new(1.0, 0.0, 0.0))
SPHERE2 = Sphere.new(1.0, Vec.new( 2.0, 0.0, Math.cos(T * 0.666)), Vec.new(0.0, 1.0, 0.0))
SPHERE3 = Sphere.new(1.5, Vec.new(-2.0, 0.5, Math.cos(T * 0.333)), Vec.new(0.0, 0.0, 1.0))
class Util
def self.clamp(t: Float, min: Float, max: Float) -> Float
if t < min
min
else
if t > max
max
else
t
end
end
end
## t: 0 ~ 1
def self.color(t: Float) -> Int
ret = (IMAGE_DEPTH.to_f * Util.clamp(t, 0.0, 1.0)).to_i
if ret == IMAGE_DEPTH then (IMAGE_DEPTH-1) else ret end
end
def self.print_col(c: Vec) -> Void
putd(Util.color(c.x)); putchar(32)
putd(Util.color(c.y)); putchar(32)
putd(Util.color(c.z)); putchar(10)
end
def self.intersect(ray: Ray, i: Isect) -> Void
SPHERE1.intersect(ray, i)
SPHERE2.intersect(ray, i)
SPHERE3.intersect(ray, i)
PLANE.intersect(ray, i)
end
end
# P3\n
puts "P3"
# W H\n
putd(IMAGE_WIDTH); puts " "; putd(IMAGE_HEIGHT); puts ""
# D
puts "255"
var row = 0; while row < IMAGE_HEIGHT
var col = 0; while col < IMAGE_WIDTH
x = col.to_f / (IMAGE_WIDTH.to_f / 2.0) - 1.0
y = (IMAGE_HEIGHT-row).to_f / (IMAGE_HEIGHT.to_f / 2.0) - 1.0
ray = Ray.new(Vec.new(0.0, 2.0, 6.0),
Vec.new(x, y, -1.0).normalize)
i = Isect.new(0, Vec.new(0.0, 0.0, 0.0), Vec.new(0.0, 0.0, 0.0), Vec.new(0.0, 0.0, 0.0),
1000000000000000000000000000000.0, Vec.new(0.0, 0.0, 0.0))
Util.intersect(ray, i)
if i.hit > 0
var dest_col = i.color
var temp_col = Vec.new(1.0, 1.0, 1.0).multi(i.color)
var j = 1; while j < MAX_REF
q = Ray.new(i.hit_point.add(i.normal.mul(EPS)),
i.ray_dir.reflect(i.normal))
Util.intersect(q, i)
if i.hit > j
dest_col = dest_col.add(temp_col.multi(i.color))
temp_col = temp_col.multi(i.color)
end
j = j + 1
end
Util.print_col(dest_col)
else
Util.print_col(Vec.new(ray.dir.y, ray.dir.y, ray.dir.y))
end
col = col + 1
end
row = row + 1
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment