Example Usage

GEM_HOME = '/home/tux/.gem/ruby/2.6.0'
require 'vecmath'

color_mode(RGB, 1)

def draw
  background(0)
  @renderer ||= GfxRender.new(graphics)
  # Move the origin so that the scene is centered on the screen.
  translate(width / 2, height / 2, 0.0)
  # Set up the lighting.
  setup_lights
  # Rotate the local coordinate system.
  smooth_rotation(5.0, 6.7, 7.3)
  # Draw the inner object.
  no_stroke
  fill(smooth_colour(10.0, 12.0, 7.0))
  draw_icosahedron(5, 60.0, false)
  # Rotate the local coordinate system again.
  smooth_rotation(4.5, 3.7, 7.3)
  # Draw the outer object.
  stroke(0.2)
  fill(smooth_colour(6.0, 9.2, 0.7))
  draw_icosahedron(5, 200.0, true)
end

def setup_lights
  ambient_light(0.025, 0.025, 0.025)
  directional_light(0.2, 0.2, 0.2, -1, -1, -1)
  spot_light(1.0, 1.0, 1.0, -200, 0, 300, 1, 0, -1, PI / 4, 20)
end

# Generate a vector whose components change smoothly over time in the range
# (0..1). Each component uses a Math.sin function to map the current time in
# milliseconds in the range (0..1).A 'speed' factor is specified for each
# component.
def smooth_vector(s1, s2, s3)
  mills = millis * 0.00003 ## Lazydogs factor
  # mills = millis * 0.0000001 ## worked for me a bit slower!!
  x = 0.5 * sin(mills * s1) + 0.5
  y = 0.5 * sin(mills * s2) + 0.5
  z = 0.5 * sin(mills * s3) + 0.5
  Vec3D.new(x, y, z)
end

# Generate a colour which smoothly changes over time.
# The speed of each component is controlled by the parameters s1, s2 and s3.
def smooth_colour(s1, s2, s3)
  v = smooth_vector(s1, s2, s3)
  color(v.x, v.y, v.z)
end

# Rotate the current coordinate system.
# Uses smooth_vector to smoothly animate the rotation.
def smooth_rotation(s1, s2, s3)
  r1 = smooth_vector(s1, s2, s3)
  rotate_x(2.0 * Math::PI * r1.x)
  rotate_y(2.0 * Math::PI * r1.y)
  rotate_x(2.0 * Math::PI * r1.z)
end

# Draw an icosahedron defined by a radius r and recursive depth d.
# Geometry data will be saved into dst. If spherical is true then the
# icosahedron is projected onto the sphere with radius r.
def draw_icosahedron(depth, r, spherical)
  # Calculate the vertex data for an icosahedron inscribed by a sphere radius
  # 'r'. Use 4 Golden Ratio rectangles as the basis.
  gr = (1.0 + Math.sqrt(5.0)) / 2.0
  h = r / Math.sqrt(1.0 + gr * gr)
  v = [
    Vec3D.new(0, -h, h * gr),
    Vec3D.new(0, -h, -h * gr),
    Vec3D.new(0, h, -h * gr),
    Vec3D.new(0, h, h * gr),
    Vec3D.new(h, -h * gr, 0),
    Vec3D.new(h, h * gr, 0),
    Vec3D.new(-h, h * gr, 0),
    Vec3D.new(-h, -h * gr, 0),
    Vec3D.new(-h * gr, 0, h),
    Vec3D.new(-h * gr, 0, -h),
    Vec3D.new(h * gr, 0, -h),
    Vec3D.new(h * gr, 0, h)
  ]
  # Draw the 20 triangular faces of the icosahedron.
  r = 0.0 unless spherical
  begin_shape(TRIANGLES)
  draw_triangle(depth, r, v[0], v[7], v[4])
  draw_triangle(depth, r, v[0], v[4], v[11])
  draw_triangle(depth, r, v[0], v[11], v[3])
  draw_triangle(depth, r, v[0], v[3], v[8])
  draw_triangle(depth, r, v[0], v[8], v[7])
  draw_triangle(depth, r, v[1], v[4], v[7])
  draw_triangle(depth, r, v[1], v[10], v[4])
  draw_triangle(depth, r, v[10], v[11], v[4])
  draw_triangle(depth, r, v[11], v[5], v[10])
  draw_triangle(depth, r, v[5], v[3], v[11])
  draw_triangle(depth, r, v[3], v[6], v[5])
  draw_triangle(depth, r, v[6], v[8], v[3])
  draw_triangle(depth, r, v[8], v[9], v[6])
  draw_triangle(depth, r, v[9], v[7], v[8])
  draw_triangle(depth, r, v[7], v[1], v[9])
  draw_triangle(depth, r, v[2], v[1], v[9])
  draw_triangle(depth, r, v[2], v[10], v[1])
  draw_triangle(depth, r, v[2], v[5], v[10])
  draw_triangle(depth, r, v[2], v[6], v[5])
  draw_triangle(depth, r, v[2], v[9], v[6])
  end_shape
end

##
# Draw a triangle either immediately or subdivide it first.
# If depth is 1 then draw the triangle otherwise subdivide first.
#
def draw_triangle(depth, r, p1, p2, p3)
  if depth == 1
    p1.to_vertex(@renderer)
    p2.to_vertex(@renderer)
    p3.to_vertex(@renderer)
  else
    # Calculate the mid points of this triangle.
    v1 = (p1 + p2) * 0.5
    v2 = (p2 + p3) * 0.5
    v3 = (p3 + p1) * 0.5
    unless r == 0.0
      # Project the vertices out onto the sphere with radius r.
      v1.normalize!
      v1 *= r
      v2.normalize!
      v2 *= r
      v3.normalize!
      v3 *= r
    end
    ## Generate the next level of detail
    depth -= 1
    draw_triangle(depth, r, p1, v1, v3)
    draw_triangle(depth, r, v1, p2, v2)
    draw_triangle(depth, r, v2, p3, v3)
    # Uncomment out the next line to include the central part of the triangle.
    # draw_triangle(depth, r, v1, v2, v3)
  end
end