New Roblox In-Game CSG API is now available

YES!

This will make so many things possible!

I’m excited to try it!

2 Likes

:weary::sweat_drops: y e s

7 Likes

I must say, I felt really negated without this feature. I’m glad you guys unioned up to create something so awesome!

Seriously though, I remember a few years ago when something like this was unthinkable with CSG.

17 Likes

thank%20you

This update is much appreciated and will definitely be very helpful with making games feel more realistic.

9 Likes

WOO! Finally! This is going to be a game changer, I just hope it’s very optimised on a larger scale. If not, that’s perfectly fine.

3 Likes

Here’s a code sample that I wrote for in-game CSG a few weeks ago.
It makes explosions carve spheres out of nearby parts!

----------------------------------------------------------------------------------------------
-- @CloneTrooper1019, 2018 <3
-- SwissCheese.lua
----------------------------------------------------------------------------------------------
 
local function createRegion3FromPart(part)
	local pos = part.Position
	local size = part.Size
	
	local corner0 = pos - (size/2)
	local corner1 = pos + (size/2)
	
	return Region3.new(corner0, corner1)
end

local function onExplosion(explosion)
	-- Ignore visual-only explosions
	if explosion.BlastPressure == 0 then
		return
	end
	
	-- Create the sphere that will be subtracted with this explosion.
	local radius = explosion.BlastRadius
	local negateSphere = Instance.new("Part")
	negateSphere.CFrame = CFrame.new(explosion.Position)
	negateSphere.Size = Vector3.new(radius, radius, radius)
	negateSphere.Shape = "Ball"
	
	-- Cast negateSphere into the 'Objects' type.
	local subtractor = {negateSphere}

	-- Capture the parts within a Region3 formed by the sphere.
	local region = createRegion3FromPart(negateSphere)
	local parts = workspace:FindPartsInRegion3(region)
	
	for _,part in pairs(parts) do
		local success, result = pcall(function ()
			return part:SubtractAsync(subtractor)
		end)
		
		if success then
			-- Move the children of the old part into the new part.
			for _,child in pairs(part:GetChildren()) do
				child.Parent = result
			end
			
			-- Swap out the part with the union.
			result.Parent = part.Parent
			result.CFrame = part.CFrame
			part:Destroy()
		else
			warn("SubtractAsync failed because:", result)
		end
	end
end

-- Listen for explosions being added to the workspace
local function onDescendantAdded(desc)
	if desc:IsA("Explosion") then
		onExplosion(desc)
	end
end

workspace.DescendantAdded:Connect(onDescendantAdded)
----------------------------------------------------------------------------------------------
30 Likes

How often does real time CSG error compared to CSG in studio?

6 Likes

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