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.
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
andrightWall
are greater than the minimum threshold, returnleftWall
,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