require 'fox'
require 'opengl'

include Fox

HANDLE_SIZE = 4.0

class FXGLPoint < FXGLObject
  attr_accessor :pos
  def initialize(*args)
    if args.length == 0
      @pos = [0.0, 0.0, 0.0]
    elsif args.length == 3
      @pos = [args[0], args[1], args[2]]
    else
      @pos = args[0]
    end
  end
  def bounds
    FXRange.new(@pos[0], @pos[0], @pos[1], @pos[1], @pos[2], @pos[2])
  end
  def draw(viewer)
    GL::Color(0.0, 0.0, 1.0)
    GL::PointSize(HANDLE_SIZE)
    GL::Begin(GL::POINTS)
    GL::Vertex(@pos)
    GL::End()
  end
  def hit(viewer)
    GL::Begin(GL::POINTS)
    GL::Vertex(@pos)
    GL::End()
  end
end

class FXGLLine < FXGLObject
  attr_accessor :fm, :to
  def initialize(*args)
    if args.length == 0
      @fm = FXGLPoint.new(-0.5, 0.0, 0.0)
      @to = FXGLPoint.new( 0.5, 0.0, 0.0)
    elsif args.length == 1
      @fm = args[0].fm
      @to = args[0].to
    else
      @fm = FXGLPoint.new(args[0], args[1], args[2])
      @to = FXGLPoint.new(args[3], args[4], args[5])
    end
  end
  def bounds
    FXRange.new(min(@fm.pos[0], @to.pos[0]),
                max(@fm.pos[0], @to.pos[0]),
                min(@fm.pos[1], @to.pos[1]),
                max(@fm.pos[1], @to.pos[1]),
                min(@fm.pos[2], @to.pos[2]),
                max(@fm.pos[2], @to.pos[2]))
  end
  def draw(viewer)
    GL::Color(1.0, 0.0, 0.0)
    GL::PointSize(HANDLE_SIZE)
    GL::Begin(GL::LINES)
    GL::Vertex(@fm.pos)
    GL::Vertex(@to.pos)
    GL::End()
  end
  def hit(viewer)
    GL::Begin(GL::LINES)
    GL::Vertex(@fm.pos)
    GL::Vertex(@to.pos)
    GL::End()
  end
end

class FXGLCube < FXGLShape

  attr_accessor :width, :height, :depth

  def initialize(*args)
    if args.length == 7
      super(args[0], args[1], args[2], SHADING_SMOOTH|STYLE_SURFACE,
            args[6], args[6])
    else
      super(args[0], args[1], args[2], SHADING_SMOOTH|STYLE_SURFACE)
    end
    @width = args[3] ? args[3] : 1.0
    @height = args[4] ? args[4] : 1.0
    @depth = args[5] ? args[5] : 1.0
    setRange(FXRange.new(-0.5*@width, 0.5*@width,
                         -0.5*@height, 0.5*@height,
                         -0.5*@depth, 0.5*@depth))
  end

  def drawshape(viewer)
    xmin, xmax = -0.5*@width, 0.5*@width
    ymin, ymax = -0.5*@height, 0.5*@height
    zmin, zmax = -0.5*@depth, 0.5*@depth

    # Draw low face
    GL::Begin(GL::TRIANGLE_STRIP)
      GL::Normal(0.0, 0.0, -1.0)
      GL::Vertex(xmin, ymin, zmin)
      GL::Vertex(xmin, ymax, zmin)
      GL::Vertex(xmax, ymin, zmin)
      GL::Vertex(xmax, ymax, zmin)
    GL::End()

    # Draw east face
    GL::Begin(GL::TRIANGLE_STRIP)
      GL::Normal(1.0, 0.0, 0.0)
      GL::Vertex(xmax, ymin, zmin)
      GL::Vertex(xmax, ymax, zmin)
      GL::Vertex(xmax, ymin, zmax)
      GL::Vertex(xmax, ymax, zmax)
    GL::End()

    # Draw high face
    GL::Begin(GL::TRIANGLE_STRIP)
      GL::Normal(0.0, 0.0, 1.0)
      GL::Vertex(xmax, ymin, zmax)
      GL::Vertex(xmax, ymax, zmax)
      GL::Vertex(xmin, ymin, zmax)
      GL::Vertex(xmin, ymax, zmax)
    GL::End()

    # Draw west face
    GL::Begin(GL::TRIANGLE_STRIP)
      GL::Normal(-1.0, 0.0, 0.0)
      GL::Vertex(xmin, ymin, zmax)
      GL::Vertex(xmin, ymax, zmax)
      GL::Vertex(xmin, ymin, zmin)
      GL::Vertex(xmin, ymax, zmin)
    GL::End()

    # Draw north face
    GL::Begin(GL::TRIANGLE_STRIP)
      GL::Normal(0.0, 1.0, 0.0)
      GL::Vertex(xmin, ymax, zmin)
      GL::Vertex(xmin, ymax, zmax)
      GL::Vertex(xmax, ymax, zmin)
      GL::Vertex(xmax, ymax, zmax)
    GL::End()

    # Draw south face
    GL::Begin(GL::TRIANGLE_STRIP)
      GL::Normal(0.0, -1.0, 0.0)
      GL::Vertex(xmin, ymin, zmax)
      GL::Vertex(xmin, ymin, zmin)
      GL::Vertex(xmax, ymin, zmax)
      GL::Vertex(xmax, ymin, zmin)
    GL::End()
  end
end

class FXGLCone < FXGLShape
  # Cone fidelity
  SLICES_NUMBER = 20
  STACKS_NUMBER = 20
  LOOPS = 4

  attr_accessor :height, :radius, :slices, :stacks, :loops

  def initialize(*args)
    if args.length == 5
      super(args[0], args[1], args[2], SHADING_SMOOTH|STYLE_SURFACE)
    elsif args.length == 6
      super(args[0], args[1], args[2], SHADING_SMOOTH|STYLE_SURFACE,
            args[5], args[5])
    end
    @height = args[3] ? args[3] : 1.0
    @radius = args[4] ? args[4] : 1.0
    @slices = SLICES_NUMBER
    @stacks = STACKS_NUMBER
    @loops  = LOOPS
    setRange(FXRange.new(-@radius, @radius, 0, @height, -@radius, @radius))
  end
  def drawshape(viewer)
    quad = GLU::NewQuadric()
    GLU::QuadricDrawStyle(quad, GLU::FILL)
    GL::PushMatrix()
    GL::Rotate(-90, 1, 0, 0)
    GLU::Cylinder(quad, @radius, 0, @height, @slices, @stacks)
    GLU::QuadricOrientation(quad, GLU::INSIDE)
    GLU::Disk(quad, 0, @radius, @slices, @loops)
    GLU::DeleteQuadric(quad)
    GL::PopMatrix()
  end
end

class FXGLCylinder < FXGLShape
  # Cylinder fidelity
  SLICES_NUMBER = 20
  STACKS_NUMBER = 20
  LOOPS = 4

  attr_accessor :height, :radius, :slices, :stacks, :loops

  def initialize(*args)
    if args.length == 5
      super(args[0], args[1], args[2], SHADING_SMOOTH|STYLE_SURFACE)
    else
      super(args[0], args[1], args[2], SHADING_SMOOTH|STYLE_SURFACE,
            args[5], args[5])
    end
    @height = args[3] ? args[3] : 1.0
    @radius = args[4] ? args[4] : 1.0
    @slices = SLICES_NUMBER
    @stacks = STACKS_NUMBER
    @loops  = LOOPS
    setRange(FXRange.new(-@radius, @radius, 0, @height, -@radius, @radius))
  end
  def drawshape(viewer)
    quad = GLU::NewQuadric()
    GLU::QuadricDrawStyle(quad, GLU::FILL)
    GL::PushMatrix()
    GL::Rotate(-90, 1, 0, 0)
    GLU::Cylinder(quad, @radius, @radius, @height, @slices, @stacks)
    GLU::QuadricOrientation(quad, GLU::INSIDE)
    GLU::Disk(quad, 0, @radius, @slices, @loops)
    GL::Translate(0, 0, @height)
    GLU::QuadricOrientation(quad, GLU::OUTSIDE)
    GLU::Disk(quad, 0, @radius, @slices, @loops)
    GL::PopMatrix()
    GLU::DeleteQuadric(quad)
  end
end

class FXGLSphere < FXGLShape
  # Sphere fidelity
  SLICES_NUMBER = 20
  STACKS_NUMBER = 20

  attr_accessor :radius, :slices, :stacks

  def initialize(*args)
    if args.length == 4
      super(args[0], args[1], args[2], SHADING_SMOOTH|STYLE_SURFACE)
    else
      super(args[0], args[1], args[2], SHADING_SMOOTH|STYLE_SURFACE,
            args[4], args[4])
    end
    @radius = args[3] ? args[3] : 1.0
    @slices = SLICES_NUMBER
    @stacks = STACKS_NUMBER
    setRange(FXRange.new(-@radius, @radius, -@radius, @radius, -@radius, @radius))
  end
  def drawshape(viewer)
    quad = GLU::NewQuadric()
    GLU::QuadricDrawStyle(quad, GLU::FILL)
    GLU::Sphere(quad, @radius, @slices, @stacks)
    GLU::DeleteQuadric(quad)
  end
end
