mirror of
https://github.com/TombEngine/TombEngine.git
synced 2025-04-28 15:57:59 +03:00
Initial Commit
This commit is contained in:
parent
6c0eeff41c
commit
1089ef9463
4 changed files with 109 additions and 34 deletions
|
@ -8,6 +8,7 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
|
||||||
## New features
|
## New features
|
||||||
* Added video playback support.
|
* Added video playback support.
|
||||||
* Added muzzle glow effect for firearms.
|
* Added muzzle glow effect for firearms.
|
||||||
|
* Added weather particle clustering and increase weather particle performance.
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
* Fixed Teleporter object.
|
* Fixed Teleporter object.
|
||||||
|
@ -25,7 +26,6 @@ TombEngine releases are located in this repository (alongside with Tomb Editor):
|
||||||
* Fixed crashes when Lara is on a vehicle unreachable by friendly NPCs.
|
* Fixed crashes when Lara is on a vehicle unreachable by friendly NPCs.
|
||||||
* Removed legacy TR5 search object code which caused issues with meshswaps.
|
* Removed legacy TR5 search object code which caused issues with meshswaps.
|
||||||
* Removed excessive HK nerfing in running state.
|
* Removed excessive HK nerfing in running state.
|
||||||
* Optimized weather particle rendering.
|
|
||||||
|
|
||||||
### Lua API changes
|
### Lua API changes
|
||||||
* Added `View.PlayVideo`, `View.StopVideo`, and other helper functions for the video playback.
|
* Added `View.PlayVideo`, `View.StopVideo`, and other helper functions for the video playback.
|
||||||
|
|
|
@ -433,13 +433,17 @@ namespace TEN::Effects::Environment
|
||||||
|
|
||||||
// Produce ripples if particle got into substance (water or swamp).
|
// Produce ripples if particle got into substance (water or swamp).
|
||||||
if (inSubstance)
|
if (inSubstance)
|
||||||
SpawnRipple(part.Position, part.RoomNumber, Random::GenerateFloat(16.0f, 24.0f), (int)RippleFlags::SlowFade | (int)RippleFlags::LowOpacity);
|
{
|
||||||
|
auto ripplePos = part.Position;
|
||||||
|
ripplePos.y = pointColl.GetWaterSurfaceHeight();
|
||||||
|
SpawnRipple(ripplePos, part.RoomNumber, Random::GenerateFloat(16.0f, 24.0f), (int)RippleFlags::SlowFade | (int)RippleFlags::LowOpacity);
|
||||||
|
}
|
||||||
|
|
||||||
// Immediately disable rain particle because it doesn't need fading out.
|
// Immediately disable rain particle because it doesn't need fading out.
|
||||||
if (part.Type == WeatherType::Rain)
|
if (part.Type == WeatherType::Rain)
|
||||||
{
|
{
|
||||||
part.Enabled = false;
|
part.Enabled = false;
|
||||||
AddWaterSparks(prevPos.x, prevPos.y, prevPos.z, 6);
|
AddWaterSparks(prevPos.x, inSubstance ? pointColl.GetWaterSurfaceHeight() : pointColl.GetFloorHeight() - 32, prevPos.z, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
@ -587,7 +591,7 @@ namespace TEN::Effects::Environment
|
||||||
|
|
||||||
auto xPos = Camera.pos.x + ((int)(phd_cos(angle) * radius));
|
auto xPos = Camera.pos.x + ((int)(phd_cos(angle) * radius));
|
||||||
auto zPos = Camera.pos.z + ((int)(phd_sin(angle) * radius));
|
auto zPos = Camera.pos.z + ((int)(phd_sin(angle) * radius));
|
||||||
auto yPos = Camera.pos.y - (BLOCK(4) + Random::GenerateInt() & (BLOCK(4) - 1));
|
auto yPos = Camera.pos.y - (BLOCK(3) + Random::GenerateInt() & (BLOCK(4) - 1));
|
||||||
|
|
||||||
auto outsideRoom = IsRoomOutside(xPos, yPos, zPos);
|
auto outsideRoom = IsRoomOutside(xPos, yPos, zPos);
|
||||||
|
|
||||||
|
@ -607,12 +611,14 @@ namespace TEN::Effects::Environment
|
||||||
switch (level.GetWeatherType())
|
switch (level.GetWeatherType())
|
||||||
{
|
{
|
||||||
case WeatherType::Snow:
|
case WeatherType::Snow:
|
||||||
|
part.ClusterSize = (int)(level.GetWeatherStrength() * WEATHER_PARTICLE_CLUSTER_MULT / 2);
|
||||||
part.Size = Random::GenerateFloat(SNOW_SIZE_MAX / 3, SNOW_SIZE_MAX);
|
part.Size = Random::GenerateFloat(SNOW_SIZE_MAX / 3, SNOW_SIZE_MAX);
|
||||||
part.Velocity.y = Random::GenerateFloat(SNOW_VELOCITY_MAX / 4, SNOW_VELOCITY_MAX) * (part.Size / SNOW_SIZE_MAX);
|
part.Velocity.y = Random::GenerateFloat(SNOW_VELOCITY_MAX / 4, SNOW_VELOCITY_MAX) * (part.Size / SNOW_SIZE_MAX);
|
||||||
part.Life = (SNOW_VELOCITY_MAX / 3) + ((SNOW_VELOCITY_MAX / 2) - ((int)part.Velocity.y >> 2));
|
part.Life = (SNOW_VELOCITY_MAX / 3) + ((SNOW_VELOCITY_MAX / 2) - ((int)part.Velocity.y >> 2));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case WeatherType::Rain:
|
case WeatherType::Rain:
|
||||||
|
part.ClusterSize = (int)(level.GetWeatherStrength() * WEATHER_PARTICLE_CLUSTER_MULT);
|
||||||
part.Size = Random::GenerateFloat(RAIN_SIZE_MAX / 2, RAIN_SIZE_MAX);
|
part.Size = Random::GenerateFloat(RAIN_SIZE_MAX / 2, RAIN_SIZE_MAX);
|
||||||
part.Velocity.y = Random::GenerateFloat(RAIN_VELOCITY_MAX / 2, RAIN_VELOCITY_MAX) * (part.Size / RAIN_SIZE_MAX) * std::clamp(level.GetWeatherStrength(), 0.6f, 1.0f);
|
part.Velocity.y = Random::GenerateFloat(RAIN_VELOCITY_MAX / 2, RAIN_VELOCITY_MAX) * (part.Size / RAIN_SIZE_MAX) * std::clamp(level.GetWeatherStrength(), 0.6f, 1.0f);
|
||||||
part.Life = (RAIN_VELOCITY_MAX * 2) - part.Velocity.y;
|
part.Life = (RAIN_VELOCITY_MAX * 2) - part.Velocity.y;
|
||||||
|
@ -622,6 +628,7 @@ namespace TEN::Effects::Environment
|
||||||
part.Velocity.x = Random::GenerateFloat(WEATHER_PARTICLE_HORIZONTAL_VELOCITY / 2, WEATHER_PARTICLE_HORIZONTAL_VELOCITY);
|
part.Velocity.x = Random::GenerateFloat(WEATHER_PARTICLE_HORIZONTAL_VELOCITY / 2, WEATHER_PARTICLE_HORIZONTAL_VELOCITY);
|
||||||
part.Velocity.z = Random::GenerateFloat(WEATHER_PARTICLE_HORIZONTAL_VELOCITY / 2, WEATHER_PARTICLE_HORIZONTAL_VELOCITY);
|
part.Velocity.z = Random::GenerateFloat(WEATHER_PARTICLE_HORIZONTAL_VELOCITY / 2, WEATHER_PARTICLE_HORIZONTAL_VELOCITY);
|
||||||
|
|
||||||
|
part.UniqueID = (int)Particles.size();
|
||||||
part.Type = level.GetWeatherType();
|
part.Type = level.GetWeatherType();
|
||||||
part.RoomNumber = outsideRoom;
|
part.RoomNumber = outsideRoom;
|
||||||
part.Position.x = xPos;
|
part.Position.x = xPos;
|
||||||
|
|
|
@ -9,6 +9,7 @@ using namespace TEN::Entities::Effects;
|
||||||
namespace TEN::Effects::Environment
|
namespace TEN::Effects::Environment
|
||||||
{
|
{
|
||||||
constexpr auto WEATHER_PARTICLE_SPAWN_DENSITY = 32;
|
constexpr auto WEATHER_PARTICLE_SPAWN_DENSITY = 32;
|
||||||
|
constexpr auto WEATHER_PARTICLE_CLUSTER_MULT = 16.0f;
|
||||||
constexpr auto WEATHER_PARTICLE_COUNT_MAX = 4096;
|
constexpr auto WEATHER_PARTICLE_COUNT_MAX = 4096;
|
||||||
constexpr auto WEATHER_PARTICLE_COLL_CHECK_DELAY_MAX = 5.0f;
|
constexpr auto WEATHER_PARTICLE_COLL_CHECK_DELAY_MAX = 5.0f;
|
||||||
|
|
||||||
|
@ -70,6 +71,7 @@ namespace TEN::Effects::Environment
|
||||||
struct WeatherParticle
|
struct WeatherParticle
|
||||||
{
|
{
|
||||||
WeatherType Type = WeatherType::None;
|
WeatherType Type = WeatherType::None;
|
||||||
|
int UniqueID = 0;
|
||||||
|
|
||||||
Vector3 Position = Vector3::Zero;
|
Vector3 Position = Vector3::Zero;
|
||||||
int RoomNumber = NO_VALUE;
|
int RoomNumber = NO_VALUE;
|
||||||
|
@ -79,6 +81,7 @@ namespace TEN::Effects::Environment
|
||||||
float Life = 0.0f;
|
float Life = 0.0f;
|
||||||
float CollisionCheckDelay = 0.0f;
|
float CollisionCheckDelay = 0.0f;
|
||||||
float Size = 0.0f;
|
float Size = 0.0f;
|
||||||
|
int ClusterSize = 1;
|
||||||
|
|
||||||
bool Enabled = false;
|
bool Enabled = false;
|
||||||
bool Stopped = false;
|
bool Stopped = false;
|
||||||
|
|
|
@ -1022,6 +1022,8 @@ namespace TEN::Renderer
|
||||||
void Renderer::PrepareWeatherParticles(RenderView& view)
|
void Renderer::PrepareWeatherParticles(RenderView& view)
|
||||||
{
|
{
|
||||||
constexpr auto RAIN_WIDTH = 4.0f;
|
constexpr auto RAIN_WIDTH = 4.0f;
|
||||||
|
constexpr auto SNOW_CLUSTER_SPREAD = BLOCK(1.0f);
|
||||||
|
constexpr auto RAIN_CLUSTER_SPREAD = BLOCK(0.35f);
|
||||||
|
|
||||||
for (const auto& part : Weather.GetParticles())
|
for (const auto& part : Weather.GetParticles())
|
||||||
{
|
{
|
||||||
|
@ -1031,15 +1033,14 @@ namespace TEN::Renderer
|
||||||
auto pos = Vector3::Lerp(part.PrevPosition, part.Position, GetInterpolationFactor());
|
auto pos = Vector3::Lerp(part.PrevPosition, part.Position, GetInterpolationFactor());
|
||||||
auto size = Lerp(part.PrevSize, part.Size, GetInterpolationFactor());
|
auto size = Lerp(part.PrevSize, part.Size, GetInterpolationFactor());
|
||||||
|
|
||||||
if (!view.Camera.Frustum.SphereInFrustum(pos, size))
|
// Underwater dust does not need clustering.
|
||||||
continue;
|
if (part.Type == WeatherType::None)
|
||||||
|
|
||||||
switch (part.Type)
|
|
||||||
{
|
{
|
||||||
case WeatherType::None:
|
if (!view.Camera.Frustum.SphereInFrustum(pos, size))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (!CheckIfSlotExists(ID_DEFAULT_SPRITES, "Underwater dust rendering"))
|
if (!CheckIfSlotExists(ID_DEFAULT_SPRITES, "Underwater dust rendering"))
|
||||||
return;
|
continue;
|
||||||
|
|
||||||
AddSpriteBillboard(
|
AddSpriteBillboard(
|
||||||
&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST],
|
&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST],
|
||||||
|
@ -1048,39 +1049,103 @@ namespace TEN::Renderer
|
||||||
0.0f, 1.0f, Vector2(size),
|
0.0f, 1.0f, Vector2(size),
|
||||||
BlendMode::Additive, true, view);
|
BlendMode::Additive, true, view);
|
||||||
|
|
||||||
break;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
case WeatherType::Snow:
|
// Clamp cluster size to 1.
|
||||||
|
int clusterSize = std::max(1, part.ClusterSize);
|
||||||
|
|
||||||
if (!CheckIfSlotExists(ID_DEFAULT_SPRITES, "Snow rendering"))
|
// If particle is dying, immediately cancel the cluster.
|
||||||
return;
|
if (part.Stopped)
|
||||||
|
clusterSize = 1;
|
||||||
|
|
||||||
AddSpriteBillboard(
|
auto finalPos = pos;
|
||||||
&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST],
|
auto finalScale = size;
|
||||||
pos,
|
|
||||||
Color(1.0f, 1.0f, 1.0f, part.Transparency()),
|
|
||||||
0.0f, 1.0f, Vector2(size),
|
|
||||||
BlendMode::Additive, true, view);
|
|
||||||
|
|
||||||
break;
|
for (int i = 0; i < clusterSize; i++)
|
||||||
|
{
|
||||||
|
if (i > 0)
|
||||||
|
{
|
||||||
|
// Combine particle index and cluster index for unique seeding.
|
||||||
|
int uniqueSeed = part.UniqueID + i;
|
||||||
|
|
||||||
case WeatherType::Rain:
|
// Use bits from uniqueSeed to determine distribution pattern.
|
||||||
|
float spread = part.Type == WeatherType::Snow ? SNOW_CLUSTER_SPREAD : RAIN_CLUSTER_SPREAD;
|
||||||
|
float offsetBase = spread * ((i + 1) / (float)clusterSize);
|
||||||
|
|
||||||
if (!CheckIfSlotExists(ID_DRIP_SPRITE, "Rain rendering"))
|
// Use bits 0, 1, 2 for axis signs.
|
||||||
return;
|
// Snow Y axis is always negative, so that snowflakes don't sink into room geometry.
|
||||||
|
float xSign = (uniqueSeed & 1) ? 1.0f : -1.0f;
|
||||||
|
float zSign = (uniqueSeed & 4) ? 1.0f : -1.0f;
|
||||||
|
|
||||||
Vector3 v;
|
// Use bits 3, 4 for axis emphasis.
|
||||||
part.Velocity.Normalize(v);
|
int axisEmphasis = uniqueSeed & 3;
|
||||||
|
float xScale = (axisEmphasis == 0) ? 1.1f : 0.4f;
|
||||||
|
float yScale = (axisEmphasis == 1) ? 1.2f : 0.5f;
|
||||||
|
float zScale = (axisEmphasis == 2) ? 1.0f : 0.6f;
|
||||||
|
|
||||||
AddSpriteBillboardConstrained(
|
Vector3 positionOffset(
|
||||||
&_sprites[Objects[ID_DRIP_SPRITE].meshIndex],
|
xSign * offsetBase * xScale,
|
||||||
pos,
|
-(offsetBase * yScale),
|
||||||
Color(0.8f, 1.0f, 1.0f, part.Transparency()),
|
zSign * offsetBase * zScale
|
||||||
0.0f, 1.0f,
|
);
|
||||||
Vector2(RAIN_WIDTH, size),
|
|
||||||
BlendMode::Additive, -v, true, view);
|
|
||||||
|
|
||||||
break;
|
// Apply deterministic offset.
|
||||||
|
finalPos = pos + positionOffset;
|
||||||
|
finalScale = size * (1.0f + abs(phd_sin(part.UniqueID + i)));
|
||||||
|
|
||||||
|
constexpr auto SNOW_SPIN_RATE = 0.05f;
|
||||||
|
constexpr auto SNOW_SPIN_RADIUS = 0.3f;
|
||||||
|
|
||||||
|
if (part.Type == WeatherType::Snow)
|
||||||
|
{
|
||||||
|
// Calculate spin angle based on vertical position.
|
||||||
|
// Wrap the vertical position to 3 blocks and multiply by 21 to get full unsigned short value.
|
||||||
|
unsigned short spinAngle = ((int)abs(finalPos.y) % BLOCK(3)) * 21;
|
||||||
|
|
||||||
|
// Apply circular motion in XZ plane.
|
||||||
|
finalPos.x += positionOffset.x * phd_sin((short)spinAngle);
|
||||||
|
finalPos.z += positionOffset.z * phd_cos((short)spinAngle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!view.Camera.Frustum.SphereInFrustum(finalPos, finalScale))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
switch (part.Type)
|
||||||
|
{
|
||||||
|
case WeatherType::Snow:
|
||||||
|
|
||||||
|
if (!CheckIfSlotExists(ID_DEFAULT_SPRITES, "Snow rendering"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
AddSpriteBillboard(
|
||||||
|
&_sprites[Objects[ID_DEFAULT_SPRITES].meshIndex + SPR_UNDERWATERDUST],
|
||||||
|
finalPos,
|
||||||
|
Color(1.0f, 1.0f, 1.0f, part.Transparency()),
|
||||||
|
0.0f, 1.0f, Vector2(finalScale),
|
||||||
|
BlendMode::Additive, false, view);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WeatherType::Rain:
|
||||||
|
|
||||||
|
if (!CheckIfSlotExists(ID_DRIP_SPRITE, "Rain rendering"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Vector3 v;
|
||||||
|
part.Velocity.Normalize(v);
|
||||||
|
|
||||||
|
AddSpriteBillboardConstrained(
|
||||||
|
&_sprites[Objects[ID_DRIP_SPRITE].meshIndex],
|
||||||
|
finalPos,
|
||||||
|
Color(0.8f, 1.0f, 1.0f, part.Transparency()),
|
||||||
|
0.0f, 1.0f,
|
||||||
|
Vector2(RAIN_WIDTH, finalScale),
|
||||||
|
BlendMode::Additive, -v, false, view);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue