Filling gaps (like starvants plugin) but live in game

Would you mind telling me what is required in order to implement this wonderful system into my code. For example, is further features necessary to implement or is iterating and changing what is already provided enough?

@Ukendio Recently reached out to me asking for a bit more information about how to use this code. Acting in the interest of the Roblox community Iā€™ve decided to revive this post to provide a bit more information. Here is a snippet from the message he sent me:

The placeWall(polls, pos) function is called by addToPoll(polls, poll, otherPoll, model). polls is a table with keys being a string of three integers separated by space, essentially a stringified Vector3. The robust definition for the string format is found in the constant variable POLL_KEY = '%d %d %d'. The value of each entry in polls is another table of walls for the given position. It is a hash table, so each key is a wall and the value is always true. The structure of a wall is found on lines 71-81:

	local wall = {
		length = {
			left = 0;
			right = 0;
		};
		model = model;
		right = right;
		left = left;
		dir = dir;
		rightDir = rightDir;
	}

As for the arguments of addToPoll(polls, poll, otherPoll, model), polls is the same table as described above, poll is a Vector3 position, otherPoll is the same, and model is the actual Model instance for the wall. It must have at least two children, Wall1 and Wall2 as defined on line 65:

local right, left = model.Wall1, model.Wall2

These left and right portions of the wall have their sizes adjusted by adjust(wall, other) to create the optimal joint between two walls. It handles the angles between walls when adjusting and has special cases for obtuse, acute, or 180 degree angles. addToPoll looks at all the walls connected to poll location to determine what walls need to be adjusted with the addition of the new wall.

The function setSize(wall, half, length) actually adjusts the wall length. You may need to edit this function depending on your left and right wall orientations. This function shrinks/grows the specified side of the wall in the wallā€™s direction to the desired length. It offsets the position of the wall so that only only one side of the wall is changed and the other remains the same.

Note that addToPolls is called is called by placeWall twice, once for each poll location, so that each side of the wall is adjusted to be optimal with the other walls on each side of the new wall.

This script will work when you define the function Vector3 snapToGrid(Vector3) (possibly to round to the nearest stud), make wallModel as a model with Wall1 and Wall2 children, and polls (the argument to placeWall(polls)) as an empty table, put this script in a LocalScript in the character scripts folder, and call placeWall. You may have resizing issues with the wall halves for which youā€™ll have to rotate and resize them to adjust which axis is pointing in the wallā€™s direction.

3 Likes

Thanks so much for the reply! And you did it so cohesively too!

Been looking at this as I have a similar problem. Just a heads up that the model clone never has its parent set. By default, when an object is cloned, its parent is set to nil.

Ah, yes. The code snippet was originally integrated with a larger system and I tried to modify it so it could stand alone but missed that. Iā€™ve modified the solution to set the parent of the model.

I am wondering how I would prevent players from trying to go below a certain degree? Like, to make them unable to make an edge unrealistically sharp. My initial attempt at patching this was to destroy the wall in the adjust function, however because addtopoll(x2) essentially calls the function four times, it will error but that is not necessarily the issue. The main issues at hand is that the wallā€¦
i. must be placed before I can check the angle of the walls that needs adjusting. And as consequence (ii)
ii. affects other wallsā€™ compenents whereas they get 'adjusted, even when prevented a new wall to transpire.

//To give backstory (idk if this will help), I am trying to make the wall indiciate when it canā€™t be placed (LIVE).

In the function addToPoll after the for loop and right before the adjustments are made, Iā€™d split the function. Instead if the angle between the leftWall and rightWall are greater than the minimum threshold, return leftWall, rightWall, and the new wall for the caller to adjust later. If either angle is less than the minimum threshold, return nothing (adding the wall to the poll failed). In the caller, if adding the wall to either poll fails (either call to addToPoll returns nothing), then donā€™t perform the adjustments on the left and right walls for each poll. Otherwise, continue with the adjustments and add the new wall to each pollā€™s list of walls. It is important that the wall being added actually has one table representing it per a poll. This is in part because the left and right neighbors are different for each poll and the directions are inverted.

Note that since the addToPoll function checks angles against the minimum threshold, the threshold will need to be visible to the function either as an argument or upvalue constant. To check the angle between the left and right walls to the new wall, check that the leftDot and rightDot variables are greater than the cosine of the minimum angle threshold. This equation stems from the fact that the dot product of two vectors is the cosine of the angle between them scales by the magnitude of both vectors. Since both are unit vectors, the dot product is simply the cosine of the angle between them. Thus, we need to compare the dot product to the cosine of the minimum threshold.

Iā€™d split the function. Instead if the angle between the leftWall and rightWall are greater than the minimum threshold, return leftWall , rightWall

When checking the angle between these walls, would using this line of code work?

local angle = math.asin((rightWall.Position-leftWall.Position).Magnitude/WALL_HEIGHT)
local degree = math.deg(angle)

if angle > 45 then
return leftWall, rightWall
else
return false
end

//Oops made the edit beforeyour comment

The left and right dot products are already computed and stored in the variables leftDot and rightDot. You can check that math.acos(leftDot) > threshold and math.acos(rightDot) > threshold, or find the cosine of the threshold once for both calculations (and if the threshold is unchanging, even store it as a constant).

I mean that after the for loop perform the above checks and then return the specified values. Leave adjusting the walls and adding the wall to the pollā€™s list of walls to the caller. This way, the caller can make sure that the up to 4 neighbor walls (2 per a poll) all meet the minimum threshold requirement before the adjustments are made and the wall added to the polls list. In placeWall you can check that the results from both calls to addToPoll are not nil and then adjust the adjacent walls and add the wall to each pollsā€™ list of walls.

Oof, I have tried a few things for a while such as implementing this. I am sure I must have misinterpreted you along the way but here are the two alternatives I wrote

if math.acos(rightDot) > math.pi/4 then
walls[wall] = true
return adjust(wall, leftWall)
end
if math.acos(leftDot) > math.pi/4 then
walls[wall] = true
return adjust(rightWall, wall)
end

and

if math.acos(rightDot) > math.pi/4 and math.acos(leftDot) > math.pi/4 then
walls[wall] = true
return leftWall, rightwall
end
adjust(wall, leftWall)
adjust(rightWall, wall)

This however has no effect, where as it keeps ā€˜functioningā€™ despite the angle is smaller than the threshold.

Actually, I fixed it.

instead of

if math.acos(rightDot) > math.pi/4 and math.acos(leftDot) > math.pi/4 then
walls[wall] = true
return leftWall, rightwall
end
adjust(wall, leftWall)
adjust(rightWall, wall)
I did this
if math.acos(rightDot) > math.pi/4 and math.acos(leftDot) > math.pi/4 then
walls[wall] = true
adjust(wall, leftWall)
adjust(rightWall, wall)
return leftWall, rightWall
else
return false
end

Close, but this part:

walls[wall] = true
adjust(wall, leftWall)
adjust(rightWall, wall)

needs to be done in the parent. Otherwise one poll may think the wall is okay to be placed and do so but the second poll says the wall is invalid. The adjustments need to be made in the caller, AFTER both calls to addToPoll return that the wall is valid.

So in my placeWall function make it like this?

local function placeWall()
if mouseDown then
templatePole.Transparency = 0.65
local wall = wallModel
wall.Parent = workspace.Walls
wall.Name = ā€œCoolā€
wall.Wall1.Transparency = 0
wall.Wall2.Transparency = 0
wall.Parent = workspace.Walls

if wall.Hitbox.Size.X < 3.5 then
	wall:Destroy()
	endWallPlacement()
else
	addToPoll(polls, pollA, pollB, wall)
	addToPoll(polls, pollB, pollA, wall)
	if addToPoll(polls, pollA, pollB, wall) then
		local walls = getWalls(polls, pollA)
		walls[wall] = true
		adjust(wall, wall.otherWall)
	end
	if addToPoll(polls, pollB, pollA, wall) then
		local walls = getWalls(polls, pollA)
		walls[wall] = true
		adjust(wall, wall.otherWall)
	end

	endWallPlacement()
end
mouseDown = false
end

end

I believe I owe you an apology. To set the stage, Iā€™m very busy between graduate school and work. The main reason I find myself active on these forums (despite not having the time for it) is that I enjoy doing what Iā€™m good at; Iā€™ve been active on Roblox for 12 years and developing on it for nearly as long. When I grow tired of struggling on difficult projects for school I come here. I really shouldnā€™t be active on here and donā€™t have time to do so, so please forgive me when I do not get back to you.

Please, donā€™t take my non-response personally; I mean no harm and harbor no ill-intent.

I find natural language to be a highly ambiguous and at times terribly inefficient at communicating details. I also found the method I originally described to produce some nasty code, so I introduced a callback to clean it up.

local thresh = acos(math.pi/2)
local function addToPoll(polls, poll, otherPoll, model)
	...

	local leftIsFound
	local leftDot
	local leftWall
	
	local rightIsFound
	local rightDot
	local rightWall
	for otherWall in next, walls do
		...
	end

	if leftDot >= thresh and rightDot >= thresh then
		return function ()
			adjust(wall, leftWall)
			adjust(rightWall, wall)
			walls[wall] = true
		end
	end
end

local function placeWall(polls)
	...

	place_A = addToPoll(polls, pollA, pollB, model)
	place_B = addToPoll(polls, pollB, pollA, model)

	if place_A and place_B then
		place_A()
		place_B()
		model.Parent = workspace
	else
		model:Destroy()
	end
end

I appreciate your help, you didnā€™t need to apologise! Secondly, I want to bring up that the model would be destroyed 100% of the time because it is not returning anything (probably due to that it is checking the angle of nothing thus returning nil ā†’ else model:Destroy() is executed?

Ah yes, where I check the leftDot and rightDot we also need to check the variables leftIsFound and rightIsFound. if (not leftIsFound or leftDot >= thresh) and (not rightIsFound or rightDot >= thresh) then ...

This unfortunately doesnā€™t work, I did try to
print(leftDot, thresh, rightDot) and got the result of 0.96346807479858 -nan(ind) 0.96346807479858 Is that perhaps the source of the issue?

Yes,

local thresh = acos(math.pi/2)

should be

local thresh = math.cos(math.pi/2)

I think I made that mistake earlier in this topicā€¦ It gets me all the time.

Ah!! I assumed it was math.acosxd! Still doesnā€™t let me place a wall! However, someone pointed out

if not next(walls) then
walls[wall] = true
return
end

Could make the function nil because it is returning before it can actually do all those checks.

Ah, they are right, it does make a problem. I wish I was in Windows, but Iā€™m waiting for a latent Dirichlet allocation algorithm to finish on 350K Twitter postsā€¦ I might be able to get a VBox up.

local thresh = math.cos(math.pi/2)

local function addToPoll(polls, poll, otherPoll, model)
	local walls = getWalls(polls, poll)
	local dir = (poll - otherPoll).Unit
	local rightDir = dir:Cross(UP)
	
	local right, left = model.Wall1, model.Wall2
	local toRight = right.Position - left.Position
	if toRight:Dot(rightDir) < 0 then
		right, left = left, right
	end

	local wall = {
		length = {
			left = 0;
			right = 0;
		};
		model = model;
		right = right;
		left = left;
		dir = dir;
		rightDir = rightDir;
	}
	local leftIsFound
	local leftDot
	local leftWall
	
	local rightIsFound
	local rightDot
	local rightWall

	local function place()
		if leftIsFound then
			adjust(wall, leftWall)
		end
		if rightIsFound then
			adjust(rightWall, wall)
		end
		walls[wall] = true
	end
	
	if not next(walls) then
		return place
	end

	for otherWall in next, walls do
		local dot = dir:Dot(otherWall.dir)
		local isRight = rightDir:Dot(otherWall.dir) < 0
		if isRight then
			if not rightIsFound or not rightDot or dot > rightDot then
				rightIsFound = true
				rightDot = dot
				rightWall = otherWall
			end
			if not leftIsFound and (not leftDot or dot < leftDot) then
				leftDot = dot
				leftWall = otherWall
			end
		else
			if not rightIsFound and (not rightDot or dot < rightDot) then
				rightDot = dot
				rightWall = otherWall
			end
			if not leftIsFound or not leftDot or dot > leftDot then
				leftIsFound = true
				leftDot = dot
				leftWall = otherWall
			end
		end
	end

	if leftDot >= thresh and rightDot >= thresh then
		return place
	end
end

local function placeWall(polls)
	local mouse = game.Players.LocalPlayer:GetMouse()

	mouse.Button1Down:Wait()
	local pollA = snapToGrid(mouse.Hit.p)

	mouse.Button1Down:Wait()
	local pollB = snapToGrid(mouse.Hit.p)

	local model = wallModel:Clone()
	model:SetPrimaryPartCFrame(CFrame.fromMatrix(
		(pollA + pollB) / 2 + WALL_OFFSET,
		(pollA - pollB).Unit,
		UP
	))

	addToPoll(polls, pollA, pollB, model)
	addToPoll(polls, pollB, pollA, model)
	model.Parent = workspace
end