New Roblox In-Game CSG API is now available

I’ve been wanting something like this for years. Thanks for the early release.

2 Likes

A developer @Lauri9 (view roblox profile) has worked on a quick demo, using this, which took only 3 hours(!!) to code and the results are promising!

4 Likes

AMAZING, I can’t wait to see what people do with this.

2 Likes

ive literally just made my entire map destructible i thank you

1 Like

This is awesome!

1 Like

Not sure how, but I got “error code -21”. Could be faulty code.

1 Like

What prevents this from being done on the client? The current solver is really fast and has little errors, especially compared to the old solver. This would reduce the network usage, and load much faster on the client.

13 Likes

Wrote a script that replicates the fragmenting parts shown off at RDC.
The secret is recursive plane cuts.

local fragmentPart = Instance.new("BindableEvent")
local tau = math.pi * 2

local function rollAngles(count)
	local result = {}
	
	for i = 1,count do
		result[i] = math.random() * tau
	end
	
	return unpack(result)
end

local function subtract(part, negativePart, collisionFidelity)
	if part:IsDescendantOf(workspace) then
		local subtractor = {negativePart}
		return part:SubtractAsync(subtractor, collisionFidelity)
	end
end

local function onFragmentPart(part, depth)
	if not part:IsDescendantOf(workspace) then
		return
	end
	
	local depth = depth or 0
	if depth > 4 then
		return
	end

	local size = part.Size
	if size.X < 1 or size.Y < 1 or size.Z < 1 then
		return
	end
	
	local magnitude = size.Magnitude
	if magnitude < 6 then
		return
	end
	
	local cf = part.CFrame
	local rx,ry,rz = rollAngles(3)
	local cutPlane = cf * CFrame.Angles(rx, ry, rz)
	
	local cut0 = Instance.new("Part")
	cut0.Size = size*4
	cut0.CFrame = cutPlane * CFrame.new(0,-size.Y*2,0)
	
	local cut1 = cut0:Clone()
	cut1.CFrame = cutPlane * CFrame.new(0,size.Y*2,0)
	
	local part0, part1
	
	pcall(function ()
		part0 = subtract(part, cut0, "Hull")
		part1 = subtract(part, cut1, "Hull")
	end)

	if part0 and part1 then
		local parent = part.Parent
		local velocity = part.Velocity
		local rotVel = part.RotVelocity
		part:Destroy()
		
		local c0 = cf:toObjectSpace(part0.CFrame)
		part0.CFrame = part.CFrame * c0
		part0.Velocity = velocity
		part0.RotVelocity = rotVel
		part0.Parent = parent

		local c1 = cf:toObjectSpace(part1.CFrame)
		part1.CFrame = part.CFrame * c1
		part1.Velocity = velocity
		part1.RotVelocity = rotVel
		part1.Parent = parent
		
		-- Recursively fragment
		fragmentPart:Fire(part0, depth+1)
		fragmentPart:Fire(part1, depth+1)
	end
end

local function onDescendantAdded(desc)
	if desc:IsA("Explosion") then
		local hasHit = {}

		local function onExplosionHit(hit)
			if not (hasHit[hit] or hit.Anchored or hit:FindFirstChildWhichIsA("FileMesh")) then
				hasHit[hit] = true
				fragmentPart:Fire(hit)
			end
		end

		local hitSignal = desc.Hit:Connect(onExplosionHit)
		
		delay(3,function ()
			-- Garbage collect after the explosion is gone.
			hitSignal:Disconnect()
			hasHit = nil
		end)
	end
end

fragmentPart.Event:Connect(onFragmentPart)
workspace.DescendantAdded:Connect(onDescendantAdded)
60 Likes

Been playing with this and made a sword that slices parts.

5 Likes

Hiya - I keep receiving an error code 21 when testing CSG.
Here’s the test place.
Error Code 21 - CSG.rbxl (13.7 KB)

You’re creating the part on the client, which you’re passing to the server via remote. Since this part is created by the client, it isn’t replicated and the server just sees nil.

Instead of passing the part to the server, just pass the position where the player clicked and create the part on the server.

1 Like

Pew pew pew! Unfortunately, the polygon count is excessive even for simple shapes. Those four little dents maxed out on the triangle limit.

12 Likes

Try using cylinders?

3 Likes

Use a cube and claim it’s for style and not because of technical limitations B-)

But srsly CSG isnt built for bullet holes in a production game.

6 Likes

You could use even less complex shapes without it being super noticeable by using a pyramid shape:

14 Likes

Oh my!

This is an excellent change. This is amazing!

Getting some pretty funky results when using UnionAsync to union more than two parts.
UnionAsyncOdditiesGif

Here’s a reproduction for anyone interested:
UnionAsync Oddities.rbxl (15.3 KB)

1 Like

This seems to be yielding a lot for me? I’m not sure if I’ve called it wrong, or too often, but it doesn’t do anything to the parts I specify and it doesn’t error; presumably it’s yielding as it doesn’t print what’s after the pcall. The same line of code worked earlier so I’m going to assume there’s some kind of rate limit that results in yielding or the error messages aren’t complete yet?

success, fault = pcall(function()
	print("Subtracting")
	newBase = baseplate:SubtractAsync({leftFoot, rightFoot})
	print("Subtracted")
end)

‘Subtracted’ doesn’t get outputted and the prints following the pcall don’t show.

This actually only seems to happen in play solo

I managed to make a really crappy, extremely laggy footprint thing. Can’t wait until this is clientsided and I can do this properly

function makeParts(parts)
	local createdParts = {}
	for i = 1, #parts do
		local newPart = Instance.new("Part")
		newPart.Size = parts[i].Size + Vector3.new(0,0.75,0)
		newPart.Position = parts[i].Position
		newPart.Color = parts[i].Color
		newPart.Anchored = true
		newPart.Name = parts[i].Name
		newPart.CanCollide = false
		createdParts[i] = newPart
	end
	return createdParts[1], createdParts[2]
end

function monitorMovement(char)
	local db = true
	repeat wait() until char:FindFirstChild("Humanoid") ~= nil
	char.Humanoid.Touched:Connect(function(part)
		if char.Humanoid.FloorMaterial ~= Enum.Material.Air and db then
			db = false
			spawn(function()
				wait(0.5)
				db = true
			end)
			spawn(function()
				print(char.Humanoid.FloorMaterial)
				local leftFoot, rightFoot = char:FindFirstChild("LeftFoot"), char:FindFirstChild("RightFoot")
				if leftFoot ~= nil and rightFoot ~= nil then
					local newPart,success,fault
					-- since meshparts aren't accepted, create parts to simulate the feet
					leftFoot, rightFoot = makeParts({leftFoot, rightFoot})
					-- do the editing
					success, fault = pcall(function()
						print("Subtracting", part:GetFullName(), leftFoot:GetFullName(), rightFoot:GetFullName())
						newPart = part:SubtractAsync({leftFoot, rightFoot}, Enum.CollisionFidelity.Hull)
						print("Subtracted")
					end)
					newPart.UsePartColor = false
					print("Worked ig")
					if success then
						print("Making new baseplate")
						newPart.Name = "AddedFootprint"
						newPart.Parent = game.Workspace
						wait(0.1)
						part:Destroy()
					else
						print("Something went wrong")
						print(fault)
					end
					-- remove created parts
					leftFoot:Destroy()
					rightFoot:Destroy()
				end
			end)
		end
	end)
end

game.Players.PlayerAdded:Connect(function(plr)
	repeat wait() until plr.Character ~= nil
	print("Character loaded")
	spawn(function() monitorMovement(plr.Character) end)
	plr.CharacterAdded:Connect(function(char)
		monitorMovement(char)
	end)
end)

Test it

cool


9 Likes

I was waiting for this update for a long time especially to do this!

23 Likes