Search code examples
c#unity-game-enginebounding-box

Unity: Relocating objects via bounding box


I need to move one object to another based on their bounding boxes. I am trying to "set" an object down on top of another, so that the bottom of its bounding box is perfectly on the top of the other's. Screenshot for additional clarity. The lower object is the target object, the higher one is the object to move.

I've been trying to use the extents, but can't seem to get the right setup here. I'm still quite new to this.

// Get the target object's mesh, find its top back edge (z = up)
Mesh target = GameObject.Find("Base Object").GetComponent<MeshFilter>().sharedMesh;
Vector3 targetTop = target.bounds.center + new Vector3(buildOptions.localPosition.x, 
    target.bounds.extents.y, target.bounds.extents.z);

// Get the moving object's mesh, find its bottom back edge (y = up)
GameObject part;
Mesh partMesh;
if (obj.transform.childCount > 0)
{
    part = obj.transform.GetChild(0).gameObject;
    partMesh = part.GetComponent<MeshFilter>().sharedMesh; 
}
else
{
    partMesh = obj.GetComponent<MeshFilter>().sharedMesh;
}

Vector3 partMeshBottom = partMesh.bounds.center + new Vector3(0, -partMesh.bounds.extents.y,
    partMesh.bounds.extents.z);

// Move the object, obj, to the target object based on bounding boxes
// In other words, set it down on top of the target
//obj.transform.localPosition = ???;

Is there a better method here? What are best practices for moving objects this way? Thanks for any help!

Additional information: The target object's elevation is its z, the object I'm moving has elevation as y. The target object is twice a child, the object I'm moving is not a child.


Solution

  • Upon further review, I've found a solution!

    My initial mistake was using the Mesh bounds instead of the Renderer bounds. The latter is the better option because it provides world space location rather than local space. This page of Unity's documentation was helpful (and silly me for not noticing earlier): https://docs.unity3d.com/ScriptReference/Renderer-bounds.html

    The next fix was with partMeshBottom; its location isn't needed, only its extents (multiplied by its scale). The only location that matters is the target's in world space, as the object only needs to move to the target's position.

    Lastly, the moving object is shifted by its extents to align it with the target's edge. It's important to make sure that shift is properly scaled if necessary.

    The result:

    GameObject target = GameObject.Find("Base Object");
    Renderer targetRenderer = target.GetComponent<Renderer>();
    Vector3 targetRendererTop = targetRenderer.bounds.center + new Vector3(buildOptions.localPosition.x, targetRenderer.bounds.extents.y, targetRenderer.bounds.extents.z);
    
    GameObject part;
    Renderer partRenderer;
    if (obj.transform.childCount > 0)
    {
        part = obj.transform.GetChild(0).gameObject;
        partRenderer = part.GetComponent<Renderer>(); 
    }
    else
    {
        partRenderer = obj.GetComponent<Renderer>();
    }
    Vector3 partRendererBottomOffset = new(0, partRenderer.bounds.extents.y * buildOptions.localScale.y, -partRenderer.bounds.extents.z * buildOptions.localScale.z);
    
    obj.transform.position = new Vector3(targetRendererTop.x, targetRendererTop.y + partRendererBottomOffset.y, targetRendererTop.z + partRendererBottomOffset.z);
    obj.transform.localRotation = Quaternion.Euler(buildOptions.localEulerAngles); ;
    obj.transform.localScale = buildOptions.localScale;
    

    Here is a screenshot of the final result. Hopefully this helps anyone that might've made similar mistakes as me or having trouble with a similar task. If anyone knows better methods/practices, please comment them! Thank you!