mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-27 01:00:13 +00:00
Add loading screen, fix tree/foliage collision, jump buffering, and fence rotation
- Loading screen stays visible until all terrain tiles finish streaming; character spawns only after terrain is loaded and Z-snapped to ground - Reduce tree trunk collision bounds (5% of canopy, capped at 5.0) and make all small/medium trees, bushes, lily pads, and foliage walkthrough - Add jump input buffering (150ms) and coyote time (100ms) for responsive jumps - Fix fence orientation by adding +180° heading rotation - Increase terrain load radius from 1 to 2 (5x5 tile grid) - Add hearthstone callback for single-player camera reset
This commit is contained in:
parent
f7cd871895
commit
6ca9e9024a
9 changed files with 188 additions and 222 deletions
|
|
@ -154,8 +154,8 @@ void CameraController::update(float deltaTime) {
|
|||
glm::vec3 forward(std::cos(moveYawRad), std::sin(moveYawRad), 0.0f);
|
||||
glm::vec3 right(-std::sin(moveYawRad), std::cos(moveYawRad), 0.0f);
|
||||
|
||||
// Toggle sit/crouch with X or C key (edge-triggered) — only when UI doesn't want keyboard
|
||||
bool xDown = !uiWantsKeyboard && (input.isKeyPressed(SDL_SCANCODE_X) || input.isKeyPressed(SDL_SCANCODE_C));
|
||||
// Toggle sit/crouch with X key (edge-triggered) — only when UI doesn't want keyboard
|
||||
bool xDown = !uiWantsKeyboard && input.isKeyPressed(SDL_SCANCODE_X);
|
||||
if (xDown && !xKeyWasDown) {
|
||||
sitting = !sitting;
|
||||
}
|
||||
|
|
@ -190,37 +190,16 @@ void CameraController::update(float deltaTime) {
|
|||
m2Renderer->setCollisionFocus(targetPos, COLLISION_FOCUS_RADIUS_THIRD_PERSON);
|
||||
}
|
||||
|
||||
// Check for water at current position
|
||||
// Check for water at current position — simple submersion test.
|
||||
// If the player's feet are meaningfully below the water surface, swim.
|
||||
std::optional<float> waterH;
|
||||
if (waterRenderer) {
|
||||
waterH = waterRenderer->getWaterHeightAt(targetPos.x, targetPos.y);
|
||||
}
|
||||
constexpr float MAX_SWIM_DEPTH_FROM_SURFACE = 12.0f;
|
||||
bool inWater = false;
|
||||
if (waterH && targetPos.z < *waterH) {
|
||||
std::optional<uint16_t> waterType;
|
||||
if (waterRenderer) {
|
||||
waterType = waterRenderer->getWaterTypeAt(targetPos.x, targetPos.y);
|
||||
}
|
||||
bool isOcean = false;
|
||||
if (waterType && *waterType != 0) {
|
||||
isOcean = (((*waterType - 1) % 4) == 1);
|
||||
}
|
||||
bool depthAllowed = isOcean || ((*waterH - targetPos.z) <= MAX_SWIM_DEPTH_FROM_SURFACE);
|
||||
if (!depthAllowed) {
|
||||
inWater = false;
|
||||
} else {
|
||||
std::optional<float> terrainH;
|
||||
std::optional<float> wmoH;
|
||||
std::optional<float> m2H;
|
||||
if (terrainManager) terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y);
|
||||
if (wmoRenderer) wmoH = wmoRenderer->getFloorHeight(targetPos.x, targetPos.y, targetPos.z + 6.0f);
|
||||
if (m2Renderer) m2H = m2Renderer->getFloorHeight(targetPos.x, targetPos.y, targetPos.z + 1.0f);
|
||||
auto floorH = selectHighestFloor(terrainH, wmoH, m2H);
|
||||
constexpr float MIN_SWIM_WATER_DEPTH = 1.8f;
|
||||
// Ocean is valid even when ground isn't currently resolved (deep water or streaming gaps).
|
||||
inWater = (floorH && ((*waterH - *floorH) >= MIN_SWIM_WATER_DEPTH)) || (isOcean && !floorH);
|
||||
}
|
||||
bool inWater = waterH && (targetPos.z < (*waterH - 0.3f));
|
||||
// Keep swimming through water-data gaps (chunk boundaries).
|
||||
if (!inWater && swimming && !waterH) {
|
||||
inWater = true;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -298,7 +277,7 @@ void CameraController::update(float deltaTime) {
|
|||
if (mh && (!floorH || *mh > *floorH)) floorH = mh;
|
||||
}
|
||||
if (floorH) {
|
||||
float swimFloor = *floorH + 0.30f;
|
||||
float swimFloor = *floorH + 0.5f;
|
||||
if (targetPos.z < swimFloor) {
|
||||
targetPos.z = swimFloor;
|
||||
if (verticalVelocity < 0.0f) verticalVelocity = 0.0f;
|
||||
|
|
@ -343,6 +322,7 @@ void CameraController::update(float deltaTime) {
|
|||
|
||||
grounded = false;
|
||||
} else {
|
||||
// Exiting water — give a small upward boost to help climb onto shore.
|
||||
swimming = false;
|
||||
|
||||
if (glm::length(movement) > 0.001f) {
|
||||
|
|
@ -350,12 +330,21 @@ void CameraController::update(float deltaTime) {
|
|||
targetPos += movement * speed * deltaTime;
|
||||
}
|
||||
|
||||
// Jump
|
||||
if (nowJump && grounded) {
|
||||
// Jump with input buffering and coyote time
|
||||
if (nowJump) jumpBufferTimer = JUMP_BUFFER_TIME;
|
||||
if (grounded) coyoteTimer = COYOTE_TIME;
|
||||
|
||||
bool canJump = (coyoteTimer > 0.0f) && (jumpBufferTimer > 0.0f);
|
||||
if (canJump) {
|
||||
verticalVelocity = jumpVel;
|
||||
grounded = false;
|
||||
jumpBufferTimer = 0.0f;
|
||||
coyoteTimer = 0.0f;
|
||||
}
|
||||
|
||||
jumpBufferTimer -= deltaTime;
|
||||
coyoteTimer -= deltaTime;
|
||||
|
||||
// Apply gravity
|
||||
verticalVelocity += gravity * deltaTime;
|
||||
targetPos.z += verticalVelocity * deltaTime;
|
||||
|
|
@ -501,7 +490,8 @@ void CameraController::update(float deltaTime) {
|
|||
}
|
||||
|
||||
// Ground the character to terrain or WMO floor
|
||||
{
|
||||
// Skip entirely while swimming — the swim floor clamp handles vertical bounds.
|
||||
if (!swimming) {
|
||||
auto sampleGround = [&](float x, float y) -> std::optional<float> {
|
||||
std::optional<float> terrainH;
|
||||
std::optional<float> wmoH;
|
||||
|
|
@ -549,15 +539,14 @@ void CameraController::update(float deltaTime) {
|
|||
lastGroundZ = *groundH;
|
||||
}
|
||||
|
||||
if (targetPos.z <= lastGroundZ + 0.1f) {
|
||||
if (targetPos.z <= lastGroundZ + 0.1f && verticalVelocity <= 0.0f) {
|
||||
targetPos.z = lastGroundZ;
|
||||
verticalVelocity = 0.0f;
|
||||
grounded = true;
|
||||
swimming = false; // Touching ground = wading, not swimming
|
||||
} else if (!swimming) {
|
||||
} else {
|
||||
grounded = false;
|
||||
}
|
||||
} else if (!swimming) {
|
||||
} else {
|
||||
// No terrain found — hold at last known ground
|
||||
targetPos.z = lastGroundZ;
|
||||
verticalVelocity = 0.0f;
|
||||
|
|
@ -762,12 +751,20 @@ void CameraController::update(float deltaTime) {
|
|||
newPos += movement * speed * deltaTime;
|
||||
}
|
||||
|
||||
// Jump
|
||||
if (nowJump && grounded) {
|
||||
// Jump with input buffering and coyote time
|
||||
if (nowJump) jumpBufferTimer = JUMP_BUFFER_TIME;
|
||||
if (grounded) coyoteTimer = COYOTE_TIME;
|
||||
|
||||
if (coyoteTimer > 0.0f && jumpBufferTimer > 0.0f) {
|
||||
verticalVelocity = jumpVel;
|
||||
grounded = false;
|
||||
jumpBufferTimer = 0.0f;
|
||||
coyoteTimer = 0.0f;
|
||||
}
|
||||
|
||||
jumpBufferTimer -= deltaTime;
|
||||
coyoteTimer -= deltaTime;
|
||||
|
||||
// Apply gravity
|
||||
verticalVelocity += gravity * deltaTime;
|
||||
newPos.z += verticalVelocity * deltaTime;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,18 @@ void getTightCollisionBounds(const M2ModelGPU& model, glm::vec3& outMin, glm::ve
|
|||
// larger than default to prevent walk-through on narrow objects
|
||||
// - default: tighter fit (avoid oversized blockers)
|
||||
// - stepped low platforms (tree curbs/planters): wider XY + lower Z
|
||||
if (model.collisionNarrowVerticalProp) {
|
||||
if (model.collisionTreeTrunk) {
|
||||
// Tree trunk: proportional cylinder at the base of the tree.
|
||||
float modelHoriz = std::max(model.boundMax.x - model.boundMin.x,
|
||||
model.boundMax.y - model.boundMin.y);
|
||||
float trunkHalf = std::clamp(modelHoriz * 0.05f, 0.5f, 5.0f);
|
||||
half.x = trunkHalf;
|
||||
half.y = trunkHalf;
|
||||
// Height proportional to trunk width, capped at 3.5 units.
|
||||
half.z = std::min(trunkHalf * 2.5f, 3.5f);
|
||||
// Shift center down so collision is at the base (trunk), not mid-canopy.
|
||||
center.z = model.boundMin.z + half.z;
|
||||
} else if (model.collisionNarrowVerticalProp) {
|
||||
// Tall thin props (lamps/posts): keep passable gaps near walls.
|
||||
half.x *= 0.30f;
|
||||
half.y *= 0.30f;
|
||||
|
|
@ -396,19 +407,19 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
(lowerName.find("flower") != std::string::npos) ||
|
||||
(lowerName.find("shrub") != std::string::npos) ||
|
||||
(lowerName.find("fern") != std::string::npos) ||
|
||||
(lowerName.find("vine") != std::string::npos);
|
||||
bool canopyLike =
|
||||
(lowerName.find("canopy") != std::string::npos) ||
|
||||
(lowerName.find("leaf") != std::string::npos) ||
|
||||
(lowerName.find("leaves") != std::string::npos);
|
||||
(lowerName.find("vine") != std::string::npos) ||
|
||||
(lowerName.find("lily") != std::string::npos) ||
|
||||
(lowerName.find("weed") != std::string::npos);
|
||||
bool treeLike = (lowerName.find("tree") != std::string::npos);
|
||||
bool hardTreePart =
|
||||
(lowerName.find("trunk") != std::string::npos) ||
|
||||
(lowerName.find("stump") != std::string::npos) ||
|
||||
(lowerName.find("log") != std::string::npos);
|
||||
bool softTree = treeLike && !hardTreePart && (canopyLike || vert > horiz * 1.35f);
|
||||
bool smallSoftShape = (horiz < 2.2f && vert < 2.4f);
|
||||
bool mediumFoliageShape = (horiz < 4.5f && vert < 4.5f);
|
||||
// Only large trees (canopy > 20 model units wide) get trunk collision.
|
||||
// Small/mid trees are walkthrough to avoid getting stuck between them.
|
||||
// Only large trees get trunk collision; all smaller trees are walkthrough.
|
||||
bool treeWithTrunk = treeLike && !hardTreePart && !foliageName && horiz > 40.0f;
|
||||
bool softTree = treeLike && !hardTreePart && !treeWithTrunk;
|
||||
bool forceSolidCurb = gpuModel.collisionSteppedLowPlatform || knownStormwindPlanter || likelyCurbName || gpuModel.collisionPlanter;
|
||||
bool narrowVerticalName =
|
||||
(lowerName.find("lamp") != std::string::npos) ||
|
||||
|
|
@ -417,6 +428,7 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
(lowerName.find("pole") != std::string::npos);
|
||||
bool narrowVerticalShape =
|
||||
(horiz > 0.12f && horiz < 2.0f && vert > 2.2f && vert > horiz * 1.8f);
|
||||
gpuModel.collisionTreeTrunk = treeWithTrunk;
|
||||
gpuModel.collisionNarrowVerticalProp =
|
||||
!gpuModel.collisionSteppedFountain &&
|
||||
!gpuModel.collisionSteppedLowPlatform &&
|
||||
|
|
@ -435,10 +447,11 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
!gpuModel.collisionSteppedFountain &&
|
||||
!gpuModel.collisionSteppedLowPlatform &&
|
||||
!gpuModel.collisionNarrowVerticalProp &&
|
||||
!gpuModel.collisionTreeTrunk &&
|
||||
!curbLikeName &&
|
||||
!lowPlatformLikeShape &&
|
||||
(smallSolidPropName || (genericSolidPropShape && !foliageName && !softTree));
|
||||
gpuModel.collisionNoBlock = ((((foliageName && smallSoftShape) || (foliageName && mediumFoliageShape)) || softTree) &&
|
||||
gpuModel.collisionNoBlock = ((foliageName || softTree) &&
|
||||
!forceSolidCurb);
|
||||
}
|
||||
gpuModel.boundMin = tightMin;
|
||||
|
|
@ -883,7 +896,7 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
|||
lastDrawCallCount = 0;
|
||||
|
||||
// Adaptive render distance: shorter in dense areas (cities), longer in open terrain
|
||||
const float maxRenderDistance = (instances.size() > 600) ? 180.0f : 350.0f;
|
||||
const float maxRenderDistance = (instances.size() > 600) ? 180.0f : 2000.0f;
|
||||
const float maxRenderDistanceSq = maxRenderDistance * maxRenderDistance;
|
||||
const float fadeStartFraction = 0.75f;
|
||||
const glm::vec3 camPos = camera.getPosition();
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ std::unique_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
p.rotation = glm::vec3(
|
||||
-placement.rotation[2] * 3.14159f / 180.0f,
|
||||
-placement.rotation[0] * 3.14159f / 180.0f,
|
||||
placement.rotation[1] * 3.14159f / 180.0f
|
||||
(placement.rotation[1] + 180.0f) * 3.14159f / 180.0f
|
||||
);
|
||||
p.scale = placement.scale / 1024.0f;
|
||||
pending->m2Placements.push_back(p);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue