Hey!
This is exactly what I am looking for, I’d greatly appreciate it if you could go more in depth.
And also, sorry for the late response.
Do you have basic grid building system yet or not?
At most what I managed to achieve is being able to build on surfaces and the grid as two separate things. The grid was simply just math.round() each axis, the building on surfaces is achieved with raycasting to the mouse position, placing the object on the hit point and rotating it using CFrame.fromMatrix() so it would be accurately placed and rotated.
Ok, I assume you’ve also offseted Mouse.Hit, (or the RaycastResult Equivalent) to avoid it placing the block inside the block you are placing it against? I wont go over that then
First of all, I’ll explain CFrame math, I’ve done some drawings some time back for this
So, in this one, you can see how “basic” CFrame multiplication works. CFrames are matrices (if you’ve done linear algebra, you don’t need to know linear algebra for what I’ll explain), but basically, matrix (and CFrame) multiplication is not commutative (A•B ~= B•A in most cases)
You can clearly see this with the two examples in my drawing, in the top example, the part is moved forward first (the LookVector points in the negative z direction on roblox for some reason), then rotated.
In the bottom example, it is rotated first, and then moved forward, but forward relative to the CFrame it multiplies. This is what makes CFrames so powerful (and hard to understand)
In the documentation, the bottom example is identical to using the :ToWorldSpace()
method: (ResultCFrame = CFrame.Angle(0,math.pi/4,0):ToWorldSpace(CFrame.new(0,-10,0))
)
The math.pi/4 is the 45° in my drawing. CFrame.Angles takes in radians rather than degrees)
In this case, the “object space” is the
CFrame.new(0,0,-10)
, since it is used as coordinates from the reference point of CFrame.Angle(0,math.pi/4,0)
. You can think of it like this as well:In dark blue, I’ve drew a new Cartesian coordinate system relative to the blue square. The orange square is at coordinates (0, -10) (x axis, z axis) from the blue square. This is what “object space” is
These examples are in 2D, but you can generalize it to 3D. It’s just a lot easier to imagine it in 2D
So far, do you have any questions?
I don’t. I understand the basics of CFrame, but when it comes to making something more advanced, such as this building system, that’s where my understand becomes limited.
The basic idea is to take the mouse position (that is relative to the origin) and instead rewrite it as being relative to the closest corner of the target part (mouse position being in the Cartesian coordinate system of the target part). Then it becomes extremely simple to round the position to the surface of that target part, it’s basically the exact same as a basic building system. You then have to convert the snapped grid coordinates back to world space (world space being the normal Cartesian coordinates)
Going from Object space to world space was covered in my previous reply (which is the final step of what I explained above), it’s the simpler of the two, and is used to convert the snapped grid position in object space to the final part CFrame in world space
Next up is going from the mouse position to the position relative to the target part, the actual first step. This is the more complicated one, as it requires using CFrame:Inverse() (or :ToObjectSpace()
), If you aren’t too comfortable with inverting CFrames, I can explain that step as well
I did try doing something similar to that, getting the relative mouse position to the target part, but it always left me with dodgy results, so I’d be thankful if you could explain it.
So, let’s say the orange square is the target part you want to build on, and the blue square is the mouse position (relative to the origin).
Inverting a CFrame is kind of like substraction, although with some differences. If the orange square was at coordinates x = 2, the blue square is at x = 5, then, to get the relative position, you’d do 5 - 2 = 3
. We do something similar with CFrames, but the order of operations is different.
Just like when doing substraction, we can think of 5
as being the position of the orange part 2
+ the position from orange to blue, 3
. With CFrames, we can think of it as, the blue square is orange’s CFrame, * the CFrame between the orange CFrame and the blue CFrame. So the idea is to cancel out orange’s CFrame, and we’ll be left out with the CFrame between the orange and the blue
The way to do this is by inverting orange’s CFrame. If you multiply the invert of a matrix (and CFrame), by the original CFrame, you’ll get the identity marix (aka the origin CFrame, in black). CFrame1:Inverse() * CFrame1 == CFrame.new(0,0,0) -- Should print true, but might not because of floating point precision error
This multiplication is also one of the rare cases where CFrame multiplication is commutative, CFrame1 * CFrame1:Inverse()
also works
Once you have the inverse of the CFrame of your target part, if you multiply blue’s square CFrame by that inverse, you will get the purple CFrame. The CFrame between the orange square and the blue square is the same as the CFrame bewteen Origin and the purple square. This is object space
You can also see that the CFrame from CFrame1:Inverse() to CFrame3 is the same as the CFrame between Origin and CFrame2 (the blue square)
However, it also kind of looks like going from the blue square to the purple square by doing CFrame2 * CFrame1:Inverse()
would work, but that is not the case. This multiplication is not commutative (it probably looks like it could work on the drawing because this is 2D, and not 3D). A general rule of thumb is to have :Inverse()
on the first CFrame, and the first CFrame be the target part
Since the blue square’s CFrame can be written as CFrame1 * CFrame3
, doing CFrame1:Inverse() * CFrame1 * CFrame3
makes CFrame1:Inverse() * CFrame1
cancel out (to the identity matrix, or the origin CFrame), and you are left with CFrame3
. Doing CFrame1 * CFrame3 * CFrame1:Inverse()
wont make CFrame1
and CFrame1:Inverse()
cancel out, since multplication is not commutative
You can also use :ToObjectSpace()
, which can be more intuitive
Using
:ToObjectSpace()
, the math to get CFrame3 would be CFrame3 = CFrame1:ToObjectSpace(CFrame2)
Any questions?
I think I understood everything with :inverse().
So, we need to use :ToObjectSpace() (the same as CFrame1:inverse() * CFrame2) on the CFrame1 (orange) and use CFrame2 (blue) as the parameter? CFrame1 being the target part and CFrame2 being mouse position (or in my case, raycast hit position)
Yep, exactly!
In my code, I first get the RelativeCFrame (in my code it’s RelativePosition, but it’s a CFrame)
Then I apply the rounding, which is basically a basic grid rounding system
Then I transform the rounded coordinates back to WorldSpace, with CFrame multiplication
My code has a bit more bells and whistles, you can ignore those
My part CFrame is also not the actual Part CFrame
I modified it to be the Closest Corner (position of the closest corner, but this CFrame still has the same orientation as the target part), it would work if you kept it as the PartCFrame, but then it would snap your part relative to the center of the TargetPart, which is not ideal
I encourage you to do your own implementation (that will look very similar) instead of copying my code. Understanding more complex CFrame math can help you make some really cool things, other than just a building system
Hey. I just had something traumatic happen literally a few minutes ago.
Can I add you on somewhere and message you later with this problem?
You can send me a private message on the devforum if needed
I already made one of these grid systems before.
So I am happy to share
This won’t be the whole code but enough to work
This will align to grid
function snapvec3(vec3:Vector3, length:number)
if(length==0)then error("length cannot be 0") end
return (vec3/length):Floor()*length;
end
Use mouse’s things to get stuff
function GridPlacement(Object,snapgridsize)
local Surface=Mouse.TargetSurface;
local PositionCF=Mouse.Hit;
local Object=Mouse.Target;
if(not Object)then return end;--if mouse is not on object
local FaceOffset=Object.CFrame*CFrame.new(Vector3.fromNormalId(Surface)*Object.Size/2);--GetCFrame of the middle of the face
local PositionOnFace=FaceOffset:ToObjectSpace(PositionCF);--get the position relative to the face
local snappedPositionOnFace=snapvec3(PositionOnFace.Position,snapgridsize);--basically snap to grid
--there are a bunch of other stuff that can be implemented
--to get a better grid placement, but this is trying to be simple
--this example does not account for face rotation.
local FinalPosition=FaceOffset*CFrame.new(snappedPositionOnFace);
Object.CFrame=FinalPosition;
end