Point (1) can be solved with a constructor like Sphere(v,d,m) = Sphere{typeof(m)}(v,d,m)
I played a little with the code because of (2). I would do two things with the HitPoint
type: make it immutable and parametrize the material as well. Thus, in the end I did this:
# ╔═╡ 3b570d37-f407-41d8-b8a0-a0af4d85b14d
"Record a hit between a ray and an object's surface"
struct HitRecord{M}
t::Float32 # vector from the ray's origin to the intersection with a surface.
# If t==Inf32, there was no hit, and all following values are undefined!
#
p::SVector{3,Float32} # point of intersection between an object's surface and ray
n⃗::SVector{3,Float32} # local normal (see diagram below)
# If true, our ray hit from outside to the front of the surface.
# If false, the ray hit from within.
front_face::Bool
mat::M
end
HitRecord() = HitRecord{NoMaterial}(Inf32,zero(SVector{3,Float32}), zero(SVector{3,Float32}), false, NoMaterial()) # no hit!
HitRecord(t,p,n⃗,front_face,mat) = HitRecord{typeof(mat)}(t,p,n⃗,front_face,mat)
# ╔═╡ 138bb5b6-0f45-4f13-8339-5110eb7cd1ff
struct Sphere{M} <: Hittable
center::SVector{3,Float32}
radius::Float32
mat::M
end
Sphere(c,r,m) = Sphere{typeof(m)}(c,r,m)
(note the constructors that solves problem 1 here as well)
That, unfortunately, didn’t improve performance, and why is that is a bit more complicated, probably. You are reaching probably a dynamic dispatch point inside the hit
function, and making the types concrete is not helping. In general those would be good advice, I think.
Probably on alternative would be to make a concrete more general “Material” which might carry the properties of all materials and a flag, to avoid run time dispatch. But that is less elegant than what you have.