Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Last active June 24, 2017 00:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/ba839e738a8489c96f76ad8d00e8f6ab to your computer and use it in GitHub Desktop.
Save JoshCheek/ba839e738a8489c96f76ad8d00e8f6ab to your computer and use it in GitHub Desktop.
3d Cubes
# Video is @ https://vimeo.com/222905527
# The math is prob wrong, but it's close enough to actually render them in 3D.
# It's just me trying to think through what should make sense based on how
# people describe things, and then trying to work out what the correct math
# is to pull it off, but obv I'm not quite there.
require 'graphics'
require 'matrix'
def √ n
Math.sqrt(n)
end
class Matrix
def unit
self / length.to_f
end
def length
√ reduce(0) { |sum, n| sum + n**2 }
end
end
class Cubes < Graphics::Simulation
def initialize
super 800, 600, 24
color.default_proc = -> h, k { k }
end
include Math
def draw(t)
clear
# various variables
camera = Matrix[[0, 5, 0]]
bobble = 2
ø = PI*2 * t/20
# viewbox
origin = [4, -7, 2]
xaxis = [1, 0, -1]
yaxis = [-1, 0.7, -1]
# cube center
x, y, z = 40, 40, 40
cube = Cube.new(x: x+0, y: y+bobble*cos(ø)-20, z: z+bobble*cos(ø)+10, side: 10)
cube.spin_x(ø/15)
.spin_z(ø/43)
.draw(self, origin: origin, xaxis: xaxis, yaxis: yaxis, camera: camera, color: :cyan)
cube = Cube.new(x: x+40, y: y-30, z: z+bobble*sin(ø), side: 10)
cube.spin_z(ø/10)
.draw(self, n: t, origin: origin, xaxis: xaxis, yaxis: yaxis, camera: camera, color: :green)
cube = Cube.new(x: x-20, y: y-20+bobble*sin(ø)-40, z: z, side: 10)
cube.spin_z(ø/15)
.draw(self, origin: origin, xaxis: xaxis, yaxis: yaxis, camera: camera, color: :white)
cube = Cube.new(x: x-30+bobble*sin(ø), y: y-30, z: z, side: 10)
cube.spin_z(ø/20)
.spin_x(ø/20)
.lol(ø)
.draw(self, origin: origin, xaxis: xaxis, yaxis: yaxis, camera: camera, color: :yellow)
end
end
class Cube
BASE = [
# bottom square
Matrix[[0, 0, 0]],
Matrix[[0, 0, 1]],
Matrix[[1, 0, 1]],
Matrix[[1, 0, 0]],
# top square
Matrix[[0, 1, 0]],
Matrix[[0, 1, 1]],
Matrix[[1, 1, 1]],
Matrix[[1, 1, 0]],
].map { |p| p - Matrix[[0.5, 0.5, 0.5]] }.freeze # center it
attr_accessor :x, :y, :z, :side, :points
include Math
def initialize(x:, y:, z:, side:, points: BASE)
self.x, self.y, self.z, self.side, self.points = x, y, z, side, points
end
def spin_x(ø)
spin Matrix[[1, 0, 0], [0, cos(ø), -sin(ø)], [0, sin(ø), cos(ø)]]
end
def spin_y(ø)
spin Matrix[[cos(ø), 0, sin(ø)], [0, 1, 0], [-sin(ø), 0, cos(ø)]]
end
def lol(ø)
m = Matrix[[x/2, y/2, z/2]]
r = Matrix[[cos(ø), 0, sin(ø)], [0, 1, 0], [-sin(ø), 0, cos(ø)]]
points = self.points.map do |point|
point * r
end
self.class.new x: x, y: y, z: z, side: side, points: points
end
def spin_z(ø)
spin Matrix[[cos(ø), -sin(ø), 0], [sin(ø), cos(ø), 0], [0, 0, 1]]
end
def spin(rotation)
origin = Matrix[[x, y, z]]
points = self.points.map do |point|
point * rotation
end
self.class.new x: x, y: y, z: z, side: side, points: points
end
# xaxis and yaxis should be unit vectors
def draw(canvas, origin:, xaxis:, yaxis:, camera:, color:, n: 0)
ox, oy, oz = origin
xx, xy, xz = xaxis
yx, yy, yz = yaxis
px = xx+yx # plane
py = xy+yy
pz = xz+yz
ø = 2*PI*n/100
r = Matrix[[1, 0, 0], [0, cos(ø), -sin(ø)], [0, sin(ø), cos(ø)]] *
Matrix[[cos(3*ø), 0, sin(3*ø)], [0, 1, 0], [-sin(3*ø), 0, cos(3*ø)]] *
Matrix[[cos(2*ø), -sin(2*ø), 0], [sin(2*ø), cos(2*ø), 0], [0, 0, 1]]
screen_points = points.map(&:unit).map do |point|
point = ((point * side + Matrix[[x, y, z]]) - camera*r)
d = (ox*px + oy*py + oz*pz).to_f / (px*point[0, 0] + py*point[0, 1] + pz*point[0, 2])
plane_point = point * d # should be a point on our plane
((ppx, ppy, ppz)) = plane_point.to_a
# 0 = (a*xx + b*xx)*ppx + (a*xy + b*xy)*ppy + (a*xz + b*xz)*ppz
# 0 = a*xx*ppx + b*xx*ppx + a*xy*ppy + b*xy*ppy + a*xz*ppz + b*xz*ppz
# 0 = a*xx*ppx + a*xy*ppy + a*xz*ppz + b*xz*ppz + b*xx*ppx + b*xy*ppy
# 0 = a*(xx*ppx + xy*ppy + xz*ppz) + b(xz*ppz + xx*ppx + xy*ppy)
# a*(xx*ppx + xy*ppy + xz*ppz) = -b(xz*ppz + xx*ppx + xy*ppy)
# a = -b(xz*ppz + xx*ppx + xy*ppy) / (xx*ppx + xy*ppy + xz*ppz)
# ppx = ox + a*xx + b*yx
# -a*xx = ox + b*yx - ppx
# a = -(ox + b*yx - ppx)/xx
#
# ppy = oy + a*xy + b*yy
# ppy = oy - (ox + b*yx - ppx)*xy/xx + b*yy
# ppy = oy - ox*xy/xx - b*yx*xy/xx + ppx*xy/xx + b*yy
# ppy - oy + ox*xy/xx - ppx*xy/xx = b*yy - b*yx*xy/xx
# ppy - oy + ox*xy/xx - ppx*xy/xx = b(yy - yx*xy/xx)
# (ppy - oy + ox*xy/xx - ppx*xy/xx)/(yy - yx*xy/xx) = b
plane_y = (ppy - oy + ox*xy/xx - ppx*xy/xx)/(yy - yx*xy/xx)
plane_x = (ppx - ox - plane_y*yx)/xx
# plane_x = (ppy*xx - ppy*xy) / (yy*xx - yx*xy)
# plane_y = (ppx - plane_x * yx) / xx
screen_x = plane_x * 30
screen_y = plane_y * 30
[screen_x, screen_y]
end
p000, p001, p101, p100, p010, p011, p111, p110 = screen_points
line canvas, *p000, *p001, color if display? canvas, p000, p001
line canvas, *p000, *p010, color if display? canvas, p000, p010
line canvas, *p000, *p100, color if display? canvas, p000, p100
line canvas, *p001, *p011, color if display? canvas, p001, p011
line canvas, *p001, *p101, color if display? canvas, p001, p101
line canvas, *p010, *p011, color if display? canvas, p010, p011
line canvas, *p010, *p110, color if display? canvas, p010, p110
line canvas, *p011, *p111, color if display? canvas, p011, p111
line canvas, *p100, *p101, color if display? canvas, p100, p101
line canvas, *p100, *p110, color if display? canvas, p100, p110
line canvas, *p101, *p111, color if display? canvas, p101, p111
line canvas, *p110, *p111, color if display? canvas, p110, p111
end
def line(canvas, x1, y1, x2, y2, color)
canvas.line x1, y1, x2, y2, color
canvas.line x1+1, y1, x2+1, y2, color
canvas.line x1, y1+1, x2, y2+1, color
canvas.line x1-1, y1, x2-1, y2, color
canvas.line x1, y1-1, x2, y2-1, color
end
def display?(canvas, *points)
points.all? do |x, y|
-canvas.w < x && -canvas.h < y && x <= 2*canvas.w && y <= 2*canvas.h
end
end
end
Cubes.new.run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment