Search Unity

Question How to find centers of inner child of a quad (geometry guy needed)

Discussion in 'Scripting' started by MagicianArtemka, Apr 27, 2021.

  1. MagicianArtemka

    MagicianArtemka

    Joined:
    Jan 15, 2019
    Posts:
    46
    Hi, I have not very big experience in the game development sphere (around 4 years), but I have collided with this problem at least 2 times already. And every time I solved it with the stupidest method. Now I have collided with it 3rd time and I need to solve it in a smart way.

    Problem description:

    Imagine, that we have a plane (quad or rectangle) [img. 1]. We want to split it into 4 children of the same size. We know all about this plane - its dimensions and center position. So we easily calculated the child's plane size and ready to move on. What we need to do next - is to define where we want to place every child. And this is the task - "how to find child plane position in the smartest (and possibly the fastest way)?"

    square.png
    Img. 1

    All next info was based on the concept that we decide that we want to find centers of that small planes.

    The result we want to achieve

    upload_2021-4-27_20-45-8.png

    Solution 1 - Cavemen calculations

    The ugliest way to resolve this task. I used it before when I need to resolve this kind of problem. And it works pretty robustly, like stone, that was taped to the stick by our grandgrandgrandfather. But this solution is ugly. It will produce a tone of bugs. It hard-to-modify solution. And it will be even more complicated for 3D.

    The code of the solution

    Code (CSharp):
    1. public void Split ()
    2. {
    3.     Vector3 nodeBoundsExtents = NodeBounds.extents;
    4.     float nodeBoundsExtentsX = nodeBoundsExtents.x;
    5.     float nodeBoundsExtentsZ = nodeBoundsExtents.z;
    6.     Vector3 childBoundsSize = new Vector3(nodeBoundsExtentsX, 0, nodeBoundsExtentsZ);
    7.     Vector3 childBoundExtents = childBoundsSize / 2.0f;
    8.     Vector3 nodeCenterPosition = NodeBounds.center;
    9.  
    10.     Vector3[] childPositions =
    11.     {
    12.         new Vector3(nodeCenterPosition.x, 0, nodeCenterPosition.z) - childBoundExtents,
    13.         new Vector3(nodeCenterPosition.x, 0, nodeCenterPosition.z) + childBoundExtents,
    14.         new Vector3(nodeCenterPosition.x - childBoundExtents.x, 0, nodeCenterPosition.z + childBoundExtents.z),
    15.         new Vector3(nodeCenterPosition.x + childBoundExtents.x, 0, nodeCenterPosition.z - childBoundExtents.z)
    16.     };
    17.  
    18.     foreach (Vector3 childPosition in childPositions)
    19.     {
    20.         CreateChild(childPosition, childBoundsSize);
    21.     }
    22. }
    23.  
    24. private void CreateChild (Vector3 centerOfTheNewChild, Vector3 childBoundsSize)
    25. {
    26.     GameObject child = new GameObject();
    27.     BoxCollider boxTest = child.AddComponent<BoxCollider>();
    28.     boxTest.size = childBoundsSize;
    29.     child.transform.position = centerOfTheNewChild;
    30. }
    This solution is based on one action - we just get the center of the big plane and shift it with offset with size of the small quad in different directions.

    Solution 2 - When you try to impress your crush with your math skills

    This solution is a bit better than the first, but it has a few cons:
    • It doesn't intuitive and it not stupid and simple
    • It can produce strange results when you will mess with the mirror vector.
    • It will become completely messy in 3D.
    The code of the solution

    Code (CSharp):
    1. public void Split ()
    2. {
    3.     Vector3 nodeBoundsExtents = NodeBounds.extents;
    4.     float nodeBoundsExtentsX = nodeBoundsExtents.x;
    5.     float nodeBoundsExtentsZ = nodeBoundsExtents.z;
    6.  
    7.     Vector3 childBoundsSize = new Vector3(nodeBoundsExtentsX, 0, nodeBoundsExtentsZ);
    8.     Vector3 childBoundExtents = childBoundsSize / 2.0f;
    9.     Vector3 nodeCenterPosition = NodeBounds.center;
    10.  
    11.     Vector3 firstQuadCenter = new Vector3(nodeCenterPosition.x, 0, nodeCenterPosition.z) - childBoundExtents;
    12.  
    13.     Vector3[] childPositions =
    14.     {
    15.         firstQuadCenter,
    16.         firstQuadCenter * -1,
    17.         Vector3.Reflect(firstQuadCenter, Vector3.right),
    18.         Vector3.Reflect(firstQuadCenter, Vector3.forward)
    19.     };
    20.  
    21.     foreach (Vector3 childPosition in childPositions)
    22.     {
    23.         CreateChild(childPosition, childBoundsSize);
    24.     }
    25. }
    26.  
    27. private void CreateChild (Vector3 centerOfTheNewChild, Vector3 childBoundsSize)
    28. {
    29.     GameObject child = new GameObject();
    30.     BoxCollider boxTest = child.AddComponent<BoxCollider>();
    31.     boxTest.size = childBoundsSize;
    32.     child.transform.position = centerOfTheNewChild;
    33. }
    Looks smart, doesn't it? We used magical Vector3.Reflect() and g̶e̶t̶ ̶b̶u̶n̶n̶y̶ ̶o̶u̶t̶ ̶f̶r̶o̶m̶ ̶a̶ ̶h̶a̶t̶ made our code a bit smaller. You can show this code to your crash and she/he will probably let you hug her/him (joke, programmers with girlfriend - it's a myth).
    But, what if I want to mirror my start cube by diagonal, but opposite to firstQuadCenter * -1? And I'm sure this will look messy for 3D. However, this code is still pretty robust for 2D. I think this solution is better than the first one, but not the "best practice".

    Solution 3 - Dizzy rotation

    This solution came to me when I remembered my math lessons about sines and cosines. We can rotate that stuff from 0 to 360 degrees and functions will change their sign.

    The code of the solution

    Code (CSharp):
    1. public void Split ()
    2. {
    3.     Vector3 nodeBoundsExtents = NodeBounds.extents;
    4.     float nodeBoundsExtentsX = nodeBoundsExtents.x;
    5.     float nodeBoundsExtentsZ = nodeBoundsExtents.z;
    6.    
    7.     Vector3 childBoundsSize = new Vector3(nodeBoundsExtentsX, 0, nodeBoundsExtentsZ);
    8.     Vector3 childBoundExtents = childBoundsSize / 2.0f;
    9.     Vector3 nodeCenterPosition = NodeBounds.center;
    10.  
    11.     Vector3 firstQuadCenter = new Vector3(nodeCenterPosition.x, 0, nodeCenterPosition.z) - childBoundExtents;
    12.  
    13.     float[] rotateAngles =
    14.     {
    15.         0.0f,
    16.         90.0f,
    17.         180.0f,
    18.         270.0f
    19.     };
    20.  
    21.     Vector3 rotationDirection = firstQuadCenter - nodeCenterPosition;
    22.  
    23.     foreach (float rotateAngle in rotateAngles)
    24.     {
    25.         Vector3 rotatedDirection = Quaternion.Euler(new Vector3(0.0f, rotateAngle, 0.0f)) * rotationDirection;
    26.      
    27.         CreateChild(rotatedDirection + nodeCenterPosition, childBoundsSize);
    28.     }
    29. }
    30.  
    31. private void CreateChild (Vector3 centerOfTheNewChild, Vector3 childBoundsSize)
    32. {
    33.     GameObject child = new GameObject();
    34.     BoxCollider boxTest = child.AddComponent<BoxCollider>();
    35.     boxTest.size = childBoundsSize;
    36.     child.transform.position = centerOfTheNewChild;
    37. }
    Smart? - Maybe. Simple? - not really. I believe this is the most uncomfortable and complicated-to-understand solution. Without any comment or image, it is hard to understand why we need to rotate something and where we do this rotation. I don't want to have this code, not in this form at least.

    The algorithm of this solution is quite simple - we just rotate the first small quad's center around the big quad's center clockwise (also we can do counterclockwise move). We rotate it with step 90. And here we can find a big disadvantage of this solution - it can be used effectively only for quad, not for the rectangle (every rectangle will have other rotation step value).

    Conclusion

    So, that's all from my side. I have no more solutions and can't imagine anything new for now. I'm pretty sure that there is a formula or two, that will resolve this geometry problem with 2 lines of robust code.
    I will be very happy if you will share your solutions or comments for my and we will find the best solution sooner or later. Or a smart guy will glance on this thread and just say "But we already have the function GameObject.SplitQuadOn4Parts(), check the documentation".
     
  2. MagicianArtemka

    MagicianArtemka

    Joined:
    Jan 15, 2019
    Posts:
    46
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Your problem seems to be getting something in global space.

    Thing is Transform has a method for this:
    https://docs.unity3d.com/ScriptReference/Transform.InverseTransformPoint.html

    This will take a point relative to some Transform (as its child) and give you a global position (something you can set to any 'transform.position').

    If you don't have a Transform, you could create a Matrix4x4, invert it, and multiply your vector by it:
    https://docs.unity3d.com/ScriptReference/Matrix4x4.MultiplyPoint.html
    Code (csharp):
    1. var globalPos = Matrix4x4.TRS(somePosition, someRotation, Vector3.one).inverse.MultiplyPoint(localPosition);
    Alternatively... you could just create the children AS children of the GameObject you're dividing.
    Code (csharp):
    1. var go =- new GameObject("child");
    2. go.transform.parent = parentquad.transform;
    3. go.transform.localPosition = localPos;
    4. go.transform.localRotation = Quaternion.identity;
    5. go.transform.localScale = Vector3.one;
    ...

    Note I don't exactly know your relative transformations of things like 'NodeBounds' since you don't actually share all the relevant code. But I assure you if you play around with this you'll figure out how it behaves and get it working.
     
    fazoll likes this.