# Repeat a pattern on a surface with EditableImage accounted to world position and part rotation

The problem is really hard to describe, so let me show instead:

As you can see, there’s a pattern which repeats along the whole world, no matter where a part is.
But when I rotate it:

Arggghhh!!! It just goes wacko mode and nothing aligns anymore!!!

Here’s the script and rblx file:

``````local Debris = game:GetService("Debris")
local editableImage = Instance.new("EditableImage")
editableImage.Parent = script.Parent.SurfaceGui.ImageLabel
editableImage:Resize(Vector2.new(8,8))

while true do

local pixels: {number} = {}
for x = 0, 7 do
for y = 0, 7 do
local pixelWorldPosition = script.Parent.CFrame:PointToWorldSpace(Vector3.new(x/7*4, y/7*4, 0))
local pixelFactor = (pixelWorldPosition.X + pixelWorldPosition.Y + pixelWorldPosition.Z)/2
local alpha = math.sin(pixelFactor)
--print(transparency)
table.insert(pixels, 1)
table.insert(pixels, 1)
table.insert(pixels, 1)
table.insert(pixels, alpha)

--Uncomment to see pixelWorldPosition
--local part = Instance.new("Part")
--part.Anchored = true
--part.Size = Vector3.new(0.1, 0.1, 0.1)
--part.Parent = workspace
--part.Position = pixelWorldPosition
end
end
--print(pixels)
--print(#pixels, editableImage.Size.X * editableImage.Size.Y * 4)

editableImage:WritePixels(Vector2.new(0,0), Vector2.new(8, 8), pixels)
end
``````

Place1.rbxl (55.4 KB)

After a lot of tinkering I´ve found a solution to this problem, or atleast for the front side of a part. I don’t know why it works, but atleast it does.
This tells me that other faces can also have this effect if you just puzzle around with it for long enough.
If anyone can tell me how to use math instead of brute force to solve this problem I´d appreciate that.

``````local Debris = game:GetService("Debris")
local editableImage = Instance.new("EditableImage")
editableImage.Parent = script.Parent.SurfaceGui.ImageLabel
editableImage:Resize(Vector2.new(8,8))

while true do

local pixels: {number} = {}
for x = 0, 7 do
for y = 0, 7 do
local pixelWorldPosition = script.Parent.CFrame:PointToWorldSpace(Vector3.new(-x/7*4+2, y/7*4-2, -2))
local pixelFactor = pixelWorldPosition.X + pixelWorldPosition.Y + pixelWorldPosition.Z
local alpha = math.sin(pixelFactor+tick())
--print(transparency)
table.insert(pixels, 1)
table.insert(pixels, 1)
table.insert(pixels, 1)
table.insert(pixels, alpha)

--Uncomment to see pixelWorldPosition
--local part = game.Workspace.clonepart:Clone()
--part.Anchored = true
--part.Size = Vector3.new(0.1, 0.1, 0.1)
--part.Parent = workspace
--part.Position = pixelWorldPosition
--part.Color = Color3.new(x/7, y/7, 0)
end
end
--print(pixels)
--print(#pixels, editableImage.Size.X * editableImage.Size.Y * 4)

editableImage:WritePixels(Vector2.new(0,0), Vector2.new(8, 8), pixels)
end
``````

Here’s a more generalized solution. I used brute force

``````local Debris = game:GetService("Debris")

local shieldEffectPart = script.Parent

local partSize = shieldEffectPart.Size
local CANVAS_SIZE_PER_STUD = 1

local surfaceGuis: {SurfaceGui} = {
shieldEffectPart.FrontGui,
shieldEffectPart.BackGui,
shieldEffectPart.LeftGui,
shieldEffectPart.RightGui,
shieldEffectPart.TopGui,
shieldEffectPart.BottomGui
}

local function GetWorldOrientedSurface(part: BasePart, normalId: Enum.NormalId): (CFrame, Vector2)
local cf = part.CFrame
local rot = cf - cf.Position

local nObject = Vector3.fromNormalId(normalId)
local nWorld = rot * nObject

-- get orthogonal vector by utilizing the order of NormalId enums
-- i.e. Front.Value is 5 -> (5+1)%6 = 0 -> Right.Value
local xWorld = rot * Vector3.fromNormalId((normalId.Value + 1) % 6)

-- get other orthogonal vector
local zWorld = nWorld:Cross(xWorld)

-- make them both point "generally down"
if xWorld.Y > 0 then xWorld = -xWorld end
if zWorld.Y > 0 then zWorld = -zWorld end

-- choose the one pointing "more down" one as the z axis for the surface
if xWorld.Y < zWorld.Y then zWorld = xWorld end

-- redefine x axis based on that
xWorld = nWorld:Cross(zWorld)

local surfaceRot = CFrame.fromMatrix(Vector3.new(), xWorld, nWorld, zWorld)

-- get width of part in direction of x and y
local sizeInWorldSpace = rot * part.Size
local sizeInSurfaceSpace = surfaceRot:Inverse() * sizeInWorldSpace

-- get position on surface
local surfaceCFrame = surfaceRot + cf.Position + nWorld * math.abs(sizeInSurfaceSpace.Y) / 2

return surfaceCFrame, Vector2.new(math.abs(sizeInSurfaceSpace.X), math.abs(sizeInSurfaceSpace.Z))
end

function CalculatePixelWorldPosition(face: Enum.NormalId, pixelPosition: Vector2, editableImage: EditableImage): Vector3
local _, surfaceSize = GetWorldOrientedSurface(shieldEffectPart, face)
local pixelObjectPosition: Vector3 = Vector3.new()
local adjustedPixelPosition = pixelPosition / CANVAS_SIZE_PER_STUD

if face == Enum.NormalId.Front then
elseif face == Enum.NormalId.Back then
elseif face == Enum.NormalId.Right then
elseif face == Enum.NormalId.Left then
elseif face == Enum.NormalId.Top  then
elseif face == Enum.NormalId.Bottom then
end

return shieldEffectPart.CFrame:PointToWorldSpace(pixelObjectPosition)
end

for _, surfaceGui in surfaceGuis do
local _, surfaceSize = GetWorldOrientedSurface(shieldEffectPart, surfaceGui.Face)
local editableImage = Instance.new("EditableImage")
editableImage.Parent = surfaceGui.ImageLabel
editableImage:Resize(surfaceSize * Vector2.new(CANVAS_SIZE_PER_STUD, CANVAS_SIZE_PER_STUD))
end

local partCloned = false
while true do

for _, surfaceGui in surfaceGuis do
local editableImage: EditableImage = surfaceGui.ImageLabel.EditableImage

local pixels: {number} = {}
for y = 1, editableImage.Size.Y do
for x = 1, editableImage.Size.X do
local pixelWorldPosition = CalculatePixelWorldPosition(surfaceGui.Face, Vector2.new(x, y), editableImage)
local tickModulus = tick() % 2048
local perlinNoiseWorldPos = pixelWorldPosition/5+Vector3.new(tickModulus, tickModulus, tickModulus)
local pixelFactor = (math.noise(perlinNoiseWorldPos.X, perlinNoiseWorldPos.Y, perlinNoiseWorldPos.Z)+1)/2
local alpha = math.sin((pixelWorldPosition.X + pixelWorldPosition.Y + pixelWorldPosition.Z) + tick())

--table.insert(pixels, x/editableImage.Size.X)
--table.insert(pixels, y/editableImage.Size.Y)
--table.insert(pixels, 0)
--table.insert(pixels, 1)

table.insert(pixels, 1)
table.insert(pixels, 1)
table.insert(pixels, 1)
table.insert(pixels, alpha)

--Uncomment to see pixelWorldPosition
--if not partCloned then
--	local part = game.Workspace.clonepart:Clone()
--	part.Anchored = true
--	part.Size = Vector3.new(0.1, 0.1, 0.1)
--	part.Parent = workspace
--	part.Position = pixelWorldPosition
--	part.Color = Color3.new(x/editableImage.Size.X, y/editableImage.Size.Y, 0)
--end
end
end
--print(pixels)
--print(#pixels, editableImage.Size.X * editableImage.Size.Y * 4)

editableImage:WritePixels(Vector2.new(0,0), editableImage.Size, pixels)
end

partCloned = true
end
``````

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.