How can I cast reflective rays?

Hello Developers,

I’m currently trying to develop reflective rays to create the illusion of Rays “bouncing off” a disco ball.

However, my partner @xM_ighty and I cannot seem to find a solution for ray casting. We have had multiple attempts.

Here is our closest we’ve ever gotten with raycasting.
image
image

Here is another attempt:

   local Part = Instance.new("Part")
    Part.Name = "Beam"
    Part.Transparency = .25
    Part.Reflectance = .25
    Part.FormFactor = "Custom"
    Part.Anchored = true
    Part.BrickColor = BrickColor.Red()
    Part.Size = Vector3.new(.2, .2, (Current - Ray_Pos).Magnitude)
    Part.CFrame = CFrame.new(Current + (Ray_Pos - Current) / 2, Ray_Pos)
    Part.Parent = workspace.Ignore
end
 
function Not_Zero(Value)
    local Up = math.ceil(Value + .1)
    local Down = math.floor(Value - .1)
    if Up ~= 0 and Up ~= -0 then
        return Up
    elseif Down ~= 0 and Down ~= -0 then
        return Down
    end
end
 
Current = Vector3.new(0, 3, 0)
Heading = Vector3.new(0, 0, -1)
 
Ray_Part, Ray_Pos = workspace:FindPartOnRay(Ray.new(Current, Heading * 900), workspace.Ignore)
if Ray_Part then
    Trace_Ray(Ray_Pos)
    Current = Ray_Pos
    Angle = Ray_Part.CFrame.lookVector
    local R_X, R_Z = (Angle.X + Heading.X) / 2, (Angle.Z + Heading.Z) / 2
    Heading = Vector3.new(R_X * -Heading.Z, 0, R_Z * -Heading.X).unit
end
repeat
    Ray_Part, Ray_Pos = workspace:FindPartOnRay(Ray.new(Current, Heading * 250), workspace.Ignore)
    if Ray_Part then
        Trace_Ray(Ray_Pos)
        Current = Ray_Pos
        Angle = Ray_Part.CFrame.lookVector
 
        local R_X, R_Z = (Angle.X + Heading.X) / 2, (Angle.Z + Heading.Z) / 2
        Heading = Vector3.new(R_X * -Heading.Z, 0, R_Z * -Heading.X).unit
    end
until not Ray_Part or Ray_Part.Name == "Stop"
Trace_Ray(RayPos)
 
-- Calculate real-time laser reflections!
while wait() do
    Current = workspace.Emitter.Position
    Heading = workspace.Emitter.CFrame.lookVector
    for ,Beam in ipairs(workspace.Ignore:GetChildren())do
        Ray_Part, Ray_Pos = workspace:FindPartOnRay(Ray.new(Current, Heading * 900), workspace.Ignore)
        if Ray_Part then
            Beam.Size = Vector3.new(.2, .2, (Current - Ray_Pos).Magnitude)
            Beam.CFrame = CFrame.new(Current + (Ray_Pos - Current) / 2, Ray_Pos)
            Current = Ray_Pos - Heading * .1
            Angle = Ray_Part.CFrame.lookVector
            local R_X, R_Z = (Angle.X + Heading.X) / 2, (Angle.Z + Heading.Z) / 2
            Heading = Vector3.new(R_X * -Heading.Z, 0, R_Z * -Heading.X).unit
        else
            Beam:Destroy()
        end
    end
    Ray_Part, Ray_Pos = workspace:FindPartOnRay(Ray.new(Current, Heading * 900), workspace.Ignore)
    if Ray_Part and #workspace.Ignore:GetChildren() < 15 then
        Trace_Ray(Ray_Pos)
    end
end

Any help would be appreciated. Thank you.

UPDATE:
Thanks for all the great responses, it helped us through the process a little bit. But after a while, we hit a brick wall. Literally and metaphorically. our ray was casted and it did reflect off the wall. The laser ray is casted off the block and reflected off of 2 walls.
image
But however, when I place a block rotated at 45 degrees, I expected the ray to reflect off the 45 degree wall and in the direction of that wall. But however, this occurs.
image
No ray is casted, nor reflected.
Here is our script placed within the block:

function Shoot()
	
	local laser = Instance.new("Part")
	laser.Name = "Laser"
	laser.FormFactor = Enum.FormFactor.Custom
	laser.TopSurface, laser.BottomSurface = 0, 0
	laser.Size = Vector3.new(0.2, 0.2, 0.2)
	laser.BrickColor = BrickColor.Random()
	laser.Anchored = true
	laser.CanCollide = false
	laser.Locked = true
	laser.CFrame = script.Parent.CFrame
	laser.Parent = game.Workspace
	
	local maxDistance = 50000000
	local curDistance = 0
	
	local stepDistance = 500
	local stepWait = 0
	
	local currentPos = script.Parent.Position
	local currentNormal = script.Parent.CFrame.lookVector
	
	local function Step(overrideDistance)
		
		-- Cast ray:
		local ray = Ray.new(currentPos, currentNormal * (overrideDistance or stepDistance))
		local hit, pos, norm = game.Workspace:FindPartOnRayWithIgnoreList(ray, {script.Parent})

		-- Update laser position:
		laser.Size = Vector3.new(0.4, 0.4, (pos - currentPos).magnitude)
		laser.CFrame = CFrame.new(currentPos:lerp(pos, 0.5), pos)
		
		local oldPos = currentPos
		currentPos = pos
		
		if (hit) then
			-- r = d - 2(d DOT n)n
			-- Reflect:
			local reflect = (currentNormal - (2 * currentNormal:Dot(norm) * norm))
			currentNormal = reflect
			Step(stepDistance - (pos - oldPos).magnitude)
			return
		end
		
		curDistance = (curDistance + (pos - oldPos).magnitude)
		
		-- Apply fade effect to laser as it approaches max distance from < 75 studs:
		if (curDistance > (maxDistance - 75)) then
			local d = (curDistance - (maxDistance - 75)) / 75
			laser.Transparency = d
		end
		
		-- Recurse if max distance not reached:
		if (curDistance < maxDistance) then
			wait(stepWait)
			Step()
		end
	end
	
	Step()
	

	
	
	
	
end

Shoot()
wait(1)

(Note that I changed the maxDistance to an exaggerated amount to test the reflection physics. However, it’s not making any sense)
Any thoughts or suggestions?
Thank you.
-Andy

2 Likes
2 Likes

Thanks Kian, editing post

Uhh a little confused if I’m interpreting the question right, because I’m not seeing a disco ball or anything like it in the pictures linked.

Regardless, if I do understand the question then have you checked the dot product article? It has a section on reflecting rays.

3 Likes
1 Like

An issue I see with that tutorial’s application is that afaik firing a ray at a sphere will return a surface normal made for a brick. It would need some modifications to work with reflecting off a sphere.

NormalId documentation

1 Like

I’ve used this before, and it bounces off of spheres perfectly.

1 Like

Exactly what I’m looking for. Thank you.

1 Like

Yes, this is also extremely useful; sorry for the confusion I just didn’t know how to explain it well. But however, thank you. Now i need to keep the Trig Functions in mind

NormalId is completely irrelevant and no that is if you use a brick and a sphere special mesh which is different from using an actual sphere. Rays are designed to intersect the collision data of objects. The raycasting functions return a surface normal vector which represents a directional unit vector that points away from the contact surface. The collision data for spheres is completely round (due to the radius based calculations instead of using polygons) so it will point away from the center no matter its size (since spheres themselves are constrained to be regular shapes).

1 Like

Noooo! Could you please, if possible, provide another link? the provided one doesn’t seem to work, and I couldn’t get the info within it.

You are in luck!

7 Likes