325 lines
9.5 KiB
C
325 lines
9.5 KiB
C
|
|
// SPDX-FileCopyrightText: 2025 Erin Catto
|
||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
|
||
|
|
#pragma once
|
||
|
|
|
||
|
|
#include "host/camera.h"
|
||
|
|
|
||
|
|
#include "box3d/types.h"
|
||
|
|
|
||
|
|
// Polled key state for samples that need continuous input (character movers).
|
||
|
|
// Fed from the host event loop; read with the KEY_* aliases in gfx/keycodes.h.
|
||
|
|
bool IsKeyDown( int key );
|
||
|
|
void SetKeyDown( int key, bool down );
|
||
|
|
|
||
|
|
// Key action passed to Keyboard, matching the old GLFW press/release values so
|
||
|
|
// sample code reads naturally without leaking sokol_app event types.
|
||
|
|
enum
|
||
|
|
{
|
||
|
|
ACTION_RELEASE = 0,
|
||
|
|
ACTION_PRESS = 1,
|
||
|
|
};
|
||
|
|
|
||
|
|
struct SampleContext
|
||
|
|
{
|
||
|
|
void Save();
|
||
|
|
void Load();
|
||
|
|
|
||
|
|
Camera camera;
|
||
|
|
bool minimized = false;
|
||
|
|
class Sample* sample = nullptr;
|
||
|
|
b3Capacity capacity;
|
||
|
|
|
||
|
|
// Latest cursor position in framebuffer pixels, fed from the host event
|
||
|
|
// loop. Drives picking on the step that follows a mouse move.
|
||
|
|
float mouseX = 0.0f;
|
||
|
|
float mouseY = 0.0f;
|
||
|
|
|
||
|
|
// Relative cursor motion for the latest move, fed from the host. Used for
|
||
|
|
// third-person look while the cursor is locked.
|
||
|
|
float mouseDX = 0.0f;
|
||
|
|
float mouseDY = 0.0f;
|
||
|
|
|
||
|
|
int windowWidth = 1920;
|
||
|
|
int windowHeight = 1080;
|
||
|
|
|
||
|
|
// Used to shift the origin and test round-off problems
|
||
|
|
b3Vec3 origin = b3Vec3_zero;
|
||
|
|
|
||
|
|
// Recording output path and the path the Replay viewer opens. Edited in the UI and persisted.
|
||
|
|
char recordingFile[256] = "recording.b3rec";
|
||
|
|
char replayFile[256] = "";
|
||
|
|
|
||
|
|
// Keyframe ring policy the Replay viewer applies on open, persisted across sessions.
|
||
|
|
int replayKeyframeBudgetMB = 512;
|
||
|
|
int replayKeyframeMinInterval = 16;
|
||
|
|
|
||
|
|
float hertz = 60.0f;
|
||
|
|
float recycleDistance = 0.05f;
|
||
|
|
float drawDistance = 100.0f; // meters, view/cull box half extent, persisted
|
||
|
|
int subStepCount = 4;
|
||
|
|
int workerCount = 1;
|
||
|
|
bool transparentDynamic = false;
|
||
|
|
bool transparent = false;
|
||
|
|
bool enableWarmStarting = true;
|
||
|
|
bool enableContinuous = true;
|
||
|
|
bool enableSleep = true;
|
||
|
|
bool pause = false;
|
||
|
|
int singleStep = 0;
|
||
|
|
bool restart = false;
|
||
|
|
|
||
|
|
// UI visibility (Tab / View > Hide UI). When hidden only the minimal HUD shows.
|
||
|
|
bool showUI = true;
|
||
|
|
|
||
|
|
// Bottom diagnostics drawer (M), set by Ctrl+O for the fuzzy picker.
|
||
|
|
bool showMetrics = false;
|
||
|
|
bool openSamplePicker = false;
|
||
|
|
|
||
|
|
// Controls help window (Help > Controls, toggled with ?). Promoted to the
|
||
|
|
// context so the key handler can reach it. Seeded from newUser after Load.
|
||
|
|
bool showControls = false;
|
||
|
|
|
||
|
|
// Deferred request from the Replay > Open menu. The native file dialog
|
||
|
|
// blocks on a nested run loop, so it must run outside the frame (top of
|
||
|
|
// OnFrame) rather than mid-frame inside the ImGui callback, which would
|
||
|
|
// re-enter the render loop. See OpenReplayFileDialog.
|
||
|
|
bool openReplayPicker = false;
|
||
|
|
|
||
|
|
// Render toggles fed into FrameInput each frame. They live here, not in
|
||
|
|
// main.cpp, so the Render menu and the frame loop share one source.
|
||
|
|
bool enableShadows = true;
|
||
|
|
bool enableGtao = true;
|
||
|
|
|
||
|
|
// View-only: treat simulation +Z as up when drawing. Does not touch the
|
||
|
|
// simulation. The frame loop hands this to the camera's render transform.
|
||
|
|
bool viewZUp = false;
|
||
|
|
|
||
|
|
float sunStrength = 1.0f;
|
||
|
|
int debugView = 0;
|
||
|
|
|
||
|
|
int sampleIndex = 0;
|
||
|
|
|
||
|
|
// No settings file found yet. The first run opens the replay viewer with the
|
||
|
|
// controls window up, so there is something to watch and a key to every action.
|
||
|
|
bool newUser = true;
|
||
|
|
};
|
||
|
|
|
||
|
|
class Sample
|
||
|
|
{
|
||
|
|
public:
|
||
|
|
explicit Sample( SampleContext* context );
|
||
|
|
virtual ~Sample();
|
||
|
|
|
||
|
|
void CreateWorld( b3Capacity* capacity );
|
||
|
|
|
||
|
|
// Position the first HUD text line below the menu bar, or near the top when
|
||
|
|
// the UI is hidden. Mirrors Box2D ResetText.
|
||
|
|
void ResetText();
|
||
|
|
|
||
|
|
// Update and render are split to support pausing the simulation
|
||
|
|
virtual void Step();
|
||
|
|
|
||
|
|
virtual void Render() {}
|
||
|
|
|
||
|
|
// Draw sample controls into the shared info panel. Return true if any widget
|
||
|
|
// was drawn so the panel can add a separator.
|
||
|
|
virtual bool DrawControls()
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Let a debug sample hide the Solver section.
|
||
|
|
virtual bool HasSolverControls() const
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Width of the right info panel in em. The bottom drawer clears to the same width. A sample that
|
||
|
|
// hosts heavier content there, such as the replay viewer's detail pane, can widen it.
|
||
|
|
virtual float InfoPanelWidthEm() const;
|
||
|
|
|
||
|
|
// Bounds the Frame shortcut should fit when the selection is not a body, e.g. a recorded query.
|
||
|
|
// Takes priority over FocusBody so an explicit selection wins over a hovered body. Returns false
|
||
|
|
// by default, leaving the body and whole-scene paths in charge.
|
||
|
|
virtual bool FocusBounds( b3AABB* bounds );
|
||
|
|
|
||
|
|
// Body the Frame shortcut should fit. Defaults to whatever the cursor is over.
|
||
|
|
// The replay viewer overrides this to fit its persistent selection instead, so
|
||
|
|
// framing works regardless of where the cursor sits.
|
||
|
|
virtual b3BodyId FocusBody() const;
|
||
|
|
|
||
|
|
// Frame shortcut with nothing selected: fit the whole scene. Defaults to the live world bounds.
|
||
|
|
// The replay viewer overrides this to fit the recording, whose world is player-owned and separate
|
||
|
|
// from the empty base world.
|
||
|
|
virtual void FocusHome();
|
||
|
|
|
||
|
|
// Arm recording on the live world, snapshotting it as the seed so capture can begin at any
|
||
|
|
// step boundary. Stop writes the file named by the context and frees the buffer.
|
||
|
|
void StartRecording();
|
||
|
|
void FinishRecording();
|
||
|
|
|
||
|
|
// Bottom diagnostics drawer (Profile / Counters / Renderer / Frame Time).
|
||
|
|
void DrawMetrics();
|
||
|
|
|
||
|
|
// Append extra tabs to the diagnostics drawer's tab bar (the Replay viewer adds Timeline).
|
||
|
|
virtual void DrawMetricsTab()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
// Draw extra top-level windows when the UI is shown (the Replay viewer adds the Outline panel).
|
||
|
|
virtual void DrawSampleWindows()
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void Keyboard( int key, int action, int modifiers )
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
virtual void MouseDown( b3Vec2 p, int button, int modifiers );
|
||
|
|
virtual void MouseUp( b3Vec2 p, int button );
|
||
|
|
virtual void MouseMove( b3Vec2 p );
|
||
|
|
|
||
|
|
void ToggleThirdPerson();
|
||
|
|
void DrawTextLine( const char* text, ... );
|
||
|
|
void ResetProfile();
|
||
|
|
|
||
|
|
// Static ground box at the origin, drawn with the procedural grid material
|
||
|
|
b3BodyId AddGroundBox( float extent );
|
||
|
|
|
||
|
|
#if defined( NDEBUG )
|
||
|
|
static constexpr bool m_isDebug = false;
|
||
|
|
#else
|
||
|
|
static constexpr bool m_isDebug = true;
|
||
|
|
#endif
|
||
|
|
|
||
|
|
static constexpr int m_maxTasks = 64;
|
||
|
|
static constexpr int m_maxThreads = 64;
|
||
|
|
static constexpr int m_profileCapacity = 512;
|
||
|
|
|
||
|
|
SampleContext* m_context;
|
||
|
|
Camera* m_camera;
|
||
|
|
|
||
|
|
b3WorldId m_worldId;
|
||
|
|
|
||
|
|
b3Pos m_mousePoint;
|
||
|
|
b3BodyId m_mouseBodyId;
|
||
|
|
b3JointId m_mouseJointId;
|
||
|
|
float m_mouseFraction;
|
||
|
|
float m_mouseForceScale;
|
||
|
|
float m_launchSpeedScale;
|
||
|
|
int m_stepCount;
|
||
|
|
int m_textLine;
|
||
|
|
int m_textIncrement;
|
||
|
|
int m_triangleIndex;
|
||
|
|
uint64_t m_userMaterialId;
|
||
|
|
|
||
|
|
// Active recording, or null when not recording. The step recording began on drives the status.
|
||
|
|
struct b3Recording* m_recording;
|
||
|
|
int m_recordStartStep;
|
||
|
|
|
||
|
|
b3Profile m_profiles[m_profileCapacity];
|
||
|
|
int m_currentProfileIndex;
|
||
|
|
int m_profileReadIndex;
|
||
|
|
int m_profileWriteIndex;
|
||
|
|
|
||
|
|
b3Vec2 m_mouseLast;
|
||
|
|
b3Vec2 m_mouseDelta;
|
||
|
|
bool m_didStep;
|
||
|
|
bool m_stepWhilePaused;
|
||
|
|
bool m_haveMouseLast;
|
||
|
|
};
|
||
|
|
|
||
|
|
#define MAX_SAMPLES 256
|
||
|
|
|
||
|
|
typedef Sample* SampleCreateFcn( SampleContext* context );
|
||
|
|
|
||
|
|
struct SampleEntry
|
||
|
|
{
|
||
|
|
const char* Category;
|
||
|
|
const char* Name;
|
||
|
|
SampleCreateFcn* CreateFcn;
|
||
|
|
};
|
||
|
|
|
||
|
|
extern SampleEntry g_sampleEntries[MAX_SAMPLES];
|
||
|
|
extern int g_sampleCount;
|
||
|
|
|
||
|
|
// Index of the registered Replay viewer, or -1 if none. Gates the Replay menu.
|
||
|
|
extern int g_replayIndex;
|
||
|
|
|
||
|
|
int RegisterSample( const char* category, const char* name, SampleCreateFcn* fcn );
|
||
|
|
|
||
|
|
// Register the Replay viewer and record its index in g_replayIndex.
|
||
|
|
int RegisterReplay( const char* category, const char* name, SampleCreateFcn* fcn );
|
||
|
|
|
||
|
|
// Destroy the active sample and build the selected one. restart keeps the camera
|
||
|
|
// by leaving the restart flag set while the new sample constructs.
|
||
|
|
void SelectSample( SampleContext* context, int selection, bool restart );
|
||
|
|
|
||
|
|
// Run the native "open replay" file picker and, on success, hand the chosen
|
||
|
|
// file to the Replay viewer. Must be called outside the frame (the dialog spins
|
||
|
|
// a blocking nested run loop), driven by SampleContext::openReplayPicker.
|
||
|
|
void OpenReplayFileDialog( SampleContext* context );
|
||
|
|
|
||
|
|
// The single host UI callback: menu bar, sample picker, info panel, and the
|
||
|
|
// bottom diagnostics drawer.
|
||
|
|
void DrawUI( SampleContext* context );
|
||
|
|
|
||
|
|
struct CastClosestContext
|
||
|
|
{
|
||
|
|
b3ShapeId shapeId;
|
||
|
|
b3Pos point;
|
||
|
|
b3Vec3 normal;
|
||
|
|
float fraction;
|
||
|
|
uint64_t materialId;
|
||
|
|
int childIndex;
|
||
|
|
int triangleIndex;
|
||
|
|
bool hit;
|
||
|
|
};
|
||
|
|
|
||
|
|
float CastClosestCallback( b3ShapeId shapeId, b3Pos point, b3Vec3 normal, float fraction, uint64_t materialId, int triangleIndex,
|
||
|
|
int childIndex, void* context );
|
||
|
|
|
||
|
|
struct MoverShapeUserData
|
||
|
|
{
|
||
|
|
float maxPush;
|
||
|
|
bool clipVelocity;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct PlaneExtra
|
||
|
|
{
|
||
|
|
b3Pos point;
|
||
|
|
b3ShapeId shapeId;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct CharacterMover
|
||
|
|
{
|
||
|
|
static constexpr int m_planeCapacity = 8;
|
||
|
|
static constexpr float m_jumpSpeed = 5.0f;
|
||
|
|
static constexpr float m_maxSpeed = 6.0f;
|
||
|
|
static constexpr float m_minSpeed = 0.01f;
|
||
|
|
static constexpr float m_stopSpeed = 1.0f;
|
||
|
|
static constexpr float m_accelerate = 30.0f;
|
||
|
|
static constexpr float m_friction = 4.0f;
|
||
|
|
static constexpr float m_gravity = 15.0f;
|
||
|
|
|
||
|
|
void Initialize( Sample* sample, b3Pos position );
|
||
|
|
void SolveMove( float timeStep, b3Vec3 forward, b3Vec3 right, b3Vec2 throttle, bool clipVelocity );
|
||
|
|
void Step( b3ShapeId* ignoreShapes, int ignoreCount, bool clipVelocity );
|
||
|
|
|
||
|
|
Sample* m_sample;
|
||
|
|
b3WorldTransform m_transform;
|
||
|
|
b3Vec3 m_velocity;
|
||
|
|
b3Capsule m_capsule;
|
||
|
|
b3CollisionPlane m_planes[m_planeCapacity] = {};
|
||
|
|
PlaneExtra m_planeExtras[m_planeCapacity] = {};
|
||
|
|
int m_planeCount;
|
||
|
|
int m_totalIterations;
|
||
|
|
float m_pogoVelocity;
|
||
|
|
bool m_onGround;
|
||
|
|
bool m_sprint;
|
||
|
|
|
||
|
|
// Transient
|
||
|
|
b3ShapeId* m_ignoreShapeIds;
|
||
|
|
int m_ignoreCount;
|
||
|
|
};
|