[Studio Beta] Solid Modeling on Meshes & new Fragment and Sweep APIs

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.

I’d also highly recommend looking at the new Fragment section in the guide: https://create.roblox.com/docs/parts/solid-modeling#fragmentasync

Hope this solves your problem, and thanks for using the new features - can’t wait to see how you use it in games!

1 Like

Thanks for letting us know. I’ve fixed the issue and will enable the bugfix with the upcoming studio release.

1 Like

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.

Edit: this is exactly what I wanted thank you so much!!!

7 Likes

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?

you have to code all this yourself then?

1 Like

hi, meshpart + meshpart unions creates flickering parts based on camera angle


for some reason it appears back while selected

Thanks for letting us know. We’ve found the issue and are working on a fix!

1 Like

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

2 Likes

@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.

1 Like

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.

  1. 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.
  2. 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.
1 Like

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 hope that answers the question!

1 Like

Yeah that’s all I needed to know thank you!!!

Something I’ve encountered while working fragmentation into my game, fragments have misaligned / offset positions (not origin points)

I noticed this when I saw the fragments spinning wildly when I would ApplyImpulseAtPosition() them

2 Likes

I think the pivots are set to the center of the original part though I’m not sure if it’s necessary. you should be able to change this tho

1 Like

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

1 Like

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!

3 Likes

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()?

Also the code I’m referring to is here. Fragment within an area defined by another part.
https://create.roblox.com/docs/parts/solid-modeling#fragmentasync

3 Likes