Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions DemoUtilities/Window.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System;
using System.Diagnostics;
using BepuUtilities;
using OpenTK;
using BepuUtilities;
using OpenTK.Graphics;
using OpenTK.Platform;
using System;
using System.Diagnostics;
using System.Threading;
using Vector2 = System.Numerics.Vector2;

Expand Down Expand Up @@ -182,6 +182,39 @@ public void Run(Action<float> updateHandler, Action<Int2> onResize)
windowUpdateLoopRunning = false;
}

public void SingleFrame(Action<float> updateHandler, Action<Int2> onResize, float dt)
{

if (disposed)
return;
if (resized)
{
//Note that minimizing or resizing the window to invalid sizes don't result in actual resize attempts. Zero width rendering surfaces aren't allowed.
if (window.Width > 0 && window.Height > 0)
{
onResize(new Int2(window.Width, window.Height));
}
resized = false;
}
window.ProcessEvents();
if (tryToClose)
{
window.Close();
return;
}
long time = Stopwatch.GetTimestamp();

if (window.WindowState != WindowState.Minimized)
{
updateHandler(dt);
}
else
{
//If the window is minimized, take a breather.
Thread.Sleep(1);
}
}

private bool disposed;
public void Dispose()
{
Expand Down
71 changes: 71 additions & 0 deletions Demos.GL/Broken/CompoundFallsThroughFloor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

using BepuPhysics;
using BepuPhysics.Collidables;
using BepuUtilities;
using DemoContentLoader;
using DemoRenderer;
using DemoUtilities;
using System.Numerics;

namespace Demos.Broken;

internal class CompoundFallsThroughFloor : Demo
{
private BodyActivityDescription NeverSleep() => new BodyActivityDescription(sleepThreshold: float.MinValue, minimumTimestepCountUnderThreshold: byte.MaxValue);

public override void Initialize(ContentArchive content, Camera camera)
{
camera.Position = new Vector3(-1000f, 400, -1000f);
camera.Yaw = MathHelper.Pi * 3f / 4;
camera.Pitch = MathHelper.Pi * 0.1f;

var narrowPhaseCallbacks = new SimpleNarrowPhaseCallbacks(
frictionCoefficient: 0.3f,
maximumRecoveryVelocity: 500,
impactFrequency: 20,
impactDampingRatio: 0.5f
);

var gravity = new Vector3(0, -9807f, 0); // mm/s
var timeStepSeconds = .12f;

Simulation = Simulation.Create(BufferPool, narrowPhaseCallbacks, new PoseIntegratorCallbacks(gravity: gravity, timeStepSeconds: timeStepSeconds), new SolveDescription(8, 1));
var floor = new Box(1000, 9, 1000);

Simulation.Statics.Add(new StaticDescription(RigidPose.Identity, Simulation.Shapes.Add(floor)));


// shared constants
const float itemMass = 1;
const float startingHeight = 18;

// Item as a standalone (not compound)
{
var rawCollidableBox = new Box(100, 130, 100);
var boxPose = new RigidPose(new Vector3(-300, rawCollidableBox.HalfHeight + floor.HalfHeight + startingHeight, 300), Quaternion.Identity);
var boxInertia = rawCollidableBox.ComputeInertia(itemMass);
var boxIndex = Simulation.Shapes.Add(rawCollidableBox);
var collisionDetection = new ContinuousDetection();
var boxCollidable = new CollidableDescription(boxIndex, collisionDetection);
var boxDescription = BodyDescription.CreateDynamic(boxPose, boxInertia, boxCollidable, NeverSleep());
Simulation.Bodies.Add(boxDescription);
}

// Item as compound
{
var childShape = new Box(100, 130, 100);
var compoundPose = new RigidPose(new Vector3(300, childShape.HalfHeight + floor.HalfHeight + startingHeight, -300), Quaternion.Identity);
var builder = new CompoundBuilder(BufferPool, Simulation.Shapes, 1);

builder.Add(childShape, RigidPose.Identity, itemMass);
builder.BuildDynamicCompound(out var children, out var inertia);
Simulation.Bodies.Add(BodyDescription.CreateDynamic(compoundPose, inertia, Simulation.Shapes.Add(new Compound(children)), NeverSleep()));
}

}

public override void Update(Window window, Camera camera, Input input, float dt)
{
base.Update(window, camera, input, dt);
}
}
33 changes: 33 additions & 0 deletions Demos.GL/Broken/PoseIntegratorCallbacks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using BepuPhysics;
using BepuUtilities;
using System.Numerics;

namespace Demos.Broken;

/// <summary>
/// An implementation of INarrowPhaseCallbacks (a BePu interface) needed to get simulation running with BePu.
/// </summary>
/// <remarks> Basically a verbatim copy from the example given here: https://github.com/bepu/bepuphysics2/blob/master/Demos/Demos/SimpleSelfContainedDemo.cs </remarks>
public struct PoseIntegratorCallbacks : IPoseIntegratorCallbacks
{
public void Initialize(Simulation simulation) { }

public readonly AngularIntegrationMode AngularIntegrationMode => AngularIntegrationMode.Nonconserving;

public readonly bool AllowSubstepsForUnconstrainedBodies => false;

public readonly bool IntegrateVelocityForKinematics => false;

private readonly Vector3Wide _gravityWideDt;
public PoseIntegratorCallbacks(Vector3 gravity, float timeStepSeconds) : this()
{
_gravityWideDt = Vector3Wide.Broadcast(gravity * timeStepSeconds);
}

public void IntegrateVelocity(Vector<int> bodyIndices, Vector3Wide position, QuaternionWide orientation, BodyInertiaWide localInertia, Vector<int> integrationMask, int workerIndex, Vector<float> dt, ref BodyVelocityWide velocity)
{
velocity.Linear += _gravityWideDt;
}

public void PrepareForIntegration(float dt) { }
}
52 changes: 52 additions & 0 deletions Demos.GL/Broken/SimpleNarrowPhaseCallbacks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using BepuPhysics;
using BepuPhysics.Collidables;
using BepuPhysics.CollisionDetection;
using BepuPhysics.Constraints;

namespace Demos.Broken;

/// <summary>
/// An implementation of INarrowPhaseCallbacks (a BePu interface) needed to get simulation running with BePu.
/// </summary>
/// <remarks>Roughly a verbatim copy of the example given here: https://github.com/bepu/bepuphysics2/blob/master/Demos/Demos/SimpleSelfContainedDemo.cs</remarks>
public struct SimpleNarrowPhaseCallbacks : INarrowPhaseCallbacks
{
private readonly float _frictionCoefficient;
private readonly float _maximumRecoveryVelocity;
private readonly float _impactFrequency;
private readonly float _impactDampingRatio;

//TODO (Nigel): verify that these populate in the online docs without the summary tag

/// <param name="frictionCoefficient"> Coefficient of friction to apply for the constraint. Maximum friction force will be equal to the normal force times the friction coefficient.</param>
/// <param name="maximumRecoveryVelocity">Maximum relative velocity along the contact normal at which the collision constraint will recover from penetration. Clamps the velocity goal created from the spring settings.</param>
/// <param name="impactFrequency">Target number of undamped oscillations per unit of time.</param>
/// <param name="impactDampingRatio"> Ratio of the spring's actual damping to its critical damping. 0 is undamped, 1 is critically damped, and higher values are overdamped.</param>
public SimpleNarrowPhaseCallbacks(float frictionCoefficient, float maximumRecoveryVelocity, float impactFrequency, float impactDampingRatio)
{
_frictionCoefficient = frictionCoefficient;
_maximumRecoveryVelocity = maximumRecoveryVelocity;
_impactFrequency = impactFrequency;
_impactDampingRatio = impactDampingRatio;
}
public void Initialize(Simulation simulation) { }

public void Dispose() { }

public bool AllowContactGeneration(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB) => true;
public bool ConfigureContactManifold<TManifold>(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : unmanaged, IContactManifold<TManifold>
{
pairMaterial.FrictionCoefficient = _frictionCoefficient;
pairMaterial.MaximumRecoveryVelocity = _maximumRecoveryVelocity;
pairMaterial.SpringSettings = new SpringSettings(_impactFrequency, _impactDampingRatio);
//For the purposes of the demo, contact constraints are always generated.
return true;
}
public bool ConfigureContactManifold(int workerIndex, CollidablePair pair, int childIndexA, int childIndexB, ref ConvexContactManifold manifold) => true;

public bool AllowContactGeneration(int workerIndex, CollidableReference a, CollidableReference b, ref float speculativeMargin)
{

return a.Mobility == CollidableMobility.Dynamic || b.Mobility == CollidableMobility.Dynamic;
}
}
13 changes: 7 additions & 6 deletions Demos/Demo.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
using BepuUtilities.Memory;
using BepuPhysics;
using BepuUtilities;
using BepuUtilities.Memory;
using DemoContentLoader;
using DemoRenderer;
using DemoRenderer.UI;
using DemoUtilities;
using BepuPhysics;
using System;
using DemoRenderer.UI;
using DemoContentLoader;
using BepuUtilities;

namespace Demos;

Expand Down Expand Up @@ -56,13 +56,14 @@ public virtual void LoadGraphicalContent(ContentArchive content, RenderSurface s
public const float TimestepDuration = 1 / 60f;
public virtual void Update(Window window, Camera camera, Input input, float dt)
{
if (dt == 0) return;
//In the demos, we use one time step per frame. We don't bother modifying the physics time step duration for different monitors so different refresh rates
//change the rate of simulation. This doesn't actually change the result of the simulation, though, and the simplicity is a good fit for the demos.
//In the context of a 'real' application, you could instead use a time accumulator to take time steps of fixed length as needed, or
//fully decouple simulation and rendering rates across different threads.
//(In either case, you'd also want to interpolate or extrapolate simulation results during rendering for smoothness.)
//Note that taking steps of variable length can reduce stability. Gradual or one-off changes can work reasonably well.
Simulation.Timestep(TimestepDuration, ThreadDispatcher);
Simulation.Timestep(dt, ThreadDispatcher);

////Here's an example of how it would look to use more frequent updates, but still with a fixed amount of time simulated per update call:
//const float timeToSimulate = 1 / 60f;
Expand Down
3 changes: 2 additions & 1 deletion Demos/DemoSet.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using DemoContentLoader;
using DemoRenderer;
using Demos.Broken;
using Demos.Demos;
using Demos.Demos.Cars;
using Demos.Demos.Characters;
using Demos.Demos.Dancers;
using Demos.Demos.Sponsors;
using Demos.Demos.Tanks;
using Demos.SpecializedTests;
using System;
using System.Collections.Generic;

Expand Down Expand Up @@ -44,6 +44,7 @@ struct Option

public DemoSet()
{
AddOption<CompoundFallsThroughFloor>();
AddOption<CarDemo>();
AddOption<TankDemo>();
AddOption<CharacterDemo>();
Expand Down
16 changes: 11 additions & 5 deletions Demos/GameLoop.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using DemoRenderer;
using BepuUtilities;
using BepuUtilities.Memory;
using DemoRenderer;
using DemoUtilities;
using System;
using BepuUtilities;
using BepuUtilities.Memory;

namespace Demos;

Expand All @@ -29,10 +29,10 @@ public GameLoop(Window window)
window.Resolution, enableDeviceDebugLayer: false
);
Renderer = new Renderer(Surface);
Camera = new Camera(window.Resolution.X / (float)window.Resolution.Y, (float)Math.PI / 3, 0.01f, 100000);
Camera = new Camera(window.Resolution.X / (float)window.Resolution.Y, (float)Math.PI / 3, 0.01f, 100000);
}

void Update(float dt)
public void Update(float dt)
{
Input.Start();
if (DemoHarness != null)
Expand All @@ -53,6 +53,12 @@ public void Run(DemoHarness harness)
Window.Run(Update, OnResize);
}

public void SingleFrame(DemoHarness harness, float dt)
{
DemoHarness = harness;
Window.SingleFrame(Update, OnResize, dt);
}

private void OnResize(Int2 resolution)
{
//We just don't support true fullscreen in the demos. Would be pretty pointless.
Expand Down
52 changes: 52 additions & 0 deletions Demos/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,64 @@
using DemoContentLoader;
using DemoUtilities;
using OpenTK;
using System;
using System.Runtime.InteropServices;
using System.Threading;



namespace Demos;



class Program
{

[DllImport("user32.dll")]
static extern short GetAsyncKeyState(int key);

static bool IsKeyPressed(int key) => (GetAsyncKeyState(key) & 0x8000) != 0;




static void Main()
{
var window = new Window("pretty cool multicolored window",
new Int2((int)(DisplayDevice.Default.Width * 0.75f), (int)(DisplayDevice.Default.Height * 0.75f)), WindowMode.Windowed);
var loop = new GameLoop(window);
ContentArchive content;
using (var stream = typeof(Program).Assembly.GetManifestResourceStream("Demos.Demos.contentarchive"))
{
content = ContentArchive.Load(stream);
}
//HeadlessTest.Test<ShapePileTestDemo>(content, 4, 32, 512);
var demo = new DemoHarness(loop, content);
var dt = 0.12f;
DateTime? arrowDownTime = null;
var holdDownTimeMs = 500;
loop.SingleFrame(demo, 0);
while (true)
{
if (IsKeyPressed(0x1B)) break; // escape
if (!IsKeyPressed(0x27))
{
arrowDownTime = null;
Thread.Sleep(10);
continue;
}
if (arrowDownTime is null || (DateTime.Now - arrowDownTime.Value).TotalMilliseconds > holdDownTimeMs)
{
loop.SingleFrame(demo, dt);
Thread.Sleep(50);
}
}

loop.Dispose();
window.Dispose();
}

static void OldMain()
{
var window = new Window("pretty cool multicolored window",
new Int2((int)(DisplayDevice.Default.Width * 0.75f), (int)(DisplayDevice.Default.Height * 0.75f)), WindowMode.Windowed);
Expand Down