1105 lines
49 KiB
C++
1105 lines
49 KiB
C++
#include "Engine.h"
|
|
|
|
// ============================================================================
|
|
// Standard library
|
|
// ============================================================================
|
|
#include <iostream>
|
|
#include <vector>
|
|
#include <random>
|
|
#include <string>
|
|
#include <memory>
|
|
#include <type_traits>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
// ============================================================================
|
|
// OpenGL
|
|
// ============================================================================
|
|
#include <glad/glad.h>
|
|
#include <GLFW/glfw3.h>
|
|
|
|
// ============================================================================
|
|
// Math
|
|
// ============================================================================
|
|
#include <glm/glm.hpp>
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
#include <glm/gtc/quaternion.hpp>
|
|
|
|
// ============================================================================
|
|
// Third party
|
|
// ============================================================================
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
|
|
// ============================================================================
|
|
// Engine core
|
|
// ============================================================================
|
|
#include "common.h"
|
|
#include "Window.h"
|
|
#include "Camera.h"
|
|
#include "Keyboard.h"
|
|
#include "Mouse.h"
|
|
#include "Physics.h"
|
|
#include "RenderSystem.h"
|
|
#include "SoundSystem.h"
|
|
#include "ResourceManager.h"
|
|
#include "tFunctions.h"
|
|
#include "Debug.h"
|
|
|
|
// Rendering
|
|
#include "shaderClass.h"
|
|
#include "VBO.h"
|
|
#include "VAO.h"
|
|
#include "EBO.h"
|
|
#include "FBO.h"
|
|
#include "texture.h"
|
|
#include "Material.h"
|
|
#include "Mesh.h"
|
|
#include "Model.h"
|
|
#include "Cubemap.h"
|
|
#include "QuadVertices.h"
|
|
#include "Particle.h"
|
|
#include "Lighting.h"
|
|
#include "DirLight.h"
|
|
|
|
// Scene
|
|
#include "Folder.h"
|
|
#include "Sound.h"
|
|
#include "Gui.h"
|
|
#include "font.h"
|
|
#include "TexturePainter.h"
|
|
|
|
// Character / animation
|
|
#include "Rig.h"
|
|
#include "Animator.h"
|
|
#include "Character.h"
|
|
#include "CharacterManager.h"
|
|
#include "Ragdoll.h"
|
|
#include "DefaultBodyComponents.h"
|
|
#include "BodyComponentActions.h"
|
|
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#include "stb_image.h"
|
|
|
|
// ============================================================================
|
|
// Constructor - window, callbacks, systems, shaders
|
|
// ============================================================================
|
|
Engine::Engine()
|
|
{
|
|
engine = this;
|
|
|
|
glfwSetFramebufferSizeCallback(window->handle, framebuffer_size_callback);
|
|
glfwSetCursorPosCallback(window->handle, mouse_callback);
|
|
glfwSetScrollCallback(window->handle, scroll_callback);
|
|
glfwSetKeyCallback(window->handle, key_callback);
|
|
glfwSetMouseButtonCallback(window->handle, mouse_button_callback);
|
|
glfwSwapInterval(1);
|
|
|
|
int fbw, fbh;
|
|
glfwGetFramebufferSize(window->handle, &fbw, &fbh);
|
|
framebuffer_size_callback(window->handle, fbw, fbh);
|
|
|
|
rendersystem = new RenderSystem();
|
|
rendersystem->Initialize();
|
|
physicsengine = new Physics();
|
|
physicsengine->SetGravity(physicsengine->GetGravity() * 1.25f);
|
|
mainf = new Folder();
|
|
camera = new Camera();
|
|
camera->t.TranslateTo({ 0.0f, 5.0f, 0.0f });
|
|
|
|
rendersystem->RigShader = new Shader("shaders/rig.vert", "shaders/default.frag");
|
|
rendersystem->MeshShader = new Shader("shaders/default.vert", "shaders/default.frag");
|
|
rendersystem->GeometryShader = new Shader("shaders/geometry.vert", "shaders/geometry.frag");
|
|
rendersystem->GeometryRigShader = new Shader("shaders/geometry_rig.vert", "shaders/geometry.frag");
|
|
rendersystem->DeferredLightingShader = new Shader("shaders/deferred_lighting.vert", "shaders/deferred_lighting.frag");
|
|
rendersystem->GBufferDebugShader = new Shader("shaders/gbuffer_debug.vert", "shaders/gbuffer_debug.frag");
|
|
rendersystem->BloomExtractShader = new Shader("shaders/ScreenShader.vert", "shaders/bloom_extract.frag");
|
|
rendersystem->BloomBlurShader = new Shader("shaders/ScreenShader.vert", "shaders/bloom_blur.frag");
|
|
rendersystem->MeshShadowShader = new Shader("shaders/meshshadow.vert", "shaders/shadow.frag");
|
|
rendersystem->RigShadowShader = new Shader("shaders/rigshadow.vert", "shaders/shadow.frag");
|
|
rendersystem->SkyboxShader = new Shader("shaders/skybox.vert", "shaders/skybox.frag");
|
|
rendersystem->ScreenShader = new Shader("shaders/ScreenShader.vert", "shaders/ScreenShader.frag");
|
|
rendersystem->ImageBoxShader = new Shader("shaders/ImageBox.vert", "shaders/ImageBox.frag");
|
|
rendersystem->BoxShader = new Shader("shaders/Box.vert", "shaders/Box.frag");
|
|
rendersystem->Text2DShader = new Shader("shaders/2DText.vert", "shaders/2DText.frag");
|
|
rendersystem->ParticleShader = new Shader("shaders/Particle.vert", "shaders/Particle.frag");
|
|
rendersystem->HitboxShader = new Shader("shaders/Hitbox.vert", "shaders/Hitbox.frag");
|
|
}
|
|
|
|
// ============================================================================
|
|
// Initiate - scene setup + game loop
|
|
// ============================================================================
|
|
void Engine::Initiate()
|
|
{
|
|
Font* arial = resourcemanager->GetFont("assets/ft/arial.ttf");
|
|
Texture* blanktex = resourcemanager->GetTexture("assets/blanktexture.png");
|
|
|
|
charactermanager = new CharacterManager();
|
|
charactermanager->LoadRig("humanoid", "assets/rig.glb");
|
|
charactermanager->LoadAnimation("walk", "assets/walkanim.glb", "humanoid");
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Player character
|
|
// ------------------------------------------------------------------------
|
|
Character& testcharacter = *charactermanager->CreateCharacter("character", "humanoid", "walk");
|
|
charactermanager->SetControlledCharacter("character");
|
|
testcharacter.castshadow = true;
|
|
testcharacter.colorOverride = glm::vec4(0.62f, 0.64f, 0.68f, 1.0f);
|
|
testcharacter.roughness = 0.65f;
|
|
testcharacter.body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
|
testcharacter.body->t.TranslateTo({ 0.0f, 1.5f, 0.0f });
|
|
testcharacter.body->animator->loops = true;
|
|
testcharacter.BindToRenderSystem();
|
|
|
|
p* characterBody = new p(testcharacter.body);
|
|
characterBody->mass = 80.0f;
|
|
characterBody->lockRotation = true;
|
|
characterBody->friction = 0.0f;
|
|
physicsengine->AddObject(characterBody);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// NPC
|
|
// ------------------------------------------------------------------------
|
|
Character& npc = *charactermanager->CreateCharacter("npc", "humanoid", "walk");
|
|
npc.castshadow = true;
|
|
npc.colorOverride = glm::vec4(0.10f, 0.20f, 0.48f, 1.0f);
|
|
npc.metallic = 0.1f;
|
|
npc.roughness = 0.5f;
|
|
npc.body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
|
npc.body->t.TranslateTo({ 3.0f, 1.25f, 3.0f });
|
|
npc.BindToRenderSystem();
|
|
|
|
testcharacter.SetWalking(false);
|
|
npc.SetWalking(false);
|
|
npc.EnableRagdoll(*physicsengine);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// 3D audio
|
|
// ------------------------------------------------------------------------
|
|
Sound* music = new Sound();
|
|
music->name = "music";
|
|
music->LoadSound("assets/fs.wav", true);
|
|
music->Update3DPosition(0.0f, 0.0f, 0.0f);
|
|
music->PlayTrack(true);
|
|
mainf->AddChild(music);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// GUI
|
|
// ------------------------------------------------------------------------
|
|
TextBox* fpsCounter = new TextBox();
|
|
fpsCounter->font = arial;
|
|
fpsCounter->text = "";
|
|
fpsCounter->fontsize = 0.5f;
|
|
fpsCounter->Color = { 0.0f, 0.0f, 0.0f };
|
|
fpsCounter->t2d.position = { 1.0f, 0.0f, 0.0f, 0.0f };
|
|
fpsCounter->t2d.center = { 1.0f, 0.0f };
|
|
fpsCounter->textCenter = { 0.0f, 0.5f };
|
|
fpsCounter->t2d.size = { 0.1f, 0.1f, 0.0f, 0.0f };
|
|
fpsCounter->BindToRenderSystem();
|
|
|
|
struct MenuButton {
|
|
Box* box;
|
|
Box* accent;
|
|
TextBox* label;
|
|
glm::vec3 color;
|
|
glm::vec3 hoverColor;
|
|
float opacity;
|
|
float labelX;
|
|
float labelSlide; // how far the label moves right on hover
|
|
bool hovered = false;
|
|
float hover = 0.0f; // eased 0-1
|
|
};
|
|
|
|
enum class MenuScreen { Main, Paused, Settings, Graphics, Drawer };
|
|
MenuScreen menuScreen = MenuScreen::Main;
|
|
MenuScreen settingsReturn = MenuScreen::Main;
|
|
bool menuActive = true;
|
|
bool prevMouseDown = false;
|
|
std::vector<Box*> menuItems;
|
|
|
|
// Everything hangs off a left side panel so the live scene stays visible.
|
|
// Items are positioned in pixels from the left edge and the vertical center of the window.
|
|
const float menuPanelWidth = 460.0f;
|
|
const float menuMargin = 48.0f;
|
|
const float menuContentWidth = 364.0f;
|
|
const glm::vec3 menuAccent(0.96f, 0.58f, 0.16f);
|
|
const glm::vec3 menuText(0.95f, 0.97f, 1.0f);
|
|
const glm::vec3 menuMutedText(0.62f, 0.68f, 0.77f);
|
|
const glm::vec3 menuButtonColor(0.11f, 0.14f, 0.19f);
|
|
const glm::vec3 menuButtonHover(0.20f, 0.25f, 0.33f);
|
|
|
|
Box* menuVeil = new Box();
|
|
menuVeil->Color = { 0.02f, 0.03f, 0.05f };
|
|
menuVeil->t2d.position = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
menuVeil->t2d.size = { 1.0f, 1.0f, 0.0f, 0.0f };
|
|
menuVeil->BindToRenderSystem();
|
|
menuItems.push_back(menuVeil);
|
|
|
|
Box* menuPanel = new Box();
|
|
menuPanel->Color = { 0.03f, 0.04f, 0.06f };
|
|
menuPanel->t2d.position = { 0.0f, 0.0f, 0.0f, 0.0f };
|
|
menuPanel->t2d.size = { 0.0f, 1.0f, menuPanelWidth, 0.0f };
|
|
menuPanel->BindToRenderSystem();
|
|
menuItems.push_back(menuPanel);
|
|
|
|
Box* menuPanelEdge = new Box();
|
|
menuPanelEdge->Color = menuAccent;
|
|
menuPanelEdge->t2d.position = { 0.0f, 0.0f, menuPanelWidth, 0.0f };
|
|
menuPanelEdge->t2d.size = { 0.0f, 1.0f, 3.0f, 0.0f };
|
|
menuPanelEdge->BindToRenderSystem();
|
|
menuItems.push_back(menuPanelEdge);
|
|
|
|
auto MakeMenuText = [&](const char* text, float x, float y, float width, float height,
|
|
float fontsize, glm::vec3 color, float alignX = 0.0f) {
|
|
TextBox* label = new TextBox();
|
|
label->font = arial;
|
|
label->text = text;
|
|
label->fontsize = fontsize;
|
|
label->Color = color;
|
|
label->t2d.position = { 0.0f, 0.5f, x, y };
|
|
label->t2d.center = { 0.0f, 0.5f };
|
|
label->t2d.size = { 0.0f, 0.0f, width, height };
|
|
label->textCenter = { alignX, 0.5f };
|
|
label->BindToRenderSystem();
|
|
menuItems.push_back(label);
|
|
return label;
|
|
};
|
|
|
|
TextBox* menuTitle = MakeMenuText("EIIFWHRN", menuMargin, -235.0f, 400.0f, 100.0f, 1.6f, menuText);
|
|
TextBox* menuSubtitle = MakeMenuText("MAIN MENU", menuMargin, -165.0f, menuContentWidth, 36.0f, 0.5f, menuAccent);
|
|
|
|
auto MakeMenuButton = [&](const char* text, float x, float y, float width, float height,
|
|
float fontsize, glm::vec3 color, glm::vec3 hoverColor,
|
|
float opacity = 0.6f, bool compact = false) {
|
|
Box* box = new Box();
|
|
box->Color = color;
|
|
box->t2d.position = { 0.0f, 0.5f, x, y };
|
|
box->t2d.center = { 0.0f, 0.5f };
|
|
box->t2d.size = { 0.0f, 0.0f, width, height };
|
|
box->rounding = 0.15f;
|
|
box->BindToRenderSystem();
|
|
menuItems.push_back(box);
|
|
|
|
Box* accent = new Box();
|
|
accent->Color = menuAccent;
|
|
accent->t2d.position = { 0.0f, 0.5f, x, y };
|
|
accent->t2d.center = { 0.0f, 0.5f };
|
|
accent->t2d.size = { 0.0f, 0.0f, 0.0f, height };
|
|
accent->BindToRenderSystem();
|
|
menuItems.push_back(accent);
|
|
|
|
// Compact buttons hold a single centered symbol, the rest are left aligned and slide on hover
|
|
const float padding = compact ? 0.0f : 20.0f;
|
|
TextBox* label = MakeMenuText(text, x + padding, y, width - padding * 2.0f, height,
|
|
fontsize, menuText, compact ? 0.5f : 0.0f);
|
|
|
|
return MenuButton{ box, accent, label, color, hoverColor, opacity,
|
|
x + padding, compact ? 0.0f : 8.0f };
|
|
};
|
|
|
|
const float mainButtonHeight = 60.0f;
|
|
MenuButton playButton = MakeMenuButton("PLAY", menuMargin, -60.0f, menuContentWidth, mainButtonHeight, 0.75f,
|
|
glm::vec3(0.82f, 0.45f, 0.10f), menuAccent, 0.95f);
|
|
MenuButton drawerButton = MakeMenuButton("TEXTURE DRAWER", menuMargin, 12.0f, menuContentWidth, mainButtonHeight, 0.75f,
|
|
menuButtonColor, menuButtonHover);
|
|
MenuButton settingsButton = MakeMenuButton("SETTINGS", menuMargin, 84.0f, menuContentWidth, mainButtonHeight, 0.75f,
|
|
menuButtonColor, menuButtonHover);
|
|
MenuButton quitButton = MakeMenuButton("QUIT", menuMargin, 156.0f, menuContentWidth, mainButtonHeight, 0.75f,
|
|
menuButtonColor, glm::vec3(0.42f, 0.14f, 0.16f));
|
|
|
|
// Settings rows: LABEL [-] VALUE [+]
|
|
const float stepperDownX = menuMargin + 176.0f;
|
|
const float stepperValueX = stepperDownX + 44.0f;
|
|
const float stepperUpX = menuMargin + menuContentWidth - 44.0f;
|
|
const float stepperValueWidth = stepperUpX - stepperValueX;
|
|
const float sensRowY = -120.0f;
|
|
const float fovRowY = -64.0f;
|
|
|
|
TextBox* sensLabel = MakeMenuText("SENSITIVITY", menuMargin, sensRowY, 172.0f, 44.0f, 0.5f, menuMutedText);
|
|
TextBox* sensValue = MakeMenuText("", stepperValueX, sensRowY, stepperValueWidth, 44.0f, 0.5f, menuText, 0.5f);
|
|
TextBox* fovLabel = MakeMenuText("FIELD OF VIEW", menuMargin, fovRowY, 172.0f, 44.0f, 0.5f, menuMutedText);
|
|
TextBox* fovValue = MakeMenuText("", stepperValueX, fovRowY, stepperValueWidth, 44.0f, 0.5f, menuText, 0.5f);
|
|
|
|
MenuButton sensitivityDown = MakeMenuButton("-", stepperDownX, sensRowY, 44.0f, 44.0f, 0.7f,
|
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
|
MenuButton sensitivityUp = MakeMenuButton("+", stepperUpX, sensRowY, 44.0f, 44.0f, 0.7f,
|
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
|
MenuButton fovDown = MakeMenuButton("-", stepperDownX, fovRowY, 44.0f, 44.0f, 0.7f,
|
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
|
MenuButton fovUp = MakeMenuButton("+", stepperUpX, fovRowY, 44.0f, 44.0f, 0.7f,
|
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
|
MenuButton hitboxToggle = MakeMenuButton("HITBOXES: OFF", menuMargin, -18.0f, menuContentWidth, 40.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover);
|
|
MenuButton graphicsButton = MakeMenuButton("GRAPHICS", menuMargin, 26.0f, menuContentWidth, 40.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover);
|
|
MenuButton resetButton = MakeMenuButton("RESET DEFAULTS", menuMargin, 70.0f, menuContentWidth, 40.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover);
|
|
|
|
MenuButton bloomToggle = MakeMenuButton("", menuMargin, -128.0f, menuContentWidth, 44.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover);
|
|
MenuButton shadowToggle = MakeMenuButton("", menuMargin, -78.0f, menuContentWidth, 44.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover);
|
|
MenuButton lightShadowToggle = MakeMenuButton("", menuMargin, -28.0f, menuContentWidth, 44.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover);
|
|
MenuButton vsyncToggle = MakeMenuButton("", menuMargin, 22.0f, menuContentWidth, 44.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover);
|
|
MenuButton cullingToggle = MakeMenuButton("", menuMargin, 72.0f, menuContentWidth, 44.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover);
|
|
MenuButton backButton = MakeMenuButton("BACK", menuMargin, 120.0f, menuContentWidth, 50.0f, 0.6f,
|
|
glm::vec3(0.82f, 0.45f, 0.10f), menuAccent, 0.95f);
|
|
|
|
// Texture drawer. Filled in further down, once the things it paints on exist.
|
|
struct DrawableSurface {
|
|
const char* name;
|
|
SurfaceTexture* surface;
|
|
glm::vec4* colorOverride;
|
|
bool hasCanvas = false;
|
|
};
|
|
std::vector<DrawableSurface> drawableSurfaces;
|
|
int selectedSurface = 0;
|
|
int selectedSwatch = 0;
|
|
TexturePainter painter;
|
|
bool painting = false;
|
|
|
|
// The first edit swaps in a private copy, so painting never touches the shared image file.
|
|
// A colour override would hide the paint, so that goes too.
|
|
auto EnsureCanvas = [&](DrawableSurface& drawable) {
|
|
if (drawable.hasCanvas)
|
|
return;
|
|
|
|
SurfaceTexture& surface = *drawable.surface;
|
|
surface.texture = surface.texture ? resourcemanager->CopyTexture(*surface.texture)
|
|
: resourcemanager->CreateTexture(256, 256);
|
|
drawable.colorOverride->w = 0.0f;
|
|
drawable.hasCanvas = true;
|
|
};
|
|
|
|
const float surfaceRowY = -120.0f;
|
|
const float swatchRowY = -64.0f;
|
|
const float brushRowY = -8.0f;
|
|
|
|
MenuButton surfacePrevious = MakeMenuButton("<", menuMargin, surfaceRowY, 44.0f, 44.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
|
MenuButton surfaceNext = MakeMenuButton(">", stepperUpX, surfaceRowY, 44.0f, 44.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
|
TextBox* surfaceName = MakeMenuText("", menuMargin + 44.0f, surfaceRowY, menuContentWidth - 88.0f, 44.0f, 0.5f, menuText, 0.5f);
|
|
|
|
const glm::vec3 swatchColors[] = {
|
|
{ 0.0f, 0.0f, 0.0f }, { 1.0f, 1.0f, 1.0f }, { 0.90f, 0.15f, 0.15f },
|
|
{ 0.96f, 0.58f, 0.16f }, { 0.20f, 0.75f, 0.30f }, { 0.20f, 0.45f, 0.95f }
|
|
};
|
|
const float swatchSize = 52.0f;
|
|
const float swatchSpacing = 60.0f;
|
|
|
|
std::vector<MenuButton> swatches;
|
|
swatches.reserve(std::size(swatchColors));
|
|
for (int i = 0; i < (int)std::size(swatchColors); i++) {
|
|
swatches.push_back(MakeMenuButton("", menuMargin + swatchSpacing * i, swatchRowY, swatchSize, 44.0f, 0.5f,
|
|
swatchColors[i], glm::mix(swatchColors[i], glm::vec3(1.0f), 0.2f), 1.0f, true));
|
|
}
|
|
|
|
Box* swatchMarker = new Box();
|
|
swatchMarker->Color = menuAccent;
|
|
swatchMarker->t2d.center = { 0.0f, 0.5f };
|
|
swatchMarker->t2d.size = { 0.0f, 0.0f, swatchSize, 4.0f };
|
|
swatchMarker->BindToRenderSystem();
|
|
menuItems.push_back(swatchMarker);
|
|
|
|
TextBox* brushLabel = MakeMenuText("BRUSH SIZE", menuMargin, brushRowY, 172.0f, 44.0f, 0.5f, menuMutedText);
|
|
TextBox* brushValue = MakeMenuText("", stepperValueX, brushRowY, stepperValueWidth, 44.0f, 0.5f, menuText, 0.5f);
|
|
MenuButton brushDown = MakeMenuButton("-", stepperDownX, brushRowY, 44.0f, 44.0f, 0.7f,
|
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
|
MenuButton brushUp = MakeMenuButton("+", stepperUpX, brushRowY, 44.0f, 44.0f, 0.7f,
|
|
menuButtonColor, menuButtonHover, 0.6f, true);
|
|
MenuButton clearButton = MakeMenuButton("CLEAR", menuMargin, 52.0f, menuContentWidth, 50.0f, 0.6f,
|
|
menuButtonColor, menuButtonHover);
|
|
|
|
// The canvas sits in the middle of the space right of the panel
|
|
const float canvasSize = 640.0f;
|
|
|
|
Box* canvasBorder = new Box();
|
|
canvasBorder->Color = menuAccent;
|
|
canvasBorder->t2d.position = { 0.5f, 0.5f, menuPanelWidth * 0.5f, 0.0f };
|
|
canvasBorder->t2d.center = { 0.5f, 0.5f };
|
|
canvasBorder->t2d.size = { 0.0f, 0.0f, canvasSize + 8.0f, canvasSize + 8.0f };
|
|
canvasBorder->BindToRenderSystem();
|
|
menuItems.push_back(canvasBorder);
|
|
|
|
Box* canvasBlank = new Box(); // shows through until the surface has a texture
|
|
canvasBlank->t2d.position = canvasBorder->t2d.position;
|
|
canvasBlank->t2d.center = { 0.5f, 0.5f };
|
|
canvasBlank->t2d.size = { 0.0f, 0.0f, canvasSize, canvasSize };
|
|
canvasBlank->BindToRenderSystem();
|
|
menuItems.push_back(canvasBlank);
|
|
|
|
ImageBox* canvasImage = new ImageBox();
|
|
canvasImage->t2d.position = canvasBorder->t2d.position;
|
|
canvasImage->t2d.center = { 0.5f, 0.5f };
|
|
canvasImage->t2d.size = { 0.0f, 0.0f, canvasSize, canvasSize };
|
|
canvasImage->BindToRenderSystem();
|
|
menuItems.push_back(canvasImage);
|
|
|
|
std::vector<MenuButton*> menuButtons = {
|
|
&playButton, &drawerButton, &settingsButton, &quitButton,
|
|
&sensitivityDown, &sensitivityUp, &fovDown, &fovUp,
|
|
&hitboxToggle, &graphicsButton, &resetButton, &backButton,
|
|
&bloomToggle, &shadowToggle, &lightShadowToggle, &vsyncToggle, &cullingToggle,
|
|
&surfacePrevious, &surfaceNext, &brushDown, &brushUp, &clearButton
|
|
};
|
|
for (MenuButton& swatch : swatches)
|
|
menuButtons.push_back(&swatch);
|
|
|
|
TextBox* menuHint = new TextBox();
|
|
menuHint->font = arial;
|
|
menuHint->fontsize = 0.42f;
|
|
menuHint->Color = menuMutedText;
|
|
menuHint->t2d.position = { 0.0f, 1.0f, menuMargin, -28.0f };
|
|
menuHint->t2d.center = { 0.0f, 1.0f };
|
|
menuHint->t2d.size = { 0.0f, 0.0f, menuContentWidth, 110.0f };
|
|
menuHint->textCenter = { 0.0f, 1.0f };
|
|
menuHint->BindToRenderSystem();
|
|
menuItems.push_back(menuHint);
|
|
|
|
auto SetButtonVisible = [](MenuButton& button, bool visible) {
|
|
button.box->Opacity = visible ? button.opacity : 0.0f;
|
|
button.accent->Opacity = visible ? 1.0f : 0.0f;
|
|
button.label->Opacity = visible ? 1.0f : 0.0f;
|
|
};
|
|
|
|
auto ApplyMenuLayout = [&]() {
|
|
const bool main = menuScreen == MenuScreen::Main;
|
|
const bool paused = menuScreen == MenuScreen::Paused;
|
|
const bool settings = menuScreen == MenuScreen::Settings;
|
|
const bool graphics = menuScreen == MenuScreen::Graphics;
|
|
const bool drawer = menuScreen == MenuScreen::Drawer;
|
|
const bool mainButtons = main || paused;
|
|
|
|
menuVeil->Opacity = 0.22f;
|
|
menuPanel->Opacity = 0.82f;
|
|
menuPanelEdge->Opacity = 1.0f;
|
|
menuTitle->Opacity = 1.0f;
|
|
menuSubtitle->Opacity = 1.0f;
|
|
menuHint->Opacity = 1.0f;
|
|
menuSubtitle->text = main ? "MAIN MENU" : (paused ? "PAUSED"
|
|
: (drawer ? "TEXTURE DRAWER" : (graphics ? "GRAPHICS" : "SETTINGS")));
|
|
playButton.label->text = paused ? "RESUME" : "PLAY";
|
|
quitButton.label->text = paused ? "QUIT TO MENU" : "QUIT";
|
|
menuHint->text = drawer
|
|
? "DRAG ON THE CANVAS TO DRAW\nESC TO GO BACK"
|
|
: (settings || graphics ? "ESC TO GO BACK" : "WASD TO MOVE\nESC FOR MENU");
|
|
|
|
SetButtonVisible(playButton, mainButtons);
|
|
SetButtonVisible(drawerButton, mainButtons);
|
|
SetButtonVisible(settingsButton, mainButtons);
|
|
SetButtonVisible(quitButton, mainButtons);
|
|
SetButtonVisible(surfacePrevious, drawer);
|
|
SetButtonVisible(surfaceNext, drawer);
|
|
SetButtonVisible(brushDown, drawer);
|
|
SetButtonVisible(brushUp, drawer);
|
|
SetButtonVisible(clearButton, drawer);
|
|
for (MenuButton& swatch : swatches)
|
|
SetButtonVisible(swatch, drawer);
|
|
|
|
Texture* canvasTexture = drawableSurfaces[selectedSurface].surface->texture;
|
|
canvasImage->surface = SurfaceTexture::Stretched(canvasTexture);
|
|
canvasImage->Opacity = drawer && canvasTexture ? 1.0f : 0.0f;
|
|
canvasBlank->Opacity = drawer ? 1.0f : 0.0f;
|
|
canvasBorder->Opacity = drawer ? 1.0f : 0.0f;
|
|
surfaceName->Opacity = drawer ? 1.0f : 0.0f;
|
|
surfaceName->text = drawableSurfaces[selectedSurface].name;
|
|
brushLabel->Opacity = drawer ? 1.0f : 0.0f;
|
|
brushValue->Opacity = drawer ? 1.0f : 0.0f;
|
|
brushValue->text = std::to_string(painter.brushSize);
|
|
swatchMarker->Opacity = drawer ? 1.0f : 0.0f;
|
|
swatchMarker->t2d.position = { 0.0f, 0.5f, menuMargin + swatchSpacing * selectedSwatch, swatchRowY + 28.0f };
|
|
|
|
SetButtonVisible(sensitivityDown, settings);
|
|
SetButtonVisible(sensitivityUp, settings);
|
|
SetButtonVisible(fovDown, settings);
|
|
SetButtonVisible(fovUp, settings);
|
|
SetButtonVisible(hitboxToggle, settings);
|
|
SetButtonVisible(graphicsButton, settings);
|
|
SetButtonVisible(resetButton, settings);
|
|
SetButtonVisible(backButton, settings || drawer || graphics);
|
|
|
|
auto ShowToggle = [&](MenuButton& button, const char* name, bool enabled) {
|
|
SetButtonVisible(button, graphics);
|
|
button.label->text = std::string(name) + (enabled ? ": ON" : ": OFF");
|
|
button.color = enabled ? glm::vec3(0.82f, 0.45f, 0.10f) : menuButtonColor;
|
|
};
|
|
ShowToggle(bloomToggle, "BLOOM", rendersystem->graphics.bloom);
|
|
ShowToggle(shadowToggle, "SUN SHADOWS", rendersystem->graphics.shadows);
|
|
ShowToggle(lightShadowToggle, "LIGHT SHADOWS", rendersystem->graphics.lightShadows);
|
|
ShowToggle(vsyncToggle, "VSYNC", rendersystem->graphics.vsync);
|
|
ShowToggle(cullingToggle, "CULLING", rendersystem->graphics.culling);
|
|
|
|
sensLabel->Opacity = settings ? 1.0f : 0.0f;
|
|
sensValue->Opacity = settings ? 1.0f : 0.0f;
|
|
fovLabel->Opacity = settings ? 1.0f : 0.0f;
|
|
fovValue->Opacity = settings ? 1.0f : 0.0f;
|
|
|
|
hitboxToggle.label->text = showHitboxes ? "HITBOXES: ON" : "HITBOXES: OFF";
|
|
hitboxToggle.color = showHitboxes ? glm::vec3(0.82f, 0.45f, 0.10f) : menuButtonColor;
|
|
|
|
sensValue->text = std::to_string((int)std::lround(StartupCmPer360)) + " CM";
|
|
fovValue->text = std::to_string((int)std::lround(camera->FOV));
|
|
};
|
|
|
|
auto OpenMenu = [&](MenuScreen screen) {
|
|
menuActive = true;
|
|
menuScreen = screen;
|
|
charactermanager->ClearInput();
|
|
testcharacter.SetWalking(false);
|
|
npc.SetWalking(false);
|
|
fpsCounter->Opacity = 0.0f;
|
|
camera->lockedcursor = false;
|
|
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
prevMouseDown = mouse->IsLeftDown();
|
|
};
|
|
|
|
auto ResumeGame = [&]() {
|
|
menuActive = false;
|
|
charactermanager->ClearInput();
|
|
for (Box* item : menuItems) item->Opacity = 0.0f;
|
|
fpsCounter->Opacity = 1.0f;
|
|
camera->lockedcursor = true;
|
|
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
|
int ww, wh;
|
|
glfwGetWindowSize(window->handle, &ww, &wh);
|
|
glfwSetCursorPos(window->handle, ww * 0.5, wh * 0.5);
|
|
};
|
|
|
|
fpsCounter->Opacity = 0.0f;
|
|
camera->lockedcursor = false;
|
|
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Baseplate
|
|
// ------------------------------------------------------------------------
|
|
Mesh* floor = CreateCubeMesh();
|
|
floor->name = "baseplate";
|
|
floor->surface = SurfaceTexture::Tiled(blanktex, 8.0f);
|
|
floor->roughness = 0.9f;
|
|
floor->t.ScaleTo({ 200.0f, 2.0f, 200.0f });
|
|
floor->t.TranslateTo({ 0.0f, -1.0f, 0.0f });
|
|
floor->BindToRenderSystem();
|
|
mainf->AddChild(floor);
|
|
|
|
p* floorBody = new p(floor);
|
|
floorBody->velocity = false;
|
|
physicsengine->AddObject(floorBody);
|
|
const BoundingBox floorBounds = floor->t.GetRotationlessAABB();
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Light cubes
|
|
// ------------------------------------------------------------------------
|
|
auto AddLightCube = [&](const char* name, const glm::vec3& position, float size, const glm::vec3& color) -> Mesh* {
|
|
Mesh* cube = CreateCubeMesh();
|
|
cube->name = name;
|
|
cube->colorOverride = glm::vec4(color, 1.0f);
|
|
cube->emissive = 1.0f;
|
|
cube->t.ScaleTo(size);
|
|
cube->t.TranslateTo(position);
|
|
cube->BindToRenderSystem();
|
|
mainf->AddChild(cube);
|
|
return cube;
|
|
};
|
|
|
|
const glm::vec3 skyLightColor(0.45f, 0.65f, 1.0f);
|
|
const glm::vec3 skyLightPosition(-6.0f, 7.0f, -6.0f);
|
|
Mesh* skyLightCube = AddLightCube("spot_light_cube", skyLightPosition, 1.5f, skyLightColor);
|
|
rendersystem->lighting->AddSpotLight(skyLightPosition, { 0.0f, -1.0f, 0.0f }, skyLightColor, 1200.0f, 25.0f, 18.0f, 26.0f);
|
|
|
|
const glm::vec3 pointLightPosition(6.0f, 3.0f, -6.0f);
|
|
const glm::vec3 pointLightColor(1.0f, 0.35f, 0.05f);
|
|
Mesh* pointLightCube = AddLightCube("point_light_cube", pointLightPosition, 0.8f, pointLightColor);
|
|
rendersystem->lighting->AddPointLight(pointLightPosition, pointLightColor, 300.0f, 30.0f);
|
|
|
|
drawableSurfaces.push_back({ "BASEPLATE", &floor->surface, &floor->colorOverride });
|
|
drawableSurfaces.push_back({ "PLAYER", &testcharacter.surface, &testcharacter.colorOverride });
|
|
drawableSurfaces.push_back({ "DUMMY", &npc.surface, &npc.colorOverride });
|
|
drawableSurfaces.push_back({ "SKY LIGHT CUBE", &skyLightCube->surface, &skyLightCube->colorOverride });
|
|
drawableSurfaces.push_back({ "POINT LIGHT CUBE", &pointLightCube->surface, &pointLightCube->colorOverride });
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Ground check + respawn
|
|
// ------------------------------------------------------------------------
|
|
auto IsGrounded = [&]() -> bool {
|
|
Ray downRay;
|
|
downRay.origin = testcharacter.body->t.GetTranslation();
|
|
downRay.direction = glm::vec3(0.0f, -1.0f, 0.0f);
|
|
const float halfHeight = testcharacter.body->t.GetScale().y * 0.5f;
|
|
for (p* obj : physicsengine->GetObjects()) {
|
|
if (obj->pointer == testcharacter.body) continue;
|
|
if (!obj->collision) continue;
|
|
auto hit = IsRayInT(downRay, obj->pointer->t);
|
|
if (hit.has_value()) {
|
|
if (glm::distance(downRay.origin, hit.value()) < halfHeight + 0.15f)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
std::mt19937 respawnRng(std::random_device{}());
|
|
auto RandomSpawn = [&]() -> glm::vec3 {
|
|
std::uniform_real_distribution<float> spread(-14.0f, 14.0f);
|
|
return { spread(respawnRng), 15.0f, spread(respawnRng) };
|
|
};
|
|
|
|
auto UpdateHitboxDebug = [&]() {
|
|
std::vector<HitboxDebugLine> lines;
|
|
if (showHitboxes) {
|
|
for (Character* character : charactermanager->GetCharacters()) {
|
|
if (!character->body) continue;
|
|
if (character->ragdoll) {
|
|
for (Ragdoll::Part* part : character->ragdoll->GetParts())
|
|
lines.push_back({ part->transform.t.GetMatrix(), glm::vec3(0.2f, 0.9f, 0.35f) });
|
|
continue;
|
|
}
|
|
lines.push_back({ character->body->t.GetMatrix(), glm::vec3(0.2f, 0.9f, 0.35f) });
|
|
}
|
|
}
|
|
rendersystem->SetHitboxDebugLines(lines);
|
|
};
|
|
|
|
// ========================================================================
|
|
// Game loop
|
|
// ========================================================================
|
|
while (!glfwWindowShouldClose(window->handle))
|
|
{
|
|
frame++;
|
|
|
|
static double prevFrameTime = glfwGetTime();
|
|
const double frameNow = glfwGetTime();
|
|
const float deltatime = glm::clamp(float(frameNow - prevFrameTime), 0.001f, 1.0f / 60.0f) * (144.0f / float(fps));
|
|
prevFrameTime = frameNow;
|
|
const float refDt = 1.0f / float(fps);
|
|
|
|
static bool prevEscapeDown = false;
|
|
const bool escapeDown = keyboard->IsKeyDown(GLFW_KEY_ESCAPE);
|
|
const bool escapePressed = escapeDown && !prevEscapeDown;
|
|
prevEscapeDown = escapeDown;
|
|
|
|
if (escapePressed) {
|
|
if (!menuActive)
|
|
OpenMenu(MenuScreen::Paused);
|
|
else if (menuScreen == MenuScreen::Settings || menuScreen == MenuScreen::Drawer)
|
|
menuScreen = settingsReturn;
|
|
else if (menuScreen == MenuScreen::Graphics)
|
|
menuScreen = MenuScreen::Settings;
|
|
else if (menuScreen == MenuScreen::Paused)
|
|
ResumeGame();
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
// Mouse look
|
|
// --------------------------------------------------------------------
|
|
while (Mouse::Event buffer = mouse->Read()) {
|
|
if (menuActive) continue;
|
|
if (buffer.GetType() == Mouse::Event::Type::Move && camera->lockedcursor) {
|
|
float xoffset = mouse->GetX() - mouse->GetLastX();
|
|
float yoffset = mouse->GetLastY() - mouse->GetY();
|
|
camera->ProcessMouseMovement(xoffset, yoffset);
|
|
}
|
|
else if (buffer.GetType() == Mouse::Event::Type::RPress) {
|
|
camera->lockedcursor = !camera->lockedcursor;
|
|
if (camera->lockedcursor) {
|
|
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
|
int ww, wh;
|
|
glfwGetWindowSize(window->handle, &ww, &wh);
|
|
glfwSetCursorPos(window->handle, ww * 0.5, wh * 0.5);
|
|
}
|
|
else {
|
|
glfwSetInputMode(window->handle, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (menuActive) {
|
|
ApplyMenuLayout();
|
|
|
|
const bool liveBackdrop = menuScreen == MenuScreen::Main ||
|
|
((menuScreen == MenuScreen::Settings || menuScreen == MenuScreen::Graphics || menuScreen == MenuScreen::Drawer)
|
|
&& settingsReturn == MenuScreen::Main);
|
|
if (liveBackdrop) {
|
|
static float menuAngle = 0.6f;
|
|
menuAngle += deltatime * 0.12f;
|
|
const glm::vec3 menuTarget(0.0f, 1.4f, 0.0f);
|
|
const glm::vec3 menuCamera = menuTarget +
|
|
glm::vec3(std::sin(menuAngle) * 7.5f, 2.6f, std::cos(menuAngle) * 7.5f);
|
|
camera->t.TranslateTo(menuCamera);
|
|
camera->t.RotateToQuaternion(LookAt(menuTarget - menuCamera));
|
|
camera->Step(deltatime);
|
|
|
|
charactermanager->Step(deltatime);
|
|
}
|
|
|
|
soundsystem->SetListenerPosition(
|
|
camera->t.GetTranslation(),
|
|
camera->t.GetFrontVector(),
|
|
camera->t.GetUpVector()
|
|
);
|
|
soundsystem->Update();
|
|
|
|
const glm::vec2 mousePos((float)mouse->GetX(), (float)mouse->GetY());
|
|
const float hoverEase = 1.0f - std::exp(-12.0f * deltatime);
|
|
|
|
for (MenuButton* button : menuButtons) {
|
|
t2d_package& t2d = button->box->t2d;
|
|
t2d.Recalculate();
|
|
button->hovered = button->box->Opacity > 0.0f &&
|
|
mousePos.x > t2d.pixelposition.x &&
|
|
mousePos.y > t2d.pixelposition.y &&
|
|
mousePos.x < t2d.pixelposition.x + t2d.pixelsize.x &&
|
|
mousePos.y < t2d.pixelposition.y + t2d.pixelsize.y;
|
|
|
|
button->hover += ((button->hovered ? 1.0f : 0.0f) - button->hover) * hoverEase;
|
|
button->box->Color = glm::mix(button->color, button->hoverColor, button->hover);
|
|
button->accent->t2d.size.z = 5.0f * button->hover;
|
|
button->label->t2d.position.z = button->labelX + button->labelSlide * button->hover;
|
|
}
|
|
|
|
const bool mouseDown = mouse->IsLeftDown();
|
|
if (mouseDown && !prevMouseDown) {
|
|
if (menuScreen == MenuScreen::Settings) {
|
|
if (sensitivityDown.hovered) {
|
|
StartupCmPer360 = glm::clamp(StartupCmPer360 + 2.0f, 4.0f, 80.0f);
|
|
camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
|
|
}
|
|
else if (sensitivityUp.hovered) {
|
|
StartupCmPer360 = glm::clamp(StartupCmPer360 - 2.0f, 4.0f, 80.0f);
|
|
camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
|
|
}
|
|
else if (fovDown.hovered) {
|
|
camera->FOV = glm::clamp(camera->FOV - 5.0f, 60.0f, 130.0f);
|
|
}
|
|
else if (fovUp.hovered) {
|
|
camera->FOV = glm::clamp(camera->FOV + 5.0f, 60.0f, 130.0f);
|
|
}
|
|
else if (hitboxToggle.hovered) {
|
|
showHitboxes = !showHitboxes;
|
|
}
|
|
else if (graphicsButton.hovered) {
|
|
menuScreen = MenuScreen::Graphics;
|
|
}
|
|
else if (resetButton.hovered) {
|
|
StartupCmPer360 = 16.0f;
|
|
StartupMouseDpi = 800.0f;
|
|
camera->SetSensitivityCm(StartupCmPer360, StartupMouseDpi);
|
|
camera->FOV = 110.0f;
|
|
showHitboxes = false;
|
|
}
|
|
else if (backButton.hovered) {
|
|
menuScreen = settingsReturn;
|
|
}
|
|
}
|
|
else if (menuScreen == MenuScreen::Graphics) {
|
|
GraphicsSettings& graphics = rendersystem->graphics;
|
|
if (bloomToggle.hovered) {
|
|
graphics.bloom = !graphics.bloom;
|
|
}
|
|
else if (shadowToggle.hovered) {
|
|
graphics.shadows = !graphics.shadows;
|
|
}
|
|
else if (lightShadowToggle.hovered) {
|
|
graphics.lightShadows = !graphics.lightShadows;
|
|
}
|
|
else if (vsyncToggle.hovered) {
|
|
graphics.vsync = !graphics.vsync;
|
|
glfwSwapInterval(graphics.vsync ? 1 : 0);
|
|
}
|
|
else if (cullingToggle.hovered) {
|
|
graphics.culling = !graphics.culling;
|
|
}
|
|
else if (backButton.hovered) {
|
|
menuScreen = MenuScreen::Settings;
|
|
}
|
|
}
|
|
else if (menuScreen == MenuScreen::Drawer) {
|
|
const int surfaceCount = (int)drawableSurfaces.size();
|
|
if (surfacePrevious.hovered) {
|
|
selectedSurface = (selectedSurface + surfaceCount - 1) % surfaceCount;
|
|
}
|
|
else if (surfaceNext.hovered) {
|
|
selectedSurface = (selectedSurface + 1) % surfaceCount;
|
|
}
|
|
else if (brushDown.hovered) {
|
|
painter.brushSize = glm::max(painter.brushSize - 2, 2);
|
|
}
|
|
else if (brushUp.hovered) {
|
|
painter.brushSize = glm::min(painter.brushSize + 2, 64);
|
|
}
|
|
else if (clearButton.hovered) {
|
|
DrawableSurface& drawable = drawableSurfaces[selectedSurface];
|
|
EnsureCanvas(drawable);
|
|
painter.Clear(*drawable.surface->texture);
|
|
}
|
|
else if (backButton.hovered) {
|
|
menuScreen = settingsReturn;
|
|
}
|
|
for (int i = 0; i < (int)swatches.size(); i++) {
|
|
if (swatches[i].hovered) {
|
|
selectedSwatch = i;
|
|
painter.color = swatchColors[i];
|
|
}
|
|
}
|
|
}
|
|
else if (playButton.hovered) {
|
|
ResumeGame();
|
|
}
|
|
else if (drawerButton.hovered) {
|
|
settingsReturn = menuScreen;
|
|
menuScreen = MenuScreen::Drawer;
|
|
}
|
|
else if (settingsButton.hovered) {
|
|
settingsReturn = menuScreen;
|
|
menuScreen = MenuScreen::Settings;
|
|
}
|
|
else if (quitButton.hovered) {
|
|
if (menuScreen == MenuScreen::Main)
|
|
glfwSetWindowShouldClose(window->handle, 1);
|
|
else
|
|
menuScreen = MenuScreen::Main;
|
|
}
|
|
}
|
|
// A stroke has to start on the canvas, but carries on if the mouse wanders off it
|
|
canvasImage->t2d.Recalculate();
|
|
const glm::vec2 canvasUV = (mousePos - canvasImage->t2d.pixelposition) / canvasImage->t2d.pixelsize;
|
|
if (!mouseDown || menuScreen != MenuScreen::Drawer) {
|
|
painting = false;
|
|
}
|
|
else if (!prevMouseDown) {
|
|
painting = canvasUV.x >= 0.0f && canvasUV.x <= 1.0f && canvasUV.y >= 0.0f && canvasUV.y <= 1.0f;
|
|
}
|
|
|
|
if (painting) {
|
|
DrawableSurface& drawable = drawableSurfaces[selectedSurface];
|
|
EnsureCanvas(drawable);
|
|
painter.Stroke(*drawable.surface->texture, glm::clamp(canvasUV, glm::vec2(0.0f), glm::vec2(1.0f)));
|
|
}
|
|
else {
|
|
painter.EndStroke();
|
|
}
|
|
|
|
prevMouseDown = mouseDown;
|
|
|
|
UpdateHitboxDebug();
|
|
rendersystem->Render(*camera);
|
|
glfwPollEvents();
|
|
continue;
|
|
}
|
|
|
|
const glm::vec3 camFront = camera->t.GetFrontVector();
|
|
const glm::vec3 camRight = camera->t.GetRightVector();
|
|
const float movespeed = 3.0f * 1.3f * 0.6f;
|
|
|
|
// --------------------------------------------------------------------
|
|
// WASD movement
|
|
// --------------------------------------------------------------------
|
|
const glm::vec3 inputDirection = testcharacter.GetMovementDirection(camFront, camRight);
|
|
const bool hasMoveInput = glm::length(inputDirection) > 0.0f;
|
|
|
|
if (hasMoveInput)
|
|
testcharacter.SetTurnDirection(inputDirection);
|
|
testcharacter.SetWalking(hasMoveInput);
|
|
|
|
charactermanager->Step(deltatime);
|
|
|
|
const glm::vec3 inputVel = hasMoveInput
|
|
? testcharacter.GetForwardDirection() * movespeed
|
|
: glm::vec3(0.0f);
|
|
|
|
const float accel = 40.0f;
|
|
const float damp = 0.75f;
|
|
const float airControl = 0.5f;
|
|
const float groundTopSpeed = movespeed * accel * refDt * damp / (1.0f - damp);
|
|
|
|
if (IsGrounded()) {
|
|
const float dampStep = std::pow(damp, deltatime / refDt);
|
|
const float accelScale = refDt * damp * (1.0f - dampStep) / (deltatime * dampStep * (1.0f - damp));
|
|
characterBody->linearvelocity.x *= dampStep;
|
|
characterBody->linearvelocity.z *= dampStep;
|
|
characterBody->linearvelocity.x += inputVel.x * accel * deltatime * accelScale;
|
|
characterBody->linearvelocity.z += inputVel.z * accel * deltatime * accelScale;
|
|
}
|
|
else if (glm::length(inputVel) > 0.0f) {
|
|
const glm::vec3 wishDir = glm::normalize(inputVel);
|
|
const float current = glm::dot(glm::vec3(characterBody->linearvelocity.x, 0.0f,
|
|
characterBody->linearvelocity.z), wishDir);
|
|
const float add = glm::clamp(groundTopSpeed - current, 0.0f,
|
|
accel * airControl * movespeed * deltatime);
|
|
characterBody->linearvelocity.x += wishDir.x * add;
|
|
characterBody->linearvelocity.z += wishDir.z * add;
|
|
}
|
|
|
|
{
|
|
const int substeps = std::max(1, (int)std::ceil(deltatime * 120.0f));
|
|
const float subDt = deltatime / float(substeps);
|
|
for (int i = 0; i < substeps; i++) physicsengine->Step(subDt);
|
|
}
|
|
npc.ragdoll->Update(deltatime);
|
|
|
|
if (testcharacter.body->t.GetTranslation().y < -20.0f) {
|
|
testcharacter.body->t.TranslateTo(RandomSpawn());
|
|
characterBody->linearvelocity = glm::vec3(0.0f);
|
|
characterBody->WakeUp();
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
// Third person camera
|
|
// --------------------------------------------------------------------
|
|
{
|
|
const glm::vec3 playerPos = testcharacter.body->t.GetTranslation();
|
|
const glm::vec3 target = playerPos + glm::vec3(0.0f, 0.8f, 0.0f);
|
|
glm::vec3 cameraPos = target - camera->t.GetFrontVector() * 5.0f;
|
|
|
|
const bool playerOffFloor =
|
|
playerPos.x < floorBounds.min.x || playerPos.x > floorBounds.max.x ||
|
|
playerPos.z < floorBounds.min.z || playerPos.z > floorBounds.max.z;
|
|
const bool ignoreFloorCollision = playerOffFloor || playerPos.y < floorBounds.max.y;
|
|
|
|
const glm::vec3 cameraRay = cameraPos - target;
|
|
const float desiredDistance = glm::length(cameraRay);
|
|
const glm::vec3 cameraDir = desiredDistance > 0.0001f ? cameraRay / desiredDistance : glm::vec3(0.0f);
|
|
|
|
Ray cameraSweep;
|
|
cameraSweep.origin = target;
|
|
cameraSweep.direction = cameraDir;
|
|
float nearestHit = desiredDistance;
|
|
for (p* obj : physicsengine->GetObjects()) {
|
|
if (obj->pointer == testcharacter.body || !obj->collision)
|
|
continue;
|
|
if (obj->collisionGroup != 0) // flailing ragdoll limbs would make the camera jump
|
|
continue;
|
|
if (obj == floorBody && ignoreFloorCollision)
|
|
continue;
|
|
auto hit = IsRayInT(cameraSweep, obj->pointer->t);
|
|
if (!hit.has_value())
|
|
continue;
|
|
const float hitDistance = glm::dot(hit.value() - target, cameraDir);
|
|
if (hitDistance > 0.001f && hitDistance < nearestHit)
|
|
nearestHit = hitDistance;
|
|
}
|
|
|
|
const float cameraPadding = 0.3f;
|
|
cameraPos = target + cameraDir * glm::max(nearestHit - cameraPadding, 0.05f);
|
|
camera->t.TranslateTo(cameraPos);
|
|
}
|
|
camera->Step(deltatime);
|
|
|
|
Sound* music = static_cast<Sound*>(mainf->GetFirstChildOfName("music"));
|
|
if (music) music->Update3DPosition(0.0f, 0.0f, 0.0f);
|
|
|
|
soundsystem->SetListenerPosition(
|
|
camera->t.GetTranslation(),
|
|
camera->t.GetFrontVector(),
|
|
camera->t.GetUpVector()
|
|
);
|
|
soundsystem->Update();
|
|
|
|
// --------------------------------------------------------------------
|
|
// FPS counter
|
|
// --------------------------------------------------------------------
|
|
{
|
|
static double lastTime = glfwGetTime();
|
|
static int frameCount = 0;
|
|
static int measuredFps = 0;
|
|
static float accumulator = 0.0f;
|
|
double now = glfwGetTime();
|
|
accumulator += (float)(now - lastTime);
|
|
lastTime = now;
|
|
frameCount++;
|
|
if (accumulator >= 0.25f) {
|
|
measuredFps = (int)(frameCount / accumulator);
|
|
frameCount = 0;
|
|
accumulator = 0.0f;
|
|
}
|
|
fpsCounter->text = std::to_string(measuredFps) + " FPS";
|
|
}
|
|
|
|
// --------------------------------------------------------------------
|
|
// Render
|
|
// --------------------------------------------------------------------
|
|
UpdateHitboxDebug();
|
|
rendersystem->Render(*camera);
|
|
|
|
glfwPollEvents();
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// GLFW callbacks
|
|
// ============================================================================
|
|
void framebuffer_size_callback(GLFWwindow* window, int w, int h)
|
|
{
|
|
if (w <= 0 || h <= 0) return;
|
|
::window->width = w;
|
|
::window->height = h;
|
|
glViewport(0, 0, w, h);
|
|
if (engine && engine->rendersystem)
|
|
engine->rendersystem->Resize(w, h);
|
|
}
|
|
|
|
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
|
{
|
|
if (key == GLFW_KEY_F11 && action == GLFW_PRESS) {
|
|
static int wx = 100, wy = 100, ww = 1920, wh = 1080;
|
|
if (glfwGetWindowMonitor(window)) {
|
|
glfwSetWindowMonitor(window, nullptr, wx, wy, ww, wh, 0);
|
|
}
|
|
else {
|
|
glfwGetWindowPos(window, &wx, &wy);
|
|
glfwGetWindowSize(window, &ww, &wh);
|
|
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
|
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
|
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
|
|
}
|
|
glfwSwapInterval(engine && engine->rendersystem && !engine->rendersystem->graphics.vsync ? 0 : 1);
|
|
}
|
|
|
|
if (key == GLFW_KEY_H && action == GLFW_PRESS && engine && engine->rendersystem)
|
|
engine->rendersystem->CycleDebugView();
|
|
|
|
if (action == GLFW_PRESS) keyboard->KeyDown(key);
|
|
else if (action == GLFW_RELEASE) keyboard->KeyUp(key);
|
|
|
|
if (key >= GLFW_KEY_A && key <= GLFW_KEY_Z && engine && engine->charactermanager) {
|
|
const char letter = (char)('A' + (key - GLFW_KEY_A));
|
|
if (action == GLFW_PRESS) engine->charactermanager->PressInput(letter);
|
|
else if (action == GLFW_RELEASE) engine->charactermanager->ReleaseInput(letter);
|
|
}
|
|
}
|
|
|
|
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
|
|
{
|
|
}
|
|
|
|
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
|
|
{
|
|
mouse->OnMouseMove(static_cast<float>(xposIn), static_cast<float>(yposIn));
|
|
if (!engine || !engine->camera || !engine->camera->lockedcursor) return;
|
|
|
|
int ww, wh;
|
|
glfwGetWindowSize(window, &ww, &wh);
|
|
const double cx = ww / 2.0;
|
|
const double cy = wh / 2.0;
|
|
if (std::abs(xposIn - cx) < 0.5 && std::abs(yposIn - cy) < 0.5) return;
|
|
|
|
glfwSetCursorPos(window, cx, cy);
|
|
mouse->OnMouseMove((float)cx, (float)cy);
|
|
}
|
|
|
|
void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
|
|
{
|
|
if (button == GLFW_MOUSE_BUTTON_RIGHT) {
|
|
if (action == GLFW_PRESS) mouse->RightDown();
|
|
else mouse->RightUp();
|
|
}
|
|
else if (button == GLFW_MOUSE_BUTTON_LEFT) {
|
|
if (action == GLFW_PRESS) mouse->LeftDown();
|
|
else mouse->LeftUp();
|
|
}
|
|
}
|