Filling gaps (like starvants plugin) but live in game

Holy what… ok, so I tried setting the thickness to 0.2, and got this :open_mouth:



It’s so beautiful :heart_eyes:

Unfortunately, it kinda stops there


90 degree still has the lil gap

So for some reason, certain angles work like magic, other angles just aren’t right :confused:

Messing around with more angles


lol

After staring at the code I saw it and hit myself on the head.

This:

local function getAngle(wallDirA, wallDirB)
    return math.acos(wallDirA:Dot(wallDirB))
end

Should be this:

local function getAngle(wallDirA, wallDirB)
    return wallDirA:Dot(wallDirB)
end

I think that is the last bug!

Also, the thickness should be 0.4 in that code I gave you, but you could also use this:

local WALL_HALF_THICKNESS = 0.2
local function getAdditionalLength(angleBetweenWalls)
    return math.tan(angleBetweenWalls / 2) * WALL_HALF_THICKNESS
end
local function getAngle(wallDirA, wallDirB)
    return wallDirA:Dot(wallDirB)
end

tried that and it did nothing :grimacing: now the walls don’t resize at all


Also are you sure it should be 0.4? Cause it worked like a charm with 0.2 for some of the angles, but didnt work at all with 0.4

Is there a test place you can show me? I’d like to see the behavior in action. We could also chat in game / group until we have a solution others could use.

local WALL_THICKNESS = 0.4
local function getAdditionalLength(angleBetweenWalls)
    return math.tan(angleBetweenWalls / 2) * WALL_THICKNESS / 2
end

local function getAngle(wallDirA, wallDirB)
    return math.acos(wallDirA:Dot(wallDirB))
end

This seemed to work with 90 degree walls


But was adding like 2-3 studs on any other angle

I can invite you to a team create?? i think you need to be on my friends list tho

1 Like

Good news! Last night we got the last script posted here working and we also added a couple more features. It now handles both halves of each wall (inner / outer or in other words left / right) differently so that when coloring them they don’t glitch the graphics where they overlap. In addition, we discovered that when the smallest angle between the walls is less than 90 degrees, the walls can’t be brought together to form a clean point. Doing so makes the wall’s thickness stick out the other end and form an hour glass looking shape around the poll. So, one wall is chosen to form the end and the other wall stops inside of that wall. This code also handles multiple-wall intersections by resizing the left half with the right half of the wall on its left side, and the right half with the left half of the wall on its right side (forward being toward the poll).

If anyone runs across a similar issue at some point, here is the code. Sorry if the math variables are not well named, there so many related in various special ways that it became difficult to find good names for them.

local WALL_HEIGHT = 12
local WALL_OFFSET = Vector3.new(0, WALL_HEIGHT, 0)
local UP = Vector3.new(0, 1, 0)
local WALL_HALF_THICKNESS = 0.2

local abs = math.abs
local function absVector3(v3)
	return Vector3.new(
		abs(v3.X),
		abs(v3.Y),
		abs(v3.Z)
	)
end

local function setSize(wall, half, length)
	local delta = length - wall.length[half]
	local dir = wall.dir * delta
	local part = wall[half]
	local offset = part.CFrame:VectorToObjectSpace(dir)
	part.Size = part.Size + math.sign(delta) * absVector3(offset)
	part.CFrame = part.CFrame + dir / 2
	wall.length[half] = length
end

local function adjust(wall, other)
	local angle = math.acos(wall.rightDir:Dot(-other.rightDir))
	local wallLength, otherLength
	if math.abs(angle - math.pi/2) < 0.001 then
		if wall.dir:Dot(other.rightDir - wall.rightDir) > 0 then
			wallLength = WALL_HALF_THICKNESS
			otherLength = 0
		else
			wallLength = 0
			otherLength = -WALL_HALF_THICKNESS
		end
	elseif angle > math.pi/2 then
		local otherAngle = math.acos(wall.dir:Dot(other.dir))
		local sign = -math.sign(wall.rightDir:Dot(other.dir))
		wallLength = sign * WALL_HALF_THICKNESS / math.tan(otherAngle)
		otherLength = sign * math.sqrt(wallLength^2 + WALL_HALF_THICKNESS^2)
	else
		wallLength = math.tan(angle / 2) * WALL_HALF_THICKNESS
		otherLength = wallLength
	end
	setSize(wall, 'left', wallLength)
	setSize(other, 'right', otherLength)
end

local POLL_KEY = '%d %d %d'
local function getWalls(polls, pos)
	local key = POLL_KEY:format(pos.X, pos.Y, pos.Z)
	local walls = polls[key]
	if not walls then
		walls = {}
		polls[key] = walls
	end
	return walls
end

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;
	}
	
	if not next(walls) then
		walls[wall] = true
		return
	end

	local leftIsFound
	local leftDot
	local leftWall
	
	local rightIsFound
	local rightDot
	local rightWall
	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

	adjust(wall, leftWall)
	adjust(rightWall, wall)
	walls[wall] = true
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
10 Likes

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