You’ve been somewhat lucky that you’ve come across this problem now as getting mesh data like this wasn’t possible until recently. Well, not unless you were willing to import the mesh data as a constant somewhere, e.g. stored as a string in a module script.
Unfortunately EditableMesh is still in beta so you’ll have to turn this on in Studio to use it; and do note that you won’t be able to use this in a live game until Roblox announces that it’s live.
1. Getting mesh data
If you need it in a live experience you’ll have to import the data as a constant in something like a ModuleScript - the best option here will depend on your use case etc.
If you’re happy to wait for EditableMesh release, you can get the data by:
Note: The vertices of an EditableMesh are not in world-space, you would need to use
CFrame:PointToWorldSpace(vertexPosition * scale + offset)if you needed to get the surface area scaled & relative to the world
-
Create an EditableMesh instance from the
MeshPartinstances you want to project - announcement found here - which you can do viaAssetService::CreateEditableMeshFromPartAsync, documentation here. -
Use the
EditableMesh:GetVertices(),EditableMesh:GetTriangleVerticesandEditableMesh:GetVertexNormal(vertexId: number)methods; found here, here and here respectively. This will give you the mesh’s vertices & indices, sadly you will have to iterate through each of the vertices to get the normal(s) though -
Cache these somewhere for each of the assets so you’re not doing this repeatedly and they can be used for other calculation(s)
2. Computing surface area
I’m assuming you’re wanting to calculate the surface normal for the lift?
This is less relevant to calculating drag, you’ll probs need to look into getting the cross-sectional area of the convex hull if you want to do that - if you get stuck here then feel free to send me a link of a Scripting Support post in the future and I can take a look.
If we’re only considering computing the surface area so we can calculate lift though:
2.1. Explanation
Right now we only have the vertices, the triangles and the normals but we need the normal of the face so that we can exclude/cull the faces that are facing away from the face we’re interested in.
To get the normals of each of the faces, we would so something like the following:
Face normals
--[=[
compute the normal of the triangle
@param v0 Vector3 - the 1st vertex of the triangle
@param v1 Vector3 - the 2nd vertex of the triangle
@param v2 Vector3 - the 3rd vertex of the triangle
@param vertexNormal Vector3 - the normal of the 1st vertex of the triangle
@returns Vector3 - the triangle surface normal
]=]
local function computeTriangleNormal(v0, v1, v2, vertexNormal)
local p0 = v1 - v0
local p1 = v2 - v0
local normal = p0:Cross(p1)
if vertexNormal then
-- we could also compute the average of
-- the three vertex normals and use the
-- following instead:
--
-- d = normal:Dot((na + nb + nc) / 3)
--
local d = normal:Dot(vertexNormal)
normal = d < 0 and -normal or normal
end
return normal -- .Unit to normalise
end
Now that we have the normals of each of the triangle, we need to exclude any that aren’t facing towards us.
Since we want the bottom of the object, we would need to cull from the perspective of the object’s up vector, i.e. meshPart.CFrame.UpVector, to get the faces that are looking towards us.
We can do this by checking if the dot product of the triangle normal & the point-to-triangle is equal to or greater than zero, e.g. …
Back-face culling
--[=[
det. whether a triangle is facing away
from our viewpoint
@param point Vector3 - the viewpoint position
@param vertex Vector3 - the first vertex of the triangle
@param normal Vector3 - the normal of the triangle
@returns boolean - reflects the back-facing status of the triangle
]=]
local function isBackFacing(point, vertex, normal)
return normal:Dot(vertex - point) >= 0
end
Finally, we can calculate the area of each of the remaining faces such that:
Triangle area
--[=[
compute the area of the triangle
@param v0 Vector3 - the 1st vertex of the triangle
@param v1 Vector3 - the 2nd vertex of the triangle
@param v2 Vector3 - the 3rd vertex of the triangle
@returns number - the triangle area
]=]
local function computeArea(v0, v1, v2)
local p0 = v1 - v0
local p1 = v2 - v0
return p1:Cross(p0).Magnitude * 0.5
end
2.2. Example
Condensed Example Code
--[=[
compute the surface area of a given mesh for triangles
that are facing towards the given NormalId
@param face NormalId - NormalId enum _e.g._ `Enum.NormalId.Bottom`
@param vertices table<Vector3> - the vertices array of the mesh, _e.g._ [Vector3, Vector3, ...]
@param indices table<number> - the indices array that make up the mesh, _e.g._
[tri1, tri1, tri1, tri2, tri2, tri2, ..., tri_n, tri_n, tri_n]
@returns number - the triangle area
]=]
local function computeSurfaceArea(face, vertices, indices)
local direction = -Vector3.fromNormalId(face)
local area = 0
for i = 0, #indices - 1, 3 do
local v0 = vertices[indices[i + 1]]
local v1 = vertices[indices[i + 2]]
local v2 = vertices[indices[i + 3]]
local p0 = v1 - v0
local p1 = v2 - v0
local normal = p1:Cross(p0)
local d = normal:Dot(direction)
if d >= 0 then
area += d
end
end
return 0.5*area
end
3. Projecting onto a plane
Unsure if this is actually necessary for your use case but after culling the triangles of the mesh, you could project the vertices of each of the triangles onto the plane like so:
Project a vector onto a plane
local function projectOnPlane(vec, normal)
local m = normal:Dot(normal)
if m < 1e-6 then
return vec
end
local d = vec:Dot(normal)
return vec - normal*d / m
end
The issue then becomes how you would calculate the area. You could compute the area of the triangles as above but I would imagine you would yield better results if you first computed the convex hull of the points and then calculated the area of the resulting hull.