Modélisation solide est le processus de jonction de BaseParts ensemble de manière unique pour former des formes plus complexes. Cela inclut les opérations booléennes union, intersection et soustraction qui sont communément connues sous le nom de géométrie solide constructive (CSG). Vous pouvez effectuer la modélisation solide dans Studio, plugins, et en jeu à la fois sur le serveur et le client.
En plus de la CSG booléenne, la modélisation solide prend également en charge les maillages, tant qu'ils sont étanches, et des opérations comme balayage et fragmentation qui vous permettent, à vous et à vos joueurs, de trancher, couper et briser la géométrie pour des interactions de jeu uniques.


Géométrie étanche
Les opérations de modélisation solide ne peuvent fonctionner qu'avec une géométrie étanche. En termes techniques, un maillage étant étanche signifie qu'il est fermé, manifold et non auto-intersectant. Ces termes ont des définitions strictes, mais voici quelques règles simples :
- Chaque face doit avoir un côté "intérieur" et un côté "extérieur". Ceux-ci sont déterminés par l'ordre de rotation de la face, qui est l'ordre de ses trois sommets.
- Chaque arête doit être partagée par exactement deux triangles. Cela signifie qu'il ne peut y avoir aucun trou dans le maillage car les arêtes autour du bord d'un trou n'auraient qu'un triangle.
- Les faces ne peuvent pas passer à travers d'autres faces.
- Les triangles adjacents doivent s'accorder sur quel côté est le côté "extérieur".
- Chaque sommets doit avoir exactement un éventail de triangles adjacents.
Chacun des exemples suivants est non étanche pour la raison notée :




Le système de modélisation solide est capable de réparer automatiquement des problèmes spécifiques mineurs avec un maillage, mais en général, les appels API échoueront si le maillage n'est pas étanche. Il n'y a pas de solution universelle pour réparer un maillage non étanche existant, mais il existe plusieurs plugins Blender qui peuvent aider, tels que 3D Print Toolbox et Mesh Repair Tools. Comme autre option, Meshlab dispose également d'outils très utiles intégrés pour essayer de rendre le maillage manifold, ce qui est la principale exigence pour qu'un maillage soit étanche.
Une façon de voir qu'un maillage sera extrêmement difficile à rendre étanche est de le regarder dans Studio sous tous les angles, puis d'essayer d'activer et de désactiver la propriété MeshPart.DoubleSided du maillage. Si vous pouvez voir une différence, alors le maillage est juste une coque et les outils mentionnés ci-dessus ne fonctionneront pas car ils ne peuvent pas deviner quel espace est à l'intérieur par rapport à l'extérieur du maillage. Cependant, si tout ce que vous voulez est un maillage fin et qu'il n'est pas important de garder les dimensions du maillage exactement les mêmes, vous pouvez utiliser le modificateur Solidify de Blender pour épaissir légèrement la coque en un maillage étanche.


Modélisation solide dans Studio
Vous pouvez effectuer trois opérations booléennes de base à l'aide de quatre outils dans la barre d'outils de l'onglet Modèle.

| Outil | Raccourci | Description |
|---|---|---|
| Union | ShiftCtrlG (Windows) Shift⌘G (Mac) | Joindre deux ou plusieurs pièces pour former une union solide unique. |
| Intersect | ShiftCtrlI (Windows) Shift⌘I (Mac) | Intersecter des pièces qui se chevauchent en une seule intersection solide. |
| Separate | ShiftCtrlU (Windows) Shift⌘U (Mac) | Séparer l'union ou l'intersection en ses pièces individuelles. |
| Negate | ShiftCtrlN (Windows) Shift⌘N (Mac) | Nier des pièces, utile pour créer des trous et des indentations. |
Union
L'outil Union joint deux ou plusieurs pièces pour former une seule UnionOperation solide.


Pour combiner des pièces ensemble en une union :
- Sélectionnez toutes les pièces que vous souhaitez joindre.
- Cliquez sur le bouton Union. Toutes les pièces se combinent en une seule UnionOperation solide nommée Union.
Intersect
L'outil Intersect intersecte des pièces qui se chevauchent en une seule IntersectOperation solide.


Pour intersecter des pièces qui se chevauchent :
- Sélectionnez toutes les pièces que vous souhaitez intersecter.
- Cliquez sur le bouton Intersect. Toutes les pièces se combinent en une seule IntersectOperation solide nommée Intersection.
Negate
L'outil Negate nie une pièce afin que lorsqu'elle est unie avec une autre pièce, la forme de la pièce niée soit soustraite de l'autre pièce.


Pour soustraire une pièce d'autres pièces qui se chevauchent :
- Sélectionnez la pièce que vous souhaitez nier par rapport aux autres pièces.
- Cliquez sur Negate. La pièce devient translucide avec une teinte rougeâtre pour indiquer son état.
- Sélectionnez à la fois la pièce niée et les pièces dont vous souhaitez la soustraire.
- Cliquez sur Union. La pièce niée est découpée des pièces qui se chevauchent incluses.
Separate
L'outil Separate sépare une UnionOperation en ses pièces individuelles, servant essentiellement d'outil "annuler" pour les unions et intersections.
Pour séparer une union ou une intersection en pièces individuelles :
- Sélectionnez l'union ou l'intersection.
- Cliquez sur Separate. Les pièces se séparent en leur forme originale.
Modélisation solide en jeu
Vous pouvez également effectuer des opérations de modélisation solide pendant qu'un jeu est en cours ou à partir d'un plugin via les méthodes GeometryService.
Opérations principales
Similaire aux outils d'opération intégrés de Studio, vous pouvez utiliser les méthodes GeometryService comme UnionAsync(), IntersectAsync(), et SubtractAsync() pour effectuer des opérations booléennes de base. Par exemple, le script suivant utilise SubtractAsync() pour soustraire le volume d'une pièce d'une autre.
local GeometryService = game:GetService("GeometryService")
local mainPart = Instance.new("Part")
local otherPart = Instance.new("Part")
otherPart.Position = Vector3.new(1, 0.5, 1)
local success, newParts = pcall(function()
return GeometryService:SubtractAsync(mainPart, {otherPart})
end)
if success and newParts then
for _, newPart in pairs(newParts) do
newPart.Parent = workspace
end
endPour démontrer davantage, l'exemple de code suivant combine la géométrie de mainPart et des pièces dans le tableau otherParts, puis détruit les pièces originales impliquées dans l'opération. Vous pouvez remplacer l'appel à UnionAsync() par IntersectAsync() ou SubtractAsync() pour effectuer les autres opérations booléennes.
local GeometryService = game:GetService("GeometryService")
local mainPart = workspace.BlueBlock
local otherParts = { workspace.PurpleCylinder }
local options = {
CollisionFidelity = Enum.CollisionFidelity.Default,
RenderFidelity = Enum.RenderFidelity.Automatic,
SplitApart = false
}
local success, newParts = pcall(function()
return GeometryService:UnionAsync(mainPart, otherParts, options)
end)
if success and newParts then
-- Boucle à travers les pièces résultantes pour réaffecter/repositionner
for _, newPart in pairs(newParts) do
newPart.Parent = mainPart.Parent
newPart.CFrame = mainPart.CFrame
newPart.Anchored = mainPart.Anchored
end
-- Détruire les pièces originales
mainPart.Parent = nil
mainPart:Destroy()
for _, otherPart in pairs(otherParts) do
otherPart.Parent = nil
otherPart:Destroy()
end
endBien qu'il n'existe pas de méthode au niveau du moteur pour la négation de pièces, vous pouvez ajouter le tag rbxNegate dans un script ou un plugin pour effectuer une négation équivalente au bouton de la barre d'outils Negate de Studio.
local CollectionService = game:GetService("CollectionService")
CollectionService:AddTag(workspace.Part, "rbxNegate")Comparé à BasePart:UnionAsync()/BasePart:IntersectAsync()/BasePart:SubtractAsync(), les méthodes GeometryService diffèrent comme suit :
- La sortie est un tableau d'instances plutôt qu'une seule instance.
- Les pièces d'entrée n'ont pas besoin d'être parentées à la scène, permettant des opérations en arrière-plan.
- Lorsque l'option SplitApart est définie sur true (par défaut), chaque corps distinct sera retourné dans son propre PartOperation/MeshPart.
- Toutes les pièces retournées sont dans l'espace de coordonnées de la pièce principale, donc leurs positions PVInstance.Origin sont les mêmes que celles de la pièce principale. Cela garde les sommets du maillage à la même position par rapport à l'objet qu'avant l'opération, mais cela signifie également que le (0, 0, 0) d'une pièce retournée n'est pas nécessairement au centre de son corps.
Balayage de pièce
La méthode SweepPartAsync() crée un MeshPart qui a la forme de la pièce d'entrée traînée à travers un ensemble donné de positions CFrame. Cette méthode peut être très utile pour effectuer des interactions de découpe et de tranchage.
L'entrée peut être une Part, PartOperation, ou MeshPart. La forme du résultat est définie comme l'union des coques convexes de chaque paire adjacente de CFrames; si une seule CFrame est fournie, le résultat sera une coque convexe de la pièce d'entrée.
Pour démontrer comment cette méthode fonctionne, l'exemple de code suivant balaie une balle à travers un ensemble de positions CFrame pour créer une spirale :
local GeometryService = game:GetService("GeometryService")
local inputPart = Instance.new("Part")
inputPart.Shape = Enum.PartType.Ball
local cframeList = {}
for i = 1, 50 do
local rotation = CFrame.Angles(0, i * 0.5, 0)
local position = Vector3.new(0, i * 0.1, -1)
table.insert(cframeList, rotation * CFrame.new(position))
end
local success, sweptPart = pcall(function()
return GeometryService:SweepPartAsync(inputPart, cframeList)
end)
if success and sweptPart then
sweptPart.Parent = workspace
end

Cet exemple utilise SweepPartAsync() pour réaliser une fonctionnalité de gameplay de découpe de l'épée ou du pistolet laser, où le mouvement de l'épée est basé sur la position de la souris du joueur. Le mouvement de la souris de l'utilisateur est enregistré sous forme de liste de CFrames, SweepPartAsync() construit un maillage de découpe à partir de ces données, puis le maillage de découpe est soustrait de la pièce qui a été touchée.
Pour faire fonctionner cet exemple dans Studio :
Créez le Script suivant dans ServerScriptService pour effectuer toutes les opérations de modélisation solide.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local GeometryService = game:GetService("GeometryService")local DrawCurveEvent = ReplicatedStorage:WaitForChild("DrawCurveEvent")DrawCurveEvent.OnServerEvent:Connect(function(player, cframeList, hitInstance)local blade = Instance.new("Part")blade.Size = Vector3.new(0.2, 0.2, 15.0)local success, sweptPart = pcall(function()return GeometryService:SweepPartAsync(blade, cframeList)end)if success and sweptPart then-- Visualiser le balayagesweptPart.Parent = workspacesweptPart.Transparency = 0.5sweptPart.Anchored = truesweptPart.CanQuery = false-- Soustraire le balayage de l'instance touchéelocal subtractSuccess, newParts = pcall(function()return GeometryService:SubtractAsync(hitInstance, {sweptPart})end)if subtractSuccess and newParts thenfor _, newPart in pairs(newParts) donewPart.Parent = hitInstance.ParentnewPart.Anchored = trueendhitInstance:Destroy()endendend)Créez le LocalScript suivant dans StarterPlayerScripts pour gérer l'entrée utilisateur.
local ReplicatedStorage = game:GetService("ReplicatedStorage")local GeometryService = game:GetService("GeometryService")local DrawCurveEvent = ReplicatedStorage:WaitForChild("DrawCurveEvent")DrawCurveEvent.OnServerEvent:Connect(function(player, cframeList, hitInstance)local blade = Instance.new("Part")blade.Size = Vector3.new(0.2, 0.2, 15.0)local success, sweptPart = pcall(function()return GeometryService:SweepPartAsync(blade, cframeList)end)if success and sweptPart then-- Visualiser le balayagesweptPart.Parent = workspacesweptPart.Transparency = 0.5sweptPart.Anchored = truesweptPart.CanQuery = false-- Soustraire le balayage de l'instance touchéelocal subtractSuccess, newParts = pcall(function()r eturn GeometryService:SubtractAsync(hitInstance, {sweptPart})end)if subtractSuccess and newParts thenfor _, newPart in pairs(newParts) donewPart.Parent = hitInstance.ParentnewPart.Anchored = trueendhitInstance:Destroy()endendend)Créez un RemoteEvent dans ReplicatedStorage nommé DrawCurveEvent.
Fragmentation
Les méthodes FragmentAsync() et GenerateFragmentSites() vous permettent de briser une pièce en morceaux avec des formes naturelles. FragmentAsync() utilise la décomposition voronoi pour diviser une seule pièce en plusieurs instances MeshPart selon le motif de points passés, tandis que GenerateFragmentSites() est une méthode d'assistance qui génère des points connus sous le nom de sites voronoi à passer dans FragmentAsync().
L'exemple de code suivant génère des sites voronoi pour fragmenter une pièce de bloc :
local GeometryService = game:GetService("GeometryService")
local inputPart = Instance.new("Part")
inputPart.Position = Vector3.new(0, 0.7, 20)
local sites = GeometryService:GenerateFragmentSites(inputPart)
local success, fragments = pcall(function()
return GeometryService:FragmentAsync(inputPart, sites)
end)
if success and fragments then
for _, item in fragments do
local instance = item.Instance
instance.Parent = workspace
end
end

Le script suivant fragmente une zone d'une pièce, donnée par une position et un rayon. La position peut souvent provenir d'une collision physique ou d'un raycast d'un joueur.
Le premier élément du tableau de sites que GenerateFragmentSites() génère sera un tableau interne de tous les sites qui sont en dehors du rayon demandé. Si vous souhaitez faire quelque chose de spécifique à la portion 'non brisée' restante de la pièce, vous pouvez trouver cette portion en vérifiant fragments[i].Index == 1 lors de la boucle sur les résultats de FragmentAsync().
local GeometryService = game:GetService("GeometryService")
local function fragmentAtPosition(player, part, contactPoint, radius)
local allSites = GeometryService:GenerateFragmentSites(part, {Origin = contactPoint, Radius = radius})
local success, fragments = pcall(function()
return GeometryService:FragmentAsync(part, allSites)
end)
if not success then
warn("Échec de la fracture:"..tostring(fragments))
return
end
local decals = {}
for _,child in pairs(part:GetChildren()) do
if child:IsA("Decal") or child:IsA("SurfaceAppearance") then
table.insert(decals,child)
end
end
for i = 1, #fragments do
local fragment = fragments[i].Instance
if fragment == nil then
continue
end
for _,d in pairs(decals) do
local d2 = d:Clone()
d2.Parent = fragment
end
fragment.Anchored = false
fragment.Parent = part.Parent
fragment:SetNetworkOwner(player)
end
if #fragments ~= 0 then
part:Destroy()
end
return fragments
endLe script suivant brise des fragments de la première pièce dans une forme définie en utilisant une seconde pièce comme pochoir. Seuls les sites voronoi à l'intérieur de la seconde pièce donneront des morceaux séparés. Tous les autres sites auront leurs cellules combinées en une seule pièce.


local GeometryService = game:GetService("GeometryService")
local function fragmentWithinStencil(player, part)
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Include
overlapParams.FilterDescendantsInstances = {workspace.Stencil}
overlapParams.RespectCanCollide = false
local sensor = Instance.new("Part")
sensor.Size = Vector3.new(0.01, 0.01, 0.01)
local allSites = GeometryService:GenerateFragmentSites(part, {SiteSpacing = 0.9})
local fragmentSites = {}
local mainPartSites = {}
for _, site in ipairs(allSites) do
sensor.CFrame = CFrame.new(site)
local partsFound = workspace:GetPartsInPart(sensor, overlapParams)
if #partsFound > 0 then
table.insert(fragmentSites, site)
else
table.insert(mainPartSites, site)
end
end
local sortedSites = fragmentSites
table.insert(sortedSites, mainPartSites)
workspace.Stencil:Destroy()
local success, fragments = pcall(function()
return GeometryService:FragmentAsync(part, sortedSites, {SplitApart = false})
end)
if not success then
warn("Échec de la fracture:"..tostring(fragments))
return
end
local decals = {}
for _,child in pairs(part:GetChildren()) do
if child:IsA("Decal") or child:IsA("SurfaceAppearance") then
table.insert(decals,child)
end
end
for i = 1, #fragments do
local fragment = fragments[i].Instance
if fragment == nil then
continue
end
for _,d in pairs(decals) do
local d2 = d:Clone()
d2.Parent = fragment
end
fragment.Anchored = false
fragment.Parent = part.Parent
fragment:SetNetworkOwner(player)
end
if #fragments ~= 0 then
part:Destroy()
end
return fragments
endLe script suivant est un cas d'utilisation beaucoup plus spécifique, mais il démontre la puissance des données d'index qui sont retournées par FragmentAsync().
Par exemple, de nombreux endroits contiennent des bâtiments formés de plusieurs pièces de blocs non unies. Si une grenade, un boulet de canon ou un marteau-piqueur devaient les endommager, vous voudriez que toutes les pièces de mur soient fragmentées. Ce script fragmente toutes les pièces à proximité, puis unie les fragments de différentes pièces ensemble pour cacher complètement les coutures.
Cela implique plusieurs opérations Async(), donc cela peut ne pas être adapté à une utilisation en jeu comme réponse instantanée à l'entrée de l'utilisateur, comme un outil de marteau-piqueur.


local GeometryService = game:GetService("GeometryService")
local function fragmentCrossPart(player, part, contactPoint, radius)
local allSites = GeometryService:GenerateFragmentSites(part, {Origin = contactPoint, Radius = radius})
local fragmentsSorted = {}
for i = 1, #allSites do
fragmentsSorted[i] = {}
end
local partsFound = workspace:GetPartBoundsInRadius(contactPoint, radius)
for i, part in ipairs(partsFound) do
local success, fragments = pcall(function()
return GeometryService:FragmentAsync(part, allSites)
end)
if not success then
warn("Échec de la fracture:"..tostring(fragments))
return
end
for i = 1, #fragments do
local fragment = fragments[i].Instance
local siteIndex = fragments[i].Index
if fragment == nil or siteIndex == nil then
continue
end
table.insert(fragmentsSorted[siteIndex], fragment)
end
end
for i = 1, #fragmentsSorted do
local fragmentList = fragmentsSorted[i]
if #fragmentList == 0 then
continue
end
if #fragmentList == 1 then
local fragment = fragmentList[1]
fragment.Anchored = false
fragment.Parent = part.Parent
fragment:SetNetworkOwner(player)
continue
end
if i == #allSites then
for j = 1, #fragmentList do
local fragment = fragmentList[j]
fragment.Parent = part.Parent
fragment.Anchored = true
end
continue
end
local mainPart = fragmentList[1]
local otherParts = {}
for j = 2, #fragmentList do
table.insert(otherParts, fragmentList[j])
end
local success, results = pcall(function()
return GeometryService:UnionAsync(mainPart, otherParts)
end)
if not success then
warn("Échec de l'union:"..tostring(results))
return
end
for j = 1, #results do
results[j].Parent = part.Parent
results[j].Anchored = false
results[j]:SetNetworkOwner(player)
end
end
for i, part in ipairs(partsFound) do
part:Destroy()
end
endLe script suivant est un remplacement Luau presque identique pour GenerateFragmentSites(). Si vous souhaitez un comportement similaire à GenerateFragmentSites() mais que vous souhaitez apporter de légers changements, vous pouvez utiliser ceci comme point de départ.
Il utilise une grille de points jittered et garantit que la zone fragmentée est bien comportée, contrairement à des points complètement aléatoires.
local function generateFragmentSites(part: BasePart, siteSpacing: number?, origin: Vector3?, radius: number?): {Vector3}
local RANDOMNESS_MULTIPLIER = 1.0 -- Utilisez ceci pour ajuster la quantité de jitter
if (origin and not radius) or (radius and not origin) then
warn("Soit l'origine et le rayon doivent être fournis, soit aucun.")
return {}
end
local isLocalized = (radius ~= nil) -- isLocalized signifie ne pas fracturer toute la pièce, juste une section.
local partCFrame = part.ExtentsCFrame
local gridDimensions: Vector3
local localGridCenter: Vector3
local spacing
if siteSpacing then
spacing = siteSpacing
elseif isLocalized then
spacing = radius * 0.5
else
local partSize = part.Size
local volume = partSize.X * partSize.Y * partSize.Z
spacing = (volume / 5) ^ (1/3)
end
if isLocalized then
local localOrigin = partCFrame:PointToObjectSpace(origin)
local gridSize = math.ceil(radius * 2 / spacing) + 3
gridDimensions = Vector3.new(gridSize, gridSize, gridSize)
localGridCenter = localOrigin
else
local partSize: Vector3 = part.Size
local xCount = math.ceil(partSize.X / spacing)
local yCount = math.ceil(partSize.Y / spacing)
local zCount = math.ceil(partSize.Z / spacing)
gridDimensions = Vector3.new(xCount, yCount, zCount)
localGridCenter = Vector3.zero
end
local totalGridSize = gridDimensions * spacing
local halfCell = Vector3.new(spacing, spacing, spacing) * 0.5
local localStartOffset = localGridCenter - (totalGridSize * 0.5) + halfCell
local innerJitter = spacing * 0.5 * RANDOMNESS_MULTIPLIER
local outerJitter = math.min(spacing * 0.5 * 0.866, innerJitter)
local sitesFlatList = {}
for x = 0, gridDimensions.X - 1 do
for y = 0, gridDimensions.Y - 1 do
for z = 0, gridDimensions.Z - 1 do
local isOuterShell =
x == 0 or x == gridDimensions.X - 1 or
y == 0 or y == gridDimensions.Y - 1 or
z == 0 or z == gridDimensions.Z - 1
local jitterAmount = if (isOuterShell and isLocalized) then outerJitter else innerJitter
local jitterOffset = Vector3.new(
(math.random() - 0.5) * 2 * jitterAmount,
(math.random() - 0.5) * 2 * jitterAmount,
(math.random() - 0.5) * 2 * jitterAmount
)
local offsetInGrid = Vector3.new(x, y, z) * spacing
table.insert(sitesFlatList, localStartOffset + offsetInGrid + jitterOffset)
end
end
end
local sitesListFinal = {}
if isLocalized then
local mainPartSites = {}
for _, localSite in ipairs(sitesFlatList) do
local worldSite = partCFrame * localSite
local distance = (worldSite - origin).Magnitude
if distance < radius then
table.insert(sitesListFinal, worldSite)
else
table.insert(mainPartSites, worldSite)
end
end
table.insert(sitesListFinal, 1, mainPartSites)
else
for _, localSite in ipairs(sitesFlatList) do
local worldSite = partCFrame * localSite
table.insert(sitesListFinal, worldSite)
end
end
return sitesListFinal
endPréserver les contraintes
Si une pièce d'entrée a des contraintes ou des attachements que vous souhaitez préserver, vous pouvez les transférer aux pièces résultantes. Il peut être fastidieux de déterminer à quelle pièce de sortie une contrainte doit être attachée, il est donc recommandé d'utiliser CalculateConstraintsToPreserve() pour générer un tableau de recommandations que vous pouvez parcourir et appliquer.
Pour démontrer, l'exemple de code suivant effectue une opération de soustraction, parcourt les pièces résultantes pour réaffecter et repositionner les pièces résultantes, puis calcule un tableau de contraintes et d'attachements à préserver ou à abandonner avant de détruire toutes les pièces originales.
local GeometryService = game:GetService("GeometryService")
local mainPart = workspace.PurpleBlock
local otherParts = { workspace.BlueBlock }
local options = {
CollisionFidelity = Enum.CollisionFidelity.Default,
RenderFidelity = Enum.RenderFidelity.Automatic,
SplitApart = true,
}
local constraintOptions = {
tolerance = 0.1,
weldConstraintPreserve = Enum.WeldConstraintPreserve.All,
dropAttachmentsWithoutConstraints = false,
}
-- Effectuer l'opération de soustraction dans pcall() car elle est asynchrone
local success, newParts = pcall(function()
return GeometryService:SubtractAsync(mainPart, otherParts, options)
end)
if success and newParts then
-- Boucle à travers les pièces résultantes pour réaffecter/repositionner
for _, newPart in pairs(newParts) do
newPart.Parent = mainPart.Parent
newPart.CFrame = mainPart.CFrame
newPart.Anchored = mainPart.Anchored
end
-- Calculer les contraintes/attachements à préserver ou à abandonner
local recommendedTable = GeometryService:CalculateConstraintsToPreserve(mainPart, newParts, constraintOptions)
-- Préserver les contraintes/attachements en fonction du tableau recommandé
for _, item in pairs(recommendedTable) do
if item.Attachment then
item.Attachment.Parent = item.AttachmentParent
if item.Constraint then
item.Constraint.Parent = item.ConstraintParent
end
elseif item.NoCollisionConstraint then
local newNoCollision = Instance.new("NoCollisionConstraint")
newNoCollision.Part0 = item.NoCollisionPart0
newNoCollision.Part1 = item.NoCollisionPart1
newNoCollision.Parent = item.NoCollisionParent
elseif item.WeldConstraint then
local newWeldConstraint = Instance.new("WeldConstraint")
newWeldConstraint.Part0 = item.WeldConstraintPart0
newWeldConstraint.Part1 = item.WeldConstraintPart1
newWeldConstraint.Parent = item.WeldConstraintParent
end
end
-- Détruire les pièces originales
mainPart.Parent = nil
mainPart:Destroy()
for _, otherPart in pairs(otherParts) do
otherPart.Parent = nil
otherPart:Destroy()
end
endRésultats de la modélisation solide
Détails du comportement
Si la pièce principale est en mouvement pendant le calcul de l'opération, vous pouvez définir les pièces résultantes sur la CFrame mise à jour de la pièce principale, puisque les pièces retournées sont dans le même espace de coordonnées que la pièce principale.
Si une opération de modélisation solide devait aboutir à des pièces avec plus de 20 000 triangles, elles seront simplifiées à 20 000. Si cela ne peut pas être fait, généralement dans un cas avec des milliers de composants non superposés, l'opération aboutit à une erreur.
Il existe des méthodes pour remplacer les données de maillage d'une instance, facilitant l'utilisation de la géométrie de l'opération tout en maintenant les propriétés, attributs, tags et enfants de la pièce principale, tels que Attachments, Constraints, ParticleEmitters, objets lumineux et décalcomanies. Cette approche contourne également le potentiel "clignotement" de remplacer complètement l'original PartOperation par un autre.
- Si vous utilisez cette méthode avec un PartOperation comme pièce principale et qu'aucune des autres pièces n'est MeshParts, vous pouvez substituer la géométrie d'un autre PartOperation via SubstituteGeometry().
- Si la pièce principale est un MeshPart, vous pouvez utiliser MeshPart:ApplyMesh().
Il est possible d'appeler ces méthodes sur le client, mais avec certaines limitations. Tout d'abord, cela doit être fait avec des objets créés sur le client. Deuxièmement, il n'y a pas de réplication disponible du client au serveur.
Les propriétés suivantes de la pièce principale sont appliquées aux PartOperations ou MeshParts résultants :
Couleurs et UVs
Les couleurs des pièces résultantes après la modélisation solide proviennent de deux endroits : les couleurs des faces et la Color de la pièce.
- Si le résultat est un PartOperation, il aura la Color de la première pièce que vous avez sélectionnée dans Studio, mais Studio utilise par défaut les couleurs des faces pour garder chaque face de la même couleur qu'elle l'était avant l'opération. Vous pouvez activer sa propriété UsePartColor dans Studio pour remplacer ce comportement et faire en sorte que l'ensemble du résultat soit d'une seule couleur.
- Si le résultat est un MeshPart, sa Color sera blanche, et les couleurs des faces montreront toujours à travers. Vous pouvez ajuster la teinte des pièces résultantes en changeant leur Color, mais cela sera mélangé (multiplié) avec les couleurs des faces. Cela teinte le résultat plutôt que de remplacer complètement les couleurs des faces. Si vous souhaitez un contrôle total sur la couleur de la sortie, il est préférable de rendre les entrées blanches d'abord.
Les UV sont également gérés différemment selon le type de résultat :
- PartOperations ont toujours des UV mappés par boîte, ce qui signifie que chaque face aura le matériau/texture/décalcomanie d'une direction (l'une de -x, +x, -y, +y, -z, +z) appliquée. Cela peut étirer les textures.
- MeshParts ne sont pas mappés par boîte. Les UV du maillage de la pièce principale sont utilisés. Puisque Roblox n'a actuellement pas de support multi-matériaux, les UV des faces provenant des autres pièces reçoivent des UV de (0, 0). Pour de meilleurs résultats, assurez-vous que le pixel (0, 0) de votre texture a une couleur raisonnable.
Angle de lissage
La propriété SmoothingAngle d'une pièce modélisée solide lisse les angles entre les surfaces adjacentes de la même couleur. Une valeur plus élevée produit une apparence plus lisse tandis qu'une valeur plus basse produit une apparence plus rugueuse avec plus de bords tranchants.
Bien qu'une valeur entre 30 et 70 degrés produise généralement un bon résultat, des valeurs entre 90 et 180 ne sont pas recommandées car elles peuvent provoquer un effet "d'ombre" sur les unions et intersections avec des bords tranchants.

