How to reflect rays on hit


#1

Reflecting rays using raycasting

Want bullets to ricochet? Lasers to bounce? Custom Pong game? Here's a simple way to reflect a ray when it hits a surface using the surface normal returned by FindPartOnRay!


Formula
We will be working with this equation:
[source]

r = d - 2(d β‹… n)n

Where 'r' is the resultant reflection normal, 'd' is the normal of our ray, and 'n' is the surface normal that our ray hit (n will be the value FindPartOnRay returns).

Because we are doing reflections, we will end up casting more than 1 ray. So instead of basing our "laser" rays on a single ray cast, we can string a bunch of them together. We can limit this to either a certain distance overall or a certain number of raycast iterations. Whichever suits you best.


Casting a ray
First, how do we apply that formula above? Let's take a look at a single ray cast:

local startCFrame  = part.CFrame -- Where we derive our position & normal
local normal = startCFrame.lookVector
local position = startCFrame.p
local ray = Ray.new(position, normal * 500)
local hit, position, surfaceNormal = game.Workspace:FindPartOnRay(ray) -- Cast ray

Reflecting the ray
Ok, so now we've cast our ray outward 500 studs. In this example, I'm using a part's CFrame property to initialize where the ray will begin. This is practical since you will probably be doing something similar (e.g. the tip of a gun). Next, we need to see if the ray hit anything. If so, we need to reflect the ray:

if (hit) then
	-- Get the reflected normal: (this is the formula applied)
	local reflectedNormal = (normal - (2 * normal:Dot(surfaceNormal) * surfaceNormal))
	-- Override our current normal with the reflected one:
	normal = reflectedNormal
end

In that code chunk, we calculated the reflected normal using the first formula shown. Then, we overrode our current 'normal' value with the reflection normal. The next step would be to reiterate this process multiple times. The flow should look like this:

  1. Cast Ray
  2. Update current position with new position (end of ray)
  3. If hit, find reflection, override normal with reflected normal
  4. If not hit, do nothing
  5. If max iterations/distance/whatever reached, RETURN/STOP
  6. Else, recurse; go back to 1 and cast again

Example code:
Put script into a Part. This Part will shoot a laser part when Shoot() is called. This is my first write-up of this code. I have written it in preparation of a small game I'm making. Feel free to use it and edit it as you like. I'll probably heavily edit it myself too.

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 = 400
	local curDistance = 0
	
	local stepDistance = 4
	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()
	
	-- Done! Destroy laser:
	laser:Destroy()
	
end

Shoot() -- Fire shot!

Example:


Edit:
If I made a mistake or something wasn't clear, let me know. I'm simply apply a formula here that I'm not super familiar with yet, so I'm sure it's prone to error. The application of it seems to work properly though as far as I've seen.

Edit 2:
Reformatted to fit with new forum.


How can I cast reflective rays?
#2

At this point it looks like you're making lasers.

https://www.youtube.com/watch?v=xGDGh6Q5X-U


#3

It's possible that I might be making my own twist of laser tag


#4

The first thing I thought of when I saw this was Terraria's Shadowbeam Staff. So, I quickly remade it: http://gfycat.com/PolishedJovialBoilweevil Kinda a little crappy, doesn't do damage, stuff like that. Nonetheless, I thought it was pretty cool to see the staff in 3d.


#5

I think it's funny I made this a day before you did. :smile:

We are connected. Creepy space sound


#6

http://www.roblox.com/games/293887672/PEW-PEW-GUNS


#7

I posted this on Twitter earlier yesterday


#8

I've updated this to be correctly formatted on this new forum.


#9

I just wanted to let you know that your tutorial is still very relevant, and this just help me bring my game from good to great. Thank you for posting this, I can’t wait to implement the math!


#10

3 Years Wow!
Actually this seems handy for a new game I plan to create, bouncing lasers? :open_mouth: never thought of that idea until I saw this thread.


#11

Thats an interesting way of doing that, nice