@ -61,39 +61,17 @@ public partial class PickupController : Node3D
if ( ! _ player . IsLocal ) return ;
if ( ! _ player . IsLocal ) return ;
if ( HasItemsHeld ) {
if ( HasItemsHeld ) {
// This ray will be blocked by static and dynamic objects.
if ( GetValidPlacement ( HeldItem ) is var ( grid , transform , isFree ) ) {
const PhysicsLayer Mask = PhysicsLayer . Place | PhysicsLayer . Static | PhysicsLayer . Dynamic ;
var outlineColor = isFree ? OutlineYesPlace : OutlineNoPlace ;
if ( ( RayToMouseCursor ( Mask ) is RayResult ray ) & & ( ray . Collider is Grid grid )
// Not pointing at item's own grid, or one of its nested grids.
& & ( grid . GetParent ( ) ! = HeldItem ) & & ! grid . ContainsItem ( HeldItem ) )
{
var inverseTransform = grid . GlobalTransform . AffineInverse ( ) ;
var inverseBasis = grid . GlobalBasis . Inverse ( ) ;
var pos = inverseTransform * ray . Position ;
var normal = inverseBasis * ray . Normal ;
if ( grid . CanPlaceAgainst ( HeldItem , pos , normal ) ) {
var transform = new Transform3D ( inverseBasis * HeldItem . GlobalBasis , pos ) ;
transform = grid . Snap ( transform , normal , HeldItem ) ;
var canPlace = grid . CanPlaceAt ( HeldItem , transform ) ;
var outlineColor = canPlace ? OutlineYesPlace : OutlineNoPlace ;
_ outlineMaterial . SetShaderParameter ( "line_color" , outlineColor ) ;
_ outlineMaterial . SetShaderParameter ( "line_color" , outlineColor ) ;
_ preview . GlobalTransform = grid . GlobalTransform * transform ;
_ preview . GlobalTransform = grid . GlobalTransform * transform ;
_ preview . Visible = true ;
_ preview . Visible = true ;
_ targetedGrid = canPlace ? grid : null ;
_ targetedGrid = isFree ? grid : null ;
} else {
_ preview . Visible = false ;
_ targetedGrid = null ;
}
} else {
} else {
_ preview . Visible = false ;
_ preview . Visible = false ;
_ targetedGrid = null ;
_ targetedGrid = null ;
}
}
} else {
} else {
var interactable = RayToMouseCursor ( PhysicsLayer . Pickup ) ? . Collider ;
var interactable = RayToMouseCursor ( PhysicsLayer . Pickup ) ? . Collider ;
@ -142,8 +120,48 @@ public partial class PickupController : Node3D
return preview ;
return preview ;
}
}
( Grid Grid , Transform3D Target , bool IsFree ) ? GetValidPlacement ( Item itemToPlace )
{
// This ray will be blocked by static and dynamic objects.
const PhysicsLayer Mask = PhysicsLayer . Static | PhysicsLayer . Dynamic | PhysicsLayer . Item ;
// FIXME: Remove .Place and .Pickup physics layers?
// TODO: We need a separate physics layers for:
// - The physical item collider used for physics calculations (simplified).
// - The placement collider which should match the item's appearance.
// - The general space / size an item takes up, as a cuboid.
// TODO: Probably just overhaul the physics layers altogether.
// It would be better to have a "collides with player" and "player collides with it" layer, etc.
var excludeSet = new HashSet < CollisionObject3D > { HeldItem } ;
var heldItemGrid = HeldItem . GetNodeOrNull < Grid > ( nameof ( Grid ) ) ;
heldItemGrid ? . AddItemsRecursively ( excludeSet ) ;
// Cast a ray and make sure it hit something.
if ( RayToMouseCursor ( Mask , excludeSet ) is not RayResult ray ) return null ;
// Find a grid to place against, which will be either the grid belonging
// to the item the ray intersected, or the grid said item is placed upon.
var grid = ray . Collider . GetNodeOrNull < Grid > ( nameof ( Grid ) )
? ? ray . Collider . GetParentOrNull < Grid > ( ) ;
if ( grid = = null ) return null ; // No suitable grid found.
var inverseTransform = grid . GlobalTransform . AffineInverse ( ) ;
var inverseBasis = grid . GlobalBasis . Inverse ( ) ;
var pos = inverseTransform * ray . Position ;
var normal = inverseBasis * ray . Normal ;
if ( ! grid . CanPlaceAgainst ( itemToPlace , pos , normal ) ) return null ;
var transform = new Transform3D ( inverseBasis * itemToPlace . GlobalBasis , pos ) ;
transform = grid . Snap ( transform , normal , itemToPlace ) ;
var isFree = grid . CanPlaceAt ( itemToPlace , transform ) ;
return ( grid , transform , isFree ) ;
}
record class RayResult ( CollisionObject3D Collider , Vector3 Position , Vector3 Normal ) ;
record class RayResult ( CollisionObject3D Collider , Vector3 Position , Vector3 Normal ) ;
RayResult RayToMouseCursor ( PhysicsLayer collisionMask )
RayResult RayToMouseCursor ( PhysicsLayer collisionMask , IEnumerable < CollisionObject3D > excluded = null )
{
{
var camera = _ player . Camera . Camera ;
var camera = _ player . Camera . Camera ;
var mouse = GetViewport ( ) . GetMousePosition ( ) ;
var mouse = GetViewport ( ) . GetMousePosition ( ) ;
@ -153,12 +171,10 @@ public partial class PickupController : Node3D
var query = PhysicsRayQueryParameters3D . Create ( from , to ) ;
var query = PhysicsRayQueryParameters3D . Create ( from , to ) ;
query . CollisionMask = ( uint ) collisionMask ;
query . CollisionMask = ( uint ) collisionMask ;
query . CollideWithAreas = true ;
query . CollideWithAreas = true ;
// Exclude the `CurrentItem` from collision checking if it's being held.
query . Exclude = new ( ( excluded ? ? [ ] ) . Select ( obj = > obj . GetRid ( ) ) ) ;
query . Exclude = HasItemsHeld ? [ HeldItem . GetRid ( ) ] : [ ] ;
var result = GetWorld3D ( ) . DirectSpaceState . IntersectRay ( query ) ;
var result = GetWorld3D ( ) . DirectSpaceState . IntersectRay ( query ) ;
return ( result . Count > 0 ) ? new (
return ( result . Count > 0 ) ? new (
// FIXME: Unable to cast object of type 'ReplacePalette' to type 'Godot.CollisionObject3D'.
result [ "collider" ] . As < CollisionObject3D > ( ) ,
result [ "collider" ] . As < CollisionObject3D > ( ) ,
( Vector3 ) result [ "position" ] ,
( Vector3 ) result [ "position" ] ,
( Vector3 ) result [ "normal" ]
( Vector3 ) result [ "normal" ]