Best way to check magnitude from surface?

Let’s say I have a tree model
image

and I have a player attacking this tree model
image

What’s the best way to detect how far away the person is from the tree?

  • Method 1 Simple Trunk.Position:
    This is bad because it would look like this
    (the entire blue area)
    image

  • Method 2 Raycast
    I do a raycast with whitelist to the Trunk.Position.
    Then I check the magnitude based on where it hits:
    image

  • Method 3 Region3
    I use a region3 with whitelist (maybe even a RotatedRegion3)
    If the player is within that area then he’s close enough to be attacking the tree
    image

Method 2 definitely has it’s drawbacks, but how much more efficient would it be than method 3?
Is there a better method than method 3?

1 Like

Raycasting would be the best option, it is very optimized and you can raycast 60 times a second and it wouldn’t cause lag.

Instead of raycasting to the trees position, raycast straight in front of the player, because it is more accurate.

This belongs in #help-and-feedback:game-design-support.

2 Likes

I think the magnitude approach is even faster than raycasting since all Vector3 values naturally have a defined Magnitude property which is why you can easily read magnitudes of vectors very fast down to RunService events without any loss of performance.

@VegetationBush
Raycasting, with non-spherical shapes, isn’t the best option.
If you had a scenerio like this:
image
The tree might look close to both people from their perspective
image
But we get pretty different results using just raycasts.

This doesn’t have anything to do with core game loops, game feel, or anything like that. It’s more of a technical problem. I already have working code, I’m just trying to improve it. I’ve presented in a visual manner to make it easier to digest.

@ArtFoundation
Magnitude isn’t the best option either for basically the same reason.
That might work fine if the object was smaller or more spherical in nature, but it wouldn’t work well with something large and rectangular.

Correct! Magnitude is by far the fastest in terms of how it’s implemented on the C++ side of things. However, it seems @dispeller is really keen that the distance between the character and the tree be exact.


If you want precision, consider casting a ray from the player to the closest point on the model. This can be done by iterating through the relevant parts on the model and doing the following per part:

  • Transform your reference point of the character to the local space of the part.
  • Compute the closest point on the axis-aligned bounding box through clamping the value as appropriate based on the part’s dimensions.
  • Take the computed point and transform it back to the previous reference frame.

This is computationally intensive relative to doing a magnitude check or even using rays. It also becomes more complex if you try to bypass axis-aligned bounding-boxes as you then have to check said point is in the same geometric shape as the part, essentially, computing more complex bounding volumes; you have to ensure the part is intended to be viewed as part of the model; you also will have an extremely hard time when dealing with custom shapes, although even for how precise you desire that amount of error is negligible.


Lazily raytracing from the player to the closest-ish point within a bounding volume isn’t so bad either, restricting it to a single axis to remove variance.

2 Likes

I could calculate the closest point to character on the part’s surface by clamping, but that gets complicated (especially when you deal with rotated bricks)

Restricting to a single axis to remove variance isn’t a bad idea either, though I’d have to calculate which axis to restrict.

I think method 3, Region3 with whitelist, seems to be the best solution.
I’ll probably just end up using EgoMoose’s Rotated Region 3 module.

Oh you want how close they are via the surface and not central position? Your post didn’t make that clear enough as I was confused.

You can get a unit vector which is a vector difference between the player and tree central position, multiply that by half the size of the tree wood, then do a magnitude calculation with this new result and I think that could work.

Region3 I don’t think is as optimized for very fast usage compared to the other two but I could be wrong.

1 Like

I don’t entirely see your logic there.
I tested it out to be sure but that doesn’t seem to work.

Player = game.Players.LocalPlayer
Char = Player.Character
HRP = Char:WaitForChild('HumanoidRootPart')
Tree = workspace.Part

while wait() do
	local justMagnitude = (HRP.Position-Tree.Position).Magnitude
	local difference = (HRP.Position-Tree.Position).Unit
	local distanceVector = difference*(Tree.Size/2)
	print(distanceVector,'|',distanceVector.Magnitude,'|',justMagnitude)
end

Define the following:
h = height of trunk
p = position of player
t = position of trunk

A decent way to get the closest distance from the player to the trunk would be the following:

local function GetDistanceFromTree(h: number, p: Vector3, t: Vector3): number
    local rawDifference = p-t;
    local horizontalDifference = rawDifference*Vector3.new(1,0,1);
    return (horizontalDifference+Vector3.new(0,math.max(0,math.abs(rawDifference.Y)-h/2))).Magnitude;
end

What I’m doing is getting the horizontal distance from the tree, then taking into account the vertical distance IF the player is either above or below the tree.

1 Like

This seems to still have the same issue that magnitude regularly has.
Also, it’s kinda impossible to do what I’m trying to do without knowing the width of the trunk.

If it’s non spherical, it’s non spherical. If you raycast and you hit the corner of the part, you hit it, it’s accurate, and that’s what the players want.

Magnitude and rays are not intensive. They are much more optimized than you think they are

Region3’s wouldn’t be the best option, in my opinion. Because regions cannot be rotated and is far more intensive than raycasts or magnitude checks.


My solution only needs one raycast, depending on the situation, relative to the LookVector of the player. The is efficient because just raycasting straight forward is basically all you have to do, hence the fact that no one attacks backwards:

local AttackDist = 3 -- How far the player can reach
local Ray = Ray.new(HumanoidRootPart.Position, HumanoidRootPart.CFrame.LookVector.Unit * AttackDist)

local Part = workspace:FindPartOnRay(Ray)
if Part then
    if Part.Name == "Treetrunk" then
        -- do something
    end
end
1 Like

You treat the newly made vector you created from multiplying the unit by the size/2 as a new position vector that essentially replaces using central position of the tree.

Basically, my logic is that the vector you created will be from central position to the closest point of the surface to the player.

The Unit part may need to be reversed since I am unsure what order to place the vectors in so if it doesn’t work, reverse the subtraction order (tree to HRP).

Also, to make sure I am understanding correctly, you want to detect how close a player is from the surface of the tree? Or to be within range to hit it?

1 Like

Oh I understand what you’re saying now.
Wow, why didn’t I think of that?
I might end up doing that.

also, there’s a module that allows you to rotate regions


@ArtFoundation I think I’m starting to understand what you’re saying as well. I got addition and multiplication mixed up when it comes to Vector3s. Also, you forgot to mention that you would have to add the resulting vector to the Tree position’s part.
I tested it out and it looks like that still makes a circular ellipse when you go around

Player = game.Players.LocalPlayer
Char = Player.Character
HRP = Char:WaitForChild('HumanoidRootPart')
Tree = workspace.Part

visualPart = Instance.new('Part',workspace)
visualPart.Size = Vector3.new(1,1,1)
visualPart.Name='visual'
visualPart.Anchored = true

while wait() do
	local justMagnitude = (Tree.Position-HRP.Position).Magnitude
	local difference = (Tree.Position-HRP.Position).Unit
	local distanceVector = difference*(Tree.Size/2)
	distanceVector = Tree.Position+distanceVector
	visualPart.CFrame = CFrame.new(distanceVector)	
	--print(distanceVector,'|',distanceVector.Magnitude,'|',justMagnitude)
	print(distanceVector.Magnitude,justMagnitude)
end

Option four? No raycasting or areas or anything.

-- returns distance from a part's centerline to a position
local function GetDistanceToTree(tree, pos)
	local trunkLen = tree.Size.Y
	local origin = tree.Position
	local a = tree.CFrame.UpVector
	local b = (pos - origin)
	
	local projLen = b:Dot(a)
	
	-- clamp to tree top/bottom (could remove if you don't want)
	if (projLen < -trunkLen / 2) then projLen = -trunkLen / 2 end
	if (projLen > trunkLen / 2) then projLen = trunkLen / 2 end
	
	local proj = origin + a * projLen
	
	return (proj - pos).magnitude
end

-- usage e.g.
while (wait(0.5)) do
	local dist = GetDistanceToTree(workspace.TreeTrunk, game.Players.LocalPlayer.Character.HumanoidRootPart.Position)
	print("Distance to tree: ", dist)
end
1 Like