Making a Placement System more efficient than what I have currently?

Hey DevForum, most of my questions I’ve asked have recently related to a placement system but I think for being efficient, I’m being stupid. :stuck_out_tongue:

For Anybody looking to create a Placement System.

A future resource for anybody that will stumble on this.

Anybody searching on how to create a basic Placement System should check this out.
EgoMoose’s Tutorial

Anybody looking for an advanced placement system should try this.
Tunicus Placement

I’m not using the above sources though. I’ll explain.
I’m trying to create a Placement System that can hold multiple parts.

However, I’m a bit stumped on ideas.

For a Placement System that can hold multiple parts, how would I go with calculating grid extents?

At first I considered creating a HitBox using all the models and using math.clamp to restrict it, I don’t think this is the best solution.

Question I asked relating to it.

local function GetBoundingBox(Array) -- Worst function to use in a RenderStepped lol
	local Model = Instance.new("Model")
	for i,v in ipairs(Array) do
		v:Clone().Parent = Model
	end
	local Coordinate, Size = Model:GetBoundingBox()
	Model:Destroy()
	return Size, Coordinate
end

Any more efficient ways to keeping multiple items on grid, possibly without needing to grab the size of the group of models and even if all the items have different rotations?

Speaking of Rotation, I also need to hold alot of Data just for rotation, creating an array for all the original rotations.

local function SnapToGrid(Items,Base,ItemHolder,Rotations,OrigRotation)
	local Coordinates = {}
	local HitCFrame = Hit({unpack(Items)},Base) -- Ray
	
	if HitCFrame then
		local ModelSize, ModelCFrame = GetBoundingBox(Items)
		local HitRelativeToBase = Base.PrimaryPart.CFrame:ToObjectSpace(HitCFrame)
		
		HitRelativeToBase = CFrame.new(Round(HitRelativeToBase.X,3),HitRelativeToBase.Y,Round(HitRelativeToBase.Z,3))
		ModelSize = CFrame.new(math.abs(ModelSize.X),math.abs(ModelSize.Y),math.abs(ModelSize.Z)) * CFrame.Angles(0,Rotation,0)
		
		ModelCFrame = Base.PrimaryPart.CFrame:ToObjectSpace(ModelCFrame)
		ModelCFrame = CFrame.new(Round(ModelCFrame.X,3),ModelCFrame.Y,Round(ModelCFrame.Z,3)) * CFrame.Angles(0,OrigRotation,0)
		
		local GridExtents = CFrame.new((Base.PrimaryPart.Size - Vector3.new(ModelSize.X,0,ModelSize.Z))/2) * CFrame.Angles(0,GetAngle(ModelCFrame),0)
		
		HitRelativeToBase = CFrame.new(Round(HitRelativeToBase.X,3),0,Round(HitRelativeToBase.Z,3))
		HitRelativeToBase = CFrame.new(Round(math.clamp(HitRelativeToBase.X,-GridExtents.X,GridExtents.X),3),HitRelativeToBase.Y,Round(math.clamp(HitRelativeToBase.Z,-GridExtents.Z,GridExtents.Z),3))

		for Index, Item in ipairs(Items) do
			--Rotations[Index] is it's original rotation on start up, OrigRotation should be in 90 degree intervals
			local RotationalY = Rotations[Index]-OrigRotation -- Table of older rotations subtracted by the rotation given here.
			local ItemCFrameOffset = ModelCFrame:ToObjectSpace(Item.PrimaryPart.CFrame)
			local ItemCoordinates = HitRelativeToBase * CFrame.new(0,Rotation,0) * ItemCFrameOffset
			local ItemRotation = CFrame.Angles(0,GetAngle(ItemCoordinates),0)
			ItemCoordinates = CFrame.new(ItemCoordinates.X,((Base.PrimaryPart.CFrame.Y + Base.PrimaryPart.Size.Y/2) + Item.PrimaryPart.Size.Y/2),ItemCoordinates.Z)
			Coordinates[Index] = (ItemCoordinates * ItemRotation):ToWorldSpace(Base.PrimaryPart.CFrame)
		end
		
		Rotation = 0
		
		return Coordinates
	end
end

Any efficient way to create a Rotation offset, without having a table of rotations?

This is the worst placement system I would have ever created.
Not the most efficient with Placement Systems, but trying to learn, it’s a bit more complicated once you add multiple items. :stuck_out_tongue:

4 Likes

Here is some code to create the minimum and maximum corners of an AABB around any array of parts:

local function minMax(min, max, p, o)
	local a = p - o
	local b = p + o
	if a < min then
		min = a
	end
	if b > max then
		max = b
	end
	return min, max
end

local function getAxes(part)
	local s = part.Size * 0.5
	local cf = part.CFrame
	local i = cf.RightVector * s.X
	local j = cf.UpVector * s.Y
	local k = cf.LookVector * s.Z
	return i, j, k
end

local function maxVec(v1, v2, v3)
	return Vector3.new(
		math.max(v1.X, v2.X, v3.X, -v1.X, -v2.X, -v3.X),
		math.max(v1.Y, v2.Y, v3.Y, -v1.Y, -v2.Y, -v3.Y),
		math.max(v1.Z, v2.Z, v3.Z, -v1.Z, -v2.Z, -v3.Z)
	)
end

local function extendAABB(min, max, part)
	local p, o = part.Position, maxVec(getAxes(part))
	local minX, maxX = minMax(min.X, max.X, p.X, o.X)
	local minY, maxY = minMax(min.Y, max.Y, p.Y, o.Y)
	local minZ, maxZ = minMax(min.Z, max.Z, p.Z, o.Z)
	return
		Vector3.new(minX, minY, minZ),
		Vector3.new(maxX, maxY, maxZ)
end

local function getAABB(part)
	local p, o = part.Position, maxVec(getAxes(part))
	return p - o, p + o
end

local parts = workspace.Model:GetChildren()
local min, max = getAABB(parts[1])
for i, part in next, parts do
	min, max = extendAABB(min, max, part)
end
print(min)
print(max)

You can then make sure that whenever the player moves a group of parts, its AABB min and max points are inside the grid. Optimally, this wont be done every frame or even every time the mouse moves, but only after the parts snap to a new position (for example, the common 1 stud snap when dragging a part). In addition, if the selected parts are moving as a whole group, the AABB doesn’t need to be recalculated every time the parts are moved, but instead it can be translated.

10 Likes