Get the Corners of a BasePart (No matter what shape)

It was originally written on Github (by myself)

Table of Contents

Get the Corners of a normal Part

Get the Corners of a normal Part

Base Project

Base Project

Sketch/Script

In this tutorial I will deal with getting the corners of a part. These can be used for many other things, for example to control a part into a certain region without using the function game.Workspace:FindPartInRegion3(). Let’s start now. In this tutorial each corner is represented by a part which is 1x1x1. So you can see if the corner is really there where it should be. To get the corners of a non-rotating part you need a formula. For this it is better to paint a sketch. Here is what I painted (I’m not an artist so please no criticism):

In this sketch you can see how you can get all the corners of a normal part. The technique is to use vectors, just being able to read a bit, turn everything into vectors and there you have it! Because I can’t paint at all I will explain it manually: say we want to get point A, then we have to use everything we know. The point in the middle is the position and we know that our target position is in the upper left corner at the back, and this one is also at the back. With all this we can calculate the position of A: left is -x, top is y and back is -z, we calculate everything together and get Vector3.new(-x, y,-z). But wait a minute, @Eternalove_fan32 didn’t you say that we need the vectors x, y and z, where do we get them? If it was a cube then we could just use one size. But the case is not always the same and sometimes the size could be 3x1x5, not necessarily a cube. Therefore we should use any size. So our three vectors (I mean the -x, y, -z) are not different from the corresponding size, so you could also write the position of point A as Vector.new(-Size.X, Size.Y, -Size.Z) Oh, I forgot to say: the size and position should be the part where you want to get the corners from.

The result is this:

UnRotatedPart

local Parent = script.Parent
local Position = Parent.CFrame
local Size = Parent.Size

local PartA = Instance.new("Part", workspace)
local PartB = Instance.new("Part", workspace)
local PartC = Instance.new("Part", workspace)
local PartD = Instance.new("Part", workspace)
local PartE = Instance.new("Part", workspace)
local PartF = Instance.new("Part", workspace)
local PartG = Instance.new("Part", workspace)
local PartH = Instance.new("Part", workspace)


PartA.Name = "A"
PartB.Name = "B"
PartC.Name = "C"
PartD.Name = "D"
PartE.Name = "E"
PartF.Name = "F"
PartG.Name = "G"
PartH.Name = "H"


PartA.CanCollide = false
PartB.CanCollide = false
PartC.CanCollide = false
PartD.CanCollide = false
PartE.CanCollide = false
PartF.CanCollide = false
PartG.CanCollide = false
PartH.CanCollide = false


PartA.Anchored = true
PartB.Anchored = true
PartC.Anchored = true
PartD.Anchored = true
PartE.Anchored = true
PartF.Anchored = true
PartG.Anchored = true
PartH.Anchored = true


PartA.Size = Vector3.new(1,1,1)
PartB.Size = Vector3.new(1,1,1)
PartC.Size = Vector3.new(1,1,1)
PartD.Size = Vector3.new(1,1,1)
PartE.Size = Vector3.new(1,1,1)
PartF.Size = Vector3.new(1,1,1)
PartG.Size = Vector3.new(1,1,1)
PartH.Size = Vector3.new(1,1,1)


PartA.CFrame = CFrame.new(Position.Position + Vector3.new(-Size.X/2, Size.Y/2, -Size.Z/2))
PartB.CFrame = CFrame.new(Position.Position + Vector3.new(-Size.X/2, Size.Y/2, Size.Z/2))
PartC.CFrame = CFrame.new(Position.Position - Size/2)
PartD.CFrame = CFrame.new(Position.Position + Vector3.new(Size.X/2 , -Size.Y/2 , Size.Z/2))
PartE.CFrame = CFrame.new(Position.Position + Vector3.new(Size.X/2 , -Size.Y/2 , -Size.Z/2))
PartF.CFrame = CFrame.new(Position.Position + Vector3.new(Size.X/2 , Size.Y/2 , -Size.Z/2))
PartG.CFrame = CFrame.new(Position.Position + Size/2)
PartH.CFrame = CFrame.new(Position.Position + Vector3.new(-Size.X/2 , -Size.Y/2 , Size.Z/2))

Yes, it works, but it still remains a problem:

RotatedWithoutCFramePart

As you can see, if our part turns, our corners don’t position themselves where they should and this can lead to unwanted results. This part was the hardest, but could be solved with the help of Elcore (I asked here for help). To solve this problem you need to use CFrames to transform the CFrame of the corners of the part in the object space, as it says in the wiki we just have to multiply the two CFrames and the final result:

RotatedWithCFrame_Trim

Edits

Edit 1 We make our script shorter and more readable

This script can of course be written shorter:

--The two scripts must be in the part from which you want to get the corners.
--First Script
local Position = script.Parent.CFrame
local Size = script.Parent.Size
local RunService = game:GetService("RunService")
local Event = script.Parent.Event

local Corners = {
	local PartA = Instance.new("Part", workspace)
	local PartB = Instance.new("Part", workspace)
	local PartC = Instance.new("Part", workspace)
	local PartD = Instance.new("Part", workspace)
	local PartE = Instance.new("Part", workspace)
	local PartF = Instance.new("Part", workspace)
	local PartG = Instance.new("Part", workspace)
	local PartH = Instance.new("Part", workspace)
}

Corners.PartA.Name = "A"
Corners.PartB.Name = "B"
Corners.PartC.Name = "C"
Corners.PartD.Name = "D"
Corners.PartE.Name = "E"
Corners.PartF.Name = "F"
Corners.PartG.Name = "G"
Corners.PartH.Name = "H"

for i,v in pairs(Corners) do
	v.CanCollide = false

	v.Anchored = true

	v.Size = Vector3.new(1,1,1)
end

RunService.Heartbeat:Connect(function()
	local Parent = script.Parent
	local Position = Parent.CFrame
	local Size = Parent.Size
	Event:Fire(Corners.PartA, Corners.PartB, Corners.PartC, Corners.PartD, Corners.PartE, Corners.PartF, Corners.PartG, Corners.PartH, Position, Size)
end)

--Second Script
local Event = script.Parent.Event

Event.Event:Connect(function(PartA,PartB,PartC,PartD,PartE,PartF,PartG,PartH,Position,Size)
	PartA.CFrame = Position*CFrame.new(-Size.X/2, Size.Y/2, -Size.Z/2)
	PartB.CFrame = Position*CFrame.new(-Size.X/2, Size.Y/2, Size.Z/2)
	PartC.CFrame = Position*CFrame.new(-Size/2)
	PartD.CFrame = Position*CFrame.new(Size.X/2 , -Size.Y/2 , Size.Z/2)
	PartE.CFrame = Position*CFrame.new(Size.X/2 , -Size.Y/2 , -Size.Z/2)
	PartF.CFrame = Position*CFrame.new(Size.X/2 , Size.Y/2 , -Size.Z/2)
	PartG.CFrame = Position*CFrame.new(Size/2)
	PartH.CFrame = Position*CFrame.new(-Size.X/2 , -Size.Y/2 , Size.Z/2)	
end)

So we have a shorter script and it is more readable.

Edit 2 We use tables and the tipps of the replys

As recommended by @starmaq and @Cinema_Sin , I will optimize the script thanks to loops and other little things:

--This is only one script, not two.

--Variables
local Position = script.Parent.CFrame
local Size = script.Parent.Size
local RunService = game:GetService("RunService")

--This is a table with all the corners inside
local Vertex = {
local PartA = Instance.new("Part")
local PartB = Instance.new("Part")
local PartC = Instance.new("Part")
local PartD = Instance.new("Part")
local PartE = Instance.new("Part")
local PartF = Instance.new("Part")
local PartG = Instance.new("Part")
local PartH = Instance.new("Part")
}

--Give a name to all corner
Vertex.PartA.Name = "A"
Vertex.PartB.Name = "B"
Vertex.PartC.Name = "C"
Vertex.PartD.Name = "D"
Vertex.PartE.Name = "E"
Vertex.PartF.Name = "F"
Vertex.PartG.Name = "G"
Vertex.PartH.Name = "H"

for _,vertex in pairs(Vertex) do --Go trough the table and set this properties
    vertex.CanCollide = false
    vertex.Anchored = true
    vertex.Size = Vector3.new(1,1,1)
end

RunService.Heartbeat:Connect(function()
        --Update us variables
        Position = script.Parent.CFrame
        Size = script.Parent.Size
        --Then change the Position of the Corner (here represented by a Part)
	Vertex.PartA.CFrame = Position*CFrame.new(-Size.X/2, Size.Y/2, -Size.Z/2)
	Vertex.PartB.CFrame = Position*CFrame.new(-Size.X/2, Size.Y/2, Size.Z/2)
	Vertex.PartC.CFrame = Position*CFrame.new(-Size/2)
	Vertex.PartD.CFrame = Position*CFrame.new(Size.X/2 , -Size.Y/2 , Size.Z/2)
	Vertex.PartE.CFrame = Position*CFrame.new(Size.X/2 , -Size.Y/2 , -Size.Z/2)
	Vertex.PartF.CFrame = Position*CFrame.new(Size.X/2 , Size.Y/2 , -Size.Z/2)
	Vertex.PartG.CFrame = Position*CFrame.new(Size/2)
	Vertex.PartH.CFrame = Position*CFrame.new(-Size.X/2 , -Size.Y/2 , Size.Z/2)	
end)

```Lua
--The two scripts must be in the part from which you want to get the corners.
--First Script
local Position = script.Parent.CFrame
local Size = script.Parent.Size
local RunService = game:GetService("RunService")
local Event = script.Parent.Event

local PartA = Instance.new("Part", workspace)
local PartB = Instance.new("Part", workspace)
local PartC = Instance.new("Part", workspace)
local PartD = Instance.new("Part", workspace)
local PartE = Instance.new("Part", workspace)
local PartF = Instance.new("Part", workspace)
local PartG = Instance.new("Part", workspace)
local PartH = Instance.new("Part", workspace)


PartA.Name = "A"
PartB.Name = "B"
PartC.Name = "C"
PartD.Name = "D"
PartE.Name = "E"
PartF.Name = "F"
PartG.Name = "G"
PartH.Name = "H"


PartA.CanCollide = false
PartB.CanCollide = false
PartC.CanCollide = false
PartD.CanCollide = false
PartE.CanCollide = false
PartF.CanCollide = false
PartG.CanCollide = false
PartH.CanCollide = false


PartA.Anchored = true
PartB.Anchored = true
PartC.Anchored = true
PartD.Anchored = true
PartE.Anchored = true
PartF.Anchored = true
PartG.Anchored = true
PartH.Anchored = true


PartA.Size = Vector3.new(1,1,1)
PartB.Size = Vector3.new(1,1,1)
PartC.Size = Vector3.new(1,1,1)
PartD.Size = Vector3.new(1,1,1)
PartE.Size = Vector3.new(1,1,1)
PartF.Size = Vector3.new(1,1,1)
PartG.Size = Vector3.new(1,1,1)
PartH.Size = Vector3.new(1,1,1)

RunService.Heartbeat:Connect(function()
	Event:Fire(PartA,PartB,PartC,PartD,PartE,PartF,PartG,PartH,script.Parent.CFrame,script.Parent.Size)
end)

--Second Script
local Event = script.Parent.Event

Event.Event:Connect(function(PartA,PartB,PartC,PartD,PartE,PartF,PartG,PartH,Position,Size)
	PartA.CFrame = Position*CFrame.new(-Size.X/2, Size.Y/2, -Size.Z/2)
	PartB.CFrame = Position*CFrame.new(-Size.X/2, Size.Y/2, Size.Z/2)
	PartC.CFrame = Position*CFrame.new(-Size/2)
	PartD.CFrame = Position*CFrame.new(Size.X/2 , -Size.Y/2 , Size.Z/2)
	PartE.CFrame = Position*CFrame.new(Size.X/2 , -Size.Y/2 , -Size.Z/2)
	PartF.CFrame = Position*CFrame.new(Size.X/2 , Size.Y/2 , -Size.Z/2)
	PartG.CFrame = Position*CFrame.new(Size/2)
	PartH.CFrame = Position*CFrame.new(-Size.X/2 , -Size.Y/2 , Size.Z/2)	
end)

Get Corners of a Sphere

Get Corners of a Sphere

Base Project

Base Project

Sketch/Script

In this tutorial I will deal with getting the corners of a sphere shaped part. These can be used for many other things, for example to control a part into a certain region without using the function game.Workspace:FindPartInRegion3(). Let’s start now. In this tutorial each corner is represented by a part which is 0.01x0.01x0.01. So you can see if the corner is really there where it should be. To get the corners of a non-rotating part you need a formula. For this it is better to paint a sketch. Here is what I painted (I’m not an artist so please no criticism):

In this sketch you can see how you can get all the corners of a sphere. I will use vectors, then only see the Picture and with a bit math we have our Idea!

Okay, but I can’t read it. What are all these arrows? And what is this, or this and this? It’s okay, though, because I’m here to help you. Like I said, I used vectors and because it’s hard to paint in 3D, I painted it in 2D. Before we start to calculate the vertex of the ball we have to know the normal position. For this there is nothing better than a ball with radius 1 and the position at 0,0,0, so the center of the room, then export the whole thing as OBJ file and finally in a program, like Blender. For those of you who wonder why: How else can we get the vertices? Roblox does not currently allow to get the vertices in itself, so we need another program. In Blender, we should cut the ball in four through the vertical, then cut the ball in one through the horizontal. In total we should have 8 pieces. From here on everybody has his own method, but I would personally take the 1/8th that is positive, for those who don’t figure out what I mean here is a picture of it:

https://prnt.sc/rg9ong

Then we can switch to edit mode in Blender so you can move, modify, add vertices and most importantly: get their position. In Blender we then switch to this edit mode by pressing the Tab key or finding the GUI of it and just clicking it. Then we should press the N key: to the right of the viewport frame there should be a new GUI. Now comes one of the most boring and exhausting work there is: Getting every single vertex of that 1/8th of an inch. To get a better overview we should delete the other 7/8, be careful to delete only these vertices, not the whole OBJ model.

When in Blender, move the camera with the middle button of the mouse. Attention: You need a mouse and no laptop integrated mouse. Then there are many other keys, but because Blender has so many key combinations I can’t tell you all the basics. With the Z key you can see through the model by selecting wireframe. Then with the B key you can select multiple vertices and with the X key you can delete the selected vertices by selecting vertices.

If you select a vertex with the left mouse click then the position of the vertex should appear in the GUI that came with the N key: By the way, you have to be careful because Blender uses the real vector system, so while in Roblox you form a vector like this: Vector3.new(x,y,z), the real vector system looks like this: Vector3.new(x,z,y), the z and y are fooled, so for example the height is the Z axis and not the Y axis. Then my plan is to add all received positions to the table. Now the question comes up: Why then use only 1/8 of the whole ball and not get all positions from every single vertex? The reason is simple: A RobloxBall has over 100 vertices, so it would be a real pain to control, copy and vectorize everything one by one. Why this particular 1/8th? You will see later.

Now, we go into studio then we have to make a table (it’s recommended by me, otherwise it looks like a catastrophe):

local Vertices = {
	Vector3.new(0.5, -0.000107, -0.000009),
	Vector3.new(0.474, -0.000107, 0.15811),
	Vector3.new(0.416, -0.000107, 0.27734),
	Vector3.new(0.354, -0.000107, 0.35355),
	Vector3.new(0.474, 0.15799, -0.000009),
	Vector3.new(0.452, 0.15069, 0.15075),
	Vector3.new(0.40099, 0.13349, 0.26726),
	Vector3.new(0.34399, 0.11459, 0.34412),
	Vector3.new(0.416, 0.27729, -0.000009),
	Vector3.new(0.40099, 0.26719, 0.13362),
	Vector3.new(0.364, 0.24239, 0.24253),
	Vector3.new(0.32, 0.21309, 0.3198),
	Vector3.new(0.354, 0.35349, -0.000009),
	Vector3.new(0.34399, 0.34399, 0.1147),
	Vector3.new(0.32, 0.31969, 0.21319),
	Vector3.new(0.28899, 0.28859, 0.28867),
	Vector3.new(0.277, 0.41589, -0.000009),
	Vector3.new(0.267, 0.40079, 0.13362),
	Vector3.new(0.243, 0.36369, 0.24253),
	Vector3.new(0.158, 0.47419, -0.000009),
	Vector3.new(0.15099, 0.45219, 0.15075),
	Vector3.new(0, 0.49989, -0.000009),
	Vector3.new(0.277, -0.000107, 0.41602),
	Vector3.new(0.158, -0.000107, 0.47434),
	Vector3.new(0, -0.000107, 0.49999),
	Vector3.new(0.267, 0.40089, 0.000009),
	Vector3.new(0.15099, 0.15069, 0.45226),
	Vector3.new(0, 0.15799, 0.47434),
	Vector3.new(0.243, 0.24239, 0.3638),
	Vector3.new(0.134, 0.267219, 0.40089),
	Vector3.new(0, 0.27729, 0.41602),
	Vector3.new(0.213, 0.31969, 0.3198),
	Vector3.new(0.115, 0.34399, 0.34412),
	Vector3.new(0, 0.35349, 0.35355),
	Vector3.new(0.134, 0.40079, 0.26726),
	Vector3.new(0, 0.41589, 0.27734),
	Vector3.new(0, 0.47419, 0.15811),
	Vector3.new(0.267, 0.40089, 0.13349),
	Vector3.new(0.267, 0.13349, 0.40089)
}

So now we have reached every vertex, but it does not help us in this state. That’s where the skis come in: We multiply the position (by which I mean the CFrame) of the ball by the position of each vertex in the table. Now you’ll probably say: “But don’t tell me that you’re really going to multiply each vertex by the position (I still mean the CFrame) of the ball: @Eternalove_fan32, you have really gone crazy now.” After this tutorial I might really have become :crazy_face:. Jokes on page, math on. No, I’m so stupid of course, I’ll just use loops for that, which will result:

--The Vertice Table over this line and insert this script into the target sphere

--The Folder in them we are going stock all the Corners Parts since we use Parts for seeing the invisible corners
local VertexFolder = Instance.new("Folder")
VertexFolder.Parent = workspace
VertexFolder.Name = "SphereVertex"

--The "Position" of the sphere, and with position i mean the CFrame
local Position = script.Parent.CFrame

--The loop
for index, vert in pairs(Vertices) do
        --For each Vertex make a Part with Size 0.01^3, Red Color and as Parent the Folder
	local Vertex = Instance.new("Part")
	Vertex.Parent = VertexFolder
	Vertex.BrickColor = BrickColor.Red()
	Vertex.Size = Vector3.new(0.01, 0.01, 0.01)
	Vertex.CanCollide = false
	Vertex.Anchored = true
	Vertex.Name = "Vertex"..index --The Name: only concat the string Vertex with the index
	Vertex.Position = Position * vert --Get us target Position
end

Okay, but Eterna, you said we should take 1/8 of it, and it had to be 1/8 of the positive, but why? And why not other 1/8ths? Ok, you can also work with one of the other 1/8, but the corner (I don’t want to repeat the 1/8 all the time) that I recommended is the easy one: If we start the script now, you can see that:

Fine, but where are the other vertices? Simple: They are not there because they are not in the table. So, who wants to define more than 200 vertices in this table? Certainly not me. That’s why the loops exist: It can do all the hard work for us:

--The Vertices Table over this line

for _,vert in pairs(Vertices) do
    local Mirrored = -vert
    local MirroredX = Vector3.new(-vert.X, vert.Y, vert.Z)
    local MirroredY = Vector3.new(vert.X, -vert.Y, vert.Z)
    local MirroredZ = Vector3.new(vert.X, vert.Y, -vert.Z)

    if table.find(Vertices, Mirrored) == "nil" then --If it not find the already mirrored vertex, then insert it in the table
        table.insert(Vertices, #Vertices + 1, Mirrored)

    elseif table.find(Vertices, MirroredX) == "nil" then
        table.insert(Vertices, #Vertices + 1, MirroredX)

    elseif table.find(Vertices, MirroredY) == "nil" then
        table.insert(Vertices, #Vertices + 1, MirroredY)

    elseif table.find(Vertices, MirroredZ) == "nil" then
        table.insert(Vertices, #Vertices + 1, MirroredZ)
    end
end

Now we have two problems, the first one comes when you try another size, while the second comes when you try the script from up here, the script takes more than 10 seconds before it shows us the vertices. Ten seconds is quite long and can change everything, especially in fast games as well as action games, fighting games and so on. There is only one way to speed it up: put everything into the table. I used a loop and print system, but if you want to know in detail, just ask down here. The final table is this one:
https://github.com/DragonGamerDevelopers/Roblox/blob/master/TooLargeTables/VerticesOfaRobloxSphere.txt

Now we can deal with the second problem: if we now take the ball with radius 1 then we get this result:

But if you try another size:

The problem is simple: Since we took the vertices of the ball with radius 1, it is calculated with radius 1, although it is not absolutely necessary now. So, how can we solve it? The solution is very simple, just multiply the vertex with the size and then we have also adjusted the vertex to the size:

And we’re practically finished! You can now use the vertexes of your RobloxBall the way you want. Final script:

Be sure to follow this post to be the first to be warned of possible new methods, new ways to get their vertex from others shapes or just always be warned.

Any questions or suggestions? Put them down here I’ll be happy to answer them.
Need help? Just ask!

I hope you enjoyed it and that it helped you. Final Script: https://github.com/DragonGamerDevelopers/Roblox/blob/master/TooLargeScripts/GetCornerOfaSphere.md

~~Eterna

14 Likes

Quite Some Work, but lovely! Thank you so much! :heart:

2 Likes

Not gonna lie this is a great article! Very well explained the the visuals are good as well. But it is important to use good practices in your code, such as using a loop instead of repeating lines of code, and using the 2nd parameter of Instance.new() which is not recommended.

2 Likes

Yes, I edited it on Github, but forgot to do the same here :sweat_smile:. Anyway, thanks for your positive answers, glad you enjoyed it :grinning:. I’m working on it so you can get the corners of a sphere (if you see it on Github, you can see that I’ve already started). Then I will add it to this tutorial and I will improve my script thanks to the loops.

1 Like

I second this, also it’s a argument, not a parameter if you read the post you’ll find the absoultely astonishing performance differences. It’s absolutely mad.


I’ve seen you everywhere on the forum @Eternalove_fan32, I wanted to say you are a great guy in general. This tutorial is very good, and if I were you, I would keep it up. I wouldn’t say you’re too far from member :wink:

2 Likes

Wow, thanks @Cinema_Sin , it’s really nice of you. This encourages me and gives me the strength to keep on closing and helping people :grin:.

I’ve been working on it since 8:00 a.m., it took a long time and it was also hard: calculating with vectors, using blender, reading everything correctly, using chance and so on. But finally it is done: I found a system to get the vertices of a ball!!! It takes a bit long, because it uses tables this time and I had to take a quarter of all vertices (there are over 100 vertices, so no need to calculate them all!) But I will add it all to this tutorial, so if you don’t understand something here, it’s no big deal. But honestly, I am destroyed and for once in my life I want to learn for school (who would have thought?). At the end: I have found a method to get the vertices of a Robloxball, but I have worked too long and now I want to cool down, so I will turn my knowledge into a tutorial as soon as I have recovered. But don’t panic, when it’s done I will warn you in this topic.

2 Likes