EditableMeshes do not render correctly across client/server

I’m trying to make a wall using EditableMeshes. However, if you render it on the server, it ONLY renders correctly on the server.

This is a wall generated on the server


But on the client, you can see it has not rendered correctly whatsoever. It’s just a little cube

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

-- Given 4 vertex IDs, adds a quad with shared normals for smooth shading
local function addSmoothQuad(eMesh, vid0, vid1, vid2, vid3, normal0, normal1, normal2, normal3)
	local nid0 = eMesh:AddNormal(normal0)
	local nid1 = eMesh:AddNormal(normal1)
	local nid2 = eMesh:AddNormal(normal2)
	local nid3 = eMesh:AddNormal(normal3)

	local fid1 = eMesh:AddTriangle(vid2, vid1, vid0)
	eMesh:SetFaceNormals(fid1, {nid2, nid1, nid0})

	local fid2 = eMesh:AddTriangle(vid3, vid2, vid0)
	eMesh:SetFaceNormals(fid2, {nid3, nid2, nid0})
end

-- Creates a straight wall mesh between two points in local space (0,0,0 centered)
local function makeWallMesh(length)
	local eMesh = AssetService:CreateEditableMesh()

	-- Create vertices in local space, centered at origin
	local halfLength = length/2

	-- Calculate vertices for the wall
	local backTopLeft = Vector3.new(-halfLength, WALL_HEIGHT, WALL_THICKNESS/2)
	local backTopRight = Vector3.new(halfLength, WALL_HEIGHT, WALL_THICKNESS/2)
	local backBottomLeft = Vector3.new(-halfLength, 0, WALL_THICKNESS/2)
	local backBottomRight = Vector3.new(halfLength, 0, WALL_THICKNESS/2)

	local frontTopLeft = Vector3.new(-halfLength, WALL_HEIGHT, -WALL_THICKNESS/2)
	local frontTopRight = Vector3.new(halfLength, WALL_HEIGHT, -WALL_THICKNESS/2)
	local frontBottomLeft = Vector3.new(-halfLength, 0, -WALL_THICKNESS/2)
	local frontBottomRight = Vector3.new(halfLength, 0, -WALL_THICKNESS/2)

	-- Add vertices
	local v1 = eMesh:AddVertex(backBottomLeft)
	local v2 = eMesh:AddVertex(backBottomRight)
	local v3 = eMesh:AddVertex(backTopLeft)
	local v4 = eMesh:AddVertex(backTopRight)
	local v5 = eMesh:AddVertex(frontBottomLeft)
	local v6 = eMesh:AddVertex(frontBottomRight)
	local v7 = eMesh:AddVertex(frontTopLeft)
	local v8 = eMesh:AddVertex(frontTopRight)

	-- Front face
	addSmoothQuad(
		eMesh,
		v5, v6, v8, v7,
		Vector3.new(0, 0, -1), Vector3.new(0, 0, -1),
		Vector3.new(0, 0, -1), Vector3.new(0, 0, -1)
	)

	-- Back face
	addSmoothQuad(
		eMesh,
		v2, v1, v3, v4,
		Vector3.new(0, 0, 1), Vector3.new(0, 0, 1),
		Vector3.new(0, 0, 1), Vector3.new(0, 0, 1)
	)

	-- Top face
	addSmoothQuad(
		eMesh,
		v7, v8, v4, v3,
		Vector3.new(0, 1, 0), Vector3.new(0, 1, 0),
		Vector3.new(0, 1, 0), Vector3.new(0, 1, 0)
	)

	-- Bottom face
	addSmoothQuad(
		eMesh,
		v1, v2, v6, v5,
		Vector3.new(0, -1, 0), Vector3.new(0, -1, 0),
		Vector3.new(0, -1, 0), Vector3.new(0, -1, 0)
	)

	-- End caps
	addSmoothQuad(
		eMesh,
		v1, v5, v7, v3,
		Vector3.new(-1, 0, 0), Vector3.new(-1, 0, 0),
		Vector3.new(-1, 0, 0), Vector3.new(-1, 0, 0)
	)

	addSmoothQuad(
		eMesh,
		v6, v2, v4, v8,
		Vector3.new(1, 0, 0), Vector3.new(1, 0, 0),
		Vector3.new(1, 0, 0), Vector3.new(1, 0, 0)
	)

	eMesh:RemoveUnused()
	return eMesh
end

-- Create and setup the wall part
local function createDynamicWall()
	-- First create a MeshPart with initial mesh
	local initialMesh = makeWallMesh(1)
	local meshPart = AssetService:CreateMeshPartAsync(Content.fromObject(initialMesh))
	meshPart.Name = "DynamicWall"
	meshPart.Material = Enum.Material.SmoothPlastic
	meshPart.Anchored = true
	meshPart.Parent = workspace

	local currentLength = 1

	local function updateWall(startPos, endPos)
		-- Calculate wall properties
		local wallVector = endPos - startPos
		local length = wallVector.Magnitude
		local midPoint = startPos:Lerp(endPos, 0.5)
		local direction = wallVector.Unit

		-- Update mesh if length changed
		if math.abs(length - currentLength) > 0.01 then
			local wallMesh = makeWallMesh(length)
			meshPart:ApplyMesh(AssetService:CreateMeshPartAsync(Content.fromObject(wallMesh)))
			-- Update the MeshPart's size to match new length
			meshPart.Size = Vector3.new(length, WALL_HEIGHT, WALL_THICKNESS)
			currentLength = length
		end

		-- Calculate orientation
		local rightVector = direction
		local upVector = Vector3.new(0, 1, 0)
		local forwardVector = rightVector:Cross(upVector)

		-- Set CFrame
		meshPart.CFrame = CFrame.fromMatrix(
			midPoint,
			rightVector,
			upVector,
			forwardVector
		)
	end

	return meshPart, updateWall
end


-- Example usage:
local wall, updateWall = createDynamicWall()

local startPos = workspace.PartA.Position -- Replace with your dynamic positions
local endPos = workspace.PartB.Position  -- Replace with your dynamic positions
updateWall(startPos, endPos)

Expected behavior

The wall should render correctly across client/server. Otherwise, this whole feature is borderline useless

Thanks for the report! We’ll follow up when we have an update for you.

1 Like

Unfortunately this is a known limitation, please see the “safety” and “known issues” notes

EditableMesh and EditableImage do not automatically replicate (i.e., edits are not automatically shared between clients or the server like other instances and properties on the platform).

and

Since EditableMesh and EditableImage objects do not currently replicate, we replicate an unusable Object of the same type in its place. Any attempt to read or write the contents of these “replicated” objects will throw. We’ll replace this with standard replication behavior in the future.

See the full announcement

We are working towards removing this limitation and supporting safe and efficient server->client replication of objects.

For now I would only recommend using Editables on the client side only. An Editable originally generated on client, not replicated from server, should render correctly.
As the previous reply mentioned, we don’t have Editable replication server->client yet. I don’t recommend to have Editable on server today, unless you really really have to. In that case, you could either maintain an identical copy on client, or manually replicate server Editable to client through i.e. removeEvents, although I don’t recommend you doing so, since once we release the replication, this would create a mess.

1 Like