solid-modeling
1
总安装量
1
周安装量
#76652
全站排名
安装命令
npx skills add https://github.com/taozhuo/game-dev-skills --skill solid-modeling
Agent 安装分布
cursor
1
Skill 文档
Solid Modeling / CSG
Constructive Solid Geometry (CSG) builds complex shapes from simple primitives using Boolean operations.
Core Operations
UNION (A ⪠B) SUBTRACT (A - B) INTERSECT (A ⩠B)
Combines volumes Cuts B from A Keeps only overlap
| Operation | Roblox API | Use For |
|---|---|---|
| Union | part:UnionAsync({others}) |
Joining pieces |
| Subtract | part:SubtractAsync({others}) |
Holes, cutouts |
| Intersect | part:IntersectAsync({others}) |
Complex curves, clipping |
Primitive Selection Guide
Box (Most Versatile)
Best for:
âââ Walls, floors, ceilings
âââ Frames (box - smaller box)
âââ Steps (stacked boxes)
âââ Slots, grooves (subtract thin box)
âââ Beveled edges (rotated box union)
âââ Any rectangular feature
Cylinder
Best for:
âââ Holes (subtract from anything)
âââ Pillars, poles, pipes
âââ Rounded edges (union at corners)
âââ Wheels, discs
âââ Tubes (cylinder - cylinder)
âââ Bolts, screws (with sphere cap)
Sphere
Best for:
âââ Domes (sphere â© box = hemisphere)
âââ Bowls (sphere - smaller sphere)
âââ Rounded corners (intersect with box)
âââ Organic bulges
âââ Ball joints, sockets
âââ Smooth blends between shapes
Wedge
Best for:
âââ Ramps, slopes
âââ Roof sections
âââ Chamfered edges
âââ Angular cuts
âââ Triangular features
Corner Wedge
Best for:
âââ Pyramid tops
âââ Corner slopes
âââ Hip roof sections
âââ Diagonal transitions
Common Recipes
Architecture
-- WINDOW: Wall with rectangular hole
local wall = createBox(10, 8, 0.5)
local windowHole = createBox(3, 4, 0.5)
windowHole.Position = Vector3.new(0, 1, 0)
local result = wall:SubtractAsync({windowHole})
-- DOOR FRAME: Box minus box
local frame = createBox(4, 8, 0.5)
local opening = createBox(3, 7, 0.5)
opening.Position = Vector3.new(0, -0.5, 0)
local result = frame:SubtractAsync({opening})
-- ARCH: Rectangle + half cylinder cutout
local wall = createBox(10, 8, 1)
local rectCut = createBox(3, 5, 1)
rectCut.Position = Vector3.new(0, -1.5, 0)
local archCut = createCylinder(1.5, 1)
archCut.CFrame = CFrame.new(0, 2.5, 0) * CFrame.Angles(math.rad(90), 0, 0)
local cutout = rectCut:UnionAsync({archCut})
local result = wall:SubtractAsync({cutout})
-- PILLAR WITH BASE: Cylinder + wider cylinder
local shaft = createCylinder(1, 10)
local base = createCylinder(1.5, 0.5)
base.Position = Vector3.new(0, -4.75, 0)
local capital = createCylinder(1.5, 0.5)
capital.Position = Vector3.new(0, 4.75, 0)
local result = shaft:UnionAsync({base, capital})
Mechanical
-- PIPE: Cylinder minus cylinder
local outer = createCylinder(2, 10)
local inner = createCylinder(1.5, 10)
local pipe = outer:SubtractAsync({inner})
-- FLANGE: Pipe + wider disc
local flange = createCylinder(3, 0.5)
flange.Position = Vector3.new(0, 5, 0)
local result = pipe:UnionAsync({flange})
-- BOLT HOLES: Subtract cylinders in circle
local plate = createBox(10, 10, 1)
local holes = {}
for i = 0, 5 do
local angle = i * (math.pi * 2 / 6)
local hole = createCylinder(0.5, 1)
hole.Position = Vector3.new(math.cos(angle) * 3, 0, math.sin(angle) * 3)
table.insert(holes, hole)
end
local result = plate:SubtractAsync(holes)
-- GEAR TOOTH: Wedge shapes around cylinder
local disc = createCylinder(5, 1)
local teeth = {}
for i = 0, 11 do
local angle = i * (math.pi * 2 / 12)
local tooth = createWedge(1, 1, 0.5)
tooth.CFrame = CFrame.new(math.cos(angle) * 5.5, 0, math.sin(angle) * 5.5)
* CFrame.Angles(0, -angle, 0)
table.insert(teeth, tooth)
end
local result = disc:UnionAsync(teeth)
Organic / Curved
-- ROUNDED BOX: Box intersect sphere
local box = createBox(4, 4, 4)
local sphere = createSphere(3.2) -- Slightly larger than box diagonal / 2
local rounded = box:IntersectAsync({sphere})
-- DOME: Sphere intersect box (top half)
local sphere = createSphere(5)
local cutter = createBox(10, 5, 10)
cutter.Position = Vector3.new(0, 2.5, 0)
local dome = sphere:IntersectAsync({cutter})
-- BOWL: Sphere minus smaller sphere
local outer = createSphere(5)
local inner = createSphere(4)
inner.Position = Vector3.new(0, 1, 0) -- Offset up for bowl shape
local bowl = outer:SubtractAsync({inner})
-- TORUS (Donut): Revolve circle (approximate with unions)
local segments = {}
for i = 0, 23 do
local angle = i * (math.pi * 2 / 24)
local ring = createSphere(1)
ring.Position = Vector3.new(math.cos(angle) * 4, 0, math.sin(angle) * 4)
table.insert(segments, ring)
end
local torus = segments[1]:UnionAsync({unpack(segments, 2)})
Terrain / Environment
-- CAVE ENTRANCE: Hill minus stretched sphere
local hill = createSphere(20)
hill.Size = Vector3.new(40, 20, 30) -- Flatten
local caveCut = createSphere(8)
caveCut.Position = Vector3.new(0, -5, -15) -- Position at front
local result = hill:SubtractAsync({caveCut})
-- CLIFF LEDGE: Box with angular cut
local cliff = createBox(20, 15, 10)
local cut = createWedge(20, 10, 10)
cut.CFrame = CFrame.new(0, 7.5, 5) * CFrame.Angles(0, 0, math.rad(180))
local result = cliff:SubtractAsync({cut})
-- ROCK: Multiple spheres unioned
local spheres = {}
for i = 1, 8 do
local s = createSphere(math.random(2, 4))
s.Position = Vector3.new(
math.random(-3, 3),
math.random(-2, 2),
math.random(-3, 3)
)
table.insert(spheres, s)
end
local rock = spheres[1]:UnionAsync({unpack(spheres, 2)})
Operation Order Strategy
Principle: Minimize Operations
-- BAD: Many sequential operations
local result = a:UnionAsync({b})
result = result:UnionAsync({c})
result = result:UnionAsync({d})
result = result:SubtractAsync({e})
result = result:SubtractAsync({f})
-- GOOD: Batch similar operations
local additions = a:UnionAsync({b, c, d})
local result = additions:SubtractAsync({e, f})
Principle: Subtract Last
-- GOOD: Build up, then cut
local solid = base:UnionAsync({extensions})
local final = solid:SubtractAsync({allHoles})
-- BAD: Alternating (more mesh complexity)
local step1 = base:SubtractAsync({hole1})
local step2 = step1:UnionAsync({ext1})
local step3 = step2:SubtractAsync({hole2})
Principle: Hierarchical Assembly
-- Build sub-assemblies, then combine
local leftWing = buildWing(params)
local rightWing = buildWing(params):Clone()
rightWing.CFrame = mirrorCFrame
local fuselage = buildFuselage(params)
local tail = buildTail(params)
local aircraft = fuselage:UnionAsync({leftWing, rightWing, tail})
Roblox CSG API
Basic Usage
-- Union: Combine parts
local union = mainPart:UnionAsync(otherParts)
union.Parent = workspace
-- Subtract: Cut away
local result = mainPart:SubtractAsync(cutParts)
-- Intersect: Keep overlap
local result = mainPart:IntersectAsync(clipParts)
With Options
local result = mainPart:UnionAsync(others, {
CollisionFidelity = Enum.CollisionFidelity.Hull,
RenderFidelity = Enum.RenderFidelity.Automatic
})
Collision Fidelity
| Setting | Use When |
|---|---|
Box |
Background props, fastest |
Hull |
Convex shapes, good balance |
Default |
Precise collision needed |
PreciseConvexDecomposition |
Most accurate, slowest |
Property Inheritance
The calling part’s properties transfer to result:
- Color, Material, Transparency
- Anchored, CanCollide
- Physical properties (Density, Friction, etc.)
-- mainPart's properties will be on result
local result = mainPart:SubtractAsync({cutPart})
UsePartColor
-- true: Each original part keeps its color
-- false: Result uses single color
result.UsePartColor = true
Performance Guidelines
Triangle Count
Each operation can multiply triangles:
- Simple box: 12 triangles
- Box + Cylinder: ~50 triangles
- Complex union: 100s-1000s
Monitor with: result:GetTrianglesCount() -- in Studio
Avoid These
-- BAD: Tiny cuts (creates many small faces)
for i = 1, 100 do
local tinyHole = createCylinder(0.1, 1)
result = result:SubtractAsync({tinyHole})
end
-- BAD: Near-coplanar faces (z-fighting, artifacts)
local cut = createBox(10, 10, 0.001) -- Too thin!
-- BAD: Many sequential single operations
for _, part in pairs(parts) do
result = result:UnionAsync({part}) -- Slow!
end
Do These Instead
-- GOOD: Batch operations
local allCuts = {}
for i = 1, 20 do
table.insert(allCuts, createHole(positions[i]))
end
result = base:SubtractAsync(allCuts)
-- GOOD: Reasonable cut sizes
local cut = createBox(10, 10, 0.5) -- Visible thickness
-- GOOD: Use CollisionFidelity.Box for non-gameplay parts
result.CollisionFidelity = Enum.CollisionFidelity.Box
Async Considerations
-- Operations yield - don't spam
-- BAD:
for i = 1, 100 do
result = result:UnionAsync({parts[i]}) -- 100 yields!
end
-- GOOD:
result = base:UnionAsync(parts) -- Single yield
Debugging CSG
Visual Issues
| Problem | Cause | Fix |
|---|---|---|
| Holes in mesh | Coplanar faces | Offset by 0.01+ |
| Z-fighting | Overlapping faces | Extend cuts through |
| Inside-out faces | Bad operation order | Check normals |
| Missing parts | Didn’t intersect | Ensure overlap |
Collision Issues
-- Show collision geometry in Studio:
-- View > Show > Decomposition Geometry
-- Or at runtime:
workspace:SetAttribute("ShowDecompositionGeometry", true)
Fixing Bad Unions
-- If union has artifacts, try:
-- 1. Scale up parts slightly before operation
-- 2. Use SeparateAsync and re-union
-- 3. Recreate with offset positions
Complete Example: Building Generator
local function createBuilding(config)
local width = config.width or 20
local height = config.height or 15
local depth = config.depth or 20
local windowSize = config.windowSize or Vector3.new(3, 4, 1)
local windowSpacing = config.windowSpacing or 5
local doorSize = config.doorSize or Vector3.new(4, 7, 1)
-- Main structure
local shell = Instance.new("Part")
shell.Size = Vector3.new(width, height, depth)
shell.Position = Vector3.new(0, height/2, 0)
shell.Anchored = true
-- Hollow interior
local interior = Instance.new("Part")
interior.Size = Vector3.new(width - 1, height - 1, depth - 1)
interior.Position = shell.Position
local building = shell:SubtractAsync({interior})
-- Windows
local windows = {}
local windowY = 5
while windowY < height - 3 do
local windowX = -width/2 + windowSpacing
while windowX < width/2 - windowSpacing do
-- Front windows
local w = Instance.new("Part")
w.Size = windowSize
w.Position = Vector3.new(windowX, windowY, -depth/2)
table.insert(windows, w)
-- Back windows
local w2 = w:Clone()
w2.Position = Vector3.new(windowX, windowY, depth/2)
table.insert(windows, w2)
windowX = windowX + windowSpacing
end
windowY = windowY + windowSpacing
end
if #windows > 0 then
building = building:SubtractAsync(windows)
end
-- Door
local door = Instance.new("Part")
door.Size = doorSize
door.Position = Vector3.new(0, doorSize.Y/2, -depth/2)
building = building:SubtractAsync({door})
-- Properties
building.Anchored = true
building.Material = Enum.Material.Concrete
building.Color = Color3.fromRGB(200, 200, 200)
building.CollisionFidelity = Enum.CollisionFidelity.Hull
building.Parent = workspace
return building
end
-- Usage
local office = createBuilding({
width = 30,
height = 20,
depth = 25,
windowSpacing = 6
})
Quick Reference
What To Build With CSG
| Object | Approach |
|---|---|
| Building | Box shell – interior – windows – door |
| Pipe | Cylinder – cylinder |
| Wheel | Cylinder – center hole – spoke holes |
| Bridge | Box deck ⪠supports – arch cutouts |
| Tunnel | Box – stretched cylinder |
| Stairs | Union stacked boxes |
| Ramp | Box ⪠wedge |
| Dome | Sphere â© box |
| Arch | Box – (box ⪠half-cylinder) |
| Frame | Box – box |
| Gear | Cylinder ⪠teeth – center hole |
What NOT To Build With CSG
| Object | Why Not | Use Instead |
|---|---|---|
| Characters | Organic, needs animation | MeshPart + rig |
| Trees/plants | Organic, many polys | MeshPart |
| Cloth | Needs simulation | MeshPart |
| High detail | Polygon explosion | MeshPart |
| Smooth curves | Faceted result | MeshPart |
CSG Thinking: Mental Models
Beyond recipes, here’s how to think about solid modeling.
Positive vs Negative Space
Every object is:
- Positive space: Material that exists
- Negative space: Voids, holes, cavities
A coffee mug:
âââ Positive: Cylinder (body), Torus (handle)
âââ Negative: Cylinder (cavity inside)
A window:
âââ Positive: Wall exists
âââ Negative: Rectangle removed
Key insight: Think “What’s THERE vs what’s MISSING” – expert modelers often think subtractive first.
Silhouette Analysis
Look at object from 3 orthogonal axes:
TOP (Y)
â
â¼
âââââââââââ
â Plan â
â View â
âââââââââââ
FRONT (Z) SIDE (X)
â â
â¼ â¼
âââââââââ âââââââââ
â Front â â Side â
â View â â View â
âââââââââ âââââââââ
Each silhouette suggests extrusions:
- Front silhouette â extrude along Z
- Side silhouette â extrude along X
- Top silhouette â extrude along Y
Intersect all three = approximation of complex object
Decomposition Strategies
Strategy A: Outside-In (Subtractive)
Start: Bounding box that contains entire object
Process: Carve away material until shape emerges
Best for:
âââ Objects that fit in a box
âââ Objects with holes/cavities
âââ Architecture (walls with openings)
âââ Sculpture-like objects
Mental model: Carving marble
Strategy B: Inside-Out (Additive)
Start: Nothing
Process: Add pieces until complete
Best for:
âââ Assemblies of distinct parts
âââ Objects with protruding features
âââ Mechanical systems
âââ Modular constructions
Mental model: Building with Lego
Strategy C: Skeleton-Based
Start: Central axis or spine
Process: Build volume around skeleton
Best for:
âââ Elongated objects (pipes, beams, limbs)
âââ Symmetric objects
âââ Branching structures
âââ Objects with clear "core"
Mental model: Adding flesh to bones
Strategy D: Slice-Based
Start: 2D cross-section profile
Process: Extrude, sweep, or revolve the profile
Best for:
âââ Objects with consistent cross-section
âââ Revolution solids (vases, bowls, spindles)
âââ Extruded profiles (beams, moldings)
âââ Swept paths (curved pipes)
Mental model: Lathe or extrusion
Operation Selection Logic
DECISION TREE:
Need to combine separate volumes?
âââ YES â UNION
Need to remove material / make holes?
âââ YES â SUBTRACT
Need only the overlapping region?
âââ YES â INTERSECT
But also ask:
âââ Can I avoid an operation entirely?
â âââ Repositioning primitives may achieve same result
âââ Can I batch multiple operations?
â âââ Union all additions THEN subtract all cuts
âââ Will this create valid geometry?
âââ Check for manifold issues
Why Operations Fail: Topology
Manifold vs Non-Manifold
MANIFOLD (valid):
âââ Every edge shared by exactly 2 faces
âââ Clear inside/outside distinction
âââ Watertight surface
âââ CSG operations work correctly
NON-MANIFOLD (problematic):
âââ Edge shared by 0, 1, or 3+ faces
âââ Ambiguous inside/outside
âââ Has holes, dangling geometry
âââ CSG operations may fail or produce artifacts
Operations That Create Non-Manifold
TANGENT TOUCHING (edges align exactly)
âââââ
â âââââ â Edges touch but don't overlap
ââââ⤠â â Result: ambiguous
â â â â
âââââ´ââââ´ââââ
FIX: Ensure overlap, not just touching
âââââ
â âââ¼ââ â Volumes actually intersect
âââââ¼ââ¼ââ¼ââ Result: clean boolean
â â â â â
âââââ´ââ´ââ´ââ
COPLANAR FACES (same plane)
âââââââââ
â â â Cut stops exactly at surface
â ââââ⤠Result: z-fighting, holes
â â â
âââââ´ââââ
FIX: Extend cuts THROUGH the object
âââââââââ
â â
â âââââââ¼ââ â Cut extends beyond surface
â â â Result: clean cut
âââ´ââââââ
The Golden Rule
Always extend subtractive geometry through and beyond the target surface.
Level of Detail Decisions
QUESTION: How much detail does this object need?
FACTORS:
1. View Distance
âââ Far (>100 studs): Only silhouette matters
âââ Medium (20-100): Major features visible
âââ Close (<20): Fine details matter
2. Purpose
âââ Visual only: Can be complex
âââ Collision: Should be simplified
âââ Both: Balance needed
3. Quantity
âââ Unique hero prop: High detail OK
âââ Repeated 100+ times: Minimize polys
4. Generation Time
âââ Pre-built in Studio: Complex OK
âââ Runtime generated: Keep simple
DECISION:
High Detail
â
Hero prop ââââââââââââ¼âââââââââââ Background prop
Close-up â Distant
Unique â Repeated
Pre-built â Runtime
â
Low Detail
Parametric Thinking
Don’t hardcode dimensions – use configuration:
-- BAD: Magic numbers everywhere
local hole = createCylinder(2.5, 10)
hole.Position = Vector3.new(3.7, 0, 4.2)
-- GOOD: Parametric and reusable
local function createBoltPattern(config)
local boltRadius = config.boltRadius or 0.5
local boltCount = config.boltCount or 6
local patternRadius = config.patternRadius or 3
local plateThickness = config.plateThickness or 1
local holes = {}
for i = 0, boltCount - 1 do
local angle = i * (math.pi * 2 / boltCount)
local hole = createCylinder(boltRadius, plateThickness * 1.1)
hole.Position = Vector3.new(
math.cos(angle) * patternRadius,
0,
math.sin(angle) * patternRadius
)
table.insert(holes, hole)
end
return holes
end
-- Now reusable with different configs
local smallPattern = createBoltPattern({boltCount = 4, patternRadius = 2})
local largePattern = createBoltPattern({boltCount = 8, patternRadius = 5})
Benefits:
- Reusable across projects
- Easy to adjust and iterate
- Self-documenting (config keys explain purpose)
- Testable with different inputs
Reference Analysis Workflow
When asked to “build X”, follow this process:
1. OBSERVE
âââ What are the major volumes?
â âââ Break down into largest recognizable shapes
âââ What are the negative spaces?
â âââ Identify all holes, cavities, cutouts
âââ What symmetry exists?
â âââ Can I build half and mirror?
âââ What's the simplest bounding shape?
âââ Box? Cylinder? Sphere?
2. DECOMPOSE
âââ Map each feature to a primitive
â âââ "This curved part = sphere intersection"
âââ Identify operation types
â âââ "These holes = subtracts, this extension = union"
âââ Determine dependencies
â âââ "Must create body before cutting windows"
âââ Group related operations
âââ "All bolt holes can be one subtract"
3. SIMPLIFY
âââ Can two operations become one?
â âââ Batch unions, batch subtracts
âââ Can detail be omitted?
â âââ Will anyone notice from gameplay distance?
âââ Can a complex shape approximate?
â âââ Cylinder instead of 12-sided polygon
âââ Is CSG even the right approach?
âââ Maybe MeshPart for this one
4. IMPLEMENT
âââ Build sub-assemblies first
â âââ Test each component independently
âââ Batch similar operations
â âââ All unions, then all subtracts
âââ Apply materials/colors appropriately
â âââ Set on main part before operations
âââ Verify collision and visuals
âââ Test in context
Example: Analyzing a Chair
OBSERVATION:
âââ Seat: Flat rectangular volume
âââ Back: Flat rectangular volume, angled
âââ Legs: 4 vertical cylinders (or boxes)
âââ Negative: Open space under seat, between legs
DECOMPOSITION OPTIONS:
Option A: Pure Additive
seat ⪠back ⪠leg1 ⪠leg2 ⪠leg3 ⪠leg4
Pros: Simple, clear
Cons: Many operations (6 parts)
Option B: Subtractive from Block
solidBlock - underSeatVoid - behindBackVoid - legGaps
Pros: Might be fewer operations
Cons: Complex void shapes, more triangles
Option C: Hybrid
(seat ⪠back) as one L-shaped piece
then ⪠legs
Pros: Reduces operations
Cons: L-shape might need subtract anyway
DECISION FACTORS:
âââ Chair style: Modern (boxy) vs Classic (curved)
âââ Leg shape: Round legs = cylinders, Square = boxes
âââ Detail level: Cushion? Armrests? Carved details?
âââ Quantity: One chair or 50 chairs?
LIKELY BEST: Option A for simple chair
- Clear and maintainable
- Each part can have different material
- Easy to adjust proportions
The Fundamental Trade-off
ACCURACY âââââââââââââââââââââââââââââ⺠PERFORMANCE
More operations Fewer operations
More primitives Simpler shapes
Precise collision Box collision
Higher triangle count Lower triangle count
Longer generation time Faster generation
â â
â¼ â¼
Hero props Background/repeated
Close-up items Distant items
Gameplay-critical collision Visual-only props
Design-time generation Runtime generation
There is no “correct” answer – only appropriate trade-offs for your context.