Orienting a tree using CFrame.Angles while also making it face in the direction of another part

I’m currently scripting a tweened “tree fall” destruction system, where if characters are knocked into a tree, it’s supposed to fall 90 degrees away from the direction they’re facing.

My current issue is I can’t figure out how to implement the HumanoidRootPart LookVector of the character into my CFrame.Angles rotation.

I’ve tried using CFrame.lookAt() with ToOrientation at the end to no avail, as well as searching through the DevForum.

It’s important to note that I’m utilizing the Pivot Point of the tree trunk in order to ensure that the tree rotates from the bottom of the tree rather than the center.

Here is what it currently looks like with no LookVector taken into account:
Watch Rural Japan - Roblox Studio 2024-06-06 18-11-31 | Streamable -Streamable link, not sure how to embed it.

Here is the goal:

Here is my current code:

local cframeValue = Instance.new("CFrameValue")
cframeValue.Parent = cloneObject
cloneObject.PivotOffset = CFrame.new(0,cloneObject.Size.Y/-2,0)
		
		
local pivotPoint = cloneObject:GetPivot()
		
local change
change = cframeValue:GetPropertyChangedSignal("Value"):connect(function()
	cloneObject:PivotTo(pivotPoint * cframeValue.Value)
end)
		
local offset = CFrame.Angles(0,0,math.rad(90))
local tween1 = TS:Create(cframeValue, TweenInfo.new(2, Enum.EasingStyle.Bounce), {Value = offset})
tween1:Play()

Any help would be appreciated.

5 Likes

Where is the code that sets the tree’s pivot outside of the tween?

3 Likes

image

3 Likes

Missed that. Could you show me how you apply the lookVector to the tree?

2 Likes

Yeah that’s the current issue, I’m not sure how to go about implementing that. I can’t directly apply it to the offset as it’s a CFrame and LookVector is a Vector3.

3 Likes

What about trying to set up a CFrame variable, one that starts at the tree’s pivot position, and looks in the direction of the lookVector? Then, pivot the tree (or the cloneObject right now) to cframeVariable * 90 degrees. Or have you already tried that?

2 Likes

Tried that, the result is that it just sends the tree into the air.

3 Likes

Let me see the code for it, hopefully I didn’t miss anything.

2 Likes
cloneObject.PivotOffset = CFrame.new(0,cloneObject.Size.Y/-2,0)
		
		
local pivotPoint = cloneObject:GetPivot()
		
local change
change = cframeValue:GetPropertyChangedSignal("Value"):connect(function()
	cloneObject:PivotTo(pivotPoint * cframeValue.Value)
end)
		
		
		
local look = movingChar.HumanoidRootPart.CFrame.LookVector
		
local lookAt = CFrame.lookAt(pivotPoint.Position, look)
		
local offset = CFrame.Angles(0,0,math.rad(90)) * lookAt
local tween1 = TS:Create(cframeValue, TweenInfo.new(2, Enum.EasingStyle.Bounce), {Value = offset})
tween1:Play()

2 Likes

Can’t test this right now but this might work:

local offset = CFrame.Angles(math.rad(90), 0, 0):ToWorldSpace(movingChar.HumanoidRootPart.CFrame)

If you want to tree to fall away from the players character then I think you would have to replace the CFrame passed to :ToWorldSpace() with this:

CFrame.new(movingChar.HumanoidRootPart.CFrame.Position, cloneObject.Position)

No guarantees this is the correct solution, I’m not very good at CFrame math myself.

2 Likes

You should be able to use local AngleCF = Player.CFrame:ToWorldSpace(CFrame.Angles(0, 0, math.rad(90))) This should return a CFrame object with no transformation (just rotation) in world space which you can then convert to the trees object space using Tree.CFrame:ToObjectSpace(AngleCF).

2 Likes

I’m currently using :PivotTo as a means of rotating the part. I’m assuming the process would be different to convert the AngleCF back to ObjectSpace. I’m not sure how though.

Here is the code where the tree object is being pivoted:

cloneObject:PivotTo(pivotPoint * cframeValue.Value)
1 Like

Given the first parameter in your CFrame, I believe this will alter the position of the character which is not what I want. The goal is simply to move rotate the tree based on the direction that the HumanoidRootPart is facing with an offset of 90 degrees, all while maintaining the current position of the Character seeing as I already rotate it for the wall splat mechanic as shown in the reference video.

1 Like

Shouldn’t be, GetPivot and PivotTo are both based on the primary part / pivot point of primary part (if applicable).

1 Like

Result:

local pivotPoint = cloneObject:GetPivot()
local angleCF = movingChar.HumanoidRootPart.CFrame:ToWorldSpace(CFrame.Angles(0, 0, math.rad(90)))
cloneObject:PivotTo(pivotPoint:ToObjectSpace(angleCF))

image

Also for clarification, I am not using a model. I’m only using :PivotTo so as to ensure the tree rotates from the bottom (where the pivot point is set).

1 Like

AngleCF has no transformation value, so it will be at 0, 0, 0. Do pivotPoint * pivotPoint:ToObjectSpace(angleCF)

1 Like

Hi there @d9umi!

Heres my solution.
Id say drop the cframe angles and switch to matrices.
This is a presonal opinion of mine but I think it’s more readable and versatile.
I didn’t bother with tweening it cause it would make the code unnececeraly longer and harder to read.
Script is only so you can understand how it works and is supposed to be integrated into your system but feel free to copy stuff.

local directionPart: Part = workspace.Direction
local treeModel: Model = workspace.Tree -- I assumed you were using a model but it's pretty easy to switch to a part/mesh that you are using (My bad)
local Angle: NumberValue = script.Angle -- Positive numbers will go higher, Negative numbers will go lower (Default is 0 for the 90 degree turn)

function Fall()
	local treeProperties = treeModel:GetPivot()
	local lookAt = CFrame.lookAt(directionPart.Position, treeProperties.Position)
	
	-- Since we don't include the "Y" value of our lookVector (In line 14 we replaced it with "Angle" value)
	-- We need to fallback the function, Otherwise the tree could get an empty/dysfunctional vector
	-- This empty/dysfunctional vector will either: Do something unexpected or remove the model altogether
	if lookAt.LookVector.X == 0 and lookAt.LookVector.Z == 0 then
		-- TODO: add a fallback function
		return
	end
	
	local matrix = CFrame.fromMatrix(treeProperties.Position, lookAt.XVector, Vector3.new(lookAt.LookVector.x, Angle.Value, lookAt.LookVector.Z))

	treeModel:PivotTo(matrix)
end

-- The following section of this script is for the video demonstration and can be ignored
directionPart:GetPropertyChangedSignal("Position"):Connect(Fall)
Angle:GetPropertyChangedSignal("Value"):Connect(Fall)

Keep in mind that the pivot of this model is set to the bottom

Is there a reason you are unable to use matrices? I would gladly assist!

1 Like

Have you tried CFrame.lookAlong()?

cframeValue.Value = CFrame.lookAlong(cframeValue.Value.Position, character:GetPivot().LookVector) -- "character" is the variable of the character which contains the HumanoidRootPart that you want to face the tree to its LookVector

I would imagine something like the following would work; in which I’ve included a demo so you can understand how it works better - see the comments for more details.

Example

Note: the Maid library here was just being used to cleanup the parts/connections, remove that if you don’t / haven’t used a Maid library - it’s not necessary for the demo.

--[=[
  computeFallTransform

  @desc computes the new transform of an object if it were to fall in the
        direction of `vec` by the specified `fallAngle` given an upVector

  @param transform CFrame  - the object's original CFrame/WorldPivot
  @param vec       Vector3 - the direction of the fall
  @param fallAngle  number - (optional) defaults to rad(90deg); the max angle - in radians - that we'd like to fall
  @param upVector  Vector3 - (optional) defaults to Vec3.yAxis; the up vector

  @returns CFrame - the new transform; note that we default to the original transform if the given `vec` parameter's
                    magnitude is approximately 0

]=]
local function computeFallTransform(transform, vec, fallAngle, upVector)
  fallAngle = typeof(fallAngle) == 'number' and fallAngle or -math.pi*0.5
  upVector = typeof(upVector) == 'Vector3' and upVector or Vector3.yAxis

  -- ensure our direction vector is non-zero
  local dis = vec.X*vec.X + vec.Y*vec.Y + vec.Z*vec.Z
  if math.abs(dis) > 1e-6 then
    -- normalize our direction vector
    vec = vec * (1/math.sqrt(math.abs(dis)))

    -- ensure that we're not facing our upVector, if we are then
    -- let's default to the world x axis
    if math.abs(vec:Dot(upVector)) >= 9.9e-1 then
      vec = Vector3.xAxis
    end

    -- rotate the transform in our given direction by some given amount
    local direction = transform:VectorToObjectSpace(vec)
    return transform * CFrame.fromAxisAngle(direction:Cross(upVector), fallAngle)
  end

  return transform
end


----------------------------------------------
--                                          --
--               EXAMPLE USAGE              --
--                                          --
----------------------------------------------

--> note just some Maid class to cleanup our connections/parts when we're done;
--> remove this if you don't use one and remove all maid:addTask(...) lines
local Maid = require(path.to.maid.library) 
local maid = Maid.new()

--> debug parts for demo
local tree = Instance.new('Model')
tree.Name = 'Tree'

local trunk = Instance.new('Part')
trunk.Name = 'Trunk'
trunk.Position = Vector3.yAxis*(10.5 + 6*0.5)
trunk.PivotOffset = CFrame.identity - Vector3.yAxis*6*0.5
trunk.Size = Vector3.new(2, 6, 2)
trunk.Locked = true
trunk.Anchored = true
trunk.CanQuery = false
trunk.CanTouch = false
trunk.CanCollide = false
trunk.Transparency = 0
trunk.Parent = tree

tree.PrimaryPart = trunk
tree.Parent = workspace
maid:addTask(tree)

local part = Instance.new('Part') -- move & rotate this part around in workspace to see changes
part.Name = 'Point'
part.Position = Vector3.yAxis*11 + Vector3.xAxis*5
part.Size = Vector3.one
part.Locked = false
part.Anchored = true
part.CanQuery = false
part.CanTouch = false
part.CanCollide = false
part.BrickColor = BrickColor.Red()
part.Transparency = 0
part.Parent = workspace
maid:addTask(part)

local handles = Instance.new('Handles')
handles.Style = Enum.HandlesStyle.Movement
handles.Adornee = part
handles.Faces = Faces.new(Enum.NormalId.Front)
handles.Parent = part

--> demo settings
local USE_LOOK_VECTOR = true -- if false: we'll use the direction between the part and the tree instead of the look vector

--> record & cache the original CFrame & PivotOffsets of the tree
local origin = trunk.CFrame
local pivotOffset = trunk.PivotOffset
local transform = origin * pivotOffset

--> now let's update & visualise our results
local connection
connection = RunService.Heartbeat:Connect(function (dt)
  local vec
  if USE_LOOK_VECTOR then
    vec = part.CFrame.LookVector
  else
    vec = transform.Position - part.Position

    -- Note: the following is only used to update our rotation for the demo
    part.CFrame = CFrame.lookAlong(part.Position, vec)
  end

  local targetTransform = computeFallTransform(transform, vec)
  tree:PivotTo(targetTransform)
end)
maid:addTask(connection)