I like this update
I have a question about performance. Letâs say that i generate 5 cube parts that i randomly place in a radius. I want to use them to make a hole in the part using substract but also create debris using intersect. So here is the question: would unioning the parts before doing any substract and intersect operations increase performance? Or would it actually make it worse? Or maybe there wouldnât be any difference?
Hi everyone. Happy New year
Couple of updates:
- @zagusan We have fix for this coming soon (knock on wood).
- Following this bug fix, we should be ready for an open beta relatively quickly.
- @BubasGaming Many simple tools is much better for the API than unioned parts. Quick behind the curtain: When you do the union, you generate a convex decomposition, mesh and a network call. Additionally, when your tools are complex (ie, not primitives), they currently have to be recomputed in the same coordinate space as the main part. In other words. It is MUCH better to pass simple tools to the API. That being said, if there is a request for a combined subtract/intersect operation, it might be worth taking a look at it
BelgianBikeGuy
Wow cool feature, I remember that baseParts also had it but itâs only server sided so thatâs a no-no from me. I found a pretty unique (imo) way of using it as a sort of a âvoid explosionâ
I am pretty sure I have done something wrong since it still sometimes flickers while somewhere in documentation it said it shouldnât flick. And itâs also slow for the Suburban template (although I donât know whatâs inside houses tbh, maybe itâs too much to handle). Not to mention sometimes it throws an error -4 and -6
Source code
local TweenService = game:GetService('TweenService')
local GeometryService = game:GetService('GeometryService')
local ContextActionService = game:GetService('ContextActionService')
local mouse = game:GetService('Players').LocalPlayer:GetMouse()
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Include
overlapParams.FilterDescendantsInstances = {workspace.GameMap} -- a random folder so we are only interested in static props/buildings
local radius = 25
local options = {
CollisionFidelity = Enum.CollisionFidelity.Default,
RenderFidelity = Enum.RenderFidelity.Automatic,
SplitApart = false
}
local function createBall(position)
local part = Instance.new('Part')
part.Shape = Enum.PartType.Ball
part.Anchored = true
part.CanCollide = false
part.CanQuery = false
part.CanTouch = false
part.Size = Vector3.one * radius * 2
part.Position = position
part.Color = Color3.fromRGB(75, 6, 131)
part.TopSurface = Enum.SurfaceType.Smooth
part.BottomSurface = Enum.SurfaceType.Smooth
part.CastShadow = false
part.Material = Enum.Material.ForceField
return part
end
local function fire(action, userInputState: UserInputState, inputObject: InputObject)
if userInputState ~= Enum.UserInputState.Begin then return end
local mouseCFrame = mouse.Hit
local partsInRadius = workspace:GetPartBoundsInRadius(mouseCFrame.Position, radius, overlapParams)
local resultPartsTable = {}
local negateBall = createBall(mouseCFrame.Position)
negateBall.Parent = workspace.Temp -- works as an 'explosion' effect
TweenService:Create(negateBall, TweenInfo.new(1.25), {Transparency = 1}):Play()
for i, part in partsInRadius do
local resultParts = GeometryService:SubtractAsync(part, {negateBall}, options)
for i, resultPart in resultParts do
table.insert(resultPartsTable, resultPart)
resultPart.Parent = workspace.Temp
end
part.Transparency = 1 -- making original parts invisible
end
task.wait(2)
negateBall:Destroy() -- this part is invisible at this state
for i, part in partsInRadius do
TweenService:Create(part, TweenInfo.new(3), {Transparency = 0}):Play() -- making original parts visible again
end
task.wait(3.2)
for i, part in resultPartsTable do
part:Destroy() -- destroying generated parts
end
end
ContextActionService:BindAction('fire', fire, true, Enum.UserInputType.MouseButton1)
Edit: just saw a guy in this topic that uses new api to destroy a map, guess itâs not-so-original now
Hi thatâs me lol
They changed that a while back, now you can also use CSG on the client, but the results donât replicate to other users
Thatâs a pretty cool use of the technology. Iâd like to see it turned into a game mechanic
Ok, so void explosions are a super neat idea.
Error -4 and -6 mean that theyâre either an unknown primitive or a broken CSG object from the catalog. We are aware that the errors are very cryptic (and annoying honestly) and we have are planning on making them more user readable (with more information so you guys know how you should react to them).
Additionally, Iâm kinda interested in the many little parts case. Do you have a simple repro (it would save me a decent amount of time ) @MrSuperKrut
Thanks
~BelgianBikeGuy
I have made multiple improvements to this code, most importantly if part is fully within the radius then instead of us performing a SubtractAsync operation we just hide this part, saving a lot of time. Nonetheless here is the source code
Source code
local WAIT_FOR_GENERATING_PARTS = false -- if true, the script wont hide parts until it generates all parts using geometryService
local IGNORE_UNANCHORED_PARTS = true -- setting to true to any of these will make all choosen parts ignored
local IGNORE_UNION_PARTS = false
local IGNORE_MESH_PARTS = false
local EXPLOSION_RADIUS = 50
local tweenService = game:GetService('TweenService')
local geometryService = game:GetService('GeometryService')
local mouse = game:GetService('Players').LocalPlayer:GetMouse() -- i know it's deprecated but it will do for this
local userInputService = game:GetService('UserInputService')
local actionService = game:GetService('ContextActionService')
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
overlapParams.FilterDescendantsInstances = {workspace.Animation} -- a random folder so we ignore newly created parts and characters
local options = {
CollisionFidelity = Enum.CollisionFidelity.Default,
RenderFidelity = Enum.RenderFidelity.Automatic,
SplitApart = false
}
local function isPartFullyInRadius(part: BasePart, position: Vector3) -- this function returns true if all corners of a given part are within a radius
-- position is a position of a mouse
local cframes = {
part.CFrame * CFrame.new(part.Size.X / 2, part.Size.Y / 2, part.Size.Z / 2),
part.CFrame * CFrame.new(part.Size.X / 2, part.Size.Y / 2, -part.Size.Z / 2),
part.CFrame * CFrame.new(part.Size.X / 2, -part.Size.Y / 2, -part.Size.Z / 2),
part.CFrame * CFrame.new(-part.Size.X / 2, -part.Size.Y / 2, -part.Size.Z / 2),
part.CFrame * CFrame.new(part.Size.X / 2, -part.Size.Y / 2, part.Size.Z / 2),
part.CFrame * CFrame.new(-part.Size.X / 2, -part.Size.Y / 2, part.Size.Z / 2),
part.CFrame * CFrame.new(-part.Size.X / 2, part.Size.Y / 2, part.Size.Z / 2),
part.CFrame * CFrame.new(-part.Size.X / 2, part.Size.Y / 2, -part.Size.Z / 2),
}
for i, cframe: CFrame in cframes do
if (cframe.Position - position).Magnitude >= EXPLOSION_RADIUS then -- one of the corners are outside of radius - returning false
return false
end
end
return true
end
local function createBall(position)
local part = Instance.new('Part')
part.Shape = Enum.PartType.Ball
part.Anchored = true
part.CanCollide = false
part.CanQuery = false
part.CanTouch = false
part.Size = Vector3.one * EXPLOSION_RADIUS * 2
part.Position = position
part.Color = Color3.fromRGB(75, 6, 131)
part.TopSurface = Enum.SurfaceType.Smooth
part.BottomSurface = Enum.SurfaceType.Smooth
part.CastShadow = false
part.Material = Enum.Material.ForceField
return part
end
local function fire(action, userInputState: UserInputState, inputObject: InputObject)
if userInputState ~= Enum.UserInputState.Begin then return end
local startTime = os.clock()
local generatingTotalTime = 0
local mouseCFrame = mouse.Hit
local partsInRadius = workspace:GetPartBoundsInRadius(mouseCFrame.Position, EXPLOSION_RADIUS, overlapParams)
local originalPartsTransparency = {} -- [index] = Transparency. Some objects could be semi-transparent so we are saving their original Transparency property here
local resultPartsTable = {}
local negateBall = createBall(mouseCFrame.Position)
negateBall.Parent = workspace.Animation
tweenService:Create(negateBall, TweenInfo.new(1.25), {Transparency = 1}):Play()
for i, part in partsInRadius do
originalPartsTransparency[i] = part.Transparency
if (part.Anchored == false and IGNORE_UNANCHORED_PARTS) or (part:IsA('UnionOperation') and IGNORE_UNION_PARTS) or ((part:IsA('MeshPart') or part:FindFirstChildWhichIsA('SpecialMesh', false)) and IGNORE_MESH_PARTS) then
continue
elseif not isPartFullyInRadius(part, mouseCFrame.Position) then -- if part isn't fully within a radius - perform a subtractAsync operation
local generatingStartTime = os.clock()
local success, resultParts = pcall(function()
return geometryService:SubtractAsync(part, {negateBall}, options)
end)
if not success then
warn('Error for', part ,`. Error: {resultParts}`)
else
for i, resultPart in resultParts do
table.insert(resultPartsTable, resultPart)
if WAIT_FOR_GENERATING_PARTS == false then
resultPart.Parent = workspace.Animation
end
end
end
generatingTotalTime += os.clock() - generatingStartTime
end
if WAIT_FOR_GENERATING_PARTS == false then
part.Transparency = 1
end
end
if WAIT_FOR_GENERATING_PARTS then -- instead of hiding parts during generating new parts, it can hide parts right here after all parts were generated
for i, part in partsInRadius do
part.Transparency = 1
end
for i, part in resultPartsTable do
part.Parent = workspace.Animation
end
end
print('Generating:' .. generatingTotalTime .. ' s., total: ' .. os.clock() - startTime)
print('Parts skipped: ' .. #partsInRadius - #resultPartsTable .. ', generated: ' .. #resultPartsTable .. ', total: ' .. #partsInRadius)
task.wait(2.5)
negateBall:Destroy()
local lastTween
for i, part in partsInRadius do
local targetTransparency = originalPartsTransparency[i]
lastTween = tweenService:Create(part, TweenInfo.new(3), {Transparency = targetTransparency})
lastTween:Play()
end
lastTween.Completed:Wait()
for i, part in resultPartsTable do
part:Destroy()
end
end
actionService:BindAction('fire', fire, true, Enum.KeyCode.Q)
destructionTest.rbxl (1.3 MB)
In the output you can see something like Generating: x s., total: y s.
. It just tells you the amount of seconds it took for a script to use SubtractAsync
and a total time it took to go trough the function (excluding the last part where we just using TweenService
). Usually they are almost the same.
In addition it also prints Parts skipped: x, generated: y, total: x + y
. Parts skipped mean how many parts were ignored with the SOMETHING_IGNORE
property and not used in the SubtractAsync
due to part being fully within the explosion radius; generated is how many parts were involved into generating new parts.
It also handles the SubtractAsync
errors providing the following: Error for Instance . Error: errorMessage
. Instance is clickable and will choose a model that caused an error
This project uses default Suburban template with the âAnimationâ folder created and has 2 scripts:
StarterPlayer.StarterPlayerScripts.LocalScript
is the code above;
StarterPlayer.StarterCharacterScripts.Script
is a script that just sets characterâs parent to âAnimationâ folder inside of workspace
To use an âexplosionâ effect, press Q on your keyboard and it will use your mouse as an explosion position
And sorry if this code isnât not very readable, too much complicated and has some grammar errors
Also forgot to mention that spamming âexplosionsâ will break the world since it will remember the wrong Transparency value, and it just not gonna work if you use it in the same position
Edit 2: after playing with a code a little bit I achieved low generating times using task.spawn
shenanigans
Before using task.spawn
:
After:
What I am trying to say is it would be cool to have an ability to do it with just a single SubtractAsync
call instead of using this method since I believe people behind the engine know more that most devs.
Sorry if I misread your code, but why are you using SubtractAsync
on every part individually instead of using a list of parts in a single SubtractAsync
call?
Because a first argument of a SubtractAsync must be an Instance
aka the part you want to modify. Only one part can be modified in a single call. The second argument is the ânegative partsâ array. In the script i am taking multiple parts from the world to modify their appearence using one sphere (referenced as explosion) hence thatâs why i am making multiple calls
Instead of bringing new APIs to an existing method - Why not work more on the actual CSG methodology?
Iâm unsure as to why CSG v3 does not have the same smoothing abilities as CSG v1 - SmoothingAngle is completely useless in comparison to the smoothing that CSG v1 offered.
When will this be out of bata?
Any news on when this will be in our hands or if there will be availability to early enroll a game? This is the last hope for a big project Iâm working on to be released, so some knowledge would be very helpful
fix the unions please⌠i already filled a bug report and nobody is answering this is urgent.
Hi @TimTsuki, we were able to reproduce and fixed the issue. Please recheck. If your issue persists, please provide us with your model for further investigation.
omg let me check, i will refresh the studio and see, iâve made a bug report but nobody answers!! https://devforum.roblox.com/t/important-textures-show-incorrectly-on-the-geometry-of-the-new-unions/2819657/2?u=timtsuki
welp, iâm not sure if iâve got the Update on my studio yet⌠the issue persists.
Heres a example model:
bugged unions.rbxm (162.5 KB)
i think my studio havenât updated yet, you can see my unions thought, there some examples of an union + a Texture / decal
hello again, @meshadapt
i forgot to mention that the new broken unions
have this triangular fissures and micro fissures (which they can only be seen when selecting) the bigger fissures can be seen when using unions that involves spheres. i use solid modelling a lot and is breaking my progress
@TimTsuki this seems to be a different issue. Could you provide us the model that can reproduce the problem?
NEVERMIND! it is already solved, it was fixed when you fixed the wrong displayed textures!! (my studio didnât seem to update at the time) Thank you so much @meshadapt !
Does anyone know when this will be available for external use?