Compare commits
9 Commits
45025b6682
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d365763209 | |||
| fbe079559a | |||
| 335f32fcea | |||
| a65e3d4f24 | |||
| ac982a2b21 | |||
| e1337c2560 | |||
| 170d953fe2 | |||
| 0060c2a00a | |||
| b3e026af24 |
@@ -0,0 +1,105 @@
|
|||||||
|
#ifndef ANIMATOR_CLASS
|
||||||
|
#define ANIMATOR_CLASS
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <assimp/scene.h>
|
||||||
|
#include <assimp/Importer.hpp>
|
||||||
|
#include "Rig.h"
|
||||||
|
|
||||||
|
// Scale of the "Head" bone (and what hangs off it), around the neck. 1 = as authored.
|
||||||
|
inline float HeadScale = 1.0f;
|
||||||
|
|
||||||
|
struct AssimpNodeData
|
||||||
|
{
|
||||||
|
glm::mat4 transformation;
|
||||||
|
std::string name;
|
||||||
|
int childrenCount;
|
||||||
|
std::vector<AssimpNodeData> children;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Animation
|
||||||
|
// ============================================================================
|
||||||
|
class Animation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Animation() = default;
|
||||||
|
|
||||||
|
Animation(const std::string& animationPath, Rig* rig = nullptr)
|
||||||
|
{
|
||||||
|
Assimp::Importer importer;
|
||||||
|
const aiScene* scene = importer.ReadFile(animationPath, aiProcess_Triangulate);
|
||||||
|
assert(scene && scene->mRootNode);
|
||||||
|
|
||||||
|
const aiAnimation* anim = scene->mAnimations[0];
|
||||||
|
m_Duration = (float)anim->mDuration;
|
||||||
|
m_TicksPerSecond = (int)(anim->mTicksPerSecond != 0 ? anim->mTicksPerSecond : 24);
|
||||||
|
|
||||||
|
ReadHierarchyData(m_RootNode, scene->mRootNode);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < anim->mNumChannels; i++) {
|
||||||
|
const aiNodeAnim* channel = anim->mChannels[i];
|
||||||
|
m_Bones.emplace_back(channel->mNodeName.data, 0, channel);
|
||||||
|
m_BoneNames.push_back(channel->mNodeName.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rig) RegisterBonesWithRig(*rig);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterBonesWithRig(Rig& rig)
|
||||||
|
{
|
||||||
|
for (const std::string& name : m_BoneNames) {
|
||||||
|
if (rig.m_BoneInfoMap.find(name) == rig.m_BoneInfoMap.end()) {
|
||||||
|
BoneInfo info;
|
||||||
|
info.id = rig.m_BoneCounter++;
|
||||||
|
info.offset = glm::mat4(1.0f);
|
||||||
|
rig.m_BoneInfoMap[name] = info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bone* FindBone(const std::string& name)
|
||||||
|
{
|
||||||
|
for (Bone& b : m_Bones)
|
||||||
|
if (b.GetBoneName() == name) return &b;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetTicksPerSecond() const { return (float)m_TicksPerSecond; }
|
||||||
|
float GetDuration() const { return m_Duration; }
|
||||||
|
const AssimpNodeData& GetRootNode() const { return m_RootNode; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
float m_Duration = 0.0f;
|
||||||
|
int m_TicksPerSecond = 24;
|
||||||
|
std::vector<Bone> m_Bones;
|
||||||
|
std::vector<std::string> m_BoneNames;
|
||||||
|
AssimpNodeData m_RootNode;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ReadHierarchyData(AssimpNodeData& dest, const aiNode* src)
|
||||||
|
{
|
||||||
|
assert(src);
|
||||||
|
dest.name = src->mName.data;
|
||||||
|
dest.transformation = AssimpGLMHelpers::ConvertMatrixToGLMFormat(src->mTransformation);
|
||||||
|
dest.childrenCount = (int)src->mNumChildren;
|
||||||
|
for (unsigned int i = 0; i < src->mNumChildren; i++) {
|
||||||
|
AssimpNodeData child;
|
||||||
|
ReadHierarchyData(child, src->mChildren[i]);
|
||||||
|
dest.children.push_back(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Animator
|
||||||
|
// blendWeight ramps from 0?1 over blendDuration when PlayAnimation is called.
|
||||||
|
// CharacterBody::GetBlendedBoneMatrices() lerps all animators by blendWeight.
|
||||||
|
// ============================================================================
|
||||||
|
class Animator
|
||||||
|
{
|
||||||
|
public:
|
||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
#include "Rig.h"
|
#include "Rig.h"
|
||||||
|
|
||||||
// Scale of the "Head" bone (and what hangs off it), around the neck. 1 = as authored.
|
// Scale of the "Head" bone (and what hangs off it), around the neck. 1 = as authored.
|
||||||
inline float HeadScale = 0.25f;
|
inline float HeadScale = 1.0f;
|
||||||
|
|
||||||
struct AssimpNodeData
|
struct AssimpNodeData
|
||||||
{
|
{
|
||||||
|
|||||||
+5
-1
@@ -58,5 +58,9 @@ void Character::Render(Shader& shader) {
|
|||||||
for (int i = 0; i < transforms.size(); i++)
|
for (int i = 0; i < transforms.size(); i++)
|
||||||
shader.SetMat4(("finalBonesMatrices[" + std::to_string(i) + "]").c_str(), transforms[i]);
|
shader.SetMat4(("finalBonesMatrices[" + std::to_string(i) + "]").c_str(), transforms[i]);
|
||||||
}
|
}
|
||||||
body->rig->Render(shader, body->t.GetTranslationMatrix() * body->t.GetRotationMatrix());
|
shader.Set4F("colorOverride", colorOverride);
|
||||||
|
// body->t's scale is the hitbox only; the rig is drawn at visualScale so it fills that hitbox
|
||||||
|
body->rig->Render(shader, body->t.GetTranslationMatrix() * body->t.GetRotationMatrix()
|
||||||
|
* glm::scale(glm::mat4(1.0f), glm::vec3(visualScale)));
|
||||||
|
shader.Set4F("colorOverride", glm::vec4(0.0f)); // don't leak into other rig draws
|
||||||
}
|
}
|
||||||
@@ -116,6 +116,8 @@ class Character : public Object, public Renderable {
|
|||||||
public:
|
public:
|
||||||
CharacterMind* mind = nullptr;
|
CharacterMind* mind = nullptr;
|
||||||
CharacterBody* body = nullptr;
|
CharacterBody* body = nullptr;
|
||||||
|
glm::vec4 colorOverride = glm::vec4(0.0f); // rgb + strength (0 = keep the rig's own texture)
|
||||||
|
float visualScale = 1.4f; // rig is ~1.8 tall natively; hitbox is 2.5
|
||||||
|
|
||||||
Character();
|
Character();
|
||||||
void Render(Shader& shader) override;
|
void Render(Shader& shader) override;
|
||||||
|
|||||||
+119
-13
@@ -130,6 +130,7 @@ void Engine::Initiate()
|
|||||||
Texture* blanktex = new Texture("assets/blanktexture.png");
|
Texture* blanktex = new Texture("assets/blanktexture.png");
|
||||||
Texture* magic = new Texture("assets/itsmagicbitch.jpg");
|
Texture* magic = new Texture("assets/itsmagicbitch.jpg");
|
||||||
Texture* snowflake = new Texture("assets/snowflake.png");
|
Texture* snowflake = new Texture("assets/snowflake.png");
|
||||||
|
Texture* shockwavetex = new Texture("assets/shockwave.png");
|
||||||
Texture* leontex = new Texture("assets/revolvertex.png");
|
Texture* leontex = new Texture("assets/revolvertex.png");
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@@ -191,11 +192,15 @@ void Engine::Initiate()
|
|||||||
struct RemotePlayer {
|
struct RemotePlayer {
|
||||||
Character* character = nullptr;
|
Character* character = nullptr;
|
||||||
double lastSeen = 0.0;
|
double lastSeen = 0.0;
|
||||||
|
p* body = nullptr; // host only: lets the host's cubes collide with this player
|
||||||
|
glm::vec3 targetPos = glm::vec3(0.0f); // latest pose received; the model eases toward it every frame
|
||||||
|
glm::quat targetRot = glm::quat(1.0f, 0.0f, 0.0f, 0.0f);
|
||||||
};
|
};
|
||||||
std::map<int, RemotePlayer> remotes;
|
std::map<int, RemotePlayer> remotes;
|
||||||
auto SpawnRemote = [&]() -> Character* {
|
auto SpawnRemote = [&]() -> Character* {
|
||||||
Character* c = new Character();
|
Character* c = new Character();
|
||||||
c->name = "remote";
|
c->name = "remote";
|
||||||
|
c->colorOverride = glm::vec4(0.02f, 0.08f, 0.5f, 1.0f); // dark blue
|
||||||
c->castshadow = true;
|
c->castshadow = true;
|
||||||
c->body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
c->body->t.ScaleBy({ 0.7f, 2.5f, 0.7f });
|
||||||
c->body->SetRig(testrig);
|
c->body->SetRig(testrig);
|
||||||
@@ -328,6 +333,23 @@ void Engine::Initiate()
|
|||||||
grappleText->t2d.size = { 0.0f, 0.0f, 560.0f, 40.0f };
|
grappleText->t2d.size = { 0.0f, 0.0f, 560.0f, 40.0f };
|
||||||
grappleText->BindToRenderSystem();
|
grappleText->BindToRenderSystem();
|
||||||
|
|
||||||
|
// Shot cooldown bar at the bottom of the screen: full and white when ready, empties on a shot
|
||||||
|
// and refills as the cooldown runs down.
|
||||||
|
const float shotBarWidth = 300.0f, shotBarHeight = 8.0f;
|
||||||
|
Box* shotBarBack = new Box();
|
||||||
|
shotBarBack->Color = { 0.0f, 0.0f, 0.0f };
|
||||||
|
shotBarBack->Opacity = 0.45f;
|
||||||
|
shotBarBack->t2d.position = { 0.5f, 1.0f, 0.0f, -30.0f };
|
||||||
|
shotBarBack->t2d.center = { 0.5f, 1.0f };
|
||||||
|
shotBarBack->t2d.size = { 0.0f, 0.0f, shotBarWidth + 4.0f, shotBarHeight + 4.0f };
|
||||||
|
shotBarBack->BindToRenderSystem();
|
||||||
|
Box* shotBar = new Box();
|
||||||
|
shotBar->Color = { 1.0f, 1.0f, 1.0f };
|
||||||
|
shotBar->t2d.position = { 0.5f, 1.0f, -shotBarWidth * 0.5f, -32.0f };
|
||||||
|
shotBar->t2d.center = { 0.0f, 1.0f };
|
||||||
|
shotBar->t2d.size = { 0.0f, 0.0f, shotBarWidth, shotBarHeight };
|
||||||
|
shotBar->BindToRenderSystem();
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// World meshes
|
// World meshes
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
@@ -384,16 +406,17 @@ void Engine::Initiate()
|
|||||||
pos.z - size.z * 0.5f, pos.z + size.z * 0.5f });
|
pos.z - size.z * 0.5f, pos.z + size.z * 0.5f });
|
||||||
};
|
};
|
||||||
// Concentric stack of square tiers, widest at the bottom (stands in for dome / cone)
|
// Concentric stack of square tiers, widest at the bottom (stands in for dome / cone)
|
||||||
auto AddTiers = [&](const char* name, glm::vec2 pos, float baseWidth, float tierHeight, int tiers) {
|
// xStretch scales the stack along x only (left/right); height and depth are unchanged
|
||||||
|
auto AddTiers = [&](const char* name, glm::vec2 pos, float baseWidth, float tierHeight, int tiers, float xStretch = 1.0f) {
|
||||||
for (int i = 0; i < tiers; ++i) {
|
for (int i = 0; i < tiers; ++i) {
|
||||||
const float w = baseWidth * (1.0f - float(i) / float(tiers));
|
const float w = baseWidth * (1.0f - float(i) / float(tiers));
|
||||||
AddBlock(name, { pos.x, tierHeight * i, pos.y }, { w, tierHeight, w });
|
AddBlock(name, { pos.x, tierHeight * i, pos.y }, { w * xStretch, tierHeight, w });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map is a rectangle: x in [-60, 60], z in [-30, 30] (authored at 1x, scaled by mapScale)
|
// Map is a rectangle: x in [-60, 60], z in [-30, 30] (authored at 1x, scaled by mapScale)
|
||||||
// Centre dome
|
// Centre dome
|
||||||
AddTiers("dome", { 0.0f, 0.0f }, 18.0f, 2.0f, 5);
|
AddTiers("dome", { 0.0f, 0.0f }, 18.0f, 2.0f, 5, 0.5f);
|
||||||
// Cones, opposite corners of the dome
|
// Cones, opposite corners of the dome
|
||||||
AddTiers("cone", { -12.0f, -13.0f }, 6.0f, 1.0f, 5);
|
AddTiers("cone", { -12.0f, -13.0f }, 6.0f, 1.0f, 5);
|
||||||
AddTiers("cone", { 12.0f, 13.0f }, 6.0f, 1.0f, 5);
|
AddTiers("cone", { 12.0f, 13.0f }, 6.0f, 1.0f, 5);
|
||||||
@@ -442,6 +465,18 @@ void Engine::Initiate()
|
|||||||
float shockedTime = 0.0f;
|
float shockedTime = 0.0f;
|
||||||
const float grappleRange = 30.0f; // metres from the player
|
const float grappleRange = 30.0f; // metres from the player
|
||||||
const float grappleSpeed = 28.0f; // launch speed toward the hit point
|
const float grappleSpeed = 28.0f; // launch speed toward the hit point
|
||||||
|
// Visible grapple rope: a thin black box stretched from the hand to the latch point
|
||||||
|
const float grappleLineShowTime = 0.5f;
|
||||||
|
float grappleLineTime = 0.0f;
|
||||||
|
glm::vec3 grappleLineEnd(0.0f);
|
||||||
|
Mesh* grappleLine = CreateCubeMesh();
|
||||||
|
grappleLine->name = "grappleline";
|
||||||
|
grappleLine->meshTexture = blanktex;
|
||||||
|
grappleLine->colorOverride = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); // black
|
||||||
|
grappleLine->t.TranslateTo({ 0.0f, -1000.0f, 0.0f }); // hidden until a grapple latches
|
||||||
|
grappleLine->BindToRenderSystem();
|
||||||
|
mainf->AddChild(grappleLine);
|
||||||
|
const float grappleLift = 10.0f; // extra upward speed added on top of the pull
|
||||||
const float grappleCooldownTime = 2.5f; // seconds
|
const float grappleCooldownTime = 2.5f; // seconds
|
||||||
float grappleCooldown = 0.0f;
|
float grappleCooldown = 0.0f;
|
||||||
|
|
||||||
@@ -465,7 +500,7 @@ void Engine::Initiate()
|
|||||||
|
|
||||||
// Cubes (E). Whoever hosts (or an offline player) owns and simulates them and decides when they
|
// Cubes (E). Whoever hosts (or an offline player) owns and simulates them and decides when they
|
||||||
// despawn; LAN clients only ask the host for one and draw the poses it streams back.
|
// despawn; LAN clients only ask the host for one and draw the poses it streams back.
|
||||||
const float cubeCooldownTime = 0.5f; // seconds between cubes per player
|
const float cubeCooldownTime = 0.1f; // seconds between cubes per player
|
||||||
const double cubeLifetime = 30.0; // seconds before a cube despawns
|
const double cubeLifetime = 30.0; // seconds before a cube despawns
|
||||||
float cubeCooldown = 0.0f;
|
float cubeCooldown = 0.0f;
|
||||||
// Cubes also rain from the sky on their own (host / offline only; clients see them via the stream)
|
// Cubes also rain from the sky on their own (host / offline only; clients see them via the stream)
|
||||||
@@ -517,7 +552,7 @@ void Engine::Initiate()
|
|||||||
|
|
||||||
Mesh* blastMesh = CreateCubeMesh();
|
Mesh* blastMesh = CreateCubeMesh();
|
||||||
blastMesh->name = "shockblast";
|
blastMesh->name = "shockblast";
|
||||||
blastMesh->meshTexture = blanktex;
|
blastMesh->meshTexture = shockwavetex;
|
||||||
blastMesh->t.TranslateTo(hiddenPos);
|
blastMesh->t.TranslateTo(hiddenPos);
|
||||||
blastMesh->BindToRenderSystem();
|
blastMesh->BindToRenderSystem();
|
||||||
mainf->AddChild(blastMesh);
|
mainf->AddChild(blastMesh);
|
||||||
@@ -566,6 +601,15 @@ void Engine::Initiate()
|
|||||||
shockActive = false;
|
shockActive = false;
|
||||||
shockMesh->t.TranslateTo(hiddenPos);
|
shockMesh->t.TranslateTo(hiddenPos);
|
||||||
}
|
}
|
||||||
|
else if (lan && lan->IsActive()) {
|
||||||
|
// Another player's blast: launch the cubes (host: the real ones; client: its local copies)
|
||||||
|
for (p* obj : physicsengine->GetObjects()) {
|
||||||
|
bool isCube = false;
|
||||||
|
for (const HostCube& hc : hostCubes) if (obj->pointer == hc.mesh) isCube = true;
|
||||||
|
for (auto& kv : puppetCubes) if (obj->pointer == kv.second.mesh) isCube = true;
|
||||||
|
if (isCube) launch(obj->pointer->t.GetTranslation(), obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
blastMesh->t.TranslateTo(center);
|
blastMesh->t.TranslateTo(center);
|
||||||
blastMesh->t.ScaleTo(glm::vec3(0.5f));
|
blastMesh->t.ScaleTo(glm::vec3(0.5f));
|
||||||
blastTime = blastShowTime;
|
blastTime = blastShowTime;
|
||||||
@@ -575,7 +619,7 @@ void Engine::Initiate()
|
|||||||
const int maxHealth = 100;
|
const int maxHealth = 100;
|
||||||
const int bodyDamage = 50;
|
const int bodyDamage = 50;
|
||||||
const int punchDamage = 25;
|
const int punchDamage = 25;
|
||||||
const float headFraction = 0.8f;
|
const float headFraction = 0.8f; // top 20% of the hitbox counts as the head
|
||||||
|
|
||||||
// Random respawn spot on the floor (NPC and player), kept clear of every map prop
|
// Random respawn spot on the floor (NPC and player), kept clear of every map prop
|
||||||
std::mt19937 respawnRng(std::random_device{}());
|
std::mt19937 respawnRng(std::random_device{}());
|
||||||
@@ -804,8 +848,12 @@ void Engine::Initiate()
|
|||||||
}
|
}
|
||||||
if (found) {
|
if (found) {
|
||||||
characterBody->linearvelocity = glm::normalize(bestPoint - ray.origin) * grappleSpeed;
|
characterBody->linearvelocity = glm::normalize(bestPoint - ray.origin) * grappleSpeed;
|
||||||
|
// Extra upward fling so the grapple gains height instead of just pulling sideways
|
||||||
|
characterBody->linearvelocity.y = std::max(characterBody->linearvelocity.y, 0.0f) + grappleLift;
|
||||||
characterBody->WakeUp();
|
characterBody->WakeUp();
|
||||||
grappleCooldown = grappleCooldownTime;
|
grappleCooldown = grappleCooldownTime;
|
||||||
|
grappleLineTime = grappleLineShowTime;
|
||||||
|
grappleLineEnd = bestPoint;
|
||||||
camera->AddScreenshake(0.1f);
|
camera->AddScreenshake(0.1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -933,7 +981,6 @@ void Engine::Initiate()
|
|||||||
}
|
}
|
||||||
|
|
||||||
rain->t.TranslateTo(camera->t.GetTranslation() + glm::vec3(0.0f, 20.0f, 0.0f));
|
rain->t.TranslateTo(camera->t.GetTranslation() + glm::vec3(0.0f, 20.0f, 0.0f));
|
||||||
for (int i = 0; i < 30; i++) rain->Emit();
|
|
||||||
|
|
||||||
if (!sliding) {
|
if (!sliding) {
|
||||||
testcharacter->body->t.RotateToQuaternion(
|
testcharacter->body->t.RotateToQuaternion(
|
||||||
@@ -972,6 +1019,26 @@ void Engine::Initiate()
|
|||||||
}
|
}
|
||||||
fpsCounter->text = std::to_string(measuredFps) + " FPS";
|
fpsCounter->text = std::to_string(measuredFps) + " FPS";
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
const float ready = 1.0f - glm::clamp(shootCooldown / shootCooldownTime, 0.0f, 1.0f);
|
||||||
|
shotBar->t2d.size = { 0.0f, 0.0f, shotBarWidth * ready, shotBarHeight };
|
||||||
|
shotBar->Opacity = ready > 0.0f ? 1.0f : 0.0f;
|
||||||
|
}
|
||||||
|
// Grapple rope: starts at the hand (follows the camera), ends on the latch point
|
||||||
|
grappleLineTime = std::max(grappleLineTime - deltatime, 0.0f);
|
||||||
|
if (grappleLineTime > 0.0f) {
|
||||||
|
const glm::vec3 handPos = camera->t.GetTranslation() + camera->t.GetFrontVector() * 0.6f
|
||||||
|
+ camera->t.GetRightVector() * 0.3f - camera->t.GetUpVector() * 0.3f;
|
||||||
|
const glm::vec3 delta = grappleLineEnd - handPos;
|
||||||
|
const float len = glm::length(delta);
|
||||||
|
if (len > 0.01f) {
|
||||||
|
grappleLine->t.TranslateTo(handPos + delta * 0.5f);
|
||||||
|
grappleLine->t.ScaleTo({ 0.05f, 0.05f, len });
|
||||||
|
// Plain shortest-arc rotation of the box's long (z) axis onto the rope; LookAt breaks on straight up/down
|
||||||
|
grappleLine->t.RotateToQuaternion(RotationBetweenVectors(glm::vec3(0.0f, 0.0f, 1.0f), delta));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else grappleLine->t.TranslateTo(hiddenPos);
|
||||||
hitMarkerTime = std::max(hitMarkerTime - deltatime, 0.0f);
|
hitMarkerTime = std::max(hitMarkerTime - deltatime, 0.0f);
|
||||||
for (Box* b : hitMarker) b->Opacity = hitMarkerTime / hitMarkerDuration;
|
for (Box* b : hitMarker) b->Opacity = hitMarkerTime / hitMarkerDuration;
|
||||||
// Damage flash: any HP loss restarts a short green fade-out
|
// Damage flash: any HP loss restarts a short green fade-out
|
||||||
@@ -1089,15 +1156,31 @@ void Engine::Initiate()
|
|||||||
m->t.ScaleTo(1.0f);
|
m->t.ScaleTo(1.0f);
|
||||||
m->BindToRenderSystem();
|
m->BindToRenderSystem();
|
||||||
mainf->AddChild(m);
|
mainf->AddChild(m);
|
||||||
|
// Simulated locally exactly like the host's cubes (so the local player pushes them with
|
||||||
|
// no lag); the host's stream below only corrects drift.
|
||||||
|
physicsengine->AddObject(new p(m));
|
||||||
|
m->t.TranslateTo(nc.position);
|
||||||
|
m->t.RotateToQuaternion(nc.rotation);
|
||||||
it = puppetCubes.emplace(nc.id, PuppetCube{ m, now }).first;
|
it = puppetCubes.emplace(nc.id, PuppetCube{ m, now }).first;
|
||||||
}
|
}
|
||||||
it->second.mesh->t.TranslateTo(nc.position);
|
else {
|
||||||
it->second.mesh->t.RotateToQuaternion(nc.rotation);
|
t& ct = it->second.mesh->t;
|
||||||
|
const glm::vec3 err = nc.position - ct.GetTranslation();
|
||||||
|
if (glm::length(err) > 3.0f) {
|
||||||
|
ct.TranslateTo(nc.position);
|
||||||
|
ct.RotateToQuaternion(nc.rotation);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ct.TranslateBy(err * 0.35f);
|
||||||
|
ct.RotateToQuaternion(glm::slerp(ct.GetRotationQuaternion(), nc.rotation, 0.35f));
|
||||||
|
}
|
||||||
|
}
|
||||||
it->second.lastSeen = now;
|
it->second.lastSeen = now;
|
||||||
}
|
}
|
||||||
for (auto it = puppetCubes.begin(); it != puppetCubes.end();) {
|
for (auto it = puppetCubes.begin(); it != puppetCubes.end();) {
|
||||||
if (now - it->second.lastSeen > 1.5) {
|
if (now - it->second.lastSeen > 1.5) {
|
||||||
rendersystem->RemoveRenderable(it->second.mesh);
|
rendersystem->RemoveRenderable(it->second.mesh);
|
||||||
|
physicsengine->RemoveObject(it->second.mesh);
|
||||||
it->second.mesh->Delete();
|
it->second.mesh->Delete();
|
||||||
it = puppetCubes.erase(it);
|
it = puppetCubes.erase(it);
|
||||||
}
|
}
|
||||||
@@ -1122,13 +1205,21 @@ void Engine::Initiate()
|
|||||||
rp.character = SpawnRemote();
|
rp.character = SpawnRemote();
|
||||||
rp.character->body->t.TranslateTo(u.state.position);
|
rp.character->body->t.TranslateTo(u.state.position);
|
||||||
rp.character->body->t.RotateToQuaternion(u.state.rotation);
|
rp.character->body->t.RotateToQuaternion(u.state.rotation);
|
||||||
|
rp.targetPos = u.state.position;
|
||||||
|
rp.targetRot = u.state.rotation;
|
||||||
|
if (lan->GetMode() == Lan::Mode::Host) {
|
||||||
|
// Solid for the host's cubes (kinematic: moved by the client's stream only)
|
||||||
|
rp.body = new p(rp.character->body);
|
||||||
|
rp.body->velocity = false;
|
||||||
|
physicsengine->AddObject(rp.body);
|
||||||
|
}
|
||||||
it = remotes.emplace(u.id, rp).first;
|
it = remotes.emplace(u.id, rp).first;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Smooth over the 60 Hz updates
|
// Packets arrive in bursts (several in one frame, then none), so just remember the
|
||||||
t& rt = it->second.character->body->t;
|
// newest pose here; the per-frame smoothing below does the actual moving.
|
||||||
rt.TranslateTo(glm::mix(rt.GetTranslation(), u.state.position, 0.5f));
|
it->second.targetPos = u.state.position;
|
||||||
rt.RotateToQuaternion(glm::slerp(rt.GetRotationQuaternion(), u.state.rotation, 0.5f));
|
it->second.targetRot = u.state.rotation;
|
||||||
}
|
}
|
||||||
it->second.lastSeen = now;
|
it->second.lastSeen = now;
|
||||||
}
|
}
|
||||||
@@ -1137,10 +1228,25 @@ void Engine::Initiate()
|
|||||||
for (auto it = remotes.begin(); it != remotes.end();) {
|
for (auto it = remotes.begin(); it != remotes.end();) {
|
||||||
if (now - it->second.lastSeen > 2.0) {
|
if (now - it->second.lastSeen > 2.0) {
|
||||||
rendersystem->RemoveRenderable(it->second.character);
|
rendersystem->RemoveRenderable(it->second.character);
|
||||||
|
if (it->second.body) physicsengine->RemoveObject(it->second.character->body);
|
||||||
delete it->second.character;
|
delete it->second.character;
|
||||||
it = remotes.erase(it);
|
it = remotes.erase(it);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// Frame-rate independent ease toward the latest pose (no matter how packets were batched)
|
||||||
|
t& rt = it->second.character->body->t;
|
||||||
|
const glm::vec3 before = rt.GetTranslation();
|
||||||
|
if (glm::distance(before, it->second.targetPos) > 6.0f)
|
||||||
|
rt.TranslateTo(it->second.targetPos); // respawn / big jump: snap
|
||||||
|
else {
|
||||||
|
const float k = 1.0f - std::exp(-deltatime * 25.0f);
|
||||||
|
rt.TranslateTo(glm::mix(before, it->second.targetPos, k));
|
||||||
|
}
|
||||||
|
rt.RotateToQuaternion(glm::slerp(rt.GetRotationQuaternion(), it->second.targetRot,
|
||||||
|
1.0f - std::exp(-deltatime * 25.0f)));
|
||||||
|
// Kinematic body: its velocity makes cubes get pushed like the client's own body does there
|
||||||
|
if (it->second.body && deltatime > 0.0f)
|
||||||
|
it->second.body->linearvelocity = (rt.GetTranslation() - before) / deltatime;
|
||||||
it->second.character->Step(deltatime);
|
it->second.character->Step(deltatime);
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class Mesh : public Object , public Renderable, public t_package , public MeshDa
|
|||||||
protected:
|
protected:
|
||||||
public:
|
public:
|
||||||
Texture* meshTexture = nullptr;
|
Texture* meshTexture = nullptr;
|
||||||
|
glm::vec4 colorOverride = glm::vec4(0.0f); // rgb + strength; 0 = use the texture's own colour
|
||||||
|
|
||||||
VAO vao;
|
VAO vao;
|
||||||
VBO vbo;
|
VBO vbo;
|
||||||
@@ -469,6 +470,7 @@ public:
|
|||||||
else glBindTexture(GL_TEXTURE_2D, 0);
|
else glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
ShaderProgram.SetMat4("modl", topass);
|
ShaderProgram.SetMat4("modl", topass);
|
||||||
|
ShaderProgram.Set4F("colorOverride", colorOverride);
|
||||||
vao.Bind();
|
vao.Bind();
|
||||||
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
|
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
|
||||||
vao.Unbind();
|
vao.Unbind();
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 686 B |
@@ -129,11 +129,9 @@ static void PromptSensitivity() {
|
|||||||
catch (...) {} // empty / not a number: keep the default
|
catch (...) {} // empty / not a number: keep the default
|
||||||
};
|
};
|
||||||
std::cout << "\n=== MOUSE ===\n";
|
std::cout << "\n=== MOUSE ===\n";
|
||||||
float cm = 0.0f, dpi = 800.0f;
|
float cm = 0.0f;
|
||||||
ask("Sensitivity in cm per 360 degrees [default]: ", cm);
|
ask("Sensitivity in cm per 360 degrees [default]: ", cm);
|
||||||
if (cm > 0.0f) ask("Mouse DPI [800]: ", dpi);
|
|
||||||
StartupCmPer360 = cm;
|
StartupCmPer360 = cm;
|
||||||
StartupMouseDpi = dpi;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ uniform vec3 viewPos;
|
|||||||
|
|
||||||
uniform sampler2DShadow shadowMap;
|
uniform sampler2DShadow shadowMap;
|
||||||
uniform sampler2D tex;
|
uniform sampler2D tex;
|
||||||
|
uniform vec4 colorOverride = vec4(0.0); // rgb replaces the texture colour when w > 0
|
||||||
|
|
||||||
float ShadowCalculation(vec4 fragposlightspace)
|
float ShadowCalculation(vec4 fragposlightspace)
|
||||||
{
|
{
|
||||||
@@ -79,6 +80,8 @@ void main()
|
|||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lightless.rgb = mix(lightless.rgb, colorOverride.rgb, colorOverride.w);
|
||||||
|
|
||||||
vec3 norm = normalize(normal);
|
vec3 norm = normalize(normal);
|
||||||
vec3 viewDir = normalize(viewPos - FragPos);
|
vec3 viewDir = normalize(viewPos - FragPos);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user