Well I’d be best if you naturally “map” this positions out because it can become quick tedious very quickly, but it would obviously be automated by some code of some sort.
But for edge/corner detection what we do is.
- Send a ray in any direction to a surface to get the normal.
- After getting the normal we make another raycast aligned to the face and do some funny vector math to get the rightvector/upvector aligned to the part and this ray is only limited to the part. And it should be placed far enough too.
EDIT: I stole some code and played around with it and it works pretty good.
local function mark(cf: CFrame, color: Color3)
local part = Instance.new("Part")
part.Anchored = true
part.CanCollide = false
part.Material = Enum.Material.Neon
part.Size = Vector3.one * 0.5
part.Transparency = 0.5
part.Color = color or part.Color
part.CFrame = cf
part.Parent = workspace
game.Debris:AddItem(part, 1)
end
type FaceData = {
["Front" | "Back" | "Left" | "Right" | "Top" | "Bottom"]: { -- normalId works but i dont like enums..
Length: number,
CFrame: CFrame,
}
}
local AXIS_VECTORS: { [string]: Vector3 } = {
["Right"] = Vector3.xAxis,
["Left"] = -Vector3.xAxis,
["Top"] = Vector3.yAxis,
["Bottom"] = -Vector3.yAxis,
["Back"] = Vector3.zAxis,
["Front"] = -Vector3.zAxis,
}
local function getFaceNameFromNormal(v: Vector3): string?
for normalId, axis in AXIS_VECTORS do
if axis == v then
return normalId
end
end
return nil
end
local function axis(axis: Vector3, size: Vector3): number
return
math.abs(axis.X) * size.X +
math.abs(axis.Y) * size.Y +
math.abs(axis.Z) * size.Z
end
local function getHitFaceName(hit: BasePart, worldNormal: Vector3): string
local localNormal = hit.CFrame:VectorToObjectSpace(worldNormal)
local best, bestDot = "Top", -math.huge
for normalId, axis in AXIS_VECTORS do
local d = axis:Dot(localNormal)
if d > bestDot then
bestDot = d
best = normalId
end
end
return best
end
local function getFaceEdgesFromRaycast(raycastResult: RaycastResult): FaceData
local results: FaceData = {}
local hit, worldNormal = raycastResult.Instance, raycastResult.Normal
local halfSize = hit.Size * 0.5
local n = AXIS_VECTORS[getHitFaceName(hit, worldNormal)]
local u, v
if n.X ~= 0 then
u, v = Vector3.yAxis, Vector3.zAxis
elseif n.Y ~= 0 then
u, v = Vector3.xAxis, Vector3.zAxis
else
u, v = Vector3.xAxis, Vector3.yAxis
end
local nExtent, uExtent, vExtent =
axis(n, halfSize), axis(u, halfSize), axis(v, halfSize)
local faceCenter = n * nExtent
local c1 = faceCenter + u * uExtent + v * vExtent
local c2 = faceCenter + u * uExtent - v * vExtent
local c3 = faceCenter - u * uExtent - v * vExtent
local c4 = faceCenter - u * uExtent + v * vExtent
local edges = {
{ from = c1, to = c2, borders = getFaceNameFromNormal(u) },
{ from = c2, to = c3, borders = getFaceNameFromNormal(-v) },
{ from = c3, to = c4, borders = getFaceNameFromNormal(-u) },
{ from = c4, to = c1, borders = getFaceNameFromNormal(v) },
}
for _, edge in edges do
local localMid = (edge.from + edge.to) / 2
local localDir = edge.to - edge.from
local length = localDir.Magnitude
local localCFrame = CFrame.lookAt(localMid, localMid + localDir.Unit, n)
local worldCFrame = hit.CFrame * localCFrame
results[edge.borders] = {
Length = length,
CFrame = worldCFrame,
}
mark(worldCFrame, Color3.new(0, 1, 0))
end
return results
end
while true do
local result = workspace:Raycast(Vector3.zero, Vector3.new(0, 0, 1000))
if result then
print(getFaceEdgesFromRaycast(result))
end
task.wait(1)
end