/// <summary> /// Align all transforms position with one Transform, or on mini/max values of boundaries /// </summary> /// <param name="referenceTransform"> /// A <see cref="Transform"/> which all other transforms will be aligned with, if alignType is "mean" /// </param> /// <param name="transformList"> /// The <see cref="Transform[]"/> of all objects to be aligned /// </param> /// <param name="axis"> /// The axis to align on : <see cref="Vector3.one"/> to align all axis, <see cref="Vector3.right"/> to align on the X axis, <see cref="Vector3.up"/> to align on the Y axis, <see cref="Vector3.forward"/> to align on the Z axis /// </param> /// <param name="alignType"> /// Witch type of alignement we do, from the <see cref="Landmark"/> enum /// </param> public static void AlignPosition(Transform referenceTransform, Transform[] transformList, Vector3 axis, Landmark alignType, ExtentsGetter.BoundType boundType) { //bool useBounds = (alignType != Landmark.distributed && alignType != Landmark.mean); // Get the position from the active selected object Vector3 markPosition = referenceTransform.position; // If alignment is not the mean one, search the min or max positioned object if (alignType == Landmark.minimum) { markPosition = ExtentsGetter.GetMinMarkPosition(referenceTransform.position, transformList, boundType); } else if (alignType == Landmark.maximum) { markPosition = ExtentsGetter.GetMaxMarkPosition(referenceTransform.position, transformList, boundType); } Vector3 activePosition = markPosition; foreach (Transform nextTransform in transformList) { Vector3 newPos; Vector3 delta = Vector3.zero; if (alignType == Landmark.maximum) { if (nextTransform.renderer) { delta = -nextTransform.renderer.bounds.extents; } else if (nextTransform.collider) { delta = -nextTransform.collider.bounds.extents; } } else if (alignType == Landmark.minimum) { if (nextTransform.renderer) { delta = nextTransform.renderer.bounds.extents; } else if (nextTransform.collider) { delta = nextTransform.collider.bounds.extents; } } // refers to axisList : None, X, Y, Z, All -> 0, 1, 2, 3, 4 newPos.x = (axis == Vector3.one || axis == Vector3.right) ? activePosition.x + delta.x : nextTransform.position.x; newPos.y = (axis == Vector3.one || axis == Vector3.up) ? activePosition.y + delta.y : nextTransform.position.y; newPos.z = (axis == Vector3.one || axis == Vector3.forward) ? activePosition.z + delta.z : nextTransform.position.z; nextTransform.position = newPos; } }
// <summary> /// Raycast down from the object bounds so it appears to have fallen on the colliders below (from the down to the top) /// Beware : due to approximation in Transform values, it's not 100% accurate /// </summary> /// <param name="fallingTransform"> /// The <see cref="Transform"/> of the object to be aligned /// </param> /// <param name="rotateToTerrainAngle"> /// <see cref="true"/> to rotate <see cref="Transform"/> to the normal from the raycast hit /// </param> public static void FallOnTerrain(Transform fallingTransform, bool rotateToTerrainAngle, ExtentsGetter.BoundType boundType) { Bounds activeObjectBounds = ExtentsGetter.GetHierarchyBounds(fallingTransform.gameObject, boundType); // TODO : rewrite so it use a bounding box to get objects inside and then the upper one, then use Collider.ClosestPointOnBounds to get the closest hit point // If we apply a rotation to the object when falling, make it fall as if it was upside Quaternion wasRotation = fallingTransform.rotation; if (rotateToTerrainAngle) { fallingTransform.rotation = Quaternion.identity; } float distance = System.Single.PositiveInfinity; bool hitSomething = false; // The radius is calculated from the bound size, taking the greatest value of width and depth float sphereRadius = Mathf.Max(activeObjectBounds.extents.x, activeObjectBounds.extents.z); // Set the default new position to the current position Vector3 newPosition = fallingTransform.position; Vector3 normal = fallingTransform.up; // New from 2.1 : the Raycast has been replaced by a SphereCastAll method so every single object in the sphere sweep returns a hit // SphereCast down to check all hits RaycastHit[] hits; // TIPS : the cast can return the current Transform so we must ensure to not take it into account hits = Physics.SphereCastAll(activeObjectBounds.center + Vector3.up * sphereRadius, sphereRadius, Vector3.down); if (hits.Length > 0) { for (int i = 0; i < hits.Length; i++) { // Somehow, the hits[i].distance is not equal to (raycast.origin - hits[i].point) ? float hitDistance = activeObjectBounds.center.y - hits[i].point.y; // v2.2 Fix : do not take into account any hit from inside the boundaries (use the y extents) if (!hits[i].collider.transform.Equals(fallingTransform) && hitDistance <distance && hitDistance> activeObjectBounds.extents.y) { hitSomething = true; distance = hitDistance; normal = hits[i].normal; } } } if (hitSomething) { if (distance > 0) { // Do not move the object if the distance is 0 or below if (rotateToTerrainAngle) { // Assume a well oriented mesh (Vector3.up means the up side of the mesh Quaternion rotation = Quaternion.FromToRotation(Vector3.up, normal); wasRotation = rotation; } // Move to new position : nearest hit distance - half size of the falling object newPosition.y = newPosition.y - distance + activeObjectBounds.extents.y; fallingTransform.position = newPosition; } fallingTransform.rotation = wasRotation; } }
/// <summary> /// Duplicate a Prefab or a GameObject in a grid /// </summary> /// <param name="prefabObject"> /// The <see cref="GameObject"/> prefab if there is one /// </param> /// <param name="referenceObject"> /// The <see cref="GameObject"/> to duplicate /// </param> /// <param name="transformList"> /// All <see cref="Transform[]"/> to be distributed /// </param> /// <param name="sortBy"> /// The axis which is used to sort the transform list, <see cref="SortAxis"/> /// </param> /// <param name="axis"> /// The axis to distribute the transforms on, using the same <see cref="Vector3"/> format /// </param> public static void DuplicateInGrid(GameObject prefabObject, GameObject referenceObject, Vector3 gridSize, Vector3 offsets, ExtentsGetter.BoundType type) { // Get the min and max marks Vector3 minMarkPosition = referenceObject.transform.position; // Total grid object instantiation count int gridCount = 0; // The steps are here to be sure that we start after the selected object, on the right axis int cellX = 0; int cellY = 0; int cellZ = 0; // Calculate the count of elements to create gridCount = Mathf.RoundToInt(Mathf.Max(gridSize.x, 1) * Mathf.Max(gridSize.y, 1) * Mathf.Max(gridSize.z, 1)); // Interval between parts (do not use prefab, as its extents are not calculated by Unity before instantiation) Vector3 distanceBetween = ExtentsGetter.GetHierarchyBounds(referenceObject, type).size; Vector3 newPos = minMarkPosition; // First step, to avoid creating in the same position of the original object if (gridSize.x > 1) { cellX = 1; } else if (gridSize.y > 1) { cellY = 1; } else { cellZ = 1; } // ... GameObject duplicatedObj = null; string baseName = ""; // Starts @ 1 to avoid duplicate the first object (@ the current location) for (int i = 1; i < gridCount; i++) { // Keep the link to the prefab, if there is one if (prefabObject) { duplicatedObj = PrefabUtility.InstantiatePrefab(prefabObject) as GameObject; baseName = prefabObject.name; } else { duplicatedObj = (GameObject)GameObject.Instantiate(referenceObject); baseName = referenceObject.name; } // Name the new object from its position in the grid duplicatedObj.name = baseName; if (gridSize.x > 0) { duplicatedObj.name += "_" + cellX; } if (gridSize.y > 0) { duplicatedObj.name += "_" + cellY; } if (gridSize.z > 0) { duplicatedObj.name += "_" + cellZ; } // Compute object position newPos.x = minMarkPosition.x + (distanceBetween.x + offsets.x) * cellX; newPos.y = minMarkPosition.y + (distanceBetween.y + offsets.y) * cellY; newPos.z = minMarkPosition.z + (distanceBetween.z + offsets.z) * cellZ; duplicatedObj.transform.position = newPos; // Update the grid indexes cellX++; if (cellX >= gridSize.x || gridSize.x == 0) { cellX = 0; cellY++; if (cellY >= gridSize.y || gridSize.y == 0) { cellY = 0; cellZ++; } } } }
/// <summary> /// Duplicate a Prefab or a GameObject in a grid /// </summary> /// <param name="prefabObject"> /// The <see cref="GameObject"/> prefab if there is one /// </param> /// <param name="referenceObject"> /// The <see cref="GameObject"/> to duplicate /// </param> /// <param name="transformList"> /// All <see cref="Transform[]"/> to be distributed /// </param> /// <param name="sortBy"> /// The axis which is used to sort the transform list, <see cref="SortAxis"/> /// </param> /// <param name="axis"> /// The axis to distribute the transforms on, using the same <see cref="Vector3"/> format /// </param> public static void DuplicateInGrid(GameObject prefabObject, GameObject referenceObject, Vector3 gridSize, Vector3 offsets, ExtentsGetter.BoundType type) { // Get the min and max marks Vector3 minMarkPosition = referenceObject.transform.position; // Total grid object instantiation count int gridCount = 0; // The steps are here to be sure that we start after the selected object, on the right axis int cellX = 0; int cellY = 0; int cellZ = 0; // Calculate the count of elements to create gridCount = Mathf.RoundToInt(Mathf.Max(gridSize.x, 1) * Mathf.Max(gridSize.y, 1) * Mathf.Max(gridSize.z, 1)); // Interval between parts (do not use prefab, as its extents are not calculated by Unity before instantiation) Vector3 distanceBetween = ExtentsGetter.GetHierarchyBounds(referenceObject, type).size; Vector3 newPos = minMarkPosition; // First step, to avoid creating in the same position of the original object if (gridSize.x > 1) cellX = 1; else if (gridSize.y > 1) cellY = 1; else cellZ = 1; // ... GameObject duplicatedObj = null; string baseName = ""; // Starts @ 1 to avoid duplicate the first object (@ the current location) for (int i = 1; i < gridCount; i++) { // Keep the link to the prefab, if there is one if (prefabObject) { duplicatedObj = PrefabUtility.InstantiatePrefab(prefabObject) as GameObject; baseName = prefabObject.name; } else { duplicatedObj = (GameObject) GameObject.Instantiate(referenceObject); baseName = referenceObject.name; } // Name the new object from its position in the grid duplicatedObj.name = baseName; if (gridSize.x > 0) duplicatedObj.name += "_" + cellX; if (gridSize.y > 0) duplicatedObj.name += "_" + cellY; if (gridSize.z > 0) duplicatedObj.name += "_" + cellZ; // Compute object position newPos.x = minMarkPosition.x + (distanceBetween.x + offsets.x) * cellX; newPos.y = minMarkPosition.y + (distanceBetween.y + offsets.y) * cellY; newPos.z = minMarkPosition.z + (distanceBetween.z + offsets.z) * cellZ; duplicatedObj.transform.position = newPos; // Update the grid indexes cellX++; if (cellX >= gridSize.x || gridSize.x == 0) { cellX = 0; cellY++; if (cellY >= gridSize.y || gridSize.y == 0) { cellY = 0; cellZ++; } } } }
// <summary> /// Raycast down from the object bounds so it appears to have fallen on the colliders below (from the down to the top) /// Beware : due to approximation in Transform values, it's not 100% accurate /// </summary> /// <param name="fallingTransform"> /// The <see cref="Transform"/> of the object to be aligned /// </param> /// <param name="rotateToTerrainAngle"> /// <see cref="true"/> to rotate <see cref="Transform"/> to the normal from the raycast hit /// </param> public static void FallOnTerrain(Transform fallingTransform, bool rotateToTerrainAngle, ExtentsGetter.BoundType boundType) { Bounds activeObjectBounds = ExtentsGetter.GetHierarchyBounds(fallingTransform.gameObject, boundType); // TODO : rewrite so it use a bounding box to get objects inside and then the upper one, then use Collider.ClosestPointOnBounds to get the closest hit point // If we apply a rotation to the object when falling, make it fall as if it was upside Quaternion wasRotation = fallingTransform.rotation; if (rotateToTerrainAngle) { fallingTransform.rotation = Quaternion.identity; } float distance = System.Single.PositiveInfinity; bool hitSomething = false; // The radius is calculated from the bound size, taking the greatest value of width and depth float sphereRadius = Mathf.Max(activeObjectBounds.extents.x, activeObjectBounds.extents.z); // Set the default new position to the current position Vector3 newPosition = fallingTransform.position; Vector3 normal = fallingTransform.up; // New from 2.1 : the Raycast has been replaced by a SphereCastAll method so every single object in the sphere sweep returns a hit // SphereCast down to check all hits RaycastHit[] hits; // TIPS : the cast can return the current Transform so we must ensure to not take it into account hits = Physics.SphereCastAll(activeObjectBounds.center + Vector3.up * sphereRadius, sphereRadius, Vector3.down); if (hits.Length > 0) { for (int i=0;i<hits.Length;i++) { // Somehow, the hits[i].distance is not equal to (raycast.origin - hits[i].point) ? float hitDistance = activeObjectBounds.center.y - hits[i].point.y; // v2.2 Fix : do not take into account any hit from inside the boundaries (use the y extents) if (!hits[i].collider.transform.Equals(fallingTransform) && hitDistance < distance && hitDistance > activeObjectBounds.extents.y) { hitSomething = true; distance = hitDistance; normal = hits[i].normal; } } } if (hitSomething) { if (distance > 0) { // Do not move the object if the distance is 0 or below if (rotateToTerrainAngle) { // Assume a well oriented mesh (Vector3.up means the up side of the mesh Quaternion rotation = Quaternion.FromToRotation(Vector3.up, normal); wasRotation = rotation; } // Move to new position : nearest hit distance - half size of the falling object newPosition.y = newPosition.y - distance + activeObjectBounds.extents.y; fallingTransform.position = newPosition; } fallingTransform.rotation = wasRotation; } }
// <summary> /// Raycast down from the object bounds so it appears to have fallen on the colliders below (from the down to the top) /// </summary> /// <param name="transformList"> /// The <see cref="Transform[]"/> of all objects to be aligned /// </param> /// <param name="rotateToTerrainAngle"> /// <see cref="true"/> to rotate <see cref="Transform"/> to the normal from the raycast hit /// </param> public static void FallOnTerrain(Transform[] transformList, bool rotateToTerrainAngle, ExtentsGetter.BoundType boundType) { // List of selected objects, to sort from the min position to the max position List<Transform> list = new List<Transform>(transformList); // Sort the selected objects from the down to the top, so the lowest 'falls' first list.Sort(DistributionManager.ByVector3PositionY); foreach (Transform fallingTransform in list) { FallOnTerrain(fallingTransform, rotateToTerrainAngle, boundType); } }