-- Processes a given model for simulation behavior
local function ProcessModel(model)
-- Run the function on a separate task thread (non-blocking)
task.delay(0, function()
-- Validate input model
if not model or not model:IsA("Model") or model.Name == "Trees" then
warn("Invalid model provided: ".. model.Name)
return
end
-- Get the model's PrimaryPart (used as the anchor/reference)
local primaryPart = model.PrimaryPart
-- Remove all attributes from the PrimaryPart
for attributeName in pairs(primaryPart:GetAttributes()) do
primaryPart:SetAttribute(attributeName, nil)
end
-- Function to check if a part is grounded (touching Terrain)
local function isGrounded(part)
-- Get a list of parts that the current part is touching
local touchingParts = part:GetTouchingParts()
-- Return true if touching terrain
for _, touchingPart in ipairs(touchingParts) do
if touchingPart:IsA("Terrain") then
return true
end
end
return false
end
-- Weld two parts together using WeldConstraint
local function weldParts(part1, part2)
-- Only proceed if both are BaseParts
if not part1:IsA("BasePart") or not part2:IsA("BasePart") then
return
end
-- Check if a weld already exists to avoid duplication
for _, weld in ipairs(part1:GetChildren()) do
if weld:IsA("WeldConstraint") and
((weld.Part0 == part1 and weld.Part1 == part2) or
(weld.Part0 == part2 and weld.Part1 == part1)) then
return -- Weld already exists
end
end
-- Create and set up the weld
local weld = Instance.new("WeldConstraint")
weld.Part0 = part1
weld.Part1 = part2
weld.Parent = part1
end
-- Collect all BaseParts in the model
local allParts = {}
for _, part in pairs(model:GetDescendants()) do
if part:IsA("BasePart") then
table.insert(allParts, part)
-- Unanchor all parts initially (except the primary part)
if part ~= primaryPart then
part.Anchored = false
end
-- Connect a Touched event to re-anchor the part if it touches Terrain
local touchConnection
touchConnection = part.Touched:Connect(function(hitPart)
-- If part is removed, disconnect the connection
if not part:IsDescendantOf(workspace) then
touchConnection:Disconnect()
return
end
-- Re-anchor part if it touches terrain (and is still grounded)
if hitPart:IsA("Terrain") then
if isGrounded(part) then
part.Anchored = true
end
end
end)
-- Monitor each part per frame to check if it's still grounded
local heartbeatConnection
heartbeatConnection = game:GetService("RunService").Heartbeat:Connect(function()
if not part:IsDescendantOf(workspace) then
heartbeatConnection:Disconnect()
return
end
-- If the part was anchored but no longer grounded, unanchor it
if part.Anchored and part ~= primaryPart then
if not isGrounded(part) then
part.Anchored = false
end
end
end)
end
end
-- On startup: anchor any part already touching the ground
for _, part in ipairs(allParts) do
if part ~= primaryPart and isGrounded(part) then
part.Anchored = true
end
end
-- === TOUCHING LOGIC PLACEHOLDER ===
-- You could add logic here to weld together parts that are touching each other
-- Function to check if model should be removed (e.g., only primary part is left)
local function checkForRemoval()
local remainingParts = 0
for _, part in pairs(model:GetDescendants()) do
if part:IsA("BasePart") then
remainingParts += 1
end
end
-- If only 1 BasePart remains (the primary), destroy the whole model
if remainingParts <= 1 then
model:Destroy()
end
end
-- Listen for parts being removed from the model
model.DescendantRemoving:Connect(function(descendant)
-- If a BasePart (not the primary) is removed, check if model should be destroyed
if descendant:IsA("BasePart") and descendant ~= primaryPart then
task.defer(checkForRemoval) -- delay to let removal finish
end
end)
return true -- indicate success
end)
end
Function is called every time a pet is placed for the terrain. The video explains the outcomes shown. This rock model in particular is just a min model with a primary and parts underneath it. One issue could be that the parts are deeply intersecting when anchored. But even so this shouldn’t happen