Jack V Purvis has created this excellent app Visor for live coding of Processing in the Ruby language. Visor attempts to empower Processing for live performance by combining elements from live coding and VJing practice. Turns out that JRubyArt is perfect for prototyping sketches for use in Visor, further there is a possibility for collaboration to create an even more awesome tool. Here is an example of Vec2D sketch in JRubyArt translated for Visor, that includes use of a custom gem to provide Vec2D and Vec3D classes.

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


require 'forwardable'

# Here we use the JRubyArt Vec2D class, and not toxis Vec2D. We avoid using
# ToxicLibsSupport, by using our own AppRender to translate Vec2D to vertices.
# Further we use the power of ruby (metaprogramming) to make Branch enumerable
# and use Forwardable to define which enumerable methods we want to use.
visor_class :Branch do
  include Enumerable
  extend Forwardable
  def_delegators(:@children, :<<, :each, :length)
  # variance angle for growth direction per time step
  THETA = Math::PI / 6
  # max segments per branch
  MAX_LEN = 100
  # max recursion limit
  MAX_GEN = 3
  # branch chance per time step
  BRANCH_CHANCE = 0.05
  # branch angle variance
  BRANCH_THETA = Math::PI / 3
  attr_reader :position, :dir, :path, :children, :xbound, :speed, :ybound, :app

  def initialize(pos, dir, speed)
    @position = pos
    @dir = dir
    @speed = speed
    @path = []
    @children = []
    @xbound = Boundary.new(0, 400)
    @ybound = Boundary.new(0, 400)
    path << pos
  end

  def run
    grow
    display
  end

  private

  # NB: use of both rotate! (changes original) rotate (returns a copy) of Vec2D
  def grow
    check_bounds(position + (dir * speed)) if path.length < MAX_LEN
    @position += (dir * speed)
    dir.rotate!(rand(-0.5..0.5) * THETA)
    path << position
    if (length < MAX_GEN) && (rand < BRANCH_CHANCE)
      branch_dir = dir.rotate(rand(-0.5..0.5) * BRANCH_THETA)
      self << Branch.new(position.copy, branch_dir, speed * 0.99)
    end
    each(&:grow)
  end

  def display
    beginShape
    stroke(255)
    no_fill
    path.each { |vec| vertex(vec.x, vec.y) }
    endShape
    each(&:display)
  end

  def check_bounds(pos)
    dir.x *= -1 if xbound.exclude? pos.x
    dir.y *= -1 if ybound.exclude? pos.y
  end
end

# we are looking for excluded values
Boundary = Struct.new(:lower, :upper) do
  def exclude?(val)
    !(lower...upper).cover? val
  end
end

@root = Branch.new(
    Vec2D.new(0, height / 2),
    Vec2D.new(1, 0),
    10.0
)

def draw
  background(0)
  root.run
end

Here is a Vec3D sketch translated for Visor:-

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

color_mode(RGB, 1)

def draw
  background(0)
  # 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
    vertex p1.x, p1.y, p1.z
    vertex p2.x, p2.y, p2.z
    vertex p3.x, p3.y, p3.z
  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

Screenshot:-

elegant_ball.png