I have no clue why you would want to do this but if you do this is going to be a long tutorial.
To start with you are expected to know a bit of math and a decent amount of scripting knowledge
If you can’t be bothered to read it all here is the file for the code
File.rbxm (4.9 KB)
Big thanks to @EgoMoose, his github repository and scripts on triangles is what makes this thing work.
I also recommend watching this video:
because it goes into what you need to do for a 5 point star.
Originally this was meant to be for parts. Which is possible with the same method and like 10 extra lines but is a bit clunky as it uses unions and negates.
Getting Started
So first things first let’s create a module script. You can put this anywhere but I recommend in a place easy to get to
Alright so now you want to go to the github repository above read it all through then copy the code near the bottom and just tweak it slightly to return the wedges
local Star = {}
local wedge = Instance.new("WedgePart");
wedge.Anchored = true;
wedge.TopSurface = Enum.SurfaceType.Smooth;
wedge.BottomSurface = Enum.SurfaceType.Smooth;
function Star.draw3dTriangle(a, b, c, parent)
local edges = {
{longest = (c - a), other = (b - a), origin = a},
{longest = (a - b), other = (c - b), origin = b},
{longest = (b - c), other = (a - c), origin = c}
};
local edge = edges[1];
for i = 2, #edges do
if (edges[i].longest.magnitude > edge.longest.magnitude) then
edge = edges[i];
end
end
local theta = math.acos(edge.longest.unit:Dot(edge.other.unit));
local w1 = math.cos(theta) * edge.other.magnitude;
local w2 = edge.longest.magnitude - w1;
local h = math.sin(theta) * edge.other.magnitude;
local p1 = edge.origin + edge.other * 0.5;
local p2 = edge.origin + edge.longest + (edge.other - edge.longest) * 0.5;
local right = edge.longest:Cross(edge.other).unit;
local up = right:Cross(edge.longest).unit;
local back = edge.longest.unit;
local cf1 = CFrame.new(
p1.x, p1.y, p1.z,
-right.x, up.x, back.x,
-right.y, up.y, back.y,
-right.z, up.z, back.z
);
local cf2 = CFrame.new(
p2.x, p2.y, p2.z,
right.x, up.x, -back.x,
right.y, up.y, -back.y,
right.z, up.z, -back.z
);
-- put it all together by creating the wedges
local wedge1 = wedge:Clone();
wedge1.Size = Vector3.new(0.2, h, w1);
wedge1.CFrame = cf1;
wedge1.Parent = parent;
local wedge2 = wedge:Clone();
wedge2.Size = Vector3.new(0.2 , h, w2);
wedge2.CFrame = cf2;
wedge2.Parent = parent;
return wedge1,wedge2
end
return Star
I think that’s all you need to copy and paste from other places.
Now that the long one is done your going to need 2 more short ones.
local function AbsVector(vector)
return Vector3.new(math.abs(vector.X),math.abs(vector.Y),math.abs(vector.Z))
end
and
The function just makes all the vectors positive.
Drawing the points
So we know a 5 point star has 5 points because it’s in the name and it is symmetrical
Knowing this we can get the exterior angle by doing 360 (degrees in a circle) / 5 --number of sides
This gives us 72
with this we can create a little script which rotates a part around a point from any given angle:
local function rotatePartAroundPoint(part, rotationPoint, angleInDegrees)
local axis = Vector3.new(0, 1, 0) -- You can change the axis of rotation as needed
local rotation = CFrame.Angles(0, math.rad(angleInDegrees), 0)
local translation = CFrame.new(rotationPoint)
part.CFrame = CFrame.new(translation * rotation * translation:PointToWorldSpace(part.Position))
part.Position = Vector3.new(part.Position.X,rotationPoint.Y,part.Position.Z)
end
And then create a for i loop where we increment by 72 every time while also storing the parts in a table for later:
local Parts = {}
for i = 1,5 do
local myPart = Instance.new("Part")
myPart.Size = Vector3.new(1, 1, 1)
myPart.Position = Vector3.new(1 , 0, 0)
myPart.Parent = Model
myPart.Anchored = true
angleToRotate += 72
myPart.Name = i
table.insert(Parts,myPart)
rotatePartAroundPoint(myPart, rotationPoint, angleToRotate)
end
So now it creates a star shape cool. Time to connect it
Connecting the points
So I think this is probably the simplest bit as it requires the least math but all you need to do is find the distance between the 2 points and get the magnitude. Then you need to multiply that by the lookvector and then make them all positive with the Abs function from earlier before finally multiplying the CFrame of it by the distance / 2.
Got that?
If not here is the script:
function Star.ConnectParts(a,b,name)
local Distance = (a - b).Magnitude
local Connector = Instance.new("Part")
local Size = Vector3.new(Distance,Distance,Distance) * Connector.CFrame.LookVector
Connector.Size = Vector3.new(5,1,5)
Connector.Position = b
Connector.CFrame = CFrame.lookAt(b,a)
Connector.Material = Enum.Material.SmoothPlastic
Connector.Size = AbsVector(Size)
Connector.Size = Vector3.new(1,Connector.Size.Y,Connector.Size.Z)
Connector.Size = Vector3.new(Connector.Size.X,0.5,Connector.Size.Z)
Connector.CFrame *= CFrame.new(0,0,-Distance/2)
Connector.Anchored = true
Connector.Parent = game.Workspace
Connector.Name = name or "Connector"
Connector.Transparency = 1
return Connector
end
Now following the video from earlier we can just look in the parts table and do
local Connector1 = Star.ConnectParts(Parts[4].Position,Parts[1].Position,"Connector1")
local Connector2 = Star.ConnectParts(Parts[1].Position,Parts[3].Position,"Connector2")
local Connector3 = Star.ConnectParts(Parts[2].Position,Parts[4].Position,"Connector3")
Alright cool the parts are connected. Now let’s fill it in
Filling in the star
To do this were going to need to find the intersection of where the points meet and I can’t be bothered to explain this function as it is just a simple ray cast script
function Idk.FindIntersection(Part,lookFor,genpart,parent,workspace)
local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Include
Params.FilterDescendantsInstances = {lookFor}
for i = 0,100 do
local ray = workspace:Raycast((Part.Position - Vector3.new(0,5,0)) + (Part.CFrame.LookVector * i),Vector3.new(0,15,0),Params)
if ray then
if ray.Instance then
if genpart == true then
local NewP = Instance.new("Part")
NewP.Anchored = true
NewP.Position = ray.Position
NewP.Size = Vector3.new(1,1,1)
NewP.Material = Enum.Material.Neon
NewP.Color = Color3.fromRGB(255,0,0)
NewP.Parent = parent
return ray,NewP
end
return ray
end
end
end
for i = 1,100 do
local ray = workspace:Raycast((Part.Position - Vector3.new(0,5,0)) + (-Part.CFrame.LookVector * i),Vector3.new(0,15,0),Params)
if ray then
if ray.Instance then
if genpart == true then
local NewP = Instance.new("Part")
NewP.Anchored = true
NewP.Position = ray.Position
NewP.Size = Vector3.new(1,1,1)
NewP.Material = Enum.Material.Neon
NewP.Color = Color3.fromRGB(255,0,0)
NewP.Parent = parent
return ray,NewP
end
return ray
end
end
end
end
Cool so now with this we can find the intersections and create parts where they meet:
But before that we need to create the main triangle so we can use that
local ray,P1= Star.FindIntersection(Connector3,{Connector2},true,Model,Model)
local w1,w2 = Star.draw3dTriangle(Parts[2].Position,Parts[5].Position,Parts[3].Position,Model,scale)
w1.Name = "w1"
w2.Name = "w2"
--
local ray1,Mid = Star.FindIntersection(Connector1,{w1,w2},true,Model,Model)
local ray2,LeftP = Star.FindIntersection(Connector2,{w1,w2},true,Model,Model)
local ray3,RightP = Star.FindIntersection(Connector3,{w1,w2},true,Model,Model)
local w3,w4 = Idk.draw3dTriangle(Parts[2].Position,NewP.Position,Parts[3].Position,Model,scale)
w3.Name = "w3"
w4.Name = "w4"
local w5,w6 = Idk.draw3dTriangle(Mid.Position,Parts[4].Position,RightP.Position,Model,scale)
w5.Name = "w5"
w6.Name = "w6"
local w7,w8 = Idk.draw3dTriangle(Mid.Position,Parts[1].Position,RightP.Position,Model,scale)
w7.Name = "w7"
w8.Name = "w8"
After that the triangle is filled in with parts and may look a bit weird with one side being to big
But that is fine they get turned into air when we make the terrain
Finalizing
for i,v in pairs(StarM:GetChildren()) do
if v:IsA('BasePart') then
v.Position = Vector3.new(v.Position.X,Position.Y,v.Position.Z)
end
if v:IsA('WedgePart') then
if v.Orientation.Z < 0 then
v.Orientation = Vector3.new(0,v.Orientation.Y,-90)
elseif v.Orientation.Z > 0 then
v.Orientation = Vector3.new(0,v.Orientation.Y,90)
end
end
end
Here I am just adjusting the orientation’s of the star so it is mostly flat
So now all we need to do is create the terrain. We can do this by using the game.Workspace.Terrain:FillWedge()
function with the other wedges as our size and position
You can change the material as grass is just a placeholder
game.Workspace.Terrain:FillWedge(w1.CFrame,w1.Size ,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w2.CFrame,w2.Size ,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w5.CFrame,w5.Size ,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w6.CFrame,w6.Size ,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w7.CFrame,w7.Size,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w8.CFrame,w8.Size ,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w3.CFrame,w3.Size ,Enum.Material.Air)
game.Workspace.Terrain:FillWedge(w4.CFrame,w4.Size ,Enum.Material.Air)
game.Workspace.Terrain:FillWedge(w3.CFrame,w3.Size ,Enum.Material.Air)
game.Workspace.Terrain:FillWedge(w4.CFrame,w4.Size ,Enum.Material.Air)
For some reason you need to make an area air twice otherwise you get left with these tiny bits. But idk maybe that’s just me
This is probably by no means the best, fastest and easiest way. But it works
If we add in a scale property to it and put it in a world model so the parts don’t show then we can scale it up
That would look like as a full module script:
local Star = {}
local wedge = Instance.new("WedgePart");
wedge.Anchored = true;
wedge.TopSurface = Enum.SurfaceType.Smooth;
wedge.BottomSurface = Enum.SurfaceType.Smooth;
local function AbsVector(vector)
return Vector3.new(math.abs(vector.X),math.abs(vector.Y),math.abs(vector.Z))
end
function Star.ConnectParts(a,b,name)
local Distance = (a - b).Magnitude
local Connector = Instance.new("Part")
local Size = Vector3.new(Distance,Distance,Distance) * Connector.CFrame.LookVector
Connector.Size = Vector3.new(5,1,5)
Connector.Position = b
Connector.CFrame = CFrame.lookAt(b,a)
Connector.Material = Enum.Material.SmoothPlastic
Connector.Size = AbsVector(Size)
Connector.Size = Vector3.new(1,Connector.Size.Y,Connector.Size.Z)
Connector.Size = Vector3.new(Connector.Size.X,0.5,Connector.Size.Z)
Connector.CFrame *= CFrame.new(0,0,-Distance/2)
Connector.Anchored = true
Connector.Parent = game.Workspace
Connector.Name = name or "Connector"
Connector.Transparency = 1
return Connector
end
function Star.FindIntersection(Part,lookFor,genpart,parent,workspace,scale)
local Params = RaycastParams.new()
Params.FilterType = Enum.RaycastFilterType.Include
Params.FilterDescendantsInstances = {lookFor}
for i = 0,100 do
local ray = workspace:Raycast((Part.Position - Vector3.new(0,5 * scale,0)) + (Part.CFrame.LookVector * i),Vector3.new(0,15 * scale,0),Params)
if ray then
if ray.Instance then
if genpart == true then
local NewP = Instance.new("Part")
NewP.Anchored = true
NewP.Position = ray.Position
NewP.Size = Vector3.new(1,1,1)
NewP.Material = Enum.Material.Neon
NewP.Color = Color3.fromRGB(255,0,0)
NewP.Parent = parent
return ray,NewP
end
return ray
end
end
end
for i = 1,100 do
local ray = workspace:Raycast((Part.Position - Vector3.new(0,5 * scale,0)) + (-Part.CFrame.LookVector * i),Vector3.new(0,15 * scale,0),Params)
if ray then
if ray.Instance then
if genpart == true then
local NewP = Instance.new("Part")
NewP.Anchored = true
NewP.Position = ray.Position
NewP.Size = Vector3.new(1,1,1)
NewP.Material = Enum.Material.Neon
NewP.Color = Color3.fromRGB(255,0,0)
NewP.Parent = parent
return ray,NewP
end
return ray
end
end
end
end
function Star.draw3dTriangle(a, b, c, parent,scale)
local edges = {
{longest = (c - a), other = (b - a), origin = a},
{longest = (a - b), other = (c - b), origin = b},
{longest = (b - c), other = (a - c), origin = c}
};
local edge = edges[1];
for i = 2, #edges do
if (edges[i].longest.magnitude > edge.longest.magnitude) then
edge = edges[i];
end
end
local theta = math.acos(edge.longest.unit:Dot(edge.other.unit));
local w1 = math.cos(theta) * edge.other.magnitude;
local w2 = edge.longest.magnitude - w1;
local h = math.sin(theta) * edge.other.magnitude;
local p1 = edge.origin + edge.other * 0.5;
local p2 = edge.origin + edge.longest + (edge.other - edge.longest) * 0.5;
local right = edge.longest:Cross(edge.other).unit;
local up = right:Cross(edge.longest).unit;
local back = edge.longest.unit;
local cf1 = CFrame.new(
p1.x, p1.y, p1.z,
-right.x, up.x, back.x,
-right.y, up.y, back.y,
-right.z, up.z, back.z
);
local cf2 = CFrame.new(
p2.x, p2.y, p2.z,
right.x, up.x, -back.x,
right.y, up.y, -back.y,
right.z, up.z, -back.z
);
-- put it all together by creating the wedges
scale = scale / 2.5
local wedge1 = wedge:Clone();
wedge1.Size = Vector3.new(0.2 * scale, h, w1);
wedge1.CFrame = cf1;
wedge1.Parent = parent;
local wedge2 = wedge:Clone();
wedge2.Size = Vector3.new(0.2 * scale, h, w2);
wedge2.CFrame = cf2;
wedge2.Parent = parent;
return wedge1,wedge2
end
local function rotatePartAroundPoint(part, rotationPoint, angleInDegrees)
local axis = Vector3.new(0, 1, 0) -- You can change the axis of rotation as needed
local rotation = CFrame.Angles(0, math.rad(angleInDegrees), 0)
local translation = CFrame.new(rotationPoint)
part.CFrame = CFrame.new(translation * rotation * translation:PointToWorldSpace(part.Position))
part.Position = Vector3.new(part.Position.X,rotationPoint.Y,part.Position.Z)
end
function Star.Create(Parent,Position,scale)
-- Function to rotate a part around a specified point
local Model = Instance.new("WorldModel")
Model.Parent = Parent
local rotationPoint = Position
local angleToRotate = 0
local Parts = {}
for i = 1,5 do
local myPart = Instance.new("Part")
myPart.Size = Vector3.new(1, 1, 1)
myPart.Position = Vector3.new(1 * scale, 0, 0)
myPart.Parent = Model
myPart.Anchored = true
angleToRotate += 72
table.insert(Parts,myPart)
myPart.Name = i
rotatePartAroundPoint(myPart, rotationPoint, angleToRotate)
end
local Connector1 = Star.ConnectParts(Parts[4].Position,Parts[1].Position,"Connector1")
local Connector2 = Star.ConnectParts(Parts[1].Position,Parts[3].Position,"Connector2")
local Connector3 = Star.ConnectParts(Parts[2].Position,Parts[4].Position,"Connector3")
Connector1.Parent = Model
Connector2.Parent = Model
Connector3.Parent = Model
local ray,NewP = Star.FindIntersection(Connector3,{Connector2},true,Model,Model,scale)
local w1,w2 = Star.draw3dTriangle(Parts[2].Position,Parts[5].Position,Parts[3].Position,Model,scale)
w1.Name = "w1"
w2.Name = "w2"
local ray1,Mid = Star.FindIntersection(Connector1,{w1,w2},true,Model,Model,scale)
local ray2,LeftP = Star.FindIntersection(Connector2,{w1,w2},true,Model,Model,scale)
local ray3,RightP = Star.FindIntersection(Connector3,{w1,w2},true,Model,Model,scale)
local w3,w4 = Star.draw3dTriangle(Parts[2].Position,NewP.Position,Parts[3].Position,Model,scale)
w3.Name = "w3"
w4.Name = "w4"
local w5,w6 = Star.draw3dTriangle(Mid.Position,Parts[4].Position,RightP.Position,Model,scale)
w5.Name = "w5"
w6.Name = "w6"
local w7,w8 = Star.draw3dTriangle(Mid.Position,Parts[1].Position,RightP.Position,Model,scale)
w7.Name = "w7"
w8.Name = "w8"
for i,v in pairs(Model:GetChildren()) do
if v:IsA('BasePart') then
v.Position = Vector3.new(v.Position.X,Position.Y,v.Position.Z)
end
if v:IsA('WedgePart') then
if v.Orientation.Z < 0 then
v.Orientation = Vector3.new(0,v.Orientation.Y,-90)
elseif v.Orientation.Z > 0 then
v.Orientation = Vector3.new(0,v.Orientation.Y,90)
end
end
end
Model:PivotTo(CFrame.new(Position) * Model:GetPivot().Rotation)
game.Workspace.Terrain:FillWedge(w1.CFrame,w1.Size ,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w2.CFrame,w2.Size ,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w5.CFrame,w5.Size ,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w6.CFrame,w6.Size ,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w7.CFrame,w7.Size,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w8.CFrame,w8.Size ,Enum.Material.Grass)
game.Workspace.Terrain:FillWedge(w3.CFrame,w3.Size ,Enum.Material.Air)
game.Workspace.Terrain:FillWedge(w4.CFrame,w4.Size ,Enum.Material.Air)
game.Workspace.Terrain:FillWedge(w3.CFrame,w3.Size ,Enum.Material.Air)
game.Workspace.Terrain:FillWedge(w4.CFrame,w4.Size ,Enum.Material.Air)
end
return Star
Um thanks for reading I hope this was ok. I am not very good at explaining really so I hope you understood it. If not I will try to answer any questions and yeh. Enjoy