I am creating a cloth simulation, and I have decided that the best method is to create a cage made of Parts and SpringConstraints inside of the Workspace service, then use Camera:WorldToScreenPoint() to generate a wire-frame with edges and vertices inside of the UI. My only problem is being able to generate faces, which require filling in the space in-between edges. So far, the simulation is very realistic, and I do not want to abandon it.
Here is what the simulation looks like so far.
If you want the source code for edges and vertices, here it is. They are all LocalScripts under Frames.
VERTEX:
local RunService = game:GetService("RunService") --RUNSERVICE
--CAMERA
local camera = workspace.CurrentCamera
--PART OF CAGE
local vertex = workspace:WaitForChild("vertex1")
local point = script.Parent --FRAME
--DRAW--
RunService.RenderStepped:Connect(function()
--FIND WHERE POINT IS ON SCREEN
local vertPos, vertOnScreen = camera:WorldToScreenPoint(vertex.Position)
--IS THE POINT ON SCREEN?
if vertOnScreen then
point.BackgroundTransparency = 0 --VISIBLE
point.Size = UDim2.new(0, 10*(10/vertPos.Z), 0, 10*(10/vertPos.Z)) --SET SCALE BASED ON Z
point.Position = UDim2.new(0, vertPos.X - (point.Size.X.Offset / 2), 0, vertPos.Y - (point.Size.Y.Offset / 2)) --CENTERED ON POINT IN CAGE
else
point.BackgroundTransparency = 1 --INVISIBLE
end
end)
--BLAHBLAHIDONTKNO
EDGE:
local RunService = game:GetService("RunService") --RUNSERVICE
--CAMERA
local camera = workspace.CurrentCamera
--PARTS OF CAGE
local vertex1 = workspace:WaitForChild("vertex1")
local vertex2 = workspace:WaitForChild("vertex2")
--FRAME
local edge = script.Parent
--DRAW--
RunService.RenderStepped:Connect(function()
--FIND WHERE POINT IS ON SCREEN
local vert1Pos, vert1OnScreen = camera:WorldToScreenPoint(vertex1.Position)
local vert2Pos, vert2OnScreen = camera:WorldToScreenPoint(vertex2.Position)
--CHECK IF POINT IS ON SCREEN
if vert1OnScreen and vert2OnScreen then
edge.BackgroundTransparency = 0 --VISIBLE
--DISTANCES FOR X AND Y
local yDelta = vert2Pos.Y - vert1Pos.Y
local xDelta = vert2Pos.X - vert1Pos.X
--SET POSITION TO MIDDLE OF TWO POINTS
edge.Position = UDim2.new(0, (vert1Pos.X - (edge.Size.X.Offset / 2)) + (xDelta / 2), 0, vert1Pos.Y - (edge.Size.Y.Offset / 2) + (yDelta / 2))
local dist = math.sqrt((xDelta^2)+(yDelta^2)) --DISTANCE BETWEEN TWO POINTS
edge.Rotation = math.deg(math.atan(yDelta/xDelta)) --USE XDELTA AND YDELTA TO COMPUTE ANGLE IN DEGREES
edge.Size = UDim2.new(0, dist, 0, 5*(5/vert1Pos.Z)) --SET LENGTH TO DISTANCE, SET THICKNESS BASED ON Z DIST
else
edge.BackgroundTransparency = 1 --INVISIBLE
end
end)
--BLAHBLAHIDONTKNO
How should I approach filling in the faces?
EDIT: Actually, using knowledge from vector graphics, it could be possible to use the three edges as an "enclosed vector curve", and then create a grid of pixels confined to that group of edges, allowing for custom shapes.
Here is what I am talking about.
So far, I have gotten the grid to track to the triangle by taking the minimum and maximum points, and then positioning and scaling the grid based on those points.
Here's the current script for the face. I had to modify the size of each "pixel" from 3 pixels to 5 pixels to reduce lag.
local RunService = game:GetService("RunService") --RUNSERVICE
local camera = workspace.CurrentCamera --CAMERA
--PARTS OF CAGE
local vertex1 = workspace:WaitForChild("vertex1")
local vertex2 = workspace:WaitForChild("vertex2")
local vertex3 = workspace:WaitForChild("vertex14")
--CONTAINER OF PIXELS
local face = script.Parent
--DRAW--
RunService.RenderStepped:Connect(function()
--VERTICES THAT MAKE UP TRI
local vert1Pos, vert1OnScreen = camera:WorldToScreenPoint(vertex1.Position)
local vert2Pos, vert2OnScreen = camera:WorldToScreenPoint(vertex2.Position)
local vert3Pos, vert3OnScreen = camera:WorldToScreenPoint(vertex3.Position)
local vertPos = {vert1Pos, vert2Pos, vert3Pos} --TABLE OF POSITIONS
--DO NOT APPEAR WHEN POINTS ARE OFF SCREEN
if vert1OnScreen and vert2OnScreen and vert3OnScreen then
face.BackgroundTransparency = 1 --CONTAINER IS INVISIBLE
--FIND MINIMUM AND MAXIMUM POINTS OF TRI
local minX = 999999
local maxX = -999999
local minY = 999999
local maxY = -999999
for _, v in pairs(vertPos) do
if v.X < minX then
minX = v.X
end
if v.X > maxX then
maxX = v.X
end
if v.Y < minY then
minY = v.Y
end
if v.Y > maxY then
maxY = v.Y
end
end
--DETERMINE POSITION AND SIZE OF GRID BASED ON MIN AND MAX
face.Position = UDim2.new(0, minX, 0, minY)
--local dist = math.sqrt((xDelta^2)+(yDelta^2))
--face.Rotation = math.deg(math.atan(v2Delta/v3Delta))
face.Size = UDim2.new(0, maxX - minX, 0, maxY - minY)
--DRAW PIXELS
local pixelSize = UDim2.new(0, 5, 0, 5)
for i = 0, face.Size.X.Offset do
for j = 0, face.Size.Y.Offset do
if i % 5 == 0 and j % 5 == 0 then
local pixel = Instance.new("Frame", face)
pixel.Size = pixelSize
pixel.Position = UDim2.new(0, i, 0, j)
--pixel.BorderSizePixel = 0
pixel.BackgroundColor3 = Color3.new(255, 255, 255)
end
end
end
wait()
--REFRESH
for _, v in pairs(face:GetChildren()) do
if v:IsA("Frame") then
v:Destroy()
end
end
else
face.BackgroundTransparency = 1
end
end)
--BLAHBLAHIDONTKNO
Actually, now that I think about it, spamming edges would be the most efficient method, as spamming pixels generates more lag. Thanks, @StreamG0D!

5 comments
So do you want the faces to be flexible too or are they static within the wire frame?
I do want the faces to be flexible, as the whole point of using the UI was to create a deformable mesh based on the cage.
The easiest solution I can think of is to use a bunch of edges as a way to fill the grid, but I doubt the engine could handle that.
I guess you could place a false CanCollided Part and make it swing with the Cloth, but that'd be exceptionally hard.
So I was just looking throug unanswered questions and came across this old one, I am glad you thought my answer was best, but did it actually solve your problem or does it still lag a lot?