How can I stop it from rotating?

So, I’m trying to make some roofParts via script. I have been struggling for days to get it working, but it keeps rotating.

I have tried everything from

CFrame.lookAt()

to anything else…

What do I want to achieve?
I want to be able to make it scale / stretch only, not rotate around the topNode aka. the centrePart.

For more info, look at the gif down below:

https://i.gyazo.com/be71a82fcc91147689cfb0f789b7dd80.gif

local function generateRoof(depth, width, thickness, angleOfElevation, location, parent)
    local partwidth0 = workspace.point1
    local partwidth1 = workspace.point2
    local height = 5
    
    local heightpart = workspace.topNode
    heightpart.CFrame = partwidth0.CFrame:Lerp(partwidth1.CFrame, 0.5) + Vector3.new(0, height, 0)

    --RoofA.Position = PointC.Position + Vector3.new(0, (Distance between A & C (YAxis))/2, (Distance between A & C (ZAxis))/2)
    --RoofA.Size = Vector3.new(math.abs(Distance between A & B (X Axis), 1, math.sqrt( (Distance between A & C (X Axis)^2 + (Distance between A & C (Y Axis)^2 ))
    --RoofA.CFrame = CFrame.new(RoofA.Position, PointC.Position)

    local part = Instance.new('Part', parent)
    part.Anchored = true
    local dist = (partwidth0.Position - heightpart.Position).Magnitude + 2 * height * part.Size.Y / (partwidth1.Position - partwidth0.Position).Magnitude
    part.Position = (partwidth0.Position + heightpart.Position) / 2
    part.Size = Vector3.new(depth, part.Size.Y, dist)
    part.CFrame = CFrame.new(part.Position, heightpart.Position)
    
    local part2 = Instance.new('Part', parent)
    local dist = (partwidth1.Position - heightpart.Position).Magnitude + 2 * height * part2.Size.Y / (partwidth1.Position - partwidth0.Position).Magnitude
    part2.Size = Vector3.new(depth, part2.Size.Y, dist)
    part2.Anchored = true
    part2.Position = (partwidth1.Position + heightpart.Position) / 2    
    part2.CFrame = CFrame.new(part2.Position, heightpart.Position)
    --CFrame.lookAt(part2.Position, heightpart.Position)
    
    --heightpart:Destroy()
    
end

game:GetService('RunService').Heartbeat:Connect(function()

    local point1 = workspace.point1.Position
    local point2 = workspace.point2.Position

    local depth = math.abs(point2.X - point1.X)
    local width = math.abs(point2.Z - point1.Z)

    local q = workspace.NewRoof

    q:ClearAllChildren()

    generateRoof(
        width,
        depth / 2,
        5,
        30,
        point1:Lerp(point2, 0.5),
        q
    )

end)

The problem is occurring because the point1 and point2 positions change each time you move them. When you place the height part you should make it so the point1 and point2 positions are locked in place until the roof is completed or cancelled.

Other things to note is that the parent property of instances should always be set last, changing properties after the parent is set forces it to do physics calculations each time which is unneeded.

Also that CFrame constructor CFrame.new(origin, target) is deprecated. CFrame.lookAt serves the exact same purpose (with an optional third argument) and should be used instead.

2 Likes

How would I make their positions “locked”? :thinking:

1 Like
function lookAt(target, eye)
	
	local forwardVector = (eye - target).Unit
	local upVector = Vector3.new(0, 1, 0)
	
	local rightVector = forwardVector:Cross(upVector)
	local upVector2 = rightVector:Cross(forwardVector)
	
	return CFrame.fromMatrix(eye, rightVector, upVector2)
end

How would this function work? :thinking:

you would have to save their initial positions in a variable outside of the heartbeat connection so it doesn’t get overwritten each frame.

local function generateRoof(depth, width, thickness, angleOfElevation, location, parent)
    local p1 = workspace.point1.Position
    local p2 = workspace.point2.Position

    local listener;
    listener = Heartbeat:Connect() -- updating the roof goes in here
    listener:Disconnect()    
end

Here’s an example quickly made up just to give a general idea, you can have your function return a heartbeat connection or whatever that you can disconnect after the roof is finished placing.

1 Like

What assumptions can you make when trying to solve this problem?

Is the roofline always aligned to the Z axis? Will it ever need to rotate to be aligned with a different axis? Is that axis always going to be either X or Z?

Are the two reference points always at the same Y coordinate?

Are you basing the roof off of an angle of the roof, or off of a height of the roof relative to the midpoint, or something else? You have stuff that implies both.

This doesn’t really answer your question, but I’ll put it here anyways :slight_smile:

Just sort of ran with the general problem.

I implemented this roof computation in desmos:

And then in roblox:

(not shown in video, but you can also provide an arbitrary “roofline direction”)

image

type Roof = {
	Width: number,
	CFrame0: CFrame,
	Length0: number,
	CFrame1: CFrame,
	Length1: number,
	IsValid: boolean
}

-- Computes the locations and sizes of two roof pieces
-- 
-- p0: Vector3	one corner of the roof
-- p1: Vector3	another corner of the roof
-- incline: number	the slope (in radians) of the two roof pieces
-- rooflineDir: Vector3?	(optional) the direction of the roofline. default is X-Axis
local function ComputeRoof(p0: Vector3, p1: Vector3, incline: number, rooflineDir: Vector3?): Roof
	rooflineDir = rooflineDir or Vector3.new(1, 0, 0)
	assert(rooflineDir.Y < 0.000001, "roofline must lie on X-Z plane")
	assert(incline > -math.pi and incline < math.pi / 2, "incline must be between =pi/2 and pi/2 rads")
	
	-- cf = cframe positioned at p0 whose RightVector is the rooflineDir
	local cf = CFrame.fromMatrix(p0, rooflineDir, Vector3.new(0, 1, 0))
	local p = cf:PointToObjectSpace(p1)
	
	if p.Z < 0 then
		-- swap around p0 and p1
		p = -p
		cf = CFrame.fromMatrix(p1, rooflineDir, Vector3.new(0, 1, 0), -cf.LookVector)
	end
	
	-- could just do m = math.tan(incline) directly, but splitting it into cos
	-- and sin so we can compute roof lengths easier
	local invCos = 1 / math.cos(incline)
	local sin = math.sin(incline)

	-- slope = tan(incline) == sin(incline) / cos(incline)
	local m = sin * invCos
	print(m)

	-- compute line intersection to get object-space coordinates of roof tip
	local z = 0.5 * (p.Z + p.Y / m)	
	local y = m * z

	-- length of the first roof piece
	-- equivalent to math.sqrt(z*z + y*y)
	local len0 = invCos * z

	-- length of second roof piece
	local len1 = invCos * (p.Z - z)

	-- orientation of first roof piece
	local cframe0 = cf 
		* CFrame.Angles(-incline, 0, 0) -- rotate upwards
		* CFrame.new(0.5*p.X, 0, 0.5*len0) -- move halfway towards the tip

	-- orientation of second roof piece
	local cframe1 = cf 
		* CFrame.new(0.5*p.X, y, z)  -- move to the roof tip
		* CFrame.Angles(incline, 0, 0)  -- rotate downwards
		* CFrame.new(0, 0, 0.5*len1) -- move halfway towards p1

	return {
		Width = math.abs(p.X),
		
		CFrame0 = cframe0,
		Length0 = math.abs(len0),
		
		CFrame1 = cframe1,
		Length1 = math.abs(len1),
		
		IsValid = z >= 0 and z <= p.Z
	}
end



-- example, visualizing the two parts

local r0 = Instance.new("Part")
r0.Anchored = true
r0.CanCollide = false
r0.Parent = workspace

local r1 = r0:Clone()
r1.Parent = workspace

game:GetService("RunService").Stepped:Connect(function()
	local p0 = workspace.PartA.Position
	local p1 = workspace.PartB.Position
	local rooflineDir = ((workspace.PartC.Position - p0) * Vector3.new(1, 0, 1)).Unit

	local roof = ComputeRoof(p0, p1, math.rad(30))
	
	-- with defining roofline:
	-- local roof = ComputeRoof(p0, p1, math.rad(30), rooflineDir)

	r0.CFrame = roof.CFrame0
	r0.Size = Vector3.new(roof.Width, 1, roof.Length0)
	r1.CFrame = roof.CFrame1
	r1.Size = Vector3.new(roof.Width, 1, roof.Length1)
	
	if roof.IsValid then
		r0.BrickColor = BrickColor.Gray()
		r1.BrickColor = BrickColor.Gray()
	else
		r0.BrickColor = BrickColor.Red()
		r1.BrickColor = BrickColor.Red()
	end
end)

I fixed it, thanks to @centau_ri :slight_smile: