BasePart.Resize with Enum results in axis mismatch

Lets say we want to cut a part in half!
One approach you could use to cut parts in half is with real-time union operations. This works really well but I encounter a small issue which I can’t wrap my head around.

How does it work?

So, Imagine a part which is influenced by gravity.
image
Lets say we cut the part from the start. The start is the hitmarker basically. On hit I Instance.new a new part ontop of the hitmark.
image
Note: I do this twice to generate the illusion the part is cut in half (for the top part and the bottom part). But that is besides the point.

I need to figure out what the Top and the Bottom is of the part from the location we started from.
The new Part Instance will then be resized to cover everything of the part I don’t need.
image
Then a SubtractAsync is called to make the illusion complete.

How do I know what the Top part of the part is from the cut location?

Sure, you could do it like this:

part:Resize(Enum.NormalId.Top, 20);

Don’t get me wrong, this actually does work! But here is my problem.

If the part has been cut before and looks like this in the environment and we would cut it again
image

part:Resize(Enum.NormalId.Top, 20);

Sees “Top” as

image

And not as

image

I’ve trie Enumerous things to make this work but have not found a proper solution yet.
This is my last resource.

How could one increase the size of a part in a specific direction even if the axis of the part might have changed?

You can get the rounded Orientation of the part to the nearest 90 degrees:

local function roundToNearest90(Number) -- Rounds the number to the nearest 90
    return math.floor(Number / 90 + 0.5) * 90
end
local Faces = { -- Which direction to increase the size based on orientation
    [Vector3.new(0, 0, 0)] = "Top",
    [Vector3.new(0, 90, 0)] = "Front",
    etc...
}

part:Resize(Faces[roundToNearest90(part.Orientation)], 20) -- Obviously you can't round Vector3's so edit the function so you can
1 Like

I love this solution, but its limited.
I worked it out into a method like this:

local function calculateCutSide(part) -- Returns Enum Position
	-- Note: This method works well except for situations where the result would be Vector3.new(90, -90, 0)
	if part:IsA("Part")
		or part:IsA("UnionOperation")
		or part:IsA("MeshPart") 
		or part:IsA("TrussPart") 
		or part:IsA("WedgePart") 
		or part:IsA("CornerWedgePart") 
		or part:IsA("SpawnLocation") then
		-- Just check if the part has a Orientation
		
		local Faces = { -- Possible Directions
			[Vector3.new(0, 0, 0)] = "Front",
			[Vector3.new(0, 180, 0)] = "Back",
			[Vector3.new(90, 0, 0)] = "Top",
			[Vector3.new(-90, 0, 0)] = "Bottom",
			[Vector3.new(0, -90, 0)] = "Right",
			[Vector3.new(0, 90, 0)] = "Left"
		};
		
		local face = Faces[Vector3.new(math.floor(part.Orientation .x / 90 + 0.5) * 90, math.floor(part.Orientation .y / 90 + 0.5) * 90, math.floor(part.Orientation .z / 90 + 0.5) * 90)];
		
		-- Return the correct ENUM value
		if face == nil then
			-- If Face has no enum value
			return Enum.NormalId.Front; -- This is just here so if face check returns nil my code doesn't crash.
		elseif face:lower() == "front" then
			return Enum.NormalId.Front;
		elseif face:lower() == "back" then
			return Enum.NormalId.Back;
		elseif face:lower() == "top" then
			return Enum.NormalId.Top;
		elseif face:lower() == "bottom" then
			return Enum.NormalId.Bottom;
		elseif face:lower() == "right" then
			return Enum.NormalId.Right;
		elseif face:lower() == "left" then
			return Enum.NormalId.Left;
		end

	end
end

I use this method like this

somePart:Resize(calculateCutSide(part), 20);

There are two issues with this code.

  1. I have checked the orientation of all sides with a Part and a decal to match the orientation vector. It could be that “Back” is wrong.
  2. If the result of face is for example Vector3.new(90,-90,0) we have an issue.
  • All the enum sides are listed, so enum might come short to cover all rotations

Edit: I just realize that 90, -90 might be back or front… hold up
Edit2: Nope, no luck

1 Like

Seemed fun, this works similarly to @VegetationBush’s

local p = workspace.P2

-- snaps a unit vector to the closest axis, i.e. so its length is 1
-- but it is pointing in the closest +/- XYZ direction
local function SnapToAxis(vec)
	local lx = math.abs(vec.X)
	local ly = math.abs(vec.Y)
	local lz = math.abs(vec.Z)
	if (lx > ly and lx > lz) then
		return Vector3.new(math.sign(vec.X), 0, 0);
	elseif (ly > lx and ly > lz) then
		return Vector3.new(0, math.sign(vec.Y), 0);
	else
		return Vector3.new(0, 0, math.sign(vec.Z));
	end
end

-- resizes `part` along the closest face to the world-oriented normalId by deltaAmount
local function ResizeWorld(part: BasePart, normalId: Enum.NormalId, deltaAmount: number)
	-- some vector in object space aprx. where we want to resize towards
	local axisObject = part.CFrame:VectorToObjectSpace(Vector3.FromNormalId(normalId))
	
	-- e.g. 0, -1, 0
	local snapped = SnapToAxis(axisObject)
	
	-- e.g. 0, 1, 0
	local mask = Vector3.new(math.abs(snapped.X), math.abs(snapped.Y), math.abs(snapped.Z))
	
	part.Size = part.Size + mask * deltaAmount
	part.CFrame = part.CFrame * CFrame.new(snapped * deltaAmount / 2)
end

-- example

while true do
	for _, normalId in pairs(Enum.NormalId:GetEnumItems()) do
		wait(1)
		print(normalId)
		ResizeWorld(p, normalId, 2)
	end
end

Edit: I might have been confused about what you’re asking. I don’t think I fully understand your question. In my code, ResizeWorld(part, Enum.NormalId.Top, 5) will choose the face of the part that is facing “the most up”, and then pull that face out by 5 studs.

If that’s not what you want, could you clarify your question? Because when you say:

I disagree. If the top surface of the part is facing towards the right (in this photo), then part.Resize(Enum.NormalId.Top, 20) would move that face (the top of the part, right of the photo) out 5 studs, regardless of orientation.

1 Like

I appriciate your example and I tried to adapt it to my test environment. I encountered an interesting hotdog while slicing haha

All fun and games but lets get serious.

My main objective is to Slice parts like this using the CSG tool. My approach is not perfect but I wanted to see if I can create the slice mechenic.

After hours and hours of reading forum topics and documentation I created a tool so I could left click anywhere and came with the following solution.

  • Tool hits object, Figure out where tool hit and most importantly what the tool hit
local currentMousePos = UserInputService:GetMouseLocation(); -- Get Vector2 of mouse position
local unitRay = camera:ScreenPointToRay(currentMousePos.x, currentMousePos.y); -- Cast a 2D raycast point
local ray = Ray.new(unitRay.Origin, unitRay.Direction * 500); -- Init the raycast class
local ignoreTable = {mouse, tool, tool.Parent, game.Workspace.Baseplate, localPlayer, game.Workspace.Spawns}; -- Add ignore table
local hitPart, hitPosition = workspace:FindPartOnRayWithIgnoreList(ray,ignoreTable); -- Fire raycast

I solved this by raycasting, do note that this is a localscript in the tool itself. The slice magic happens in a remote event.

Once we do some validation (still in the localscript) if the part that got hit is choppable we create a “line” around the part as indicator that the user sliced at that specific location. (localhost)


This black line is the exact location I just sliced with my tool.

So, how do we slice?
There is not much information out there regarding this specific topic. But what I’ve found is some kind of forum post which suggested to clone the original part that got hit with the tool. Chop them into pieces based on the cut line and resize them accordingly. Destroy the original Part. and Unanchore the substracted Union. This gives you the illusion that you have cut the part on any random location you have choosen.

What my expected result would be is this

But what I got is this

@VegetationBush Provided an excellent boilerplate on how to solve this issue
@nicemike40 Your idea is amazing and brings me a bit closer to my goal, the issue with your implementation is that the position seems to be static and always the center of the part itself. It results in those Hotdogs. (See top of this message)

Now, I was playing a bit more with this and I watched those videos. The cuts he make are also diagonal so I started to doubt my approach.

I was refactoring my remote event to see if i can closer to the result in the video, the code is a bit of a mess and half of it is outcomment and such, you know how it goes.

I think this is a difficult challange and I hope to have clarified my problem a little bit more.
It has to be duable

1 Like

I got it to work! Preview
Inspired by B Ricey

I had to update some of the code and have added some extra features.

Normaly I don’t do this, but for future reference and as a small thank you @VegetationBush and @nicemike40 : Slice.rbxl (34.1 KB)

Left mouse button: Vertical Cut
Middle mouse button: Horizontal Cut

You will find a Settings folder in ReplicatedStorage which contains Slice settings. The Magnitude one is the one that is the distance you need to be in front of the object before you are allowed to cut.

Thank you all

2 Likes