Add option to quickly create box, sphere, convex or triangle mesh collider for the selected static model
This commit is contained in:
@@ -75,13 +75,34 @@ namespace FlaxEditor.Content
|
||||
throw new Exception("Failed to create new asset.");
|
||||
}
|
||||
|
||||
private bool TryUseCollisionData(Model model, BinaryAssetItem assetItem, Action<CollisionData> created, bool alwaysDeferCallback, CollisionDataType type)
|
||||
{
|
||||
var collisionData = FlaxEngine.Content.Load<CollisionData>(assetItem.ID);
|
||||
if (collisionData)
|
||||
{
|
||||
var options = collisionData.Options;
|
||||
if ((options.Model == model.ID || options.Model == Guid.Empty) && options.Type == type)
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Select(assetItem);
|
||||
if (created != null && alwaysDeferCallback)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(() => created(collisionData));
|
||||
else if (created != null)
|
||||
created(collisionData);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create collision data from model.
|
||||
/// </summary>
|
||||
/// <param name="model">The associated model.</param>
|
||||
/// <param name="created">The action to call once the collision data gets created (or reused from existing).</param>
|
||||
/// <param name="withRenaming">True if start initial item renaming by user, or tru to skip it.</param>
|
||||
public void CreateCollisionDataFromModel(Model model, Action<CollisionData> created = null, bool withRenaming = true)
|
||||
/// <param name="alwaysDeferCallback">True if always call <paramref name="created"/> callback on the next engine update, otherwise callback might be called within this function if collision data already exists.</param>
|
||||
/// <param name="type">Type of the collider to create.</param>
|
||||
public void CreateCollisionDataFromModel(Model model, Action<CollisionData> created = null, bool withRenaming = true, bool alwaysDeferCallback = true, CollisionDataType type = CollisionDataType.ConvexMesh)
|
||||
{
|
||||
// Check if there already is collision data for that model to reuse
|
||||
var modelItem = (AssetItem)Editor.Instance.ContentDatabase.Find(model.ID);
|
||||
@@ -92,31 +113,19 @@ namespace FlaxEditor.Content
|
||||
// Check if there is collision that was made with this model
|
||||
if (child is BinaryAssetItem b && b.IsOfType<CollisionData>())
|
||||
{
|
||||
var collisionData = FlaxEngine.Content.Load<CollisionData>(b.ID);
|
||||
if (collisionData && collisionData.Options.Model == model.ID)
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Select(b);
|
||||
if (created != null)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(() => created(collisionData));
|
||||
if (TryUseCollisionData(model, b, created, alwaysDeferCallback, type))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is a auto-imported collision
|
||||
// Check if there is an auto-imported collision
|
||||
if (child is ContentFolder childFolder && childFolder.ShortName == modelItem.ShortName)
|
||||
{
|
||||
foreach (var childFolderChild in childFolder.Children)
|
||||
{
|
||||
if (childFolderChild is BinaryAssetItem c && c.IsOfType<CollisionData>())
|
||||
{
|
||||
var collisionData = FlaxEngine.Content.Load<CollisionData>(c.ID);
|
||||
if (collisionData && (collisionData.Options.Model == model.ID || collisionData.Options.Model == Guid.Empty))
|
||||
{
|
||||
Editor.Instance.Windows.ContentWin.Select(c);
|
||||
if (created != null)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(() => created(collisionData));
|
||||
if (TryUseCollisionData(model, c, created, alwaysDeferCallback, type))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +144,7 @@ namespace FlaxEditor.Content
|
||||
}
|
||||
Task.Run(() =>
|
||||
{
|
||||
Editor.CookMeshCollision(assetItem.Path, CollisionDataType.ConvexMesh, model);
|
||||
Editor.CookMeshCollision(assetItem.Path, type, model);
|
||||
if (created != null)
|
||||
FlaxEngine.Scripting.InvokeOnUpdate(() => created(collisionData));
|
||||
});
|
||||
|
||||
@@ -320,6 +320,10 @@ namespace FlaxEditor.Modules
|
||||
|
||||
SpawnBegin?.Invoke();
|
||||
|
||||
// During play in editor mode spawned actors should be dynamic (user can move them)
|
||||
if (isPlayMode)
|
||||
actor.StaticFlags = StaticFlags.None;
|
||||
|
||||
// Add it
|
||||
Level.SpawnActor(actor, parent);
|
||||
|
||||
@@ -328,10 +332,6 @@ namespace FlaxEditor.Modules
|
||||
if (actorNode == null)
|
||||
throw new InvalidOperationException("Failed to create scene node for the spawned actor.");
|
||||
|
||||
// During play in editor mode spawned actors should be dynamic (user can move them)
|
||||
if (isPlayMode)
|
||||
actor.StaticFlags = StaticFlags.None;
|
||||
|
||||
// Call post spawn action (can possibly setup custom default values)
|
||||
actorNode.PostSpawn();
|
||||
|
||||
|
||||
@@ -101,7 +101,12 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
{
|
||||
base.OnContextMenu(contextMenu, window);
|
||||
|
||||
contextMenu.AddButton("Add collider", () => OnAddMeshCollider(window)).Enabled = ((StaticModel)Actor).Model != null;
|
||||
var menu = contextMenu.AddChildMenu("Add collider");
|
||||
menu.Enabled = ((StaticModel)Actor).Model != null;
|
||||
menu.ContextMenu.AddButton("Box", () => OnAddCollider(window, CreateBox));
|
||||
menu.ContextMenu.AddButton("Sphere", () => OnAddCollider(window, CreateSphere));
|
||||
menu.ContextMenu.AddButton("Convex", () => OnAddCollider(window, CreateConvex));
|
||||
menu.ContextMenu.AddButton("Triangle Mesh", () => OnAddCollider(window, CreateTriangle));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -143,7 +148,60 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
return base.GetActorSelectionPoints();
|
||||
}
|
||||
|
||||
private void OnAddMeshCollider(EditorWindow window)
|
||||
private delegate void Spawner(Collider collider);
|
||||
private delegate void CreateCollider(StaticModel actor, Spawner spawner, bool singleNode);
|
||||
|
||||
private void CreateBox(StaticModel actor, Spawner spawner, bool singleNode)
|
||||
{
|
||||
var collider = new BoxCollider
|
||||
{
|
||||
Transform = actor.Transform,
|
||||
};
|
||||
spawner(collider);
|
||||
// BoxColliderNode fits the box collider automatically on spawn
|
||||
}
|
||||
|
||||
private void CreateSphere(StaticModel actor, Spawner spawner, bool singleNode)
|
||||
{
|
||||
var bounds = actor.Sphere;
|
||||
var collider = new SphereCollider
|
||||
{
|
||||
Transform = actor.Transform,
|
||||
|
||||
// Refit into the sphere bounds that are usually calculated from mesh box bounds
|
||||
Position = bounds.Center,
|
||||
Radius = bounds.Radius / Mathf.Max(actor.Scale.MaxValue, 0.0001f) * 0.707f,
|
||||
};
|
||||
spawner(collider);
|
||||
}
|
||||
|
||||
private void CreateConvex(StaticModel actor, Spawner spawner, bool singleNode)
|
||||
{
|
||||
CreateMeshCollider(actor, spawner, singleNode, CollisionDataType.ConvexMesh);
|
||||
}
|
||||
|
||||
private void CreateTriangle(StaticModel actor, Spawner spawner, bool singleNode)
|
||||
{
|
||||
CreateMeshCollider(actor, spawner, singleNode, CollisionDataType.TriangleMesh);
|
||||
}
|
||||
|
||||
private void CreateMeshCollider(StaticModel actor, Spawner spawner, bool singleNode, CollisionDataType type)
|
||||
{
|
||||
// Create collision data (or reuse) and add collision actor
|
||||
var created = (CollisionData collisionData) =>
|
||||
{
|
||||
var collider = new MeshCollider
|
||||
{
|
||||
Transform = actor.Transform,
|
||||
CollisionData = collisionData,
|
||||
};
|
||||
spawner(collider);
|
||||
};
|
||||
var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy<CollisionData>();
|
||||
collisionDataProxy.CreateCollisionDataFromModel(actor.Model, created, singleNode, false, type);
|
||||
}
|
||||
|
||||
private void OnAddCollider(EditorWindow window, CreateCollider createCollider)
|
||||
{
|
||||
// Allow collider to be added to evey static model selection
|
||||
var selection = Array.Empty<SceneGraphNode>();
|
||||
@@ -157,72 +215,63 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
{
|
||||
if (node is not StaticModelNode staticModelNode)
|
||||
continue;
|
||||
|
||||
var actor = (StaticModel)staticModelNode.Actor;
|
||||
var model = ((StaticModel)staticModelNode.Actor).Model;
|
||||
if (!model)
|
||||
continue;
|
||||
Spawner spawner = collider =>
|
||||
{
|
||||
collider.StaticFlags = staticModelNode.Actor.StaticFlags;
|
||||
staticModelNode.Root.Spawn(collider, staticModelNode.Actor);
|
||||
var colliderNode = window is PrefabWindow prefabWindow ? prefabWindow.Graph.Root.Find(collider) : Editor.Instance.Scene.GetActorNode(collider);
|
||||
createdNodes.Add(colliderNode);
|
||||
};
|
||||
|
||||
// Special case for in-built Editor models that can use analytical collision
|
||||
var modelPath = model.Path;
|
||||
if (modelPath.EndsWith("/Primitives/Cube.flax", StringComparison.Ordinal))
|
||||
{
|
||||
var actor = new BoxCollider
|
||||
var collider = new BoxCollider
|
||||
{
|
||||
StaticFlags = staticModelNode.Actor.StaticFlags,
|
||||
Transform = actor.Transform,
|
||||
};
|
||||
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
|
||||
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
|
||||
spawner(collider);
|
||||
continue;
|
||||
}
|
||||
if (modelPath.EndsWith("/Primitives/Sphere.flax", StringComparison.Ordinal))
|
||||
{
|
||||
var actor = new SphereCollider
|
||||
var collider = new SphereCollider
|
||||
{
|
||||
StaticFlags = staticModelNode.Actor.StaticFlags,
|
||||
Transform = actor.Transform,
|
||||
};
|
||||
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
|
||||
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
|
||||
spawner(collider);
|
||||
collider.LocalTransform = Transform.Identity;
|
||||
continue;
|
||||
}
|
||||
if (modelPath.EndsWith("/Primitives/Plane.flax", StringComparison.Ordinal))
|
||||
{
|
||||
var actor = new BoxCollider
|
||||
spawner(new BoxCollider
|
||||
{
|
||||
StaticFlags = staticModelNode.Actor.StaticFlags,
|
||||
Transform = actor.Transform,
|
||||
Size = new Float3(100.0f, 100.0f, 1.0f),
|
||||
};
|
||||
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
|
||||
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (modelPath.EndsWith("/Primitives/Capsule.flax", StringComparison.Ordinal))
|
||||
{
|
||||
var actor = new CapsuleCollider
|
||||
var collider = new CapsuleCollider
|
||||
{
|
||||
StaticFlags = staticModelNode.Actor.StaticFlags,
|
||||
Transform = actor.Transform,
|
||||
Radius = 25.0f,
|
||||
Height = 50.0f,
|
||||
};
|
||||
Editor.Instance.SceneEditing.Spawn(actor, staticModelNode.Actor);
|
||||
actor.LocalPosition = new Vector3(0, 50.0f, 0);
|
||||
actor.LocalOrientation = Quaternion.Euler(0, 0, 90.0f);
|
||||
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
|
||||
spawner(collider);
|
||||
collider.LocalPosition = new Vector3(0, 50.0f, 0);
|
||||
collider.LocalOrientation = Quaternion.Euler(0, 0, 90.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create collision data (or reuse) and add collision actor
|
||||
Action<CollisionData> created = collisionData =>
|
||||
{
|
||||
var actor = new MeshCollider
|
||||
{
|
||||
StaticFlags = staticModelNode.Actor.StaticFlags,
|
||||
CollisionData = collisionData,
|
||||
};
|
||||
staticModelNode.Root.Spawn(actor, staticModelNode.Actor);
|
||||
createdNodes.Add(window is PrefabWindow pWindow ? pWindow.Graph.Root.Find(actor) : Editor.Instance.Scene.GetActorNode(actor));
|
||||
};
|
||||
var collisionDataProxy = (CollisionDataProxy)Editor.Instance.ContentDatabase.GetProxy<CollisionData>();
|
||||
collisionDataProxy.CreateCollisionDataFromModel(model, created, selection.Length == 1);
|
||||
createCollider(actor, spawner, selection.Length == 1);
|
||||
}
|
||||
|
||||
// Select all created nodes
|
||||
@@ -230,9 +279,9 @@ namespace FlaxEditor.SceneGraph.Actors
|
||||
{
|
||||
Editor.Instance.SceneEditing.Select(createdNodes);
|
||||
}
|
||||
else if (window is PrefabWindow pWindow)
|
||||
else if (window is PrefabWindow prefabWindow)
|
||||
{
|
||||
pWindow.Select(createdNodes);
|
||||
prefabWindow.Select(createdNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user