Why are there these line shaped holes in my raycasting model pixelator?

So, I’ve been making a model pixelator that makes use of raycasting. But there is this one annoying issue that is stopping this system from being great. And that is these annoying line-shaped holes in the pixelated model.

even with regular parts and bricks the issue still occurs.

Its most likely to do a math error of mine, but i cannot confirm that. Some help will be greatly appreciated

PLACE FILE:
Model pixelater.rbxl (93.4 KB)

SCRIPT:

--== SETTINGS ==--

local ModelToPixelate = workspace:WaitForChild("Cube") -- The model to pixelate

local ScanningSize = 100 -- The area to scan in all 3 axis

local ScanDistance = 500 -- How far the scanning ray is casted 

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

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

local DebugMode = true -- Show the scanning ray's Origin position



--== FUNCTIONS ==--

local BlockCount = 0

local BlockSize = 1 -- do not change

local DebugBrick1

local function round(n)
	return math.floor(n + 0.5)
end

local PixelsGroup = Instance.new("Model")
PixelsGroup.Name = "Pixelated " .. ModelToPixelate.Name
PixelsGroup.Parent = ModelToPixelate

function CreateBlock(PartColour, PartMaterial, PartPosition, PartTransparency)
	print("Created block")
	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 KeepMaterials then
		Block.Material = PartMaterial
	else
		Block.Material = Enum.Material.SmoothPlastic
	end
	if PartTransparency and KeepTransparency then
		Block.Transparency = PartTransparency
	end
	Block.Parent = PixelsGroup
	BlockCount = BlockCount + 1
	return Block
end

function ScanFaceAndPixelate(Origin, Direction)
	
	if DebugBrick1 then
		DebugBrick1.Position = Origin
	end
	
	local Params = RaycastParams.new()
	Params.FilterType = Enum.RaycastFilterType.Whitelist
	Params.FilterDescendantsInstances = {ModelToPixelate}
	
	local ScanRay = workspace:Raycast(Origin, Direction, Params)
	if ScanRay then
		local Hit = ScanRay.Instance
		if Hit and Hit:IsA("BasePart") and Hit.Name ~= "PixelBlock" then
			CreateBlock(Hit.Color, Hit.Material, ScanRay.Position)
		end
	end
	
end


--== MAIN ==--

if ModelToPixelate and ModelToPixelate.PrimaryPart then
	
	local OriginalModelToPixelateCFrame = ModelToPixelate:GetPrimaryPartCFrame()

	local CurrentPos = ModelToPixelate.PrimaryPart.Position + Vector3.new(ScanningSize / 2, ScanningSize / 2, ScanningSize / 2)

	if DebugMode then
		DebugBrick1 = CreateBlock(Color3.fromRGB(255, 0, 0), nil, CurrentPos)
		DebugBrick1.Shape = Enum.PartType.Ball
		DebugBrick1.Name = "DebugPart"
		DebugBrick1.Transparency = 0.5
		DebugBrick1.CanCollide = false
	end


	-- Begin pixelating the model


	function ScanModel()
		local CurrentPos = ModelToPixelate.PrimaryPart.Position + Vector3.new(ScanningSize / 2, ScanningSize / 2, ScanningSize / 2)
		for index = 1, ScanningSize/BlockSize do
			wait()
			for index = 1, ScanningSize/BlockSize do
				--print(CurrentPos)
				ScanFaceAndPixelate(CurrentPos, CurrentPos - Vector3.new(0, ScanDistance, 0))
				CurrentPos = CurrentPos - Vector3.new(BlockSize, 0, 0)
			end
			CurrentPos = Vector3.new(ScanningSize / 2, ScanningSize / 2, CurrentPos.Z) - Vector3.new(0, 0, BlockSize)
		end
	end
	
	print("Scanning side 1 out of 6 | Front")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, 0))
	ScanModel()
	
	print("Scanning side 2 out of 6 | Right")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(90), 0, 0))
	ScanModel()
	
	print("Scanning side 3 out of 6 | Back")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(180), 0, 0))
	ScanModel()
	
	print("Scanning side 4 out of 6 | Left")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(270), 0, 0))
	ScanModel()
	
	print("Scanning side 5 out of 6 | Top")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, math.rad(90)))
	ScanModel()
	
	print("Scanning side 6 out of 6 | Bottom")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, math.rad(270)))
	ScanModel()
	
	ModelToPixelate:SetPrimaryPartCFrame(OriginalModelToPixelateCFrame)
	
	if DebugBrick1 then
		DebugBrick1:Destroy()
	end
	
	PixelsGroup.Parent = workspace
	
	print(ModelToPixelate.Name .. " has been successfully pixelated! | " ..  BlockCount .. " parts created")
else
	error("'ModelToPixelate' is nil or 'ModelToPixelate' has no set PrimaryPart")
end
3 Likes

Hmm, seems like your method works for the most part except for at these parts.

This got me thinking is it an issue with the mesh model, is the raycasting detecting anything at these parts of the model? Just thinking.

Collision problem with the raycast?

2 Likes

No matter what model, parts, meshes, or unions you use. you will always get the line holes. Its my code that’s the problem i’m pretty sure

1 Like

I have discovered something. The problem that is causing this issue, is rotating the model to scan another side. You see, rotating a model somehow ruins the Y value of the ray’s hit position.

as shown here, before rotating the model, the ray’s hit position would be 0.5. But then rotating the model causes the Y value to change ever so slightly, to the point where it causes an error with my round() function

image

local function round(n)
	return math.floor(n + 0.5)
end

round(0.5) will return 1

but round(0.050000005960464) will return 0

Anyone know how i can fix this?

Ah good catch, maybe its more than just a rounding issue as well since I see this in your code

Since you are using SetPrimaryPartCFrame, I recognize that this method of rotating the model is real buggy, due to floating point issues as you have discovered.

I remember there was a community resource which solves this problem but I can’t seem to find it now maybe later if so I’ll edit it.

Edit: Found it

maybe this will help

This fixed the slightly rotated model issue, but my models are still getting the line-shaped holes

Ok I discovered that my rounding is the problem.

Though if I don’t round my positions, I end up getting these very small gaps in model.

And the problem that’s causing that is the ray casting part

As you can see in the image below, the ray cast origin’s and direction’s X and Z axis are these same. But the Ray’s hit position is different. Which is causing that problem

image

also if the model is tilted, you get clipping parts. So i kinda need the round function

function ScanFaceAndPixelate(Origin, Direction)
	
	--print("ORIGIN")
	--print(Origin)
	--print("DIRECTION")
	--print(Direction)
	
	if DebugBrick1 then
		DebugBrick1.Position = Origin
	end
	
	local Params = RaycastParams.new()
	Params.FilterType = Enum.RaycastFilterType.Whitelist
	Params.FilterDescendantsInstances = {ModelToPixelate}
	
	local ScanRay = workspace:Raycast(Origin, Direction, 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)
		end
	end
	
end

I have found a temporary solution. I have solved this issue by casting 5 rays that each have different offsets to make sure that they don’t miss an area while scanning the model. And you no longer get holes!

Here are some results:

--== SETTINGS ==--

local ModelToPixelate = workspace:WaitForChild("Model") -- The model to pixelate

local ScanningSize = 150 -- The area to scan in all 3 axis

local ScanDistance = 500 -- How far the scanning ray is casted 

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

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

local DebugMode = true -- Show the scanning ray's Origin position


--== FUNCTIONS ==--

local BlockCount = 0
local BlockSize = 1
local DebugBrick1

local ModelMover = require(script:WaitForChild("ModelMover"))

local function round(n)
	return math.ceil(n - 0.5)
end

local PixelsGroup = Instance.new("Model")
PixelsGroup.Name = "Pixelated " .. ModelToPixelate.Name
PixelsGroup.Parent = ModelToPixelate

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 KeepMaterials then
		Block.Material = PartMaterial
	else
		Block.Material = Enum.Material.SmoothPlastic
	end
	if PartTransparency and KeepTransparency then
		Block.Transparency = PartTransparency
	end
	Block.Parent = PixelsGroup
	BlockCount = BlockCount + 1
	return Block
end

function ScanFaceAndPixelate(Origin, Direction)
	
	--print("ORIGIN")
	--print(Origin)
	--print("DIRECTION")
	--print(Direction)
	
	if DebugBrick1 then
		DebugBrick1.Position = Origin
	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)
			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)
	
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)
		DebugBrick1.Shape = Enum.PartType.Ball
		DebugBrick1.Name = "DebugPart"
		DebugBrick1.Parent = workspace
		DebugBrick1.Transparency = 0.5
		DebugBrick1.CanCollide = false
	end


	-- Begin pixelating the model


	function ScanModel()
		local CurrentPos = Vector3.new(ScanningSize / 2, ScanningSize / 2, ScanningSize / 2)
		for index = 1, ScanningSize do
			wait()
			for index = 1, ScanningSize do
				--print(CurrentPos)
				CurrentPos = CurrentPos - Vector3.new(BlockSize, 0, 0)
				ScanFaceAndPixelate(CurrentPos, CurrentPos - Vector3.new(0, ScanDistance, 0))
			end
			CurrentPos = Vector3.new(ScanningSize / 2, ScanningSize / 2, CurrentPos.Z) - Vector3.new(0, 0, BlockSize)
		end
	end
	
	print("Scanning side 1 out of 6 | Front")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, 0))
	--local cframes = ModelMover.GetCFrameTable(ModelToPixelate)
	--ModelMover.SetPrimaryPartCFrame(ModelToPixelate, CFrame.Angles(math.rad(0), math.rad(0), math.rad(0)), cframes)
	ScanModel()
	
	print("Scanning side 2 out of 6 | Right")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(90), 0, 0))
	--cframes = ModelMover.GetCFrameTable(ModelToPixelate)
	--ModelMover.SetPrimaryPartCFrame(ModelToPixelate, CFrame.Angles(math.rad(90), math.rad(0), math.rad(0)), cframes)
	ScanModel()
	
	print("Scanning side 3 out of 6 | Back")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(180), 0, 0))
	--cframes = ModelMover.GetCFrameTable(ModelToPixelate)
	--ModelMover.SetPrimaryPartCFrame(ModelToPixelate, CFrame.Angles(math.rad(180), math.rad(0), math.rad(0)), cframes)
	ScanModel()
	
	print("Scanning side 4 out of 6 | Left")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(math.rad(270), 0, 0))
	--cframes = ModelMover.GetCFrameTable(ModelToPixelate)
	--ModelMover.SetPrimaryPartCFrame(ModelToPixelate, CFrame.Angles(math.rad(270), math.rad(0), math.rad(0)), cframes)
	ScanModel()
	
	print("Scanning side 5 out of 6 | Top")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, math.rad(90)))
	--cframes = ModelMover.GetCFrameTable(ModelToPixelate)
	--ModelMover.SetPrimaryPartCFrame(ModelToPixelate, CFrame.Angles(math.rad(0), math.rad(0), math.rad(90)), cframes)
	ScanModel()
	
	print("Scanning side 6 out of 6 | Bottom")
	ModelToPixelate:SetPrimaryPartCFrame(CFrame.Angles(0, 0, math.rad(270)))
	--cframes = ModelMover.GetCFrameTable(ModelToPixelate)
	--ModelMover.SetPrimaryPartCFrame(ModelToPixelate, CFrame.Angles(math.rad(0), math.rad(0), math.rad(270)), cframes)
	ScanModel()
	
	--ModelMover.SetPrimaryPartCFrame(ModelToPixelate, OriginalModelToPixelateCFrame, cframes)
	ModelToPixelate:SetPrimaryPartCFrame(OriginalModelToPixelateCFrame)
	
	if DebugBrick1 then
		DebugBrick1:Destroy()
	end
	
	PixelsGroup.Parent = workspace
	
	print(ModelToPixelate.Name .. " has been successfully pixelated! | " ..  BlockCount .. " parts created")
else
	error("ModelToPixelate is nil or ModelToPixelate has no set PrimaryPart")
end