Making a placement system work with rotated parts

Heya DevForum;

Am making a placement system as seen below - players can rotate parts/models on the Y and Z axis by pressing R or T respectively. I want the parts to be placed on top of the white floor, but when the parts are rotated they start to stick through.

Here’s the current behavior:
(I can’t upload MP4 files to this website, dunno why. So here’s some photos instead.)

It works off the part’s size and determines the offset based off of that, which works fine for the un-rotated brick:
image

It fails massively when the part is rotated on the Z axis:
image

It also fails when the mouse is on a previously placed part:
image

It currently snaps parts to a 0.5 stud grid.

I’d like to be able to move parts into the correct position no matter the rotation or if the part is near a wall or other object.

How would I go about doing this? Would I need to work out which direction each face is pointing and work out the offset for the part? How can I stop it from going within other parts like the third example?

Many thanks :smiley:

3 Likes

You have a couple of options, depending on exactly how you want it to work. If the collision detection needs to be precise, you’re inevitably going to end up with a rather complex set of checks using something like GetTouchingParts(). There are simple geometric special cases you can solve with a bit of math, like if all your parts are just rectangular boxes, but I’m assuming you’re going to have more complex things like meshparts.

Another thing that you might find useful would be to put the part being manipulated into a Model, so that you can use Model:GetBoundingBox(). This will tell you the bounding box (extents) and position in the coordinate system of the Model’s primary part. If you want an axis-aligned bounding box (world extents), you can put a tiny, invisible part in the model too which is axis aligned, and make that the primary part.

3 Likes

You can use my code in the post below to find the AABB of any part or set of parts without adding them to a model.

I also have a GJK distance module which I’ve posted example usages of here:

and here:

Calling the GJK module costs about as much as a call to print to prove that two parts are intersecting, and when they are clearly separated is very, very fast. You can use it to detect if any oriented convex shape is intersecting another shape. This includes meshes, if you can tell GJK where the points of the mesh are (by writing a support function for the mesh).

1 Like

The reason it fails when rotated is probably that you’re moving it “up” (increase in world Y coord), by an amount equal to the part’s “height” (Y coord of size). When the part has been rotated, the height is no longer the Y coord of the size, but some other axis.

The reason it fails when trying to place it on other parts than the baseplate is probably that your raycasts don’t check for those parts. Are you using Workspace:FindPartsOnRay or Workspace:FindPartsOnRayWithWhiteList? Please post the code that is responsible for this. Anyway, a raycast returns not only the part it hit, but also the surface normal of the surface that it hit. So you don’t actually want to move the part up by its height, but in the direction of whichever surface you’re raycasting to by the part’s thickness in that direction.

One important detail is that this only works if all the parts are axis aligned, i.e. only rotated in increments of 90 degrees. Otherwise, no axis of the part’s size will match exactly with the direction it needs to be moved to be outside the target part.

Here's how you might figure out which axis best fits any direction:
function dirToNearestAxis(dir)
	local greatestAxis = math.max(math.abs(dir.X), math.abs(dir.Y), math.abs(dir.Z))

	if greatestAxis == dir.X then
		return Enum.Axis.X
	elseif greatestAxis == dir.Y then
		return Enum.Axis.Y
	else
		return Enum.Axis.Z
	end
end
Here's how you might use that to find the thickness of a part in any given direction, and move the part accordingly:
function sizeToAxisThickness( size, axis )
	--axisFilter will be e.g. (1, 0, 0) in the case of axis == X
	local axisFilter = Vector3.FromAxis(axis)

	--v0 * v1 = (v0[X] * v1[X], v0[Y] * v1[Y], v0[Z] * v1[Z]), 
	-- so only the X component of size will be left in the case of axis == X
	return (size * axisFilter).Magnitude 
end

function placePart( partToPlace, rayPosition, targetPart, targetNormal )
	--targetNormal Transformed to partToPlace's object space, because the size's axes are in object space. 
	local thicknessAxis = dirToNearestAxis( partToPlace.CFrame:VectorToObjectSpace(targetNormal) )
	local moveAmount = sizeToAxisThickness( partToPlace.Size, thicknessAxis ) * 0.5
	partToPlace.CFrame = CFrame.new(rayPosition) + ( targetNormal * moveAmount )
end

I hope this is enough to get you started. Ask if you have any questions, or need help to move on from this point.

1 Like