Hi, I think I understand what you’re saying, and luckily I believe the APIs provided do have a way to solve your problem.
GenerateFragmentSites returns the sites which are outside the radius in the last (EDIT: I’m changing this to first.) element of its output array. FragmentAsync keeps track of which mesh output comes from which site index, so you can simply look at the fragment.Index to keep track of which one it is.
for i = 1, #fragments do
local fragment = fragments[i].Instance
local siteIndex = fragments[i].Index
if siteIndex == #sites then
-- fragment is the remaining area of the input part.
Yes, you can totally use the union API for this once it is out of beta (and we allow publishing the MeshParts created in studio). The only other caveat I would mention is that the union will only work when the MeshParts are watertight.
@Int_lligence we’ve worked our way through most bugs, the main blocker right now is publishing in studio. We are considering whether we can move the in-experience part out of beta sooner. Will keep you posted.
@Hvdsxn Thanks for letting us know. Will look into this!
Thank you for your quick response, I appreciate it! I actually did not notice this as it felt random but thank you for letting me know because I assumed the order was random.
in fact I was not referring specifically to the new api.
Let’s say I have 10 decorative regular parts (not mesh parts) on a vehicle. they are all different colors and are connected with weld constraints to the vehicle.
and let’s say that I have 5 vehicles in the workspace.
if I simply union these 10 parts in a single union (1 per vehicle), will I get similar rendering performance compared to exporting these 10 parts to a mesh and importing them back in Studio?
I.e. does a union consisting of 10 parts and copied 5 times in the workspace have a similar rendering performance to a mesh with a similar geometry, copied 5 times?
my demo code is super ugly but I hope my snippet helps
local GeometryService = game:GetService("GeometryService")
local Objects = {}
export type Glass = BasePart
Objects["Glass"] = function(object: Glass, player: Player, hit: CFrame, surface: Enum.NormalId)
local sites = GeometryService:GenerateFragmentSites(object, {
Origin = hit.Position,
Radius = 2
})
local success, fragments: {{Index: number, Instance: BasePart}} = pcall( function()
return GeometryService:FragmentAsync(object, sites)
end)
if success and fragments then
print("fragments:", fragments)
table.sort(fragments, function(a, b)
return a.Index > b.Index
end)
local main = fragments[1]
for _, item in fragments do
local instance = item.Instance :: BasePart
instance.Name = item.Index
if item == main then
instance.Anchored = true
instance.Parent = object.Parent
for _, tag in object:GetTags() do
instance:AddTag(tag)
end
for attribute, value in object:GetAttributes() do
instance:SetAttribute(attribute, value)
end
instance.Name = object.Name
for _, child in pairs(object:GetChildren()) do
child.Parent = instance
end
else
instance.Parent = main.Instance
instance.Anchored = false
instance:SetNetworkOwner(player)
end
end
task.defer(function()
object:Destroy()
end)
end
end
return Objects
@Inkthirsty Btw, I’m changing the mainPart/remainder from being the last one in the list to the first one in the list (always index 1). There was a reason we had put it at the end but that reason isn’t valid anymore, and I think having it at the beginning is more ergonomic and less surprising. But your script will need a minor change, of course.
I expect the change will be applied late next week.
Thank you for letting me know and I appreciate you listening to my feedback!
Btw I noticed missing fragment indices like skipping from 2 to 4 and 5 to 7, I’m not sure if fragments merge to save memory, but I was paranoid about them being in the incorrect order so I’ve been sorting the table before iterating it. I also recall seeing fragments return in a random order when I tested this on April 5th (e.g. index 6 appearing before index 3), although I haven’t seen this happen again so it may have been fixed or I imagined it.
table.sort(fragments, function(a, b)
return a.Index < b.Index
end)
No problem! Thanks for keeping the excellent feedback coming.
We haven’t actually been attempting to keep the fragment list sorted by .Index, but I’ll have to consider doing that because ‘usually sorted’ could be confusing.
There’s no guarantee of a 1-to-1 relationship between sites and fragments.
If a site is outside the actual bounds of the Part, which is common, it often doesn’t result in a fragment because no portion of the Part is closest to that site.
If the SplitApart option is True, and the Part is concave, then a single site can be responsible for two or more fragments. Also, a single inner array of sites (like when using point-radius mode) can very easily result in multiple fragments even on a convex part.
It makes sense to exclude parts that do not intersect the geometry, that was my assumption for the missing indices.
If I currently cannot trust the order, would you recommend using table.sort and fragments[1] as a temporary solution to always find the remainder? While this isn’t an issue for me, I feel like most developers would assume the order will always be sequential or they may look for .Index == 1 which will be nil if the remainder’s index is 2 or higher
I also didn’t notice that I could add options to FragmentAsync as I only noticed them for GenerateFragmentSites so thank you for letting me know, I’ll be sure to try them out!
The remainder’s .Index will always be 1 (starting next week, of course). If there aren’t any parts with Index == 1 then there was no remainder. (For example, if you set the radius larger than the part.)
As a temporary solution for this week, I would recommend simply checking .Index == #sites.
I agree, the position / offset is based on the part that it’s a fragment of! I still haven’t been able to find any way to set the position / pivot to the actual mass of the fragment yet though…
I can see a use for the pivots to be the way are now, but at the same time I’d like if it could be decided by the scripter whether or not they’re based on the fragmented part or the actual mass of the fragment
Hi, yes solid modeling using the GeometryService APIs frequently results in parts with positions far from the visual center of the object. It makes some things easier, for example when using ApplyMesh/SubstituteGeometry.
I think what you’ll want to do in most cases is adjust the PivotOffset to place the pivot at the center of mass, with these three lines:
local centerOfMassWorld = fragment.AssemblyCenterOfMass
local localCenter = fragment.CFrame:ToObjectSpace(CFrame.new(centerOfMassWorld))
fragment.PivotOffset = localCenter
We are already considering adding a recenter option to the boolean APIs, so I’ll make sure we include Fragment in that discussion. Thanks for the feedback!
Could we get a :IsPointIntersecting() or an :ArePointsIntersecting() method for the basepart class? The example code you guys have on your website shows a important usecase for this with the new CSG apis but the code is forced to use a slow hacky workaround. Why not just make an API for this like you guys did with :GetClosestPointOnSurface()?