i have attachments placed like this
each circle represents the attachments i want grouped together, although im not sure how i would get attachments like this
i have attachments placed like this
Do you only care about cardinal directions? Or does it have to be any direction?
well, id guess any direction because some may be diagonal.
If three points A, B and C are exactly on the same line, the length (magnitude) of the cross product of vector from A to B and vector from A to C is zero because the vectors are parallel. So by using two points that are known to be on a line, you can calculate whether some other point is on the same line. I haven’t tested this code.
local maxCrossProductMagnitude = 0.1 -- this can be changed
local attachments = -- table containing all attachments
local lines = {}
local function removeLinesContainingSubsetOfAttachmentsOfThisLine(line)
local numberOfRemovedLines = 0
for indexOfAnotherLine = #lines, 1, -1 do
local anotherLine = lines[indexOfAnotherLine]
if anotherLine == line then
continue
end
local isSubsetOfGivenLine = true
for _, attachment in anotherLine do
if table.find(line, attachment) == nil then
isSubsetOfGivenLine = false
break
end
end
if isSubsetOfGivenLine then
table.remove(lines, indexOfAnotherLine)
numberOfRemovedLines += 1
end
end
return numberOfRemovedLines
end
-- Two points always have a line in which they both are so this first loop creates a "line" (attachment list) for each pair of attachments.
for i, attachment in attachments do
for j = i + 1, #attachments do
local anotherattachment = attachments[j]
table.insert(lines, {attachment, anotherAttachment})
end
end
local i = #lines
while i > 0 do
local line = lines[i]
local oneDirectionVectorOfTheLine = line[2].WorldPosition - line[1].WorldPosition
for _, attachment in attachments do
if table.find(line, attachment) ~= nil then
continue
end
local vectorToThisAttachment = attachment.WorldPosition - line[1].WorldPosition
local isInLine = oneDirectionVectorOfTheLine:Cross(vectorToThisAttachment).Magnitude <= maxCrossProductMagnitude
if isInLine then
table.insert(line, attachment)
end
end
local howManyRemoved = removeLinesContainingSubsetOfAttachmentsOfThisLine(line)
i = i - 1 - howManyRemoved
end
In your case, you should get your desired groups of attachments by removing the groups that only contain two attachments. This can be done by running the code below after the code above.
for i = #lines, 1, -1 do
local line = lines[i]
if #line == 2 then
table.remove(lines, i)
end
end
This seems like it would work but for the situation im trying to use this in, i do not have 2 points, only 1 random attachment that is somewhere along the line.
I do have code from my attempt at getting the direction of the line although i came to make this thread as i didnt think it was well made
local Attachment_PathSection = Attachment.Parent
local AttachmentsTable = {Attachment}
local ChosenOrientation1 = nil
local ChosenOrientation2 = nil
local psPos = Attachment_PathSection.Size
for i = 1, 4 do
if not ChosenOrientation1 then
local RightPosition = Attachment.WorldCFrame + ((Attachment.WorldCFrame * CFrame.fromOrientation(0,rad(90 * i), 0)).LookVector * Vector3.new(psPos.X, psPos.Y, psPos.Z))
for _AttachmentNumber, _Attachment in ipairs(WallAttachTable) do
if Wall_Delay_TimeNum >= Wall_Delay_Number*100 then task.wait(); Wall_Delay_TimeNum = 0 else Wall_Delay_TimeNum += 1 end
if (_Attachment.WorldPosition - RightPosition.Position).Magnitude < 0.01 then
if _Attachment ~= Attachment and not table.find(AttachmentsTable, _Attachment) then
if _Attachment.Parent ~= Attachment.Parent then
ChosenOrientation1 = CFrame.fromOrientation(0,rad(90 * i),0)
ChosenOrientation2 = CFrame.fromOrientation(0,rad(-(90 * i)),0)
end
end
end
end
end
end
(section from a bigger script)
For any two attachments, there is a line in which both these attachments are although that may not be one of the lines you are looking for because the line can be between walls and you want attachments that are on a line that goes along a wall.
So for any attachment pair, it’s possible to calculate which other attachments are on the same line.
If it’s possible that there are three attachments that are on the same line but should not be grouped (the line does not go along a wall), then my code will not give you a desired result. Otherwise, if the code works as expected, I think it should give the correct groups.
What is the problem with your current code? Does it not work or do you just want another way to do this?
(sorry for the late response, i had school)
well it works but im looking for another way to do it with less mess and without so many bugs
because currently with the code im having it the same attachments in multiple tables, not sure why. (this is on me)
but aswell as problems with overlapping attachments due to how my generation works
which is a issue because of what i will be doing with these line segements later.
^
As for that the only solution i can think of with how this is scripted currently is going all the way through each attachment again then checking for overlapping attachments and then determining which one matches the orientation of the starting attachment and just so on
all this feels like it can just be simplified alot.
Also theres a ton of if statements which doesnt look good
Try this. This time I’ve actually tested my code. In the main loop, it goes through the given attachments table which should contain all the attachments that should be divided into groups. Whenever the main loop finds an attachment that isn’t in a group yet, it creates a new group. The main loop will not continue to its next iteration until all the attachments that should be in this group are added to this group using the other loops. So on every main loop iteration, either 0 or 1 new group is added.
On every iteration of the repeat until loop, either 0 or 1 attachment is added to the new group created in the main loop. If no attachment is added, then the group is complete, the repeat until loop breaks and the main loop continues to its next iteration.
The for loops inside the repeat until loop are used to check if any attachment that is not in a group yet is in the correct direction and at the correct distance from any attachment that is in the new group. The correct direction and distance are calculated based on part orientation and part size. The wall direction in the code is a world direction vector of the part axis on which the size of the part is the greatest. If such an attachment is found, it is added to the new group and both for loops are broken resulting in a new iteration of the repeat until loop. If such an attachment is not found, then wasANewAttachmentAddedInIteration
will be false at the end of the repeat until loop iteration which will result in breaking the repeat until loop (which, as mentioned, means that the group is complete and the main loop continues).
The reason why the code repeatedly does the direction and distance check for every attachment that is not in a group until no attachment satisfies the conditions is the distance constraint. For example, let’s say we have three attachments a1, a2 and a3 on the same wall, a2 is located (WorldPosition) between a1 and a3, d is the distance between attachments of adjacent parts of the wall, and a1 is the first attachment in the group. If the code looped through the attachment table only once after initializing the group with a1 and a3 came before a2 in the loop, a3 would not be added to the group because its distance from a1 is 2d, although its distance from a2 is d which means that it should be added to the group.
local equalityMaxDifference = 0.1 -- this can be changed
local function areApproximatelyEqual(num1, num2)
return math.abs(num1 - num2) <= equalityMaxDifference
end
local function getWallDirectionAndAttachmentDistance(attachment: Attachment)
local part = attachment:FindFirstAncestorWhichIsA("BasePart")
local verticalAxisInLocalCoordinateSystem
if areApproximatelyEqual(part.CFrame.RightVector:Cross(Vector3.yAxis).Magnitude, 0) then
verticalAxisInLocalCoordinateSystem = "X"
elseif areApproximatelyEqual(part.CFrame.UpVector:Cross(Vector3.yAxis).Magnitude, 0) then
verticalAxisInLocalCoordinateSystem = "Y"
elseif areApproximatelyEqual(part.CFrame.LookVector:Cross(Vector3.yAxis).Magnitude, 0) then
verticalAxisInLocalCoordinateSystem = "Z"
else
error("Part orientation is incompatible for this.")
end
local sizesToCompare = {"X", "Y", "Z"}
table.remove(sizesToCompare, table.find(sizesToCompare, verticalAxisInLocalCoordinateSystem))
-- horAxis means horizontal axis in local coordinate system (local coordinate system axis that
-- is parallel to the world coordinate system xy-plane)
local horAxis1, horAxis2 = sizesToCompare[1], sizesToCompare[2]
local size1, size2 = part.Size[horAxis1], part.Size[horAxis2]
local biggestSizeAxisVector: Vector3, biggestSize: number
if size1 > size2 then
biggestSizeAxisVector = Vector3.FromAxis(Enum.Axis[horAxis1])
biggestSize = size1
else
biggestSizeAxisVector = Vector3.FromAxis(Enum.Axis[horAxis2])
biggestSize = size2
end
local biggestSizeAxisWorldSpaceVector = part.CFrame.Rotation * biggestSizeAxisVector
return biggestSizeAxisWorldSpaceVector, biggestSize
end
local function getAttachmentGroups(attachments: {Attachment}): {{Attachment}}
local groups = {}
local hasBeenAddedToAGroup = {}
for _, attachment in attachments do
if hasBeenAddedToAGroup[attachment] then
continue
end
local newGroup = {attachment}
table.insert(groups, newGroup)
hasBeenAddedToAGroup[attachment] = true
local wallDirection, wallAttachmentDistance = getWallDirectionAndAttachmentDistance(attachment)
repeat
local wasANewAttachmentAddedInIteration = false
for _, attachmentInGroup in newGroup do
for _, otherAttachment in attachments do
if hasBeenAddedToAGroup[otherAttachment] then
continue
end
local otherAttachmentWallDirection, otherAttachmentWallAttachmentDistance = getWallDirectionAndAttachmentDistance(otherAttachment)
local areWallDirectionsSame = areApproximatelyEqual(otherAttachmentWallDirection:Cross(wallDirection).Magnitude, 0)
if not areWallDirectionsSame then
continue
end
local isInLine = areApproximatelyEqual(wallDirection:Cross(otherAttachment.WorldPosition - attachmentInGroup.WorldPosition).Magnitude, 0)
local isAtCorrectDistance = areApproximatelyEqual((otherAttachment.WorldPosition - attachmentInGroup.WorldPosition).Magnitude, wallAttachmentDistance / 2 + otherAttachmentWallAttachmentDistance / 2)
if isInLine and isAtCorrectDistance then
table.insert(newGroup, otherAttachment)
hasBeenAddedToAGroup[otherAttachment] = true
wasANewAttachmentAddedInIteration = true
break
end
end
if wasANewAttachmentAddedInIteration then
-- This break and the break above together start a new iteration of the repeat until loop.
break
end
end
until wasANewAttachmentAddedInIteration == false
end
return groups
end
Here’s the code that I used for testing this. workspace.AttachmentGroupingTest
is a folder that contains the parts I used for testing, and each part has an attachment as its child.
local attachments: {Attachment} = {}
for _, descendant in workspace.AttachmentGroupingTest:GetDescendants() do
if descendant:IsA("Attachment") then
table.insert(attachments, descendant)
end
end
local groups = getAttachmentGroups(attachments)
for groupIndex, group in groups do
for _, attachment in group do
attachment.Name = tostring(groupIndex)
local part = attachment:FindFirstAncestorWhichIsA("BasePart")
part.Name = tostring(groupIndex)
local colorBrightness = groupIndex / #groups
part.Color = Color3.new(colorBrightness, colorBrightness, colorBrightness)
end
end
Hey!, Sorry for getting back so late, i just kinda was lazy and didnt want to code for a bit.
I wont be going through the entire information providing responses to each bit as what you said pretty much all makes sense and lines up with the code, although i do have a question;
As for how cross works here (and in general), i read through this post but i still dont really understand how it works here
if areApproximatelyEqual(part.CFrame.RightVector:Cross(Vector3.yAxis).Magnitude, 0) then
verticalAxisInLocalCoordinateSystem = "X"
elseif areApproximatelyEqual(part.CFrame.UpVector:Cross(Vector3.yAxis).Magnitude, 0) then
verticalAxisInLocalCoordinateSystem = "Y"
elseif areApproximatelyEqual(part.CFrame.LookVector:Cross(Vector3.yAxis).Magnitude, 0) then
verticalAxisInLocalCoordinateSystem = "Z"
else
error("Part orientation is incompatible for this.")
end
(Lines 11-21)
and here
areApproximatelyEqual(otherAttachmentWallDirection:Cross(wallDirection).Magnitude, 0)
(Line 67)
On a unrelated note
How would it even be possible for it to error with incompatible part orientation?
Cross product
The tutorial you linked explains what cross product usually does. It usually gives a vector that is perpendicular to both of the given vectors. Usually, there’s exactly one line direction that is perpendicular to both vectors and the vector given by the cross product has one of the two opposite directions in which you can move in this line direction (which direction it is depends on the order of the vectors in the cross product).
What the tutorial didn’t mention is how it works when there’s an infinite number of directions that are perpendicular to both of the given vectors. When the vectors are parallel they are both perpendicular to a whole plane with a spesific orientation. There is an infinite number of directions you can move in on a plane. In this case, the cross product just gives a zero vector (which means the magnitude of the cross product is 0).
With a line direction I mean a pair of two opposite directions. The reason why I was talking about one line direction and one plane orientation instead of one line and one plane was because for any line direction or any plane orientation, there is an infinite number of parallel lines or planes with that direction or orientation.
In the case of coding, the attachment positions and the cross product calculations are not necessarily accurate so the cross product may not be exactly a zero vector. However, when the directions of the given vectors approach parallel directions, the length (magnitude) of the cross product approaches zero. So we can calculate whether the vectors are approximately parallel by calculating whether the length of their cross product is close to zero.
So, for example, if
areApproximatelyEqual(part.CFrame.RightVector:Cross(Vector3.yAxis).Magnitude, 0)
is true, then the RightVector of the part (which tells direction of the part’s x-axis in the world coordinate system) is parallel to a direction vector of the world y-axis in the world coordinate system. Because both Vector3.yAxis (== Vector3.new(0, 1, 0))
and CFrame.RightVector
are unit vectors this means that the RightVector
is approximately equal to either Vector3.new(0, 1, 0)
or Vector3.new(0, -1, 0)
. This means that the x-axis of the part (which is perpendicular to the left and right faces of the part) is vertical in the world coordinate system and thus verticalAxisInLocalCoordinateSystem
is set to "X"
.
Dot product as an alternative
As the tutorial mentioned, dot product can be used for finding how similar the directions of two vectors are. However, the tutorial didn’t mention that dot product will only work in measuring the similarity of vector directions when both vectors are unit vectors (or when dividing the dot product of vectors that are not necessarily unit vectors by the lengths of the vectors). The dot product equation:
dotp(a, b) = cos(a, b)|a||b|
In the equation, cos(a, b) is the cosine of the angle between vectors a and b and it is multiplied by the lengths of both vectors. If we divide the dot product by the lengths we get just the cosine which can be used to measure the similarity of the directions.
Instead of checking whether the length of the cross product of two vectors is close to zero, I could have checked whether the dot product of the unit vectors of the two vectors is close to either -1 or 1. Using cross product just felt like an easier option in this case (only one case to check).
Error
My code is designed for cases where one of the three axes of the part is parallel to the world y-axis (which means that one of its faces is parallel to the world xz-plane). If, for example, the part was rotated 45 degrees around the world x-axis, this condition wouldn’t be satisfied and my code would give that error.
Having one of its axes be parallel to the world y-axis means that the other two axes are parallel to the world xz-plane. However, they are not required to be parallel to the world x and z axes so the wall part can be rotated around the world y-axis.
Ok reading through this all,
So what cross product is doing here
(part.CFrame.RightVector:Cross(Vector3.yAxis).Magnitude
)
is going to return close to 0 if its parallel to the axis, and close 1/-1 if its perpendicular?
if so im guessing the getWallDirectionAndAttachmentDistance
function is just getting the Direction and size of wall of the biggest axis that isnt up?
and then this section
local otherAttachmentWallDirection, otherAttachmentWallAttachmentDistance = getWallDirectionAndAttachmentDistance(otherAttachment)
local areWallDirectionsSame = areApproximatelyEqual(otherAttachmentWallDirection:Cross(wallDirection).Magnitude, 0)
if not areWallDirectionsSame then
continue
end
Checks if they are on the same line as it will return 0 if both the directions are parallel to each other?
Also for this does it matter which way they are cross vector’d as you did say
(this i didnt really understand that great)
moving on from that
im now getting a little more lost as
local isInLine = areApproximatelyEqual(wallDirection:Cross(otherAttachment.WorldPosition - attachmentInGroup.WorldPosition).Magnitude, 0)
Is isInLine similar to areWallDirectionsSame because im not seeing that much of a difference right now
Ah, i see now. this shouldn’t be an issue for me, mainly because even working with stuff rotated oddly on all 3 axis’s is very annoying
Again, sorry taking a entire day to get back to you, its the same excuse before.
(edit)
also just wanted to point out you went absolutely wild on that post, 9 edits is the most ive seen ever im pretty sure
Yes, cross product length will be close to zero if they are parallel. However, the -1 and 1 aren’t related to cross product. They are what dot product would give for parallel unit vectors (1 in case of same direction, -1 in case of opposite directions). I didn’t use dot product in my code so it was kind of unrelated. I just noticed that the tutorial you linked told that dot product can be used for measuring similarity of vector directions so I decided to mention how it could have been used in my code as an alternative to cross product.
And yes, you understood correctly what getWallDirectionAndAttachmentDistance
returns.
areWallDirectionsSame
being true means that the attachments are either on the same wall or on parallel walls. So if attachmentInGroup
is in the group marked with black in your original post, areWallDirectionsSame
will be true if otherAttachment
is either in the black group or the blue group.
The reason why I added the areWallDirectionsSame
check is that otherwise, in the following picture, the attachment of the red part and the attachment of the blue part could be considered to be on the same wall if I only took in consideration whether the distance between attachmentInGroup
and otherAttachment
is correct and the vector between them is parallel to the wall direction calculated based on the part attachmentInGroup
is attached to. In the picture, if the attachment of the red part is attachmentInGroup
and the attachment of the blue part is otherAttachment
, they’d be considered to be on the same wall if areWallDirectionsSame
wasn’t checked. I don’t know if your generation code can result in a situation in which this matters, though, so this check may be unnecessary.
The order of the vectors only affects the direction of the cross product. In this case, we are only interested in the length of the cross product so in this case, the order doesn’t matter.
Is isInLine similar to areWallDirectionsSame because im not seeing that much of a difference right now
isInLine
is true when the direction of the vector from attachmentInGroup
to otherAttachment
is parallel to the wall direction calculated based on the part attachmentInGroup
is attached to. If isInLine
is true, otherAttachment
is on the line that goes through attachmentInGroup
and is parallel to wallDirection
. isInLine
would be true if attachmentInGroup
was the attachment of the red part and otherAttachment
the attachment of the blue part in the picture above if isInLine
was calculated in that situation. However, areWallDirectionsSame
would be false in this case so the inner for loop would continue to the next iteration without even calculating isInLine
.
Ah, I think I finally get the entire code, thanks for providing the information on what they do!
Before i go i do have to say that
Honestly what you’ve done is amazing. Not many people would go this far to help and provide answers for people on devforum, I truly appreciate what you’ve done and what you’ve helped me with.
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.