You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
111 lines
3.3 KiB
111 lines
3.3 KiB
6 months ago
|
[Tool]
|
||
|
public partial class Interactable : RigidBody3D
|
||
|
{
|
||
|
PackedScene _modelScene;
|
||
|
[Export] public PackedScene ModelScene {
|
||
|
get => _modelScene;
|
||
|
set => _modelScene = OnModelSceneChanged(value);
|
||
|
}
|
||
|
|
||
|
Vector3I _gridSize;
|
||
|
/// <summary> Get the size of this item in grid units. </summary>
|
||
|
[Export] public Vector3I GridSize {
|
||
|
get => _gridSize;
|
||
|
set => _gridSize = OnGridSizeChanged(value);
|
||
|
}
|
||
|
|
||
|
public override void _Ready()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public override void _Process(double delta)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
// FIXME: Only change this if ModelScene is actually changed, not when loaded.
|
||
|
PackedScene OnModelSceneChanged(PackedScene value)
|
||
|
{
|
||
|
if (GetNodeOrNull("_Model") is Node oldModel) {
|
||
|
RemoveChild(oldModel);
|
||
|
oldModel.QueueFree();
|
||
|
}
|
||
|
|
||
|
// Remove any previously added `CollisionShape3D` nodes.
|
||
|
foreach (var child in GetChildren(true).OfType<CollisionShape3D>()) {
|
||
|
RemoveChild(child);
|
||
|
child.QueueFree();
|
||
|
}
|
||
|
|
||
|
if (value is PackedScene scene) {
|
||
|
var model = scene.Instantiate<Node3D>();
|
||
|
model.Name = "_Model";
|
||
|
|
||
|
var numShapes = 0;
|
||
|
var min = Vector3.Zero;
|
||
|
var max = Vector3.Zero;
|
||
|
|
||
|
// Find all the `StaticBody3D` nodes in the model and parent
|
||
|
// their `CollisionShape3D` children to the this `RigidBody3D`.
|
||
|
// Required because shapes must be immediate children of the body.
|
||
|
// See: https://github.com/godotengine/godot-proposals/issues/535
|
||
|
// https://github.com/godotengine/godot/pull/77937
|
||
|
foreach (var body in model.FindChildren("*", "StaticBody3D").Cast<StaticBody3D>()) {
|
||
|
body.GetParent().RemoveChild(body);
|
||
|
body.QueueFree();
|
||
|
|
||
|
foreach (var shape in body.GetChildren().OfType<CollisionShape3D>()) {
|
||
|
// Not unsetting the owner results in this warning:
|
||
|
// "Adding 'CollisionShape3D' as child to 'Interactable' will make owner '...' inconsistent."
|
||
|
shape.Owner = null;
|
||
|
|
||
|
body.RemoveChild(shape);
|
||
|
shape.Name = $"_{nameof(CollisionShape3D)}_{numShapes + 1}";
|
||
|
AddChild(shape, false, InternalMode.Front);
|
||
|
// shape.Owner = this;
|
||
|
numShapes++;
|
||
|
|
||
|
// Finds the axis-aligned boundary of all collision shapes.
|
||
|
// This assumes that the shape has an identity transformation.
|
||
|
var vertices = (shape.Shape as ConvexPolygonShape3D)?.Points
|
||
|
?? (shape.Shape as ConcavePolygonShape3D)?.Data
|
||
|
?? throw new Exception("Shape must be either convex or concave");
|
||
|
foreach (var vert in vertices) {
|
||
|
min = new(Min(min.X, vert.X), Min(min.Y, vert.Y), Min(min.Z, vert.Z));
|
||
|
max = new(Max(max.X, vert.X), Max(max.Y, vert.Y), Max(max.Z, vert.Z));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
AddChild(model, false, InternalMode.Front);
|
||
|
|
||
|
// Set the grid size based on the boundary of all collision shapes.
|
||
|
GridSize = (Vector3I)((max - min).Snapped(Epsilon) / Grid.StepSize).Ceil();
|
||
|
} else {
|
||
|
GridSize = Vector3I.Zero;
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
|
||
|
Vector3I OnGridSizeChanged(Vector3I value)
|
||
|
{
|
||
|
if (GetNodeOrNull("_GridArea") is Node oldGridArea) {
|
||
|
RemoveChild(oldGridArea);
|
||
|
oldGridArea.QueueFree();
|
||
|
}
|
||
|
|
||
|
if (value.X > 0 && value.Y > 0 && value.Z > 0) {
|
||
|
var gridArea = new Area3D();
|
||
|
gridArea.Name = "_GridArea";
|
||
|
|
||
|
var shape = new CollisionShape3D();
|
||
|
shape.Shape = new BoxShape3D { Size = (Vector3)value * Grid.StepSize };
|
||
|
gridArea.AddChild(shape);
|
||
|
|
||
|
AddChild(gridArea, false, InternalMode.Front);
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
}
|
||
|
}
|