more physics tweaks (disabled NGS because it behaved poorly, added some small code to keep objects when grounded to moving bodies, fixed oversight with kinematic bodies), made darkengine lightmaps properly dark with baking it as srgb
This commit is contained in:
parent
5cc441bb63
commit
b309a60b8c
@ -26,7 +26,7 @@ ent:addHook("link:Message.%UID%", function(payload)
|
||||
|
||||
if explicitSchema ~= "" then
|
||||
local resolvedUrl = _G.DarkUtils.playSound(ent, "", explicitSchema, {
|
||||
spatial = false, streamed = true, volume = 1.0, unique = true, loop = false
|
||||
spatial = false, streamed = true, volume = 0.2, unique = true, loop = false
|
||||
})
|
||||
if resolvedUrl then isPlaying = true end
|
||||
end
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
local ent = ent
|
||||
local scene = entities.currentScene()
|
||||
local physicsBody = ent:getComponent("PhysicsBody")
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local darkMeta = metadata["dark"] or {}
|
||||
|
||||
local touching = {}
|
||||
|
||||
local tripFlags = darkMeta["trip_flags"] or 0
|
||||
local triggerOnEnter = bit.band(tripFlags, 1) ~= 0 or tripFlags == 0
|
||||
local triggerOnExit = bit.band(tripFlags, 2) ~= 0
|
||||
local triggerOnce = bit.band(tripFlags, 8) ~= 0
|
||||
local triggerPlayer = bit.band(tripFlags, 32) ~= 0
|
||||
local hasFired = false
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
if not physicsBody:initialized() then return end
|
||||
if triggerOnce and hasFired then return end
|
||||
|
||||
local currentCollisions = {}
|
||||
local collisionEvents = physicsBody:getCollisionEvents()
|
||||
@ -19,12 +29,18 @@ ent:bind( "tick", function(self)
|
||||
end
|
||||
|
||||
if other then
|
||||
local uid = other:getObject():uid()
|
||||
currentCollisions[uid] = true
|
||||
local otherEnt = other:getObject()
|
||||
local otherUid = otherEnt:uid()
|
||||
currentCollisions[otherUid] = true
|
||||
|
||||
if not touching[uid] then
|
||||
touching[uid] = true
|
||||
ent:queueHook("link:Broadcast.%UID%", { message = "TurnOn" }, 0)
|
||||
if triggerPlayer and otherEnt:name() ~= "Player" then return end
|
||||
|
||||
if not touching[otherUid] then
|
||||
touching[otherUid] = true
|
||||
if triggerOnEnter then
|
||||
ent:queueHook("link:Broadcast.%UID%", { message = "TurnOn" }, 0)
|
||||
if triggerOnce then hasFired = true end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -32,7 +48,12 @@ ent:bind( "tick", function(self)
|
||||
for uid, _ in pairs(touching) do
|
||||
if not currentCollisions[uid] then
|
||||
touching[uid] = nil
|
||||
ent:queueHook("link:Broadcast.%UID%", { message = "TurnOff" }, 0)
|
||||
if triggerOnExit and not (triggerOnce and hasFired) then
|
||||
ent:queueHook("link:Broadcast.%UID%", { message = "TurnOn" }, 0)
|
||||
if triggerOnce then hasFired = true end
|
||||
elseif triggerOnEnter then
|
||||
ent:queueHook("link:Broadcast.%UID%", { message = "TurnOff" }, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end )
|
||||
@ -23,7 +23,9 @@
|
||||
"timescale": 32
|
||||
}
|
||||
},
|
||||
"ambient": [ 0, 0, 0 ]
|
||||
"ambient": [ 0, 0, 0 ],
|
||||
"exposure": 0.75,
|
||||
"gamma": 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,8 +39,16 @@ void pbr() {
|
||||
const float Ldistance = sqrt(Lmagnitude);
|
||||
// "free" normalization, since we need to compute the above values anyways
|
||||
Li = Li / Ldistance;
|
||||
// calculate the smooth windowing
|
||||
const float radius2 = lights[i].radius * lights[i].radius;
|
||||
float window = 1.0f;
|
||||
if ( radius2 > 0.0f ) {
|
||||
float distOverRadius = Lmagnitude / radius2;
|
||||
window = max(0.0, 1.0 - (distOverRadius * distOverRadius));
|
||||
window = window * window; // smoother curve
|
||||
}
|
||||
// attenuation factor
|
||||
const float Lattenuation = 1.0 / (1 + Lmagnitude);
|
||||
const float Lattenuation = window / (1.0 + Lmagnitude);
|
||||
// skip if attenuation factor is too low
|
||||
// if ( Lattenuation <= LIGHT_POWER_CUTOFF ) continue;
|
||||
// ray cast if our surface is occluded from the light
|
||||
|
||||
@ -72,6 +72,8 @@ namespace uf {
|
||||
void UF_API maxDistance( pod::AudioSource& source, float v );
|
||||
float UF_API referenceDistance( const pod::AudioSource& source );
|
||||
void UF_API referenceDistance( pod::AudioSource& source, float v );
|
||||
void UF_API spatial( pod::AudioSource& source, bool s );
|
||||
|
||||
|
||||
float UF_API distance( const pod::Vector3f& position );
|
||||
float UF_API occlusion( const pod::Vector3f& position );
|
||||
|
||||
@ -53,5 +53,6 @@ namespace impl {
|
||||
|
||||
void applyRollingResistance( pod::PhysicsBody& body, float dt );
|
||||
void snapVelocity( pod::PhysicsBody& body, float dt, float threshold = 0.01f );
|
||||
void integrateKinematic( pod::PhysicsBody& body, float dt );
|
||||
void integrate( pod::PhysicsBody& body, float dt );
|
||||
}
|
||||
@ -368,6 +368,7 @@ namespace pod {
|
||||
struct Activity {
|
||||
bool awake = true;
|
||||
bool grounded = false;
|
||||
pod::PhysicsBody* referenceFrame = NULL;
|
||||
float sleepTimer = 0.0f;
|
||||
int32_t islandID = -1;
|
||||
static constexpr float sleepThreshold = 0.5f; // seconds
|
||||
|
||||
@ -70,10 +70,9 @@ void ext::AudioEmitterBehavior::initialize( uf::Object& self ) {
|
||||
if ( json["rolloffFactor"].is<double>() ) uf::audio::rolloff(source, json["rolloffFactor"].as<float>());
|
||||
if ( json["maxDistance"].is<double>() ) uf::audio::maxDistance(source, json["maxDistance"].as<float>());
|
||||
if ( json["referenceDistance"].is<double>() ) uf::audio::referenceDistance(source, json["referenceDistance"].as<float>());
|
||||
if ( json["spatial"].is<bool>() ) source.settings.spatial = json["spatial"].as<bool>();
|
||||
|
||||
if ( json["loop"].is<bool>() ) uf::audio::loop(source, json["loop"].as<bool>());
|
||||
else if ( json["wants loop"].is<bool>() ) {
|
||||
if ( json["spatial"].is<bool>() ) uf::audio::spatial( source, json["spatial"].as<bool>() );
|
||||
if ( json["loop"].is<bool>() ) uf::audio::loop(source, json["loop"].as<bool>()); // explicitly set it
|
||||
else if ( json["wants loop"].is<bool>() ) { // requests looping, but only if the audio file has it (for Valve-sourced WAVs)
|
||||
auto wants = json["wants loop"].as<bool>(true);
|
||||
uf::audio::loop(source, wants && clip && clip->info.loop.has);
|
||||
}
|
||||
|
||||
@ -760,7 +760,6 @@ void ext::ExtSceneBehavior::tick( uf::Object& self ) {
|
||||
.exposure = metadata.light.exposure
|
||||
};
|
||||
|
||||
|
||||
if ( shader.hasUniform("UBO") ) shader.updateBuffer( (const void*) &uniforms, sizeof(uniforms), shader.getUniformBuffer("UBO") );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1085,6 +1085,44 @@ void uf::graph::process( pod::Graph& graph ) {
|
||||
material.indexAlbedo = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ext::json::isObject( graphMetadataDark ) ) {
|
||||
// set transparent
|
||||
if ( name.find("glass") != uf::stl::string::npos ||
|
||||
name.find("trans") != uf::stl::string::npos ||
|
||||
name.find("grate") != uf::stl::string::npos ) {
|
||||
|
||||
material.modeAlpha = pod::Material::AlphaMode::BLEND;
|
||||
material.modeCull = pod::Material::CullMode::NONE;
|
||||
}
|
||||
// set emissive
|
||||
for ( auto nodeID = 0; nodeID < graph.nodes.size(); ++nodeID ) {
|
||||
auto& node = graph.nodes[nodeID];
|
||||
// check if owns a light
|
||||
auto lightName = node.name;
|
||||
auto nameID = ::fmt::format( "{}_{}", node.name, nodeID );
|
||||
if ( graph.lights.count( nameID ) > 0 ) lightName = nameID;
|
||||
if ( graph.lights.count( lightName ) == 0 ) {
|
||||
continue;
|
||||
}
|
||||
auto& light = graph.lights[lightName];
|
||||
if ( !(0 <= node.mesh && node.mesh < graph.meshes.size()) ) continue;
|
||||
// iterate primitives for materials
|
||||
auto& primitives = storage.primitives.map[graph.primitives[node.mesh]];
|
||||
for ( auto& primitive : primitives ) {
|
||||
auto materialID = primitive.instance.materialID;
|
||||
if ( !(0 <= materialID && materialID <= graph.materials.size()) ) {
|
||||
UF_MSG_DEBUG("node={}, lightName={} has invalid material: {}", node.name, lightName, materialID);
|
||||
continue;
|
||||
}
|
||||
auto& materialName = graph.materials[materialID];
|
||||
// set emissive
|
||||
material.modeAlpha = pod::Material::AlphaMode::EMISSIVE;
|
||||
material.colorEmissive = light.color * light.intensity;
|
||||
UF_MSG_DEBUG("name={}, light={}, emissive={}", node.name, lightName, uf::vector::toString( material.colorEmissive ));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto tag = ext::json::find( name, graphMetadataJson["tags"] );
|
||||
if ( ext::json::isObject( tag ) ) {
|
||||
@ -1440,11 +1478,13 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
bool emitsAudio = false;
|
||||
|
||||
// bind elevator
|
||||
bool isElevator = false;
|
||||
bool isElevator = false; // node.name.find("Elevator") != std::string::npos
|
||||
bool isTrap = false; // node.name.find("Trap") != std::string::npos
|
||||
if ( ext::json::isArray( metadataDark["scripts"] ) ) {
|
||||
ext::json::forEach( metadataDark["scripts"], [&]( ext::json::Value& value ){
|
||||
auto script = value.as<uf::stl::string>();
|
||||
if ( script == "BaseElevator" || script == "Elevator" ) isElevator = true;
|
||||
if ( script.ends_with("Elevator") ) isElevator = true;
|
||||
if ( script.starts_with("Trap") ) isTrap = true;
|
||||
});
|
||||
}
|
||||
|
||||
@ -1485,7 +1525,7 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
}
|
||||
|
||||
// bind trap
|
||||
if ( node.name == "Sound Trap" || node.name == "VO Trap" ) {
|
||||
if ( isTrap ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/dark/trap.lua");
|
||||
emitsAudio = true;
|
||||
// bind ambient
|
||||
|
||||
@ -440,6 +440,8 @@ namespace impl {
|
||||
uf::stl::unordered_map<int32_t, uf::stl::string> classTag;
|
||||
uf::stl::unordered_map<int32_t, uf::stl::string> schMsg;
|
||||
uf::stl::unordered_map<int32_t, uf::stl::string> schAction;
|
||||
|
||||
uf::stl::unordered_map<int32_t, int32_t> tripFlags;
|
||||
|
||||
uf::stl::unordered_map<int32_t, uf::stl::vector<uf::stl::string>> script;
|
||||
} properties;
|
||||
@ -1204,6 +1206,9 @@ namespace impl {
|
||||
(float)((sample >> 5) & 0x1F),
|
||||
(float)((sample ) & 0x1F),
|
||||
} / 31.0f;
|
||||
|
||||
color = uf::vector::pow( color, 2.2f ); // convert to SRGB
|
||||
|
||||
impl::encodeRGBE( color, &image.pixels[(y * w + x) * 4] );
|
||||
}
|
||||
}
|
||||
@ -1527,6 +1532,12 @@ namespace impl {
|
||||
}
|
||||
}
|
||||
|
||||
// bind trip f l ags
|
||||
int32_t tripFlags = 0;
|
||||
if ( ctx.findInheritedProperty( objectID, ctx.properties.tripFlags, tripFlags ) ) {
|
||||
metadata["trip_flags"] = tripFlags;
|
||||
}
|
||||
|
||||
// bind frobbage
|
||||
PropertyFrobInfo frob;
|
||||
bool isFrobbable = false;
|
||||
@ -1623,6 +1634,7 @@ namespace impl {
|
||||
}
|
||||
|
||||
// bind light (at the end because we insert a new node)
|
||||
// to-do: verify if this doesn't double up on lights
|
||||
PropertyLight light;
|
||||
if ( ctx.findInheritedProperty( objectID, ctx.properties.light, light ) ) {
|
||||
// create new node
|
||||
@ -1632,11 +1644,15 @@ namespace impl {
|
||||
lightNode.name = lightNodeName;
|
||||
graph.nodes[nodeID].children.emplace_back(lightNodeID);
|
||||
|
||||
float radiusFactor = 1.2f;
|
||||
float intensityFactor = 0.05f;
|
||||
graph.lights[::fmt::format("{}_{}", lightNode.name, lightNodeID)] = {
|
||||
.range = light.radius,
|
||||
.range = light.radius * radiusFactor,
|
||||
.color = impl::hsvToRgb(light.hue, light.saturation, 1.0f),
|
||||
.intensity = light.brightness / M_PI,
|
||||
.intensity = light.brightness * intensityFactor,
|
||||
};
|
||||
|
||||
// set emissive factor
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1865,6 +1881,7 @@ namespace impl {
|
||||
impl::extractProperty( ctx, "PhysState", ctx.properties.physState );
|
||||
impl::extractProperty( ctx, "SchPlayParams", ctx.properties.schPlayParams );
|
||||
impl::extractProperty( ctx, "SchLoopParams", ctx.properties.schLoopParams );
|
||||
impl::extractProperty( ctx, "TripFlags", ctx.properties.tripFlags );
|
||||
|
||||
// parse schsamp
|
||||
if ( inventory.count("SchSamp") > 0 ) {
|
||||
@ -2066,6 +2083,8 @@ void ext::lgs::loadMis( pod::Graph& graph, const uf::stl::string& filename, cons
|
||||
impl::processSongs( graph, ctx );
|
||||
impl::processSchema( graph, ctx );
|
||||
|
||||
graph.metadata["dark"]["_"] = true;
|
||||
|
||||
// disable postprocessing flags
|
||||
if ( filename.starts_with("game://") ) graph.metadata["exporter"]["enabled"] = false; // disable exporting if loaded from a VPK
|
||||
graph.metadata["exporter"]["unwrap"] = false; // do not unwrap UVs for baking (we already have those)
|
||||
|
||||
@ -234,7 +234,16 @@ float uf::audio::referenceDistance( const pod::AudioSource& source ) {
|
||||
return v;
|
||||
}
|
||||
void uf::audio::referenceDistance( pod::AudioSource& source, float v ) {
|
||||
source.alSource.set( AL_REFERENCE_DISTANCE, v );
|
||||
source.alSource.set( AL_REFERENCE_DISTANCE, v );
|
||||
}
|
||||
|
||||
void uf::audio::spatial( pod::AudioSource& source, bool s ) {
|
||||
if ( (source.settings.spatial = s) ) {
|
||||
source.alSource.set( AL_SOURCE_RELATIVE, AL_FALSE );
|
||||
} else {
|
||||
source.alSource.set( AL_SOURCE_RELATIVE, AL_TRUE );
|
||||
source.alSource.set( AL_POSITION, 0.0f, 0.0f, 0.0f );
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@ -135,9 +135,8 @@ void uf::physics::step( pod::World& world, float dt ) {
|
||||
}
|
||||
}
|
||||
|
||||
for ( auto* body : bodies ) {
|
||||
impl::integrate( *body, dt );
|
||||
}
|
||||
for ( auto* body : bodies ) impl::integrateKinematic( *body, dt );
|
||||
for ( auto* body : bodies ) impl::integrate( *body, dt );
|
||||
//UF_TIMER_MULTITRACE("Integration & Flattening");
|
||||
|
||||
// rebuild static bvh if dirty
|
||||
@ -251,8 +250,14 @@ void uf::physics::step( pod::World& world, float dt ) {
|
||||
if ( !isTrigger ) for ( auto& c : manifold.points ) {
|
||||
if ( std::fabs(uf::vector::dot(c.normal, pod::Vector3f{0,1,0})) > uf::physics::settings.groundedThreshold ) {
|
||||
// only mark if contact point is below body
|
||||
if ( c.point.y < impl::getPosition(a).y ) a.activity.grounded = true;
|
||||
if ( c.point.y < impl::getPosition(b).y ) b.activity.grounded = true;
|
||||
if ( c.point.y < impl::getPosition(a).y ) {
|
||||
a.activity.grounded = true;
|
||||
if ( b.inverseMass == 0.0f ) a.activity.referenceFrame = &b;
|
||||
}
|
||||
if ( c.point.y < impl::getPosition(b).y ) {
|
||||
b.activity.grounded = true;
|
||||
if ( a.inverseMass == 0.0f ) b.activity.referenceFrame = &a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -554,11 +559,7 @@ void uf::physics::applyTorque( pod::PhysicsBody& body, const pod::Vector3f& torq
|
||||
}
|
||||
void uf::physics::setVelocity( pod::PhysicsBody& body, const pod::Vector3f& v ) {
|
||||
impl::wakeBody( body );
|
||||
if ( body.inverseMass == 0.0f ) {
|
||||
body.transform->position += v * uf::physics::time::delta;
|
||||
} else {
|
||||
body.velocity = v;
|
||||
}
|
||||
body.velocity = v;
|
||||
}
|
||||
void uf::physics::applyVelocity( pod::PhysicsBody& body, const pod::Vector3f& v ) {
|
||||
impl::wakeBody( body );
|
||||
|
||||
@ -166,7 +166,7 @@ void impl::applyRollingResistance( pod::PhysicsBody& body, float dt ) {
|
||||
|
||||
// snap velocity for grounded bodies
|
||||
void impl::snapVelocity( pod::PhysicsBody& body, float dt, float threshold ) {
|
||||
if ( !body.activity.grounded || !body.activity.awake ) return;
|
||||
if ( !body.activity.grounded || !body.activity.awake || body.inverseMass == 0.0f ) return;
|
||||
|
||||
float threshold2 = threshold * threshold;
|
||||
// snap velocity if body is grounded and nearly still
|
||||
@ -181,6 +181,21 @@ void impl::snapVelocity( pod::PhysicsBody& body, float dt, float threshold ) {
|
||||
if ( angSpeed2 < threshold2 ) body.angularVelocity = {};
|
||||
}
|
||||
|
||||
void impl::integrateKinematic( pod::PhysicsBody& body, float dt ) {
|
||||
if ( !body.activity.awake || body.inverseMass != 0.0f ) return;
|
||||
|
||||
auto& transform = *body.transform;
|
||||
|
||||
transform.position += body.velocity * dt;
|
||||
|
||||
float angularSpeed2 = uf::vector::magnitude( body.angularVelocity );
|
||||
if ( angularSpeed2 > EPS2 ) {
|
||||
float angularSpeed = std::sqrt( angularSpeed2 );
|
||||
pod::Quaternion<> dq = uf::quaternion::axisAngle( body.angularVelocity / angularSpeed, angularSpeed * dt );
|
||||
uf::transform::rotate( transform, dq );
|
||||
}
|
||||
}
|
||||
|
||||
void impl::integrate( pod::PhysicsBody& body, float dt ) {
|
||||
// only integrate awake and dynamic bodies
|
||||
if ( !body.activity.awake || body.inverseMass == 0.0f ) return;
|
||||
@ -190,6 +205,13 @@ void impl::integrate( pod::PhysicsBody& body, float dt ) {
|
||||
auto fT = uf::transform::flatten( transform );
|
||||
auto gravity = uf::physics::getGravity( body );
|
||||
|
||||
if ( body.activity.referenceFrame ) {
|
||||
auto ref = body.activity.referenceFrame->velocity;
|
||||
if ( body.velocity.y > ref.y + 1.0f ) {
|
||||
body.activity.referenceFrame = NULL;
|
||||
} else if ( body.velocity.y > ref.y ) body.velocity.y = ref.y;
|
||||
}
|
||||
|
||||
// linear integration
|
||||
pod::Vector3f acceleration = (body.forceAccumulator * body.inverseMass);
|
||||
acceleration += gravity; // apply gravity
|
||||
|
||||
@ -41,6 +41,7 @@ namespace impl {
|
||||
float vRel = uf::vector::dot((vB - vA), contact.normal);
|
||||
|
||||
float e = std::min(a.material.restitution, b.material.restitution);
|
||||
if ( a.inverseMass == 0.0f || b.inverseMass == 0.0f) e = 0.0f;
|
||||
float restitutionBias = (vRel < -vSlop) ? -e * vRel : 0.0f;
|
||||
|
||||
rhs[i] = -vRel + restitutionBias;
|
||||
|
||||
@ -21,6 +21,7 @@ void impl::iterativeImpulseSolver( pod::PhysicsBody& a, pod::PhysicsBody& b, pod
|
||||
|
||||
float restitutionBias = 0.0f;
|
||||
float e = std::min(a.material.restitution, b.material.restitution);
|
||||
if ( a.inverseMass == 0.0f || b.inverseMass == 0.0f) e = 0.0f;
|
||||
float velAlongNormal = uf::vector::dot(rv, contact.normal);
|
||||
if ( velAlongNormal < -vSlop ) restitutionBias = -e * velAlongNormal;
|
||||
float targetVelocity = restitutionBias;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user