[Studio Beta] Major updates to in-experience Mesh & Image APIs

Plane doesn’t render.
editablemesh_plane.rbxl (53.2 KB)

run a for loop in a smaller res than the image, scale these iterations proportionately along the image, get the color data for those pixels, and make a new image that draws clusters of pixels that represent each downscaled pixel

problem solved

1 Like

this is because all vertices’ Y positions are the same. It’s been a bug for a bit now.
You can fix it by making sure they’re not all on the same Y position like this

	local v1 = eMesh:AddVertex(Vector3.new(-5, 0, -5))
	local v2 = eMesh:AddVertex(Vector3.new(5, 1, -5)) --> change it to something that's not also 0
	local v3 = eMesh:AddVertex(Vector3.new(-5, 0, 5))
	local v4 = eMesh:AddVertex(Vector3.new(5, 0, 5))
3 Likes

Thanks for sharing! Both are fixed and will be updated soon!

1 Like

I’m enabling a change to log a warning each time EditableMesh:CreateMeshPartAsync is called:

EditableMesh:CreateMeshPartAsync is deprecated and will be removed in the next release. Use AssetService:CreateMeshPartAsync instead.

Planning to disable EditableMesh:CreateMeshPartAsync a week from now, next Wednesday.

My studio client is crashing when pressing the stop button during a play solo test when using EditableImage.

Here’s a repro place:

bugTest.rbxl (55.5 KB)

Log file:

0.650.0.6500743_20241107T083141Z_Studio_EFD72_last.log (502.6 KB)


Simply just run a solo test, maybe walk around for a couple seconds, and then press stop, and it will freeze the client for a few seconds and then close the window.


@TheGamer101

2 Likes

Can we get a simple, toned down script examples in the hub for each of the API. Just trying to create a simple mesh is super confused? The way it’s worded, this

local AssetService = game:GetService("AssetService")

local EditableMesh = AssetService:CreateEditableMesh()

local MeshPart = AssetService:CreateMeshPartAsync(EditableMesh) -- ?

CreateMeshPartAsync sounds like it creates a MeshPart, but I get unable to cast Object to Content? First sentence


To me implies that the EditableMesh is what I put inside the CreateMeshPartAsync?

And even example code provided is confusing
image
To me, I’d imagine I should be able to just copy/paste from the hub and press play and it’d work, but the function never gets called and I have no clue how to actually attribute the EditableMesh to a mesh part

See, I read the hub as this? Like what is MeshContent?

AssetService:CreateMeshPartAsync(makeSharpCube())

Like can anyone just provide simple code for creating a flat plane, or a cube. That I can copy/paste into studio and play and have it work right there? Cause nothing here is working in studio for me and I can’t figure it out myself :sob:

local AssetService = game:GetService("AssetService")
local myMeshPart = workspace:FindFirstChildWhichIsA("MeshPart")
local myEditableMesh = AssetService:CreateEditableMesh()

local v1 = myEditableMesh:AddVertex(Vector3.new(0, 0, 0))
local v2 = myEditableMesh:AddVertex(Vector3.new(1, 0, 0))
local v3 = myEditableMesh:AddVertex(Vector3.new(0, 1, 0))
local v4 = myEditableMesh:AddVertex(Vector3.new(1, 1, 0))
local v5 = myEditableMesh:AddVertex(Vector3.new(0, 0, 1))
local v6 = myEditableMesh:AddVertex(Vector3.new(1, 0, 1))
local v7 = myEditableMesh:AddVertex(Vector3.new(0, 1, 1))
local v8 = myEditableMesh:AddVertex(Vector3.new(1, 1, 1))

-- Helper method to compute extents of a Mesh. This will eventually
-- be replaced with a direct getter method on EditableMesh
local function computeExtents(em: EditableMesh)
	local verts = em:GetVertices()
	if #verts == 0 then
		return Vector3.zero
	end
	local inf = math.huge
	local min = Vector3.new(inf, inf, inf)
	local max = Vector3.new(-inf, -inf, -inf)
	for _, id in verts do
		local v = em:GetPosition(id)
		min = min:Min(v)
		max = max:Max(v)
	end
	return max - min
end

-- Create a new MeshPart instance linked to this EditableMesh Content
-- Note: EditableMesh:CreateMeshPartAsync will be replaced with
-- AssetService:CreateMeshPartAsync(Content, …) before public release
local newMeshPart = myEditableMesh:CreateMeshPartAsync(computeExtents(myEditableMesh))

-- Apply newMeshPart which is linked to the EditableMesh onto myMeshPart
myMeshPart:ApplyMesh(newMeshPart)

-- Any changes to myEditableMesh will now live update on myMeshPart
local vertexId = myEditableMesh:GetVertices()[1]
myEditableMesh:SetPosition(vertexId, Vector3.one)

-- You can create more MeshPart instances that reference the same
-- EditableMesh content
local newMeshPart2 = myEditableMesh:CreateMeshPartAsync(computeExtents(myEditableMesh))
-- Drop newMeshPart2 under workspace to render it
newMeshPart2.parent = workspace

-- Any changes to myEditableMesh will now live update on both
-- myMeshPart and newMeshPart2
myEditableMesh:SetPosition(vertexId, Vector3.one*2)

-- Calling these two lines again will recalculate collision and fluid geometry
-- with a snapshot of the current edits and update myMeshPart.
-- It is generally recommended to do this at the end of a conceptual edit operation.
-- Note: EditableMesh:CreateMeshPartAsync will be replaced with
-- AssetService:CreateMeshPartAsync(Content, …) before public release
local newMeshPart = myEditableMesh:CreateMeshPartAsync(computeExtents(myEditableMesh))
myMeshPart:ApplyMesh(newMeshPart)

Invalid mesh. Cannot create a MeshPart from an empty mesh. Mesh has no faces. - Server - Script:35
like what am I doing wrong… it has faces, I create vertices, thus it should have faces??

4 Likes

EditableMesh is of type Object but AssetService:CreateMeshPartAsync takes in a Content. so all you need to do is to convert your EditableMesh using Content.fromObject() like this:

local MeshPart = AssetService:CreateMeshPartAsync(Content.fromObject(EditableMesh))

Take a read through this section on Content for more info and background.

Remember that your EditableMesh cannot be empty (i.e. it needs to have valid vertices and faces before it can be converted into a MeshPart)

You seem to already have the code to create vertices, but you are missing the code to group those vertices into valid triangles using local faceId = myEditableMesh:AddTriangle(v1, v2, v3).

Take a look at the second code snippet to create a sharp cube here. You will notice that the addSharpQuad() function adds two triangles and a new normal given four vertices:

-- Given 4 vertex ids, adds a new normal and 2 triangles, making a sharp quad
local function addSharpQuad(emesh, vid0, vid1, vid2, vid3)
	-- AddTriangle creates a merged normal per vertex by default.
	-- For the sharp cube, we override the default normals with 
	-- 6 normals - a new normal to use for each side of the cube
	local nid = emesh:AddNormal() 

	local fid1 = emesh:AddTriangle(vid0, vid1, vid2)
	emesh:SetFaceNormals(fid1, {nid, nid, nid})
	
	local fid2 = emesh:AddTriangle(vid0, vid2, vid3)
	emesh:SetFaceNormals(fid2, {nid, nid, nid})
end

It would be nice to be able to load Roblox-generated images like avatar thumbnails.
For example, rbxthumb://type=Avatar&id=1084073&w=352&h=352 which was generated by the method Players:GetUserThumbnailAsync()
This seems pretty fair-game to me. Thanks for consideration.

3 Likes

Thanks for your report, we should have a fix for this issue coming out very soon. There is a bug that causes a crash if EditableImage or EditableMesh are deconstructed during DataModel shutdown.

1 Like

There a huge issue where a mesh generated when I play test server, it generates correctly, but when I play solo it is messed up?


local WALL_THICKNESS = 0.8
local WALL_HEIGHT = 14
local AssetService = game:GetService("AssetService")

-- Given 4 vertex IDs, adds a new normal and 2 triangles, making a sharp quad
local function addSharpQuad(eMesh, vid0, vid1, vid2, vid3)
	local nid = eMesh:AddNormal()
	local fid1 = eMesh:AddTriangle(vid0, vid1, vid2)
	eMesh:SetFaceNormals(fid1, {nid, nid, nid})
	local fid2 = eMesh:AddTriangle(vid0, vid2, vid3)
	eMesh:SetFaceNormals(fid2, {nid, nid, nid})
end

-- Creates a wall mesh between two points
local function makeWallBetweenPoints(startPos, endPos)
	local eMesh = AssetService:CreateEditableMesh()

	-- Calculate wall direction and length
	local wallVector = endPos - startPos
	local wallLength = wallVector.Magnitude

	-- Calculate rotation angle around Y axis
	local angle = math.atan2(wallVector.X, wallVector.Z)
	local cs = math.cos(angle)
	local sn = math.sin(angle)

	-- Function to rotate a point around Y axis
	local function rotatePoint(x, y, z)
		local rotatedX = x * cs - z * sn
		local rotatedZ = x * sn + z * cs
		return Vector3.new(
			rotatedX + startPos.X,
			y + startPos.Y,
			rotatedZ + startPos.Z
		)
	end

	-- Create vertices for the wall
	local v1 = eMesh:AddVertex(rotatePoint(0, 0, 0))
	local v2 = eMesh:AddVertex(rotatePoint(wallLength, 0, 0))
	local v3 = eMesh:AddVertex(rotatePoint(0, WALL_HEIGHT, 0))
	local v4 = eMesh:AddVertex(rotatePoint(wallLength, WALL_HEIGHT, 0))
	local v5 = eMesh:AddVertex(rotatePoint(0, 0, WALL_THICKNESS))
	local v6 = eMesh:AddVertex(rotatePoint(wallLength, 0, WALL_THICKNESS))
	local v7 = eMesh:AddVertex(rotatePoint(0, WALL_HEIGHT, WALL_THICKNESS))
	local v8 = eMesh:AddVertex(rotatePoint(wallLength, WALL_HEIGHT, WALL_THICKNESS))

	-- Create the faces
	addSharpQuad(eMesh, v5, v6, v8, v7)  -- Front
	addSharpQuad(eMesh, v1, v3, v4, v2)  -- Back
	addSharpQuad(eMesh, v1, v5, v7, v3)  -- Left
	addSharpQuad(eMesh, v2, v4, v8, v6)  -- Right
	addSharpQuad(eMesh, v1, v2, v6, v5)  -- Bottom
	addSharpQuad(eMesh, v3, v7, v8, v4)  -- Top

	eMesh:RemoveUnused()
	return eMesh
end

local function createWallBetweenParts(partA, partB)
	local startPos = partA.Position
	local endPos = partB.Position
	local wallMesh = makeWallBetweenPoints(startPos, endPos)
	local meshPart = AssetService:CreateMeshPartAsync(Content.fromObject(wallMesh))
	meshPart.Parent = workspace
	return meshPart
end

local wallMesh = makeWallBetweenPoints(Vector3.new(0, 0, 0), Vector3.new(10, 0, 10))
local meshPart = AssetService:CreateMeshPartAsync(Content.fromObject(wallMesh))
meshPart.Anchored = true
meshPart.Parent = workspace

EDIT Further testing on the above, it seems EditableMeshes only load based on if it server or client script? Can see I render the above code using a server script, and it’s visually there on the server + collisions exist but it does not render on client


Also, there’s an unloading bug

More bugs to report. Unsure if I’m mis-interpreting. But I run this is in RenderStepped. I basically want to visualize the mesh.

-- Example usage:
local Start, End = DragTracker.StartPos, DragTracker.CurrentPos

local wallMesh = makeCurvedWall(Start, End, (End + Start) / 2, 15)

if not meshPart then
	meshPart = AssetService:CreateMeshPartAsync(Content.fromObject(wallMesh))
	meshPart.Anchored = true
	meshPart.CanCollide = false
	meshPart.Parent = workspace
else
	meshPart:ApplyMesh(AssetService:CreateMeshPartAsync(Content.fromObject(wallMesh)))
end

I obviously don’t want to destroy and recreate a mesh constantly, so was trying to basically just “edit” the EditableMesh and apply it to the mesh. But my studio will crash doing this after a few seconds

1 Like

Hello good. Help me with something, I’m trying to do a kind of Cel-Shading, but from what I see, you can no longer edit a mesh in real time (and make it visible) because the meshContent method cannot be assigned. Tips? since creating a new mesh is not profitable.

Code:

local mesh = workspace.Head
local obj = Content.fromAssetId(126749825185277)
local Editable = game:GetService("AssetService"):CreateEditableMeshAsync(obj, {FixedSize = true, SkinningEnabled = true})
local lightDirection =game.Lighting:GetSunDirection()
local objectCFrame = mesh.CFrame
ID = Editable:GetColors()
Face = Editable:GetFaces()
for i, FaceID in pairs(Face) do
	local faceIndices = Editable:GetFaceNormals(FaceID)
	local normal1 = (objectCFrame:VectorToWorldSpace(Editable:GetNormal(faceIndices[1])))
	local normal2 = (objectCFrame:VectorToWorldSpace(Editable:GetNormal(faceIndices[2])))
	local normal3 = (objectCFrame:VectorToWorldSpace(Editable:GetNormal(faceIndices[3])))
	local faceNormal = (normal1 + normal2 + normal3).Unit
	local dotProduct = faceNormal:Dot(lightDirection)
	local colorids = Editable:GetFaceColors(FaceID)
	if dotProduct > 0.7 then
		for _, ids in pairs(colorids) do
			Editable:SetColor(ids, Color3.new(1, 1, 1))
		end
	elseif dotProduct > 0.3 then
		for _, ids in pairs(colorids) do
			Editable:SetColor(ids, Color3.new(0.5, 0.5, 0.5))
		end
	else
		for _, ids in pairs(colorids) do
			Editable:SetColor(ids, Color3.new(0.3, 0.3, 0.3))
		end
	end
	Editable:SetFaceColors(FaceID, colorids)
end

local finalMesh = game:GetService("AssetService"):CreateMeshPartAsync(Content.fromObject(Editable))
finalMesh.Parent = workspace
finalMesh.CFrame = objectCFrame

You can just edit the EditableMesh (var Editable) when you see fit. It should update the changes to the mesh I believe.

1 Like

Is this intentional, or did I miss something in the post?
image

2 Likes

Could EditableImage::DrawImage* also take in a buffer for its image parameter instead of solely an Object?


If I were to build a game engine and store the images (a lot of tiles) as a string/buffers/tables—which realistically have no size limitations—my biggest worry would be that EditableImages, which have a memory exhaust limit, might not always load.

Can assets that are able to load in a experience (like UGC or toolbox assets) be used for EditableImages and EditableMeshes in the future? These rules just don’t allow using an image from the toolbox and instead forces you to reupload it to your account / group while the asset can easily be downloaded with no problems from the website and it was distributed on the creator store with the creator’s approval (the option is disabled by default)

How would I make a flat plane? Previously before the deprecation of ‘EditableMesh:CreateMeshPartAsync’, I would pass a size through so that the Y size wasn’t 0 and the mesh wouldn’t be invisible. However, after these changes, I’m unsure how I would do this as my mesh is now completely invisible with a MeshSize Y of 0.

Also I’m not a 3d modeler and I won’t bother make 3d chaarcters, so I wait till they make their dummies publicly domain

Hello, I’m having a weird issue related to editable meshes where parenting a mesh part created with an editable mesh onto workspace starts to unrender objects in my game.

This is not a result of occlusion culling because I have the beta turned off.



(The object I’m changing the transparency of is a MeshPart with an EditableMesh applied onto it)


For some reason updating the editable mesh is a lot slower now as well. Unsure whether anyone else is experiencing this issue.

2 Likes

I get the permissions thing but can we still have access to some of the EditableMesh API even if we don’t own it? Like, GetFaces() or something? I have a plugin checking for mesh poly counts and its useless now because either the group owns the asset, or another user owns the asset. Something like read-only privileges would suffice I think.

1 Like