BITFALL/Assets/GSpawn - Level Designer/Scripts/Objects/ObjectToObjectSnap.cs

201 lines
9.8 KiB
C#
Raw Normal View History

2024-01-27 04:09:57 +08:00
#if UNITY_EDITOR
using UnityEngine;
using System.Collections.Generic;
namespace GSpawn
{
public static class ObjectToObjectSnap
{
private struct SnapData
{
public GameObject srcObject;
public GameObject destObject;
public Box3DFace srcSnapFace;
public Box3DFace destSnapFace;
public Vector3 snapSource;
public Vector3 snapDestination;
public float snapDistance;
public bool foundDestination;
public float minSnapDistance;
}
public enum SnapFailReson
{
None = 0,
NoDestinationFound
}
public struct SnapResult
{
private bool _success;
private SnapFailReson _failReason;
private Vector3 _snapPivot;
private Vector3 _snapDestination;
private float _snapDistance;
public bool success { get { return _success; } }
public SnapFailReson failReason { get { return _failReason; } }
public Vector3 snapPivot { get { return _snapPivot; } }
public Vector3 snapDestination { get { return _snapDestination; } }
public float snapDistance { get { return _snapDistance; } }
public SnapResult(SnapFailReson failReson)
{
_success = false;
_snapPivot = Vector3.zero;
_snapDestination = Vector3.zero;
_snapDistance = 0.0f;
_failReason = failReson;
}
public SnapResult(Vector3 snapPivot, Vector3 snapDestination, float snapDistance)
{
_success = true;
_snapPivot = snapPivot;
_snapDestination = snapDestination;
_snapDistance = snapDistance;
_failReason = SnapFailReson.None;
}
}
public struct Config
{
public int destinationLayers;
public float snapRadius;
}
private static ObjectOverlapFilter _overlapFilter = new ObjectOverlapFilter();
private static Box3DFace[] _allSnapFaces = Box3D.facesArrayCopy;
private static List<GameObject> _sourceObjectBuffer = new List<GameObject>();
private static List<GameObject> _nearbyObjectBuffer = new List<GameObject>();
private static List<Vector3> _srcSocketPtBuffer = new List<Vector3>();
private static List<Vector3> _destSocketPtBuffer = new List<Vector3>();
private static ObjectBounds.QueryConfig _nearbyGatherBoundsQConfig = new ObjectBounds.QueryConfig()
{
objectTypes = GameObjectType.Mesh | GameObjectType.Sprite,
includeInactive = false,
includeInvisible = false,
volumelessSize = Vector3.zero
};
public static int maxNumSourceObjects { get { return 100; } }
public static SnapResult snap(List<GameObject> parents, Config snapConfig)
{
var snapResult = calcSnapResult(parents, snapConfig);
if (snapResult.success)
{
Vector3 snapVector = snapResult.snapDestination - snapResult.snapPivot;
foreach (var parent in parents)
parent.transform.position += snapVector;
}
return snapResult;
}
public static SnapResult calcSnapResult(List<GameObject> parents, Config snapConfig)
{
GameObjectEx.getAllObjectsInHierarchies(parents, false, false, _sourceObjectBuffer);
SnapData snapData = new SnapData();
snapData.minSnapDistance = float.MaxValue;
// Setup the overlap filter to ignore the source objects and also any
// objects that were specified via the snap config.
_overlapFilter.layerMask = snapConfig.destinationLayers;
_overlapFilter.setIgnoredObjects(_sourceObjectBuffer);
OBB nearbyOverlapOBB = ObjectBounds.calcHierarchiesWorldOBB(parents, _nearbyGatherBoundsQConfig);
nearbyOverlapOBB.inflate(snapConfig.snapRadius * 2.0f);
PluginScene.instance.overlapBox(nearbyOverlapOBB, _overlapFilter, ObjectOverlapConfig.defaultConfig, _nearbyObjectBuffer);
if (_nearbyObjectBuffer.Count == 0) return new SnapResult(SnapFailReson.NoDestinationFound);
// Loop through all source objects
foreach (var sourceObject in _sourceObjectBuffer)
{
// Retrieve the snap data which is needed for snapping. If the data is not available, we skip.
var sourceSnapData = GameObjectDataDb.instance.getSnapData(sourceObject);
if (sourceSnapData == null) continue;
Transform srcTransform = sourceObject.transform;
snapData.srcObject = sourceObject;
// Loop through all snap faces in the current source object
foreach (var srcSnapFace in _allSnapFaces)
{
// Retrieve the area descriptor for the current snap face
var srcAreaDesc = sourceSnapData.getSnapSocketWorldAreaDesc(srcSnapFace, srcTransform);
if (srcAreaDesc.areaType == Box3DFaceAreaType.Invalid) continue;
// Calculate the snap socket OBB and socket face plane
var srcSnapSocketOBB = sourceSnapData.calcSnapSocketWorldOBB(srcSnapFace, srcTransform);
Plane srcSocketFacePlane = Box3D.calcFacePlane(srcSnapSocketOBB.center, srcSnapSocketOBB.size, srcSnapSocketOBB.rotation, srcSnapFace);
snapData.srcSnapFace = srcSnapFace;
// Extrude the socket face from center
OBB srcOverlapOBB = srcSnapSocketOBB.calcFaceExtrusionFromFaceCenter(srcSnapFace, snapConfig.snapRadius * 2.0f);
srcOverlapOBB.inflate(snapConfig.snapRadius * 2.0f, Box3D.getFaceAxisIndex(srcSnapFace));
// Loop through all nearby objects
foreach (var destObject in _nearbyObjectBuffer)
{
// Retrieve the snap data which is needed for snapping. If the data is not available, we skip.
var destSnapData = GameObjectDataDb.instance.getSnapData(destObject);
if (destSnapData == null) continue;
Transform destTransform = destObject.transform;
snapData.destObject = destObject;
// Loop through all snap faces in the current destination object
foreach (var destSnapFace in _allSnapFaces)
{
// Retrieve the area descriptor of the current snap face
var destAreaDesc = destSnapData.getSnapSocketWorldAreaDesc(destSnapFace, destTransform);
if (destAreaDesc.areaType == Box3DFaceAreaType.Invalid) continue;
// Extrude the destination face as we did for the source object
var destSnapSocketOBB = destSnapData.calcSnapSocketWorldOBB(destSnapFace, destTransform);
OBB destOverlapOBB = destSnapSocketOBB.calcFaceExtrusionFromFaceCenter(destSnapFace, snapConfig.snapRadius * 2.0f);
destOverlapOBB.inflate(snapConfig.snapRadius * 2.0f, Box3D.getFaceAxisIndex(destSnapFace));
// If the OBBs associated with the 2 extrusions (source and destination) do not overlap, it means
// the current destination snap face does not reside with the snap radius so we can ignore it.
if (!destOverlapOBB.intersectsOBB(srcOverlapOBB)) continue;
snapData.destSnapFace = destSnapFace;
Plane destSocketFacePlane = Box3D.calcFacePlane(destSnapSocketOBB.center, destSnapSocketOBB.size, destSnapSocketOBB.rotation, destSnapFace);
// Find the closest points between the source and destination points
// that belong to the corner points of the source and destination socket OBBs.
srcSnapSocketOBB.calcCorners(_srcSocketPtBuffer, false);
destSnapSocketOBB.calcCorners(_destSocketPtBuffer, false);
foreach (var srcPt in _srcSocketPtBuffer)
{
foreach (var destPt in _destSocketPtBuffer)
{
float d = (destPt - srcPt).magnitude;
if (d <= snapConfig.snapRadius && d < snapData.minSnapDistance)
{
snapData.foundDestination = true;
snapData.minSnapDistance = d;
snapData.snapDistance = d;
snapData.snapDestination = destPt;
snapData.snapSource = srcPt;
}
}
}
}
}
}
}
return snapData.foundDestination ? new SnapResult(snapData.snapSource, snapData.snapDestination, snapData.snapDistance) :
new SnapResult(SnapFailReson.NoDestinationFound);
}
}
}
#endif