using Godot;
using System;
public partial class MathHandler : Node
{
public static readonly float Pi = (float) Math.PI;
public static readonly float DegToRad = (float) Math.PI / 180;
public static readonly float RadFull = (float) Math.PI * 2;
/// <summary>
/// Increases or decreases an input number, then runs modulus.
/// </summary>
/// <param name="start">The input number.</param>
/// <param name="length">The amount by which to modulus divide.</param>
/// <param name="amount">The amount by which to change the input number.</param>
public static int AddThenModulus(int start, int length, int amount = 1)
{
int cycles_up = 0;
if (amount < 0)
{
cycles_up = Math.Abs(amount) / length + 1;
if (Math.Abs(amount) % length == 0)
{
cycles_up --;
}
}
return (length * cycles_up + start + amount) % length;
}
public static Variant[] Array_RotateUpward(Variant[] input, int amount = 1)
{
amount %= input.Length;
if (amount == 0) return input;
input = Array_ReverseSection(input, 0, input.Length);
input = Array_ReverseSection(input, 0, amount);
input = Array_ReverseSection(input, amount, input.Length);
return input;
}
public static Variant[] Array_RotateDownward(Variant[] input, int amount = 1)
{
amount %= input.Length;
if (amount == 0) return input;
input = Array_ReverseSection(input, 0, amount);
input = Array_ReverseSection(input, amount, input.Length);
input = Array_ReverseSection(input, 0, input.Length);
return input;
}
private static Variant[] Array_ReverseSection(Variant[] input, int start, int section)
{
section --;
while (start < section)
{
(input[section], input[start]) = (input[start], input[section]);
start++;
section --;
}
return input;
}
public static bool Point_IsWithinPolygon(Vector2 point, Vector2[] corners)
{
//https://stackoverflow.com/a/14998816
bool result = false;
int j = corners.Length - 1;
for (int i = 0; i < corners.Length; i++)
{
if (corners[i].Y < point.Y && corners[j].Y >= point.Y ||
corners[j].Y < point.Y && corners[i].Y >= point.Y)
{
if (corners[i].X + (point.Y - corners[i].Y) /
(corners[j].Y - corners[i].Y) *
(corners[j].X - corners[i].X) < point.X)
{
result = !result;
}
}
j = i;
}
return result;
}
}
using System;
using System.Collections.Generic;
using Godot;
public partial class Camera_Overworld : CharacterBody3D
{
//VARIABLES THAT LINK NODES TOGETHER
private CollisionShape3D myCollider_Front;
private Camera3D myCamera;
private List<CollisionShape3D> yourBounds_Colliders = [];
private List<Marker3D> your_RoomPoints = [];
//VARIABLES THAT DESCRIBE THE FIXED PROPERTIES OF THE ROOM
private Vector3[] Fixed_Corners_AxisAligned = new Vector3[2];
private readonly float BakedIn_Bound_Thickness = 1; //this is the thickness of the room's walls
private readonly float BakedIn_Wall_Height = 7; //this is the verticle distance from the floor of the room to the top of its walls
//VARIABLES THAT DESCRIBE THE FIXED PROPERTIES OF THE CAMERA
[Export] private bool State_Debug_Collision = false;
private BaseMaterial3D[] Debug_Mesh_Materials = new BaseMaterial3D[2];
private readonly float Dependent_Height_Above_Wall = 17.5f; //this depends on the sizes of the camera, and is whatever minimum height you need to prevent occlusion of objects
private float Dependent_GlobalPosition_Y; //this will be calculated from the above variable on initialization
[Export] private float Fixed_Angle = 45 * MathHandler.Pi; //this is the angle of the camera's Rotation.X
[Export] private int[] Fixed_Sizes = [16, 48]; //the zoomed-in and zoomed-out Sizes of the camera
[Export] private float Fixed_Zoom_Seconds = 0.5f; //the amount of time it takes to zoom in or out
[Export] private byte Fixed_Rotate_Power = 3; //the higher this number, the smaller each rotation on input will be
private float Fixed_Rotate_Rads; //the magnitude of each rotation on input
[Export] private float Fixed_Rotate_Seconds = 0.5f; //the amount of time it takes to rotate
[Export] private int[] Fixed_Speeds = [512, 1024, 2048]; //the speeds at which the camera moves
[Export] private float Fixed_Move_FromOOB_Speed = 32; //the speed at which the camera autocorrects if it goes OOB
//VARIABLES THAT DESCRIBE THE INITIAL STATE OF THE CAMERA
[Export] private bool Init_Zoomed = true;
[Export] private byte Init_Rotate = 0;
[Export] private byte Init_Speed = 2;
//VARIABLES THAT DESCRIBE THE CURRENT STATE OF THE ROOM
private Vector3 Current_Bound_Centroid; //the centroid of the camera-aligned rectangle the circumscribes each Marker3D
private readonly Vector3[,] CurrentAndFuture_Edges = new Vector3[3, 4]; //the GlobalPositions of the middles of the line segments connecting each corner of the circumscribed rectangle, and what they will be on rotation in either direction
private Vector2[][] CurrentAndFuture_Corners_ActualBounds = [new Vector2[4], new Vector2[4], new Vector2[4], new Vector2[4]]; //the GlobalPositions of each CollisionShape3D used to keep the camera in bounds, and what they will be on rotation in either direction
//VARIABLES THAT DESCRIBE THE CURRENT STATE OF THE CAMERA
private bool[] States_Zoom = [false, false]; //[whether-zooming, whether-zoomed-in]
private float Counter_Zoom = 0;
private bool[] States_Rotate = [false, false]; //[whether-rotating, whether-rotating-counterclockwise]
private float[] Points_Rotate = new float[2];
private float Counter_Rotate = 0;
private int Current_Rotate = 0;
private byte State_Move_Speed;
private bool State_Move_FromOOB = true; //I don't remember why this starts off true
private Vector3 Current_Move_FromOOB_Dir;
private Vector3 MostRecent_Position;
public override void _Ready()
{
CheckErrors(0);
Initialize_Link("");
Fixed_Rotate_Rads = 2 * MathHandler.Pi / MathF.Pow(2, Fixed_Rotate_Power);
CallDeferred(MethodName.Initialize_Nodes);
}
public override void _Process(double delta)
{
if (!State_Move_FromOOB)
{
if (OS.IsDebugBuild() && Input.IsActionJustPressed("debug_view_toggle"))
{
State_Debug_Collision = !State_Debug_Collision;
foreach (CollisionShape3D bound in yourBounds_Colliders)
{
if (State_Debug_Collision) bound.GetNode<MeshInstance3D>("MeshInstance3D").SetSurfaceOverrideMaterial(0, Debug_Mesh_Materials[0]);
else bound.GetNode<MeshInstance3D>("MeshInstance3D").SetSurfaceOverrideMaterial(0, Debug_Mesh_Materials[1]);
}
}
if (Input.IsActionJustPressed("cam_speed_slow") ^ Input.IsActionJustPressed("cam_speed_medium") ^ Input.IsActionJustPressed("cam_speed_fast"))
{
byte new_speed_maybe;
if (Input.IsActionJustPressed("cam_speed_slow")) new_speed_maybe = 0;
else if (Input.IsActionJustPressed("cam_speed_medium")) new_speed_maybe = 1;
else new_speed_maybe = 2;
if (new_speed_maybe != State_Move_Speed)
{
State_Move_Speed = new_speed_maybe;
}
}
}
}
public override void _PhysicsProcess(double delta)
{
if (State_Move_FromOOB) Logic_Move((float) delta);
else
{
//this short-circuits to disable rotation if currently zooming. I'm not sure if that's necessary but I implemented it early because I like it
if (Logic_Zoom((float) delta) || Logic_Rotate((float) delta)) return;
Logic_Move((float) delta);
}
}
private void CheckErrors(byte step)
{
switch (step)
{
case 0:
if (Fixed_Rotate_Power <= 1)
{
GD.PushError("Rotation power must be greater than 1.");
GetTree().Quit();
}
else if (Fixed_Rotate_Power > 3)
{
GD.PushWarning("Bound logic has not been properly configured for Rotation_Power > 3 and does not scale from current logic."); //see Possible Improvements note 2
}
if (Init_Rotate >= (float) Math.Pow(2, Fixed_Rotate_Power))
{
GD.PushError("Starting rotation increment is greater than range of sections.");
GetTree().Quit();
}
break;
case 1:
if (yourBounds_Colliders.Count != 4)
{
GD.PushError("One or more linked CollisionShape3Ds have not been set.");
GetTree().Quit();
}
if (your_RoomPoints.Count != 5)
{
GD.PushWarning("Linked Marker3Ds indicate a non-quadrilateral room, which has not been tested."); //see Possible Improvements note 1
}
float running = your_RoomPoints[0].GlobalPosition.Y;
for (byte i = 1; i < your_RoomPoints.Count - 1; i ++)
{
if (your_RoomPoints[i].GlobalPosition.Y != running)
{
GD.PushError("One or more members of Camera_Bound_Points have unequal Y positions."); //I didn't check what would happen if you broke this rule
GetTree().Quit();
}
}
break;
}
}
private void Initialize_Link(NodePath thisNode)
{
//this is a little janky but at a small number of nodes it's fine
if (thisNode == "")
{
myCollider_Front = GetNode<CollisionShape3D>("Collider_Front");
myCamera = myCollider_Front.GetNode<Camera3D>("Camera3D");
}
else
{
string node_path = Owner.GetPath() + thisNode.ToString()[2..];
if (node_path.Contains("Camera_Bounds"))
{
yourBounds_Colliders.Add(GetNode<CollisionShape3D>(node_path));
}
else if (node_path.Contains("Camera_Bound_Points"))
{
your_RoomPoints.Add(GetNode<Marker3D>(node_path));
}
else
{
GD.PushError("An unexpected Node is emitting a signal toward Initialize_Link.");
GetTree().Quit();
}
}
}
private Vector3[] Initialize_Markers()
{
//find the smallest axis-aligned rectangle that contains each of your_RoomPoints
Vector3 coord_min = new(float.MaxValue, your_RoomPoints[0].GlobalPosition.Y, float.MaxValue);
Vector3 coord_max = new(float.MinValue, your_RoomPoints[0].GlobalPosition.Y, float.MinValue);
//doing it this way futureproofs it in case there's ever a non-rectangular room, just change the i maximum
for (byte i = 0; i < 4; i ++)
{
for (byte j = 0; j < 3; j += 2)
{
coord_min[j] = Math.Min(your_RoomPoints[i].GlobalPosition[j], coord_min[j]);
coord_max[j] = Math.Max(your_RoomPoints[i].GlobalPosition[j], coord_max[j]);
}
}
return [coord_min, coord_max];
}
private void Initialize_Nodes()
{
CheckErrors(1);
//initialize the debug materials here since I can't figure out how to construct BaseMaterial3D without cloning it
Debug_Mesh_Materials[0] = yourBounds_Colliders[0].GetNode<MeshInstance3D>("MeshInstance3D").GetSurfaceOverrideMaterial(0).Duplicate() as BaseMaterial3D;
Debug_Mesh_Materials[1] = Debug_Mesh_Materials[0];
Debug_Mesh_Materials[1].Transparency = BaseMaterial3D.TransparencyEnum.Disabled;
//initialize axis-aligned rectangle for room geometry
Fixed_Corners_AxisAligned = Initialize_Markers();
//initialize zoom
States_Zoom[1] = Init_Zoomed;
if (Init_Zoomed) myCamera.Size = Fixed_Sizes[0];
else myCamera.Size = Fixed_Sizes[1];
//initialize rotation
myCamera.Rotation = new Vector3(-Fixed_Angle, 0, 0);
GlobalRotation = new Vector3(0, Init_Rotate * Fixed_Rotate_Rads, 0);
Current_Rotate = Init_Rotate;
//initialize movement
State_Move_Speed = Init_Speed;
//reparent the node to the first instance of the player character node
Reparent(Player_Overworld_CharacterBody3D.ourPlayers[0]); //just replace this with you player character object
//calculate the fixed Y height
Dependent_GlobalPosition_Y = your_RoomPoints[0].GlobalPosition.Y + Dependent_Height_Above_Wall;
//move pivot point directly above player, then adjust
Vector3 pos_player = GetParent<Player_Overworld_CharacterBody3D>().GlobalPosition;
MostRecent_Position = GlobalPosition = new Vector3
(
pos_player.X,
Dependent_GlobalPosition_Y,
pos_player.Z
);
//set the distance of the front-facing collider
myCollider_Front.Position = new Vector3(0, 0, (BakedIn_Wall_Height + Dependent_Height_Above_Wall) / MathF.Tan(Fixed_Angle));
Update_Bounds();
}
private void Update_Bounds()
{
//amount by which to increase the corner a bound looks at so that its position relative to the camera is constant
//e.g. at GlobalRotation = 0, yourBounds_Colliders[0] should go from corner 3 to corner 0
//while at GlobalRotation = Pi / 2, that same bound should go from corner 0 to corner 1
byte[] corners = new byte[2];
//declare tuple variables
int corner_advance_factor;
Vector3[] current_camaligned_corners;
float[] sin_s;
float[] cos_s;
Vector3 camaligned_margin;
//declare adjustment variables
//nothing ever touches the Y and Z components of scale, so they can just be set once here
Vector3 scale = new(1f / 16f, 1, 1f / 16f);
float rotation_y;
BoxShape3D new_shape;
Vector2[] returned_oobs = new Vector2[4];
//loop through the bound function and run it stripped down for the next clockwise and counterclockwise rotations
for (int i = -1; i < 2; i ++)
{
//NOTE THAT SOME MATH IS DONE IN THE TUPLE FUNCTION WITH corner_advance_factor BEFORE RETURNING ITS FINAL VALUE
(corner_advance_factor, current_camaligned_corners, sin_s, cos_s, camaligned_margin) = Updater_Calculate_Variables(i);
//set the transforms of the bounds on the camera-aligned rectangle, then adjust by relevant margin
for (byte j = 0; j < 4; j ++)
{
corners[0] = (byte) ((3 + j + corner_advance_factor) % 4);
corners[1] = (byte) ((corners[0] + 1) % 4);
//translate is a messy switch statement so it gets siloed in its own method
CurrentAndFuture_Edges[i + 1, j] = Update_Bound_Position(i, j, corners, corner_advance_factor, current_camaligned_corners, camaligned_margin, sin_s, cos_s);
//everything in this block only applies to current bounds (i == 0)
if (i == 0)
{
//determine scale and rotation
scale.X = current_camaligned_corners[corners[0]].DistanceTo(current_camaligned_corners[corners[1]]);
if (j % 2 == 0) rotation_y = GlobalRotation.Y + MathHandler.Pi / 2;
else rotation_y = GlobalRotation.Y;
//assign scale and rotation
new_shape = new() {Size = scale};
yourBounds_Colliders[j].Shape = new_shape;
yourBounds_Colliders[j].GetNode<MeshInstance3D>("MeshInstance3D").Scale = scale;
yourBounds_Colliders[j].GlobalRotation = new Vector3(0, rotation_y, 0);
yourBounds_Colliders[j].GlobalPosition = CurrentAndFuture_Edges[1, j];
}
//update corners used in OOB detection
if (j == 3)
{
CurrentAndFuture_Corners_ActualBounds[i + 1] = Update_CoordsOOB((byte) (i + 1), corner_advance_factor);
if (i == 0) CurrentAndFuture_Corners_ActualBounds[3] = Update_CoordsOOB(3, corner_advance_factor);
}
}
}
}
private Tuple<int, Vector3[], float[], float[], Vector3> Updater_Calculate_Variables(int rotational_shift)
{
int total_slices = (int) Math.Pow(2, Fixed_Rotate_Power);
int simulated_current_rotate = MathHandler.AddThenModulus(Current_Rotate, total_slices, rotational_shift);
int quarter_slices = (int) Math.Pow(2, Fixed_Rotate_Power - 2);
int corner_advance_factor = simulated_current_rotate / quarter_slices;
//IF THIS ISN'T CALLED BEFORE DIAGONAL ADVANCEMENT IT WILL SHIFT THE ORDER OF THE RETURN ARRAY
Vector3[] current_camaligned_corners = Update_CameraAlignedBoundingBox(corner_advance_factor, rotational_shift);
//diagonal advancement
//(from my own comments: can you write a more detailed comment explaining why the hell this needs to happen next time)
corner_advance_factor += Math.Sign(simulated_current_rotate % quarter_slices);
//set trigonometry variables
//camaligned_margin IS THE THING THAT MAKES THIS ENTIRE OPERATION WORK
//https://forum.godotengine.org/t/mathematically-find-orthographic-camera-bound-knowing-size-angle-and-position/142503
float[] angles =
[
GlobalRotation.Y + rotational_shift * Fixed_Rotate_Rads + MathHandler.Pi / 2,
GlobalRotation.Y + rotational_shift * Fixed_Rotate_Rads
];
float[] sin_s = [MathF.Sin(angles[0]), MathF.Sin(angles[1])];
float[] cos_s = [MathF.Cos(angles[0]), MathF.Cos(angles[1])];
Vector3 camaligned_margin = new
(
myCamera.Size / 2 * GetViewport().GetVisibleRect().Size.X / GetViewport().GetVisibleRect().Size.Y,
0,
(myCamera.Size / 2 - (Dependent_GlobalPosition_Y - current_camaligned_corners[0].Y) * MathF.Cos(Fixed_Angle)) / MathF.Sin(Fixed_Angle)
);
return Tuple.Create(corner_advance_factor, current_camaligned_corners, sin_s, cos_s, camaligned_margin);
}
private Vector3[] Update_CameraAlignedBoundingBox(int corner_advance_factor, int rotational_shift)
{
Current_Bound_Centroid = new
(
(Fixed_Corners_AxisAligned[0].X + Fixed_Corners_AxisAligned[1].X) / 2,
your_RoomPoints[0].GlobalPosition.Y,
(Fixed_Corners_AxisAligned[0].Z + Fixed_Corners_AxisAligned[1].Z) / 2
);
float inner_half_width = (Fixed_Corners_AxisAligned[1].X - Fixed_Corners_AxisAligned[0].X) / 2;
float inner_half_height = (Fixed_Corners_AxisAligned[1].Z - Fixed_Corners_AxisAligned[0].Z) / 2;
float cos_theta = (float) Math.Cos(GlobalRotation.Y + Fixed_Rotate_Rads * rotational_shift);
float sin_theta = (float) Math.Sin(GlobalRotation.Y + Fixed_Rotate_Rads * rotational_shift);
Vector3 axis_circum_width = new(cos_theta, 0, sin_theta);
Vector3 axis_circum_height = new(-sin_theta, 0, cos_theta);
float circum_half_width = inner_half_width * MathF.Abs(cos_theta) + inner_half_height * MathF.Abs(sin_theta);
float circum_half_height = inner_half_width * MathF.Abs(sin_theta) + inner_half_height * MathF.Abs(cos_theta);
Variant[] sort_buffer =
[
Current_Bound_Centroid + circum_half_width * axis_circum_width - circum_half_height * axis_circum_height,
Current_Bound_Centroid - circum_half_width * axis_circum_width - circum_half_height * axis_circum_height,
Current_Bound_Centroid - circum_half_width * axis_circum_width + circum_half_height * axis_circum_height,
Current_Bound_Centroid + circum_half_width * axis_circum_width + circum_half_height * axis_circum_height,
];
//always keep the same corners in the same index
//rotate the array downward for each 90 degrees of camera rotation
sort_buffer = MathHandler.Array_RotateDownward(sort_buffer, corner_advance_factor);
return
[
(Vector3) sort_buffer[0],
(Vector3) sort_buffer[1],
(Vector3) sort_buffer[2],
(Vector3) sort_buffer[3]
];
}
private Vector3 Update_Bound_Position(int access_modifier, byte bound_index, byte[] corners_in_question, int corner_advance_factor, Vector3[] current_camaligned_corners, Vector3 camaligned_margin, float[] sin_s, float[] cos_s)
{
Vector3 bound_camaligned_position = (current_camaligned_corners[corners_in_question[0]] + current_camaligned_corners[corners_in_question[1]]) / 2;
Vector3 transl_with_margin = Vector3.Zero;
if (State_Debug_Collision) bound_camaligned_position.Y = Dependent_GlobalPosition_Y - Dependent_Height_Above_Wall;
else bound_camaligned_position.Y = Dependent_GlobalPosition_Y;
byte simulated_current_rotate = (byte) MathHandler.AddThenModulus(Current_Rotate, (int) Math.Pow(2, Fixed_Rotate_Power), access_modifier);
//translate bound by margin so it stops camera from moving past edges of room
//the following are from my own comments:
//for whatever reason the advance factor seems to always be correct on a certain result of if_even() for diagonal
//NOTE the differences in the if statements are subtle! check the second comparison operators
//2026_08_13 ALSO this is not scaleable indefinitely, it breaks at Fixed_Rotate_Power > 3
//but I don't see a need for that to happen and would like to move on with my life
//I'm sure it's possible and might have to do with readjusting corner_advance_factor, but again, my life
//just don't send them inward if the camera is zoomed out I guess
if (States_Zoom[1])
{
switch (bound_index)
{
case 0:
transl_with_margin.X = camaligned_margin.X * sin_s[0] * -1;
if (simulated_current_rotate % 2 == 0 && corner_advance_factor % 2 == 0) transl_with_margin.Z = camaligned_margin.X * cos_s[0];
else transl_with_margin.Z = camaligned_margin.X * cos_s[0] * -1;
break;
case 1:
float adjustment = Dependent_Height_Above_Wall - BakedIn_Wall_Height - BakedIn_Bound_Thickness / 4;
transl_with_margin.X = (camaligned_margin.Z + adjustment) * sin_s[1];
if (simulated_current_rotate % 2 == 0 && corner_advance_factor % 2 != 0) transl_with_margin.Z = (camaligned_margin.Z + adjustment) * cos_s[1] * -1;
else transl_with_margin.Z = (camaligned_margin.Z + adjustment) * cos_s[1];
break;
case 2:
transl_with_margin.X = camaligned_margin.X * sin_s[0];
transl_with_margin.Z = camaligned_margin.X * cos_s[0];
break;
case 3:
transl_with_margin.Z = camaligned_margin.Z * cos_s[1] * -1;
if (simulated_current_rotate % 2 == 0 && corner_advance_factor % 2 == 0) transl_with_margin.X = camaligned_margin.Z * sin_s[1];
else transl_with_margin.X = camaligned_margin.Z * sin_s[1] * -1;
break;
}
}
return bound_camaligned_position + transl_with_margin;
}
private Vector2[] Update_CoordsOOB(byte access_index, int corner_advance_factor)
{
/*
access_index == 0 --> adjacent clockwise rotation
access_index == 1 --> current rotation
access_index == 2 --> adjacent counterclockwise rotation
access_index == 3 --> current rotation shrunk toward centroid
*/
byte[] virtual_is = new byte[2];
Vector2[] rotated_bound_positions = new Vector2[2];;
bool was_access_index_3 = access_index == 3;
if (was_access_index_3) access_index = 1;
//quarter_increment is the same as Current_Rotate but only for traveling through one quarter of the circle
//(from my own comments: this will break if you use the raw increment instead of the quarter and I don't quite recall why, oops)
int quarter_increment = MathHandler.AddThenModulus(Current_Rotate, (int) Math.Pow(2, Fixed_Rotate_Power), access_index - 1) % (int) Math.Pow(2, Fixed_Rotate_Power - 2);
float analyte_angle = - quarter_increment * Fixed_Rotate_Rads;
Vector2 translate_values = Vector2.Zero;
int adj_sign_z;
Vector2[] actual_corners = new Vector2[4];
for (byte i = 0; i < 4; i ++)
{
virtual_is[0] = i;
virtual_is[1] = (byte) ((i + 1) % 4);
//find the coordinates of each bound adjacent to the current corner, rotated around the centroid by the current angle
rotated_bound_positions =
[
new Vector2
(
MathF.Cos(analyte_angle) * (CurrentAndFuture_Edges[access_index, virtual_is[0]].X - Current_Bound_Centroid.X) - MathF.Sin(analyte_angle) * (CurrentAndFuture_Edges[access_index, virtual_is[0]].Z - Current_Bound_Centroid.Z) + Current_Bound_Centroid.X,
MathF.Sin(analyte_angle) * (CurrentAndFuture_Edges[access_index, virtual_is[0]].X - Current_Bound_Centroid.X) + MathF.Cos(analyte_angle) * (CurrentAndFuture_Edges[access_index, virtual_is[0]].Z - Current_Bound_Centroid.Z) + Current_Bound_Centroid.Z
),
new Vector2
(
MathF.Cos(analyte_angle) * (CurrentAndFuture_Edges[access_index, virtual_is[1]].X - Current_Bound_Centroid.X) - MathF.Sin(analyte_angle) * (CurrentAndFuture_Edges[access_index, virtual_is[1]].Z - Current_Bound_Centroid.Z) + Current_Bound_Centroid.X,
MathF.Sin(analyte_angle) * (CurrentAndFuture_Edges[access_index, virtual_is[1]].X - Current_Bound_Centroid.X) + MathF.Cos(analyte_angle) * (CurrentAndFuture_Edges[access_index, virtual_is[1]].Z - Current_Bound_Centroid.Z) + Current_Bound_Centroid.Z
)
];
//find the camera-aligned distance between those rotated positions,
//and use it to translate the actual position of a bound to its edge a.k.a the current corner
switch ((i + corner_advance_factor) % 4)
{
case 0:
case 2:
translate_values.X = (rotated_bound_positions[1].Y - rotated_bound_positions[0].Y) * MathF.Sin(analyte_angle);
translate_values.Y = (rotated_bound_positions[1].Y - rotated_bound_positions[0].Y) * MathF.Cos(analyte_angle);
break;
case 1:
case 3:
adj_sign_z = 1;
if (quarter_increment != 0) adj_sign_z = -1;
translate_values.X = (rotated_bound_positions[1].X - rotated_bound_positions[0].X) * MathF.Cos(analyte_angle);
translate_values.Y = (rotated_bound_positions[1].X - rotated_bound_positions[0].X) * MathF.Sin(analyte_angle) * adj_sign_z;
break;
}
actual_corners[i] = new Vector2
(
CurrentAndFuture_Edges[access_index, i].X + translate_values.X,
CurrentAndFuture_Edges[access_index, i].Z + translate_values.Y
);
//shrunk down set is smaller to make the camera have to move further to get back in bounds than it did to escape
if (was_access_index_3)
{
actual_corners[i] = new Vector2
(
actual_corners[i].X - BakedIn_Bound_Thickness / 2 * Math.Sign(actual_corners[i].X - Current_Bound_Centroid.X),
actual_corners[i].Y - BakedIn_Bound_Thickness / 2 * Math.Sign(actual_corners[i].Y - Current_Bound_Centroid.Z)
);
}
}
return actual_corners;
}
private bool Logic_Zoom(float delta)
{
if(!States_Zoom[0] && Input.IsActionJustPressed("cam_zoom_toggle"))
{
States_Zoom[0] = true;
return true;
}
else if (States_Zoom[0])
{
if (Counter_Zoom >= 1)
{
States_Zoom[0] = false;
States_Zoom[1] = !States_Zoom[1];
Counter_Zoom = 0;
Update_Bounds();
return false;
}
else
{
Counter_Zoom += delta / Fixed_Zoom_Seconds;
if (States_Zoom[1]) myCamera.Size = Mathf.Lerp(Fixed_Sizes[0], Fixed_Sizes[1], Counter_Zoom);
else myCamera.Size = Mathf.Lerp(Fixed_Sizes[1], Fixed_Sizes[0], Counter_Zoom);
return true;
}
}
return false;
}
private bool Logic_Rotate(float delta)
{
if (!States_Rotate[0] && (Input.IsActionJustPressed("cam_rot_left") || Input.IsActionJustPressed("cam_rot_right")))
{
//simulate the desired rotation and check it against
byte simulation = 0;
if (Input.IsActionJustPressed("cam_rot_left")) simulation = 2;
if (true)
//if (Check_IsWithinBounds(simulation))
{
States_Rotate[0] = true;
States_Rotate[1] = Input.IsActionJustPressed("cam_rot_left");
Points_Rotate[0] = GlobalRotation.Y;
if (States_Rotate[1])
{
Points_Rotate[1] = GlobalRotation.Y + Fixed_Rotate_Rads;
Current_Rotate = MathHandler.AddThenModulus(Current_Rotate, (int) Math.Pow(2, Fixed_Rotate_Power));
}
else
{
Points_Rotate[1] = GlobalRotation.Y - Fixed_Rotate_Rads;
Current_Rotate = MathHandler.AddThenModulus(Current_Rotate, (int) Math.Pow(2, Fixed_Rotate_Power), -1);
}
Enable_Collision(false);
return true;
}
else return false;
}
else if (States_Rotate[0])
{
if (Counter_Rotate >= 1)
{
States_Rotate[0] = false;
Counter_Rotate = 0;
Enable_Collision(true);
Update_Bounds();
return false;
}
else
{
Counter_Rotate += delta / Fixed_Rotate_Seconds;
float rot_current;
if (States_Rotate[1]) {rot_current = Mathf.Lerp(Points_Rotate[0], Points_Rotate[1], Counter_Rotate);}
else {rot_current = Mathf.Lerp(Points_Rotate[1], Points_Rotate[0], 1 - Counter_Rotate);}
GlobalRotation = new Vector3
(
GlobalRotation.X,
rot_current,
GlobalRotation.Z
);
return true;
}
}
return false;
}
private void Enable_Collision(bool enabled)
{
//(from my own comments: 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 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 because 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(3))
{
State_Move_FromOOB = false;
Enable_Collision(true);
}
}
else if (Check_IsWithinBounds(1) || State_Debug_Collision)
{
//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();
}
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, Dependent_GlobalPosition_Y, Current_Bound_Centroid.Z);
Current_Move_FromOOB_Dir = (centroid_xz - GlobalPosition).Normalized();
}
}
private bool Check_IsWithinBounds(byte array_row)
{
//I don't actually remember why it starts with State_Move_FromOOB = true
if (yourBounds_Colliders.Count == 0)
{
return true;
}
//this is actually a fixed variable but it's not stored
float dist_to_lookpoint_xz = (BakedIn_Wall_Height + Dependent_Height_Above_Wall) / MathF.Tan(Fixed_Angle);
//simulate rotation of camera around pivot point
Vector2 check_pos;
float theta_difference;
if (array_row == 0 || array_row == 2)
{
theta_difference = Fixed_Rotate_Rads * - (array_row - 1);
check_pos = new Vector2
(
MathF.Cos(theta_difference) * (myCamera.GlobalPosition.X - GlobalPosition.X) - MathF.Sin(theta_difference) * (myCamera.GlobalPosition.Z - GlobalPosition.Z) + GlobalPosition.X,
MathF.Sin(theta_difference) * (myCamera.GlobalPosition.X - GlobalPosition.X) + MathF.Cos(theta_difference) * (myCamera.GlobalPosition.Z - GlobalPosition.Z) + GlobalPosition.Z
);
}
else
{
if (array_row != 1 && array_row != 3)
{
GD.PushError("Camera_Overworld: Parameter array_row of Check_IsWithinBounds must be from 0 to 3.");
GetTree().Quit();
}
theta_difference = 0;
check_pos = new Vector2(myCamera.GlobalPosition.X, myCamera.GlobalPosition.Z);
}
Vector2 check_lookingat_pos = new
(
GlobalPosition.X + dist_to_lookpoint_xz * MathF.Cos(GlobalRotation.Y + theta_difference),
GlobalPosition.Z + dist_to_lookpoint_xz * MathF.Sin(GlobalRotation.Y + theta_difference)
);
//not a huge fan of this being such a simple check since it's always preferable to use myCamera if possible
//but it works well enough that it can be left alone for now
if (check_lookingat_pos.DistanceTo(CurrentAndFuture_Corners_ActualBounds[array_row][1]) < check_lookingat_pos.DistanceTo(CurrentAndFuture_Corners_ActualBounds[array_row][3]))
{
check_pos = new Vector2 (GlobalPosition.X, GlobalPosition.Z);
}
return MathHandler.Point_IsWithinPolygon(check_pos, CurrentAndFuture_Corners_ActualBounds[array_row]);
}
}