User:NXTBoy/Scripts/LightRay

This is a slightly updated copy of the code running in my raycasting place. This class made everything much more fun to work with - ray the wrong length? ray.length = 50.

local LightRay = {}
__private = setmetatable({}, {__mode="k"})

--[==========================[Getters and Setters ]==========================]
LightRay.__getters = {}
function LightRay.__getters.endPoint(p)
	return p.endPoint or p.startPoint + p.direction
end
function LightRay.__getters.direction(p)
	return p.direction or  p.endPoint - p.startPoint
end
function LightRay.__getters.length(p)
	return LightRay.__getters.direction(p).magnitude
end
function LightRay.__getters.unitRay(p)
	return Ray.new(p.startPoint, getters.direction(p).unit)
end

LightRay.__setters = {}
function LightRay.__setters.length(p, length)
	p.direction = getters.direction(p).unit * length
	p.endPoint = false
end
function LightRay.__setters.startPoint(p, startPoint)
	p.endPoint = getters.endPoint(p)
	p.direction = false
	p.startPoint = startPoint
end

--[===============================[Metatable ]===============================]
function LightRay:__index(k)
	local properties = __private[self]
	local getter = self.__getters[k]
	return getter and getter(properties) or properties[k] or LightRay[k]
end
function LightRay:__newindex(k, v)
	local properties = __private[self]
	local setter = self.__setters[k]
	if setter then setter(properties, v) end
end
function LightRay.__mul(a, b)
	if type(a) == "number" then
		a, b = b, a
	end
	if type(b) == "number" then
		return LightRay.new(a.startPoint, a.direction * b, a.color)
	else
		error("Invalid argument!")
	end
end

--[==============================[Constructors]==============================]
function __createRay(props)
	local ray = setmetatable({}, LightRay)
	__private[ray] = props
	return ray
end
function LightRay.new(origin, direction, color)
	if not origin or not direction then error("Argument is Nil") end
	return __createRay({
		startPoint = origin,
		direction = direction,
		endPoint = false,
		color = color
	})
end
function LightRay.between(startPoint, endPoint, color)
	if not startPoint or not endPoint then error("Argument is Nil") end
	return __createRay({
		startPoint = startPoint,
		direction = false,
		endPoint = endPoint,
		color = color
	})
end

function LightRay.fromRay(ray)
	return LightRay.new(ray.Origin, ray.Direction)
end

--[============================[Member Functions]============================]
function LightRay:toRay()
	return Ray.new(self.startPoint, self.direction)
end

function LightRay:draw(diameter)
	local beam = game.Lighting.Beam:clone()
	beam.Size = Vector3.new(diameter, self.length, diameter)
	beam.CFrame = CFrame.new(self.startPoint + self.direction / 2, self.endPoint)*CFrame.Angles(math.rad(90), 0, 0)
	beam.BrickColor = self.color or beam.BrickColor
	return beam
end

function LightRay:raycast(filter)
	if self.length > 1000 then
		print("WARNING: Ray length ("..self.length..") must be <= 1000. Truncating ray")
		self.length = 999 --Avoid rounding errors at all cost
	end
	
	local part, hitPoint = workspace:findPartOnRay(self:toRay(), filter)
	
	if not hitPoint then
		hitPoint = self.endPoint
	end
	
	local s, b = self:split(hitPoint)
	return part, s, b
end

function LightRay:specularReflection(normal)
	normal = normal.unit
	local incident = self.direction
	local dotProduct = normal:Dot(incident)

	--prevent internal reflection
	local reflected = dotProduct < 0 and incident - 2*dotProduct*normal or incident
	
	return LightRay.new(self.startPoint, reflected, self.color)
end

function LightRay:split(point)
	if not point then error("Argument is Nil") end
	return LightRay.between(self.startPoint, point, self.color),
		   LightRay.between(point, self.endPoint, self.color)
end

function LightRay:clone()
	return LightRay.new(self.origin, self.direction, self.color)
end