require_relative 'vectorize'

# Frustum class, which is useful for creating cameras and viewports in 3D space.
class Frustum
    using Vectorize

    # Near image plate distance from eye
    attr_accessor :near
    # Far image plate distance from eye
    attr_accessor :far
    # Field of view, in radians
    attr_accessor :field_of_view
    # Image plate width, in pixels
    attr_accessor :width
    # Image plate height, in pixels
    attr_accessor :height

    # Initialize.
    def initialize( near = 1.0, far = 1e5, fov = 90.0.radians, width, height )
        @near = near
        @far = far
        @field_of_view = fov
        @width = width
        @height = height
    end

    # Compute the depth of field, as a range.
    def depth_of_field()
        (@near .. @far)
    end
    alias :dof :depth_of_field

    # Compute the aspect ratio, as a float.
    def aspect_ratio()
        @width.to_f / @height.to_f
    end

    # Compute the projection matrix of this frustum.
    def projection()
        Matrix.projection( @near, @far, Math.tan( @field_of_view.radians * 0.5 * @near ), aspect_ratio() )
    end

    # Peform radar testing of a point with the specified bounding radius.  Requires that a position and rotation be
    # supplied to compute the inferred view matrix.  By default, the point is assumed to be a unit sphere (r = 1u).
    def visible?( position, rotation, vec, radius = 1.0 )
        vmtx = Matrix.view_matrix( position, rotation )

        # test Z-axis
        v = (vec - vmtx).normalize.xyz
        z = v.dot( vec.z )
        return false if z > @far + radius or z < @near - radius

        # test Y-axis
        t = Math.tan( @field_of_view )
        d = radius > 0.0 ? radius * (1.0 / Math.cos( @field_of_view )) : 0.0
        y = v.dot( vec.y )
        z *= t
        return false if y > z + d or y < -z - d

        # test X-axis
        d = radius > 0.0 ? radius * (1.0 / Math.cos( Math.atan( t ))) : 0.0
        x = v.dot( vec.x )
        z *= aspect_ratio()
        not x > z + d or x < -z - d
    end

    alias :fov :field_of_view
end
