so i’m going to take the liberty of simplifying some of what you’re doing, then i’ll ask that you restructure client-server communication such that we’re passing the CFrame component and not a decomposition of positions and angles. the difference between serializing position and orientation components relative to the entire CFrame, i think, is negligible considering that we don’t communicate this information to the server very rapidly and we are already sending a lot of other information so whats some more in the grand scheme of preformance? sending the whole cframe rather than a decomposition of it has the added benefit of not allowing for errors in the process of translation.
as for simplification, roblox’s workflow for working multipart assets is a bit neglected but i’ll give you two tips for dealing with this.
- first is to make a plugin that lets you post-process the assets you’re using for the building mechanic by adding a extra bounding box part and setting the model’s primary part to that.
- if you can, use imported meshes (blender fbx to roblox export scale is 0.01, prototype in roblox replicate in blender and import) or union the thing the geometry permits it.
- as an addition for #1, if you look around you can find some stuff that calculates the center of a collection of geometries rather than the center of bounding box using some mass heuristics but it’s expensive and complicated.
as for the code what you’re trying to do is find surface and position the part relative to that. here is the code for that:
local CFrameU = {}
function CFrameU.getRotationBetween(u, v, axis)
local dot, uxv = u:Dot(v), u:Cross(v)
if (dot < -0.99999) then return CFrame.fromAxisAngle(axis, math.pi) end
return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
end
local EXTRASPIN = CFrame.fromEulerAnglesXYZ(math.pi/2, 0, 0)
function CFrameU.getSurfaceCF(part, lnormal)
local transition = CFrameU.getRotationBetween(Vector3.new(0, 1, 0), lnormal, Vector3.new(0,0, 1))
return part.CFrame * transition * EXTRASPIN
end
return CFrameU
here lnormal is typically going to be the look vector of the instance of Mouse.Hit
is targeting. as an example use case i can cast a ray and position two parts around the rays hit position with something like:
RunService.RenderStepped:Connect(function()
local cf = LocalPlayer.Character:GetPrimaryPartCFrame()
local rayResult = workspace:Raycast(
cf.p,
cf.LookVector * armLength,
armRayParams
)
if rayResult then
local surfaceNorm = CFrameU.getSurfaceCF(rayResult.Instance, rayResult.Position - rayResult.Instance.Position)
local offset = rayResult.Position - rayResult.Instance.Position
visL.CFrame = surfaceNorm * CFrame.new(-armWidth, 0, 0) + offset
visR.CFrame = surfaceNorm * CFrame.new(armWidth, 0, 0) + offset
end
end)
except for your use case you’ll want to account for the models bounding box in offset. if your primary part is a bounding box part like mentioned in #2 then it’s just a matter of adding BoundingBox.Size / 2
to offset
. here is a working example: