$LOAD_PATH.unshift(File.dirname(__FILE__))
require 'helper'

include GMath3D

MiniTest::Unit.autorun

class TriangleTestCase < MiniTest::Unit::TestCase
  def setup
    @triangle = Triangle.new( Vector3.new(1,2,2), Vector3.new(1,4,2), Vector3.new(-1,3,0))
    @triangle_default = Triangle.new()
  end

  def test_initialize
    assert_equal(Vector3.new(1,2,2), @triangle.vertices[0])
    assert_equal(Vector3.new(1,4,2), @triangle.vertices[1])
    assert_equal(Vector3.new(-1,3,0), @triangle.vertices[2])

    assert_equal(Vector3.new(0,0,0), @triangle_default.vertices[0])
    assert_equal(Vector3.new(1,0,0), @triangle_default.vertices[1])
    assert_equal(Vector3.new(0,1,0), @triangle_default.vertices[2])
  end

  def test_equals
    assert(@triangle != nil)
    assert(@triangle != "string")

    shallow_copied = @triangle
    assert(@triangle.equal?(shallow_copied))
    assert(@triangle == shallow_copied)

    assert_equal(@triangle, Triangle.new( Vector3.new(1.0,2.0,2.0), Vector3.new(1.0,4.0,2.0), Vector3.new(-1.0,3.0,0.0)))
    assert(@triangle == Triangle.new( Vector3.new(1.0,2.0,2.0), Vector3.new(1.0,4.0,2.0), Vector3.new(-1.0,3.0,0.0)))
    assert(@triangle != Triangle.new( Vector3.new(1.0,2.0,2.0), Vector3.new(1.0,4.0,2.0), Vector3.new(-1.0,3.0,0.5)))
  end

  def test_clone
    shallow_copied = @triangle
    shallow_copied.vertices[2].x = 1
    assert(@triangle == shallow_copied)
    assert(@triangle.equal?(shallow_copied))
    assert_equal(1, @triangle.vertices[2].x)
    assert_equal(1, shallow_copied.vertices[2].x)

    cloned =@triangle.clone
    assert(@triangle == cloned)
    assert(!@triangle.equal?(cloned))

    cloned.vertices[2].x = -10
    assert_equal(-10, cloned.vertices[2].x)
    assert_equal(1, @triangle.vertices[2].x) # original never changed in editing cloned one.
  end

  def test_to_s
    assert_equal("Triangle[[1, 2, 2], [1, 4, 2], [-1, 3, 0]]", @triangle.to_s)
  end

  def test_box
    box1 = @triangle.box
    box2 = @triangle_default.box
    assert_equal(Vector3.new(-1,2,0), box1.min_point)
    assert_equal(Vector3.new(1,4,2), box1.max_point)
    assert_equal(Vector3.new(0,0,0) , box2.min_point)
    assert_equal(Vector3.new(1,1,0) , box2.max_point)
  end

  def test_point
    assert_equal(@triangle.vertices[0], @triangle.point( Array.new([1,0,0])))
    assert_equal(@triangle.vertices[1], @triangle.point( Array.new([0,1,0])))
    assert_equal(@triangle.vertices[2], @triangle.point( Array.new([0,0,1])))

    #on edge
    assert_equal(Vector3.new(1,3,2), @triangle.point( Array.new([0.5,0.5,0])))
    assert_equal(Vector3.new(0,3.5,1), @triangle.point( Array.new([0,0.5,0.5])))
    assert_equal(Vector3.new(0,2.5,1), @triangle.point( Array.new([0.5,0,0.5])))

    #inside
    assert_equal(Vector3.new(0, 3, 1), @triangle.point( Array.new([0.25,0.25,0.5])))
    assert_equal(@triangle.center, @triangle.point( Array.new([1.0/3.0,1.0/3.0,1.0/3.0])))
  end

  def test_edges
    edges = @triangle.edges
    assert_equal(@triangle.vertices[0], edges[0].start_point)
    assert_equal(@triangle.vertices[1], edges[0].end_point)
    assert_equal(@triangle.vertices[1], edges[1].start_point)
    assert_equal(@triangle.vertices[2], edges[1].end_point)
    assert_equal(@triangle.vertices[2], edges[2].start_point)
    assert_equal(@triangle.vertices[0], edges[2].end_point)
  end

  def test_area
    assert_in_delta(Math::sqrt(8), @triangle.area, @triangle.tolerance)
    assert_in_delta(0.5, @triangle_default.area, @triangle_default.tolerance)
  end

  def test_normal
    assert_equal( Vector3.new(-1,0,1).normalize(), @triangle.normal )
    assert_equal( Vector3.new(0,0,1) ,@triangle_default.normal )
  end

  def test_reverse
    current_normal = @triangle.normal()
    reversed_normal = @triangle.reverse().normal()
    assert( current_normal.parallel?(reversed_normal) )
    assert( !current_normal.same_direction?(reversed_normal) )
  end

  def test_center
    center = @triangle.center
    assert_in_delta( 0.333333333333333, center.x, @triangle.tolerance)
    assert_in_delta( 3.0, center.y, @triangle.tolerance)
    assert_in_delta( 1.333333333333333, center.z, @triangle.tolerance)

    barycentric_coordinate = @triangle.barycentric_coordinate(center)
    assert_in_delta( 1.0/3.0, barycentric_coordinate[0], @triangle.tolerance)
    assert_in_delta( 1.0/3.0, barycentric_coordinate[1], @triangle.tolerance)
    assert_in_delta( 1.0/3.0, barycentric_coordinate[2], @triangle.tolerance)
  end

  def test_barycentric_coordinate
    # on vertex
    coord = @triangle.barycentric_coordinate( @triangle.vertices[1])
    assert_equal( 3, coord.size )
    assert_in_delta( 0, coord[0], @triangle.tolerance)
    assert_in_delta( 1, coord[1], @triangle.tolerance)
    assert_in_delta( 0, coord[2], @triangle.tolerance)

    # on edge
    coord = @triangle.barycentric_coordinate( Vector3.new(1,3,2) )
    assert_in_delta( 0.5, coord[0], @triangle.tolerance)
    assert_in_delta( 0.5, coord[1], @triangle.tolerance)
    assert_in_delta( 0, coord[2], @triangle.tolerance)

    # inside case
    coord = @triangle.barycentric_coordinate( Vector3.new(0,3,1) )
    assert_in_delta( 1.0, coord[0] + coord[1] + coord[2], @triangle.tolerance)
    assert_in_delta( 0.25, coord[0], @triangle.tolerance)
    assert_in_delta( 0.25, coord[1], @triangle.tolerance)
    assert_in_delta( 0.5, coord[2], @triangle.tolerance)

    # outside case
    coord = @triangle_default.barycentric_coordinate( Vector3.new(2,0,0) )
    assert_in_delta( -1.0, coord[0], @triangle.tolerance)
    assert_in_delta( 2.0, coord[1], @triangle.tolerance)
    assert_in_delta( 0.0, coord[2], @triangle.tolerance)

    # outside case
    coord = @triangle.barycentric_coordinate( Vector3.new(3,3,4) )
    assert_in_delta(  1.0, coord[0], @triangle.tolerance)
    assert_in_delta(  1.0, coord[1], @triangle.tolerance)
    assert_in_delta( -1.0, coord[2], @triangle.tolerance)

    #invalid argument
    assert_raises ArgumentError do
      coord = @triangle.barycentric_coordinate( 3 )
    end
    assert_raises ArgumentError do
      coord = @triangle.barycentric_coordinate( nil )
    end
  end

  def test_contains
    check_point = @triangle.center + @triangle.normal*3.0
    assert( !@triangle.contains?( check_point) )
    assert( @triangle.contains?( @triangle.center) )
    assert( @triangle.contains?( Vector3.new( 1,3,2)))
    assert( @triangle.contains?( Vector3.new(-1,3,0)))
    assert( @triangle_default.contains?( Vector3.new( 0.5, 0.5, 0.0 ) ))
    assert( !@triangle_default.contains?( Vector3.new( -1.0, 2.0, 0.0 )))
    assert( !@triangle_default.contains?( Vector3.new( 1.0, 1.0, 0.0 )))
  end

  def test_distance_to_point
    # on inside
    check_point = @triangle.center + @triangle.normal*3.0
    distance, point_on_triangle = @triangle.distance(check_point)
    assert_in_delta(3.0, distance, @triangle.tolerance)
    assert_equal(@triangle.center, point_on_triangle)

    # on vertex
    distance, point_on_triangle = @triangle_default.distance(Vector3.new(-1,-1,0.5))
    assert_in_delta(Math::sqrt(2.25), distance, @triangle_default.tolerance)
    assert_equal(@triangle_default.vertices[0], point_on_triangle)

    # on edge
    distance, point_on_triangle = @triangle.distance( Vector3.new(2,3,2))
    assert_in_delta(1, distance, @triangle_default.tolerance)
    assert_equal(Vector3.new(1,3,2), point_on_triangle)

    # contains to inside
    distance, point_on_triangle = @triangle_default.distance(@triangle_default.center)
    assert_in_delta(0, distance, @triangle_default.tolerance)
    assert_equal(@triangle_default.center, point_on_triangle)
  end

  def test_distance_to_line
    # on inside
    line = Line.new(Vector3.new(0,3,-1), Vector3.new(0,0,4))
    distance, point_on_triangle, point_on_line, param_on_line = @triangle.distance(line)
    assert_equal(0, distance)
    assert_equal(Vector3.new(0,3,1), point_on_triangle)
    assert_equal(Vector3.new(0,3,1), point_on_line)
    assert_in_delta(0.5, param_on_line, line.tolerance)

    # on edge
    line = Line.new(Vector3.new(1,3,-1), Vector3.new(0,0,4))
    distance, point_on_triangle, point_on_line, param_on_line = @triangle.distance(line)
    assert_equal(0, distance)
    assert_equal(Vector3.new(1,3,2), point_on_triangle)
    assert_equal(Vector3.new(1,3,2), point_on_line)
    assert_in_delta(0.75, param_on_line, line.tolerance)

    # on vertex
    line = Line.new(Vector3.new(-1,3,0), Vector3.new(1,1,2))
    distance, point_on_triangle, point_on_line, param_on_line = @triangle.distance(line)
    assert_equal(0, distance)
    assert_equal(Vector3.new(-1,3,0), point_on_triangle)
    assert_equal(Vector3.new(-1,3,0), point_on_line)
    assert_in_delta(0.0, param_on_line, line.tolerance)

    # closest point is out of triangle1
    line = Line.new(Vector3.new(2,3,0), Vector3.new(0,0,4))
    distance, point_on_triangle, point_on_line, param_on_line = @triangle.distance(line)
    assert_equal(1, distance)
    assert_equal(Vector3.new(1,3,2), point_on_triangle)
    assert_equal(Vector3.new(2,3,2), point_on_line)
    assert_in_delta(0.5, param_on_line, line.tolerance)

    # closest point is out of triangle2
    line = Line.new(Vector3.new(-2,3,-1), Vector3.new(0,0,1))
    distance, point_on_triangle, point_on_line, param_on_line = @triangle.distance(line)
    assert_equal(1, distance)
    assert_equal(Vector3.new(-1,3,0), point_on_triangle)
    assert_equal(Vector3.new(-2,3,0), point_on_line)
    assert_in_delta(1, param_on_line, line.tolerance)

    # parallel case
    line = Line.new(Vector3.new(1,0,4), Vector3.new(0,6,0))
    distance, point_on_triangle, point_on_line, param_on_line = @triangle.distance(line)
    assert_equal(2, distance)
    assert_equal(nil, point_on_triangle)
    assert_equal(nil, point_on_line)
    assert_equal(nil, param_on_line)
  end

  def test_distance_to_plane
    # intersect case
    plane = Plane.new(Vector3.new(0,0,1), Vector3.new(1,0,0))
    distance, intersect_line, point_on_triangle, point_on_plane = @triangle.distance(plane)
    assert_equal( 0, distance )
    point1 = Vector3.new(0,2.5,1)
    point2 = Vector3.new(0,3.5,1)
    assert( FiniteLine.new(point1, point2) == intersect_line || FiniteLine.new(point2, point1) == intersect_line)
    assert_equal( nil, point_on_triangle )
    assert_equal( nil, point_on_plane )

    # contains edge
    plane = Plane.new(Vector3.new(1,0,1), Vector3.new(-1,0,0))
    distance, intersect_line, point_on_triangle, point_on_plane = @triangle.distance(plane)
    assert_equal( 0, distance )
    point1 = Vector3.new(1,2,2)
    point2 = Vector3.new(1,4,2)
    assert( FiniteLine.new(point1, point2) == intersect_line || FiniteLine.new(point2, point1) == intersect_line)
    assert_equal( nil, point_on_triangle )
    assert_equal( nil, point_on_plane )

    # contains vertex
    plane = Plane.new(Vector3.new(-1,0,1), Vector3.new(1,0,0))
    distance, intersect_line, point_on_triangle, point_on_plane = @triangle.distance(plane)
    assert_equal( 0, distance )
    assert_equal( nil, intersect_line)
    assert_equal( Vector3.new(-1,3,0), point_on_triangle )
    assert_equal( Vector3.new(-1,3,0), point_on_plane )

    # closeing point is outside of triangle
    plane = Plane.new(Vector3.new(-2,0,0), Vector3.new(-1,0,0))
    distance, intersect_line, point_on_triangle, point_on_plane = @triangle.distance(plane)
    assert_equal( 1, distance )
    assert_equal( nil, intersect_line)
    assert_equal( Vector3.new(-1,3,0), point_on_triangle )
    assert_equal( Vector3.new(-2,3,0), point_on_plane )

    # parallel to plane
    plane = Plane.new(Vector3.new(0,0,4), Vector3.new(0,0,1))
    distance, intersect_line, point_on_triangle, point_on_plane = @triangle_default.distance(plane)
    assert_equal( 4, distance )
    assert_equal( nil, intersect_line)
    assert_equal( nil, point_on_triangle )
    assert_equal( nil, point_on_plane )

    # parallel to edge
    plane = Plane.new(Vector3.new(3.5,0,3), Vector3.new(-1,0,0))
    distance, closest_line, point_on_triangle, point_on_plane = @triangle.distance(plane)
    assert_equal( 2.5, distance )
    point1 = Vector3.new(1,2,2)
    point2 = Vector3.new(1,4,2)
    assert( FiniteLine.new(point1, point2) == closest_line || FiniteLine.new(point2, point1) == closest_line)
    assert_equal( nil, point_on_triangle )
    assert_equal( nil, point_on_plane )
  end

  def test_angle
    triangle1 = Triangle.new( Vector3.new(0,0,1), Vector3.new(Math.sqrt(3.0),0,1), Vector3.new(0,1,1))
    triangle2 = Triangle.new( Vector3.new(1,0,0), Vector3.new(0,0,1), Vector3.new(1,1,1))

    assert_equal(nil, triangle1.angle(-1))
    assert_equal(nil, triangle1.angle(3))

    assert_in_delta(90.0*Math::PI/180.0, triangle1.angle(0), triangle1.tolerance)
    assert_in_delta(30.0*Math::PI/180.0, triangle1.angle(1), triangle1.tolerance)
    assert_in_delta(60.0*Math::PI/180.0, triangle1.angle(2), triangle1.tolerance)

    assert_in_delta(60.0*Math::PI/180.0, triangle2.angle(0), triangle1.tolerance)
    assert_in_delta(60.0*Math::PI/180.0, triangle2.angle(1), triangle1.tolerance)
    assert_in_delta(60.0*Math::PI/180.0, triangle2.angle(2), triangle1.tolerance)
  end
end
