Oh, that’s odd. You should be able to draw on the entire surface. Just as long as it’s a either the front of back part of a surface GUI
Is this for version 4.0?? cause if so then please do free to send the fix! I will mention you as a contributor!
Oh, that’s odd. You should be able to draw on the entire surface. Just as long as it’s a either the front of back part of a surface GUI
Is this for version 4.0?? cause if so then please do free to send the fix! I will mention you as a contributor!
Yes it indeed is, it actually took me some time to find the exact cause of the issue, it does not happen usually. The issue happens when you put a surface inside a ScreenGui. I don’t have an exact explanation on why the Mouse position bugs happen if for that specific scenario, but I suppose its because the ScreenGui affects the absolute sizes and positions very weirdly.
Here is a video of mine showcasing it
Watch 0531 | Streamable
Fixed function + place showcasing the issue/bug
function Canvas:GetMousePoint(): Vector2?
if RunService:IsClient() then
local MouseLocation = UserInputService:GetMouseLocation()
local GuiInset = game.GuiService:GetGuiInset()
local CanvasFrameSize = self.CurrentCanvasFrame.AbsoluteSize
local FastCanvasFrameSize = self.CurrentCanvasFrame.FastCanvas.AbsoluteSize
local CanvasPosition = self.CurrentCanvasFrame.AbsolutePosition
local SurfaceGui = Frame:FindFirstAncestorOfClass("SurfaceGui")
MouseLocation -= GuiInset
if not SurfaceGui then
-- Gui
local MousePoint = MouseLocation - CanvasPosition
local TransformedPoint = (MousePoint / FastCanvasFrameSize) -- Normalised
TransformedPoint *= self.Resolution -- Canvas space
-- Make sure everything is aligned when the canvas is at different aspect ratios
local RatioDifference = Vector2New(CanvasFrameSize.X / FastCanvasFrameSize.X, CanvasFrameSize.Y / FastCanvasFrameSize.Y) - Vector2New(1, 1)
TransformedPoint -= (RatioDifference / 2) * self.Resolution
local UnroundedVec = TransformedPoint
local RoundX = math.ceil(TransformedPoint.X)
local RoundY = math.ceil(TransformedPoint.Y)
TransformedPoint = Vector2.new(RoundX, RoundY)
-- If the point is within the canvas, return it.
--if TransformedPoint.X > 0 and TransformedPoint.Y > 0 and TransformedPoint.X <= self.CurrentResX and TransformedPoint.Y <= self.CurrentResY then
return TransformedPoint
--end
else
-- SurfaceGui
local Part = SurfaceGui.Adornee or SurfaceGui:FindFirstAncestorWhichIsA("BasePart")
local Camera = workspace.CurrentCamera
local FastCanvasFrame = Frame:FindFirstChild("FastCanvas")
if Part and FastCanvasFrame then
local mouse = UserInputService:GetMouseLocation()
local params = RaycastParams.new()
params.FilterDescendantsInstances = {Part}
params.FilterType = Enum.RaycastFilterType.Include
local unitRay = Camera:ViewportPointToRay(mouse.X, mouse.Y)
unitRay = Ray.new(unitRay.Origin, unitRay.Direction*1000)
local result = workspace:Raycast(unitRay.Origin, unitRay.Direction) --, params) you can enable them but my system does not need it
if result and result.Instance == Part then
local topleftCFrame = Part.CFrame * CFrame.new(Part.Size.X/2, Part.Size.Y/2, -Part.Size.Z/2)
local mouseCFrame = CFrame.lookAt(result.Position, result.Position+(result.Normal or Vector3.one)) * CFrame.Angles( 0, 0, math.rad(90) )
local RelCF = topleftCFrame:ToObjectSpace(mouseCFrame)
local res = self.Resolution
return RoundPoint(Vector2.new(
math.clamp( math.abs(RelCF.X)/Part.Size.X*res.X, 0, res.X ),
math.clamp( math.abs(RelCF.Y)/Part.Size.Y*res.Y, 0, res.Y )
))
end
--[[ old method
if Result then
local Normal = Result.Normal
local IntersectionPos = Result.Position
if VectorFuncs.normalVectorToFace(Part, Normal) ~= SurfaceGui.Face then
return
end
-- Credits to @Krystaltinan for some of this code
local hitCF = CFrame.lookAt(IntersectionPos, IntersectionPos + Normal)
local topLeftCorners = VectorFuncs.getTopLeftCorners(Part)
local topLeftCFrame = topLeftCorners[SurfaceGui.Face]
local hitOffset = topLeftCFrame:ToObjectSpace(hitCF)
local ScreenPos = Vector2.new(
math.abs(hitOffset.X),
math.abs(hitOffset.Y)
)
-- Ensure the calculations work for all faces
if SurfaceGui.Face == Enum.NormalId.Front or SurfaceGui.Face == Enum.NormalId.Back then
ScreenPos -= Vector2.new(Part.Size.X / 2, Part.Size.Y / 2)
ScreenPos /= Vector2.new(Part.Size.X, Part.Size.Y)
else
return -- Other faces don't seem to work for now
end
local PositionalOffset
local AspectRatioDifference = FastCanvasFrameSize / CanvasFrameSize
local SurfaceGuiSizeDifference = SurfaceGui.AbsoluteSize / CanvasFrameSize
--print(SurfaceGuiSizeDifference)
local PosFixed = ScreenPos + Vector2.new(0.5, 0.5) -- Move origin to top left
ScreenPos = PosFixed * SurfaceGui.AbsoluteSize -- Convert to SurfaceGui space
ScreenPos -= CanvasPosition
local TransformedPoint = (ScreenPos / FastCanvasFrameSize) -- Normalised
TransformedPoint *= self.Resolution -- Canvas space
TransformedPoint += Vector2.new(0.5, 0.5)
-- Make sure everything is aligned when the canvas is at different aspect ratios
local RatioDifference = Vector2New(CanvasFrameSize.X / FastCanvasFrameSize.X, CanvasFrameSize.Y / FastCanvasFrameSize.Y) - Vector2New(1, 1)
TransformedPoint -= (RatioDifference / 2) * self.Resolution
TransformedPoint = RoundPoint(TransformedPoint)
-- If the point is within the canvas, return it.
if TransformedPoint.X > 0 and TransformedPoint.Y > 0 and TransformedPoint.X <= self.CurrentResX and TransformedPoint.Y <= self.CurrentResY then
return TransformedPoint
end
return TransformedPoint
end
]]
end
end
else
OutputWarn("Failed to get point from mouse (you cannot use this function on the server. Please call this function from a client script).")
end
end
Here is the testing place (118.6 KB)
So yea I hope my contribution was helpful for those with or without the issue.
If there is anything you need to know feel free to ask
Your use case of having a SurfaceGui in a BillboardGui is a really rare. And your fix is almost perfect, but you forgot to take into account the frame’s possible size and offset in the SurfaceGUI as shown below:
This probably explains why my version was so lengthy, but if you could add this into your code I will be sure to implement this into the next version and mention you!
Also thanks for that detailed explanation! The effort you put into this is nice, highly appreciate it!
I completely agree that the use case here is pretty rare, I was confused when I did not happen to have the issue when trying to replicate it on a testing place. It took me a few hours determining the issue but I got it.
Thank you for your response, I’ll see how I can implement the offset checks without breaking the function again
Alright awesome! If you manage to keep it quite a bit shorter than the original one even with those extra checks, ill definitely add that contribution. Also a hint to help you with that:
AbsoluteSize, AbsolutePosition, Percentages of 0 to 1
Shouldn’t be too hard to implement, I’d probably figure out how to it myself, but I just don’t have the time today. Thanks again for your help!
You’re welcome, so I have tried making sure that it takes offsets in consideration but with no success. I only managed to make sure that there is no weird offset if the scale is 1,0,1,0 or not but if the position is not 0,0,0,0 then there is a huge mouse offset which I tried to get fixed but I can’t figure out how:
function Canvas:GetMousePoint(): Vector2?
if RunService:IsClient() then
local MouseLocation = UserInputService:GetMouseLocation()
local GuiInset = game.GuiService:GetGuiInset()
local CanvasFrameSize = self.CurrentCanvasFrame.AbsoluteSize
local FastCanvasFrameSize = self.CurrentCanvasFrame.FastCanvas.AbsoluteSize
local CanvasPosition = self.CurrentCanvasFrame.AbsolutePosition
local SurfaceGui = Frame:FindFirstAncestorOfClass("SurfaceGui")
MouseLocation -= GuiInset
if not SurfaceGui then
-- Gui
local MousePoint = MouseLocation - CanvasPosition
local TransformedPoint = (MousePoint / FastCanvasFrameSize) -- Normalised
TransformedPoint *= self.Resolution -- Canvas space
-- Make sure everything is aligned when the canvas is at different aspect ratios
local RatioDifference = Vector2New(CanvasFrameSize.X / FastCanvasFrameSize.X, CanvasFrameSize.Y / FastCanvasFrameSize.Y) - Vector2New(1, 1)
TransformedPoint -= (RatioDifference / 2) * self.Resolution
local UnroundedVec = TransformedPoint
local RoundX = math.ceil(TransformedPoint.X)
local RoundY = math.ceil(TransformedPoint.Y)
TransformedPoint = Vector2.new(RoundX, RoundY)
-- If the point is within the canvas, return it.
--if TransformedPoint.X > 0 and TransformedPoint.Y > 0 and TransformedPoint.X <= self.CurrentResX and TransformedPoint.Y <= self.CurrentResY then
return TransformedPoint
--end
else
-- SurfaceGui
local Part = SurfaceGui.Adornee or SurfaceGui:FindFirstAncestorWhichIsA("BasePart")
local Camera = workspace.CurrentCamera
local FastCanvasFrame = Frame:FindFirstChild("FastCanvas")
if Part and FastCanvasFrame then
local mouse = UserInputService:GetMouseLocation()
local params = RaycastParams.new()
params.FilterDescendantsInstances = {Part}
params.FilterType = Enum.RaycastFilterType.Include
local unitRay = Camera:ViewportPointToRay(mouse.X, mouse.Y)
unitRay = Ray.new(unitRay.Origin, unitRay.Direction*1000)
local result = workspace:Raycast(unitRay.Origin, unitRay.Direction, params)
if result and result.Instance == Part then
local mouseCFrame = CFrame.lookAt(result.Position, result.Position + (result.Normal or Vector3.one)) * CFrame.Angles(0, 0, math.rad(90))
local RelCF = (Part.CFrame * CFrame.new(Part.Size.X / 2, Part.Size.Y / 2, -Part.Size.Z / 2)):ToObjectSpace(mouseCFrame)
local surfaceGuiSize = SurfaceGui.AbsoluteSize
local sizeRatio = Vector2.new(
surfaceGuiSize.X / FastCanvasFrameSize.X,
surfaceGuiSize.Y / FastCanvasFrameSize.Y
)
local correctedX = math.clamp(math.abs(RelCF.X) / Part.Size.X * surfaceGuiSize.X, 0, surfaceGuiSize.X)
local correctedY = math.clamp(math.abs(RelCF.Y) / Part.Size.Y * surfaceGuiSize.Y, 0, surfaceGuiSize.Y)
local finalX = correctedX * sizeRatio.X
local finalY = correctedY * sizeRatio.Y
local additionalOffsetX = (surfaceGuiSize.X - FastCanvasFrameSize.X) / 2
local finalPoint = Vector2.new(finalX - additionalOffsetX, finalY)
if finalPoint.X > 0 and finalPoint.Y > 0 and finalPoint.X <= self.CurrentResX and finalPoint.Y <= self.CurrentResY then
return RoundPoint(finalPoint)
else
return nil
end
end
end
end
else
OutputWarn("Failed to get point from mouse (you cannot use this function on the server. Please call this function from a client script).")
end
end
Thats how far I have gotten, also after some more digging around I found out that the reason for this huge offset in the default version (if the surfaceGui is inside a ScreenGui) is because of a bug from roblox. For no reason what so ever, roblox’s engine decides to add the Gui inset to 2d-instances that are inside a surface gui (if the surfaceGui is in a screenGui) no matter if the screenGui has “IgnoreGuiInset” toggled or not. I will probably file a bug report regarding that.
CanvasDraw Patch - v4.2.1.b
WOW THIS IS REALLY COOL!!! how long to render this?
Finally ran into a case to use this module! (Thank you good man)
I wanted to make a horror game with the same graphics as you showed in the beginning
You need the editable image beta feature enabled in your settings in studio.
EditableImage isn’t released fully
That video is showcasing a raycaster engine I made. There are plenty of online tutorials that could be easily done in luau with CanvasDraw
Module and Plugin Update v4.3.0
Hey all, been a bit since an update has happened. I’ve added some small but very useful features to both the module and the image tools plugin!
Added Canvas:SetClearRGBA()
to change the clearing colour and transparency
Added two new optional parameters to CanvasDraw.GetImageDataFromTextureId()
to limit the image size while maintaining the aspect ratio: MaxWidth
and MaxHeight
Added SetRGBA()
and GetRGBA()
to ‘ImageData’
Added two new filters to the CanvasDraw tools image editor plugin; Adjust Brightness and Colour Tint to help adjust colours and light levels.
It’s definitely possible to make a renderer within Roblox. I recently decided to make one, which offers good performance
I didn’t say it wasn’t possible. This graphics library can do real-time renderers really efficiently. Here’s two very powerful renderers I made with CanvasDraw:
Raycaster engine (runs above I think 160 FPS at 240x240)
Real-time raytracing with textures and unique rendering optimisation tricks to get above 120 FPS at 100x100
You should probably get back-face culling working first, as I dont think you’re doing that.
This video covers culling very well, and there’s a part 3 which covers clipping of triangles, which is kind of occlusion culling in a way, but it means you can have the camera close to the triangles without them going huge or running into divide by 0 issues.
Here’s a 3D OBJ renderer I made around a year ago that uses these implementations:
This was made before EditableImage
Module and Plugin Patch v4.3.1
Just fixed an error on the plugin and fixed and fixed an where the Canvas.FpsLimit
property does not function quite right
Canvas.FpsLimit
is now write safe!
Deprecated Canvas:SetFPSLimit()
in favour of Canvas.FpsLimit
no longer being read-only
Increased the speed of mass importing SaveObjects
Fixed a rare occasion where importing a specific formatted PNG may break the plugin causing the gui to get stuck on the screen.
Hello! Did one of these updates effect 3.0? In Draw & Donate the canvases are showing strange blurry lines I’ve never seen before, I didn’t change anything in the game they randomly just showed up. Also accept my DC friend request if you’re able to, I have a few more questions about this module! I love this module and would like to learn more about it to improve my game. Thank you!
Hello. This is a roblox bug
CanvasDraw 3.0 hasn’t been updated since November 2023
I sure hope this gets fixed soon
This issue appears to be fixed!