local t = {}

function waitForChild(instance, name)
	while not instance:FindFirstChild(name) do
		instance.ChildAdded:wait()
	end
end

-- Do a line/plane intersection.  The line starts at the camera.  The plane is at y == 0, normal(0, 1, 0)
--
-- vectorPos - End point of the line.
-- 
-- Return:
-- cellPos - The terrain cell intersection point if there is one, vectorPos if there isn't.
-- hit - Whether there was a plane intersection.  Value is true if there was, false if not.
function PlaneIntersection(vectorPos)
	local hit = false
	local currCamera = game:GetService("Workspace").CurrentCamera
	local startPos = Vector3.new(currCamera.CoordinateFrame.p.X, currCamera.CoordinateFrame.p.Y, currCamera.CoordinateFrame.p.Z)
	local endPos = Vector3.new(vectorPos.X, vectorPos.Y, vectorPos.Z)
	local normal = Vector3.new(0, 1, 0)
	local p3 = Vector3.new(0, 0, 0)
	local startEndDot = normal:Dot(endPos - startPos)
	local cellPos = vectorPos
	if startEndDot ~= 0  then
		local t = normal:Dot(p3 - startPos) / startEndDot
		if(t >=0 and t <=1) then
			local intersection = ((endPos - startPos) * t) + startPos
			cellPos = game:GetService("Workspace").Terrain:WorldToCell(intersection)
			hit = true
		end
	end

	return cellPos, hit
end


-- Purpose:
-- Checks for terrain touched by the mouse hit.
-- Will do a plane intersection if no terrain is touched.
--
-- mouse - Mouse to check the .hit for.
--
-- Return:
-- cellPos - Cell position hit.  Nil if none.
function GetTerrainForMouse(mouse)
	-- There was no target, so all it could be is a plane intersection.
	-- Check for a plane intersection.  If there isn't one then nothing will get hit.
	local cell = game:GetService("Workspace").Terrain:WorldToCellPreferSolid(Vector3.new(mouse.hit.x, mouse.hit.y, mouse.hit.z))
	local planeLoc = nil
	-- If nothing was hit, do the plane intersection.
	if 0 == game:GetService("Workspace").Terrain:GetCell(cell.X, cell.Y, cell.Z).Value then
		cell = nil
		planeLoc, hit = PlaneIntersection(Vector3.new(mouse.hit.x, mouse.hit.y, mouse.hit.z))
		if hit then
			cell = planeLoc
		end
	end
	return cell
end

-- setup helper functions
local insertBoundingBoxOverlapVector = Vector3.new(.3, .3, .3) -- we can still stamp if our character extrudes into the target stamping space by .3 or fewer units

-- rotates a model by yAngle radians about the global y-axis
local function rotatePartAndChildren(part, rotCF, offsetFromOrigin)
	-- rotate this thing, if it's a part
	if part:IsA("BasePart") then
		part.CFrame = (rotCF * (part.CFrame - offsetFromOrigin)) + offsetFromOrigin
	end

	-- recursively do the same to all children
	local partChildren = part:GetChildren()
	for c = 1, #partChildren do rotatePartAndChildren(partChildren[c], rotCF, offsetFromOrigin) end
end

local function modelRotate(model, yAngle)
	local rotCF = CFrame.Angles(0, yAngle, 0)
	local offsetFromOrigin = model:GetModelCFrame().p

	rotatePartAndChildren(model, rotCF, offsetFromOrigin)
end


local function collectParts(object, baseParts, scripts, decals)
	if object:IsA("BasePart") then
		baseParts[#baseParts+1] = object
	elseif object:IsA("Script") then
		scripts[#scripts+1] = object
	elseif object:IsA("Decal") then
		decals[#decals+1] = object
	end

	for index,child in pairs(object:GetChildren()) do
		collectParts(child, baseParts, scripts, decals)
	end
end

local function clusterPartsInRegion(startVector, endVector)
	local cluster = game:GetService("Workspace"):FindFirstChild("Terrain")

	local startCell = cluster:WorldToCell(startVector)
	local endCell = cluster:WorldToCell(endVector)

	local startX = startCell.X
	local startY = startCell.Y
	local startZ = startCell.Z

	local endX = endCell.X
	local endY = endCell.Y
	local endZ = endCell.Z

	if startX < cluster.MaxExtents.Min.X then startX = cluster.MaxExtents.Min.X end
	if startY < cluster.MaxExtents.Min.Y then startY = cluster.MaxExtents.Min.Y end
	if startZ < cluster.MaxExtents.Min.Z then startZ = cluster.MaxExtents.Min.Z end

	if endX > cluster.MaxExtents.Max.X then endX = cluster.MaxExtents.Max.X end
	if endY > cluster.MaxExtents.Max.Y then endY = cluster.MaxExtents.Max.Y end
	if endZ > cluster.MaxExtents.Max.Z then endZ = cluster.MaxExtents.Max.Z end

	for x = startX, endX do
		for y = startY, endY do
			for z = startZ, endZ do
				if (cluster:GetCell(x, y, z).Value) > 0 then return true end
			end
		end
	end

	return false
end

local function findSeatsInModel(parent, seatTable)
	if not parent then return end

	if parent.className == "Seat" or parent.className == "VehicleSeat" then
		table.insert(seatTable, parent)
	end
	local myChildren = parent:GetChildren()
	for j = 1, #myChildren do
		findSeatsInModel(myChildren[j], seatTable)
	end
end

local function setSeatEnabledStatus(model, isEnabled)
	local seatList = {}
	findSeatsInModel(model, seatList)

	if isEnabled then
		-- remove any welds called "SeatWeld" in seats
		for i = 1, #seatList do
			local nextSeat = seatList[i]:FindFirstChild("SeatWeld")
			while nextSeat do nextSeat:Remove() nextSeat = seatList[i]:FindFirstChild("SeatWeld") end
		end
	else
		-- put a weld called "SeatWeld" in every seat
		--   this tricks it into thinking there's already someone sitting there, and it won't make you sit XD
		for i = 1, #seatList do
			local fakeWeld = Instance.new("Weld")
			fakeWeld.Name = "SeatWeld"
			fakeWeld.Parent = seatList[i]
		end
	end
end

local function autoAlignToFace(parts)
	local aatf = parts:FindFirstChild("AutoAlignToFace")
	if aatf then return aatf.Value else return false end
end

local function getClosestAlignedWorldDirection(aVector3InWorld)
	local xDir = Vector3.new(1,0,0)
	local yDir = Vector3.new(0,1,0)
	local zDir = Vector3.new(0,0,1)
	local xDot = aVector3InWorld.x * xDir.x + aVector3InWorld.y * xDir.y + aVector3InWorld.z * xDir.z
	local yDot = aVector3InWorld.x * yDir.x + aVector3InWorld.y * yDir.y + aVector3InWorld.z * yDir.z
	local zDot = aVector3InWorld.x * zDir.x + aVector3InWorld.y * zDir.y + aVector3InWorld.z * zDir.z

	if math.abs(xDot) > math.abs(yDot) and math.abs(xDot) > math.abs(zDot) then
		if xDot > 0 then
			return 0
		else
			return 3
		end
	elseif math.abs(yDot) > math.abs(xDot) and math.abs(yDot) > math.abs(zDot) then
		if yDot > 0 then
			return 1
		else
			return 4
		end
	else
		if zDot > 0 then
			return 2
		else
			return 5
		end
	end 
end

local function positionPartsAtCFrame3(aCFrame, currentParts)
	local insertCFrame = nil
	if not currentParts then return currentParts end
	if currentParts and (currentParts:IsA("Model") or currentParts:IsA("Tool")) then
		insertCFrame = currentParts:GetModelCFrame()
		currentParts:TranslateBy(aCFrame.p - insertCFrame.p)
	else
		currentParts.CFrame = aCFrame
	end
	return currentParts
end

local function calcRayHitTime(rayStart, raySlope, intersectionPlane)
	if math.abs(raySlope) < .01 then return 0 end -- 0 slope -->  we just say intersection time is 0, and sidestep this dimension
	return (intersectionPlane - rayStart) / raySlope
end

local function modelTargetSurface(partOrModel, rayStart, rayEnd)
	if not partOrModel then
		return 0
	end

	local modelCFrame = nil
	local modelSize = nil
	if partOrModel:IsA("Model") then
		modelCFrame = partOrModel:GetModelCFrame()
		modelSize = partOrModel:GetModelSize()
	else
		modelCFrame = partOrModel.CFrame
		modelSize = partOrModel.Size
	end

	local mouseRayStart = modelCFrame:pointToObjectSpace(rayStart)
	local mouseRayEnd = modelCFrame:pointToObjectSpace(rayEnd)
	local mouseSlope = mouseRayEnd - mouseRayStart

	local xPositive = 1
	local yPositive = 1
	local zPositive = 1
	if mouseSlope.X > 0 then xPositive = -1 end
	if mouseSlope.Y > 0 then yPositive = -1 end
	if mouseSlope.Z > 0 then zPositive = -1 end

	-- find which surface the transformed mouse ray hits (using modelSize):
	local xHitTime = calcRayHitTime(mouseRayStart.X, mouseSlope.X, modelSize.X/2 * xPositive)
	local yHitTime = calcRayHitTime(mouseRayStart.Y, mouseSlope.Y, modelSize.Y/2 * yPositive)
	local zHitTime = calcRayHitTime(mouseRayStart.Z, mouseSlope.Z, modelSize.Z/2 * zPositive)

	local hitFace = 0

	--if xHitTime >= 0 and yHitTime >= 0 and zHitTime >= 0 then
	if xHitTime > yHitTime then
		if xHitTime > zHitTime then
			-- xFace is hit
			hitFace = 1*xPositive
		else
			-- zFace is hit
			hitFace = 3*zPositive
		end
	else
		if yHitTime > zHitTime then
			-- yFace is hit
			hitFace = 2*yPositive
		else
			-- zFace is hit
			hitFace = 3*zPositive
		end
	end

	return hitFace
end

local function getBoundingBox2(partOrModel)

	-- for models, the bounding box is defined as the minimum and maximum individual part bounding boxes
	-- relative to the first part's coordinate frame.
	local minVec = Vector3.new(math.huge, math.huge, math.huge)
	local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge)

	if partOrModel:IsA("Terrain") then
		minVec = Vector3.new(-2, -2, -2)
		maxVec = Vector3.new(2, 2, 2)
	elseif partOrModel:IsA("BasePart") then
		minVec = -0.5 * partOrModel.Size
		maxVec = -minVec
	else
		maxVec = partOrModel:GetModelSize()*0.5
		minVec = -maxVec
	end

	-- Adjust bounding box to reflect what the model or part author wants in  terms of justification
	local justifyValue = partOrModel:FindFirstChild("Justification")
	if justifyValue ~= nil then
		-- find the multiple of 4 that contains the model
		justify = justifyValue.Value
		two = Vector3.new(2, 2, 2)
		actualBox = maxVec - minVec - Vector3.new(0.01, 0.01, 0.01)
		containingGridBox = Vector3.new(4 * math.ceil(actualBox.x/4), 4 * math.ceil(actualBox.y/4), 4 * math.ceil(actualBox.z/4))
		adjustment = containingGridBox - actualBox
		minVec = minVec - 0.5 * adjustment * justify
		maxVec = maxVec + 0.5 * adjustment * (two - justify)
	end

	return minVec, maxVec
end

local function getBoundingBoxInWorldCoordinates(partOrModel)
	local minVec = Vector3.new(math.huge, math.huge, math.huge)
	local maxVec = Vector3.new(-math.huge, -math.huge, -math.huge)

	if partOrModel:IsA("BasePart") and not partOrModel:IsA("Terrain") then
		vec1 = partOrModel.CFrame:pointToWorldSpace(-0.5 * partOrModel.Size)
		vec2 = partOrModel.CFrame:pointToWorldSpace(0.5 * partOrModel.Size)
		minVec = Vector3.new(math.min(vec1.X, vec2.X), math.min(vec1.Y, vec2.Y), math.min(vec1.Z, vec2.Z))
		maxVec = Vector3.new(math.max(vec1.X, vec2.X), math.max(vec1.Y, vec2.Y), math.max(vec1.Z, vec2.Z))
	elseif partOrModel:IsA("Terrain") then
		-- we shouldn't have to deal with this case
		--minVec = Vector3.new(-2, -2, -2)
		--maxVec = Vector3.new(2, 2, 2)
	else
		vec1 = partOrModel:GetModelCFrame():pointToWorldSpace(-0.5 * partOrModel:GetModelSize())
		vec2 = partOrModel:GetModelCFrame():pointToWorldSpace(0.5 * partOrModel:GetModelSize())
		minVec = Vector3.new(math.min(vec1.X, vec2.X), math.min(vec1.Y, vec2.Y), math.min(vec1.Z, vec2.Z))
		maxVec = Vector3.new(math.max(vec1.X, vec2.X), math.max(vec1.Y, vec2.Y), math.max(vec1.Z, vec2.Z))
	end

	return minVec, maxVec
end

local function getTargetPartBoundingBox(targetPart)
	if targetPart.Parent:FindFirstChild("RobloxModel") ~= nil then
		return getBoundingBox2(targetPart.Parent)
	else
		return getBoundingBox2(targetPart)
	end
end

local function getMouseTargetCFrame(targetPart)
	if targetPart.Parent:FindFirstChild("RobloxModel") ~= nil then
		if targetPart.Parent:IsA("Tool") then return targetPart.Parent.Handle.CFrame
		else return targetPart.Parent:GetModelCFrame() end
	else
		return targetPart.CFrame
	end
end

local function isBlocker(part) -- returns whether or not we want to cancel the stamp because we're blocked by this part
	if not part then return false end
	if not part.Parent then return false end
	if part:FindFirstChild("Humanoid") then return false end
	if part:FindFirstChild("RobloxStamper") or part:FindFirstChild("RobloxModel") then return true end
	if part:IsA("Part") and not part.CanCollide then return false end
	if part == game:GetService("Lighting") then return false end
	return isBlocker(part.Parent)
end

-- helper function to determine if a character can be pushed upwards by a certain amount
-- character is 5 studs tall, we'll check a 1.5 x 1.5 x 4.5 box around char, with center .5 studs below torsocenter
local function spaceAboveCharacter(charTorso, newTorsoY, stampData)
	local partsAboveChar = game:GetService("Workspace"):FindPartsInRegion3(
		Region3.new(Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - Vector3.new(.75, 2.75, .75),
		Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(.75, 1.75, .75)), 
		charTorso.Parent, 
		100)
		
	for j = 1, #partsAboveChar do
		if partsAboveChar[j].CanCollide and not partsAboveChar[j]:IsDescendantOf(stampData.CurrentParts) then return false end
	end

	if clusterPartsInRegion(Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - Vector3.new(.75, 2.75, .75), 
		Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(.75, 1.75, .75)) then 
			return false
	end

	return true
end


local function findConfigAtMouseTarget(Mouse, stampData)
	-- *Critical Assumption* :
	--				This function assumes the target CF axes are orthogonal with the target bounding box faces
	--				And, it assumes the insert CF axes are orthongonal with the insert bounding box faces
	--				Therefore, insertion will not work with angled faces on wedges or other "non-block" parts, nor
	--				will it work for parts in a model that are not orthogonally aligned with the model's CF.

	if not Mouse then return nil end -- This can happen sometimes, return if so
	if not stampData then error("findConfigAtMouseTarget: stampData is nil") return nil end
	if not stampData["CurrentParts"] then return nil end

	local grid = 4.0
	local admissibleConfig = false
	local targetConfig = CFrame.new(0,0,0)

	local minBB, maxBB = getBoundingBox2(stampData.CurrentParts)
	local diagBB = maxBB - minBB

	local insertCFrame
	if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
		insertCFrame = stampData.CurrentParts:GetModelCFrame()
	else
		insertCFrame = stampData.CurrentParts.CFrame
	end

	if Mouse then
		if stampData.CurrentParts:IsA("Tool") then 
			Mouse.TargetFilter = stampData.CurrentParts.Handle
		else
			Mouse.TargetFilter = stampData.CurrentParts
		end
	end

	local hitPlane = false
	local targetPart = nil
	local success = pcall(function() targetPart = Mouse.Target end)

	if not success then-- or targetPart == nil then
		return admissibleConfig, targetConfig
	end
	
	local mouseHitInWorld = Vector3.new(0, 0, 0)
	if Mouse then
		mouseHitInWorld = Vector3.new(Mouse.Hit.x, Mouse.Hit.y, Mouse.Hit.z)
	end
	
	local cellPos = nil
	
	-- Nothing was hit, so check for the default plane.
	if nil == targetPart then
		cellPos = GetTerrainForMouse(Mouse)
		if nil == cellPos then
			hitPlane = false
			return admissibleConfig, targetConfig
		else
			targetPart = game:GetService("Workspace").Terrain
			hitPlane = true
			-- Take into account error that will occur.
			cellPos = Vector3.new(cellPos.X - 1, cellPos.Y, cellPos.Z)
			mouseHitInWorld = game:GetService("Workspace").Terrain:CellCenterToWorld(cellPos.x, cellPos.y, cellPos.z)
		end
	end

	-- test mouse hit location
	local minBBTarget, maxBBTarget = getTargetPartBoundingBox(targetPart)
	local diagBBTarget = maxBBTarget - minBBTarget
	local targetCFrame = getMouseTargetCFrame(targetPart)

	if targetPart:IsA("Terrain") then
		if not cluster then cluster = game:GetService("Workspace"):FindFirstChild("Terrain") end
		local cellID = cluster:WorldToCellPreferSolid(mouseHitInWorld)
		if hitPlane then
			cellID = cellPos
		end
		
		targetCFrame = CFrame.new(game:GetService("Workspace").Terrain:CellCenterToWorld(cellID.x, cellID.y, cellID.z))
	end
	
	local mouseHitInTarget = targetCFrame:pointToObjectSpace(mouseHitInWorld)
	local targetVectorInWorld = Vector3.new(0,0,0)
	if Mouse then
		-- DON'T WANT THIS IN TERMS OF THE MODEL CFRAME!  (.TargetSurface is in terms of the part CFrame, so this would break, right?  [HotThoth])
		--   (ideally, we would want to make the Mouse.TargetSurface a model-targetsurface instead, but for testing will be using the converse)
		--targetVectorInWorld = targetCFrame:vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface))
		targetVectorInWorld = targetPart.CFrame:vectorToWorldSpace(Vector3.FromNormalId(Mouse.TargetSurface))   -- better, but model cframe would be best
		--[[if targetPart.Parent:IsA("Model") then
			local hitFace = modelTargetSurface(targetPart.Parent, Mouse.Hit.p, game.Workspace.CurrentCamera.CoordinateFrame.p)  -- best, if you get it right
			local WORLD_AXES = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}
			if hitFace > 0 then
				targetVectorInWorld = targetCFrame:vectorToWorldSpace(WORLD_AXES[hitFace])
			elseif hitFace < 0 then
				targetVectorInWorld = targetCFrame:vectorToWorldSpace(-WORLD_AXES[-hitFace])
			end
		end]]
	end

	local targetRefPointInTarget
	local clampToSurface

	if getClosestAlignedWorldDirection(targetVectorInWorld) == 0 then
		targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(1, -1, 1))
		insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
		clampToSurface = Vector3.new(0,1,1)
	elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 3 then
		targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, -1))
		insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(1, -1, -1))
		clampToSurface = Vector3.new(0,1,1)
	elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 1 then
		targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, 1, 1))
		insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
		clampToSurface = Vector3.new(1,0,1)		
	elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 4 then
		targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
		insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, 1, 1))
		clampToSurface = Vector3.new(1,0,1)
	elseif getClosestAlignedWorldDirection(targetVectorInWorld) == 2 then
		targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(-1, -1, 1))
		insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(-1, -1, -1))
		clampToSurface = Vector3.new(1,1,0)
	else
		targetRefPointInTarget = targetCFrame:vectorToObjectSpace(Vector3.new(1, -1, -1))
		insertRefPointInInsert = insertCFrame:vectorToObjectSpace(Vector3.new(1, -1, 1))
		clampToSurface = Vector3.new(1,1,0)
	end

	targetRefPointInTarget = targetRefPointInTarget * (0.5 * diagBBTarget) + 0.5 * (maxBBTarget + minBBTarget)
	insertRefPointInInsert = insertRefPointInInsert * (0.5 * diagBB) + 0.5 * (maxBB + minBB)
	
	-- To Do: For cases that are not aligned to the world grid, account for the minimal rotation
	-- needed to bring the Insert part(s) into alignment with the Target Part
	-- Apply the rotation here

	local delta = mouseHitInTarget - targetRefPointInTarget
	local deltaClamped = Vector3.new(grid * math.modf(delta.x/grid), grid * math.modf(delta.y/grid), grid * math.modf(delta.z/grid))
	deltaClamped = deltaClamped * clampToSurface
	local targetTouchInTarget = deltaClamped + targetRefPointInTarget

	local TargetTouchRelToWorld = targetCFrame:pointToWorldSpace(targetTouchInTarget)
	local InsertTouchInWorld = insertCFrame:vectorToWorldSpace(insertRefPointInInsert)
	local posInsertOriginInWorld = TargetTouchRelToWorld - InsertTouchInWorld

	local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = insertCFrame:components()
	targetConfig = CFrame.new(posInsertOriginInWorld.x, posInsertOriginInWorld.y, posInsertOriginInWorld.z, R00, R01, R02, R10, R11, R12, R20, R21, R22)
	admissibleConfig = true

	return admissibleConfig, targetConfig, getClosestAlignedWorldDirection(targetVectorInWorld)
end

local function truncateToCircleEighth(bigValue, littleValue)
	local big = math.abs(bigValue)
	local little = math.abs(littleValue)
	local hypotenuse = math.sqrt(big*big + little*little)
	local frac = little / hypotenuse

	local bigSign = 1
	local littleSign = 1
	if bigValue < 0 then bigSign = -1 end
	if littleValue < 0 then littleSign = -1 end

	if frac > .382683432 then
		-- between 22.5 and 45 degrees, so truncate to 45-degree tilt
		return .707106781 * hypotenuse * bigSign, .707106781 * hypotenuse * littleSign
	else 
		-- between 0 and 22.5 degrees, so truncate to 0-degree tilt
		return hypotenuse * bigSign, 0
	end
end


local function saveTheWelds(object, manualWeldTable, manualWeldParentTable)
	if object:IsA("ManualWeld") or object:IsA("Rotate") then
		table.insert(manualWeldTable, object)
		table.insert(manualWeldParentTable, object.Parent)
	else
		local children = object:GetChildren()
		for i = 1, #children do
			saveTheWelds(children[i], manualWeldTable, manualWeldParentTable)
		end
	end
end

local function restoreTheWelds(manualWeldTable, manualWeldParentTable)
	for i = 1, #manualWeldTable do
		manualWeldTable[i].Parent = manualWeldParentTable[i]
	end
end

t.CanEditRegion = function(partOrModel, EditRegion) -- todo: use model and stamper metadata
	if not EditRegion then return true, false end

	local minBB, maxBB = getBoundingBoxInWorldCoordinates(partOrModel)
	
	if minBB.X < EditRegion.CFrame.p.X - EditRegion.Size.X/2 or
		minBB.Y < EditRegion.CFrame.p.Y - EditRegion.Size.Y/2 or
		minBB.Z < EditRegion.CFrame.p.Z - EditRegion.Size.Z/2 then
			return false, false
	end
	
	if maxBB.X > EditRegion.CFrame.p.X + EditRegion.Size.X/2 or
		maxBB.Y > EditRegion.CFrame.p.Y + EditRegion.Size.Y/2 or
		maxBB.Z > EditRegion.CFrame.p.Z + EditRegion.Size.Z/2 then
			return false, false
	end
	
	return true, false
end

t.GetStampModel = function(assetId, terrainShape, useAssetVersionId)
	if assetId == 0 then
		return nil, "No Asset"
	end
	if assetId < 0 then
		return nil, "Negative Asset"
	end

	local function UnlockInstances(object)
		if object:IsA("BasePart") then
			object.Locked = false
		end
		for index,child in pairs(object:GetChildren()) do
			UnlockInstances(child)
		end
	end
	
	local function getClosestColorToTerrainMaterial(terrainValue)
		if terrainValue == 1 then
			return BrickColor.new("Bright green")
		elseif terrainValue == 2 then
			return BrickColor.new("Bright yellow")
		elseif terrainValue == 3 then
			return BrickColor.new("Bright red")
		elseif terrainValue == 4 then
			return BrickColor.new("Sand red")
		elseif terrainValue == 5 then
			return BrickColor.new("Black")
		elseif terrainValue == 6 then
			return BrickColor.new("Dark stone grey")
		elseif terrainValue == 7 then
			return BrickColor.new("Sand blue")
		elseif terrainValue == 8 then
			return BrickColor.new("Deep orange")
		elseif terrainValue == 9 then
			return BrickColor.new("Dark orange")
		elseif terrainValue == 10 then
			return BrickColor.new("Reddish brown")
		elseif terrainValue == 11 then
			return BrickColor.new("Light orange")
		elseif terrainValue == 12 then
			return BrickColor.new("Light stone grey")
		elseif terrainValue == 13 then
			return BrickColor.new("Sand green")
		elseif terrainValue == 14 then
			return BrickColor.new("Medium stone grey")
		elseif terrainValue == 15 then
			return BrickColor.new("Really red")
		elseif terrainValue == 16 then
			return BrickColor.new("Really blue")
		elseif terrainValue == 17 then
			return BrickColor.new("Bright blue")
		else
			return BrickColor.new("Bright green")
		end
	end
	
	local function setupFakeTerrainPart(cellMat, cellType, cellOrient)
		local newTerrainPiece = nil
		if (cellType == 1 or cellType == 4) then newTerrainPiece = Instance.new("WedgePart")
		elseif (cellType == 2) then newTerrainPiece = Instance.new("CornerWedgePart")
		else newTerrainPiece = Instance.new("Part") end
		newTerrainPiece.Name = "MegaClusterCube"
		newTerrainPiece.Size = Vector3.new(4, 4, 4)
		newTerrainPiece.BottomSurface = "Smooth"
		newTerrainPiece.TopSurface = "Smooth"

		-- can add decals or textures here if feeling particularly adventurous...  for now, can make a table of look-up colors
		newTerrainPiece.BrickColor = getClosestColorToTerrainMaterial(cellMat)

		local sideways = 0
		local flipped = math.pi
		if cellType == 4 then sideways = -math.pi/2 end
		if cellType == 2 or cellType == 3 then flipped = 0 end
		newTerrainPiece.CFrame = CFrame.Angles(0, math.pi/2*cellOrient + flipped, sideways)

		if cellType == 3 then
			local inverseCornerWedgeMesh = Instance.new("SpecialMesh")
			inverseCornerWedgeMesh.MeshType = "FileMesh"
			inverseCornerWedgeMesh.MeshId = "https://www.roblox.com/asset/?id=66832495"
			inverseCornerWedgeMesh.Scale = Vector3.new(2, 2, 2)
			inverseCornerWedgeMesh.Parent = newTerrainPiece
		end

		local materialTag = Instance.new("Vector3Value")
		materialTag.Value = Vector3.new(cellMat, cellType, cellOrient)
		materialTag.Name = "ClusterMaterial"
		materialTag.Parent = newTerrainPiece

		return newTerrainPiece
	end
	
	-- This call will cause a "wait" until the data comes back
	-- below we wait a max of 8 seconds before deciding to bail out on loading
	local root
	local loader
	loading = true
	if useAssetVersionId then
		loader = coroutine.create(function()	
			root = game:GetService("InsertService"):LoadAssetVersion(assetId)
			loading = false
		end)
		coroutine.resume(loader)
	else
		loader = coroutine.create(function()	
			root = game:GetService("InsertService"):LoadAsset(assetId)
			loading = false
		end)
		coroutine.resume(loader)
	end

	local lastGameTime = 0
	local totalTime = 0
	local maxWait = 8
	while loading and totalTime < maxWait do
		lastGameTime = tick()
		wait(1)
		totalTime = totalTime + tick() - lastGameTime
	end
	loading = false

	if totalTime >= maxWait then
		return nil, "Load Time Fail"
	end


	if root == nil then
		return nil, "Load Asset Fail"
	end

	if not root:IsA("Model") then
		return nil, "Load Type Fail"
	end

	local instances = root:GetChildren()
	if #instances == 0 then
		return nil, "Empty Model Fail"
	end

	--Unlock all parts that are inserted, to make sure they are editable
	UnlockInstances(root)

	--Continue the insert process
	root = root:GetChildren()[1]

	--Examine the contents and decide what it looks like
	for pos, instance in pairs(instances) do
		if instance:IsA("Team") then
			instance.Parent = game:GetService("Teams")
		elseif instance:IsA("Sky") then
			local lightingService = game:GetService("Lighting")
			for index,child in pairs(lightingService:GetChildren()) do
				if child:IsA("Sky") then
					child:Remove();
				end
			end
			instance.Parent = lightingService
			return
		end
	end
	
	-- ...and tag all inserted models for subsequent origin identification
	-- if no RobloxModel tag already exists, then add it.
	if root:FindFirstChild("RobloxModel") == nil then
		local stringTag = Instance.new("BoolValue", root)
		stringTag.Name = "RobloxModel"

		if root:FindFirstChild("RobloxStamper") == nil then
			local stringTag2 = Instance.new("BoolValue", root)
			stringTag2.Name = "RobloxStamper"
		end
	end
	
	if terrainShape then
		if root.Name == "MegaClusterCube" then
			if (terrainShape == 6) then -- insert an autowedging tag
				local autowedgeTag = Instance.new("BoolValue")
				autowedgeTag.Name = "AutoWedge"
				autowedgeTag.Parent = root
			else
				local clusterTag = root:FindFirstChild("ClusterMaterial")
				if clusterTag then
					if clusterTag:IsA("Vector3Value") then
						root = setupFakeTerrainPart(clusterTag.Value.X, terrainShape, clusterTag.Value.Z)
					else
						root = setupFakeTerrainPart(clusterTag.Value, terrainShape, 0)
					end
				else
					root = setupFakeTerrainPart(1, terrainShape, 0)
				end
			end
		end
	end

	return root
end



t.SetupStamperDragger = function(modelToStamp, Mouse, StampInModel, AllowedStampRegion, StampFailedFunc)
	if not modelToStamp then
		error("SetupStamperDragger: modelToStamp (first arg) is nil!  Should be a stamper model")
		return nil 
	end
	if not modelToStamp:IsA("Model") and not modelToStamp:IsA("BasePart") then
		error("SetupStamperDragger: modelToStamp (first arg) is neither a Model or Part!")
		return nil 
	end
	if not Mouse then
		error("SetupStamperDragger: Mouse (second arg) is nil!  Should be a mouse object")
		return nil 
	end
	if not Mouse:IsA("Mouse") then
		error("SetupStamperDragger: Mouse (second arg) is not of type Mouse!")
		return nil 
	end
	
	local stampInModel = nil
	local allowedStampRegion = nil
	local stampFailedFunc = nil
	if StampInModel then
		if not StampInModel:IsA("Model") then
			error("SetupStamperDragger: StampInModel (optional third arg) is not of type 'Model'")
			return nil
		end
		if not AllowedStampRegion then
			error("SetupStamperDragger: AllowedStampRegion (optional fourth arg) is nil when StampInModel (optional third arg) is defined")
			return nil
		end
		stampFailedFunc = StampFailedFunc
		stampInModel = StampInModel
		allowedStampRegion = AllowedStampRegion
	end
	
	-- Init all state variables
	local gInitial90DegreeRotations = 0
	local stampData = nil
	local mouseTarget = nil
	
	local errorBox = Instance.new("SelectionBox")
	errorBox.Color = BrickColor.new("Bright red")
	errorBox.Transparency = 0
	errorBox.Archivable = false
	
	-- for megacluster MEGA STAMPING
	local adornPart = Instance.new("Part")
	adornPart.Parent = nil
	adornPart.Size = Vector3.new(4, 4, 4)
	adornPart.CFrame = CFrame.new()
	adornPart.Archivable = false

	local adorn = Instance.new("SelectionBox")
	adorn.Color = BrickColor.new("Toothpaste")
	adorn.Adornee = adornPart
	adorn.Visible = true
	adorn.Transparency = 0
	adorn.Name = "HighScalabilityStamperLine"
	adorn.Archivable = false

	local HighScalabilityLine = {}
	HighScalabilityLine.Start = nil
	HighScalabilityLine.End = nil
	HighScalabilityLine.Adorn = adorn
	HighScalabilityLine.AdornPart = adornPart
	HighScalabilityLine.InternalLine = nil
	HighScalabilityLine.NewHint = true

	HighScalabilityLine.MorePoints = {nil, nil}
	HighScalabilityLine.MoreLines = {nil, nil}
	HighScalabilityLine.Dimensions = 1
	
	local control = {}
	local movingLock = false
	local stampUpLock = false
	local unstampableSurface = false
	local mouseCons = {}
	local keyCon = nil
	
	local stamped = Instance.new("BoolValue")
	stamped.Archivable = false
	stamped.Value = false
	
	local lastTarget = {}
	lastTarget.TerrainOrientation = 0
	lastTarget.CFrame = 0
	
	local cellInfo = {}
	cellInfo.Material = 1
	cellInfo.clusterType = 0
	cellInfo.clusterOrientation = 0
	
	local function isMegaClusterPart()
		if not stampData then return false end
		if not stampData.CurrentParts then return false end
		
		return ( stampData.CurrentParts:FindFirstChild("ClusterMaterial",true) or (stampData.CurrentParts.Name == "MegaClusterCube") )
	end
	
	local function DoHighScalabilityRegionSelect()
		local megaCube = stampData.CurrentParts:FindFirstChild("MegaClusterCube")
		if not megaCube then 
			if not stampData.CurrentParts.Name == "MegaClusterCube" then
				return
			else
				megaCube = stampData.CurrentParts
			end
		end
		
		HighScalabilityLine.End = megaCube.CFrame.p
		local line = nil
		local line2 = Vector3.new(0, 0, 0)
		local line3 = Vector3.new(0, 0, 0)
			
		if HighScalabilityLine.Dimensions == 1 then
			-- extract the line from these positions and limit to a 2D plane made from 2 of the world axes
			--   then use dominating axis to limit line to be at 45-degree intervals
			--   will use this internal representation of the line for the actual stamping
			line = (HighScalabilityLine.End - HighScalabilityLine.Start)

			if math.abs(line.X) < math.abs(line.Y) then
				if math.abs(line.X) < math.abs(line.Z) then
					-- limit to Y/Z plane, domination unknown
					local newY, newZ
					if (math.abs(line.Y) > math.abs(line.Z)) then 
						newY, newZ = truncateToCircleEighth(line.Y, line.Z)
					else 
						newZ, newY = truncateToCircleEighth(line.Z, line.Y)
					end
					line = Vector3.new(0, newY, newZ)
				else
					-- limit to X/Y plane, with Y dominating
					local newY, newX = truncateToCircleEighth(line.Y, line.X)
					line = Vector3.new(newX, newY, 0)
				end
			else
				if math.abs(line.Y) < math.abs(line.Z) then
					-- limit to X/Z plane, domination unknown
					local newX, newZ
					if math.abs(line.X) > math.abs(line.Z) then
						newX, newZ = truncateToCircleEighth(line.X, line.Z)
					else
						newZ, newX = truncateToCircleEighth(line.Z, line.X)
					end
					line = Vector3.new(newX, 0, newZ)
				else 
					-- limit to X/Y plane, with X dominating
					local newX, newY = truncateToCircleEighth(line.X, line.Y)
					line = Vector3.new(newX, newY, 0)
				end
			end
			HighScalabilityLine.InternalLine = line

		elseif HighScalabilityLine.Dimensions == 2 then
			line = HighScalabilityLine.MoreLines[1]
			line2 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[1]
			
			-- take out any component of line2 along line1, so you get perpendicular to line1 component
			line2 = line2 - line.unit*line.unit:Dot(line2)

			tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line)

			-- then zero out whichever is the smaller component
			local yAxis = tempCFrame:vectorToWorldSpace(Vector3.new(0, 1, 0))
			local xAxis = tempCFrame:vectorToWorldSpace(Vector3.new(1, 0, 0))
					
			local xComp = xAxis:Dot(line2)
			local yComp = yAxis:Dot(line2)

			if math.abs(yComp) > math.abs(xComp) then
				line2 = line2 - xAxis * xComp
			else
				line2 = line2 - yAxis * yComp
			end

			HighScalabilityLine.InternalLine = line2
			
		elseif HighScalabilityLine.Dimensions == 3 then
			line  = HighScalabilityLine.MoreLines[1]
			line2 = HighScalabilityLine.MoreLines[2]
			line3 = HighScalabilityLine.End - HighScalabilityLine.MorePoints[2]

			-- zero out all components of previous lines
			line3 = line3 - line.unit * line.unit:Dot(line3)
			line3 = line3 - line2.unit * line2.unit:Dot(line3)

			HighScalabilityLine.InternalLine = line3
		end

		-- resize the "line" graphic to be the correct size and orientation
		tempCFrame = CFrame.new(HighScalabilityLine.Start, HighScalabilityLine.Start + line)
				
		if HighScalabilityLine.Dimensions == 1 then  -- faster calculation for line
			HighScalabilityLine.AdornPart.Size = Vector3.new(4, 4, line.magnitude + 4)
			HighScalabilityLine.AdornPart.CFrame = tempCFrame + tempCFrame:vectorToWorldSpace(Vector3.new(2, 2, 2) - HighScalabilityLine.AdornPart.Size/2)
		else
			local boxSize = tempCFrame:vectorToObjectSpace(line + line2 + line3)
			HighScalabilityLine.AdornPart.Size = Vector3.new(4, 4, 4) + Vector3.new(math.abs(boxSize.X), math.abs(boxSize.Y), math.abs(boxSize.Z))
			HighScalabilityLine.AdornPart.CFrame = tempCFrame + tempCFrame:vectorToWorldSpace(boxSize/2)
		end
		
		-- make player able to see this ish

		local gui = nil
		if game:GetService("Players")["LocalPlayer"] then 
			gui = game:GetService("Players").LocalPlayer:FindFirstChild("PlayerGui")
			if gui and gui:IsA("PlayerGui") then
				if HighScalabilityLine.Dimensions == 1 and line.magnitude > 3 then -- don't show if mouse hasn't moved enough
					HighScalabilityLine.Adorn.Parent = gui
				elseif HighScalabilityLine.Dimensions > 1 then
					HighScalabilityLine.Adorn.Parent = gui
				end
			end
		end
		
		if gui == nil then -- we are in studio
			gui = game:GetService("CoreGui")
			if HighScalabilityLine.Dimensions == 1 and line.magnitude > 3 then -- don't show if mouse hasn't moved enough
				HighScalabilityLine.Adorn.Parent = gui
			elseif HighScalabilityLine.Dimensions > 1 then
				HighScalabilityLine.Adorn.Parent = gui
			end
		end
	end
	
	
	local function DoStamperMouseMove(Mouse)
		if not Mouse then
			error("Error: RbxStamper.DoStamperMouseMove: Mouse is nil")
			return
		end
		if not Mouse:IsA("Mouse") then
			error("Error: RbxStamper.DoStamperMouseMove: Mouse is of type", Mouse.className,"should be of type Mouse")
			return
		end
		
		-- There wasn't a target (no part or terrain), so check for plane intersection.
		if not Mouse.Target then 
			local cellPos = GetTerrainForMouse(Mouse)
			if nil == cellPos then
				return
			end
		end
		
		if not stampData then
			return
		end

		-- don't move with dragger - will move in one step on mouse down
		-- draw ghost at acceptable positions
		configFound, targetCFrame, targetSurface = findConfigAtMouseTarget(Mouse, stampData)
		if not configFound then
			error("RbxStamper.DoStamperMouseMove No configFound, returning")
			return
		end	

		local numRotations = 0 -- update this according to how many rotations you need to get it to target surface
		if autoAlignToFace(stampData.CurrentParts) and targetSurface ~= 1 and targetSurface ~= 4 then -- pre-rotate the flag or portrait so it's aligned correctly
			if		targetSurface == 3 then numRotations = 0 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
			elseif	targetSurface == 0 then numRotations = 2 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
			elseif	targetSurface == 5 then numRotations = 3 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
			elseif	targetSurface == 2 then numRotations = 1 - gInitial90DegreeRotations + autoAlignToFace(stampData.CurrentParts)
			end
		end	

		local ry = math.pi/2
		gInitial90DegreeRotations = gInitial90DegreeRotations + numRotations
		if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
			--stampData.CurrentParts:Rotate(0, ry*numRotations, 0)
			modelRotate(stampData.CurrentParts, ry*numRotations)
		else
			stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry*numRotations, 0) * stampData.CurrentParts.CFrame
		end

		-- CODE TO CHECK FOR DRAGGING GHOST PART INTO A COLLIDING STATE
		local minBB, maxBB = getBoundingBoxInWorldCoordinates(stampData.CurrentParts)

		-- need to offset by distance to be dragged
		local currModelCFrame = nil
		if stampData.CurrentParts:IsA("Model") then
			currModelCFrame = stampData.CurrentParts:GetModelCFrame()
		else 
			currModelCFrame = stampData.CurrentParts.CFrame
		end
		
		minBB = minBB + targetCFrame.p - currModelCFrame.p
		maxBB = maxBB + targetCFrame.p - currModelCFrame.p

		-- don't drag into terrain
		if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector) then
			if lastTarget.CFrame then
				if (stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)) then
					local theClusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
					if theClusterMaterial:IsA("Vector3Value") then
						local stampClusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
						if stampClusterMaterial then
							stampClusterMaterial = clusterMat
						end
					end
				end
			end
			return
		end

		-- if we are stamping a terrain part, make sure it goes on the grid! Otherwise preview block could be placed off grid, but stamped on grid
		if isMegaClusterPart() then
			local cellToStamp = game:GetService("Workspace").Terrain:WorldToCell(targetCFrame.p)
			local newCFramePosition = game:GetService("Workspace").Terrain:CellCenterToWorld(cellToStamp.X, cellToStamp.Y, cellToStamp.Z)
			local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = targetCFrame:components()
			targetCFrame = CFrame.new(newCFramePosition.X,newCFramePosition.Y,newCFramePosition.Z,R00, R01, R02, R10, R11, R12, R20, R21, R22)
		end

		positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
		lastTarget.CFrame = targetCFrame  -- successful positioning, so update 'dat cframe
		if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then
			local clusterMat = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
			if clusterMat:IsA("Vector3Value") then
				lastTarget.TerrainOrientation = clusterMat.Value.Z
			end
		end


		-- auto break joints code
		if Mouse and Mouse.Target and Mouse.Target.Parent then
			local modelInfo = Mouse.Target:FindFirstChild("RobloxModel")
			if not modelInfo then modelInfo = Mouse.Target.Parent:FindFirstChild("RobloxModel") end
			
			local myModelInfo = stampData.CurrentParts:FindFirstChild("UnstampableFaces")

			--if (modelInfo and modelInfo.Parent:FindFirstChild("UnstampableFaces")) or (modelInfo and myModelInfo) then  -- need better targetSurface calcs
			if (true) then
				local breakingFaces = ""
				local myBreakingFaces = ""
				if modelInfo and modelInfo.Parent:FindFirstChild("UnstampableFaces") then breakingFaces = modelInfo.Parent.UnstampableFaces.Value end
				if myModelInfo then myBreakingFaces = myModelInfo.Value end
				local hitFace = 0

				if modelInfo then hitFace = modelTargetSurface(modelInfo.Parent, game:GetService("Workspace").CurrentCamera.CoordinateFrame.p, Mouse.Hit.p) end

				-- are we stamping TO an unstampable surface?
				for bf in string.gmatch(breakingFaces, "[^,]+") do
					if hitFace == tonumber(bf) then
						-- return before we hit the JointsService code below!
						unstampableSurface = true
						game.JointsService:ClearJoinAfterMoveJoints() -- clear the JointsService cache
						return
					end
				end

				-- now we have to cast the ray back in the other direction to find the surface we're stamping FROM
				hitFace = modelTargetSurface(stampData.CurrentParts, Mouse.Hit.p, game:GetService("Workspace").CurrentCamera.CoordinateFrame.p)

				-- are we stamping WITH an unstampable surface?
				for bf in string.gmatch(myBreakingFaces, "[^,]+") do
					if hitFace == tonumber(bf) then
						unstampableSurface = true
						game.JointsService:ClearJoinAfterMoveJoints() -- clear the JointsService cache
						return
					end
				end

				-- just need to match breakingFace against targetSurface using rotation supplied by modelCFrame
				-- targetSurface: 1 is top, 4 is bottom, 
			end
		end

		-- to show joints during the mouse move
		unstampableSurface = false
		game.JointsService:SetJoinAfterMoveInstance(stampData.CurrentParts)

		-- most common mouse inactive error occurs here, so check mouse active one more time in a pcall
		if not pcall(function() 
				if Mouse and Mouse.Target and Mouse.Target.Parent:FindFirstChild("RobloxModel") == nil then 
					return
				else
					return
				end
			end) 
		then
			error("Error: RbxStamper.DoStamperMouseMove Mouse is nil on second check")
			game.JointsService:ClearJoinAfterMoveJoints()
			Mouse = nil
			return
		end

		if Mouse and Mouse.Target and Mouse.Target.Parent:FindFirstChild("RobloxModel") == nil then
			game.JointsService:SetJoinAfterMoveTarget(Mouse.Target)
		else
			game.JointsService:SetJoinAfterMoveTarget(nil)
		end
		game.JointsService:ShowPermissibleJoints()

		-- here we allow for a line of high-scalability parts
		if isMegaClusterPart() and HighScalabilityLine and HighScalabilityLine.Start then
				DoHighScalabilityRegionSelect()
		end
	end
	
	local function setupKeyListener(key, Mouse)
		if control and control["Paused"] then return end -- don't do this if we have no stamp
		
		key = string.lower(key)
		if key == 'r' and not autoAlignToFace(stampData.CurrentParts) then -- rotate the model
			gInitial90DegreeRotations = gInitial90DegreeRotations + 1
			
			-- Update orientation value if this is a fake terrain part
			local clusterValues = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
			if clusterValues and clusterValues:IsA("Vector3Value") then
				clusterValues.Value = Vector3.new(clusterValues.Value.X, clusterValues.Value.Y, (clusterValues.Value.Z + 1) % 4)
			end
			
			-- Rotate the parts or all the parts in the model
			local ry = math.pi/2
			if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
				--stampData.CurrentParts:Rotate(0, ry, 0)
				modelRotate(stampData.CurrentParts, ry)
			else
				stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame
			end

			-- After rotating, update the position
			configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
			if configFound then
				positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
				
				-- update everything else in MouseMove
				DoStamperMouseMove(Mouse)
			end
		elseif key == 'c' then -- try to expand our high scalability dragger dimension
			if HighScalabilityLine.InternalLine and HighScalabilityLine.InternalLine.magnitude > 0 and HighScalabilityLine.Dimensions < 3 then
				HighScalabilityLine.MorePoints[HighScalabilityLine.Dimensions] = HighScalabilityLine.End
				HighScalabilityLine.MoreLines[HighScalabilityLine.Dimensions] = HighScalabilityLine.InternalLine
				HighScalabilityLine.Dimensions = HighScalabilityLine.Dimensions + 1
				HighScalabilityLine.NewHint = true
			end
		end
	end
	
	keyCon = Mouse.KeyDown:connect(function(key) -- init key connection (keeping code close to func)
		setupKeyListener(key, Mouse)
	end)
	
	local function resetHighScalabilityLine()
		if HighScalabilityLine then
			HighScalabilityLine.Start = nil
			HighScalabilityLine.End = nil
			HighScalabilityLine.InternalLine = nil
			HighScalabilityLine.NewHint = true
		end
	end

	local function flashRedBox()
		local gui = game:GetService("CoreGui")
		if game:GetService("Players") then
			if game:GetService("Players")["LocalPlayer"] then
				if game:GetService("Players").LocalPlayer:FindFirstChild("PlayerGui") then
					gui = game:GetService("Players").LocalPlayer.PlayerGui
				end
			end
		end
		if not stampData["ErrorBox"] then return end
		
		stampData.ErrorBox.Parent = gui
		if stampData.CurrentParts:IsA("Tool") then 
			stampData.ErrorBox.Adornee = stampData.CurrentParts.Handle
		else 
			stampData.ErrorBox.Adornee = stampData.CurrentParts
		end

		delay(0,function()
			for i = 1, 3 do
				if stampData["ErrorBox"] then stampData.ErrorBox.Visible = true end
				wait(0.13)
				if stampData["ErrorBox"] then stampData.ErrorBox.Visible = false end
				wait(0.13)
			end
			if stampData["ErrorBox"] then
				stampData.ErrorBox.Adornee = nil
				stampData.ErrorBox.Parent = Tool
			end
		end)
	end
	
	local function DoStamperMouseDown(Mouse)
		if not Mouse then
			error("Error: RbxStamper.DoStamperMouseDown: Mouse is nil")
			return
		end
		if not Mouse:IsA("Mouse") then
			error("Error: RbxStamper.DoStamperMouseDown: Mouse is of type", Mouse.className,"should be of type Mouse")
			return
		end
		if not stampData then 
			return
		end

		if isMegaClusterPart() then
			if Mouse and HighScalabilityLine then
				local megaCube = stampData.CurrentParts:FindFirstChild("MegaClusterCube", true)
				local terrain = game:GetService("Workspace").Terrain
				if megaCube then
					HighScalabilityLine.Dimensions = 1
					local tempCell = terrain:WorldToCell(megaCube.CFrame.p)
					HighScalabilityLine.Start = terrain:CellCenterToWorld(tempCell.X, tempCell.Y, tempCell.Z)
					return
				else
					HighScalabilityLine.Dimensions = 1
					local tempCell = terrain:WorldToCell(stampData.CurrentParts.CFrame.p)
					HighScalabilityLine.Start = terrain:CellCenterToWorld(tempCell.X, tempCell.Y, tempCell.Z)
					return
				end
			end
		end
	end
	
	local function loadSurfaceTypes(part, surfaces)
		part.TopSurface = surfaces[1]
		part.BottomSurface = surfaces[2]
		part.LeftSurface = surfaces[3]
		part.RightSurface = surfaces[4]
		part.FrontSurface = surfaces[5]
		part.BackSurface = surfaces[6]
	end

	local function saveSurfaceTypes(part, myTable)
		local tempTable = {}
		tempTable[1] = part.TopSurface
		tempTable[2] = part.BottomSurface
		tempTable[3] = part.LeftSurface
		tempTable[4] = part.RightSurface
		tempTable[5] = part.FrontSurface
		tempTable[6] = part.BackSurface
		
		myTable[part] = tempTable
	end

	local function makeSurfaceUnjoinable(part, surface)
		-- TODO: FILL OUT!
	end

	local function prepareModel(model)
		if not model then return nil end
		
		local gDesiredTrans = 0.7
		local gStaticTrans = 1

		local clone = model:Clone()
		local scripts = {}
		local parts = {}
		local decals = {}
		
		stampData = {}
		stampData.DisabledScripts = {}
		stampData.TransparencyTable = {}
		stampData.MaterialTable = {}
		stampData.CanCollideTable = {}
		stampData.AnchoredTable = {}
		stampData.ArchivableTable = {}
		stampData.DecalTransparencyTable = {}
		stampData.SurfaceTypeTable = {}
		
		collectParts(clone, parts, scripts, decals)

		if #parts <= 0 then return nil, "no parts found in modelToStamp" end

		for index,script in pairs(scripts) do
			if not(script.Disabled) then
				script.Disabled = true
				stampData.DisabledScripts[#stampData.DisabledScripts + 1] = script
			end
		end
		for index, part in pairs(parts) do
			stampData.TransparencyTable[part] = part.Transparency
			part.Transparency = gStaticTrans + (1 - gStaticTrans) * part.Transparency
			stampData.MaterialTable[part] = part.Material
			part.Material = Enum.Material.Plastic
			stampData.CanCollideTable[part] = part.CanCollide
			part.CanCollide = false
			stampData.AnchoredTable[part] = part.Anchored
			part.Anchored = true
			stampData.ArchivableTable[part] = part.Archivable
			part.Archivable = false

			saveSurfaceTypes(part, stampData.SurfaceTypeTable)
			
			local fadeInDelayTime = 0.5
			local transFadeInTime = 0.5
			delay(0,function()
				wait(fadeInDelayTime) -- give it some time to be completely transparent
				
				local begTime = tick()
				local currTime = begTime
				while (currTime - begTime) < transFadeInTime and part and part:IsA("BasePart") and part.Transparency > gDesiredTrans do
					local newTrans = 1 - (((currTime - begTime)/transFadeInTime) * (gStaticTrans - gDesiredTrans))
					if stampData["TransparencyTable"] and stampData.TransparencyTable[part] then
						part.Transparency = newTrans + (1 - newTrans) * stampData.TransparencyTable[part]
					end
					wait(0.03)
					currTime = tick()
				end
				if part and part:IsA("BasePart") then
					if stampData["TransparencyTable"] and stampData.TransparencyTable[part] then
						part.Transparency = gDesiredTrans + (1 - gDesiredTrans) * stampData.TransparencyTable[part]
					end
				end
			end)
		end

		for index, decal in pairs(decals) do
			stampData.DecalTransparencyTable[decal] = decal.Transparency
			decal.Transparency = gDesiredTrans + (1 - gDesiredTrans) * decal.Transparency
		end
		
		-- disable all seats
		setSeatEnabledStatus(clone, true)
		setSeatEnabledStatus(clone, false)

		stampData.CurrentParts = clone

		-- if auto-alignable, we enforce a pre-rotation to the canonical "0-frame"
		if autoAlignToFace(clone) then
			stampData.CurrentParts:ResetOrientationToIdentity()
			gInitial90DegreeRotations = 0
		else -- pre-rotate if necessary
			local ry = gInitial90DegreeRotations * math.pi/2
			if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
				--stampData.CurrentParts:Rotate(0, ry, 0)
				modelRotate(stampData.CurrentParts, ry)
			else
				stampData.CurrentParts.CFrame = CFrame.fromEulerAnglesXYZ(0, ry, 0) * stampData.CurrentParts.CFrame
			end
		end

		-- since we're cloning the old model instead of the new one, we will need to update the orientation based on the original value AND how many more
		--  rotations we expect since then [either that or we need to store the just-stamped clusterMaterial.Value.Z somewhere].  This should fix the terrain rotation
		--  issue (fingers crossed) [HotThoth]

		local clusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
		if clusterMaterial and clusterMaterial:IsA("Vector3Value") then
			clusterMaterial.Value = Vector3.new(clusterMaterial.Value.X, clusterMaterial.Value.Y, (clusterMaterial.Value.Z + gInitial90DegreeRotations) % 4)
		end

		-- After rotating, update the position
		local configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)
		if configFound then
			stampData.CurrentParts = positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts)
		end
		
		-- to show joints during the mouse move
		game.JointsService:SetJoinAfterMoveInstance(stampData.CurrentParts)
		
		return clone, parts
	end
	
	local function checkTerrainBlockCollisions(cellPos, checkHighScalabilityStamp)
		local cellCenterToWorld = game:GetService("Workspace").Terrain.CellCenterToWorld
		local cellCenter = cellCenterToWorld(game:GetService("Workspace").Terrain, cellPos.X, cellPos.Y, cellPos.Z)
		local cellBlockingParts = game:GetService("Workspace"):FindPartsInRegion3(Region3.new(cellCenter - Vector3.new(2, 2, 2) + insertBoundingBoxOverlapVector, cellCenter + Vector3.new(2, 2, 2) - insertBoundingBoxOverlapVector), stampData.CurrentParts,	100)

		local skipThisCell = false
		
		for b = 1, #cellBlockingParts do
			if isBlocker(cellBlockingParts[b]) then skipThisCell = true break end
		end
								
		if not skipThisCell then
			-- pop players up above any set cells
			local alreadyPushedUp = {}
			-- if no blocking model below, then see if stamping on top of a character
			for b = 1, #cellBlockingParts do
				if	cellBlockingParts[b].Parent and 
					not alreadyPushedUp[cellBlockingParts[b].Parent] and 
					cellBlockingParts[b].Parent:FindFirstChild("Humanoid") and 
					cellBlockingParts[b].Parent:FindFirstChild("Humanoid"):IsA("Humanoid") then
				-----------------------------------------------------------------------------------
						local blockingPersonTorso = cellBlockingParts[b].Parent:FindFirstChild("Torso")
						alreadyPushedUp[cellBlockingParts[b].Parent] = true

						if blockingPersonTorso then
							-- if so, let's push the person upwards so they pop on top of the stamped model/part (but only if there's space above them)
							local newY = cellCenter.Y + 5
							if spaceAboveCharacter(blockingPersonTorso, newY, stampData) then
								blockingPersonTorso.CFrame = blockingPersonTorso.CFrame + Vector3.new(0, newY - blockingPersonTorso.CFrame.p.Y, 0)
							else
								-- if no space, we just skip this one
								skipThisCell = true
								break
							end
						end
				-----------------------------------------------------------------------------------
				end
			end
		end
							
		if not skipThisCell then  -- if we STILL aren't skipping...  then we're good to go!
			local canSetCell = true
			
			if checkHighScalabilityStamp then -- check to see if cell is in region, if not we'll skip set
				if allowedStampRegion then
					local cellPos = cellCenterToWorld(game:GetService("Workspace").Terrain, cellPos.X, cellPos.Y, cellPos.Z)
					if cellPos.X + 2 > allowedStampRegion.CFrame.p.X + allowedStampRegion.Size.X/2 then
						canSetCell = false
					elseif cellPos.X - 2 < allowedStampRegion.CFrame.p.X - allowedStampRegion.Size.X/2 then
						canSetCell = false
					elseif cellPos.Y + 2 > allowedStampRegion.CFrame.p.Y + allowedStampRegion.Size.Y/2 then
						canSetCell = false
					elseif cellPos.Y - 2 < allowedStampRegion.CFrame.p.Y - allowedStampRegion.Size.Y/2 then
						canSetCell = false
					elseif cellPos.Z + 2 > allowedStampRegion.CFrame.p.Z + allowedStampRegion.Size.Z/2 then
						canSetCell = false
					elseif cellPos.Z - 2 < allowedStampRegion.CFrame.p.Z - allowedStampRegion.Size.Z/2 then
						canSetCell = false
					end
				end
			end
			
			return canSetCell
		end
		return false
	end


	local function ResolveMegaClusterStamp(checkHighScalabilityStamp)
		local cellSet = false

		local cluser = game:GetService("Workspace").Terrain
		
		local line = HighScalabilityLine.InternalLine
		local cMax = game:GetService("Workspace").Terrain.MaxExtents.Max
		local cMin = game:GetService("Workspace").Terrain.MaxExtents.Min

		local clusterMaterial = 1 -- default is grass
		local clusterType = 0 -- default is brick
		local clusterOrientation = 0 -- default is 0 rotation

		local autoWedgeClusterParts = false
		if stampData.CurrentParts:FindFirstChild("AutoWedge") then autoWedgeClusterParts = true end

		if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then
			clusterMaterial = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) 
			if clusterMaterial:IsA("Vector3Value") then
				clusterType = clusterMaterial.Value.Y
				clusterOrientation = clusterMaterial.Value.Z
				clusterMaterial = clusterMaterial.Value.X
			elseif clusterMaterial:IsA("IntValue") then
				clusterMaterial = clusterMaterial.Value
			end
		end
			
		if HighScalabilityLine.Adorn.Parent and HighScalabilityLine.Start and ((HighScalabilityLine.Dimensions > 1) or (line and line.magnitude > 0)) then
			local startCell = game:GetService("Workspace").Terrain:WorldToCell(HighScalabilityLine.Start)
			local xInc = {0,0,0}
			local yInc = {0,0,0}
			local zInc = {0,0,0}
			
			local cluster = game:GetService("Workspace").Terrain
				
			local incrementVect = {nil, nil, nil}
			local stepVect = {Vector3.new(0, 0, 0), Vector3.new(0, 0, 0), Vector3.new(0, 0, 0)}

			local worldAxes = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}

			local lines = {}
			if HighScalabilityLine.Dimensions > 1 then table.insert(lines, HighScalabilityLine.MoreLines[1]) end
			if line and line.magnitude > 0 then table.insert(lines, line) end
			if HighScalabilityLine.Dimensions > 2 then table.insert(lines, HighScalabilityLine.MoreLines[2]) end

			for i = 1, #lines do
				lines[i] = Vector3.new(math.floor(lines[i].X+.5), math.floor(lines[i].Y+.5), math.floor(lines[i].Z+.5)) -- round to integers

				if lines[i].X > 0 then xInc[i] = 1 elseif lines[i].X < 0 then xInc[i] = -1 end
				if lines[i].Y > 0 then yInc[i] = 1 elseif lines[i].Y < 0 then yInc[i] = -1 end
				if lines[i].Z > 0 then zInc[i] = 1 elseif lines[i].Z < 0 then zInc[i] = -1 end

				incrementVect[i] = Vector3.new(xInc[i], yInc[i], zInc[i])
				if incrementVect[i].magnitude < .9 then incrementVect[i] = nil end
			end


			if not lines[2] then lines[2] = Vector3.new(0, 0, 0) end
			if not lines[3] then lines[3] = Vector3.new(0, 0, 0) end

			local waterForceTag = stampData.CurrentParts:FindFirstChild("WaterForceTag", true)
			local waterForceDirectionTag = stampData.CurrentParts:FindFirstChild("WaterForceDirectionTag", true)
			
			while (stepVect[3].magnitude*4 <= lines[3].magnitude) do
				local outerStepVectIndex = 1
				while outerStepVectIndex < 4 do
					stepVect[2] = Vector3.new(0, 0, 0)
					while (stepVect[2].magnitude*4 <= lines[2].magnitude) do
						local innerStepVectIndex = 1
						while innerStepVectIndex < 4 do
							stepVect[1] = Vector3.new(0, 0, 0)
							while (stepVect[1].magnitude*4 <= lines[1].magnitude) do
								local stepVectSum = stepVect[1] + stepVect[2] + stepVect[3]
								local cellPos = Vector3int16.new(startCell.X + stepVectSum.X, startCell.Y + stepVectSum.Y, startCell.Z + stepVectSum.Z)
								if cellPos.X >= cMin.X and cellPos.Y >= cMin.Y and cellPos.Z >= cMin.Z and cellPos.X < cMax.X and cellPos.Y < cMax.Y and cellPos.Z < cMax.Z then
									-- check if overlaps player or part
									local okToStampTerrainBlock = checkTerrainBlockCollisions(cellPos, checkHighScalabilityStamp)
									
									if okToStampTerrainBlock then
										if waterForceTag then
											cluster:SetWaterCell(cellPos.X, cellPos.Y, cellPos.Z, Enum.WaterForce[waterForceTag.Value], Enum.WaterDirection[waterForceDirectionTag.Value])
										else
											cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterMaterial, clusterType, clusterOrientation)
										end
										cellSet = true 
						
										-- auto-wedge it?
										if (autoWedgeClusterParts) then
											game:GetService("Workspace").Terrain:AutowedgeCells(Region3int16.new(Vector3int16.new(cellPos.x - 1, cellPos.y - 1, cellPos.z - 1),
												Vector3int16.new(cellPos.x + 1, cellPos.y + 1, cellPos.z + 1)))
										end
									end
								end
								stepVect[1] = stepVect[1] + incrementVect[1]
							end
							if incrementVect[2] then
								while innerStepVectIndex < 4 and worldAxes[innerStepVectIndex]:Dot(incrementVect[2]) == 0 do 
									innerStepVectIndex = innerStepVectIndex + 1
								end
								if innerStepVectIndex < 4 then 
									stepVect[2] = stepVect[2] + worldAxes[innerStepVectIndex] * worldAxes[innerStepVectIndex]:Dot(incrementVect[2])
								end
								innerStepVectIndex = innerStepVectIndex + 1
							else
								stepVect[2] = Vector3.new(1, 0, 0)
								innerStepVectIndex = 4 -- skip all remaining loops
							end 
							if (stepVect[2].magnitude*4 > lines[2].magnitude) then innerStepVectIndex = 4 end
						end
					end
					if incrementVect[3] then 
						while outerStepVectIndex < 4 and worldAxes[outerStepVectIndex]:Dot(incrementVect[3]) == 0 do
							outerStepVectIndex = outerStepVectIndex + 1
						end
						if outerStepVectIndex < 4 then
							stepVect[3] = stepVect[3] + worldAxes[outerStepVectIndex] * worldAxes[outerStepVectIndex]:Dot(incrementVect[3]) 
						end
						outerStepVectIndex = outerStepVectIndex + 1
					else -- skip all remaining loops
						stepVect[3] = Vector3.new(1, 0, 0) outerStepVectIndex = 4 
					end 
					if (stepVect[3].magnitude*4 > lines[3].magnitude) then outerStepVectIndex = 4 end
				end
			end
		end

		-- and also get rid of any HighScalabilityLine stuff if it's there
		HighScalabilityLine.Start = nil
		HighScalabilityLine.Adorn.Parent = nil
		
		-- Mark for undo.
		if cellSet then
			stampData.CurrentParts.Parent = nil
			pcall(function() game:GetService("ChangeHistoryService"): SetWaypoint("StamperMulti") end)
		end
		
		return cellSet
	end
	
	local function DoStamperMouseUp(Mouse)
		if not Mouse then
			error("Error: RbxStamper.DoStamperMouseUp: Mouse is nil")
			return false
		end
		if not Mouse:IsA("Mouse") then
			error("Error: RbxStamper.DoStamperMouseUp: Mouse is of type", Mouse.className,"should be of type Mouse")
			return false
		end

		if not stampData.Dragger then
			error("Error: RbxStamper.DoStamperMouseUp: stampData.Dragger is nil")
			return false
		end
		
		if not HighScalabilityLine then
			return false
		end
		
		local checkHighScalabilityStamp = nil
		if stampInModel then
			local canStamp = nil
			local isHSLPart = isMegaClusterPart()
			
			if isHSLPart and
				HighScalabilityLine and
				HighScalabilityLine.Start and
				HighScalabilityLine.InternalLine and
				HighScalabilityLine.InternalLine.magnitude > 0 then -- we have an HSL line, test later
					canStamp = true
					checkHighScalabilityStamp = true
			else
				canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
			end
			
			if not canStamp then
				if stampFailedFunc then
					stampFailedFunc()
				end
				return false
			end
		end

		-- if unstampable face, then don't let us stamp there!
		if unstampableSurface then
			flashRedBox()
			return false
		end
		
		-- recheck if we can stamp, as we just moved part
		canStamp, checkHighScalabilityStamp = t.CanEditRegion(stampData.CurrentParts, allowedStampRegion)
		if not canStamp then
			if stampFailedFunc then
				stampFailedFunc()
			end
			return false
		end 

		-- Prevent part from being stamped on top of a player

		local minBB, maxBB = getBoundingBoxInWorldCoordinates(stampData.CurrentParts)

		-- HotThoth's note:  Now that above CurrentParts positioning has been commented out, to be truly correct, we would need to use the 
		--                     value of configFound from the previous onStamperMouseMove call which moved the CurrentParts
		--                     Shouldn't this be true when lastTargetCFrame has been set and false otherwise?
		configFound, targetCFrame = findConfigAtMouseTarget(Mouse, stampData)

		if configFound and not HighScalabilityLine.Adorn.Parent then
			if clusterPartsInRegion(minBB + insertBoundingBoxOverlapVector, maxBB - insertBoundingBoxOverlapVector) then 
				flashRedBox()
				return false
			end

			local blockingParts = game:GetService("Workspace"):FindPartsInRegion3(Region3.new(minBB + insertBoundingBoxOverlapVector,
																	maxBB - insertBoundingBoxOverlapVector), 
																	stampData.CurrentParts,
																	100)
																	
										
			for b = 1, #blockingParts do
				if isBlocker(blockingParts[b]) then
					flashRedBox()
					return false
				end
			end

			local alreadyPushedUp = {}
				-- if no blocking model below, then see if stamping on top of a character
				for b = 1, #blockingParts do
					if	blockingParts[b].Parent and 
						not alreadyPushedUp[blockingParts[b].Parent] and 
						blockingParts[b].Parent:FindFirstChild("Humanoid") and 
						blockingParts[b].Parent:FindFirstChild("Humanoid"):IsA("Humanoid") then
					---------------------------------------------------------------------------
							local blockingPersonTorso = blockingParts[b].Parent:FindFirstChild("Torso")
							alreadyPushedUp[blockingParts[b].Parent] = true

							if blockingPersonTorso then
								-- if so, let's push the person upwards so they pop on top of the stamped model/part (but only if there's space above them)
								local newY = maxBB.Y + 3
								if spaceAboveCharacter(blockingPersonTorso, newY, stampData) then
									blockingPersonTorso.CFrame = blockingPersonTorso.CFrame + Vector3.new(0, newY - blockingPersonTorso.CFrame.p.Y, 0)
								else
									-- if no space, we just error
									flashRedBox()
									return false
								end
							end
					---------------------------------------------------------------------------
					end
				end
				
		elseif (not configFound) and not (HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent) then -- if no config then only stamp if it's a real HSL!
			resetHighScalabilityLine()
			return false
		end

		-- something will be stamped!  so set the "StampedSomething" toggle to true
		if game:FindFirstChild("Players") then
			if game:GetService("Players")["LocalPlayer"] then
				if game:GetService("Players").LocalPlayer["Character"] then
					local localChar = game:GetService("Players").LocalPlayer.Character
					local stampTracker = localChar:FindFirstChild("StampTracker")
					if stampTracker and not stampTracker.Value then 
						stampTracker.Value = true
					end
				end
			end
		end

		-- if we drew a line of mega parts, stamp them out
		if HighScalabilityLine.Start and HighScalabilityLine.Adorn.Parent and isMegaClusterPart() then
			if ResolveMegaClusterStamp(checkHighScalabilityStamp) or checkHighScalabilityStamp then
				-- kill the ghost part
				stampData.CurrentParts.Parent = nil
				return true
			end
		end

		-- not High-Scalability-Line-Based, so behave normally [and get rid of any HSL stuff]
		HighScalabilityLine.Start = nil
		HighScalabilityLine.Adorn.Parent = nil
		
		local cluster = game:GetService("Workspace").Terrain

		-- if target point is in cluster, just use cluster:SetCell
		if isMegaClusterPart() then
			-- if targetCFrame is inside cluster, just set that cell to 1 and return
			--local cellPos = cluster:WorldToCell(targetCFrame.p)

			local cellPos
			if stampData.CurrentParts:IsA("Model") then cellPos = cluster:WorldToCell(stampData.CurrentParts:GetModelCFrame().p)
			else cellPos = cluster:WorldToCell(stampData.CurrentParts.CFrame.p) end

			local cMax = game:GetService("Workspace").Terrain.MaxExtents.Max
			local cMin = game:GetService("Workspace").Terrain.MaxExtents.Min
				
			if checkTerrainBlockCollisions(cellPos, false) then 

				local clusterValues = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true)
				local waterForceTag = stampData.CurrentParts:FindFirstChild("WaterForceTag", true)
				local waterForceDirectionTag = stampData.CurrentParts:FindFirstChild("WaterForceDirectionTag", true)

				if cellPos.X >= cMin.X and cellPos.Y >= cMin.Y and cellPos.Z >= cMin.Z and cellPos.X < cMax.X and cellPos.Y < cMax.Y and cellPos.Z < cMax.Z then

					if waterForceTag then
						cluster:SetWaterCell(cellPos.X, cellPos.Y, cellPos.Z, Enum.WaterForce[waterForceTag.Value], Enum.WaterDirection[waterForceDirectionTag.Value])
					elseif not clusterValues then
						cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, cellInfo.Material, cellInfo.clusterType, gInitial90DegreeRotations % 4)
					elseif clusterValues:IsA("Vector3Value") then
						cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterValues.Value.X, clusterValues.Value.Y, clusterValues.Value.Z)
					else
						cluster:SetCell(cellPos.X, cellPos.Y, cellPos.Z, clusterValues.Value, 0, 0)
					end
				
					local autoWedgeClusterParts = false
					if stampData.CurrentParts:FindFirstChild("AutoWedge") then autoWedgeClusterParts = true end
	
					-- auto-wedge it
					if (autoWedgeClusterParts) then
						game:GetService("Workspace").Terrain:AutowedgeCells(
							Region3int16.new(
								Vector3int16.new(cellPos.x - 1, cellPos.y - 1, cellPos.z - 1),
								Vector3int16.new(cellPos.x + 1, cellPos.y + 1, cellPos.z + 1)
							)
						)
					end

					-- kill the ghost part
					stampData.CurrentParts.Parent = nil
					
					-- Mark for undo.  It has to happen here or the selection display will come back also.
					pcall(function() game:GetService("ChangeHistoryService"):SetWaypoint("StamperSingle") end)						
					return true
				end
			else 
				-- you tried to stamp a HSL-single part where one does not belong!
				flashRedBox()
				return false
			end
		end
		
		local function getPlayer()
			if game:FindFirstChild("Players") then
				if game:GetService("Players")["LocalPlayer"] then
					return game:GetService("Players").LocalPlayer
				end
			end
			return nil
		end
		

		-- Post process: after positioning the part or model, restore transparency, material, anchored and collide states and create joints
		if stampData.CurrentParts:IsA("Model") or stampData.CurrentParts:IsA("Tool") then
			if stampData.CurrentParts:IsA("Model") then
				-- Tyler's magical hack-code for allowing/preserving clones of both Surface and Manual Welds...  just don't ask X<
				local manualWeldTable = {}
				local manualWeldParentTable = {}
				saveTheWelds(stampData.CurrentParts, manualWeldTable, manualWeldParentTable)
				stampData.CurrentParts:BreakJoints()
				stampData.CurrentParts:MakeJoints()
				restoreTheWelds(manualWeldTable, manualWeldParentTable)
			end
					
			-- if it's a model, we also want to fill in the playerID and playerName tags, if it has those (e.g. for the friend-only door)
			playerIdTag = stampData.CurrentParts:FindFirstChild("PlayerIdTag")
			playerNameTag = stampData.CurrentParts:FindFirstChild("PlayerNameTag")
			if playerIdTag ~= nil then
				tempPlayerValue = getPlayer()
				if tempPlayerValue ~= nil then playerIdTag.Value = tempPlayerValue.userId end
			end
			if playerNameTag ~= nil then
				if game:FindFirstChild("Players") and game:GetService("Players")["LocalPlayer"] then
					tempPlayerValue = game:GetService("Players").LocalPlayer
					if tempPlayerValue ~= nil then playerNameTag.Value = tempPlayerValue.Name end
				end
			end
			-- ...and tag all inserted models for subsequent origin identification
			-- if no RobloxModel tag already exists, then add it.
			if stampData.CurrentParts:FindFirstChild("RobloxModel") == nil then
				local stringTag = Instance.new("BoolValue", stampData.CurrentParts)
				stringTag.Name = "RobloxModel"

				if stampData.CurrentParts:FindFirstChild("RobloxStamper") == nil then
					local stringTag2 = Instance.new("BoolValue", stampData.CurrentParts)
					stringTag2.Name = "RobloxStamper"
				end
			end

		else
			stampData.CurrentParts:BreakJoints()
			if stampData.CurrentParts:FindFirstChild("RobloxStamper") == nil then
				local stringTag2 = Instance.new("BoolValue", stampData.CurrentParts)
				stringTag2.Name = "RobloxStamper"
			end
		end
				
		-- make sure all the joints are activated before restoring anchor states
		if not createJoints then game.JointsService:CreateJoinAfterMoveJoints() end

		-- Restore the original properties for all parts being stamped
		for part, transparency in pairs(stampData.TransparencyTable) do
			part.Transparency = transparency
		end
		for part, archivable in pairs(stampData.ArchivableTable) do
			part.Archivable = archivable
		end
		for part, material in pairs(stampData.MaterialTable) do
			part.Material = material
		end
		for part, collide in pairs(stampData.CanCollideTable) do
			part.CanCollide = collide
		end
		for part, anchored in pairs(stampData.AnchoredTable) do
			part.Anchored = anchored
		end
		for decal, transparency in pairs(stampData.DecalTransparencyTable) do
			decal.Transparency = transparency
		end

		for part, surfaces in pairs(stampData.SurfaceTypeTable) do
			loadSurfaceTypes(part, surfaces)
		end

		if isMegaClusterPart() then
			stampData.CurrentParts.Transparency = 0
		end

		-- re-enable all seats
		setSeatEnabledStatus(stampData.CurrentParts, true)

		stampData.TransparencyTable = nil
		stampData.ArchivableTable = nil
		stampData.MaterialTable = nil
		stampData.CanCollideTable = nil
		stampData.AnchoredTable = nil
		stampData.SurfaceTypeTable = nil

		-- ...and tag all inserted models for subsequent origin identification
		-- if no RobloxModel tag already exists, then add it.
		if stampData.CurrentParts:FindFirstChild("RobloxModel") == nil then
			local stringTag = Instance.new("BoolValue", stampData.CurrentParts)
			stringTag.Name = "RobloxModel"
		end

		-- and make sure we don't delete it, now that it's not a ghost part
		if ghostRemovalScript then ghostRemovalScript.Parent = nil end

		--Re-enable the scripts
		for index,script in pairs(stampData.DisabledScripts) do
			script.Disabled = false
		end
		
		--Now that they are all marked enabled, reinsert them into the world so they start running
		for index,script in pairs(stampData.DisabledScripts) do
			local oldParent = script.Parent
			script.Parent = nil
			script:Clone().Parent = oldParent
		end
		
		-- clear out more data
		stampData.DisabledScripts = nil
		stampData.Dragger = nil
		stampData.CurrentParts = nil
	
		pcall(function() game:GetService("ChangeHistoryService"): SetWaypoint("StampedObject") end)
		return true
	end
	
	local function pauseStamper()
		for i = 1, #mouseCons do -- stop the mouse from doing anything
			mouseCons[i]:disconnect()
			mouseCons[i] = nil
		end
		mouseCons = {}
		
		if stampData and stampData.CurrentParts then -- remove our ghost part
			stampData.CurrentParts.Parent = nil
			stampData.CurrentParts:Remove()
		end
		
		resetHighScalabilityLine()

		game.JointsService:ClearJoinAfterMoveJoints()
	end
	

	local function prepareUnjoinableSurfaces(modelCFrame, parts, whichSurface)
		local AXIS_VECTORS = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)}  -- maybe last one is negative?  TODO: check this!
		local isPositive = 1
		if whichSurface < 0 then isPositive = isPositive * -1 whichSurface = whichSurface*-1 end
		local surfaceNormal = isPositive * modelCFrame:vectorToWorldSpace(AXIS_VECTORS[whichSurface])

		for i = 1, #parts do
			local currPart = parts[i]

			-- now just need to find which surface of currPart most closely match surfaceNormal and then set that to Unjoinable
			local surfaceNormalInLocalCoords = currPart.CFrame:vectorToObjectSpace(surfaceNormal)
			if math.abs(surfaceNormalInLocalCoords.X) > math.abs(surfaceNormalInLocalCoords.Y) then
				if math.abs(surfaceNormalInLocalCoords.X) > math.abs(surfaceNormalInLocalCoords.Z) then
					if surfaceNormalInLocalCoords.X > 0 then currPart.RightSurface = "Unjoinable" else currPart.LeftSurface = "Unjoinable" end
				else
					if surfaceNormalInLocalCoords.Z > 0 then currPart.BackSurface = "Unjoinable" else currPart.FrontSurface = "Unjoinable" end
				end
			else
				if math.abs(surfaceNormalInLocalCoords.Y) > math.abs(surfaceNormalInLocalCoords.Z) then
					if surfaceNormalInLocalCoords.Y > 0 then currPart.TopSurface = "Unjoinable" else currPart.BottomSurface = "Unjoinable" end
				else
					if surfaceNormalInLocalCoords.Z > 0 then currPart.BackSurface = "Unjoinable" else currPart.FrontSurface = "Unjoinable" end
				end
			end
		end
	end

	local function resumeStamper()
		clone, parts = prepareModel(modelToStamp)

		if not clone or not parts then
			return
		end

		-- if we have unjoinable faces, then we want to change those surfaces to be Unjoinable
		local unjoinableTag = clone:FindFirstChild("UnjoinableFaces", true)
		if unjoinableTag then
			for unjoinableSurface in string.gmatch(unjoinableTag.Value, "[^,]*") do
				if tonumber(unjoinableSurface) then
					if clone:IsA("Model") then
						prepareUnjoinableSurfaces(clone:GetModelCFrame(), parts, tonumber(unjoinableSurface))
					else
						prepareUnjoinableSurfaces(clone.CFrame, parts, tonumber(unjoinableSurface))
					end
				end
			end
		end

		stampData.ErrorBox = errorBox
		if stampInModel then
			clone.Parent = stampInModel
		else
			clone.Parent = game:GetService("Workspace")
		end
		
		if clone:FindFirstChild("ClusterMaterial", true) then -- extract all info from vector
			clusterMaterial = clone:FindFirstChild("ClusterMaterial", true)
			if (clusterMaterial:IsA("Vector3Value")) then
				cellInfo.Material = clusterMaterial.Value.X
				cellInfo.clusterType = clusterMaterial.Value.Y
				cellInfo.clusterOrientation = clusterMaterial.Value.Z
			elseif clusterMaterial:IsA("IntValue") then
				cellInfo.Material = clusterMaterial.Value
			end
		end
		
		pcall(function() mouseTarget = Mouse.Target end)

		if mouseTarget and mouseTarget.Parent:FindFirstChild("RobloxModel") == nil then
			game.JointsService:SetJoinAfterMoveTarget(mouseTarget)
		else
			game.JointsService:SetJoinAfterMoveTarget(nil)
		end
		game.JointsService:ShowPermissibleJoints()
		
		for index, object in pairs(stampData.DisabledScripts) do
			if object.Name == "GhostRemovalScript" then
				object.Parent = stampData.CurrentParts
			end
		end
		
		stampData.Dragger = Instance.new("Dragger")
		
		--Begin a movement by faking a MouseDown signal
		stampData.Dragger:MouseDown(parts[1], Vector3.new(0,0,0), parts)
		stampData.Dragger:MouseUp()
		
		DoStamperMouseMove(Mouse)
		
		table.insert(mouseCons,Mouse.Move:connect(function()
			if movingLock or stampUpLock then return end
			movingLock = true
				DoStamperMouseMove(Mouse)
			movingLock = false
		end))
		
		table.insert(mouseCons,Mouse.Button1Down:connect(function()
			DoStamperMouseDown(Mouse)
		end))
		
		table.insert(mouseCons,Mouse.Button1Up:connect(function()
			stampUpLock = true
				while movingLock do wait() end
				stamped.Value = DoStamperMouseUp(Mouse)
				resetHighScalabilityLine()
			stampUpLock = false
		end))
		
		stamped.Value = false
	end
	
	local function resetStamperState(newModelToStamp)
	
		-- if we have a new model, swap it out
		if newModelToStamp then
			if not newModelToStamp:IsA("Model") and not newModelToStamp:IsA("BasePart") then
				error("resetStamperState: newModelToStamp (first arg) is not nil, but not a model or part!")
			end
			modelToStamp = newModelToStamp
		end
	
		-- first clear our state
		pauseStamper()
		-- now lets load in the new model
		resumeStamper()
		
	end
	
	-- load the model initially
	resetStamperState()
	
	
	-- setup the control table we pass back to the user
	control.Stamped = stamped -- BoolValue that fires when user stamps
	control.Paused = false
	
	control.LoadNewModel = function(newStampModel) -- allows us to specify a new stamper model to be used with this stamper
		if newStampModel and not newStampModel:IsA("Model") and not newStampModel:IsA("BasePart") then
			error("Control.LoadNewModel: newStampModel (first arg) is not a Model or Part!")
			return nil
		end
		resetStamperState(newStampModel)
	end
	
	control.ReloadModel = function() -- will automatically set stamper to get a new model of current model and start stamping with new model
		resetStamperState()
	end
	
	control.Pause = function() -- temporarily stops stamping, use resume to start up again
		if not control.Paused then
			pauseStamper()
			control.Paused = true
		else
			print("RbxStamper Warning: Tried to call Control.Pause() when already paused")
		end
	end
	
	control.Resume = function() -- resumes stamping, if currently paused
		if control.Paused then
			resumeStamper()
			control.Paused = false
		else
			print("RbxStamper Warning: Tried to call Control.Resume() without Pausing First")
		end
	end
	
	control.ResetRotation = function() -- resets the model rotation so new models are at default orientation
		-- gInitial90DegreeRotations = 0
		-- Note:  This function will not always work quite the way we want it to; we will have to build this out further so it works with
		--        High-Scalability and with the new model orientation setting methods (model:ResetOrientationToIdentity())  [HotThoth]
	end
	
	control.Destroy = function() -- Stops current Stamp operation and destroys control construct
		for i = 1, #mouseCons do
			mouseCons[i]:disconnect()
			mouseCons[i] = nil
		end
		
		if keyCon then
			keyCon:disconnect()
		end
		
		game.JointsService:ClearJoinAfterMoveJoints()
		
		if adorn then adorn:Destroy() end
		if adornPart then adornPart:Destroy() end
		if errorBox then errorBox:Destroy() end
		if stampData then
			if stampData["Dragger"] then
				stampData.Dragger:Destroy()
			end
			if stampData.CurrentParts then
				stampData.CurrentParts:Destroy()
			end
		end
		if control and control["Stamped"] then
			control.Stamped:Destroy()
		end
		control = nil
	end

	return control
end

t.Help = 
	function(funcNameOrFunc) 
		--input argument can be a string or a function.  Should return a description (of arguments and expected side effects)
		if funcNameOrFunc == "GetStampModel" or funcNameOrFunc == t.GetStampModel then 
			return "Function GetStampModel.  Arguments: assetId, useAssetVersionId.  assetId is the asset to load in, define useAssetVersionId as true if assetId is a version id instead of a relative assetId.  Side effect: returns a model of the assetId, or a string with error message if something fails" 
		end
		if funcNameOrFunc == "SetupStamperDragger" or funcNameOrFunc == t.SetupStamperDragger then
			return "Function SetupStamperDragger. Side Effect: Creates 4x4 stamping mechanism for building out parts quickly. Arguments: ModelToStamp, Mouse, LegalStampCheckFunction. ModelToStamp should be a Model or Part, preferrably loaded from RbxStamper.GetStampModel and should have extents that are multiples of 4.  Mouse should be a mouse object (obtained from things such as Tool.OnEquipped), used to drag parts around 'stamp' them out. LegalStampCheckFunction is optional, used as a callback with a table argument (table is full of instances about to be stamped). Function should return either true or false, false stopping the stamp action."
		end
	end

return t
