code/BaseVoxelVolume/BaseVoxelVolume.Object.cs
namespace Boxfish;
partial class BaseVoxelVolume<T, U>
{
/// <summary>
/// All of the chunk objects in this volume.
/// </summary>
protected IReadOnlyDictionary<Chunk, ChunkObject> Objects => _objects;
private Dictionary<Chunk, ChunkObject> _objects = new();
/// <summary>
/// Get or create a <see cref="ChunkObject"/>.
/// </summary>
/// <param name="chunk"></param>
/// <returns></returns>
protected ChunkObject GetChunkObject( Chunk chunk )
{
ICollection collection = _objects;
lock ( collection.SyncRoot )
{
if ( chunk == null )
{
Logger.Error( $"Tried to get ChunkObject of null Chunk?" );
return null;
}
if ( !_objects.TryGetValue( chunk, out var chunkObject ) )
_objects.Add( chunk, chunkObject = new()
{
Position = chunk.Position,
Parent = this
} );
return chunkObject;
}
}
/// <summary>
/// Go through all ChunkObjects and clear them.
/// </summary>
protected void DestroyObjects()
{
ICollection collection = _objects;
lock ( collection.SyncRoot )
{
foreach ( var (_, obj) in _objects )
obj?.Dispose();
_objects.Clear();
}
}
/// <summary>
/// This is the class for physical chunks in the world.
/// <para>We store a <see cref="Sandbox.SceneObject"/> and a <see cref="Sandbox.PhysicsBody"/> here.</para>
/// </summary>
protected sealed class ChunkObject
: IEquatable<ChunkObject>,
IDisposable
{
public Vector3 WorldPosition => (Vector3)Position * Parent.Scale * VoxelUtils.CHUNK_SIZE;
public Vector3Int Position { get; internal set; }
public BaseVoxelVolume<T, U> Parent { get; internal set; }
public SceneObject SceneObject { get; private set; }
public PhysicsBody Body { get; private set; }
public PhysicsShape Shape { get; private set; }
public void Rebuild( Model model, bool physics = true )
{
if ( !Parent.IsValid() )
{
Logger.Error( $"No parent for ChunkObject found?" );
return;
}
var scene = Parent.Scene;
if ( !scene.IsValid() )
{
Logger.Error( $"No scene for ChunkObject found?" );
Dispose();
return;
}
if ( model == null )
{
Dispose();
return;
}
SceneObject ??= new( scene.SceneWorld, Model.Error );
if ( !SceneObject.IsValid() )
return;
var position = WorldPosition + Parent.Scale / 2f;
// Recreate physics if needed.
if ( physics )
{
var part = model.Physics?.Parts?.FirstOrDefault()?.Meshes?.FirstOrDefault();
if ( part != null )
{
if ( !Body.IsValid() )
{
Body = new( scene.PhysicsWorld );
Body.SetComponentSource( Parent );
}
Shape?.Remove();
Shape = Body.AddShape( part, new Transform( position ), false, false );
}
}
// SceneObject for rendering.
Parent.SetAttributes( SceneObject.Attributes );
SceneObject.Batchable = true;
SceneObject.Model = model;
SceneObject.Position = Position;
SceneObject.SetComponentSource( Parent );
}
public bool Equals( ChunkObject other )
{
return other.Position.Equals( Position );
}
public override bool Equals( object obj )
{
return obj is ChunkObject other
&& Equals( other );
}
public override int GetHashCode()
{
return Position.GetHashCode();
}
public void Dispose()
{
if ( SceneObject.IsValid() )
SceneObject.Delete();
SceneObject = null;
if ( Shape.IsValid() )
Shape.Remove();
Shape = null;
if ( Body.IsValid() )
Body.Remove();
Body = null;
}
}
}