BITFALL/Assets/GSpawn - Level Designer/Scripts/Level Design/Object Erase/ObjectEraseBrush3D.cs

439 lines
22 KiB
C#

#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System;
namespace GSpawn
{
public class ObjectEraseBrush3D : ObjectEraseTool
{
private enum SurfaceType
{
Invalid = 0,
Mesh,
TerrainMesh,
SphericalMesh,
UnityTerrain,
Grid
}
private struct Surface
{
public GameObject gameObject;
public SurfaceType surfaceType;
public Vector3 genericNormal;
public Vector3 pickNormal;
public Vector3 pickPoint;
public bool isLocked;
public bool requiresCircleProjection()
{
return surfaceType == SurfaceType.UnityTerrain ||
surfaceType == SurfaceType.TerrainMesh ||
surfaceType == SurfaceType.SphericalMesh;
}
public bool isTerrain()
{
return surfaceType == SurfaceType.UnityTerrain || surfaceType == SurfaceType.TerrainMesh;
}
public void reset()
{
gameObject = null;
surfaceType = SurfaceType.Invalid;
genericNormal = Vector3.zero;
pickNormal = Vector3.zero;
pickPoint = Vector3.zero;
isLocked = false;
}
}
private static readonly int _numCirclePoints = 150;
[NonSerialized]
private List<Vector3> _circlePoints = new List<Vector3>(_numCirclePoints);
[NonSerialized]
private List<Vector3> _vector3Buffer = new List<Vector3>();
[NonSerialized]
private List<GameObject> _boxObjectOverlapBuffer = new List<GameObject>();
[NonSerialized]
private List<GameObject> _overlappedObjects = new List<GameObject>();
[NonSerialized]
private ObjectRaycastConfig _surfacePickRaycastConfig = ObjectRaycastConfig.defaultConfig;
[NonSerialized]
private SceneRaycastFilter _surfacePickRaycastFiler = new SceneRaycastFilter();
[NonSerialized]
private ObjectOverlapFilter _objectOveralpFilter = new ObjectOverlapFilter();
[NonSerialized]
private ObjectOverlapConfig _objectOverlapConfig = ObjectOverlapConfig.defaultConfig;
[NonSerialized]
private ObjectBounds.QueryConfig _overlappedBoundsQConfig = new ObjectBounds.QueryConfig();
[NonSerialized]
private Surface _surface = new Surface();
public override ObjectEraseToolId toolId { get { return ObjectEraseToolId.Brush3D; } }
public ObjectEraseBrush3D()
{
_objectOveralpFilter.objectTypes = GameObjectType.Mesh | GameObjectType.Sprite;
_objectOveralpFilter.customFilter = (GameObject gameObject) => { return ObjectErase.instance.canEraseObject(gameObject); };
_overlappedBoundsQConfig.objectTypes = GameObjectType.Mesh | GameObjectType.Sprite;
}
protected override void doOnSceneGUI()
{
Event e = Event.current;
updateSurface();
updateCircleDrawPoints();
if (_surface.surfaceType == SurfaceType.Invalid) return;
if (e.type == EventType.MouseDown && e.button == 0) _surface.isLocked = true;
else if (e.type == EventType.MouseUp && e.button == 0)
{
_surface.isLocked = false;
detectOverlappedObjects();
eraseGameObjects(_overlappedObjects);
}
else if (e.type == EventType.MouseDrag && e.button == 0)
{
detectOverlappedObjects();
eraseGameObjects(_overlappedObjects);
}
else
if (FixedShortcuts.changeHeightByScrollWheel(e))
{
e.disable();
ObjectErase.instance.eraseBrush3DSettings.eraseHeight -= 0.1f * e.getMouseScroll();
EditorUtility.SetDirty(ObjectErase.instance.eraseBrush3DSettings);
}
else
if (FixedShortcuts.changeRadiusByScrollWheel(e))
{
e.disable();
ObjectErase.instance.eraseBrush3DSettings.radius -= 0.1f * e.getMouseScroll();
EditorUtility.SetDirty(ObjectErase.instance.eraseBrush3DSettings);
}
}
protected override void draw()
{
if (_surface.surfaceType == SurfaceType.Invalid) return;
if (_surface.requiresCircleProjection())
{
Material material = MaterialPool.instance.simpleDiffuse;
material.setCullModeBack();
material.setZTestEnabled(false);
material.SetColor("_Color", ObjectErasePrefs.instance.brush3DBorderColor);
material.SetPass(0);
GLEx.drawLineLoop3D(_circlePoints);
}
else
{
Matrix4x4 circleTransform = calcCircleDrawTransform();
Material material = MaterialPool.instance.simpleDiffuse;
material.setCullModeOff();
material.setZTestEnabled(true);
material.SetColor("_Color", ObjectErasePrefs.instance.brush3DBorderColor);
material.SetPass(0);
Graphics.DrawMeshNow(MeshPool.instance.unitWireCircleXY, circleTransform);
}
HandlesEx.saveColor();
Handles.color = ObjectErasePrefs.instance.brush3DHeightIndicatorColor;
Handles.DrawLine(_surface.pickPoint, _surface.pickPoint + _surface.genericNormal * ObjectErase.instance.eraseBrush3DSettings.eraseHeight);
HandlesEx.restoreColor();
}
private Matrix4x4 calcCircleDrawTransform()
{
float circleRadius = ObjectErase.instance.eraseBrush3DSettings.radius;
Vector3 offsetNormal = _surface.isTerrain() ? _surface.genericNormal : _surface.pickNormal;
return Matrix4x4.TRS(_surface.pickPoint + offsetNormal * 0.005f * HandleUtility.GetHandleSize(_surface.pickPoint), calcCircleRotation(), new Vector3(circleRadius, circleRadius, 1.0f));
}
private Quaternion calcCircleRotation()
{
return QuaternionEx.create(Vector3.forward, _surface.genericNormal, Vector3.right);
}
private void updateSurface()
{
if (_surface.isLocked)
{
Ray pickRay = PluginCamera.camera.getCursorRay();
if (_surface.surfaceType == SurfaceType.Grid || _surface.surfaceType == SurfaceType.Mesh)
{
float t;
Plane surfacePlane = new Plane(_surface.pickNormal, _surface.pickPoint);
if (surfacePlane.Raycast(pickRay, out t)) _surface.pickPoint = pickRay.GetPoint(t);
}
else
if (_surface.surfaceType == SurfaceType.UnityTerrain)
{
TerrainCollider terrainCollider = _surface.gameObject.getTerrainCollider();
if (terrainCollider != null)
{
RaycastHit rayHit;
if (terrainCollider.Raycast(pickRay, out rayHit, float.MaxValue))
_surface.pickPoint = pickRay.GetPoint(rayHit.distance);
}
}
else
if (_surface.surfaceType == SurfaceType.TerrainMesh || _surface.surfaceType == SurfaceType.SphericalMesh)
{
PluginMesh pluginMesh = PluginMeshDb.instance.getPluginMesh(_surface.gameObject.getMesh());
if (pluginMesh != null)
{
MeshRayHit rayHit;
MeshRaycastConfig meshRaycastConfig = MeshRaycastConfig.defaultConfig;
if (pluginMesh.raycastClosest(pickRay, _surface.gameObject.transform, meshRaycastConfig, out rayHit))
_surface.pickPoint = pickRay.GetPoint(rayHit.hitEnter);
}
}
}
else
{
_surface.reset();
var eraseSettings = ObjectErase.instance.eraseBrush3DSettings;
_surfacePickRaycastFiler.layerMask = eraseSettings.surfaceLayers;
_surfacePickRaycastFiler.raycastGrid = eraseSettings.allowsGridSurface;
_surfacePickRaycastFiler.raycastObjects = eraseSettings.allowsMeshSurface | eraseSettings.allowsTerrainSurface;
_surfacePickRaycastFiler.objectTypes = GameObjectType.None;
if (eraseSettings.allowsMeshSurface) _surfacePickRaycastFiler.objectTypes |= GameObjectType.Mesh;
if (eraseSettings.allowsTerrainSurface) _surfacePickRaycastFiler.objectTypes |= GameObjectType.Terrain;
var rayHit = PluginScene.instance.raycastClosest(PluginCamera.camera.getCursorRay(), _surfacePickRaycastFiler, _surfacePickRaycastConfig);
if (rayHit.anyHit)
{
if (rayHit.wasObjectHit && !rayHit.wasGridHit) surfaceFromObjectHit(rayHit.objectHit);
else if (rayHit.wasGridHit && !rayHit.wasObjectHit) surfaceFromGridHit(rayHit.gridHit);
else
{
if (rayHit.gridHit.hitEnter < rayHit.objectHit.hitEnter &&
Mathf.Abs(rayHit.gridHit.hitEnter - rayHit.objectHit.hitEnter) > 1e-4f) surfaceFromGridHit(rayHit.gridHit);
else surfaceFromObjectHit(rayHit.objectHit);
}
}
}
}
private void updateCircleDrawPoints()
{
if (_surface.surfaceType == SurfaceType.UnityTerrain)
{
Terrain terrain = _surface.gameObject.getTerrain();
Matrix4x4 circleTransform = calcCircleDrawTransform();
Vector3 circleU = circleTransform.getRight();
Vector3 circleV = circleTransform.getUp();
CircleMesh.generateXYCirclePointsCW(_surface.pickPoint, ObjectErase.instance.eraseBrush3DSettings.radius, circleU, circleV, _numCirclePoints, _circlePoints);
terrain.projectPoints(terrain.transform.position.y, _circlePoints);
}
else
if (_surface.surfaceType == SurfaceType.TerrainMesh)
{
PluginMesh terrainMesh = PluginMeshDb.instance.getPluginMesh(_surface.gameObject.getMesh());
Matrix4x4 circleTransform = calcCircleDrawTransform();
Vector3 circleU = circleTransform.getRight();
Vector3 circleV = circleTransform.getUp();
CircleMesh.generateXYCirclePointsCW(_surface.pickPoint, ObjectErase.instance.eraseBrush3DSettings.radius, circleU, circleV, _numCirclePoints, _circlePoints);
TerrainMeshUtil.projectPoints(_surface.gameObject, terrainMesh, _circlePoints);
}
else
if (_surface.surfaceType == SurfaceType.SphericalMesh)
{
Matrix4x4 circleTransform = calcCircleDrawTransform();
Vector3 circleU = circleTransform.getRight();
Vector3 circleV = circleTransform.getUp();
CircleMesh.generateXYCirclePointsCW(_surface.pickPoint, ObjectErase.instance.eraseBrush3DSettings.radius, circleU, circleV, _numCirclePoints, _circlePoints);
SphericalMeshUtil.projectPoints(_surface.gameObject, _circlePoints);
}
}
private void surfaceFromObjectHit(ObjectRayHit objectHit)
{
_surface.gameObject = objectHit.hitObject;
GameObjectType gameObjectType = GameObjectDataDb.instance.getGameObjectType(_surface.gameObject);
_surface.surfaceType = gameObjectType == GameObjectType.Terrain ? SurfaceType.UnityTerrain : SurfaceType.Mesh;
_surface.genericNormal = gameObjectType == GameObjectType.Terrain ? _surface.gameObject.transform.up : objectHit.hitNormal;
_surface.pickNormal = objectHit.hitNormal;
_surface.pickPoint = objectHit.hitPoint;
if (_surface.gameObject.isTerrainMesh())
{
_surface.surfaceType = SurfaceType.TerrainMesh;
_surface.genericNormal = ObjectPrefs.instance.getTerrainMeshUp(_surface.gameObject);
}
else
if (_surface.gameObject.isSphericalMesh())
{
_surface.surfaceType = SurfaceType.SphericalMesh;
}
}
private void surfaceFromGridHit(GridRayHit gridHit)
{
_surface.gameObject = null;
_surface.surfaceType = SurfaceType.Grid;
_surface.genericNormal = gridHit.hitNormal;
_surface.pickNormal = gridHit.hitNormal;
_surface.pickPoint = gridHit.hitPoint;
}
private void detectOverlappedObjects()
{
_overlappedObjects.Clear();
OBB overlapBox = calcObjectOverlapBox();
if (_surface.gameObject != null) _objectOveralpFilter.setIgnoredObject(_surface.gameObject);
if (PluginScene.instance.overlapBox(overlapBox, _objectOveralpFilter, _objectOverlapConfig, _boxObjectOverlapBuffer))
{
Quaternion circleRotation = calcCircleRotation();
Vector3 circleU = circleRotation * Vector3.right;
Vector3 circleV = circleRotation * Vector3.up;
float eraseHeight = ObjectErase.instance.eraseBrush3DSettings.eraseHeight;
float circleRadius = ObjectErase.instance.eraseBrush3DSettings.radius;
bool allowPartialOverlap = ObjectErase.instance.eraseBrush3DSettings.allowPartialOverlap;
if (_surface.surfaceType == SurfaceType.UnityTerrain)
{
const float onTerrainEps = 1e-6f;
Terrain terrain = _surface.gameObject.getTerrain();
foreach (var go in _boxObjectOverlapBuffer)
{
OBB obb = ObjectBounds.calcWorldOBB(go, _overlappedBoundsQConfig);
if (!Box3D.isSpanningOrOnOrInFrontOfUnityTerrain(obb.center, obb.size, obb.rotation, terrain, onTerrainEps, eraseHeight)) continue;
if (allowPartialOverlap)
{
if (Circle3D.intersectsOBBAsInfiniteCylinder(_surface.pickPoint, circleRadius, circleU, circleV, obb)) _overlappedObjects.Add(go);
}
else
{
Box3D.calcCorners(obb.center, obb.size, obb.rotation, _vector3Buffer, false);
if (Circle3D.containsPointsAsInfiniteCylinder(_surface.pickPoint, circleRadius, circleU, circleV, _vector3Buffer)) _overlappedObjects.Add(go);
}
}
}
else
if (_surface.surfaceType == SurfaceType.TerrainMesh)
{
const float onTerrainEps = 1e-6f;
PluginMesh terrainMesh = PluginMeshDb.instance.getPluginMesh(_surface.gameObject.getMesh());
foreach (var go in _boxObjectOverlapBuffer)
{
OBB obb = ObjectBounds.calcWorldOBB(go, _overlappedBoundsQConfig);
if (!Box3D.isSpanningOrOnOrInFrontOfTerrainMesh(obb.center, obb.size, obb.rotation, _surface.gameObject, terrainMesh, onTerrainEps, eraseHeight)) continue;
if (allowPartialOverlap)
{
if (Circle3D.intersectsOBBAsInfiniteCylinder(_surface.pickPoint, circleRadius, circleU, circleV, obb)) _overlappedObjects.Add(go);
}
else
{
Box3D.calcCorners(obb.center, obb.size, obb.rotation, _vector3Buffer, false);
if (Circle3D.containsPointsAsInfiniteCylinder(_surface.pickPoint, circleRadius, circleU, circleV, _vector3Buffer)) _overlappedObjects.Add(go);
}
}
}
else
if (_surface.surfaceType == SurfaceType.Grid || _surface.surfaceType == SurfaceType.Mesh)
{
// Note: Lift the plane up a bit to avoid deleting objects below or sitting exactly on the surface plane.
const float onPlaneEps = 1e-6f;
Plane surfacePlane = new Plane(_surface.pickNormal, _surface.pickPoint + _surface.pickNormal * 1e-3f);
foreach(var go in _boxObjectOverlapBuffer)
{
OBB obb = ObjectBounds.calcWorldOBB(go, _overlappedBoundsQConfig);
if (!Box3D.isSpanningOrOnOrInFrontOfPlane(obb.center, obb.size, obb.rotation, surfacePlane, onPlaneEps, eraseHeight)) continue;
if (allowPartialOverlap)
{
if (Circle3D.intersectsOBBAsInfiniteCylinder(_surface.pickPoint, circleRadius, circleU, circleV, obb)) _overlappedObjects.Add(go);
}
else
{
Box3D.calcCorners(obb.center, obb.size, obb.rotation, _vector3Buffer, false);
if (Circle3D.containsPointsAsInfiniteCylinder(_surface.pickPoint, circleRadius, circleU, circleV, _vector3Buffer)) _overlappedObjects.Add(go);
}
}
}
else
if (_surface.surfaceType == SurfaceType.SphericalMesh)
{
const float onSphereEps = 1e-6f;
foreach (var go in _boxObjectOverlapBuffer)
{
OBB obb = ObjectBounds.calcWorldOBB(go, _overlappedBoundsQConfig);
if (!Box3D.isSpanningOrOnOrInFrontOfSphericalMesh(obb.center, obb.size, obb.rotation, _surface.gameObject, onSphereEps, eraseHeight)) continue;
if (allowPartialOverlap)
{
if (Circle3D.intersectsOBBAsInfiniteCylinder(_surface.pickPoint, circleRadius, circleU, circleV, obb)) _overlappedObjects.Add(go);
}
else
{
Box3D.calcCorners(obb.center, obb.size, obb.rotation, _vector3Buffer, false);
if (Circle3D.containsPointsAsInfiniteCylinder(_surface.pickPoint, circleRadius, circleU, circleV, _vector3Buffer)) _overlappedObjects.Add(go);
}
}
}
}
}
private OBB calcObjectOverlapBox()
{
float circleRadius = ObjectErase.instance.eraseBrush3DSettings.radius;
float eraseHeight = ObjectErase.instance.eraseBrush3DSettings.eraseHeight;
if (_surface.surfaceType == SurfaceType.UnityTerrain)
{
AABB circlePointsAABB = new AABB(_circlePoints);
OBB overlapBox = new OBB();
overlapBox.rotation = calcCircleRotation();
overlapBox.center = circlePointsAABB.center;
overlapBox.size = new Vector3(circleRadius * 2.0f, circleRadius * 2.0f, circlePointsAABB.size.y + eraseHeight);
return overlapBox;
}
else
if (_surface.surfaceType == SurfaceType.TerrainMesh)
{
AABB circlePointsAABB = new AABB(_circlePoints);
OBB overlapBox = new OBB();
overlapBox.rotation = calcCircleRotation();
overlapBox.center = circlePointsAABB.center;
overlapBox.size = new Vector3(circleRadius * 2.0f, circleRadius * 2.0f, circlePointsAABB.size.magnitude + eraseHeight);
return overlapBox;
}
else
if (_surface.surfaceType == SurfaceType.Grid || _surface.surfaceType == SurfaceType.Mesh)
{
float boxHeight = eraseHeight;
OBB overlapBox = new OBB();
overlapBox.rotation = calcCircleRotation();
overlapBox.center = _surface.pickPoint + _surface.pickNormal * (boxHeight * 0.5f);
overlapBox.size = new Vector3(circleRadius * 2.0f, circleRadius * 2.0f, boxHeight);
return overlapBox;
}
else
if (_surface.surfaceType == SurfaceType.SphericalMesh)
{
AABB circlePointsAABB = new AABB(_circlePoints);
OBB overlapBox = new OBB();
overlapBox.rotation = calcCircleRotation();
overlapBox.center = circlePointsAABB.center;
overlapBox.size = new Vector3(circleRadius * 2.0f, circleRadius * 2.0f, circlePointsAABB.size.magnitude + eraseHeight);
return overlapBox;
}
else return OBB.getInvalid();
}
}
}
#endif