module SimpleRayTracer using LinearAlgebra using Images using GeometryBasics using Rotations export OrthogonalCamera export World, Ray export Sphere abstract type AbstractCamera end abstract type AbstractObject end struct OrthogonalCamera{T<:AbstractFloat} <: AbstractCamera origin::Vec3{T} rotation::RotXYZ{T} size::Tuple{T, T} # function OrthogonalCamera(o, θ, s) # new(o, RotXYZ(0.0, 0.0, 0.0), s) # end end function get_rays(camera::OrthogonalCamera, resolution::Tuple{I, I}) where {I<:Integer} X, Y = camera.size rx, ry = resolution origins = [Vec3(x, y, 0.0) + camera.origin for x = LinRange(-X, X, rx), y = LinRange(-Y, Y, ry)] [Ray(o, Vec3(0.0, 0.0, 1.0)) for o in origins] end struct Ray{T<:AbstractFloat} origin::Vec3{T} direction::Vec3{T} # function Ray{T}(o, d) where {T<:AbstractFloat} # new(o, d |> normalize) # end end struct HitInfo hit::Bool color::RGB{N0f8} end struct World objects::Vector background::RGB{N0f8} function World(color) new([], color) end end Base.push!(w::World, x::AbstractObject) = push!(w.objects, x) struct Sphere{T<:AbstractFloat} <: AbstractObject origin::Vec3{T} radius::T color::RGB{N0f8} end function check_hit(ray::Ray{T}, sphere::Sphere{T})::Tuple{Bool, T} where {T <: AbstractFloat} p = ray.origin - sphere.origin a = dot(ray.direction, ray.direction) b = 2 * dot(p, ray.direction) c = dot(p, p) - sphere.radius^2 Δ = b^2 - 4a*c Δ < 0.0 && return false, -1.0 x, z = -b/(2a), √Δ/(2a) t = x - z t = t < eps() ? x + z : t return t < eps() ? (false, -1.0) : (true, t) end function trace_ray(world::World, ray::Ray)::HitInfo mindist = Float64 |> typemax |> prevfloat hit = false color = world.background for obj in world.objects hit, dist = check_hit(ray, obj) if hit && dist < mindist mindist = dist hit = true color = obj.color end end return HitInfo(hit, color) end end