Determining which of two sets of parts face each other and which parts face away from each other

Hi,

I’m currently brainstorming wall logic from a programming standpoint, particularly extending a wall in order to fill a gap.

So a single wall is composed of two parts: left and right like so:

image

The start and end positions of the wall are defined by theoretical “poles” at either end of the wall like so (semitransparent white parts):
image

So, suppose the player places another wall with one pole that the two walls share like so:

The pole is in the same position. Now, if we remove the pole, there’s a small issue, there’s a gap.

So I can just extend both the green and pink parts which is fine, right? Not quite, the issue is that I don’t know how to determine that the green and pink parts are the parts I should be extending from a programmatic standpoint. Sure, I can add a part but this only works with 90 degree corners. If I have a wall with an acute angle like so, a standard part wouldn’t work:
image

Also, I tried looking up how to determine whether a pair of directional vectors face each other or face away each other, most of the posts said to use :Dot on both directional vectors (which would be the LookVectors of the parts, however doing this on the two parts facing away from each other seemingly caused the dot product to be positive.

print(game.Selection:Get()[1].CFrame.LookVector:Dot(game.Selection:Get()[2].CFrame.LookVector))

outputs 1.5895228955287166e-07 which is greater than 0 despite the LookVectors facing away:

Any ideas?

My only idea is to simply calculate how much space you need filled up and the coordinates of it, and then fill it up with fitting parts. Math is the solution here

I tried that at one point but it was very unreliable and required manually checking the magnitude of the two possible positions then filled it like this:
image

It also wasn’t very pretty with 90 degree angles, filling it like so:


So I made an exception when the angle between the two walls was 90 degrees but I feel like there’s certainly a better and more reliable solution than relying on some variable.

Actually, 1.5895228955287166e-07 is supposed to be 0, it’s just a rounding error. That’s because the two vectors are 90 degrees apart from each other, which makes the dot product 0.

Can you share your code for generating these walls in the first place? What is your data structure for storing a shared joint like this?

1 Like

This code didn’t generate those walls, I made them manually, but the actual walls are generated like this (PS. I wrote this awhile ago as a passion project, I’m going to make it neater when I rewrite it). Point1 is the pole 1, point2 is pole 2, each part of the wall is 0.25 studs thick, so half is .125 which is the offset between the centre point:

		local worldPoint = itemData.Target.CFrame:PointToWorldSpace(point1)
		local centre = itemData.Target.CFrame:PointToWorldSpace((point1 + point2) / 2)
		local part1 = Instance.new('Part')
		part1.Name = 'Part1'
		part1.Parent = model
		part1.Size = Vector3.new(length,10,0.25)
		part1.CFrame = CFrame.lookAt(centre, worldPoint)
		part1.CFrame *= CFrame.new(-0.125,0,0) * CFrame.Angles(0,math.rad(90),0) -- left
		part1.Anchored = true
		local part2 = Instance.new('Part')
		part2.Name = 'Part2'
		part2.Parent = model
		part2.Size = Vector3.new(length,10,0.25)
		part2.CFrame = CFrame.lookAt(centre, worldPoint)
		part2.CFrame *= CFrame.new(0.125,0,0) * CFrame.Angles(0,math.rad(270),0) -- right
		part2.Anchored = true

So in the wall’s ItemData folder, it holds two Vector3Values which represent each pole. I’m thinking of doing ObjectValues that link to the next pole for room detection but that’s not really my priority for the time being. In the rewrite, the two wall parts will face away from each other because I assume that’s the easiest way to go about doing this.

I guess I’m mostly interested in what it means for two walls to “share” the same pole in a corner.

You’re making an undirected graph here, where your “poles” are nodes and your “walls” are edges.

How are you storing connectivity data for your graph? Put another way, how do you know two walls share a corner, and which pole that corresponds to?

I will try to check tomorrow

Ohhh I get what you mean.

When they share a corner it means one of the two points are in the same position. I’m not really sure how I plan on mapping it out but I’m thinking of something like this:

Pole_x,y would be the pole which represents an invisible part at a specific coordinate, whose name is pole_x,y where x is the x coordinate, y is the y coordinate, and the Wall values’ values correspond to a physical model (white) with one of their poles at that coordinate. The physical walls have stringvalues for ease of serialization, their values are formatted as x,y to correspond to a pole (green).
image

This is a pretty good idea! You’re basically making an adjacency list for your graph.

Just make sure to keep all the duplicated data up to date e.g. if you remove a wall, remove its reference from all the poles it was connected to.

Loop through all your poles. For those that have two walls, you can do something like this with two wedge parts:

image

You can have a rule that anything less than a certain angle gets chopped into a chamfer:

image

(that one’s not perfect, it would mean the pole would not be inside the wall in most cases—would be possible but more complicated to extend the rectangular parts, then make a chamfer).

But before I go further and try to figure out the math for that… Have you thought about using CSG to skirt the problem?

I.e. while the user is editing the wall position, show some simple representation (maybe a cylinder with diameter equal to the wall thickness at the pole). When the wall position is locked in, extend the lines far enough that they overlap, then clip the excess with some big negated parts:

image

image

There might be some flickering while you wait for the UnionAsync, but it wouldn’t be too bad and you can always hide it with some visual effects.

I will still probably try to figure out the math for the wedge method but just thought I’d throw that option out there.

1 Like

Thanks for that! I don’t really need help with that at the moment, I’m planning on using this to find the intersection point of two lines and then just extending but there’s a bit more math I need help with near the end of this reply which I’ll add on in a bit.

So first thing is finding which of the pairs of parts are facing each other. Each wall part’s LookVector would be like so:
image

Obviously we aren’t working with rays but I don’t really know how else to describe it.

So if we test yellow and green’s position, treat the black line like a wall, which would behave similarly to a ray’s origin, the LookVector starts at the yellow part’s position and can never “move” in the -LookVector direction.

If we test green and pink part using this same method, they never intersect which means they might get an extension but we don’t know for sure.

Finally, if we test the blue and yellow parts, they do face each other because they intersect from the black line onwards which means they are not the pair of walls to be “extended”, so we know it’s the opposite of these two parts that we need to extend.


To expand on this, if we have an obtuse angle like so:
image

the blue and yellow parts still intersect at some point, so we don’t extend them; they’re still technically “facing” each other, it’s the opposite of these parts that need to be extended:
image

These points never intersect beyond the black line; they’re facing “away” from each other
image

And finally, parallel walls will never get an extension.


Other question

Okay, here’s the sort of off-topic part:
So say we have two walls that are acute angles and we manage to figure out that the pink and green ones get the extension, the issue is that the intersection point method will cause the two parts to be extended like so:


Is there a way to sort of “limit” the parts’ sizes when the innermost vertex of each the green and pink parts intersect each other, sort of like where the black and white lines are to act as the “end” of each part?

Roughly, I’d say the intersection idea would work, but is a little finicky. If you can’t figure out which wall is the outer one while you’re generating it, I suppose I’d suggest something like

  • Given corner A-B-C
  • ba = a - b, bc = c - b
  • Calculate a vector that goes inbetween them like between = ba + bc
  • Check LookVector:Dot(between) for all the walls. If its positive, its an inner wall.

Sure, I could probably figure out the math if I sat down for a sec. But I spent enough time today putting together this demo of the wedges idea so maybe tomorrow :slight_smile:

The main part is this math:

type Vertex = {id: string, pos: Vector3, edges: {Edge}}
type Edge = {a: string, b: string}

local poles: {[string]: Vertex} = {}
local walls: {Edge} = {}

-- ...

-- Given a vertex with two thick parts like this:
--[[
               \      /               
                \    /                
\                \  /                /
 \                \/                / 
  \               /\               /  
   \             /  \             /   
    \           /    \           /    
     \         /      \         /     
      \       - _    _ -       /      
       \        _ -- _        /       
        \  _ -          - _  /        
         -                  -         
]]
-- Calculates the amount each part would need to be shortened so that they meet like this instead:
--[[
               \      /               
                \    /                
\                \  /                /
 \             _ - - _              /      
  \        _ -          - _        /       
   \  _ -                    - _  /        
    -                            -         
]]
local function ComputeCornerInset(v: Vertex)
	assert(#v.edges == 2)

	local e1 = v.edges[1]
	local e2 = v.edges[2]

	-- find the two vertices that aren't this one
	local p1 = poles[e1.a]
	if p1.id == v.id then p1 = poles[e1.b] end

	local p2 = poles[e2.a]
	if p2.id == v.id then p2 = poles[e2.b] end
	
	local v1 = (p1.pos - v.pos).Unit
	local v2 = (p2.pos - v.pos).Unit

	local bisect = (v1 + v2).Unit
	
	local dot = v1:Dot(bisect)
	
	local h = THICKNESS / (2 * math.sqrt(1 - dot*dot))
	local inset = h * dot

	return inset, bisect
end

Derived like this:

After you have the inset amount it’s just a bunch of cframing. Here’s the full code, you can stick it in workspace and play around

I tried to keep a data structure similar to the one you proposed. Only difference is that mine is with tables instead of folders, but it’s the same idea

--!strict

-- handles

local a = Instance.new("Part")
a.Anchored = true
a.Size = Vector3.new(2, 6, 2)
a.BrickColor = BrickColor.Red()
a.Position = Vector3.new(-1, 3, -20)
a.Parent = workspace

local b = a:Clone()
b.BrickColor = BrickColor.Green()
b.Position = Vector3.new(-10, 3, -5)
b.Parent = workspace

local c = a:Clone()
c.BrickColor = BrickColor.Blue()
c.Position = Vector3.new(5, 3, 10)
c.Parent = workspace

local d = a:Clone()
d.BrickColor = BrickColor.Yellow()
d.Position = Vector3.new(15, 3, -5)
d.Parent = workspace

-- graph stuff

type Vertex = {id: string, pos: Vector3, edges: {Edge}}
type Edge = {a: string, b: string}

-- map from an ID to a vertex object
local poles: {[string]: Vertex} = {}

local walls: {Edge} = {}

local function AddPole(id, pos)
	poles[id] = {id=id, pos=pos, edges={}}
end

local function AddEdge(a: string, b: string)
	local w = {a=a, b=b}
	table.insert(walls, w)
	table.insert(poles[a].edges, w)
	table.insert(poles[b].edges, w) 
end

-- visuals stuff

local THICKNESS = 3
local HEIGHT = 4

-- Given a vertex with two thick parts like this:
--[[
               \      /               
                \    /                
\                \  /                /
 \                \/                / 
  \               /\               /  
   \             /  \             /   
    \           /    \           /    
     \         /      \         /     
      \       - _    _ -       /      
       \        _ -- _        /       
        \  _ -          - _  /        
         -                  -         
]]
-- Calculates the amount each part would need to be shortened so that they meet like this instead:
--[[
               \      /               
                \    /                
\                \  /                /
 \             _ - - _              /      
  \        _ -          - _        /       
   \  _ -                    - _  /        
    -                            -         
]]
local function ComputeCornerInset(v: Vertex)
	assert(#v.edges == 2)

	local e1 = v.edges[1]
	local e2 = v.edges[2]

	-- find the two vertices that aren't this one
	local p1 = poles[e1.a]
	if p1.id == v.id then p1 = poles[e1.b] end

	local p2 = poles[e2.a]
	if p2.id == v.id then p2 = poles[e2.b] end
	
	local v1 = (p1.pos - v.pos).Unit
	local v2 = (p2.pos - v.pos).Unit

	local bisect = (v1 + v2).Unit
	
	local dot = v1:Dot(bisect)
	
	local h = THICKNESS / (2 * math.sqrt(1 - dot*dot))
	local inset = h * dot

	return inset, bisect
end

-- hacky nonsense so we can easily update parts every render frame,
-- normally you'd wrap all this in some kind of graph object that handles
-- its parts or do something more clever

local model = Instance.new("Model")
model.Name = "Walls"
model.Parent = workspace

local w = Instance.new("WedgePart")
w.Anchored = true
w.CanCollide = false
w.Locked = true
local wi = 1
local wedges = {}
for i = 1, 8 do wedges[i] = w:Clone() wedges[i].BrickColor = BrickColor.Random() wedges[i].Parent = model end

local p = Instance.new("Part")
p.Anchored = true
p.CanCollide = false
p.Locked = true
local pi = 1
local parts = {}
for i = 1, 4 do parts[i] = p:Clone() parts[i].BrickColor = BrickColor.Random() parts[i].Parent = model end

-- end of hacky nonsense

-- Draws a single wall, and possibly a single wedge on the end of it
local function DrawWall(wall: Edge)
	local a, b = poles[wall.a], poles[wall.b]

	local insetA = 0
	local insetB = 0

	local bisectA = nil
	local bisectB = nil

	if #a.edges == 2 then
		insetA, bisectA = ComputeCornerInset(a)
		local back = b.pos - a.pos
		local right = bisectA:Cross(back)
		local cf = CFrame.fromMatrix(a.pos, right, back:Cross(right), back)

		local w = wedges[wi]
		wi = wi % #wedges + 1
		w.Size = Vector3.new(HEIGHT, THICKNESS, insetA * 2)
		w.CFrame = cf
	end

	if #b.edges == 2 then
		insetB, bisectB = ComputeCornerInset(b)
		local back = a.pos - b.pos
		local right = bisectB:Cross(back)
		local cf = CFrame.fromMatrix(b.pos, right, back:Cross(right), back)

		local w = wedges[wi]
		wi = wi % #wedges + 1
		w.Size = Vector3.new(HEIGHT, THICKNESS, insetB * 2)
		w.CFrame = cf
	end
	
	local back = a.pos - b.pos
	
	-- TODO pick a better up vector for the wall piece, maybe slerp between the two bisectors?
	local up: Vector3 = nil
	
	if bisectA then
		up = bisectA:Cross(back)
	elseif bisectB then
		up = bisectB:Cross(back)
	else
		up = Vector3.yAxis
	end
	
	if up.Y < 0 then up = -up end
	
	local right = up:Cross(back)
	
	local dist = back.Magnitude
	
	local pos = a.pos - back.Unit * (dist + insetA - insetB)/2

	local cf = CFrame.fromMatrix(pos, right, up, back) 

	local p = parts[pi]
	pi = pi % #parts + 1
	p.Size = Vector3.new(THICKNESS, HEIGHT, dist-insetA-insetB)
	p.CFrame = cf
end

AddPole("a", a.Position)
AddPole("b", b.Position)
AddPole("c", c.Position)
AddPole("d", d.Position)

AddEdge("a", "b")
AddEdge("c", "b")
AddEdge("c", "d")
AddEdge("d", "a")

game:GetService("RunService"):BindToRenderStep("gizdraw", 100, function()
	poles["a"].pos = a.Position
	poles["b"].pos = b.Position
	poles["c"].pos = c.Position
	poles["d"].pos = d.Position

	for _, wall in pairs(walls) do
		DrawWall(wall)
	end
end)

Again, that wasn’t really what you asked, but I needed to write this down somewhere :slight_smile:

4 Likes

This is amazing, thank you so much!

Seems to be the opposite actually, negative is an inner wall, positive is an outer wall

image
Outputs
-1.9999909400939941
whereas


outputs 1.9999909400939941. Regardless, it’s reliable, any configuration of look:Dot(between) returns negative for inner, positive for outer.

local poles = workspace.poles

local a,b,c = poles.p1,poles.p2,poles.p3

local ba = b.Position - a.Position
local bc = b.Position - c.Position
local between = ba + bc

local chosen = (game:GetService('Selection'):Get()[1] :: BasePart).CFrame.LookVector
local result = chosen:Dot(between)

print(result)

That’s alright, you’ve done more than enough already, I’ll read up on your script and go from there. I appreciate everything you’ve done thus far, thank you so much!

Actually I just ran into an issue, if I switch a and c, it causes the sign to be the opposite of what it was before, how do I know what point to specify as a and which to specify as c?

Try swapping these around. That would explain why your dot product is negative for the inner walls, too.

local ba = a.Position - b.Position
local bc = c.Position - b.Position
1 Like

Disregard this post actually, it was because I forgot to move the pole :man_facepalming:. Switching c/a with b worked with the inversed signs though. Thanks!