Physics corrections

This commit is contained in:
Halbear 2026-07-05 17:56:47 +01:00
parent 2b0344630e
commit eb09963e3a
17 changed files with 1126 additions and 899 deletions

View file

@ -29,7 +29,7 @@ public class RigidBody {
public Vector3f CrossSectionAreas = new Vector3f(1.8f * 0.9f, 0.6f * 0.9f, 1.8f * 0.6f);
public Vector3f WeightDistribution = new Vector3f(2.0f,5.0f,7.0f);
public float FallingAcceleration = 0.0f;
public float Bounciness = 1f;
public float Bounciness = 0.3f;
public final Vector3f LinearVelocity = new Vector3f();
public final Vector3f AngularVelocity = new Vector3f();

View file

@ -6,7 +6,7 @@ import org.joml.Vector3f;
//Resolves the Separating Axis Theorem Test into reaction forces in impulses and position correction
public class RigidCollision {
private static final float CORRECTION_THRESHOLD = 1.0f; //how much it gets corrected positionally per physics step
private static final float SLOP = 0.001f; // amount of overlap can happen before collision gets corrected
private static final float SLOP = 0.000f; // amount of overlap can happen before collision gets corrected
public synchronized static void ResolveCollision(RigidBody body1, RigidBody body2, SeparatingAxisTheoremTester.CollisionResult collisionResult){
Vector3f Normal = collisionResult.Normal;
@ -43,6 +43,9 @@ public class RigidCollision {
float j = -(1 + Restitution) * VelocityAlongNormal / InverseMassSum;
Impulse.set(normal).mul(j);
body1.LinearVelocity.mul(-1,body1.LinearVelocity.y < 0 ? 1 : -1,-1).mul(body1.Bounciness,body1.LinearVelocity.y < 0 ? 1 : body1.Bounciness,body1.Bounciness);
body2.LinearVelocity.mul(-1,body1.LinearVelocity.y < 0 ? 1 : -1,-1).mul(body1.Bounciness,body1.LinearVelocity.y < 0 ? 1 : body1.Bounciness,body1.Bounciness);
body1.ApplyImpulse(NegatedImpulse.set(Impulse).negate(), Point);
body2.ApplyImpulse(Impulse,Point);

View file

@ -25,9 +25,17 @@ public class RigidPhysicsController implements IPhysicsController {
public final List<String> Collisions = new ArrayList<>();
public static RigidBody Ground = new RigidBody("FLOOR",ConvexMesh.Box(100,100f,100),0);
public static RigidBody Wall = new RigidBody("WALL",ConvexMesh.Box(100,100f,10),0);
public static RigidBody Wall2 = new RigidBody("WALL2",ConvexMesh.Box(100,100f,10),0);
public static RigidBody Wall3 = new RigidBody("WALL3",ConvexMesh.Box(10,100f,100),0);
public static RigidBody Wall4 = new RigidBody("WALL4",ConvexMesh.Box(10,100f,100),0);
static {
Ground.Position.set(0,-100f,0);
Wall.Position.set(0,0f,-60f);
Wall2.Position.set(0,0f,60f);
Wall3.Position.set(-60f,0f,0);
Wall4.Position.set(60f,0f,0);
}
public String RigidBodyID = "NULL";
@ -49,6 +57,12 @@ public class RigidPhysicsController implements IPhysicsController {
newRigidBody.UpdateInertiaWorld();
WorldPhysicsManager.PhysicsObjects.put(ControllerID, this);
WorldPhysicsManager.ActiveControllers.add(ControllerID);
if(RigidBodyID != null && RigidBodyRegister.get(RigidBodyID) != null && ActorID != "NULL") {
RigidBody rigidBody = RigidBodyRegister.get(RigidBodyID);
if (Actor.Actors.get(ActorID) != null) {
Actor.Actors.get(ActorID).SetPivotOffset(new Vector3f(0, (rigidBody.shape.TopRightForward.y - rigidBody.shape.BottomLeftBack.y) / 2f, 0));
}
}
return this;
}
@ -56,6 +70,11 @@ public class RigidPhysicsController implements IPhysicsController {
RigidBodyID = rigidBody.GetID();
WorldPhysicsManager.PhysicsObjects.put(ControllerID, this);
WorldPhysicsManager.ActiveControllers.add(ControllerID);
if( rigidBody != null && ActorID != "NULL") {
if (Actor.Actors.get(ActorID) != null) {
Actor.Actors.get(ActorID).SetPivotOffset(new Vector3f(0, (rigidBody.shape.TopRightForward.y - rigidBody.shape.BottomLeftBack.y) / 2f, 0));
}
}
return this;
}
@ -77,7 +96,7 @@ public class RigidPhysicsController implements IPhysicsController {
@Override
public void PhysicsTick(float DeltaTime, float TargetFrameRate) {
DeltaTime/=10f;
if(DeltaTime >0.1f) DeltaTime = 0.1f;
// if(DeltaTime >0.1f) DeltaTime = 0.1f;
if(RigidBodyID != null && RigidBodyRegister.get(RigidBodyID) != null && ActorID != "NULL") {
RigidBody rigidBody = RigidBodyRegister.get(RigidBodyID);
rigidBody.Integrate(DeltaTime, new Vector3f(0,-1.8f,0));
@ -91,7 +110,6 @@ public class RigidPhysicsController implements IPhysicsController {
}
}
if(Actor.Actors.get(ActorID) != null) {
Actor.Actors.get(ActorID).SetPivotOffset(new Vector3f(0,(rigidBody.shape.TopRightForward.y - rigidBody.shape.BottomLeftBack.y)/2f,0));
Actor.Actors.get(ActorID).SetPosition(new Vector3f(rigidBody.Position));
Actor.Actors.get(ActorID).SetRotation(rigidBody.Rotation);
}

View file

@ -36,12 +36,12 @@ public class SeparatingAxisTheoremTester {
public final List<Contact> Contacts = new ArrayList<>();
}
//as much as i'd like to multithread the fuck out of this, im putting it as synchronised so it doesn't get messy, and so memory management is smoother, i want to minimize the amount of Vectors creating during loops
public synchronized static CollisionResult TestCollision(ConvexMesh Mesh1, ConvexMesh Mesh2){
public static CollisionResult TestCollision(ConvexMesh Mesh1, ConvexMesh Mesh2){
float BestOverlap = Float.MAX_VALUE;
Vector3f BestAxis = null;
ConvexMesh.Face BestFace =null;
ConvexMesh BestFaceOwner = null;
if(Mesh1 == null || Mesh2 == null) return null;
for(ConvexMesh.Face face : Mesh1.Faces){
Vector3f Axis = Mesh1.WorldFaceNormal(face);
Float Overlap = AxisOverlap(Mesh1,Mesh2,Axis);
@ -147,14 +147,14 @@ public class SeparatingAxisTheoremTester {
return result;
}
private synchronized static Float AxisOverlap(ConvexMesh mesh1, ConvexMesh mesh2, Vector3f axis) {
private static Float AxisOverlap(ConvexMesh mesh1, ConvexMesh mesh2, Vector3f axis) {
float[] Range1 = ProjectOntoAxis(mesh1, axis);
float[] Range2 = ProjectOntoAxis(mesh2, axis);
float Overlap = Math.min(Range1[1],Range2[1]) - Math.max(Range1[0],Range2[0]);
return (Overlap < 0) ? null : Overlap;
}
private synchronized static float[] ProjectOntoAxis(ConvexMesh mesh, Vector3f axis){
private static float[] ProjectOntoAxis(ConvexMesh mesh, Vector3f axis){
float minimum = Float.MAX_VALUE, maximum = -Float.MAX_VALUE;
for(int i = 0; i < mesh.LocalVertices.size(); i++){
float distance = mesh.WorldVertex(i).dot(axis);
@ -164,14 +164,14 @@ public class SeparatingAxisTheoremTester {
return new float[]{minimum,maximum};
}
private synchronized static Vector3f MeshCenter(ConvexMesh mesh){
private static Vector3f MeshCenter(ConvexMesh mesh){
Vector3f Center = new Vector3f();
for(int i = 0; i < mesh.LocalVertices.size(); i++) Center.add(mesh.WorldVertex(i));
Center.div(mesh.LocalVertices.size());
return Center;
}
private synchronized static ConvexMesh.Face FindIncidentFace(ConvexMesh mesh, Vector3f ReferenceNormal){
private static ConvexMesh.Face FindIncidentFace(ConvexMesh mesh, Vector3f ReferenceNormal){
ConvexMesh.Face Best = null;
float BestDot = Float.MAX_VALUE;
for(ConvexMesh.Face face : mesh.Faces){
@ -184,7 +184,7 @@ public class SeparatingAxisTheoremTester {
return Best;
}
private synchronized static List<Vector3f> ClipPolygonAgainstFace(List<Vector3f> Incident, List<Vector3f> ReferencePoly, Vector3f ReferenceNormal){
private static List<Vector3f> ClipPolygonAgainstFace(List<Vector3f> Incident, List<Vector3f> ReferencePoly, Vector3f ReferenceNormal){
List<Vector3f> Output = new ArrayList<>(Incident);
int Count = ReferencePoly.size();
for(int i = 0; i < Count; i++){
@ -215,7 +215,7 @@ public class SeparatingAxisTheoremTester {
return Output;
}
private synchronized static Vector3f IntersectEdge(Vector3f Vertex1, Vector3f Vertex2, Vector3f Normal, float Offset) {
private static Vector3f IntersectEdge(Vector3f Vertex1, Vector3f Vertex2, Vector3f Normal, float Offset) {
Vector3f Distance = new Vector3f(Vertex2).sub(Vertex1);
float Denominator = Normal.dot(Distance);
float t = (Math.abs(Denominator) < T4Math.Math3D.epsilon) ? 0 : (Offset - Normal.dot(Vertex1)) / Denominator;
@ -223,7 +223,7 @@ public class SeparatingAxisTheoremTester {
}
private synchronized static Vector3f[] ClosestPointsSegment2(Vector3f p1, Vector3f q1, Vector3f p2, Vector3f q2) {
private static Vector3f[] ClosestPointsSegment2(Vector3f p1, Vector3f q1, Vector3f p2, Vector3f q2) {
Vector3f d1 = new Vector3f(q1).sub(p1);
Vector3f d2 = new Vector3f(q2).sub(p2);
Vector3f r = new Vector3f(p1).sub(p2);

View file

@ -1,6 +1,7 @@
package net.halbear.Terrain4J.EngineCore.Logic.Physics;
import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import net.halbear.Terrain4J.EngineCore.Threads.PhysicsThread;
import java.util.ArrayList;
import java.util.HashMap;
@ -21,30 +22,40 @@ public class WorldPhysicsManager {
public static float FixedTimeStep = 1f / 60f;
public static int MaxSubStepsPerFrame = 8;
private static float Accumulator = 0f;
private static boolean SingleThread = false;
private static List<PhysicsThread> physicsThreads = new ArrayList<>();
public static void SteppedPhysicsTick(float DeltaTime, float TargetFrameTime){
int ThreadsNeeded = (int)Math.floor(ActiveControllers.size()/100f);
SingleThread = ThreadsNeeded <= 1;
if(EngineConfig.getInstance().IsEngineThrottled()) return;
DeltaTime *= EngineConfig.getInstance().PhysicsSpeed();
Accumulator += DeltaTime;
float maxAccumulated = FixedTimeStep * MaxSubStepsPerFrame;
if (Accumulator > maxAccumulated) Accumulator = maxAccumulated;
int stepsRun = 0;
while (Accumulator >= FixedTimeStep && stepsRun < MaxSubStepsPerFrame) {
PhysicsTick++;
for (int i = 0; i < ActiveControllers.size(); i++) {
if (PhysicsObjects.get(ActiveControllers.get(i)) != null) {
PhysicsObjects.get(ActiveControllers.get(i)).PhysicsTick(FixedTimeStep, TargetFrameTime);
}
/* if(!SingleThread && ThreadsNeeded > physicsThreads.size()) {
while(ThreadsNeeded > physicsThreads.size()) {
int Index = physicsThreads.size();
physicsThreads.add(new PhysicsThread("PhysThread"+Index, Index, ThreadsNeeded).StartThread());
}
Accumulator -= FixedTimeStep;
stepsRun++;
}
for (int i = 0; i < ActiveControllers.size(); i++) {
PhysicsObjects.get(ActiveControllers.get(i)).Collisions().clear();
}
} else if(SingleThread){*/
Accumulator += DeltaTime;
float maxAccumulated = FixedTimeStep * MaxSubStepsPerFrame;
if (Accumulator > maxAccumulated) Accumulator = maxAccumulated;
int stepsRun = 0;
while (Accumulator >= FixedTimeStep && stepsRun < MaxSubStepsPerFrame) {
PhysicsTick++;
for (int i = 0; i < ActiveControllers.size(); i++) {
if (PhysicsObjects.get(ActiveControllers.get(i)) != null) {
PhysicsObjects.get(ActiveControllers.get(i)).PhysicsTick(FixedTimeStep, TargetFrameTime);
}
}
Accumulator -= FixedTimeStep;
stepsRun++;
}
for (int i = 0; i < ActiveControllers.size(); i++) {
PhysicsObjects.get(ActiveControllers.get(i)).Collisions().clear();
}
// }
}
public static void PhysicsTick(float DeltaTime, float TargetFrameTime){

View file

@ -17,6 +17,8 @@ import net.halbear.Terrain4J.EngineCore.Logic.GameLogic;
import net.halbear.Terrain4J.EngineCore.Logic.InitData;
import net.halbear.Terrain4J.EngineCore.Logic.Physics.LegacyPhysicsController;
import net.halbear.Terrain4J.EngineCore.Logic.Physics.RigidBody.ConvexMesh;
import net.halbear.Terrain4J.EngineCore.Logic.Physics.RigidBody.RigidBody;
import net.halbear.Terrain4J.EngineCore.Logic.Physics.RigidBody.RigidPhysicsController;
import net.halbear.Terrain4J.EngineCore.Logic.Physics.WorldPhysicsManager;
import net.halbear.Terrain4J.EngineCore.Logic.Rendering.ModelLoader;
import net.halbear.Terrain4J.EngineCore.Main.Scene.*;
@ -76,8 +78,8 @@ public class GameCore implements GameLogic {
public InitData Initialise(EngineInstance engineInstance) {
IScene scene = engineInstance.scene();
List<ModelData> models = new ArrayList<>();
MelonaData = ModelLoader.LoadModel("resources/models/melona/melona.json");
List<MaterialData> MelonaDataMat = ModelLoader.LoadMaterials("resources/models/melona/melona_mat.json");
MelonaData = ModelLoader.LoadModel("resources/models/cube/Cube.json");
List<MaterialData> MelonaDataMat = ModelLoader.LoadMaterials("resources/models/cube/Cube_mat.json");
var Melona = new Actor("Melona", MelonaData.ID(), new Vector3f(0,0f,-5f)).SetRotation(0,(float)Math.toRadians(180),0);
Melona.CreatePhysicsController(LegacyPhysicsController.CollisionType.Sensor);
/* Melona.GetPhysicsController()
@ -166,7 +168,9 @@ public class GameCore implements GameLogic {
SpawnPosition.add(SpawnDirection);
var Melona = new Actor("MelonaSpawned" + scene.GetActors().size(), MelonaData.ID(), new Vector3f(SpawnPosition));
Melona.CreatePhysicsController( ConvexMesh.Box(0.75f,1,0.5f),20);
Melona.CreatePhysicsController( ConvexMesh.Box(0.5f,0.5f,0.5f),20);
RigidPhysicsController physicsController = (RigidPhysicsController) Melona.GetPhysicsController();
RigidBody.RigidBodyRegister.get(physicsController.RigidBodyID).LinearVelocity.set(0,0,-20f -(40f * (float)Math.random()));
scene.AddActor(Melona);
}
@ -245,7 +249,7 @@ public class GameCore implements GameLogic {
float MovementDist = (float)(FrameDiffNanoSeconds / 1000000L) * MOVEMENT_SPEED;
Camera camera = scene.GetCamera();
if(input.keyPressed(GLFW_KEY_ENTER)) {
SpawnNewActor(engineInstance, new Vector3f( (float)(2 * Math.random()),20 + (float)(10 * Math.random()), (float)(2 * Math.random())),new Vector3f(-1f + (float)(2 * Math.random()),-1f + (float)(2 * Math.random()),-1f + (float)(2 * Math.random())),new Vector3f((float)Math.random(),(float)Math.random(),(float)Math.random()));
SpawnNewActor(engineInstance, new Vector3f( (float)(5 * Math.random()),20 + (float)(10 * Math.random()), (float)(5 * Math.random())),new Vector3f(-1f + (float)(2 * Math.random()),-1f + (float)(2 * Math.random()),-1f + (float)(2 * Math.random())),new Vector3f((float)Math.random(),(float)Math.random(),(float)Math.random()));
}
if(input.keyPressed(GLFW_KEY_1)) {
Gimbal.universalType = Gimbal.GimbalType.Move;

View file

@ -0,0 +1,79 @@
package net.halbear.Terrain4J.EngineCore.Threads;
import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig;
import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance;
import net.halbear.Terrain4J.EngineCore.Logic.GameLogic;
import net.halbear.Terrain4J.EngineCore.Logic.Physics.WorldPhysicsManager;
import net.halbear.Terrain4J.EngineCore.Main.PrimaryRuntime;
import org.tinylog.Logger;
import static net.halbear.Terrain4J.EngineCore.Logic.Physics.WorldPhysicsManager.ActiveControllers;
import static net.halbear.Terrain4J.EngineCore.Logic.Physics.WorldPhysicsManager.PhysicsObjects;
public class PhysicsThread implements Runnable{
public static boolean running = true;
public final Thread EngineThread;
public static float FixedTimeStep = 1f / 60f;
public static int PhysicsTick = 0;
public static int MaxSubStepsPerFrame = 8;
public int Frames = 0;
public int TickFrames = 0;
private static float Accumulator = 0f;
public String ThreadName;
public int Index;
public int Total;
public Thread FetchThread(){
return EngineThread;
}
public PhysicsThread StartThread(){
EngineThread.start();
return this;
}
public PhysicsThread(String ThreadName, int Index, int TotalCount){
this.ThreadName = ThreadName;
Logger.debug("Starting {} Thread",ThreadName);
EngineThread = new Thread(this);
EngineThread.setName("Terrain4J "+ThreadName+" Engine Thread");
this.Index = Index;
this.Total = TotalCount;
}
@Override
public void run() {
long InitialTime = System.nanoTime();
long updateTime = InitialTime;
double[] FrameTimes = new double[]{0,0,0,0};
while(FastTickThread.running && (Index/Total) * ActiveControllers.size() < ActiveControllers.size()){
long now = System.nanoTime();
TickFrames++;
long nsTimeDiff = now - updateTime;
updateTime = now;
float PhysicsFramerate = 24f;
float PhysicsFrameTime = 1000f/PhysicsFramerate;
float DeltaTime = (float)nsTimeDiff/(PhysicsFrameTime*1000000f);
SteppedPhysicsTickMultiThreaded(DeltaTime, 60,Index,Total);
}
}
public static void SteppedPhysicsTickMultiThreaded(float DeltaTime, float TargetFrameTime, int Index, int Total){
Accumulator += DeltaTime;
float maxAccumulated = FixedTimeStep * MaxSubStepsPerFrame;
if (Accumulator > maxAccumulated) Accumulator = maxAccumulated;
int stepsRun = 0;
while (Accumulator >= FixedTimeStep && stepsRun < MaxSubStepsPerFrame) {
PhysicsTick++;
for (int i = (Index/Total) * ActiveControllers.size(); i < Math.min((Index + 1/Total) * ActiveControllers.size(),ActiveControllers.size()); i++) {
if (PhysicsObjects.get(ActiveControllers.get(i)) != null) {
PhysicsObjects.get(ActiveControllers.get(i)).PhysicsTick(FixedTimeStep, TargetFrameTime);
}
}
Accumulator -= FixedTimeStep;
stepsRun++;
}
}
}