init
This commit is contained in:
132
src/VoxelGrid/BeltPort.cs
Normal file
132
src/VoxelGrid/BeltPort.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
using SJK.Functional;
|
||||
|
||||
[Tool]
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class BeltPort : Node3D, IBeltPort {
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
[Dependency] public IVoxelGridRegistry Grid => this.DependOn<IVoxelGridRegistry>();
|
||||
[Export] public Direction Face { get; set; } = default!;
|
||||
[Export] public int Width { get; set; } = default!;
|
||||
[Export] public PortAccess Access { get; set; } = default!;
|
||||
[Export] public Path3D Path { get; set; } = default!;
|
||||
[Dependency] public IItemRenderer ItemRenderer => this.DependOn<IItemRenderer>();
|
||||
public BeltPortProfile Profile => new(GridTransform3D.FromGodot(GlobalTransform), Width, Access);
|
||||
[Dependency] public IItemTransferAnimator ItemTransferAnimator => this.DependOn<IItemTransferAnimator>(()=> new CurveItemTransfer(){Curve3D = Path.Curve,Tree = GetTree(), ItemRenderer = ItemRenderer, Transform3D = GlobalTransform});
|
||||
public void OnResolved()
|
||||
{
|
||||
if (Engine.IsEditorHint())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Grid.Register<IBeltPort>(this, [.. (this as IBeltPort).Points()]);
|
||||
}
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
DebugDraw3D.DrawLine(GlobalPosition,GlobalTransform * Face.ToVector(), Colors.Red);
|
||||
if (Engine.IsEditorHint())
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < _itemsDummys.Count; i++)
|
||||
{
|
||||
_itemsDummys[i] = (_itemsDummys[i].i+(float)delta,_itemsDummys[i].beltItem);
|
||||
GD.Print(_itemsDummys[i].i);
|
||||
ItemRenderer.UpdateTransform(_itemsDummys[i].beltItem,GlobalTransform * Path.Curve.SampleBakedWithRotation(_itemsDummys[i].i));
|
||||
if (_itemsDummys[i].i > Path.Curve.GetBakedLength())
|
||||
{
|
||||
_itemsDummys[i].beltItem.Dispose();
|
||||
_itemsDummys.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanAccept(IBeltItem item, LaneSpan laneSpan, float beltT)
|
||||
{
|
||||
return true;
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
List<(float i,IBeltItem beltItem)> _itemsDummys = [];
|
||||
|
||||
public bool TryInsert(IBeltItem item, LaneSpan laneSpan, float beltT)
|
||||
{
|
||||
var tween = ItemTransferAnimator.StartTransfer(new(item,beltT){LaneSpan = laneSpan}, () => GD.Print("Done"));
|
||||
// GD.Print("gg "+laneSpan);
|
||||
tween.TweenCallback(Callable.From(item.Dispose));
|
||||
// GD.Print(laneSpan);
|
||||
// GD.Print(item.ToString());
|
||||
// _itemsDummys.Add((0,item));
|
||||
// item.Dispose();
|
||||
return true;
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
public interface IItemTransferAnimator
|
||||
{
|
||||
Tween StartTransfer(
|
||||
ConveyorSlice item,
|
||||
Action onFinished = null);
|
||||
}
|
||||
|
||||
public class InstanceItemTransfer : IItemTransferAnimator
|
||||
{
|
||||
public SceneTree Tree = default!;
|
||||
public Func<ConveyorSlice, bool> AcceptFunc = default!;
|
||||
public Tween StartTransfer(ConveyorSlice item, Action onFinished = null)
|
||||
{
|
||||
AcceptFunc(item);
|
||||
onFinished?.Invoke();
|
||||
return Tree.CreateTween();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
public class CurveItemTransfer : IItemTransferAnimator
|
||||
{
|
||||
public Curve3D Curve3D = default!;
|
||||
public IItemRenderer ItemRenderer = default!;
|
||||
public Func<ConveyorSlice, bool> AcceptFunc = default!;
|
||||
public SceneTree Tree = default!;
|
||||
public Transform3D Transform3D;
|
||||
public Tween AnimateAlongCurve(
|
||||
ConveyorSlice item,
|
||||
Curve3D curve,
|
||||
float duration,
|
||||
IItemRenderer renderer)
|
||||
{
|
||||
float length = curve.GetBakedLength();
|
||||
|
||||
var tween = Tree.CreateTween();
|
||||
|
||||
tween.TweenMethod(
|
||||
Callable.From<float>(t =>
|
||||
{
|
||||
//Transform is not being set when using update transform in a tween, but can set position
|
||||
// renderer.UpdateTransform(item, Transform3D * curve.SampleBakedWithRotation(t),1/duration);
|
||||
(renderer as TestItemRendered)._items[item.Item].Transform =Transform3D *Curve3D.SampleBakedWithRotation(t).Translated(-Curve3D.SampleBakedWithRotation(t).Basis.X * item.LaneSpan.Start);
|
||||
// GD.PrintS(Transform3D * curve.SampleBakedWithRotation(t).Origin,1/duration,(renderer as TestItemRendered)._items[item].GlobalPosition);
|
||||
}),
|
||||
0f,
|
||||
length,
|
||||
duration
|
||||
);
|
||||
return tween;
|
||||
}
|
||||
public Tween StartTransfer(ConveyorSlice item, Action onFinished = null){
|
||||
var tween = AnimateAlongCurve(item,Curve3D,1,ItemRenderer);
|
||||
if (onFinished is not null){
|
||||
tween.TweenCallback(Callable.From(onFinished));
|
||||
}
|
||||
return tween;
|
||||
}
|
||||
}
|
||||
1
src/VoxelGrid/BeltPort.cs.uid
Normal file
1
src/VoxelGrid/BeltPort.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ee5aoxi8mjnw
|
||||
114
src/VoxelGrid/ConveyorItemRender.cs
Normal file
114
src/VoxelGrid/ConveyorItemRender.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class ConveyorItemRender : Node
|
||||
{
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
[Export] protected Path3D Path3D { get; set; } = default!;
|
||||
[Export] protected TestItemConveyor ItemConveyor { get; set; } = default!;
|
||||
[Chickensoft.AutoInject.Dependency] protected IItemRenderer Items => this.DependOn<IItemRenderer>();
|
||||
private Chickensoft.Sync.Primitives.AutoList<ConveyorSlice>.Binding binding = default!;
|
||||
public override async void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
if (!ItemConveyor.IsNodeReady())
|
||||
{
|
||||
await ToSignal(ItemConveyor, Node.SignalName.Ready);
|
||||
}
|
||||
binding = ItemConveyor.Items.Items.Bind();
|
||||
// binding.OnRemove(callback =>
|
||||
// {
|
||||
// Items.Remove(callback.Item);
|
||||
// // GD.PrintS(callback.Item, callback.BeltT);
|
||||
// // if (itemsRenders.TryGetValue(callback.Item, out var node))
|
||||
// // {
|
||||
// // GD.PrintS(callback.Item, callback.BeltT,node);
|
||||
// // itemsRenders.Remove(callback.Item);
|
||||
// // node.QueueFree();
|
||||
// // }
|
||||
// });
|
||||
binding.OnAdd((i, v) =>
|
||||
{
|
||||
|
||||
// itemsRenders[i.Item] = new MeshInstance3D(){Mesh = new BoxMesh()};
|
||||
// AddSibling(itemsRenders[i.Item]);
|
||||
// itemsRenders[i.Item].Transform = Path3D.Curve.SampleBakedWithRotation(i.BeltT);
|
||||
// GD.Print($"Item : {i},{v} added");
|
||||
Items.UpdateTransform(i.Item,Path3D.GlobalTransform *Path3D.Curve.SampleBakedWithRotation(i.BeltT));
|
||||
});
|
||||
binding.OnUpdate((a,b) =>
|
||||
{
|
||||
Items.UpdateTransform(a.Item, Path3D.GlobalTransform *Path3D.Curve.SampleBakedWithRotation(b.BeltT).Translated(-Path3D.Curve.SampleBakedWithRotation(b.BeltT).Basis.X*(b.LaneSpan.Start)));
|
||||
return;
|
||||
if (itemsRenders.TryGetValue(a.Item, out var node))
|
||||
{
|
||||
if (tweens.TryGetValue(a.Item, out var t))
|
||||
{
|
||||
t.Kill();
|
||||
}
|
||||
tweens[a.Item] = t = CreateTween();
|
||||
t.TweenProperty(node, "transform",Path3D.Curve.SampleBakedWithRotation(b.BeltT), .25f);
|
||||
// node.Transform = Path3D.Curve.SampleBakedWithRotation(b.BeltT);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
private Dictionary<IBeltItem, Node3D> itemsRenders = [];//This would be an item server for reuse via depency
|
||||
private Dictionary<IBeltItem, Tween> tweens = [];//This would be an item server for reuse via depency
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
binding.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IItemRenderer
|
||||
{
|
||||
// Node3D GetVisualNode(IBeltItem beltItem);
|
||||
void UpdateTransform(IBeltItem beltItem, Transform3D newTransform);
|
||||
void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time);
|
||||
void Remove(IBeltItem beltItem);
|
||||
}
|
||||
public partial class TestItemRendered : Node3D, IItemRenderer
|
||||
{
|
||||
public Dictionary<IBeltItem, Node3D> _items = [];
|
||||
private ConditionalWeakTable<IBeltItem, Tween> _tweens = [];
|
||||
public void Remove(IBeltItem beltItem)
|
||||
{
|
||||
if (_items.TryGetValue(beltItem, out var node)){
|
||||
node.QueueFree();
|
||||
}
|
||||
_items.Remove(beltItem);
|
||||
}
|
||||
|
||||
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time ){
|
||||
if (!_items.TryGetValue(beltItem, out var node))
|
||||
{
|
||||
_items.Add(beltItem,node = beltItem.CreateItemVisual());
|
||||
AddChild(node);
|
||||
beltItem.Disposed += _ =>{node.QueueFree();_items.Remove(beltItem);};
|
||||
node.Transform = newTransform;
|
||||
return;
|
||||
}
|
||||
if (_tweens.TryGetValue(beltItem, out var tween))
|
||||
{
|
||||
tween.Kill();
|
||||
_tweens.Remove(beltItem);
|
||||
}
|
||||
// GD.Print(newTransform);
|
||||
tween = GetTree().CreateTween().BindNode(node);
|
||||
tween.TweenProperty(node, "transform", newTransform, time);
|
||||
_tweens.Add(beltItem,tween);
|
||||
}
|
||||
|
||||
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform) => UpdateTransform(beltItem,newTransform,.25f);
|
||||
}
|
||||
1
src/VoxelGrid/ConveyorItemRender.cs.uid
Normal file
1
src/VoxelGrid/ConveyorItemRender.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bjuntmf2sjynp
|
||||
83
src/VoxelGrid/Equipment.cs
Normal file
83
src/VoxelGrid/Equipment.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.GodotNodeInterfaces;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
public interface IEquipment : IProvide<IEquipmentContext>//INode3D,
|
||||
{
|
||||
EquipmentId Id { get; set; }
|
||||
}
|
||||
[Tool]
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class Equipment() : Node3D, IEquipment
|
||||
{
|
||||
|
||||
public override void _Notification(int what)
|
||||
{
|
||||
if (what == NotificationTransformChanged)
|
||||
{
|
||||
OnNotificationTransformChanged();
|
||||
}
|
||||
// if (Engine.IsEditorHint())
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
this.Notify(what);
|
||||
}
|
||||
|
||||
public EquipmentId Id { get; set; } = new(Guid.NewGuid());
|
||||
[Signal] public delegate void TickEventHandler();
|
||||
[Export]public Vector3I GridPos { get; set; }
|
||||
[Dependency] protected IVoxelGridQuery<LayeredEquipment> Grid => this.DependOn<IVoxelGridQuery<LayeredEquipment>>();
|
||||
|
||||
protected IEquipmentContext _equipmentContext { get; set; } = default!;
|
||||
public override void _Ready()
|
||||
{
|
||||
this.SetNotifyTransform(true);
|
||||
base._Ready();
|
||||
// if (Engine.IsEditorHint())
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
_equipmentContext = new eqitpTest(this);
|
||||
this.Provide();
|
||||
Timer timer = new Timer(){WaitTime = .25f, Autostart = true};
|
||||
AddChild(timer);
|
||||
timer.Timeout += EmitSignalTick;
|
||||
// timer.Timeout += () => Position += Vector3.One;
|
||||
}
|
||||
|
||||
public void OnResolved()
|
||||
{
|
||||
GD.Print(Grid);
|
||||
(Grid as EquipmentVoxelGrid).AddEquipment(GridPos, this);
|
||||
GD.Print("Added Self");
|
||||
}
|
||||
IEquipmentContext IProvide<IEquipmentContext>.Value() => _equipmentContext;
|
||||
public void OnNotificationTransformChanged()
|
||||
{
|
||||
GD.Print("Transform changed, now" , Position);
|
||||
var newPos = new Vector3I(Mathf.RoundToInt(Position.X),Mathf.RoundToInt(Position.Y),Mathf.RoundToInt(Position.Z));
|
||||
if (newPos == GridPos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
GridPos = newPos;
|
||||
// Transform = Transform.Origin = newPos;;
|
||||
}
|
||||
}
|
||||
public record EquipmentId(Guid Id);
|
||||
public interface IEquipmentContext
|
||||
{
|
||||
|
||||
Equipment GetEquipment();
|
||||
EquipmentId GetEquipmentId();
|
||||
|
||||
}
|
||||
public record eqitpTest(Equipment Equipment) : IEquipmentContext
|
||||
{
|
||||
public Equipment GetEquipment() => Equipment;
|
||||
public EquipmentId GetEquipmentId() => Equipment.Id;
|
||||
}
|
||||
1
src/VoxelGrid/Equipment.cs.uid
Normal file
1
src/VoxelGrid/Equipment.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bhh1c4a5gep6o
|
||||
788
src/VoxelGrid/ItemConveyor.cs
Normal file
788
src/VoxelGrid/ItemConveyor.cs
Normal file
@@ -0,0 +1,788 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.AutoInject;
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Chickensoft.Sync.Primitives;
|
||||
using SJK.Functional;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
public interface IMovementConveyor
|
||||
{
|
||||
IBeltPort StartPort { get; }
|
||||
// IBeltSlotProfile StartPort { get; }
|
||||
IBeltPort EndPort { get; }
|
||||
// IBeltSlotProfile EndPort { get; }
|
||||
// IOption<IBeltSlotProfile> InputPort { get; }
|
||||
// IOption<IBeltSlotProfile> OutputPort { get; }
|
||||
// IEnumerable<IBeltSlotProfile> GetPorts();
|
||||
IEnumerable<IBeltPort> GetPorts();
|
||||
IAutoValue<float> SpeedValue { get; }
|
||||
float SpeedMagnitude { get; set; }
|
||||
float SignedSpeed { get; set; }
|
||||
bool IsReversed { get; set; }
|
||||
float Length {get;set;}
|
||||
// float GetAvailableTravel(ItemConveyor.BeltDirection beltDirection, LaneSpanT itemSpan, float maxDistance);
|
||||
// IBeltSlotProfile GetPortFacingStart();
|
||||
// IBeltSlotProfile GetPortFacingEnd();
|
||||
ItemConveyor.BeltDirection GetBeltDirection();
|
||||
// Option<ConveyorSlice> GetItemTowardStart();
|
||||
// Option<ConveyorSlice> GetItemTowardEnd();
|
||||
// Option<ConveyorSlice> GetItemTowardInput();
|
||||
// Option<ConveyorSlice> GetItemTowardOutput();
|
||||
IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardEnd();
|
||||
IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardStart();
|
||||
ItemConveyor.IBeltMovement GetMovementPolicy();
|
||||
// IList<ConveyorSlice> Items { get; }
|
||||
IOption<IBeltPort> GetPortFacing(IBeltPort slot);
|
||||
BeltObstacle GetDistanceToNextItem(ItemConveyor.BeltDirection beltDirection, float itemBeltT, float maxDistToCheck, LaneSpan laneSpan, HashSet<IMovementConveyor>? visted = null);
|
||||
ConveyorPort CreatePort(BeltPortProfile profile, ItemConveyor.BeltT beltT, LaneSpan laneSpan);
|
||||
}
|
||||
public readonly struct ConveyorItemHandle
|
||||
{
|
||||
private readonly Action _remove;
|
||||
private readonly Action<ConveyorSlice> _replace;
|
||||
public int Index { get; }
|
||||
public ConveyorSlice Slice { get; }
|
||||
public IBeltItem Item => Slice.Item;
|
||||
public LaneSpan Span => Slice.LaneSpan;
|
||||
public float BeltT => Slice.BeltT;
|
||||
public ConveyorItemHandle(int index, ConveyorSlice slice, Action remove, Action<ConveyorSlice> replace)
|
||||
{
|
||||
Index = index;
|
||||
Slice = slice;
|
||||
_remove = remove;
|
||||
_replace = replace;
|
||||
}
|
||||
public void Remove() => _remove();
|
||||
public void Replace(ConveyorSlice slice) => _replace(slice);
|
||||
}
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class ItemConveyor : Node, IVoxelNode
|
||||
{
|
||||
// public record ItemPair(IBeltItem Item, float BeltT);
|
||||
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
[Export] public int Length { get; set; } = 1;
|
||||
// [Export] public float Speed { get; set; } = .05f;
|
||||
private readonly AutoValue<float> _speed = new(.05f);
|
||||
public IAutoValue<float> Speed => _speed;
|
||||
public float SignedSpeed
|
||||
{
|
||||
get => _speed.Value;
|
||||
set => _speed.Value = value;
|
||||
}
|
||||
|
||||
public float SpeedMagnitude
|
||||
{
|
||||
get => Mathf.Abs(_speed.Value);
|
||||
set => _speed.Value = Mathf.Abs(value) * Mathf.Sign(_speed.Value);
|
||||
}
|
||||
public bool IsReversed { get => Mathf.Sign(_speed.Value) < 0; set => _speed.Value = SpeedMagnitude * (value ? -1 : 1); }
|
||||
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
|
||||
[Dependency] public IBeltMovement MovementSystem => this.DependOn<IBeltMovement>(() => new IndividualMovement());
|
||||
public const float ITEMSIZE = .2f;
|
||||
private const float ITEMHALFSIZE = ITEMSIZE / 2f;
|
||||
//Basci port for simple conveyor, asuming no rever,
|
||||
private List<ConveyorPort> _ports = [
|
||||
new(){//EjectFace
|
||||
Face = Direction.Front,
|
||||
Direction= PortAccess.InOut,
|
||||
LocalOffset = Vector3I.Zero,
|
||||
BeltT = new BeltTEnd(ConveyorEnd.End),
|
||||
PullPush = TransferMode.PushPull,
|
||||
},
|
||||
new(){//PullFace
|
||||
Face = Direction.Back,
|
||||
Direction= PortAccess.InOut,
|
||||
LocalOffset = Vector3I.Zero,
|
||||
BeltT = new BeltTEnd(ConveyorEnd.Start),
|
||||
PullPush = TransferMode.PushPull//Or none, PlateUp has grtabber and none grabby varietns
|
||||
},
|
||||
new(){//PullFace
|
||||
Face = Direction.Right,
|
||||
Direction= PortAccess.BiDirectional,
|
||||
LocalOffset = Vector3I.Zero,
|
||||
BeltT = new BeltTOffset(.5f),
|
||||
},
|
||||
new(){//PullFace
|
||||
Face = Direction.Left,
|
||||
Direction= PortAccess.BiDirectional,
|
||||
LocalOffset = Vector3I.Zero,
|
||||
BeltT = new BeltTOffset(.5f),
|
||||
},
|
||||
new(){//PullFace
|
||||
Face = Direction.Up,
|
||||
Direction= PortAccess.BiDirectional,
|
||||
LocalOffset = Vector3I.Zero,
|
||||
BeltT = new BeltTOffset(.5f),
|
||||
}
|
||||
];
|
||||
public ConveyorPort GetInputPort() => _ports.First(item => (item.PullPush & TransferMode.Pull)>0 && item.BeltT == (SignedSpeed >= 0 ? new BeltTEnd(ConveyorEnd.Start):new BeltTEnd(ConveyorEnd.End)));//Cache this
|
||||
public ConveyorPort GetOutputPort() => _ports.First(item => (item.PullPush & TransferMode.Push)>0 && item.BeltT == (SignedSpeed < 0 ? new BeltTEnd(ConveyorEnd.Start):new BeltTEnd(ConveyorEnd.End)));//Cache this
|
||||
public ConveyorPort GetStartPort() => _ports.First(item => item.BeltT == new BeltTEnd(ConveyorEnd.Start));
|
||||
public ConveyorPort GetEndPort() => _ports.First(item => item.BeltT == new BeltTEnd(ConveyorEnd.End));
|
||||
// public ConveyorPort GetPortInDirectionOfTravel() => Speed >= 0 ? GetOutputPort() : GetInputPort();
|
||||
//needs better name
|
||||
// private IEnumerable<ConveyorPort> ActivePorts(TransferMode p) => _ports.Where(item => item.PullPush == p);
|
||||
public void OnResolved()
|
||||
{
|
||||
// GD.Print(SlotExtesion.MapSlotToFacingSlot(Vector3I.Zero,Direction.Back,2,new(2,0,1),Direction.Front,3));
|
||||
// GD.Print(SlotExtesion.MapSlotToFacingSlot(new(2,0,1),Direction.Front,3,Vector3I.Zero,Direction.Back,2));
|
||||
GridRegistry.Register(this);
|
||||
Timer timer = new Timer() { WaitTime = .25f, Autostart = true };//TEST
|
||||
AddChild(timer);//TEST
|
||||
timer.Timeout += OnTick;//TEST
|
||||
for (int i = 0; i < _ports.Count; i++)
|
||||
{
|
||||
var port = _ports[i];
|
||||
if (port.Access.HasFlag(PortAccess.In)){
|
||||
port.AcceptItemFunc = (item, belt) =>
|
||||
{
|
||||
GD.PrintS("============================",port.Access,item,belt,port.BeltT);
|
||||
if (port.BeltT is BeltTEnd end)
|
||||
{
|
||||
return TryInsertAtEnd(end.End, item);
|
||||
}
|
||||
if (port.BeltT is BeltTOffset offset && TryFindLocalInsertion(offset.T,ITEMHALFSIZE,out var point))
|
||||
{
|
||||
InsertInMiddleWithoutCheck(item, point);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
_ports[i] = port;
|
||||
}
|
||||
}
|
||||
|
||||
// MovementSystem.OnItemMoved += static (item, segments, delta) => GD.PrintS(item, string.Join(',', segments.ToArray().Select(i => $"({i.Conveyor},{i.StartT},{i.EndT}")));
|
||||
}
|
||||
public override void _ExitTree()
|
||||
{
|
||||
base._ExitTree();
|
||||
GridRegistry.UnRegister(Id);
|
||||
}
|
||||
public void OnTick()//TEMP METHOOD FOR TESTING
|
||||
{
|
||||
if (Input.IsActionPressed("ui_up"))
|
||||
{
|
||||
SignedSpeed = -SignedSpeed;
|
||||
}
|
||||
if (VoxelPosition == Vector3I.Forward*-2 && TryInsertAtEnd(ConveyorEnd.Start, new TestItem(){Height = 1, Width = 1, Temp = 1}))
|
||||
InsertAtEndWithoutCheck(ConveyorEnd.Start,new TestItem(){Height = 1, Width = 1, Temp = 1});
|
||||
MovementSystem.AdvanceBelt(this,1f);
|
||||
return;
|
||||
// if (_items.Count <= 0)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// if (Speed == 0)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// GD.Print("Thing: " + Speed + " "+ GetConveyorPortFacing(GetOutputPort()));
|
||||
// var distToEnd = DistanceFromEnd(Speed < 0 ? ConveyorEnd.Start : ConveyorEnd.End);
|
||||
// var maxSpeed = Mathf.Min(Mathf.Abs(Speed), distToEnd) * Mathf.Sign(Speed);
|
||||
|
||||
// if (maxSpeed == 0)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// // GD.Print()
|
||||
// for (int i = 0; i < _items.Count; i++)
|
||||
// {
|
||||
// _items[i] = _items[i] with { BeltT = Mathf.Clamp(_items[i].BeltT + maxSpeed, 0, Length) };
|
||||
// GD.Print($"Item:{_items[i].Item}, Position:{_items[i].BeltT}");
|
||||
// }
|
||||
// // _items.ForEach((item) => item = item = Mathf.Clamp(item.Position + maxSpeed, 0, Length));
|
||||
// var list = _items.Where(item=> Speed>=0?item.BeltT >= Length:item.BeltT <= 0).ToList();
|
||||
// // GD.Print(list.Count);
|
||||
// foreach (var item in list)
|
||||
// {
|
||||
// GD.Print($"Item:{item.Item}, Position:{item.BeltT}");
|
||||
// //TEST
|
||||
// var v = GetConveyorPortFacing(GetOutputPort());
|
||||
// if (v.HasValue)
|
||||
// {
|
||||
// GD.PrintS(v.Value.Port.LocalOffset,v.Value.Port.BeltT,v.Value.Port.Direction,v.Value.Port.PullPush,v.Value.Port.Face);
|
||||
// if(v.Value.Conveyor.TryInsertAtEnd((v.Value.Port.BeltT as BeltTEnd).End, item.Item))
|
||||
// {
|
||||
// GD.Print("Item Moved");
|
||||
// _items.Remove(item);
|
||||
// }
|
||||
|
||||
// }
|
||||
// //Move Item Into Other COnveyor with offset, this means a item could therolitcly move two the end of another conveyor, but need to decide if that should keep moving recurively, opr make items only move one conveyor max at atime
|
||||
// }
|
||||
|
||||
}
|
||||
public float? GetAvailableTravelForFrontItem(bool accountForNextConveyor = false) => GetBeltDirection() switch
|
||||
{
|
||||
BeltDirection.TowardStart => AnyItems() ? GetAvailableTravel(0, BeltDirection.TowardStart, accountForNextConveyor) : null,
|
||||
BeltDirection.TowardEnd => AnyItems() ? GetAvailableTravel(ItemsCount - 1, BeltDirection.TowardEnd, accountForNextConveyor) : null,
|
||||
BeltDirection.NotMoving => null,
|
||||
_ => throw new NotSupportedException(),
|
||||
};
|
||||
public float GetAvailableSpaceFromEnd(ConveyorEnd end) => end switch
|
||||
{
|
||||
ConveyorEnd.Start => AnyItems()?_items[0].BeltT - ITEMSIZE:Length -ITEMHALFSIZE,
|
||||
ConveyorEnd.End => AnyItems()?_items[^1].BeltT + ITEMSIZE : ITEMHALFSIZE,
|
||||
_ => throw new NotSupportedException(),
|
||||
};
|
||||
public float GetAvailableTravel(
|
||||
int itemIndex,
|
||||
BeltDirection direction,
|
||||
bool accountForNextConveyor = false
|
||||
)
|
||||
{
|
||||
if (itemIndex < 0 || itemIndex >= _items.Count)
|
||||
throw new IndexOutOfRangeException(
|
||||
$"{nameof(itemIndex)}={itemIndex}, Count={_items.Count}"
|
||||
);
|
||||
var item = _items[itemIndex];
|
||||
|
||||
// Determine neighbor and boundary
|
||||
bool towardEnd = direction == BeltDirection.TowardEnd;
|
||||
|
||||
float limit;
|
||||
|
||||
if (towardEnd)
|
||||
{
|
||||
var other = GetConveyorPortFacing(GetEndPort());
|
||||
// Next item or belt end
|
||||
limit = (itemIndex + 1 < _items.Count)
|
||||
? _items[itemIndex + 1].BeltT - ITEMSIZE
|
||||
: Length + (accountForNextConveyor && other.HasValue && other.Value.Port.BeltT is BeltTEnd end ? other.Value.Conveyor.GetAvailableSpaceFromEnd(end.End) : -ITEMHALFSIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
var other = GetConveyorPortFacing(GetStartPort());
|
||||
// Previous item or belt start
|
||||
limit = (itemIndex - 1 >= 0)
|
||||
? _items[itemIndex - 1].BeltT + ITEMSIZE
|
||||
: accountForNextConveyor && other.HasValue && other.Value.Port.BeltT is BeltTEnd end ? other.Value.Conveyor.GetAvailableSpaceFromEnd(end.End) : ITEMHALFSIZE;
|
||||
}
|
||||
|
||||
float available = towardEnd
|
||||
? limit - item.BeltT
|
||||
: item.BeltT - limit;
|
||||
|
||||
return Mathf.Max(0f, available);
|
||||
}
|
||||
|
||||
public enum ConveyorEnd { Start, End }
|
||||
public static ConveyorEnd SwapEnd(ConveyorEnd end) => end switch
|
||||
{
|
||||
ConveyorEnd.Start => ConveyorEnd.End,
|
||||
ConveyorEnd.End => ConveyorEnd.Start,
|
||||
_ => throw new NotSupportedException($"{end}"),
|
||||
};
|
||||
public float DistanceFromEnd(ConveyorEnd end) => end switch
|
||||
{
|
||||
ConveyorEnd.Start => _items.Count == 0 ? Length : _items[0].BeltT,
|
||||
ConveyorEnd.End => _items.Count == 0 ? Length : Length - _items[^1].BeltT,
|
||||
_ => throw new NotSupportedException($"{end}"),
|
||||
};
|
||||
public float GetTravelDistanceInDirection(BeltDirection direction) => direction switch
|
||||
{
|
||||
BeltDirection.TowardEnd => _items.Count == 0 ? Length : _items[0].BeltT,
|
||||
BeltDirection.TowardStart => _items.Count == 0 ? Length : Length - _items[^1].BeltT,
|
||||
BeltDirection.NotMoving => 0,
|
||||
_ => throw new NotSupportedException($"{direction}"),
|
||||
};
|
||||
public enum BeltDirection : sbyte {
|
||||
TowardStart = -1,
|
||||
NotMoving = 0,
|
||||
TowardEnd = 1
|
||||
}
|
||||
public bool TryFindLocalInsertion(
|
||||
float beltT,
|
||||
float maxDistance,
|
||||
out float resultT
|
||||
)
|
||||
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// float minAllowed = Mathf.Max(ITEMHALFSIZE, beltT - maxDistance);
|
||||
// float maxAllowed = Mathf.Min(Length - ITEMHALFSIZE, beltT + maxDistance);
|
||||
|
||||
// if (minAllowed > maxAllowed)
|
||||
// {
|
||||
// resultT = default;
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (_items.Count == 0)
|
||||
// {
|
||||
// resultT = Mathf.Clamp(beltT, minAllowed, maxAllowed);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// int index = _items.BinarySearch(
|
||||
// new(null!, beltT),
|
||||
// Comparer<ConveyorSlice>.Create(
|
||||
// (a, b) => a.BeltT.CompareTo(b.BeltT)
|
||||
// )
|
||||
// );
|
||||
|
||||
// if (index < 0)
|
||||
// {
|
||||
// index = ~index;
|
||||
// }
|
||||
|
||||
// float gapMin = index > 0
|
||||
// ? _items[index - 1].BeltT + ITEMSIZE
|
||||
// : ITEMHALFSIZE;
|
||||
|
||||
// float gapMax = index < _items.Count
|
||||
// ? _items[index].BeltT - ITEMSIZE
|
||||
// : Length - ITEMHALFSIZE;
|
||||
|
||||
// // Intersect gap with allowed window
|
||||
// gapMin = Mathf.Max(gapMin, minAllowed);
|
||||
// gapMax = Mathf.Min(gapMax, maxAllowed);
|
||||
|
||||
// if (gapMin > gapMax)
|
||||
// {
|
||||
// resultT = default;
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// resultT = Mathf.Clamp(beltT, gapMin, gapMax);
|
||||
// return true;
|
||||
}
|
||||
public BeltDirection GetBeltDirection() => _speed.Value switch
|
||||
{
|
||||
0 => BeltDirection.NotMoving,
|
||||
> 0 => BeltDirection.TowardEnd,
|
||||
< 0 => BeltDirection.TowardStart,
|
||||
_ => throw new NotSupportedException($"{nameof(Speed)} with value {Speed} is not Supported")
|
||||
};
|
||||
/// <summary>
|
||||
/// Returns the Item that would be Leading the Belt based off the direction of the belt, or null if no items or Speed is zero.
|
||||
/// </summary>
|
||||
/// <returns>The Leading Item</returns> <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ConveyorSlice? GetFirstItem() => GetBeltDirection() switch
|
||||
{
|
||||
BeltDirection.TowardStart => _items.Any() ? _items[0] : null,
|
||||
BeltDirection.TowardEnd => _items.Any() ? _items[^1] : null,
|
||||
BeltDirection.NotMoving => null,
|
||||
_ => null
|
||||
};
|
||||
/// <summary>
|
||||
/// Returns the Last item that would be oppsite the Leading Item, or null if there is no items or Speed is zero.
|
||||
/// </summary>
|
||||
/// <returns>The last Item in Secuance</returns> <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ConveyorSlice? GetLastItem() => GetBeltDirection() switch
|
||||
{
|
||||
BeltDirection.TowardStart => _items.Any() ? _items[0] : null,
|
||||
BeltDirection.TowardEnd => _items.Any() ? _items[^1] : null,
|
||||
BeltDirection.NotMoving => null,
|
||||
_ => null
|
||||
};
|
||||
public bool AnyItems() => _items.Any();
|
||||
public int ItemsCount => _items.Count;
|
||||
private void InsertInMiddleWithoutCheck(IBeltItem item, float beltT)
|
||||
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_items.Add(new(item, beltT));
|
||||
|
||||
// _items.Sort(Comparer<ConveyorSlice>.Create(
|
||||
// (a, b) => a.BeltT.CompareTo(b.BeltT)));
|
||||
}
|
||||
|
||||
// public float SpaceToInsertFromEnd(ConveyorEnd end)//Needs to know if belt space exists in next/previous
|
||||
// {
|
||||
// var distToEnd = DistanceFromEnd(end);
|
||||
// var other = GetConveyorPortFacing(GetOutputPort());
|
||||
// if (!other.HasValue || other.Value.Port.BeltT != new BeltTEnd(SwapEnd(end)) || other.Value.Port.Direction == PortAccess.Out)
|
||||
// {
|
||||
// return Mathf.Max(0, distToEnd - ITEMHALFSIZE);
|
||||
// }
|
||||
|
||||
// return distToEnd + other.Value.Conveyor.DistanceFromEnd(SwapEnd(end));//Should Be space to inset, but needs to be serpate into 2 functons to prevent stgackoverflow
|
||||
|
||||
// }
|
||||
public bool TryInsertAtEnd(ConveyorEnd end, IBeltItem itemStack)
|
||||
{
|
||||
var space = DistanceFromEnd(end);
|
||||
if (space >= ITEMHALFSIZE)
|
||||
{
|
||||
InsertAtEndWithoutCheck(end, itemStack);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private void InsertAtEndWithoutCheck(ConveyorEnd end, IBeltItem item, float offset = 0)
|
||||
{
|
||||
ConveyorSlice itemPair = new(item, end switch { ConveyorEnd.Start => offset, ConveyorEnd.End => Length - offset, _ => throw new NotSupportedException() });
|
||||
if (end == ConveyorEnd.Start)
|
||||
{
|
||||
_items.Insert(0, itemPair);
|
||||
}
|
||||
else
|
||||
{
|
||||
_items.Add(itemPair);
|
||||
}
|
||||
}
|
||||
private (ItemConveyor Conveyor, ConveyorPort Port)? GetConveyorPortFacing(ConveyorPort port, Predicate<ConveyorPort>? predicate = default)
|
||||
{
|
||||
predicate ??= _ => true;
|
||||
var pos = VoxelPosition + port.LocalOffset + port.Face.ToVector();
|
||||
var others = GridRegistry.Get<ItemConveyor>(pos);
|
||||
foreach (var item in others)
|
||||
{
|
||||
var otherPorts = item._ports.Where(otherPort => otherPort.Face.Reverse() == port.Face && predicate(otherPort));
|
||||
GD.Print("ports "+string.Join(',',otherPorts.Select(i=>i.BeltT)));
|
||||
if (otherPorts.Any())
|
||||
{
|
||||
return (item, otherPorts.First());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public Guid Id { get; set; }
|
||||
[Export]
|
||||
public Vector3I VoxelPosition { get; set; }
|
||||
[Export]
|
||||
public Direction VoxelRotation { get; set; } = Direction.Front;//test
|
||||
|
||||
public IEnumerable<Vector3I> Shape => [Vector3I.Zero];
|
||||
|
||||
private readonly AutoList<ConveyorSlice> _items = [];
|
||||
public IAutoList<ConveyorSlice> Items => _items;
|
||||
public struct ConveyorPort : IBeltSlotProfile// May chagne to record
|
||||
{
|
||||
public Vector3I LocalOffset;
|
||||
public BeltT BeltT;
|
||||
public Direction Face;
|
||||
public TransferMode PullPush;
|
||||
public PortAccess Direction;
|
||||
public Func<IBeltItem, float, bool>? AcceptItemFunc;
|
||||
public Func<IBeltItem, float, bool>? CanAcceptItemFunc;
|
||||
public IMovementConveyor MovementConveyor;
|
||||
public readonly Vector3I Position => LocalOffset;
|
||||
|
||||
public readonly int Width => 1;
|
||||
|
||||
public readonly PortAccess Access => Direction;
|
||||
|
||||
public Guid Id => throw new NotImplementedException();
|
||||
|
||||
public Vector3I VoxelPosition => LocalOffset;
|
||||
|
||||
public Direction VoxelRotation => throw new NotImplementedException();
|
||||
|
||||
public IEnumerable<Vector3I> Shape => [new Vector3I(0, 0, 0)];
|
||||
|
||||
// readonly Direction IBeltSlotProfile.Direction => Face;
|
||||
|
||||
[MemberNotNullWhen(true,nameof(CanAcceptItemFunc))]
|
||||
[MemberNotNullWhen(true,nameof(AcceptItemFunc))]
|
||||
public readonly bool CanAcceptItem(IBeltItem beltItem, LaneSpan laneSpan, float beltT = 0)
|
||||
{
|
||||
if (CanAcceptItemFunc is null || AcceptItemFunc is null || !Access.HasFlag(PortAccess.In) )//|| LaneSpan.Encapsulates(new(0, (ushort)Width), laneSpan))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return CanAcceptItemFunc(beltItem, beltT);
|
||||
}
|
||||
|
||||
|
||||
public readonly bool TryInsertItem(IBeltItem beltItem, LaneSpan laneSpan, float beltT = 0)
|
||||
{
|
||||
if (!CanAcceptItem(beltItem, laneSpan, beltT))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return AcceptItemFunc(beltItem, beltT);
|
||||
}
|
||||
}
|
||||
public abstract record BeltT();
|
||||
public record BeltTEnd(ConveyorEnd End) : BeltT();
|
||||
public record BeltTOffset(float T) : BeltT();
|
||||
// public enum PortAccess : byte
|
||||
// {
|
||||
// In,
|
||||
// Out,
|
||||
// InOut,
|
||||
// BiDirectional = InOut
|
||||
// }
|
||||
// [Flags]
|
||||
// public enum TransferMode : byte//Need Better Name
|
||||
// {
|
||||
// None = 0,
|
||||
// Push = 1,
|
||||
// Pull = 2,
|
||||
// PushPull = Push | Pull
|
||||
// }
|
||||
public interface IBeltMovement
|
||||
{
|
||||
void AdvanceBelt(ItemConveyor conveyor, float delta);
|
||||
void AdvanceBelt(IMovementConveyor conveyor, float delta);
|
||||
// event ItemMoved? OnItemMoved;
|
||||
// delegate void ItemMoved(
|
||||
// IBeltItem item,
|
||||
// ReadOnlySpan<ItemMovementSegment> segments,
|
||||
// float deltaTime
|
||||
// );
|
||||
|
||||
}
|
||||
public sealed class IndividualMovement : IBeltMovement
|
||||
{
|
||||
public void AdvanceBelt(ItemConveyor conveyor, float delta)
|
||||
{
|
||||
if (!conveyor.AnyItems())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var beltDirection = conveyor.GetBeltDirection();
|
||||
if (beltDirection == BeltDirection.NotMoving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var towardStart = beltDirection == BeltDirection.TowardStart;
|
||||
var next = conveyor.GetConveyorPortFacing(conveyor.GetOutputPort());
|
||||
if (towardStart)
|
||||
{
|
||||
for (int i = 0; i < conveyor.ItemsCount; i++)
|
||||
{
|
||||
var possibleItemTravel = conveyor.GetAvailableTravelForFrontItem(next.HasValue);
|
||||
var maxSpeed = Mathf.Min(conveyor.SpeedMagnitude * delta, possibleItemTravel.Value) * Mathf.Sign(conveyor.SignedSpeed);//Or possibly (int)BeltDirection, givn that they stgore the sign as part of the enum
|
||||
if (maxSpeed == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var endT = Mathf.Clamp(conveyor._items[i].BeltT + maxSpeed, 0, conveyor.Length);
|
||||
|
||||
conveyor._items[i] = conveyor._items[i] with { BeltT = endT };
|
||||
if (endT <= 0)
|
||||
{
|
||||
// segments.Add(new(next.Value.Conveyor, ));
|
||||
if (next.Value.Port.TryInsertItem(conveyor._items[i].Item, LaneSpan.One))
|
||||
{
|
||||
GD.Print("Item Moved");
|
||||
conveyor._items.Remove(conveyor._items[i]);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = conveyor.ItemsCount-1; i >=0 ; i--)
|
||||
{
|
||||
var possibleItemTravel = conveyor.GetAvailableTravelForFrontItem(next.HasValue);
|
||||
var maxSpeed = Mathf.Min(conveyor.SpeedMagnitude * delta, possibleItemTravel.Value) * Mathf.Sign(conveyor.SignedSpeed);//Or possibly (int)BeltDirection, givn that they stgore the sign as part of the enum
|
||||
if (maxSpeed == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var endT = Mathf.Clamp(conveyor._items[i].BeltT + maxSpeed, 0, conveyor.Length);
|
||||
|
||||
conveyor._items[i] = conveyor._items[i] with { BeltT = endT };
|
||||
if (endT >= conveyor.Length)
|
||||
{
|
||||
// segments.Add(new(next.Value.Conveyor, ));
|
||||
if (next.Value.Port.TryInsertItem(conveyor._items[i].Item, LaneSpan.One))
|
||||
{
|
||||
GD.Print("Item Moved");
|
||||
conveyor._items.Remove(conveyor._items[i]);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AdvanceBelt(IMovementConveyor conveyor, float delta)
|
||||
{
|
||||
// GD.Print(conveyor.Items);
|
||||
if (conveyor.GetBeltDirection() == BeltDirection.NotMoving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// GD.Print("hello");
|
||||
var towardStart = conveyor.GetBeltDirection() == BeltDirection.TowardStart;
|
||||
foreach (var itemRef in towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd())
|
||||
{
|
||||
float maxMove = conveyor.SpeedMagnitude * delta;
|
||||
var space = conveyor.GetDistanceToNextItem(conveyor.GetBeltDirection(),itemRef.Value.BeltT,maxMove,itemRef.Value.LaneSpan);
|
||||
|
||||
|
||||
// space -= ITEMSIZE;//AcountForSPacing
|
||||
// if ()
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (space is ItemBeltObstacle itemOb && itemOb.Distance <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float amountToMove = Mathf.Min(space.Distance, maxMove);
|
||||
|
||||
itemRef.Replace(itemRef.Value with { BeltT = itemRef.Value.BeltT + amountToMove * Mathf.Sign(conveyor.SignedSpeed)});
|
||||
// GD.PrintS(conveyor.Items.Count,space,amountToMove," "+ itemRef.BeltT,itemRef.Item,conveyor.GetBeltDirection(),towardStart);
|
||||
if (towardStart ? itemRef.Value.BeltT <= 0 : itemRef.Value.BeltT >= conveyor.Length)
|
||||
{
|
||||
var facing = conveyor.GetPortFacing(towardStart ? conveyor.StartPort : conveyor.EndPort);
|
||||
// GD.Print(facing.HasValue(out var slot2),slot2);// , slot2.CanAccept(new TestItem(),LaneSpan.One,0));
|
||||
if (facing.HasValue(out var slot) && slot.TryInsert(itemRef.Value.Item, space is PortBeltObstacle portO ? portO.LaneSpan : itemRef.Value.LaneSpan, 0))
|
||||
{
|
||||
// GD.Print(space is PortBeltObstacle port?port.LaneSpan:itemRef.Value.LaneSpan);
|
||||
// GD.PrintS(itemRef.Value.LaneSpan,itemRef.Value.LaneSpan);
|
||||
// GD.Print(itemRef.Item);
|
||||
itemRef.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public sealed class StrictMovement : IBeltMovement
|
||||
{
|
||||
// public event IBeltMovement.ItemMoved? OnItemMoved;
|
||||
|
||||
public void AdvanceBelt(ItemConveyor conveyor, float delta)
|
||||
{
|
||||
if (!conveyor.AnyItems())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var beltDirection = conveyor.GetBeltDirection();
|
||||
if (beltDirection == BeltDirection.NotMoving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var towardStart = beltDirection == BeltDirection.TowardStart;
|
||||
// GD.Print("Thing: " + Speed + " "+ conveyor.GetConveyorPortFacing(conveyor.GetOutputPort()));
|
||||
// var moveItemsIntoNextBelt = false;
|
||||
var next = conveyor.GetConveyorPortFacing(conveyor.GetOutputPort());
|
||||
var possibleItemTravel = conveyor.GetAvailableTravelForFrontItem(next.HasValue);
|
||||
|
||||
// return;
|
||||
if (!possibleItemTravel.HasValue)
|
||||
{
|
||||
//There is no items, this should not be called
|
||||
throw new NotSupportedException("This should not e possible if there is no items or the belt is not moving, it should have returend before");
|
||||
}
|
||||
// if (next.HasValue && next.Value.Port.BeltT is BeltTEnd beltTEnd && (next.Value.Port.Direction == PortAccess.In || next.Value.Port.Direction == PortAccess.BiDirectional))
|
||||
// {
|
||||
// possibleItemTravel += next.Value.Conveyor.DistanceFromEnd(beltTEnd.End);
|
||||
// moveItemsIntoNextBelt = true;
|
||||
// }
|
||||
|
||||
|
||||
// var itemPair = conveyor.GetFirstItem();
|
||||
// var distToEnd = conveyor.GetAvailableTravel(conveyor.ItemsCount - 1, beltDirection);//(Speed < 0 ? ConveyorEnd.Start : ConveyorEnd.End);
|
||||
var maxSpeed = Mathf.Min(conveyor.SpeedMagnitude * delta, possibleItemTravel.Value) * Mathf.Sign(conveyor.SignedSpeed);//Or possibly (int)BeltDirection, givn that they stgore the sign as part of the enum
|
||||
if (maxSpeed == 0)
|
||||
{
|
||||
// var fI = conveyor.GetAvailableTravelForFrontItem();
|
||||
// if (fI.HasValue && fI.Value <= 0 && next.Value.Port.TryInsertItem(conveyor.GetFirstItem().Value.Item, LaneSpan.One))
|
||||
// {
|
||||
// GD.Print("Item Moved");
|
||||
// conveyor._items.Remove(conveyor.GetFirstItem().Value);
|
||||
// }
|
||||
|
||||
return;
|
||||
}
|
||||
List<ConveyorSlice> toMove = [];
|
||||
// Dictionary<IBeltItem, List<ItemMovementSegment>> moves = [];
|
||||
for (int i = 0; i < conveyor.ItemsCount; i++)
|
||||
{
|
||||
// List<ItemMovementSegment> segments = [];
|
||||
// var startT = conveyor._items[i].BeltT;
|
||||
var endT = Mathf.Clamp(conveyor._items[i].BeltT + maxSpeed, 0, conveyor.Length);
|
||||
|
||||
conveyor._items[i] = conveyor._items[i] with { BeltT = endT };
|
||||
// segments.Add(new(conveyor, startT, towardStart? Mathf.Max(endT, 0) : Mathf.Min(endT, conveyor.Length),Mathf.Abs(maxSpeed)));
|
||||
if (towardStart ? (endT <= 0) : endT >= conveyor.Length)
|
||||
{
|
||||
toMove.Add(conveyor._items[i]);
|
||||
// segments.Add(new(next.Value.Conveyor, ));
|
||||
}
|
||||
// moves.Add(conveyor._items[i].Item, segments);
|
||||
|
||||
GD.Print($"Item: {conveyor._items[i].Item}, Position:{endT}");
|
||||
}
|
||||
// conveyor._items.ForEach((item) => item = item = Mathf.Clamp(item.Position + maxSpeed, 0, Length));
|
||||
// var list = _items.Where(item=> Speed>=0?item.BeltT >= Length:item.BeltT <= 0).ToList();
|
||||
// GD.Print(list.Count);
|
||||
if (next.HasValue)
|
||||
{
|
||||
foreach (var item in toMove)
|
||||
{
|
||||
GD.Print($"Item: {item.Item}, Position:{item.BeltT} -----------------");
|
||||
//TEST
|
||||
// var v = GetConveyorPortFacing(GetOutputPort());
|
||||
// if (v.HasValue)//Should be true if items are > Length
|
||||
// {
|
||||
// GD.PrintS(v.Value.Port.LocalOffset,v.Value.Port.BeltT,v.Value.Port.Direction,v.Value.Port.PullPush,v.Value.Port.Face);
|
||||
// var end = (next.Value.Port.BeltT as BeltTEnd).End;
|
||||
// next.Value.Conveyor.InsertAtEndWithoutCheck(end, item.Item);
|
||||
if (next.Value.Port.TryInsertItem(item.Item, item.LaneSpan))
|
||||
{
|
||||
GD.Print("Item Moved");
|
||||
conveyor._items.Remove(item);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AdvanceBelt(IMovementConveyor conveyor, float delta) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct ConveyorSlice(IBeltItem item, float beltT = 0)
|
||||
{
|
||||
public IBeltItem Item = item;
|
||||
public float BeltT = beltT;
|
||||
public LaneSpan LaneSpan = LaneSpan.One;
|
||||
public override string ToString() => $"{Item}, {BeltT}, {LaneSpan}";
|
||||
|
||||
}
|
||||
public readonly struct LaneSpan : IEquatable<LaneSpan>
|
||||
{
|
||||
public readonly ushort Start;
|
||||
public readonly ushort End;
|
||||
public readonly int Width => End - Start;
|
||||
public static readonly LaneSpan One = new(0, 1);
|
||||
public static readonly LaneSpan Zero = new(0, 0);
|
||||
public LaneSpan(ushort start, ushort end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
// [MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static bool OverLaps(LaneSpan a, LaneSpan b) => a.Start < b.End && b.Start < a.End;
|
||||
// [MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static LaneSpan Shifted(LaneSpan a, int amount) => checked(new LaneSpan((ushort)(a.Start + amount), (ushort)(a.End + amount)));
|
||||
public static bool Encapsulates(LaneSpan a, LaneSpan b) => b.Start>=a.Start && b.End<= b.End;
|
||||
public override string ToString() => $"(Start:{Start}, End:{End})";
|
||||
public bool Equals(LaneSpan other) => Start == other.Start && End == other.End;
|
||||
public static bool operator ==(LaneSpan a, LaneSpan b) => a.Equals(b);
|
||||
public static bool operator !=(LaneSpan a, LaneSpan b) => !a.Equals(b);
|
||||
}
|
||||
public static class Extersions
|
||||
{
|
||||
public static ItemConveyor.BeltDirection DirectionTo(this ItemConveyor.ConveyorEnd end) => end switch
|
||||
{
|
||||
ItemConveyor.ConveyorEnd.Start => ItemConveyor.BeltDirection.TowardStart,
|
||||
ItemConveyor.ConveyorEnd.End => ItemConveyor.BeltDirection.TowardEnd,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
1
src/VoxelGrid/ItemConveyor.cs.uid
Normal file
1
src/VoxelGrid/ItemConveyor.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cx35jfqkjnou8
|
||||
131
src/VoxelGrid/SlotComponentNode.cs
Normal file
131
src/VoxelGrid/SlotComponentNode.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class SlotComponentNode
|
||||
: Node3D , IEquipmentComponent
|
||||
{
|
||||
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
[Export] public SlotDirection Direction;
|
||||
[Export] public Vector3I LocalCellOffset;
|
||||
|
||||
[Dependency] protected IVoxelGridQuery<LayeredEquipment> Grid => this.DependOn<IVoxelGridQuery<LayeredEquipment>>();
|
||||
|
||||
[Dependency] protected IEquipmentContext EquipmentContext =>this.DependOn<IEquipmentContext>();
|
||||
[Dependency] protected IEquipmentComponentRegistry ComponentRegistry => this.DependOn<IEquipmentComponentRegistry>();
|
||||
|
||||
public EquipmentId EquipmentId => EquipmentContext.GetEquipmentId();
|
||||
|
||||
// SO instead of using an hidden list of compeonts on the equimpent, have the equpment provide an reggistery that compnents depend on to register themselves to, and allow for muitple of the same type.
|
||||
public void OnResolved()
|
||||
{
|
||||
GD.PrintS(EquipmentContext, Grid);
|
||||
EquipmentContext.GetEquipment().Tick += Tick;
|
||||
ComponentRegistry.Register(this);
|
||||
GD.Print("Added Tick Listener");
|
||||
}
|
||||
private void Tick()
|
||||
{
|
||||
Grid.GetVoxel(EquipmentContext.GetEquipment().GridPos + LocalCellOffset/*TODO Acount for Direction*/).IfAny(e =>
|
||||
{
|
||||
var slots = e.SelectMany(ee => ee.GetChildren().OfType<SlotComponentNode>().Where(_=> true/*Where slot faces this one and is not output*/)).ToList();
|
||||
// slots.First().AcceptItem(new TestItem());//For testing accuming adding item, should probly sort and cascade so progatrion stops when out of items, but should only be one result, as each componet is one face
|
||||
});
|
||||
// GD.Print("Tryied moveing items test");
|
||||
}
|
||||
public bool AcceptItem(IBeltItem itemStack)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
public override void _ExitTree()
|
||||
{
|
||||
base._ExitTree();
|
||||
EquipmentContext.GetEquipment().Tick -= Tick;
|
||||
ComponentRegistry.Unregister(this);
|
||||
}
|
||||
|
||||
// public Vector3I GetSlotCell() {
|
||||
// return Equipment.GetOccupiedCell() + LocalCellOffset;
|
||||
// }
|
||||
|
||||
// public Vector3 GetSlotWorldPosition() {
|
||||
// return Grid.CellToWorld(GetSlotCell());
|
||||
// }
|
||||
}
|
||||
public interface IEquipmentComponentRegistry {
|
||||
void Register<T>(T component)
|
||||
where T : class, IEquipmentComponent;
|
||||
|
||||
void Unregister<T>(T component)
|
||||
where T : class, IEquipmentComponent;
|
||||
|
||||
T? Get<T>(EquipmentId id)
|
||||
where T : class, IEquipmentComponent;
|
||||
|
||||
IReadOnlyList<T> GetAll<T>(EquipmentId id)
|
||||
where T : class, IEquipmentComponent;
|
||||
}public sealed class EquipmentComponentRegistry
|
||||
: IEquipmentComponentRegistry {
|
||||
|
||||
private readonly Dictionary<
|
||||
EquipmentId,
|
||||
Dictionary<Type, List<object>>
|
||||
> _map = new();
|
||||
|
||||
public void Register<T>(T component)
|
||||
where T : class, IEquipmentComponent {
|
||||
GD.Print(component);
|
||||
if (!_map.TryGetValue(component.EquipmentId, out var types)) {
|
||||
types = new();
|
||||
_map[component.EquipmentId] = types;
|
||||
}
|
||||
|
||||
var type = typeof(T);
|
||||
|
||||
if (!types.TryGetValue(type, out var list)) {
|
||||
list = new();
|
||||
types[type] = list;
|
||||
}
|
||||
|
||||
list.Add(component);
|
||||
}
|
||||
|
||||
public T? Get<T>(EquipmentId id)
|
||||
where T : class, IEquipmentComponent {
|
||||
|
||||
if (_map.TryGetValue(id, out var types) &&
|
||||
types.TryGetValue(typeof(T), out var list))
|
||||
return list[0] as T;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IReadOnlyList<T> GetAll<T>(EquipmentId id)
|
||||
where T : class, IEquipmentComponent {
|
||||
|
||||
if (_map.TryGetValue(id, out var types) &&
|
||||
types.TryGetValue(typeof(T), out var list))
|
||||
return list.Cast<T>().ToList();
|
||||
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
public void Unregister<T>(T component)
|
||||
where T : class, IEquipmentComponent {
|
||||
|
||||
if (_map.TryGetValue(component.EquipmentId, out var types) &&
|
||||
types.TryGetValue(typeof(T), out var list))
|
||||
list.Remove(component);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IEquipmentComponent {
|
||||
EquipmentId EquipmentId { get; }
|
||||
}
|
||||
1
src/VoxelGrid/SlotComponentNode.cs.uid
Normal file
1
src/VoxelGrid/SlotComponentNode.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://drsqsmj0bn1ob
|
||||
250
src/VoxelGrid/SlotFace.cs
Normal file
250
src/VoxelGrid/SlotFace.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using Chickensoft.GodotNodeInterfaces;
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.AutoInject;
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ChickenGameTest;
|
||||
|
||||
public interface ISlotFace : IVoxelNode // INode3D,
|
||||
{
|
||||
ISlotFace GetConnectingFace();
|
||||
|
||||
}
|
||||
[Tool]
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class SlotFace : Node3D, ISlotFace
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public IEnumerable<Vector3I> Shape => [Vector3I.Zero];
|
||||
|
||||
[Export] public Vector3I VoxelPosition { get; set; }
|
||||
[Export] public Direction VoxelRotation { get; set; }
|
||||
[Export] public bool ShowDebug { get; set; } = false;
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
|
||||
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
|
||||
public void OnResolved()
|
||||
{
|
||||
GridRegistry.Register(this);
|
||||
GD.Print("Regestered");
|
||||
}
|
||||
public ISlotFace GetConnectingFace()//TODO there could be tecnaly muiple slots shareing a face, and should be acounted for
|
||||
{
|
||||
var nodes = GridRegistry.Get<ISlotFace>(VoxelPosition + VoxelRotation.ToVector());//TODO Acoount for rotation to get slot direction
|
||||
// GD.Print(nodes.Count());
|
||||
foreach (var item in nodes)
|
||||
{
|
||||
if (item.VoxelRotation.Reverse() == VoxelRotation)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static Vector3[] _Square = [new(-.5f, .5f, -.5f), new(.5f, .5f, -.5f), new(.5f, -.5f, -.5f), new(-.5f, -.5f, -.5f), new(-.5f, .5f, -.5f)];
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (ShowDebug)
|
||||
{
|
||||
ShowDebugFace();
|
||||
}
|
||||
}
|
||||
private void ShowDebugFace()
|
||||
{
|
||||
|
||||
var color = Colors.Red;
|
||||
var pos = ToGlobal(new Vector3(0, 0, -.5f));
|
||||
if (!Engine.IsEditorHint())
|
||||
{
|
||||
color = GetConnectingFace() is null ? Colors.Red : Colors.Green;
|
||||
}
|
||||
// var text = $"SlotFace{Name}";
|
||||
// GD.PrintS(color);
|
||||
DebugDraw3D.DrawLinePath([.. _Square.Select(ToGlobal)],color);
|
||||
// DebugDraw3D.DrawText(pos, text, 16);
|
||||
}
|
||||
}
|
||||
public interface IVoxelGridRegistry
|
||||
{
|
||||
[Obsolete]
|
||||
void Register<T>(T voxelNode) where T : IVoxelNode;
|
||||
void Register<T>(T voxelNode, params Vector3I[] positions);
|
||||
void UnRegister<T>(T voxelNode) where T : IVoxelNode=> UnRegister(voxelNode.Id);
|
||||
void UnRegister<T>(T voxelNode, params Vector3I[] positions);
|
||||
void UnRegister(Guid id);
|
||||
IEnumerable<T> Get<T>(Vector3I voxelPos);
|
||||
IEnumerable<T> Get<T>(params Vector3I[] voxelPos);
|
||||
|
||||
}
|
||||
public sealed class VoxelRegistry : IVoxelGridRegistry
|
||||
{
|
||||
private Dictionary<Vector3I, ICollection<object>> _data;
|
||||
public VoxelRegistry()
|
||||
{
|
||||
_data = new();
|
||||
}
|
||||
public IEnumerable<T> Get<T>(Vector3I voxelPos)
|
||||
{
|
||||
if (!_data.TryGetValue(voxelPos, out var entrys))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
foreach (var item in entrys.OfType<T>())
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> Get<T>(params Vector3I[] voxelPos)
|
||||
{
|
||||
foreach (var item in voxelPos)
|
||||
{
|
||||
foreach (var item2 in Get<T>(item))
|
||||
{
|
||||
yield return item2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Register<T>(T voxelNode) where T : IVoxelNode
|
||||
{
|
||||
void register(Vector3I pos, T point)
|
||||
{
|
||||
if (!_data.TryGetValue(pos, out var entrys))
|
||||
{
|
||||
entrys = [];
|
||||
_data[pos] = entrys;
|
||||
}
|
||||
entrys.Add(point);
|
||||
}
|
||||
voxelNode.Shape.ToList().ForEach(
|
||||
item =>
|
||||
{
|
||||
register(item + voxelNode.VoxelPosition, voxelNode);//TODO Acount For Rotation
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void Register<T>(T voxelNode, params Vector3I[] positions)
|
||||
{
|
||||
void register(Vector3I pos, T point)
|
||||
{
|
||||
if (!_data.TryGetValue(pos, out var entrys))
|
||||
{
|
||||
entrys = [];
|
||||
_data[pos] = entrys;
|
||||
}
|
||||
entrys.Add(point);
|
||||
}
|
||||
foreach (var pos in positions)
|
||||
{
|
||||
if (positions.Length>1){
|
||||
GD.Print("hhhhhhhhh ",pos);}
|
||||
register(pos, voxelNode);//TODO Acount For Rotation
|
||||
}
|
||||
}
|
||||
public void UnRegister(Guid id) => throw new NotImplementedException();
|
||||
public void UnRegister<T>(T voxelNode, params Vector3I[] positions)
|
||||
{
|
||||
void unRegister(Vector3I pos, T point)
|
||||
{
|
||||
if (!_data.TryGetValue(pos, out var entrys))
|
||||
{
|
||||
return;
|
||||
}
|
||||
entrys.Remove(point);
|
||||
}
|
||||
foreach (var item in positions)
|
||||
{
|
||||
unRegister(item, voxelNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
public interface IVoxelNode
|
||||
{
|
||||
Guid Id { get; }
|
||||
Vector3I VoxelPosition { get; }
|
||||
Direction VoxelRotation { get; } //TODO replace with struct/record for static rotation typeing
|
||||
IEnumerable<Vector3I> Shape { get; }//TOPO make SHape able to account for occpancy/ partial filled voxels.
|
||||
}
|
||||
|
||||
public enum Direction
|
||||
{
|
||||
Up, Down, Left, Right, Front, Back
|
||||
}
|
||||
public static class DirectionExtession
|
||||
{
|
||||
public static Vector3I ToVector(this Direction direction) => direction switch
|
||||
{
|
||||
Direction.Up => Vector3I.Up,
|
||||
Direction.Down => Vector3I.Down,
|
||||
Direction.Left => Vector3I.Left,
|
||||
Direction.Right => Vector3I.Right,
|
||||
Direction.Front => Vector3I.Forward,
|
||||
Direction.Back => Vector3I.Back,
|
||||
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
|
||||
};
|
||||
public static Direction Reverse(this Direction direction) => direction switch
|
||||
{
|
||||
Direction.Up => Direction.Down,
|
||||
Direction.Down => Direction.Up,
|
||||
Direction.Left => Direction.Right,
|
||||
Direction.Right => Direction.Left,
|
||||
Direction.Front => Direction.Back,
|
||||
Direction.Back => Direction.Front,
|
||||
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
|
||||
};
|
||||
public static Direction RotateClockWise(this Direction direction) => direction switch
|
||||
{
|
||||
Direction.Up => Direction.Up,
|
||||
Direction.Down => Direction.Down,
|
||||
Direction.Left => Direction.Back,
|
||||
Direction.Right => Direction.Front,
|
||||
Direction.Front => Direction.Right,
|
||||
Direction.Back => Direction.Left,
|
||||
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
|
||||
};
|
||||
public static Direction RotateCounterClockWise(this Direction direction) => direction switch
|
||||
{
|
||||
Direction.Up => Direction.Up,
|
||||
Direction.Down => Direction.Down,
|
||||
Direction.Right => Direction.Front,
|
||||
Direction.Left => Direction.Back,
|
||||
Direction.Back => Direction.Right,
|
||||
Direction.Front => Direction.Left,
|
||||
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
|
||||
};
|
||||
}
|
||||
public interface IStorage<T> where T : class
|
||||
{
|
||||
StorageDefinition Definition { get; }
|
||||
IEnumerable<T> GetAllItems();
|
||||
void Remove(T item);
|
||||
bool TryInsert(T item);
|
||||
|
||||
record StorageDefinition(string Name, ItemType Type);
|
||||
}
|
||||
public record ItemType(string Name);
|
||||
public partial class ItemStoragePool : Node, IStorage<IBeltItem>
|
||||
{
|
||||
[Export] public int MaxAmount { get; set; } = 5;
|
||||
public IStorage<IBeltItem>.StorageDefinition Definition { get; set; } = default!;
|
||||
private readonly List<IBeltItem> _items = [];
|
||||
public IEnumerable<IBeltItem> GetAllItems() => _items;
|
||||
public bool TryInsert(IBeltItem item)
|
||||
{
|
||||
if (_items.Count >= MaxAmount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_items.Add(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Remove(IBeltItem item) => _items.Remove(item);
|
||||
}
|
||||
1
src/VoxelGrid/SlotFace.cs.uid
Normal file
1
src/VoxelGrid/SlotFace.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dao8u7hn0yadp
|
||||
711
src/VoxelGrid/TestItemConveyor.cs
Normal file
711
src/VoxelGrid/TestItemConveyor.cs
Normal file
@@ -0,0 +1,711 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.AutoInject;
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Chickensoft.Sync.Primitives;
|
||||
using SJK.Functional;
|
||||
|
||||
public class Sorted1DList<T>
|
||||
{
|
||||
private readonly AutoList<T> _items = [];
|
||||
#if DEBUG
|
||||
private readonly AutoList<T>.Binding _binding;
|
||||
#endif
|
||||
private readonly Func<T, float> _getPosition;
|
||||
public Sorted1DList(Func<T, float> getPos)
|
||||
{
|
||||
_getPosition = getPos;
|
||||
// return;
|
||||
#if DEBUG
|
||||
_binding = _items.Bind();
|
||||
_binding.OnUpdate((a, b, c) =>
|
||||
{
|
||||
// GD.PrintS("++++++++++++++++",a,b,c);
|
||||
});
|
||||
// return;
|
||||
_binding.OnModify(() =>
|
||||
{
|
||||
for (int i = 1; i < _items.Count; i++)
|
||||
{
|
||||
if (_getPosition(_items[i - 1]) >= _getPosition(_items[i]))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"AutoList not sorted at index {i - 1} → {i}. ({_getPosition(_items[i-1])},{_getPosition(_items[i])})");
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
}
|
||||
public int Count => _items.Count;
|
||||
public IAutoList<T> Items => _items;
|
||||
public IEnumerable<ItemHandle> EnumerateTowardEnd(int? startIndex = null)
|
||||
{
|
||||
if (startIndex.HasValue && startIndex >= Count)
|
||||
{
|
||||
throw new IndexOutOfRangeException($"{nameof(startIndex)}:{startIndex} can not be greater then or equal to {nameof(Count)}:{Count}");
|
||||
}
|
||||
for (int i = Mathf.Max(0,startIndex ?? 0); i < _items.Count; i++)
|
||||
{
|
||||
bool removed = false;
|
||||
yield return new(_items[i],
|
||||
() =>
|
||||
{
|
||||
if (removed)
|
||||
{
|
||||
throw new NotSupportedException($"{nameof(ItemHandle.Remove)} can only be called once per item.");
|
||||
}
|
||||
removed = true;
|
||||
_items.RemoveAt(i);
|
||||
i--;
|
||||
},
|
||||
(replaced) =>
|
||||
{
|
||||
if (removed)
|
||||
{
|
||||
throw new NotSupportedException("Can not Replace an Item after Removing it");
|
||||
}
|
||||
_items[i] = replaced;
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
public IEnumerable<ItemHandle> EnumerateTowardStart(int? startIndex = null)
|
||||
{
|
||||
if (startIndex.HasValue && startIndex < 0)
|
||||
{
|
||||
throw new IndexOutOfRangeException($"{nameof(startIndex)}:{startIndex} can not be less than zero");
|
||||
}
|
||||
for (int i = Math.Min(startIndex ?? (_items.Count - 1), _items.Count -1); i>=0 ; i--)
|
||||
{
|
||||
bool removed = false;
|
||||
yield return new(_items[i],
|
||||
() =>
|
||||
{
|
||||
if (removed)
|
||||
{
|
||||
throw new NotSupportedException($"{nameof(ItemHandle.Remove)} can only be called once per item.");
|
||||
}
|
||||
removed = true;
|
||||
_items.RemoveAt(i);
|
||||
// i--;
|
||||
},
|
||||
(replaced) =>
|
||||
{
|
||||
if (removed)
|
||||
{
|
||||
throw new NotSupportedException("Can not Replace an Item after Removing it");
|
||||
}
|
||||
|
||||
if (i-1>=0 && _getPosition(_items[i-1]) >= _getPosition(replaced))
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
if (i+1<Count && _getPosition(_items[i+1]) <= _getPosition(replaced))
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
_items[i] = replaced;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
public void Insert(T item)
|
||||
{
|
||||
float pos = _getPosition(item);
|
||||
if (_items.Count == 0 || pos >= _getPosition(_items[^1]))
|
||||
{
|
||||
_items.Add(item);
|
||||
return;
|
||||
}
|
||||
if (pos <=_getPosition(_items[0]))
|
||||
{
|
||||
_items.Insert(0, item);
|
||||
return;
|
||||
}
|
||||
var index = LowerBound(pos);
|
||||
_items.Insert(index, item);
|
||||
}
|
||||
private int LowerBound(float pos)
|
||||
{
|
||||
int lo = 0;
|
||||
int hi = _items.Count;
|
||||
|
||||
while (lo < hi)
|
||||
{
|
||||
int mid = (lo + hi) >> 1;
|
||||
|
||||
if (_getPosition(_items[mid]) < pos)
|
||||
{
|
||||
lo = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
hi = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return lo;
|
||||
}
|
||||
public (Option<T> lower, Option<T> upper) GetNeighbors(float pos)
|
||||
{
|
||||
int index = LowerBound(pos);
|
||||
|
||||
var lower = index > 0 ? Option<T>.Some(_items[index - 1]) : Option<T>.None;
|
||||
var upper = index < _items.Count ? Option<T>.Some(_items[index]) : Option<T>.None;
|
||||
|
||||
return (lower, upper);
|
||||
}
|
||||
public readonly struct ItemHandle
|
||||
{
|
||||
private readonly Action _remove;
|
||||
private readonly Action<T> _replace;
|
||||
public T Value { get; }
|
||||
public ItemHandle(T value, Action remove, Action<T> replace)
|
||||
{
|
||||
Value = value;
|
||||
_remove = remove;
|
||||
_replace = replace;
|
||||
}
|
||||
public void Remove() => _remove();
|
||||
public void Replace(T slice) => _replace(slice);
|
||||
}
|
||||
}
|
||||
[Meta(typeof(IAutoNode))][Tool]
|
||||
public partial class TestItemConveyor : Node, IMovementConveyor
|
||||
{
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
[Dependency] public ItemConveyor.IBeltMovement MovementSystem => this.DependOn<ItemConveyor.IBeltMovement>(() => new ItemConveyor.IndividualMovement());
|
||||
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
|
||||
// private readonly AutoList<ConveyorSlice> _items = [];
|
||||
// public IAutoList<ConveyorSlice> Items => _items;
|
||||
public readonly Sorted1DList<ConveyorSlice> Items = new(pos => pos.BeltT);
|
||||
// private AutoList<ConveyorSlice>.Binding _itemsBinding;
|
||||
public IBeltPort StartPort { get; set; }
|
||||
// public IBeltSlotProfile StartPort => new ItemConveyor.ConveyorPort() {
|
||||
// Face = Direction.Back,
|
||||
// MovementConveyor = this,
|
||||
// LocalOffset = Position,
|
||||
// BeltT = new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
|
||||
// PullPush = TransferMode.PushPull ,
|
||||
// Direction = PortAccess.BiDirectional,
|
||||
// AcceptItemFunc = (item,offset)=>{ _items.Insert(0,new ConveyorSlice(item,offset)); return true;},
|
||||
// CanAcceptItemFunc = (item, offset) =>
|
||||
// {
|
||||
// return .25f <= GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, 5f, LaneSpan.One);
|
||||
// }
|
||||
// };
|
||||
public IBeltPort EndPort { get; set; }
|
||||
// public IBeltSlotProfile EndPort => new ItemConveyor.ConveyorPort() {
|
||||
// Face = Direction.Front,
|
||||
// MovementConveyor = this,
|
||||
// Direction = PortAccess.BiDirectional,
|
||||
// LocalOffset = Position,
|
||||
// BeltT = new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
|
||||
// PullPush = TransferMode.PushPull,
|
||||
// AcceptItemFunc = (item,offset)=>{ _items.Add(new ConveyorSlice(item,Length-offset)); return true;},
|
||||
// CanAcceptItemFunc = (item,offset)=> .25f >= GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart,Length,.5f,LaneSpan.One)
|
||||
// };
|
||||
public IList<IBeltPort> OtherPorts = [];
|
||||
// [Export] public Vector3I Position { get; set; } = default!;
|
||||
//Speed per unit time
|
||||
private readonly AutoValue<float> _speed = new(1);
|
||||
public IAutoValue<float> SpeedValue => _speed;
|
||||
|
||||
public float SignedSpeed
|
||||
{
|
||||
get => _speed.Value;
|
||||
set => _speed.Value = value;
|
||||
}
|
||||
|
||||
public float SpeedMagnitude
|
||||
{
|
||||
get => Mathf.Abs(_speed.Value);
|
||||
set => _speed.Value = Mathf.Abs(value) * Mathf.Sign(_speed.Value);
|
||||
}
|
||||
public bool IsReversed { get => _speed.Value < 0; set => _speed.Value = SpeedMagnitude * (value ? -1 : 1); }
|
||||
// public IList<ConveyorSlice> Items => _items;
|
||||
public float Length { get; set; } = 1;
|
||||
|
||||
// public IEnumerable<ConveyorSlice> EnumerateTowardEnd() => _items;
|
||||
|
||||
// public IEnumerable<ConveyorSlice> EnumerateTowardStart() => _items.Reverse();
|
||||
public ItemConveyor.BeltDirection GetBeltDirection() => IsReversed ? ItemConveyor.BeltDirection.TowardStart : ItemConveyor.BeltDirection.TowardEnd;
|
||||
public ItemConveyor.IBeltMovement GetMovementPolicy() => MovementSystem;
|
||||
public IEnumerable<IBeltPort> GetPorts() => [StartPort, EndPort, .. OtherPorts];
|
||||
// public IEnumerable<IBeltSlotProfile> GetPorts() => [StartPort, EndPort,
|
||||
// new ItemConveyor.ConveyorPort(){
|
||||
// BeltT = new ItemConveyor.BeltTOffset(.5f),
|
||||
// Face = Direction.Right,
|
||||
// Direction = PortAccess.InOut,
|
||||
// PullPush = TransferMode.Passive,
|
||||
// MovementConveyor = this,
|
||||
// LocalOffset = Position,
|
||||
// },
|
||||
// new ItemConveyor.ConveyorPort(){
|
||||
// BeltT = new ItemConveyor.BeltTOffset(.5f),
|
||||
// Face = Direction.Left,
|
||||
// Direction = PortAccess.InOut,
|
||||
// PullPush = TransferMode.Passive,
|
||||
// MovementConveyor = this,
|
||||
// LocalOffset = Position,
|
||||
// },
|
||||
// new ItemConveyor.ConveyorPort(){
|
||||
// BeltT = new ItemConveyor.BeltTOffset(.5f),
|
||||
// Face = Direction.Up,
|
||||
// Direction = PortAccess.In,
|
||||
// PullPush = TransferMode.Passive,
|
||||
// MovementConveyor = this,
|
||||
// LocalOffset = Position,
|
||||
// }
|
||||
// ];Path3D.Curve.SampleBakedWithRotation(b.BeltT).Basis.X*b.Item.Width
|
||||
public void OnResolved()
|
||||
{
|
||||
if (Engine.IsEditorHint())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var timer = new Timer() { WaitTime = .05f, Autostart = true };
|
||||
AddChild(timer);
|
||||
timer.Timeout += () => MovementSystem.AdvanceBelt(this, (float)timer.WaitTime);
|
||||
// Items.Add(new ConveyorSlice(new TestItem(), 0));
|
||||
// StartPort = new ConveyorPort(this,
|
||||
// new BeltPortProfile(Position, Direction.Back, 1, PortAccess.BiDirectional),
|
||||
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
|
||||
// (item, offset) =>
|
||||
// {
|
||||
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, offset, LaneSpan.One);
|
||||
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
// },
|
||||
// (item, offset) => { _items.Insert(0, new(item, offset)); return true;});
|
||||
// EndPort = new ConveyorPort(this,
|
||||
// new BeltPortProfile(Position, Direction.Front, 1, PortAccess.BiDirectional),
|
||||
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
|
||||
// (item, offset) =>
|
||||
// {
|
||||
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, Length, offset, LaneSpan.One);
|
||||
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
// // return ItemConveyor.ITEMSIZE < GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, Length, SpeedMagnitude, LaneSpan.One);
|
||||
// },
|
||||
// (item, offset) => { _items.Add(new(item, offset)); return true;});
|
||||
if (StartPort is null || EndPort is null)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
foreach (var item in GetPorts())
|
||||
{
|
||||
var points = item.Points();
|
||||
GridRegistry.Register(item, [.. points]);
|
||||
}
|
||||
StartPort.TryInsert(new TestItem(), LaneSpan.Shifted(LaneSpan.One,0), 0);
|
||||
// StartPort.TryInsert(new TestItem(), LaneSpan.Sh fted(LaneSpan.One,1), 0);
|
||||
// Items.Insert(new ConveyorSlice(new TestItem(), 0));
|
||||
// _items.Add(new (new TestItem()));
|
||||
}
|
||||
public override void _Ready()
|
||||
{
|
||||
// _itemsBinding = _items.Bind();
|
||||
// _itemsBinding.OnModify(() =>
|
||||
// {
|
||||
|
||||
// });
|
||||
// OnResolved();
|
||||
base._Ready();
|
||||
//test
|
||||
|
||||
}
|
||||
//ChatGPT Assisted
|
||||
public BeltObstacle GetDistanceToNextItem(
|
||||
ItemConveyor.BeltDirection beltDirection,
|
||||
float itemBeltT,
|
||||
float maxDistToCheck,
|
||||
LaneSpan laneSpan,
|
||||
HashSet<IMovementConveyor>? visited = null)
|
||||
{
|
||||
if (beltDirection == ItemConveyor.BeltDirection.NotMoving)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
visited ??= [];
|
||||
if (!visited.Add(this))
|
||||
{
|
||||
return new BeltObstacle(0);
|
||||
}
|
||||
|
||||
bool towardStart = beltDirection == ItemConveyor.BeltDirection.TowardStart;
|
||||
|
||||
// 1️⃣ Try to find next item on this conveyor
|
||||
var nextItem = FindNextLocalItem(towardStart, itemBeltT, laneSpan);
|
||||
// GD.Print(nextItem);
|
||||
if (nextItem.HasValue(out var item))
|
||||
{
|
||||
return new ItemBeltObstacle(MathF.Abs(itemBeltT - item.BeltT) - ItemConveyor.ITEMSIZE, item, laneSpan);
|
||||
// return MathF.Abs(itemBeltT - item.BeltT);//TODO UseLaneSpan inadditon
|
||||
}
|
||||
|
||||
// 2️⃣ No local item → try crossing into adjacent conveyor
|
||||
return GetDistanceAcrossPort(
|
||||
towardStart,
|
||||
itemBeltT,
|
||||
maxDistToCheck,
|
||||
laneSpan,
|
||||
visited
|
||||
);
|
||||
}
|
||||
//ChatGPT Assisted
|
||||
private IOption<ConveyorSlice> FindNextLocalItem(
|
||||
bool towardStart,
|
||||
float itemBeltT,
|
||||
LaneSpan laneSpan)
|
||||
{
|
||||
var sequence = towardStart
|
||||
? EnumerateTowardStart()
|
||||
: EnumerateTowardEnd();
|
||||
|
||||
return sequence
|
||||
.Where(i => towardStart ? i.Value.BeltT < itemBeltT : i.Value.BeltT > itemBeltT)
|
||||
.Where(i => LaneSpan.OverLaps(i.Value.LaneSpan, laneSpan))
|
||||
.Select(item => item.Value)
|
||||
.FirstOrNone();
|
||||
}
|
||||
//ChatGPT Assisted
|
||||
private BeltObstacle GetDistanceAcrossPort(
|
||||
bool towardStart,
|
||||
float itemBeltT,
|
||||
float maxDistToCheck,
|
||||
LaneSpan laneSpan,
|
||||
HashSet<IMovementConveyor> visited)
|
||||
{
|
||||
var port = towardStart ? StartPort : EndPort;
|
||||
var boundaryT = towardStart ? 0f : Length;
|
||||
var distanceToBoundary = MathF.Abs(itemBeltT - boundaryT);
|
||||
|
||||
// 🚫 Boundary already exceeds budget
|
||||
// if (distanceToBoundary >= maxDistToCheck)
|
||||
// {
|
||||
// return maxDistToCheck;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
var neighborPort = FindFacingConveyorPort(port);
|
||||
if (!neighborPort.HasValue(out var conveyorPort))
|
||||
{
|
||||
var o = GetPortFacing(port);
|
||||
if (o.HasValue(out var otherport))
|
||||
{
|
||||
var mappedLane2 = MapLaneOrFail(port, otherport, laneSpan);
|
||||
if (mappedLane2.HasValue(out var lane2))
|
||||
{
|
||||
return new PortBeltObstacle(distanceToBoundary,lane2,otherport);
|
||||
// return new BeltObstacle(ObstacleKind.Boundary,distanceToBoundary+1,lane1,this,null, null);//TODO THIS SHOULD GET SPACE IN PORT SO ITEMS KNOW IF THEY CAN FIT
|
||||
|
||||
}
|
||||
return new BoundaryBeltObstacle(distanceToBoundary);
|
||||
// return new BeltObstacle(ObstacleKind.Boundary,distanceToBoundary,laneSpan,this,null, null);//TODO THIS SHOULD GET SPACE IN PORT SO ITEMS KNOW IF THEY CAN FIT
|
||||
}
|
||||
return new BoundaryBeltObstacle(distanceToBoundary);
|
||||
}
|
||||
|
||||
if (conveyorPort.BeltT is not ItemConveyor.BeltTEnd end)
|
||||
{
|
||||
return new BoundaryBeltObstacle(distanceToBoundary);
|
||||
}
|
||||
|
||||
var mappedLane = MapLaneOrFail(port, conveyorPort, laneSpan);
|
||||
if (!mappedLane.HasValue(out var lane))
|
||||
{
|
||||
return new BoundaryBeltObstacle(distanceToBoundary);
|
||||
}
|
||||
|
||||
var nextDirection = DirectionAwayFromEnd(end.End);
|
||||
var startT = end.End == ItemConveyor.ConveyorEnd.Start
|
||||
? 0f
|
||||
: conveyorPort.Conveyor.Length;
|
||||
|
||||
// 🔻 Remaining budget after reaching boundary
|
||||
// var remainingDist = maxDistToCheck - distanceToBoundary;
|
||||
// if (remainingDist <= 0f)
|
||||
// {
|
||||
// return maxDistToCheck;
|
||||
// }
|
||||
|
||||
var recursiveDistance =
|
||||
conveyorPort.Conveyor.GetDistanceToNextItem(
|
||||
nextDirection,
|
||||
startT,
|
||||
maxDistToCheck,
|
||||
lane,
|
||||
visited
|
||||
);
|
||||
return recursiveDistance with { Distance = recursiveDistance.Distance + distanceToBoundary };
|
||||
// return new BeltObstacle(recursiveDistance.Kind, recursiveDistance.DistanceToCenter + distanceToBoundary, recursiveDistance.LaneSpan, recursiveDistance.Conveyor, recursiveDistance.Item,null);
|
||||
// return distanceToBoundary + recursiveDistance;
|
||||
}
|
||||
private IOption<ConveyorPort> FindFacingConveyorPort(
|
||||
IBeltPort slot)
|
||||
{
|
||||
var targetpos = slot.Profile.LocalOffset.TransformDirection(Vector3I.Forward);
|
||||
// var targetPos = port.Profile.Position + port.Profile.Face.ToVector();
|
||||
return GridRegistry
|
||||
.Get<IBeltPort>(slot.Profile.LocalOffset.LocalToWorld(Vector3I.Forward))
|
||||
|
||||
.Where(port => port is ConveyorPort)
|
||||
.Where(port => port.Profile.LocalOffset.TransformDirection(Vector3I.Forward) == slot.Profile.LocalOffset.TransformDirection(Vector3I.Back))
|
||||
.FirstOrNone()
|
||||
.Bind(p => p is ConveyorPort cp
|
||||
? cp.ToOption()
|
||||
: None<ConveyorPort>.Of());
|
||||
}
|
||||
public IOption<IBeltPort> GetPortFacing(IBeltPort slot)
|
||||
{
|
||||
var toCheck = new List<Vector3I>();
|
||||
for (int i = 0; i < slot.Profile.Width; i++)
|
||||
{
|
||||
toCheck.Add(slot.Profile.LocalOffset.LocalToWorld(Vector3I.Forward));
|
||||
}
|
||||
return GridRegistry
|
||||
.Get<IBeltPort>([.. toCheck])
|
||||
.Where(port => port.Profile.LocalOffset.TransformDirection(Vector3I.Forward) == slot.Profile.LocalOffset.TransformDirection(Vector3I.Back))
|
||||
.FirstOrNone();
|
||||
}
|
||||
private IOption<LaneSpan> MapLaneOrFail(
|
||||
IBeltPort from,
|
||||
IBeltPort to,
|
||||
LaneSpan incoming)
|
||||
{
|
||||
// return None<LaneSpan>.Of();
|
||||
bool mirror = false;
|
||||
// full mapping of the source port
|
||||
var mapped = from.MapLaneSpanToFacingPort(to);
|
||||
|
||||
if (mapped == LaneSpan.Zero)
|
||||
return LaneSpan.Zero.ToOption();
|
||||
|
||||
int sourceWidth = from.Profile.Width;
|
||||
int targetWidth = mapped.Width;
|
||||
|
||||
int startOffset = incoming.Start; // start relative to source port
|
||||
int spanLength = incoming.Width;
|
||||
|
||||
int newStart;
|
||||
if (mirror)
|
||||
{
|
||||
// mirrored: right side of incoming aligns with right side of mapped
|
||||
newStart = mapped.End - (startOffset + spanLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
// normal: left side of incoming aligns with left side of mapped
|
||||
newStart = mapped.Start + startOffset;
|
||||
}
|
||||
|
||||
int newEnd = newStart + spanLength;
|
||||
|
||||
// clamp to mapped range
|
||||
newStart = Math.Max(mapped.Start, newStart);
|
||||
newEnd = Math.Min(mapped.End, newEnd);
|
||||
|
||||
GD.Print(incoming, new LaneSpan((ushort)newStart, (ushort)newEnd));
|
||||
if (newStart >= newEnd)
|
||||
{
|
||||
GD.Print("none");
|
||||
return None<LaneSpan>.Of();
|
||||
}
|
||||
|
||||
return new LaneSpan((ushort)newStart, (ushort)newEnd).ToOption();
|
||||
}
|
||||
|
||||
private ItemConveyor.BeltDirection DirectionAwayFromEnd(ItemConveyor.ConveyorEnd conveyorEnd) => conveyorEnd switch {ItemConveyor.ConveyorEnd.Start=>ItemConveyor.BeltDirection.TowardEnd,ItemConveyor.ConveyorEnd.End=>ItemConveyor.BeltDirection.TowardStart};
|
||||
public IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardEnd() => Items.EnumerateTowardEnd();
|
||||
public IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardStart() => Items.EnumerateTowardStart();
|
||||
private static Vector3[] _Square = [new(-.5f, .5f, -.5f), new(.5f, .5f, -.5f), new(.5f, -.5f, -.5f), new(-.5f, -.5f, -.5f), new(-.5f, .5f, -.5f)];
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (StartPort is null || EndPort is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var item in GetPorts())
|
||||
{
|
||||
for (int ii = 0; ii < item.Profile.Width; ii++)
|
||||
{
|
||||
DebugDraw3D.DrawLinePath(_Square.Select(i => item.Profile.LocalOffset.ToGodot().TranslatedLocal(i).TranslatedLocal(new Vector3(ii,0,0)).Origin).ToArray(),(!Engine.IsEditorHint())&&GetPortFacing(item).HasValue()?Colors.Green:Colors.Red);
|
||||
|
||||
}
|
||||
// DebugDraw3D.DrawArrow(item.Profile.LocalOffset.Origin, item.Profile.LocalOffset.LocalToWorld(item.Profile.Face.ToVector()),(!Engine.IsEditorHint())&&GetPortFacing(item).HasValue()?Colors.Green:Colors.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public ConveyorPort CreatePort(BeltPortProfile profile, ItemConveyor.BeltT beltT, LaneSpan laneSpan)
|
||||
{
|
||||
// var towardStartEnd = beltT is ItemConveyor.BeltTEnd end && end.End == ItemConveyor.ConveyorEnd.End;
|
||||
bool accept(IBeltItem item, float offset, LaneSpan lane)
|
||||
{
|
||||
if (beltT is ItemConveyor.BeltTEnd end)
|
||||
{
|
||||
var startBeltT = end.End == ItemConveyor.ConveyorEnd.Start ? 0 : Length;
|
||||
var obstacle = GetDistanceToNextItem(ItemConveyor.SwapEnd(end.End).DirectionTo(), startBeltT, offset, lane);
|
||||
var distanceAllowed = obstacle.Distance;
|
||||
return ItemConveyor.ITEMSIZE < distanceAllowed;
|
||||
}
|
||||
else if (beltT is ItemConveyor.BeltTOffset endOffset)
|
||||
{
|
||||
return HasClearance(endOffset.T,lane);
|
||||
// var lowerObstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, endOffset.T, ItemConveyor.ITEMSIZE,LaneSpan.One);
|
||||
// var upperObstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, endOffset.T, ItemConveyor.ITEMSIZE,LaneSpan.One);
|
||||
// var lowerDistanceAllowed = lowerObstacle.DistanceToCenter - (lowerObstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
// var upperObstacleAllowed = upperObstacle.DistanceToCenter - (upperObstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
// return ItemConveyor.ITEMSIZE < lowerDistanceAllowed && ItemConveyor.ITEMSIZE < upperObstacleAllowed;
|
||||
}
|
||||
// return true;
|
||||
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
// if (beltT is ItemConveyor.BeltTEnd end && end.End == ItemConveyor.ConveyorEnd.End){
|
||||
// accept = (IBeltItem item, float offset) =>
|
||||
// {
|
||||
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, Length, offset, LaneSpan.One);
|
||||
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
// };
|
||||
// }
|
||||
bool tryInsert(IBeltItem item, float offset, LaneSpan laneSpan)
|
||||
{
|
||||
if (beltT is ItemConveyor.BeltTEnd end)
|
||||
{
|
||||
if (end.End == ItemConveyor.ConveyorEnd.End)
|
||||
{
|
||||
Items.Insert(new (item, Length-offset){LaneSpan = laneSpan});
|
||||
// _items.Add(new(item, Length-offset));
|
||||
}
|
||||
else
|
||||
{
|
||||
Items.Insert(new (item, offset) {LaneSpan = laneSpan});
|
||||
// _items.Insert(0, new(item, offset));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (beltT is ItemConveyor.BeltTOffset endOffset)
|
||||
{
|
||||
|
||||
if (!HasClearance(endOffset.T, LaneSpan.One))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Items.Insert(new (item, endOffset.T){LaneSpan = laneSpan});
|
||||
// int insertIndex = FindInsertIndex(endOffset.T);
|
||||
|
||||
// _items.Insert(insertIndex, new(item, endOffset.T));
|
||||
|
||||
return true;
|
||||
}
|
||||
// _items.Insert(0,new ConveyorSlice(item,0));
|
||||
// return true;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
var port = new ConveyorPort(this,
|
||||
profile,
|
||||
beltT,
|
||||
accept,
|
||||
tryInsert);
|
||||
return port;
|
||||
}
|
||||
private bool HasClearance(float centerT, LaneSpan span)
|
||||
{
|
||||
var lower = GetDistanceToNextItem(
|
||||
ItemConveyor.BeltDirection.TowardStart,
|
||||
centerT,
|
||||
ItemConveyor.ITEMSIZE,
|
||||
span
|
||||
);
|
||||
|
||||
var upper = GetDistanceToNextItem(
|
||||
ItemConveyor.BeltDirection.TowardEnd,
|
||||
centerT,
|
||||
ItemConveyor.ITEMSIZE,
|
||||
span
|
||||
);
|
||||
|
||||
float lowerAllowed =
|
||||
lower.Distance;// -
|
||||
// (lower.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
|
||||
float upperAllowed =
|
||||
upper.Distance;// -
|
||||
// (upper.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
|
||||
return ItemConveyor.ITEMSIZE < lowerAllowed &&
|
||||
ItemConveyor.ITEMSIZE < upperAllowed;
|
||||
}
|
||||
|
||||
}
|
||||
// Items.Add(new ConveyorSlice(new TestItem(), 0));
|
||||
// StartPort = new ConveyorPort(this,
|
||||
// new BeltPortProfile(Position, Direction.Back, 1, PortAccess.BiDirectional),
|
||||
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
|
||||
// (item, offset) =>
|
||||
// {
|
||||
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, offset, LaneSpan.One);
|
||||
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
// },
|
||||
// (item, offset) => { _items.Insert(0, new(item, offset)); return true;});
|
||||
// EndPort = new ConveyorPort(this,
|
||||
// new BeltPortProfile(Position, Direction.Front, 1, PortAccess.BiDirectional),
|
||||
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
|
||||
// (item, offset) =>
|
||||
// {
|
||||
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, Length, offset, LaneSpan.One);
|
||||
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
// // return ItemConveyor.ITEMSIZE < GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, Length, SpeedMagnitude, LaneSpan.One);
|
||||
// },
|
||||
// (item, offset) => { _items.Add(new(item, offset)); return true;});
|
||||
//TODO Should liklely account for max search distance where the conveyorm may be needed to know
|
||||
public record class BeltObstacle(float Distance)
|
||||
{
|
||||
// public ObstacleKind Kind { get; }
|
||||
|
||||
// public BeltObstacle()
|
||||
// {
|
||||
// }
|
||||
// public float DistanceToCenter { get; } // always center / reference
|
||||
// public LaneSpan LaneSpan { get; } // only meaningful for Item
|
||||
// public ConveyorSlice? Item { get; } // only if Kind == Item
|
||||
// public IMovementConveyor? Conveyor { get; } // boundary case
|
||||
// public IBeltPort? Port { get; } // Port case
|
||||
|
||||
// public bool IsItem => Kind == ObstacleKind.Item;
|
||||
// public bool IsBoundary => Kind == ObstacleKind.Boundary;
|
||||
}
|
||||
|
||||
// public enum ObstacleKind
|
||||
// {
|
||||
// None,
|
||||
// Item,
|
||||
// Boundary,
|
||||
// }
|
||||
public record ItemBeltObstacle(float Distance, ConveyorSlice Item, LaneSpan LaneSpan) : BeltObstacle(Distance)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public record BoundaryBeltObstacle(float DistanceToBoundary) : BeltObstacle(DistanceToBoundary)
|
||||
{
|
||||
|
||||
}
|
||||
public record PortBeltObstacle(float DistanceToBoundary, LaneSpan LaneSpan, IBeltPort BeltPort) : BoundaryBeltObstacle(DistanceToBoundary)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
1
src/VoxelGrid/TestItemConveyor.cs.uid
Normal file
1
src/VoxelGrid/TestItemConveyor.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://j24fuotdwwx4
|
||||
177
src/VoxelGrid/VoxelGridNode.cs
Normal file
177
src/VoxelGrid/VoxelGridNode.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using SJK.Functional;
|
||||
|
||||
//Will Liklely be the game instead of a node like this
|
||||
[Meta(typeof(IAutoNode))]// [Tool]
|
||||
public partial class VoxelGridNode : Node3D, IProvide<IVoxelGridQuery<LayeredEquipment>>, IProvide<IEquipmentComponentRegistry>, IProvide<IVoxelGridRegistry>, IProvide<IItemRenderer>
|
||||
{
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
private IVoxelGridQuery<LayeredEquipment> _voxelGrid = default!;
|
||||
IVoxelGridQuery<LayeredEquipment> IProvide<IVoxelGridQuery<LayeredEquipment>>.Value() => _voxelGrid;
|
||||
|
||||
private IVoxelGridRegistry _voxelGridRegistry = default!;
|
||||
IVoxelGridRegistry IProvide<IVoxelGridRegistry>.Value() => _voxelGridRegistry;
|
||||
private IItemRenderer _itemRenderer = default!;
|
||||
IItemRenderer IProvide<IItemRenderer>.Value() => _itemRenderer;
|
||||
public override void _Ready()
|
||||
{
|
||||
TestStructural.Test();
|
||||
GD.Print();
|
||||
base._Ready();
|
||||
_voxelGrid = new EquipmentVoxelGrid();
|
||||
_equipmentComponentRegistry = new EquipmentComponentRegistry();
|
||||
_voxelGridRegistry = new VoxelRegistry();
|
||||
_itemRenderer = new TestItemRendered();
|
||||
AddChild(_itemRenderer as Node);
|
||||
Timer timer = new Timer() { WaitTime = .25f, Autostart = true };//TEST
|
||||
AddChild(timer);//TEST
|
||||
// timer.Timeout += _itemRenderer.Tick;//TEST
|
||||
this.Provide();
|
||||
}
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
base._Process(delta);
|
||||
// GD.Print(_voxelGridRegistry.Get<ISlotFace>(Vector3I.Zero).First().GetConnectingFace());
|
||||
}
|
||||
|
||||
IEquipmentComponentRegistry _equipmentComponentRegistry;
|
||||
IEquipmentComponentRegistry IProvide<IEquipmentComponentRegistry>.Value() => _equipmentComponentRegistry;
|
||||
|
||||
}
|
||||
// [Meta, Id("voxel_grid")]
|
||||
public sealed partial class EquipmentVoxelGrid : IVoxelGridQuery<LayeredEquipment>
|
||||
{
|
||||
private System.Collections.Generic.Dictionary<Vector3I, LayeredEquipment> _data = new();
|
||||
public void AddEquipment(Vector3I position, Equipment equipment)
|
||||
{
|
||||
if (!_data.TryGetValue(position, out var layers))
|
||||
{
|
||||
_data[position] = layers = new();
|
||||
}
|
||||
layers.Add(equipment);
|
||||
}
|
||||
//TODO Use Options instead of nu;lable
|
||||
public LayeredEquipment GetVoxel(Vector3I position) => _data.TryGetValue(position, out var result) ? result : new();
|
||||
public IEnumerable<Equipment> GetEquipmentAt(Vector3I position) => GetVoxel(position).GetEquipments();
|
||||
// public
|
||||
// public void SetEquipmentAt(Vector3I position, T value)
|
||||
// {
|
||||
// _data[position] = value;
|
||||
// GD.Print(value);
|
||||
// }
|
||||
|
||||
public LayeredEquipment SetVoxel(Vector3I position, LayeredEquipment value) => _data[position] = value;
|
||||
public bool IsOccupied(Vector3I cell) => _data.ContainsKey(cell);
|
||||
}
|
||||
public interface IVoxelGridQuery<T>
|
||||
{
|
||||
T GetVoxel(Vector3I cell);
|
||||
T SetVoxel(Vector3I cell, T value);
|
||||
bool IsOccupied(Vector3I cell);
|
||||
}
|
||||
|
||||
public class LayeredEquipment
|
||||
{
|
||||
public int Count => _equipment.Count;
|
||||
private readonly HashSet<Equipment> _equipment = new();
|
||||
public IEnumerable<Equipment> GetEquipments() => _equipment;
|
||||
public bool Add(Equipment equipment) => _equipment.Add(equipment);
|
||||
public bool Remove(Equipment equipment) => _equipment.Remove(equipment);
|
||||
public void AddMany(params Equipment[] equipment)
|
||||
{
|
||||
foreach (var item in equipment)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
public void RemoveMany(params Equipment[] equipment)
|
||||
{
|
||||
foreach (var item in equipment)
|
||||
{
|
||||
Remove(item);
|
||||
}
|
||||
}
|
||||
public void IfAny(Action<IEnumerable<Equipment>> action)
|
||||
{
|
||||
if (Count > 0)
|
||||
{
|
||||
action(GetEquipments());
|
||||
}
|
||||
}
|
||||
}
|
||||
public sealed class SlotDescriptor {
|
||||
public SlotDirection Direction { get; }
|
||||
|
||||
public SlotDescriptor(
|
||||
SlotDirection direction
|
||||
)
|
||||
{
|
||||
Direction = direction;
|
||||
}
|
||||
}
|
||||
public abstract class SlotLogic<TPayload> {
|
||||
public SlotDescriptor Descriptor { get; }
|
||||
|
||||
protected SlotLogic(SlotDescriptor descriptor) {
|
||||
Descriptor = descriptor;
|
||||
}
|
||||
|
||||
public abstract bool CanTransfer(
|
||||
TPayload payload
|
||||
);
|
||||
|
||||
public abstract bool TryTransfer(
|
||||
TPayload payload
|
||||
);
|
||||
}
|
||||
public sealed class ItemSlotLogic
|
||||
: SlotLogic<IBeltItem> {
|
||||
|
||||
public ItemSlotLogic(SlotDescriptor descriptor)
|
||||
: base(descriptor) {}
|
||||
|
||||
public override bool CanTransfer(IBeltItem item) => false;
|
||||
// item.Count > 0;
|
||||
|
||||
public override bool TryTransfer(IBeltItem item) {
|
||||
// routing rules
|
||||
return true;
|
||||
}
|
||||
}
|
||||
[Meta]
|
||||
public partial class ItemSlotNode
|
||||
: SlotComponentNode {
|
||||
|
||||
private ItemSlotLogic _logic;
|
||||
|
||||
public override void _Ready() {
|
||||
base._Ready();
|
||||
_logic = new ItemSlotLogic(
|
||||
new SlotDescriptor(Direction)
|
||||
);
|
||||
}
|
||||
public void OnResolved()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Tick(IBeltItem stack) {
|
||||
if (_logic.CanTransfer(stack))
|
||||
_logic.TryTransfer(stack);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum SlotDirection
|
||||
{
|
||||
Input,
|
||||
Output
|
||||
}
|
||||
1
src/VoxelGrid/VoxelGridNode.cs.uid
Normal file
1
src/VoxelGrid/VoxelGridNode.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dcrb286hmpli
|
||||
93
src/VoxelGrid/VoxelGridNode.tscn
Normal file
93
src/VoxelGrid/VoxelGridNode.tscn
Normal file
@@ -0,0 +1,93 @@
|
||||
[gd_scene format=3 uid="uid://dfacxkkkc0v10"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dcrb286hmpli" path="res://src/VoxelGrid/VoxelGridNode.cs" id="1_tsdpe"]
|
||||
[ext_resource type="Script" uid="uid://ee5aoxi8mjnw" path="res://src/VoxelGrid/BeltPort.cs" id="6_2wkfx"]
|
||||
[ext_resource type="PackedScene" uid="uid://c4h7mwnfrdesg" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn" id="6_mxaon"]
|
||||
|
||||
[sub_resource type="Curve3D" id="Curve3D_mxaon"]
|
||||
_data = {
|
||||
"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0.5, -0.5, 0, 0, 0, 0, 0, 0, 0, 0.5, 0),
|
||||
"tilts": PackedFloat32Array(0, 0)
|
||||
}
|
||||
point_count = 2
|
||||
|
||||
[node name="VoxelGridNode" type="Node3D" unique_id=825696340]
|
||||
script = ExtResource("1_tsdpe")
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="." unique_id=75458542]
|
||||
transform = Transform3D(0.6279494, -0.32487354, 0.70720345, -8.896721e-09, 0.908705, 0.4174389, -0.77825415, -0.26213053, 0.5706208, 4.635174, 2.6038146, 2.739409)
|
||||
|
||||
[node name="ConveyorBeltStraight5" parent="." unique_id=975225936 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 3, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight22" parent="." unique_id=81137442 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 2, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight13" parent="." unique_id=607296315 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 0, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight26" parent="." unique_id=626657643 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 1, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight6" parent="." unique_id=264656404 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -1, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight23" parent="." unique_id=1438072389 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -2, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight14" parent="." unique_id=1045608025 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -4, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight27" parent="." unique_id=899289297 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -3, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight7" parent="." unique_id=1602707514 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 3, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight24" parent="." unique_id=904248251 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 2, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight15" parent="." unique_id=533235542 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 0, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight28" parent="." unique_id=2116943332 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 1, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight8" parent="." unique_id=1109978762 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -1, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight25" parent="." unique_id=1110001269 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -2, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight16" parent="." unique_id=1178816500 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -4, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight29" parent="." unique_id=1537356869 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -3, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="Node3D2" type="Node3D" parent="." unique_id=1815100025 node_paths=PackedStringArray("Path")]
|
||||
transform = Transform3D(1.3113416e-07, 0, 1, 0, 1, 0, -1, 0, 1.3113416e-07, 4, 0, 1)
|
||||
script = ExtResource("6_2wkfx")
|
||||
Face = 4
|
||||
Width = 1
|
||||
Access = 3
|
||||
Path = NodePath("Path3D")
|
||||
|
||||
[node name="Path3D" type="Path3D" parent="Node3D2" unique_id=1525648764]
|
||||
curve = SubResource("Curve3D_mxaon")
|
||||
Reference in New Issue
Block a user