local function round(number)
return math.floor((number/var.Grid) + 0.5)*var.Grid
end
if mouse.Target.Name == 'Wall' then
local unitRay = workspace.CurrentCamera:ScreenPointToRay(mouse.X, mouse.Y, 1)
local ray = Ray.new(unitRay.Origin, unitRay.Direction*1000)
local hit, pos, normal = workspace:FindPartOnRay(ray, itemClone)
itemClone:SetPrimaryPartCFrame(CFrame.new(round(pos.X), round(pos.Y), round(pos.Z))*CFrame.Angles(0, math.rad(90), var.Rotation))
end
End result
So the 2 problems are
It moves onto my character when I get close, even tho the mouse.Target is the wall
It isnât attaching to the wall
It works great on the front facing walls, but not the side walls. Pretty sure it has something to do with the math.rad(90) on the Y axis of Angles.
It moves onto my character when I get close, even tho the mouse.Target is the wall
Yes the mouse target is the wall but the ray you cast from the camera hits your character before the wall. You might want to add the character to the ignore list.
It isnât attaching to the wall
You answered this yourself, the rotation of the part youâre placing is static on the Y axis. You might want to base this on the surface normal instead.
And I figured that was the case for it not attaching to other walls, but I donât know how to fix it. Getting rid of the math.rad(90) means it doesnt attach to any of the walls
Well just getting rid of it means the static value is 0 instead of 90, no real difference. Instead you can get the surface normal from the raycast to base the rotation on, as I mentioned in my first comment (third returned value).
I tried that before, but that screws with my rounding system
local function round(number)
return math.floor((number/var.Grid) + 0.5)*var.Grid
end
itemClone:SetPrimaryPartCFrame(CFrame.new(round(pos.X), round(pos.Y), round(pos.Z))*CFrame.Angles(0, 0, var.Rotation))
That would kinda make sense. However, without rounding it just moves freely completely. Surely thereâs a way to round based on the side of the wall itâs facing
Sure, just not as straight forward. Youâd need to define how you want it to snap since the previous definition is no longer enough. Ideas would be to find the closest point on a side to a certain grid position or only round the axis perpendicular to our âforwardâ and let the part/hit decide the parallel axis. Forward could for example be the axis the normal is closest to.
Decided to remove the rounding for now. Itâs starting to look better now, however, it part still clips into the wall, meaning it canât be placed at all
if mouse.Target.Name == 'Wall' then
local unitRay = workspace.CurrentCamera:ScreenPointToRay(mouse.X, mouse.Y, 1)
local ray = Ray.new(unitRay.Origin, unitRay.Direction*1000)
local hit, pos, normal = workspace:FindPartOnRay(ray, itemClone)
itemClone:SetPrimaryPartCFrame(CFrame.new(pos, pos + normal*15)*CFrame.Angles(0, 0, var.Rotation))
end
for _, v in pairs(getTouchingParts(itemClone.PrimaryPart)) do
if not v:IsDescendantOf(itemClone) then
itemClone.PrimaryPart.Transparency = 0.5
break
end
itemClone.PrimaryPart.Transparency = 1
end
EDIT And still getting the problem with the ray on the player. Tried the Whitelist one but that just made the object disappear completely
local coords = {'X', 'Y', 'Z'}
local function getCFrame(mouse, thickness)
if mouse.Target.Name == 'Wall' then
local CF = mouse.Target.CFrame
local touch = CF:Inverse() * mouse.Hit
local pos = {}
local normalAxis = mouse.TargetSurface % 3 + 1
local dir = {0, 0, 0}
for axis, coord in next, coords do
if axis == normalAxis then
pos[axis] = touch[coord] + thickness / 2
dir[axis] = math.sign(touch[coord])
else
pos[axis] = math.floor(touch[coord] + 0.5)
end
end
pos = Vector3(unpack(pos))
dir = Vector3(unpack(dir))
return CF * CFrame.new(pos, pos + normal)
end
end
Since it uses mouse.Target, mouse.Hit, and mouse.TargetSurface instead of FindPartOnRay, it ignores the character by default. I also round on the position while in local space. Since to continue to be on the surface we need to keep one axis unaltered I only round two axis depending on the TargetSurface. I also added in the painting thickness so that it wonât partially go into the wall and return the correct CFrame for the painting. When the mouse isnât pointing at a wall it returns nil.
Edit: Updated code to be a bit cleaner and update the normal correctly. I also noticed that I accidentally replied to Ninjo, sorry!
Keep in mind the hit of the raycast position is exactly on the parts side, and parts are positioned with CFrame based on their center, I suggest you edit each model to have a primary part that is allowed to clip into the wall so you can model the rest of the model based on that, knowing it will look appropriate on the wall, as I mentioned earlier.
The 15 in that snippet does nothing, are you trying to move the CFrame 15 studs?
Did you understand how to use it? If you just want to ignore the player try the ignore list instead, if thatâs easier.
Edit: You could also round the wall coordinates by creating a new origin and snapping along the side of the part instead of the global axes.
local unitRay = workspace.CurrentCamera:ScreenPointToRay(mouse.X, mouse.Y, 1)
local ray = Ray.new(unitRay.Origin, unitRay.Direction*1000)
local hit, pos, normal = workspace:FindPartOnRay(ray, itemClone)
local coords = {'X', 'Y', 'Z'}
local CF = mouse.Target.CFrame
local touch = CF:Inverse() * mouse.Hit
local pos = {}
local normalAxis = mouse.TargetSurface % 3 + 1
local dir = {0, 0, 0}
for axis, coord in next, coords do
if axis == normalAxis then
pos[axis] = touch[coord] + thickness / 2
dir[axis] = math.sign(touch[coord])
else
pos[axis] = math.floor(touch[coord] + 0.5)
end
end
pos = Vector3(unpack(pos))
dir = Vector3(unpack(dir))
itemClone:SetPrimaryPartCFrame(CF * CFrame.new(pos, pos + normal))
The thickness is the thickness of the painting. This also assumes that the front of the painting is the direction facing from the painting to the viewer. If not, then you may need to apply a rotation to the painting.
You also can get rid of the unitRay, ray, hit, pos, and normal variables you have at the top of the file. They are not used.
The normal isnât returned because the CFrame is defined by both the position and the normal. It wraps them both into one neat package ready to be used ^.^
local coords = {'X', 'Y', 'Z'}
local function getCFrame(mouse, thickness)
local CF = mouse.Target.CFrame
local touch = CF:Inverse() * mouse.Hit
local pos = {}
local normalAxis = mouse.TargetSurface % 3 + 1 --ERROR HERE
local dir = {0, 0, 0}
for axis, coord in next, coords do
if axis == normalAxis then
pos[axis] = touch[coord] + thickness / 2
dir[axis] = math.sign(touch[coord])
else
pos[axis] = math.floor(touch[coord] + 0.5)
end
end
pos = Vector3(unpack(pos))
dir = Vector3(unpack(dir))
return CF * CFrame.new(pos, pos + normal)
end
-- Later down the script
if mouse.Target.Parent:IsDescendantOf(playersPlot.House) then -- Just checks to make sure the target is on the wall
itemClone:SetPrimaryPartCFrame(getCFrame(mouse, 0.1))
end
Error
[attempt to perform arithmetic on field âTargetSurfaceâ (a userdata value)]