// <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++; } } } }