How can I use Raycasts on WorldModels?

I would like to use UserInputService to detect which parts inside a ViewportFrame were clicked. With the addition of a WorldModel, raycasts are possible. However, I’m looking for some example code on how to do this using both UserInputService and Raycasting.

I’ve tried to look up some solutions to this, but I could not find any. Unfortunately, there were quite some instances where some people asked the same question and received no response, received a solution before WorldModels were implemented, or simply lacked an example of how to achieve this.

3 Likes

this might help

1 Like

This piece of code is incomplete. As stated already, I already know raycasting is possible using WorldModels. I am trying to make it actually work.

Interesting, yeah couldn’t find much googling about it.

However I found this that works

I also customized the function used with UserInputService and GuiService for GUI inset.

local GuiService = game:GetService("GuiService")
local UserInputService = game:GetService("UserInputService")

local function RaycastInViewportFrame(viewportFrame, raycastDistance, raycastParams)
	raycastDistance = raycastDistance or 1000
	local camera = viewportFrame.CurrentCamera
	local worldModel = viewportFrame:FindFirstChildWhichIsA("WorldModel")
	local mousePosition = UserInputService:GetMouseLocation() - GuiService:GetGuiInset() - viewportFrame.AbsolutePosition -- account for viewportframe offset
	local relativePosition = ((mousePosition - (viewportFrame.AbsoluteSize/2)) * Vector2.new(1, -1))/(viewportFrame.AbsoluteSize/2) -- get the relative position of the click with center of viewportFrame as origin: -1 is left/bottom and 1 is right/top for X and Y respectively
	local projectedY = math.tan(math.rad(camera.FieldOfView)/2)*raycastDistance -- the projected height of a 2D frame raycastDistance studs from the camera with same aspect ratio
	local projectedX = projectedY * (viewportFrame.AbsoluteSize.X/viewportFrame.AbsoluteSize.Y) -- projected width from aspect ratio
	local projectedPosition = Vector2.new(projectedX, projectedY) * relativePosition -- the projected position of the input on the similar frame
	local worldPosition = (camera.CFrame * CFrame.new(projectedPosition.X, projectedPosition.Y, -raycastDistance)).Position -- the 3d position of said projected position
	
	local part = visualizeRay(camera.CFrame.Position, (worldPosition - camera.CFrame.Position).Unit * raycastDistance)
	part.Parent = workspace
	local part = visualizeRay(camera.CFrame.Position, (worldPosition - camera.CFrame.Position).Unit * raycastDistance)
	part.Parent = worldModel
	local part = visualizeRay(camera.CFrame.Position, (worldPosition - camera.CFrame.Position).Unit * raycastDistance)
	part.Parent = VPF

	return worldModel:Raycast(camera.CFrame.Position, (worldPosition - camera.CFrame.Position).Unit * raycastDistance, raycastParams)
end

Edit: Whoops left the visualization functions within it, here it is anyway since it helps see how it works

visualizeRay
local function visualizeRay(pos, vector)
	local distance = vector.Magnitude
	local p = Instance.new("Part")
	p.Anchored = true
	p.CanCollide = false
	p.Size = Vector3.new(0.5, 0.5, distance)
	p.BrickColor = BrickColor.Random()
	p.CanQuery = false
	p.CFrame = CFrame.lookAt(pos, pos+vector)*CFrame.new(0, 0, -distance/2-5)
	game.Debris:AddItem(p,1)
	return p
end
3 Likes

This is an odd solution, but it is a solution nonetheless. For anyone else wanting to use this, you would need to remove any references to the visualizeRay functions as well as the part variables.

2 Likes