Model not rotating in the correct direction

Hello I’m having trouble getting a model to rotate in the correct direction when being spawned in front of the player. My intent is to spawn the model in and have it face where the player was facing, and also for it to not be rotated on the X or Z axis. The model is facing the correct direction, but is not flat on the ground.

Code:

When pivoting the model, “location” is a Vector3 located in front of the player towards the ground. Here I’m making the model face where the player was looking.

Screenshots of the problem:

As shown in the screenshot, I walked around and spawned in the models, each time facing a different direction and stopping on the last one I spawned in. The models face where the player was facing, but are not completely flat on the ground.

What was intended:

image

Any help is appreciated, thank you for your time.

Using two Vec3s in the CFrame constructor like that creates a sort of “look at” situation.

CFrame.new(pos : Vector3,lookAt : Vector3)

Since the location of the placement is on the ground and the location of the hrp is higher than that, it’s tilting the CFrame when it combines those two Vec3s in the constructor to make the CFrame. It’s placing it correctly and then pointing its LookVector at the player’s hrp (with some vertical adjustment applied).

You’d want to make sure the height of the two positions are the same, or you could use a different constructor like the “lookAlong”

local upVector = Vector3.new(0, 1, 0)     -- desired UpVector is aligned to y-axis
local cf = CFrame.lookAlong(placement_position, player_LookVector, upVector) -- might need to negate the LookVector part if its backwards

rockModel:PivotTo(cf)

Oh dang I didn’t even fathom that was happening with the “look at” constructor lol. Learned something new today! CFrame lookAlong worked just fine, thank you!

1 Like

Nice! yw. Glad that was helpful. That lookAlong constructor is also convenient if you wanted to place objects on a slanted surface. You could use the surface normal of the slanted target plane as the UpVector and the constructor should align the new cf to whatever surface. :grinning: Though you’d prob want to test what happens when the 2nd and 3rd parameters of that constructor are the same (LookAlongVector == UpVector) if trying to place things vertically…

Oh may you elaborate on that? I’m probably doing it really wrong lol. Before I clone the model I’m checking if the player’s standing on a surface tagged as “Ground”. I’m trying to get the model to face towards the normal of the raycast:

image

:upside_down_face: Hmm. Oops. That didn’t work like what I thought it would. That’ll teach me to test stuff before making claims. Sorry about that.

Here’s a test that’ll work on tilted planes. I was running it from a Script in ReplicatedStorage with RunContext set to client. It’s just a client-side test so no Remote Events to make the parts vis on the server.

It uses the mouse to world ray for the facing vector rather than the player’s hrp. It’s a bit hacky, much pasted from example code from the Roblox docs for raycast and mouse input. Can throw a few rotated Parts around a place and just click to place (it makes a little horseshoe barrier). The direction gets confused if you try placing on the underside of things, but otherwise it may give you some ideas for things to try.

--[[
	Client-side only example
]]

local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local player = Players.LocalPlayer
local character = player.Character
if not character or character.Parent == nil then
	character = player.CharacterAdded:Wait()
end
local hrp = character:WaitForChild("HumanoidRootPart")
local directionVector

local MAX_MOUSE_DISTANCE = 1000
local FIRE_RATE = 0.3
local timeOfPreviousPlacement = 0
local rand = Random.new()

local function makeBarrier()
	local offset = CFrame.new(0, 0, 6)
	local model = Instance.new("Model")
	local part = Instance.new("Part")
	part.Size = Vector3.new(5, 1, 2)
	part.Anchored = true
	
	local fn = function()
		local step = 5
		for i = 0, step-1 do
			local c = part:Clone()
			local rot = i * math.pi/(step-1)
			local cf = CFrame.Angles(0, rot, 0)
			c.CFrame = cf:ToWorldSpace(offset)
			c.Size = c.Size * Vector3.new(1,1+3*rand:NextNumber(),1)
			c.Position = Vector3.new(c.Position.X, c.Size.Y/2, c.Position.Z)
			c.Parent = model
		end
		local extSize = model:GetExtentsSize()
		model.WorldPivot -= Vector3.new(0, extSize.Y/2, 0)
		model.WorldPivot *= CFrame.Angles(0, math.pi/4, 0)
		model.Parent = workspace
	end
	fn()
	return model
end

-- Check if enough time has passed
local function canPlaceObject()
	local currentTime = tick()
	if currentTime - timeOfPreviousPlacement < FIRE_RATE then
		return false
	end
	return true
end

local function getWorldRaycast()
	local mouseLocation = UserInputService:GetMouseLocation()

	-- Create a ray from the 2D mouse location
	local screenToWorldRay = workspace.CurrentCamera:ViewportPointToRay(mouseLocation.X, mouseLocation.Y)

	-- The unit direction vector of the ray multiplied by a maximum distance
	directionVector = screenToWorldRay.Direction * MAX_MOUSE_DISTANCE

	-- Raycast from the ray's origin towards its direction
	local raycastResult = workspace:Raycast(screenToWorldRay.Origin, directionVector)

	if raycastResult then
		return raycastResult
	else
		-- No object was hit
		return nil
	end
end

local function getSurfaceAlignedCf(surfaceNormal, itemPosition, desiredForward)
	
	-- Calculate the right vector (perpendicular to both forward and up vectors)
	local rightVector = surfaceNormal:Cross(desiredForward).Unit

	-- Recalculate the forward vector to ensure orthogonality
	local forwardVector = rightVector:Cross(surfaceNormal).Unit

	-- Create the new CFrame with the position and orientation
	local newCFrame = CFrame.fromMatrix(
		itemPosition,
		rightVector,
		surfaceNormal,
		forwardVector
	)
	
	return newCFrame
end

local function placeItem()
	if not canPlaceObject() then
		return
	end
	
	local rcResult = getWorldRaycast()
	if rcResult then
		local model = makeBarrier()
		local rcSurfNorm = rcResult.Normal
		local rcPosition = rcResult.Position
		--local upVector = Vector3.new(0,1,0)
		local lookVector = Vector3.new(directionVector.X, 0, directionVector.Z).Unit
		
		
		local cf = getSurfaceAlignedCf(rcSurfNorm, rcPosition, lookVector)
		--local cf = CFrame.lookAlong(rcPosition, lookVector, sfNorm)
		model:PivotTo(cf)
	end

end

UserInputService.InputBegan:Connect(function(input, _gameProcessedEvent)
	if input.UserInputType == Enum.UserInputType.MouseButton1 then
		placeItem()
	end
end)
1 Like

Apologies for the late reply! I understand this now, I used the methods you used in your “getSurfaceAlignedCf” function today and inserted my own arguments. It works really well! I’m definitely gonna use the :Cross function more to get desired orientations, this was my first time learning about it lol. Thank you!

1 Like