I’m very happy to report that I’ve gotten the bounds into their correct positions at every rotation on a 45° rotation.
Again, thank you so much for your detailed and patient responses; I’ve tried several other approaches to implementing this behavior (as you can see in my post history), going back a long time now, so I’m thrilled to be done with this.
I’ve marked your last message as the solution, since it answers the scope of the problem described in my OP, but if you’d like, there’s one thing left I need to fix: getting the camera back within the bounds if it goes OOB. This will most commonly be when it is flush against a bound and rotates, causing the bounds to recalculate.
I’ve adapted this user’s solution into a method, but it just doesn’t work correctly and I don’t understand why, since I’ve triple-checked it. It triggers when it shouldn’t when the camera moves closer to the viewer, and doesn’t trigger when it should after the camera escapes.
These are my movement and checking methods. I should note that I actually realized a SpringArm3D was the wrong tool for this job: now the controller node has one CollisionShape3D at its origin, and places another N meters +Z of it, where N was the SpringArm3D’s SpringLength. Accounting for thickness is done to make it so the camera has to move a bit further than it should to get back into bounds, to prevent it from stopping and starting again due to slight movement.
private void Logic_Move(float delta)
{
if (State_Move_FromOOB)
{
//push toward center of room if OOB and override movement until done
//just do it at a constant speed bc to lerp it you'd need to find out which bound is closest
Velocity = Current_Move_FromOOB_Dir * Fixed_Move_FromOOB_Speed;
MoveAndSlide();
//check whether it's done
if (Check_IsWithinBounds(true))
{
State_Move_FromOOB = false;
Enable_Collision(true);
}
}
else if (Check_IsWithinBounds(false))
{
//get normalized input and convert to fixed speed
Vector2 input = Input.GetVector("cam_move_left", "cam_move_right", "cam_move_forward", "cam_move_backward").Normalized() * Fixed_Speeds[State_Move_Speed] * delta;
//convert prior result into relative direction
Velocity = new Vector3(input.X, 0, input.Y) * Basis.FromEuler(new Vector3(0, -Rotation.Y, 0));
MoveAndSlide();
return;
}
else
{
State_Move_FromOOB = true;
Enable_Collision(false);
//find the direction the pivot point needs to move
Vector3 centroid_xz = new(Current_Bound_Centroid.X, GlobalPosition.Y, Current_Bound_Centroid.Z);
Current_Move_FromOOB_Dir = (centroid_xz - myCamera.GlobalPosition).Normalized();
}
}
private void Enable_Collision(bool enabled)
{
//do both sets of CollisionShape3Ds so this method can feel more like it's contributing
GetNode<CollisionShape3D>("Collider_Origin").Disabled = !enabled;
GetNode<CollisionShape3D>("Collider_Front").Disabled = !enabled;
foreach(CollisionShape3D bound in yourBounds_Colliders)
{
bound.Disabled = !enabled;
}
}
private bool Check_IsWithinBounds(bool account_for_thickness)
{
Vector3[] corners_xz = new Vector3[4];
Array.Copy(Current_Corners_CameraAligned, corners_xz, 4);
if (account_for_thickness)
{
for (byte i = 1; i < 3; i ++)
{
//assume each bound is closer to the centroid by its thickness
corners_xz[i].X -= BakedIn_Bound_Thickness * Math.Sign(corners_xz[i].X - Current_Bound_Centroid.X);
corners_xz[i].Z -= BakedIn_Bound_Thickness * Math.Sign(corners_xz[i].Z - Current_Bound_Centroid.Z);
}
}
//https://stackoverflow.com/a/53907763
float sin = MathF.Sin(- GlobalRotation.Y);
float cos = MathF.Cos(- GlobalRotation.Y);
Vector2 point = new(myCamera.GlobalPosition.X - Current_Bound_Centroid.X, myCamera.GlobalPosition.Z - Current_Bound_Centroid.Z);
point = new Vector2(point.X * cos - point.Y * sin, point.X * sin + point.Y * cos);
point += new Vector2(Current_Bound_Centroid.X, Current_Bound_Centroid.Z);
Vector2 mins = new(float.MaxValue, float.MaxValue);
Vector2 maxs = new(float.MinValue, float.MinValue);
foreach (Vector3 corner in corners_xz)
{
if (corner.X < mins.X) mins.X = corner.X;
if (corner.X > maxs.X) maxs.X = corner.X;
if (corner.Z < mins.Y) mins.Y = corner.Z;
if (corner.Z > maxs.Y) maxs.Y = corner.Z;
}
return point.X >= mins.X && point.X <= maxs.X && point.Y >= mins.Y && point.Y <= maxs.Y;
}