0
votes

I'm currently trying to snap objects (cubes, pyramids, torus) together. I assigned each face of the objects bounding box a "snap allowed" variable and on collision try to find the closest two sides to each other. I then move the main object to the collision side and rotate it accordingly.

The code is as follows:

private static Vector3[] sides = {
    Vector3.up,
    Vector3.forward,
    Vector3.right,
    Vector3.down,
    Vector3.back,
    Vector3.left
};

private void Snap() {
    if (!snappedObject)
        return;

    if (lastSnapping > LeftController.GetLastSnappingToggle()) {
        //we dont calculate new sides, only use the old ones
        //but we have to check whether we are now further away 
        float dis = Vector3.Distance(GetWorldPositionFace(this.gameObject, lastSelf),
                                     GetWorldPositionFace(snappedObject, lastOther));
        float max = Mathf.Max(Mathf.Abs(size.x), Mathf.Max(Mathf.Abs(size.y), Mathf.Abs(size.z)));
        if (dis > max)
            return;

        ApplyToOther(lastSelf, lastOther, snappedObject);
    } else {
        //we need to find both new closest sides
        MeshSnapping other = snappedObject.GetComponent<MeshSnapping>();
        float otherDis = float.MaxValue;
        int otherSide = -1;
        //find the closest side from the other object
        for (int i = 0; i < NUM_SIDES; i++) {
            float dis = Vector3.Distance(transform.position, GetWorldPositionFace(snappedObject, i));
            if (dis < otherDis && other.sidesAllowed[i]) {
                otherDis = dis;
                otherSide = i;
            }
        }

        //find the closest side of our object
        float selfDis = float.MaxValue;
        int selfSide = 0;
        for (int i = 0; i < NUM_SIDES; i++) {
            float dis = Vector3.Distance(GetWorldPositionFace(this.gameObject, i),
                                            GetWorldPositionFace(snappedObject, otherSide));
            if (dis < selfDis && sidesAllowed[i]) {
                selfDis = dis;
                selfSide = i;
            }
        }


        //are we to far away or at a prohibited side?
        float max = Mathf.Max(Mathf.Abs(size.x), Mathf.Max(Mathf.Abs(size.y), Mathf.Abs(size.z)));
        if (selfDis > max)
            return;

        ApplyToOther(selfSide, otherSide, snappedObject);
        //save the sides for next iteration
        lastSelf = selfSide;
        lastOther = otherSide;
    }

    lastSnapping = Time.time;
}


private void OnCollisionEnter(Collision collision) {
    snappedObject = collision.gameObject;
}

private void OnCollisionExit(Collision collision) {
    snappedObject = null;
}

private Vector3 GetWorldPositionFace(GameObject other, int i) {
    //get the side in local coordinates, scaled to size
    Vector3 otherLocalSize = other.transform.localScale;
    Vector3 otherSidePoint = new Vector3(otherLocalSize.x * sides[i].x, otherLocalSize.y * sides[i].y, otherLocalSize.z * sides[i].z) / 2f;
    //rotate it according to world position
    Vector3 dir = (other.transform.rotation * otherSidePoint);
    //actually move it to world position
    Vector3 center = other.transform.position + dir;
    return center;
}


private void ApplyToOther(int selfI, int otherI, GameObject other) {
    //first get the midpoint of face of other go
    Vector3 edge = GetWorldPositionFace(other, otherI);
    Vector3 dir = edge - other.transform.position;

    RotateSides(selfI, otherI, dir);

    selfI = (selfI + NUM_SIDES / 2) % NUM_SIDES;

    //get midpoint of face of self go
    edge += GetWorldPositionFace(this.gameObject, selfI) - transform.position;
    //now move towards the combination
    transform.position = edge;
}

private void RotateSides(int selfI, int otherI, Vector3 dir) {
    //rotate self side towards this point
    switch (selfI) {
        case 0: transform.up = -dir; break;
        case 1: transform.forward = -dir; break;
        case 2: transform.right = -dir; break;
        case 3: transform.up = dir; break;
        case 4: transform.forward = dir; break;
        case 5: transform.right = dir; break;
    }

}

I can find every midpoint of the bounding box by transforming the direction vector, applying the objects current rotation and position to it (see GetWorldPositionFace() ). After finding the best combination, ApplyToOther() moves the objects to position and rotates it according to the selected face normals. So far so good, but the result is not aligned. As you can see, the front faces do not face in the same direction, i.e. I want to rotate the upper cube around the transform.up axis by this amount. This would be the result that I want.

But, if I add

float angle = Vector3.Angle(transform.forward, snappedObject.transform.forward);
transform.Rotate(transform.up, angle);

to the RotateSides() function, the result is this. The rotation axis is wrong.

Using

Quaternion.FromToRotation(transform.up, snappedObject.transform.up)

did not work either.

What did I miss? Thanks for your help!

1

1 Answers

0
votes

I figured out my problem. By setting the transform.forward and the transform.up seperately (e.g. with transform.rotate around axis), only one of them was correct. Using Quaternion.LookRotation() solves this.