Im trying to get a rotated scaled cube to be placed inside another scaled cube (currently without rotation but the option would be nice).
I’ve got the following code which reads like it should work but the rotated cube is regularly outside the bounding cube, I rarely use do/while loops so maybe theres an issue there?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
public Transform boundingCube;
public Transform innerCube;
public Vector3 rotationAngle = new Vector3(0f,180f,0f);
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown("z"))
{
PositionInnerCube();
}
if (Input.GetKeyDown("x")) // test if in bounds
{
IsInsideLimits(innerCube, boundingCube);
}
}
void PositionInnerCube()
{
int tries = 0;
do
{
if (tries < 250)
{
innerCube.position = PointInsideBounds(boundingCube);
innerCube.rotation = Quaternion.Euler(new Vector3(Random.Range(-rotationAngle.x, rotationAngle.x), Random.Range(-rotationAngle.y, rotationAngle.y), Random.Range(-rotationAngle.z, rotationAngle.z)));
tries++;
//Debug.Log(tries);
}
else
{
return;
}
}
while (IsInsideLimits(innerCube, boundingCube) == false);
}
Vector3 PointInsideBounds(Transform t)
{
Vector3 rndPosWithin;
rndPosWithin = new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f));
rndPosWithin = t.TransformPoint(rndPosWithin * .5f);
return rndPosWithin;
}
public bool IsInsideLimits(Transform toTest, Transform limits)
{
Bounds innerCubeBounds = toTest.GetComponent<Renderer>().bounds;
Vector3 innerBoundsMin = toTest.TransformPoint(innerCubeBounds.min);
Vector3 innerBoundsMax = toTest.TransformPoint(innerCubeBounds.max);
Bounds limitsBounds = limits.GetComponent<Renderer>().bounds;
if (limitsBounds.Contains(innerBoundsMin) && limitsBounds.Contains(innerBoundsMax))
{
//Debug.Log("isInside");
return true; // obBounds Min and Max IS inside the Limits
}
else
{
//Debug.Log("isOutside");
return false; // obBounds Min and Max IS NOT inside the limits
}
}
}
This really feels like you want to step through this with the debugger. You could probably do with less methods. Not every operation must be extracted into a method. If it hangs, that likely means your loop never breaks, so IsInsideLimits always returns false. Might be that Collider.bounds is not scaled?
A few things you might find interesting
UnityEngine.Random.rotation
Physics.CheckBox()
method parameters should start with a lower-case character
Ah, and if you plan on generalizing this beyond cubes, you might end up working with raycasts. For this,
Physics.queriesHitBackfaces is an interesting property that is easily missed. But be warned, checking general meshes for overlap can get tricky.
That’s an interesting puzzle. I think you should not start with the random position but with the rotation. That will determine the area where it is valid to place the cube. I could not resist to try this.
void PositionInnerCube()
{
// Random rotation
// We mean this to be a rotation in outer cube local space but
// we'll temporarily store it as the global rotation to easily
// determine its bounds in outer cube local space.
innerCube.rotation = Quaternion.Euler(new Vector3(Random.Range(-rotationAngle.x, rotationAngle.x), Random.Range(-rotationAngle.y, rotationAngle.y), Random.Range(-rotationAngle.z, rotationAngle.z)));
// Determine extents of the inner cube in outer cube local space.
// With the local rotation currently set as the global one, we can
// use the (current) global bounds for this and just need to apply the scale
Bounds bounds = innerCube.GetComponent<Collider>().bounds;
Vector3 localInnerCubeExtents = new Vector3(bounds.extents.x / boundingCube.lossyScale.x, bounds.extents.y / boundingCube.lossyScale.y, bounds.extents.z / boundingCube.lossyScale.z);
// Now we determine the random position in outer cube local space.
float maxX = 0.5f - localInnerCubeExtents.x;
float maxY = 0.5f - localInnerCubeExtents.y;
float maxZ = 0.5f - localInnerCubeExtents.z;
Vector3 randomLocalPosition = new Vector3(Random.Range(-maxX, maxX), Random.Range(-maxY, maxY), Random.Range(-maxZ, maxZ));
// And we're done. All that is left is to set the rotation and position of the inner cube in global space
innerCube.rotation = boundingCube.rotation * innerCube.rotation;
innerCube.position = boundingCube.transform.TransformPoint(randomLocalPosition);
}
When testing this I also realized that your IsInsideLimits test may return false negatives. An inner cube might well fit into the outer cube even when its bounds may not.
Sorry to bring this thread back up but a really weird issue when I use the bounds of multiple children.
Attached is a .unitypackage which shows whats happening.
I’ve simplified the code to disregard rotations but it seems as though the bounding box calculation is always working on the previous run-through’s innerCube’s scale not the current innerCube’s scale.
The only thing I can think of to explain it is that transform.localScale doesn’t get applied immediately instead at the end of the script but I can’t find any other instances of this behaviour.
Here’s the simplified code:- At the moment I’m just placing the inner cube and its children at the minimum point it could fit in the outer bounding cube @Cannist maybe you could shed some light on whats happening?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test2 : MonoBehaviour
{
public Transform boundingCube;
public Transform innerCube;
public Vector3 rotationAngle = new Vector3(0f,180f,0f);
Vector3 minWorldPoint;
Vector3 maxWorldPoint;
public Transform fakeGizmoSphere;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown("1"))
{
innerCube.localScale = new Vector3(Random.value * 5f, Random.value * 5f, Random.value * 5f);
SimplePositionInnerCubeTest(innerCube, boundingCube);
}
}
Bounds GetBoundsOfAllChildren(GameObject g)
{
Bounds b = new Bounds(g.transform.position, Vector3.zero);
foreach (Collider c in g.GetComponentsInChildren<Collider>()) {
b.Encapsulate(c.bounds);
}
return b;
}
void SimplePositionInnerCubeTest(Transform innerCube, Transform boundingCube)
{
minWorldPoint = boundingCube.GetComponent<Collider>().bounds.min + (GetBoundsOfAllChildren(innerCube.gameObject).extents);
maxWorldPoint = boundingCube.GetComponent<Collider>().bounds.max - (GetBoundsOfAllChildren(innerCube.gameObject).extents);
//Vector3 randomPos = new Vector3(Random.Range(minWorldPoint.x, maxWorldPoint.x),
// Random.Range(minWorldPoint.y, maxWorldPoint.y),
// Random.Range(minWorldPoint.z, maxWorldPoint.z));
innerCube.position = minWorldPoint;
fakeGizmoSphere.position = maxWorldPoint;
}
}
Indeed, the collider bounds seem to report old values immediately after changing the local scale. And delaying the calculation by a single frame after changing the scale does not help, either. I wonder if perhaps colliders are only updated in sync with the physics system… the existence of the function Physics.SyncTransforms might suggest so and could be a potential solution, though I did not try this.
Anyway, there is a better solution. I learned that you can also get the bounds from the renderer which means you do not even need colliders on your objects. This works for me:
Bounds GetBoundsOfAllChildren(GameObject g)
{
Bounds b = new Bounds(g.transform.position, Vector3.zero);
foreach (Renderer r in g.GetComponentsInChildren<Renderer>()) {
b.Encapsulate(r.bounds);
}
return b;
}