Separation of concerns
This commit is contained in:
parent
c39715b84c
commit
a2452e3cf6
@ -3,7 +3,7 @@ CXXFLAGS = -std=c++20 -Wall -Wextra -O2 -I.
|
|||||||
LDFLAGS = -lraylib -lboost_system -lpthread -lGL -lm -ldl -lrt -lX11
|
LDFLAGS = -lraylib -lboost_system -lpthread -lGL -lm -ldl -lrt -lX11
|
||||||
|
|
||||||
TARGET = game
|
TARGET = game
|
||||||
SOURCES = main.cpp entity/Entity.cpp entity/player/PlayerController.cpp net/NetworkManager.cpp sky/Sky.cpp
|
SOURCES = main.cpp entity/Entity.cpp entity/player/PlayerController.cpp entity/player/PlayerRenderer.cpp net/NetworkManager.cpp sky/Sky.cpp
|
||||||
OBJECTS = $(SOURCES:.cpp=.o)
|
OBJECTS = $(SOURCES:.cpp=.o)
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|||||||
96
client/entity/player/PlayerRenderer.cpp
Normal file
96
client/entity/player/PlayerRenderer.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#include "PlayerRenderer.hpp"
|
||||||
|
#include "../../net/NetworkManager.hpp"
|
||||||
|
|
||||||
|
PlayerRenderer::PlayerRenderer() {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerRenderer::~PlayerRenderer() {
|
||||||
|
// Unload textures
|
||||||
|
for (auto& [color, texture] : playerTextures) {
|
||||||
|
UnloadTexture(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload models
|
||||||
|
UnloadModel(localPlayerModel);
|
||||||
|
for (auto& [id, model] : remotePlayerModels) {
|
||||||
|
UnloadModel(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerRenderer::init() {
|
||||||
|
// Load all player color textures
|
||||||
|
playerTextures["red"] = LoadTexture("../assets/textures/red.png");
|
||||||
|
playerTextures["green"] = LoadTexture("../assets/textures/green.png");
|
||||||
|
playerTextures["orange"] = LoadTexture("../assets/textures/orange.png");
|
||||||
|
playerTextures["purple"] = LoadTexture("../assets/textures/purple.png");
|
||||||
|
playerTextures["white"] = LoadTexture("../assets/textures/white.png");
|
||||||
|
|
||||||
|
// Create local player model
|
||||||
|
auto cubeMesh = GenMeshCube(1.0f, 2.0f, 1.0f);
|
||||||
|
localPlayerModel = LoadModelFromMesh(cubeMesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerRenderer::renderLocalPlayer(RenderContext& ctx, const Vector3& position, const std::string& color) {
|
||||||
|
// Set texture based on player color
|
||||||
|
if (playerTextures.count(color) > 0) {
|
||||||
|
localPlayerModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = playerTextures[color];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit to render context
|
||||||
|
ctx.submitModel(localPlayerModel, position, 1.0f, WHITE, RenderContext::RenderLayer::ENTITIES);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerRenderer::renderRemotePlayers(RenderContext& ctx,
|
||||||
|
const std::unordered_map<uint32_t, RemotePlayer>& players) {
|
||||||
|
for (const auto& [id, player] : players) {
|
||||||
|
// Ensure model exists for this player
|
||||||
|
ensurePlayerModel(id);
|
||||||
|
|
||||||
|
// Update texture based on player color
|
||||||
|
if (remotePlayerModels.count(id) > 0 && playerTextures.count(player.color) > 0) {
|
||||||
|
remotePlayerModels[id].materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = playerTextures[player.color];
|
||||||
|
|
||||||
|
// Submit to render context
|
||||||
|
ctx.submitModel(remotePlayerModels[id], player.position, 1.0f, WHITE,
|
||||||
|
RenderContext::RenderLayer::ENTITIES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerRenderer::ensurePlayerModel(uint32_t playerId) {
|
||||||
|
if (remotePlayerModels.find(playerId) == remotePlayerModels.end()) {
|
||||||
|
// Create new model for this player
|
||||||
|
auto cubeMesh = GenMeshCube(1.0f, 2.0f, 1.0f);
|
||||||
|
remotePlayerModels[playerId] = LoadModelFromMesh(cubeMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerRenderer::removePlayerModel(uint32_t playerId) {
|
||||||
|
auto it = remotePlayerModels.find(playerId);
|
||||||
|
if (it != remotePlayerModels.end()) {
|
||||||
|
UnloadModel(it->second);
|
||||||
|
remotePlayerModels.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayerRenderer::cleanupDisconnectedPlayers(const std::unordered_map<uint32_t, RemotePlayer>& currentPlayers) {
|
||||||
|
// Remove models for players who are no longer connected
|
||||||
|
for (auto it = remotePlayerModels.begin(); it != remotePlayerModels.end();) {
|
||||||
|
if (currentPlayers.find(it->first) == currentPlayers.end()) {
|
||||||
|
UnloadModel(it->second);
|
||||||
|
it = remotePlayerModels.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Texture2D PlayerRenderer::getPlayerTexture(const std::string& color) const {
|
||||||
|
auto it = playerTextures.find(color);
|
||||||
|
if (it != playerTextures.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
// Return a default texture or handle error
|
||||||
|
return Texture2D{};
|
||||||
|
}
|
||||||
42
client/entity/player/PlayerRenderer.hpp
Normal file
42
client/entity/player/PlayerRenderer.hpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <raylib.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <string>
|
||||||
|
#include <cstdint>
|
||||||
|
#include "../../render/RenderContext.hpp"
|
||||||
|
|
||||||
|
class PlayerRenderer {
|
||||||
|
private:
|
||||||
|
Model localPlayerModel;
|
||||||
|
std::unordered_map<uint32_t, Model> remotePlayerModels;
|
||||||
|
std::unordered_map<std::string, Texture2D> playerTextures;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PlayerRenderer();
|
||||||
|
~PlayerRenderer();
|
||||||
|
|
||||||
|
// Initialize player models and textures
|
||||||
|
void init();
|
||||||
|
|
||||||
|
// Render local player
|
||||||
|
void renderLocalPlayer(RenderContext& ctx, const Vector3& position, const std::string& color);
|
||||||
|
|
||||||
|
// Render remote players
|
||||||
|
void renderRemotePlayers(RenderContext& ctx, const std::unordered_map<uint32_t, struct RemotePlayer>& players);
|
||||||
|
|
||||||
|
// Update player model for a specific player ID
|
||||||
|
void ensurePlayerModel(uint32_t playerId);
|
||||||
|
|
||||||
|
// Remove model for disconnected player
|
||||||
|
void removePlayerModel(uint32_t playerId);
|
||||||
|
|
||||||
|
// Clean up models for players not in the list
|
||||||
|
void cleanupDisconnectedPlayers(const std::unordered_map<uint32_t, struct RemotePlayer>& currentPlayers);
|
||||||
|
|
||||||
|
// Get texture for a color
|
||||||
|
Texture2D getPlayerTexture(const std::string& color) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward declaration for RemotePlayer struct (defined in NetworkManager)
|
||||||
|
struct RemotePlayer;
|
||||||
123
client/main.cpp
123
client/main.cpp
@ -7,15 +7,17 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <unordered_map>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "entity/player/PlayerController.hpp"
|
#include "entity/player/PlayerController.hpp"
|
||||||
|
#include "entity/player/PlayerRenderer.hpp"
|
||||||
#include "net/NetworkManager.hpp"
|
#include "net/NetworkManager.hpp"
|
||||||
#include "sky/Sky.hpp"
|
#include "sky/Sky.hpp"
|
||||||
|
#include "render/RenderContext.hpp"
|
||||||
|
#include "utils/Coords.hpp"
|
||||||
|
|
||||||
constexpr int WORLD_SIZE = 100;
|
constexpr int WORLD_SIZE = 100;
|
||||||
constexpr float WORLD_SCALE = 10.0f;
|
constexpr float WORLD_SCALE = 10.0f;
|
||||||
@ -110,15 +112,14 @@ enum GameState {
|
|||||||
|
|
||||||
class Game {
|
class Game {
|
||||||
PlayerController playerController;
|
PlayerController playerController;
|
||||||
|
std::unique_ptr<PlayerRenderer> playerRenderer;
|
||||||
|
RenderContext renderContext;
|
||||||
Model terrainModel;
|
Model terrainModel;
|
||||||
Model playerModel;
|
|
||||||
Heightmap heightmap;
|
Heightmap heightmap;
|
||||||
NetworkManager network;
|
NetworkManager network;
|
||||||
std::unique_ptr<Sky> sky;
|
std::unique_ptr<Sky> sky;
|
||||||
Vector3 playerPos{0, 0, 0};
|
Vector3 playerPos{0, 0, 0};
|
||||||
Texture2D terrainTexture;
|
Texture2D terrainTexture;
|
||||||
std::unordered_map<std::string, Texture2D> playerTextures;
|
|
||||||
std::unordered_map<uint32_t, Model> remotePlayerModels;
|
|
||||||
|
|
||||||
// Login UI state
|
// Login UI state
|
||||||
GameState gameState = STATE_LOGIN;
|
GameState gameState = STATE_LOGIN;
|
||||||
@ -131,12 +132,17 @@ class Game {
|
|||||||
float lastHeartbeatTime = 0.0f;
|
float lastHeartbeatTime = 0.0f;
|
||||||
const float HEARTBEAT_INTERVAL = 5.0f; // Send heartbeat every 5 seconds
|
const float HEARTBEAT_INTERVAL = 5.0f; // Send heartbeat every 5 seconds
|
||||||
|
|
||||||
|
// Debug options
|
||||||
|
bool showDebugAxes = false;
|
||||||
|
bool showWorldBounds = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Game() {
|
Game() {
|
||||||
InitWindow(1280, 720, "Game");
|
InitWindow(1280, 720, "Game");
|
||||||
SetTargetFPS(60);
|
SetTargetFPS(60);
|
||||||
|
|
||||||
// Initialize sky after window is created
|
// Initialize components after window is created
|
||||||
|
playerRenderer = std::make_unique<PlayerRenderer>();
|
||||||
sky = std::make_unique<Sky>();
|
sky = std::make_unique<Sky>();
|
||||||
|
|
||||||
// Load heightmap
|
// Load heightmap
|
||||||
@ -147,21 +153,10 @@ public:
|
|||||||
// Load textures
|
// Load textures
|
||||||
terrainTexture = LoadTexture("../assets/textures/black.png");
|
terrainTexture = LoadTexture("../assets/textures/black.png");
|
||||||
|
|
||||||
// Load all player color textures
|
|
||||||
playerTextures["red"] = LoadTexture("../assets/textures/red.png");
|
|
||||||
playerTextures["green"] = LoadTexture("../assets/textures/green.png");
|
|
||||||
playerTextures["orange"] = LoadTexture("../assets/textures/orange.png");
|
|
||||||
playerTextures["purple"] = LoadTexture("../assets/textures/purple.png");
|
|
||||||
playerTextures["white"] = LoadTexture("../assets/textures/white.png");
|
|
||||||
|
|
||||||
// Create terrain model
|
// Create terrain model
|
||||||
auto terrainMesh = heightmap.generateMesh();
|
auto terrainMesh = heightmap.generateMesh();
|
||||||
terrainModel = LoadModelFromMesh(terrainMesh);
|
terrainModel = LoadModelFromMesh(terrainMesh);
|
||||||
terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = terrainTexture;
|
terrainModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = terrainTexture;
|
||||||
|
|
||||||
// Create player cube (texture will be set when we know our color)
|
|
||||||
auto cubeMesh = GenMeshCube(1.0f, 2.0f, 1.0f);
|
|
||||||
playerModel = LoadModelFromMesh(cubeMesh);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~Game() {
|
~Game() {
|
||||||
@ -171,14 +166,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
UnloadTexture(terrainTexture);
|
UnloadTexture(terrainTexture);
|
||||||
for (auto& [color, texture] : playerTextures) {
|
|
||||||
UnloadTexture(texture);
|
|
||||||
}
|
|
||||||
UnloadModel(terrainModel);
|
UnloadModel(terrainModel);
|
||||||
UnloadModel(playerModel);
|
|
||||||
for (auto& [id, model] : remotePlayerModels) {
|
|
||||||
UnloadModel(model);
|
|
||||||
}
|
|
||||||
CloseWindow();
|
CloseWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,38 +232,20 @@ private:
|
|||||||
sky->setTimeOfDay(currentTime);
|
sky->setTimeOfDay(currentTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug toggles
|
||||||
|
if (IsKeyPressed(KEY_F1)) showDebugAxes = !showDebugAxes;
|
||||||
|
if (IsKeyPressed(KEY_F2)) showWorldBounds = !showWorldBounds;
|
||||||
|
|
||||||
// Get server position and update player controller
|
// Get server position and update player controller
|
||||||
playerPos = network.getPosition();
|
playerPos = network.getPosition();
|
||||||
|
|
||||||
|
// Clamp position to world bounds
|
||||||
|
playerPos = Coords::clampToWorldBounds(playerPos);
|
||||||
playerController.setPlayerPosition(playerPos);
|
playerController.setPlayerPosition(playerPos);
|
||||||
|
|
||||||
// Set player texture based on assigned color
|
// Clean up disconnected player models
|
||||||
if (network.isConnected() && playerTextures.count(network.getPlayerColor()) > 0) {
|
|
||||||
playerModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = playerTextures[network.getPlayerColor()];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update remote player models
|
|
||||||
auto remotePlayers = network.getRemotePlayers();
|
auto remotePlayers = network.getRemotePlayers();
|
||||||
for (const auto& [id, player] : remotePlayers) {
|
playerRenderer->cleanupDisconnectedPlayers(remotePlayers);
|
||||||
if (remotePlayerModels.find(id) == remotePlayerModels.end()) {
|
|
||||||
// Create new model for this player
|
|
||||||
auto cubeMesh = GenMeshCube(1.0f, 2.0f, 1.0f);
|
|
||||||
remotePlayerModels[id] = LoadModelFromMesh(cubeMesh);
|
|
||||||
}
|
|
||||||
// Always update texture in case color changed
|
|
||||||
if (playerTextures.count(player.color) > 0) {
|
|
||||||
remotePlayerModels[id].materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = playerTextures[player.color];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove models for players who left
|
|
||||||
for (auto it = remotePlayerModels.begin(); it != remotePlayerModels.end();) {
|
|
||||||
if (remotePlayers.find(it->first) == remotePlayers.end()) {
|
|
||||||
UnloadModel(it->second);
|
|
||||||
it = remotePlayerModels.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update player controller (handles camera)
|
// Update player controller (handles camera)
|
||||||
float deltaTime = GetFrameTime();
|
float deltaTime = GetFrameTime();
|
||||||
@ -404,36 +374,53 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void renderGame() {
|
void renderGame() {
|
||||||
BeginMode3D(playerController.getCamera());
|
// Begin 3D rendering with render context
|
||||||
|
renderContext.begin3D(playerController.getCamera());
|
||||||
|
|
||||||
// Render skybox first (it will handle its own depth settings)
|
// Submit skybox first (lowest layer)
|
||||||
|
renderContext.submitCustom([this]() {
|
||||||
if (sky) {
|
if (sky) {
|
||||||
sky->renderSkybox(playerController.getCamera());
|
sky->renderSkybox(playerController.getCamera());
|
||||||
}
|
}
|
||||||
|
}, RenderContext::RenderLayer::SKYBOX);
|
||||||
|
|
||||||
// Draw terrain
|
// Submit terrain
|
||||||
DrawModel(terrainModel, {0, 0, 0}, 1.0f, WHITE);
|
renderContext.submitModel(terrainModel, {0, 0, 0}, 1.0f, WHITE,
|
||||||
|
RenderContext::RenderLayer::TERRAIN);
|
||||||
|
|
||||||
// Draw player
|
// Submit players if connected
|
||||||
if (network.isConnected()) {
|
if (network.isConnected()) {
|
||||||
DrawModel(playerModel, playerPos, 1.0f, WHITE);
|
// Local player
|
||||||
}
|
playerRenderer->renderLocalPlayer(renderContext, playerPos, network.getPlayerColor());
|
||||||
|
|
||||||
// Draw remote players
|
// Remote players
|
||||||
auto remotePlayers = network.getRemotePlayers();
|
auto remotePlayers = network.getRemotePlayers();
|
||||||
for (const auto& [id, player] : remotePlayers) {
|
playerRenderer->renderRemotePlayers(renderContext, remotePlayers);
|
||||||
if (remotePlayerModels.find(id) != remotePlayerModels.end()) {
|
|
||||||
DrawModel(remotePlayerModels[id], player.position, 1.0f, WHITE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EndMode3D();
|
// Debug rendering
|
||||||
|
if (showDebugAxes) {
|
||||||
|
renderContext.submitCustom([this]() {
|
||||||
|
Coords::drawDebugAxes(playerPos, 2.0f);
|
||||||
|
// Draw world origin axes
|
||||||
|
Coords::drawDebugAxes({0, 0, 0}, 5.0f);
|
||||||
|
}, RenderContext::RenderLayer::TRANSPARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showWorldBounds) {
|
||||||
|
renderContext.submitCustom([]() {
|
||||||
|
Coords::drawWorldBounds();
|
||||||
|
}, RenderContext::RenderLayer::TRANSPARENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute all render commands in order
|
||||||
|
renderContext.end3D();
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
DrawText(TextFormat("Logged in as: %s", currentUsername.c_str()), 10, 10, 20, WHITE);
|
DrawText(TextFormat("Logged in as: %s", currentUsername.c_str()), 10, 10, 20, WHITE);
|
||||||
DrawText("WASD: Move | Q/E: Strafe | Right-Click: Rotate Camera", 10, 35, 20, WHITE);
|
DrawText("WASD: Move | Q/E: Strafe | Right-Click: Rotate Camera", 10, 35, 20, WHITE);
|
||||||
DrawText("Left/Right Arrow: Change Color | Mouse Wheel: Zoom | ESC: Logout", 10, 60, 20, WHITE);
|
DrawText("Left/Right Arrow: Change Color | Mouse Wheel: Zoom | ESC: Logout", 10, 60, 20, WHITE);
|
||||||
DrawText("T: Change Time of Day", 10, 85, 20, WHITE);
|
DrawText("T: Change Time | F1: Debug Axes | F2: World Bounds", 10, 85, 20, WHITE);
|
||||||
if (network.isConnected()) {
|
if (network.isConnected()) {
|
||||||
std::string colorText = "Your color: " + network.getPlayerColor();
|
std::string colorText = "Your color: " + network.getPlayerColor();
|
||||||
DrawText(colorText.c_str(), 10, 110, 20, WHITE);
|
DrawText(colorText.c_str(), 10, 110, 20, WHITE);
|
||||||
@ -445,7 +432,13 @@ private:
|
|||||||
int minute = (int)((timeHour - hour) * 60);
|
int minute = (int)((timeHour - hour) * 60);
|
||||||
DrawText(TextFormat("Time: %02d:%02d", hour, minute), 10, 135, 20, WHITE);
|
DrawText(TextFormat("Time: %02d:%02d", hour, minute), 10, 135, 20, WHITE);
|
||||||
|
|
||||||
DrawFPS(10, 160);
|
// Show position if debug is on
|
||||||
|
if (showDebugAxes) {
|
||||||
|
DrawText(TextFormat("Pos: %.1f, %.1f, %.1f", playerPos.x, playerPos.y, playerPos.z),
|
||||||
|
10, 160, 20, YELLOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawFPS(10, showDebugAxes ? 185 : 160);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
104
client/render/RenderContext.hpp
Normal file
104
client/render/RenderContext.hpp
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <raylib.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
class RenderContext {
|
||||||
|
public:
|
||||||
|
struct RenderLayer {
|
||||||
|
enum Type {
|
||||||
|
SKYBOX = 0,
|
||||||
|
TERRAIN = 100,
|
||||||
|
ENTITIES = 200,
|
||||||
|
TRANSPARENT = 300,
|
||||||
|
UI = 400
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RenderCommand {
|
||||||
|
int layer;
|
||||||
|
std::function<void()> command;
|
||||||
|
|
||||||
|
bool operator<(const RenderCommand& other) const {
|
||||||
|
return layer < other.layer;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<RenderCommand> commands;
|
||||||
|
Camera3D* activeCamera;
|
||||||
|
bool is3DMode;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RenderContext() : activeCamera(nullptr), is3DMode(false) {}
|
||||||
|
|
||||||
|
void begin3D(Camera3D& camera) {
|
||||||
|
activeCamera = &camera;
|
||||||
|
is3DMode = true;
|
||||||
|
BeginMode3D(camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
void end3D() {
|
||||||
|
executeCommands();
|
||||||
|
EndMode3D();
|
||||||
|
is3DMode = false;
|
||||||
|
activeCamera = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void submitModel(const Model& model, const Vector3& position,
|
||||||
|
float scale = 1.0f, Color tint = WHITE,
|
||||||
|
int layer = RenderLayer::ENTITIES) {
|
||||||
|
commands.push_back({layer, [model, position, scale, tint]() {
|
||||||
|
DrawModel(model, position, scale, tint);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void submitModelEx(const Model& model, const Vector3& position,
|
||||||
|
const Vector3& rotation, const Vector3& scale,
|
||||||
|
Color tint = WHITE, int layer = RenderLayer::ENTITIES) {
|
||||||
|
commands.push_back({layer, [model, position, rotation, scale, tint]() {
|
||||||
|
DrawModelEx(model, position, rotation, 0.0f, scale, tint);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void submitCube(const Vector3& position, const Vector3& size,
|
||||||
|
Color color, int layer = RenderLayer::ENTITIES) {
|
||||||
|
commands.push_back({layer, [position, size, color]() {
|
||||||
|
DrawCube(position, size.x, size.y, size.z, color);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void submitCubeWires(const Vector3& position, const Vector3& size,
|
||||||
|
Color color, int layer = RenderLayer::ENTITIES) {
|
||||||
|
commands.push_back({layer, [position, size, color]() {
|
||||||
|
DrawCubeWires(position, size.x, size.y, size.z, color);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void submitLine3D(const Vector3& start, const Vector3& end,
|
||||||
|
Color color, int layer = RenderLayer::ENTITIES) {
|
||||||
|
commands.push_back({layer, [start, end, color]() {
|
||||||
|
DrawLine3D(start, end, color);
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
|
void submitCustom(std::function<void()> renderFunc, int layer) {
|
||||||
|
commands.push_back({layer, renderFunc});
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera3D* getActiveCamera() { return activeCamera; }
|
||||||
|
bool isIn3DMode() const { return is3DMode; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void executeCommands() {
|
||||||
|
std::sort(commands.begin(), commands.end());
|
||||||
|
|
||||||
|
for (const auto& cmd : commands) {
|
||||||
|
cmd.command();
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
63
client/utils/Coords.hpp
Normal file
63
client/utils/Coords.hpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <raylib.h>
|
||||||
|
#include <raymath.h>
|
||||||
|
|
||||||
|
class Coords {
|
||||||
|
public:
|
||||||
|
static constexpr float WORLD_BOUNDS = 50.0f;
|
||||||
|
static constexpr float WORLD_MIN_HEIGHT = 0.0f;
|
||||||
|
static constexpr float WORLD_MAX_HEIGHT = 100.0f;
|
||||||
|
|
||||||
|
static Vector3 worldToLocal(const Vector3& worldPos, const Vector3& origin) {
|
||||||
|
return Vector3Subtract(worldPos, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector3 localToWorld(const Vector3& localPos, const Vector3& origin) {
|
||||||
|
return Vector3Add(localPos, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector3 clampToWorldBounds(const Vector3& pos) {
|
||||||
|
return {
|
||||||
|
Clamp(pos.x, -WORLD_BOUNDS, WORLD_BOUNDS),
|
||||||
|
Clamp(pos.y, WORLD_MIN_HEIGHT, WORLD_MAX_HEIGHT),
|
||||||
|
Clamp(pos.z, -WORLD_BOUNDS, WORLD_BOUNDS)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isInWorldBounds(const Vector3& pos) {
|
||||||
|
return pos.x >= -WORLD_BOUNDS && pos.x <= WORLD_BOUNDS &&
|
||||||
|
pos.y >= WORLD_MIN_HEIGHT && pos.y <= WORLD_MAX_HEIGHT &&
|
||||||
|
pos.z >= -WORLD_BOUNDS && pos.z <= WORLD_BOUNDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Matrix buildTransformMatrix(const Vector3& position, const Vector3& rotation, const Vector3& scale) {
|
||||||
|
Matrix matScale = MatrixScale(scale.x, scale.y, scale.z);
|
||||||
|
Matrix matRotation = MatrixRotateXYZ(rotation);
|
||||||
|
Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z);
|
||||||
|
|
||||||
|
Matrix result = MatrixMultiply(matScale, matRotation);
|
||||||
|
result = MatrixMultiply(result, matTranslation);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Vector3 forward() { return {0.0f, 0.0f, 1.0f}; }
|
||||||
|
static Vector3 backward() { return {0.0f, 0.0f, -1.0f}; }
|
||||||
|
static Vector3 right() { return {1.0f, 0.0f, 0.0f}; }
|
||||||
|
static Vector3 left() { return {-1.0f, 0.0f, 0.0f}; }
|
||||||
|
static Vector3 up() { return {0.0f, 1.0f, 0.0f}; }
|
||||||
|
static Vector3 down() { return {0.0f, -1.0f, 0.0f}; }
|
||||||
|
|
||||||
|
static void drawDebugAxes(const Vector3& position, float size = 1.0f) {
|
||||||
|
DrawLine3D(position, Vector3Add(position, Vector3Scale(right(), size)), RED);
|
||||||
|
DrawLine3D(position, Vector3Add(position, Vector3Scale(up(), size)), GREEN);
|
||||||
|
DrawLine3D(position, Vector3Add(position, Vector3Scale(forward(), size)), BLUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drawWorldBounds() {
|
||||||
|
Color boundsColor = {255, 255, 0, 100};
|
||||||
|
DrawCubeWires({0, WORLD_MAX_HEIGHT/2, 0},
|
||||||
|
WORLD_BOUNDS * 2, WORLD_MAX_HEIGHT, WORLD_BOUNDS * 2,
|
||||||
|
boundsColor);
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user