Model pixelator plugin

image

Ever wanted to turn your roblox models into 3D 8-bit art? Well now you can with this neat plugin!

This plugin allows you to pixelate any desired model you wish. This plugin is recommended for larger models as you are limited with 1x1x1 blocks as your pixels. If you wish to do a small model (like a sword) than you can just scale up the model, pixelate it, and then scale it back down again.

Here are some examples!

Giant sphere
Build time: 144s
Scan size: 250, 250, 250
Blocks: 194,372

Utility truck
Build time: 38s
Scan size: 125, 125, 125
Blocks: 15,485

M1 Garand
Build time: 105s
Scan size: 223, 223, 223
Blocks: 6,476


This system uses raycasting. Meaning this will work on any BaseParts! Such as;

  • MeshParts
  • Unions
  • Parts
  • WedgeParts
  • etc

Please note that this system goes off by collision. Meaning that if you have a part that has a SpecialMesh in it, then the result will be dependent to the shape of your part rather than the mesh.

This plugin works by scanning all 6 sides of the model with rays, and if the ray hits an object within the model a cube will be created!

If you are going to use this plugin on a model that has Unions or MeshParts and you want an accurate result, then make sure that the Unions/MeshParts have their CollisionFidelity is set the Precise, or else your pixelate model may not look the same.

While generating models may cause a bit a of lag, performance with these models in-game is quite optimized.

Generating a sphere that is 25x25x25 studs, will give you about 1.9k parts. As that may seem a lot of parts, that’s actually the lowest you can go with out getting holes in your model.

If you are going to be pixelating a model that is 200x200x200 studs or below, your game will most likely not lag. Since this system uses raycasting, the models will be hollow, and they will be made up of cubes which has only a few polygons. So in reality, this system is quite optimized. Just what ever you do, DO NOT UNANCHOR THE MODEL. Or else your potato laptop will turn into a baked potato.

I do not recommend pixelating models that are larger than 400x400x400 studs, as you may encounter some heavy lag, or even a crash.

If roblox had a built in mesh making system, then this system would work flawlessly!

Using this plugin is easier done than said.

Once you have installed the plugin and you are in studio, you can open up the plugin in the plugins tab. And then select the Model Pixelator plugin

image

Now before we go ahead and pixelate a model, you will need to make sure that your model has a PrimaryPart. If you model does not have a primary part, then select your model, go to your properties window, and click on PrimaryPart, and then click on a part that is in the middle of your model to set it.

If your model doesn’t have a centered part, then you go to the Model Pixelator window, and increase the ExtraScanSize value to a few studs.

image

From there you can select you model, and then click PIXELATE.

If you’d like to know what the settings do, well then here’s some info;

  • Extra scan size - Increasing the scanning size for your model (can be useful for models with an offset primary part)

  • Keep materials - When set to true, your pixelated model will have the same materials as the original model. When set to false, your model will be plastic

  • Keep transparency - When set to true, your model will have the same transparent areas as the original model. When set to false, your model will have no transparent areas.

  • Delete old model - Deletes the original model after you make a pixelated clone of it.

  • Debug mode - Shows the scanning area and the origin and direction of the ray.

Plugin:

Source code:

source
local plugin = plugin -- Removes that annoying blue line you see 24/7

local Toolbar = plugin:CreateToolbar("Model Pixelator")
local PluginButton = Toolbar:CreateButton("Model pixelator", "Model pixelator plugin", "rbxassetid://5749290814")

local ChangeHistoryService = game:GetService("ChangeHistoryService")

local Selection = game:GetService("Selection")
local CoreGui = game:GetService("CoreGui")

local widgetInfo = DockWidgetPluginGuiInfo.new(
	Enum.InitialDockState.Float,  -- Widget will be initialized in floating panel
	false,  -- Widget will be initially enabled
	true,  -- Don't override the previous enabled state
	200,    -- Default width of the floating window
	270,    -- Default height of the floating window
	150,    -- Minimum width of the floating window (optional)
	150     -- Minimum height of the floating window (optional)
)

local PluginWidget = plugin:CreateDockWidgetPluginGui("TestWidget", widgetInfo)
PluginWidget.Name = "Model Pixelator Widget"
PluginWidget.Title = "Model Pixelator"

local PixelatingProcessGui = script.Parent:WaitForChild("PixelatingProcessGui")
local WidgetFrame = script.Parent:WaitForChild("WidgetFrame")
WidgetFrame.Parent = PluginWidget

local SettingsFrame = WidgetFrame:WaitForChild("SettingsFrame")
local PixelateButton = WidgetFrame:WaitForChild("PixelateButton")
CancelButton = PixelatingProcessGui:WaitForChild("CancelButton")
local WarningMessageText = WidgetFrame:WaitForChild("WarningMessage")

local ExtraScanSizeTextBox = SettingsFrame.ExtraScanSize.TextBox
local KeepMaterialsButton = SettingsFrame.KeepMaterials.TextButton
local KeepTransparencyButton = SettingsFrame.KeepTransparency.TextButton
local DeleteOldModelButton = SettingsFrame.DeleteOldModel.TextButton
local DebugModeButton = SettingsFrame.DebugMode.TextButton

local ModelToPixelate = nil -- The model to pixelate | A model that is bigger than (6, 6, 6) studs is recommended.

local ExtraScanSize = 10 -- (in studs) Increases the area scanning size (can be usefull be model's primarypart is not centered)

local UseModelMaterials = true -- Use the same materials that the model has

local UseModelTransparency = true -- Use the same transparent areas that the model has

local DebugMode = false -- Show the debugging parts and areas

local DeleteOldModel = true -- Deletes then old model when pixelating is finished

local Pixelating = false
local CanPixelate = false
local Canceled = true

PluginButton.Click:Connect(function()
	if PluginWidget.Enabled == false and not Pixelating then
		PluginWidget.Enabled = true
	else
		PluginWidget.Enabled = false
	end
end)

plugin.Unloading:Connect(function()
	local OldGui = PixelatingProcessGui:Destroy()
	Canceled = true
end)

ExtraScanSizeTextBox.FocusLost:Connect(function()
	if tonumber(ExtraScanSizeTextBox.Text) and tonumber(ExtraScanSizeTextBox.Text) > 0 then
		ExtraScanSize = tonumber(ExtraScanSizeTextBox.Text)
	elseif tonumber(ExtraScanSizeTextBox.Text) and tonumber(ExtraScanSizeTextBox.Text) < 1 then
		ExtraScanSize = 1
		ExtraScanSizeTextBox.Text = 1
	else
		ExtraScanSize = 25
		ExtraScanSizeTextBox.Text = 25
	end
end)

KeepMaterialsButton.MouseButton1Click:Connect(function()
	if UseModelMaterials == false then
		UseModelMaterials = true
		KeepMaterialsButton.Text = "True"
	else
		UseModelMaterials = false
		KeepMaterialsButton.Text = "False"
	end
end)

KeepTransparencyButton.MouseButton1Click:Connect(function()
	if UseModelTransparency == false then
		UseModelTransparency = true
		KeepTransparencyButton.Text = "True"
	else
		UseModelTransparency = false
		KeepTransparencyButton.Text = "False"
	end
end)

DeleteOldModelButton.MouseButton1Click:Connect(function()
	if DeleteOldModel == false then
		DeleteOldModel = true
		DeleteOldModelButton.Text = "True"
	else
		DeleteOldModel = false
		DeleteOldModelButton.Text = "False"
	end
end)

DebugModeButton.MouseButton1Click:Connect(function()
	if DebugMode == false then
		DebugMode = true
		DebugModeButton.Text = "True"
	else
		DebugMode = false
		DebugModeButton.Text = "False"
	end
end)

CancelButton.MouseButton1Click:Connect(function()
	Canceled = true
	
	plugin:SelectRibbonTool(Enum.RibbonTool.Move, UDim2.new(0, 0, 0, 0))
	
	Selection:Set({nil})
	
	PixelatingProcessGui.Parent = script.Parent
	
	Pixelating = false

	plugin:Deactivate()
end)

PixelateButton.MouseButton1Click:Connect(function()
	if CanPixelate and not Pixelating then
		Pixelating = true
		Canceled = false
		plugin:Activate(true)
		PluginWidget.Enabled = false
		
		Selection:Set({nil})
		
		PixelatingProcessGui.FinishedText.Visible = false
		PixelatingProcessGui.ScanningSizeText.Visible = true
		PixelatingProcessGui.StatsText.Visible = true
		PixelatingProcessGui.PartCountText.Visible = true
		CancelButton.Visible = true
		
		PixelatingProcessGui.Parent = CoreGui
		
		PixelateModel()
		
		PixelatingProcessGui.FinishedText.Visible = true
		PixelatingProcessGui.ScanningSizeText.Visible = false
		PixelatingProcessGui.StatsText.Visible = false
		PixelatingProcessGui.PartCountText.Visible = false
		
		plugin:SelectRibbonTool(Enum.RibbonTool.Move, UDim2.new(0, 0, 0, 0))
		
		CancelButton.Visible = false
		
		wait(3)
		
		PixelatingProcessGui.Parent = script.Parent
		
		Pixelating = false
		Canceled = true
		plugin:Deactivate()
	end
end)

Selection.SelectionChanged:Connect(function()
	if #Selection:Get() == 1 then
		local SelectedObjects = Selection:Get()
		local FirstModel
		for i, child in ipairs(SelectedObjects) do
			FirstModel = child
		end
		if FirstModel:IsA("Model") then
			PixelateButton.Visible = true
			WarningMessageText.Visible = false
			if FirstModel.PrimaryPart ~= nil then
				ModelToPixelate = FirstModel
				CanPixelate = true
			else
				PixelateButton.Visible = false
				WarningMessageText.Visible = true
				CanPixelate = false
				WarningMessageText.Text = "Model does not have a PrimaryPart"
			end
		else
			PixelateButton.Visible = false
			WarningMessageText.Visible = true
			CanPixelate = false
			WarningMessageText.Text = FirstModel.Name .. " is not a Model"
		end
	elseif #Selection:Get() < 1 then
		PixelateButton.Visible = false
		WarningMessageText.Visible = true
		CanPixelate = false
		WarningMessageText.Text = "Select a model"
	elseif #Selection:Get() > 1 then
		PixelateButton.Visible = false
		WarningMessageText.Visible = true
		CanPixelate = false
		WarningMessageText.Text = "Please select only 1 model"
	end
end)

function PixelateModel()	
	
	--== FUNCTIONS ==--
	
	local ModelSize = ModelToPixelate:GetExtentsSize()
	
	local ScanningSize = 0
	
	if ModelSize.X > ModelSize.Y and ModelSize.X > ModelSize.Z then
		ScanningSize = ModelSize.X + ExtraScanSize
	elseif ModelSize.Y > ModelSize.Z and ModelSize.Y > ModelSize.X then
		ScanningSize = ModelSize.Y + ExtraScanSize
	elseif ModelSize.Z > ModelSize.X and ModelSize.Z > ModelSize.Y then
		ScanningSize = ModelSize.Z + ExtraScanSize
	else
		ScanningSize = ModelSize.X + ExtraScanSize
	end
	
	local BlockCount = 0
	local BlockSize = 1
	local DebugBrick1
	local DebugBrick2
	local DebugSquare
	
	local function round(n)
		return math.ceil(n - 0.5)
	end
	
	ChangeHistoryService:SetWaypoint("Start pixelating Model")
	
	local PixelsGroup = Instance.new("Model")
	PixelsGroup.Name = "Pixelated " .. ModelToPixelate.Name
	PixelsGroup.Parent = ModelToPixelate
	
	local function CreateBlock(PartColour, PartMaterial, PartPosition, PartTransparency)
		--print("Created block" .. BlockCount)
		local Block = Instance.new("Part")
		Block.Name = "PixelBlock"
		Block.Anchored = true
		Block.Size = Vector3.new(BlockSize, BlockSize, BlockSize)
		Block.TopSurface = Enum.SurfaceType.Smooth
		Block.BottomSurface = Enum.SurfaceType.Smooth
		Block.Color = PartColour
		Block.Position = Vector3.new(round(PartPosition.X), round(PartPosition.Y), round(PartPosition.Z))
		if PartMaterial and UseModelMaterials then
			Block.Material = PartMaterial
		else
			Block.Material = Enum.Material.SmoothPlastic
		end
		if PartTransparency and UseModelTransparency then
			Block.Transparency = PartTransparency
		end
		Block.Parent = PixelsGroup
		return Block
	end
	
	local function ScanFaceAndPixelate(Origin, Direction)
		
		--print("ORIGIN")
		--print(Origin)
		--print("DIRECTION")
		--print(Direction)
		
		--print(Canceled)
		
		if DebugBrick1 then
			DebugBrick1.Position = Origin
		end
		
		if DebugBrick2 then
			DebugBrick2.Position = Direction
		end
		
		local Params = RaycastParams.new()
		Params.FilterType = Enum.RaycastFilterType.Whitelist
		Params.FilterDescendantsInstances = {ModelToPixelate}
		
		local function CastRay(OffsetX, OffsetZ)
			local ScanRay = workspace:Raycast(Origin + Vector3.new(OffsetX, 0, OffsetZ), Direction + Vector3.new(OffsetX, 0, OffsetZ), Params)
			if ScanRay then
				local Hit = ScanRay.Instance
				if Hit and Hit:IsA("BasePart") and Hit.Name ~= "PixelBlock" then
					--print("RAY HIT POS")
					--print(ScanRay.Position)
					CreateBlock(Hit.Color, Hit.Material, ScanRay.Position, Hit.Transparency)
					BlockCount = BlockCount + 1
					PixelatingProcessGui.PartCountText.Text = "Blocks: " .. BlockCount
				end
			end
		end
		
		CastRay(0, 0)
		CastRay(0.25, 0.25)
		CastRay(0.5, 0.5)
		CastRay(-0.5, -0.5)
		CastRay(-0.25, -0.25)
		CastRay(-0.25, 0.25)
		CastRay(-0.5, 0.5)
		CastRay(0.5, -0.5)
		CastRay(0.25, -0.25)
		
	end
	
	
	--== MAIN ==--
	
	if ModelToPixelate and ModelToPixelate.PrimaryPart then
		
		local OriginalModelToPixelateCFrame = ModelToPixelate:GetPrimaryPartCFrame()
		
		local CurrentPos = Vector3.new(ScanningSize / 2, ScanningSize / 2, ScanningSize / 2)
		
		if DebugMode then
			DebugBrick1 = CreateBlock(Color3.fromRGB(255, 0, 0), nil, CurrentPos, 0.5)
			DebugBrick1.Shape = Enum.PartType.Ball
			DebugBrick1.Name = "DebugPart"
			DebugBrick1.Parent = workspace
			DebugBrick1.CanCollide = false
			DebugBrick2 = DebugBrick1:Clone()
			DebugBrick2.Color = Color3.fromRGB(0, 0, 255)
			DebugBrick2.Parent = workspace
		end
		
		
		-- Begin pixelating the model
		
		
		local function ScanModel()
			local CurrentPos = Vector3.new(ScanningSize / 2, ScanningSize / 2, ScanningSize / 2)
			for index = 1, ScanningSize do
				if not Canceled then
					wait()
					for index = 1, ScanningSize do
						if not Canceled then
							--print(CurrentPos)
							CurrentPos = CurrentPos - Vector3.new(BlockSize, 0, 0)
							ScanFaceAndPixelate(CurrentPos, CurrentPos - Vector3.new(0, ScanningSize * 2, 0))
						end
					end
					CurrentPos = Vector3.new(ScanningSize / 2, ScanningSize / 2, CurrentPos.Z) - Vector3.new(0, 0, BlockSize)
				end
			end
		end
		
		if DebugMode then
			DebugSquare = Instance.new("Part")
			DebugSquare.Material = Enum.Material.SmoothPlastic
			DebugSquare.Anchored = true
			DebugSquare.CanCollide = false
			DebugSquare.Transparency = 1
			DebugSquare.Position = Vector3.new(0, 0, 0)
			DebugSquare.Size = Vector3.new(ScanningSize, ScanningSize, ScanningSize)
			DebugSquare.CastShadow = false
			local DebugSquareSelectionBox = Instance.new("SelectionBox")
			DebugSquareSelectionBox.Color3 = Color3.fromRGB(255, 157, 0)
			DebugSquareSelectionBox.LineThickness = 0.1
			DebugSquareSelectionBox.SurfaceColor3 = Color3.fromRGB(255, 157, 0)
			DebugSquareSelectionBox.SurfaceTransparency = 0.75
			DebugSquareSelectionBox.Adornee = DebugSquare
			DebugSquareSelectionBox.Parent = DebugSquare
			DebugSquare.Name = "DebugAreaSquare"
			DebugSquare.Parent = workspace
		end
		
		print("Starting... | Scanning size: " .. ScanningSize)
		PixelatingProcessGui.ScanningSizeText.Text = "Scanning size: " .. ScanningSize .. ", " .. ScanningSize .. ", " .. ScanningSize
		local ScanTimer = os.time()
		
		--print("Scanning side 1 out of 6 | Front")
		PixelatingProcessGui.StatsText.Text = "Pixelating sides (1/6)"
		ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, 0))
		ScanModel()
		
		--print("Scanning side 2 out of 6 | Right")
		PixelatingProcessGui.StatsText.Text = "Pixelating sides (2/6)"
		ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(90), 0, 0))
		ScanModel()
		
		--print("Scanning side 3 out of 6 | Back")
		PixelatingProcessGui.StatsText.Text = "Pixelating sides (3/6)"
		ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(180), 0, 0))
		ScanModel()
		
		--print("Scanning side 4 out of 6 | Left")
		PixelatingProcessGui.StatsText.Text = "Pixelating sides (4/6)"
		ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(270), 0, 0))
		ScanModel()
		
		--print("Scanning side 5 out of 6 | Top")
		PixelatingProcessGui.StatsText.Text = "Pixelating sides (5/6)"
		ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, math.rad(90)))
		ScanModel()
		
		--print("Scanning side 6 out of 6 | Bottom")
		PixelatingProcessGui.StatsText.Text = "Pixelating sides (6/6)"
		ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, math.rad(270)))
		ScanModel()
		
		ModelToPixelate:SetPrimaryPartCFrame(OriginalModelToPixelateCFrame)
			
		if DebugBrick1 then
			DebugBrick1:Destroy()
		end
		if DebugBrick2 then
			DebugBrick2:Destroy()
		end
		
		if DebugSquare then
			DebugSquare:Destroy()
		end
		
		PixelsGroup.Parent = workspace
		
		if DeleteOldModel and ModelToPixelate then
			ModelToPixelate:Destroy()
		end
		
		Selection:Set({PixelsGroup})
		
		ChangeHistoryService:SetWaypoint("Pixelate Model")
		
		print(ModelToPixelate.Name .. " has been successfully pixelated in " .. os.time() - ScanTimer .. " seconds! | " ..  BlockCount .. " parts created")
	else
		error("ModelToPixelate is nil or ModelToPixelate has no set PrimaryPart")
	end
end

print("Successfully loaded Ethanthegrand14's 'Model Pixelator'")

rbxm file:
Model pixelator.rbxm (18.7 KB)


I am open for feedback and questions, and I hope you enjoy this plugin!

Thank you!

67 Likes

The things I’m going to be capable to do with this is insane.

Very nice.

3 Likes

Every plugin is open-sourced, so no need to put it into the title.

3 Likes

I will for sure use this! Thanks for making this!

This is what happened when I tested this plugin:

It just put parts over the model. It did not pixelate the model and the model is still visible.

1 Like

local plugins, and off-sale plugins

If it’s a offsale plugin it’s not a community resource. This is why you don’t need to put open source in the title.

You just delete the original model?

No because the new model looks nothing like the original.

It gets pixelated so it is not gonna look like the original? It works perfectly for me, if you have decals on it, it won’t pixelate the decals.

Your issue is probably the scan size.

You should do some greedy meshing to minimize part count after the pixelation process

8 Likes

If you are using parts that have a specialmesh in it, then it won’t work. Simply because this system uses raycasting. The system goes by the collisions of you model.

Also

1 Like

Roblox has no meshesing system yet. Except for solid modeling and terrain.

2 Likes

Its not off sale. It may have been within an hour of release, but it has been available since yesterday.

No, its not. If you have a basic understanding of how raycasting works, then the issue is the model it self. Since this system goes off by collision, and I am pretty sure default character collisions are boxes. And hats use a part with a special mesh inside it. So the system won’t work with hats.

3 Likes

T-That’s Mind-blowing :exploding_head:

I can’t imagine how many things you can make with this, I really didn’t expect these types of plugins.
Amazing Job, keep it up!

2 Likes

I don’t see how this would be very useful, but unique idea ig

2 Likes

This plugin was intended mainly for art, and the fact that there isn’t a plugin like this. If your creative, this can be used for alotta things too.

3 Likes