audio effects via gaytracing, fixed IO connectivity of triggers, fixed linear doors not dooring
This commit is contained in:
parent
de5fc31ff5
commit
a834adca13
@ -3,10 +3,10 @@
|
||||
"scenes": {
|
||||
"start": "StartMenu",
|
||||
"lights": { "enabled": true,
|
||||
"lightmaps": false,
|
||||
"lightmaps": true,
|
||||
"max": 32,
|
||||
"shadows": {
|
||||
"enabled": true,
|
||||
"enabled": false,
|
||||
"update": 4,
|
||||
"max": 16,
|
||||
"samples": 1
|
||||
|
||||
@ -15,6 +15,7 @@ local transform = ent:getComponent("Transform")
|
||||
local physicsBody = ent:getComponent("PhysicsBody")
|
||||
local metadata = ent:getComponent("Metadata")
|
||||
local metadataDoor = metadata["door"] or {}
|
||||
local metadataValve = metadata["valve"] or {}
|
||||
|
||||
local speed = metadataDoor["speed"] or 100.0
|
||||
local wait = metadataDoor["wait"] or 4.0
|
||||
@ -30,6 +31,19 @@ if isToggle then
|
||||
wait = -1
|
||||
end
|
||||
|
||||
local sounds = {
|
||||
locked = "doors/default_locked.wav",
|
||||
open = "doors/default_move.wav",
|
||||
close = "doors/default_stop.wav",
|
||||
}
|
||||
|
||||
if metadataValve["noise1"] then
|
||||
sounds["open"] = metadataValve["noise1"]
|
||||
end
|
||||
if metadataValve["noise2"] then
|
||||
sounds["close"] = metadataValve["noise2"]
|
||||
end
|
||||
|
||||
if isRotating then
|
||||
local ax = metadataDoor["axis"]
|
||||
rotAxis = Vector3f(ax[1], ax[2], ax[3]):normalize()
|
||||
@ -61,17 +75,24 @@ else
|
||||
local size = obb.extent * 2.0
|
||||
|
||||
local travelSize = math.abs(moveDir:dot(size))
|
||||
|
||||
if travelSize < 0.8 then
|
||||
travelSize = 2.4
|
||||
end
|
||||
|
||||
targetDistance = travelSize - lip
|
||||
else
|
||||
targetDistance = 96.0 - lip
|
||||
targetDistance = 2.4 - lip
|
||||
end
|
||||
|
||||
if targetDistance <= 0 then targetDistance = 0.1 end
|
||||
end
|
||||
|
||||
local soundEmitter = ent
|
||||
|
||||
local playSound = function( key, loop )
|
||||
if not loop then loop = false end
|
||||
local url = "/door/" .. key .. ".ogg"
|
||||
local url = "valve://sound/" .. key
|
||||
soundEmitter:queueHook("sound:Emit.%UID%", {
|
||||
filename = string.resolveURI(url, metadata["system"]["root"]),
|
||||
spatial = true, streamed = true, volume = "sfx", loop = loop
|
||||
@ -81,7 +102,7 @@ end
|
||||
local function toggleDoor( payload )
|
||||
if state == 0 or state == 3 then
|
||||
state = 1
|
||||
playSound("default_move")
|
||||
playSound(sounds["open"])
|
||||
|
||||
if isRotating and payload.uid ~= nil then
|
||||
local isOneWay = (math.floor(flags / 16) % 2) ~= 0
|
||||
@ -115,7 +136,7 @@ local function toggleDoor( payload )
|
||||
end
|
||||
elseif state == 2 then
|
||||
state = 3
|
||||
playSound("default_move")
|
||||
playSound(sounds["open"])
|
||||
end
|
||||
end
|
||||
|
||||
@ -133,7 +154,7 @@ ent:bind( "tick", function(self)
|
||||
currentDistance = targetDistance
|
||||
state = 2
|
||||
timer:reset()
|
||||
playSound("default_stop")
|
||||
playSound(sounds["close"])
|
||||
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnOpen" }, 0)
|
||||
end
|
||||
@ -145,12 +166,12 @@ ent:bind( "tick", function(self)
|
||||
deltaMove = deltaMove - currentDistance
|
||||
currentDistance = 0
|
||||
state = 0
|
||||
playSound("default_stop")
|
||||
playSound(sounds["close"])
|
||||
end
|
||||
elseif state == 2 and wait >= 0 then
|
||||
if timer:elapsed() >= wait then
|
||||
state = 3
|
||||
playSound("default_move")
|
||||
playSound(sounds["open"])
|
||||
end
|
||||
end
|
||||
|
||||
@ -167,7 +188,6 @@ ent:bind( "tick", function(self)
|
||||
else
|
||||
local vec = moveDir * finalMove
|
||||
if physicsBody:initialized() then
|
||||
print("Moving by: ", finalMove, " Axis: ", moveDir.x, moveDir.y, moveDir.z)
|
||||
physicsBody:setVelocity(moveDir * (finalMove / time.delta()))
|
||||
else
|
||||
transform.position = transform.position + vec
|
||||
@ -191,7 +211,7 @@ ent:addHook( "entity:Use.%UID%", function( payload )
|
||||
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnUse" }, 0)
|
||||
else
|
||||
playSound("default_locked")
|
||||
playSound(sounds["locked"])
|
||||
end
|
||||
end )
|
||||
|
||||
|
||||
@ -9,12 +9,58 @@ if not timer:running() then
|
||||
timer:start()
|
||||
end
|
||||
|
||||
--[[
|
||||
local classname = metadataValve["classname"] or "trigger_multiple"
|
||||
local wait = tonumber(metadataValve["wait"]) or 0.2
|
||||
local isActive = true
|
||||
|
||||
if classname == "trigger_once" then
|
||||
wait = -1
|
||||
end
|
||||
|
||||
local touching = {}
|
||||
local nextTriggerTime = 0
|
||||
|
||||
ent:bind( "tick", function(self)
|
||||
if not isActive or not physicsBody:initialized() then return end
|
||||
|
||||
local currentCollisions = {}
|
||||
local collisionEvents = physicsBody:getCollisionEvents()
|
||||
|
||||
for i, event in ipairs(collisionEvents) do
|
||||
-- do something
|
||||
-- print( event.state, event.a, event.b, event.point, event.normal, event.impulse )
|
||||
local other = nil
|
||||
if event.a:getObject():uid() == ent:uid() then
|
||||
other = event.b
|
||||
elseif event.b:getObject():uid() == ent:uid() then
|
||||
other = event.a
|
||||
end
|
||||
|
||||
if other then
|
||||
local uid = other:getObject():uid()
|
||||
currentCollisions[uid] = true
|
||||
|
||||
if not touching[uid] then
|
||||
touching[uid] = true
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnStartTouch" }, 0)
|
||||
|
||||
if wait == -1 or timer:elapsed() >= nextTriggerTime then
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnTrigger" }, 0)
|
||||
|
||||
if wait == -1 then
|
||||
isActive = false
|
||||
--entities.destroy(ent)
|
||||
return
|
||||
else
|
||||
nextTriggerTime = timer:elapsed() + wait
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end )
|
||||
]]
|
||||
|
||||
for uid, _ in pairs(touching) do
|
||||
if not currentCollisions[uid] then
|
||||
touching[uid] = nil
|
||||
ent:queueHook("io:FireOutput.%UID%", { output = "OnEndTouch" }, 0)
|
||||
end
|
||||
end
|
||||
end )
|
||||
@ -27,9 +27,9 @@
|
||||
"resolution": 2048
|
||||
} },*/
|
||||
|
||||
"ambient_generic": { "action": "load", "payload": { "assets": ["ent://scripts/ambient_generic.lua"], "behaviors": ["AudioEmitterBehavior"] } },
|
||||
|
||||
// automatically handled
|
||||
// "ambient_generic": { "action": "load", "payload": { "assets": ["ent://scripts/ambient_generic.lua"], "behaviors": ["AudioEmitterBehavior"] } },
|
||||
// "/^func_door/": { "action": "load", "payload": { "import": "/door.json" } },
|
||||
// "/^prop_door/": { "action": "load", "payload": { "import": "/door.json" } },
|
||||
// "/^prop_static/": { "action": "load", "payload": { "import": "ent://prop.json" } },
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"import": "./base_sourceengine.json",
|
||||
"assets": [
|
||||
// { "filename": "./maps/mcdonalds-mds.bsp" }
|
||||
{ "filename": "./maps/mcdonalds-mds/graph.json" }
|
||||
{ "filename": "./maps/mcdonalds-mds.bsp" }
|
||||
// { "filename": "./maps/mcdonalds-mds/graph.json" }
|
||||
]
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
// "import": "./rp_downtown_v2.json"
|
||||
// "import": "./ss2_medsci1.json"
|
||||
"import": "./mds_mcdonalds.json"
|
||||
// "import": "./cs_office.json"
|
||||
// "import": "./mds_mcdonalds.json"
|
||||
"import": "./cs_office.json"
|
||||
// "import": "./gm_construct.json"
|
||||
}
|
||||
46
engine/inc/uf/ext/openal/filter.h
Normal file
46
engine/inc/uf/ext/openal/filter.h
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <uf/config.h>
|
||||
#if UF_USE_OPENAL
|
||||
|
||||
#include <AL/al.h>
|
||||
#include <AL/alc.h>
|
||||
#include <AL/alut.h>
|
||||
#define AL_ALEXT_PROTOTYPES 1
|
||||
#include <AL/efx.h>
|
||||
namespace ext {
|
||||
namespace al {
|
||||
class Filter {
|
||||
protected:
|
||||
ALuint m_index = 0;
|
||||
public:
|
||||
void initialize();
|
||||
void destroy();
|
||||
ALuint getIndex() const;
|
||||
void set( ALenum name, ALfloat x );
|
||||
void set( ALenum name, ALint x );
|
||||
};
|
||||
class Effect {
|
||||
ALuint m_index = 0;
|
||||
public:
|
||||
void initialize();
|
||||
void destroy();
|
||||
ALuint getIndex() const;
|
||||
|
||||
void set( ALenum name, ALfloat x );
|
||||
void set( ALenum name, ALint x );
|
||||
};
|
||||
|
||||
class EffectSlot {
|
||||
ALuint m_index = 0;
|
||||
public:
|
||||
void initialize();
|
||||
void destroy();
|
||||
ALuint getIndex() const;
|
||||
|
||||
void set( ALenum name, ALfloat x );
|
||||
void set( ALenum name, ALint x );
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -27,6 +27,7 @@
|
||||
|
||||
#include "source.h"
|
||||
#include "buffer.h"
|
||||
#include "filter.h"
|
||||
#include <uf/utils/audio/metadata.h>
|
||||
#include <uf/utils/math/transform.h>
|
||||
|
||||
|
||||
@ -66,6 +66,10 @@ namespace uf {
|
||||
void UF_API rolloff( pod::AudioSource& source, float v );
|
||||
float UF_API maxDistance( const pod::AudioSource& source );
|
||||
void UF_API maxDistance( pod::AudioSource& source, float v );
|
||||
|
||||
float UF_API occlusion( const pod::Vector3f& position );
|
||||
void UF_API occlude( pod::AudioSource& source, float factor );
|
||||
void UF_API acoustics( const pod::Vector3f&, const pod::Quaternion<>&, float&, float&, int& );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,11 @@ namespace uf {
|
||||
typedef uf::stl::unordered_map<uf::stl::string, uf::stl::vector<pod::AudioSource>> container_t;
|
||||
protected:
|
||||
container_t m_container;
|
||||
|
||||
ext::al::Effect m_effect;
|
||||
ext::al::EffectSlot m_effectSlot;
|
||||
bool m_efxInitialized = false;
|
||||
uf::Timer<> m_acousticTimer = {true};
|
||||
public:
|
||||
~AudioEmitter();
|
||||
|
||||
@ -21,6 +26,7 @@ namespace uf {
|
||||
|
||||
void update();
|
||||
void update( const pod::Vector3f& position, const pod::Quaternion<>& orientation );
|
||||
void updateAcoustics( const pod::Vector3f& position, const pod::Quaternion<>& orientation );
|
||||
void cleanup( bool purge = false );
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include <uf/utils/math/transform.h>
|
||||
|
||||
#include <uf/utils/memory/string.h>
|
||||
#include <uf/utils/memory/vector.h>
|
||||
@ -50,9 +51,13 @@ namespace pod {
|
||||
|
||||
struct UF_API AudioSource {
|
||||
ext::al::Source alSource;
|
||||
#if !UF_ENV_DREAMCAST
|
||||
ext::al::Filter alFilter;
|
||||
#endif
|
||||
ext::al::Buffer streamBuffers;
|
||||
|
||||
pod::AudioClip* clip = NULL;
|
||||
pod::Transform<> transform;
|
||||
|
||||
struct {
|
||||
uf::Timer<> timer;
|
||||
|
||||
@ -51,6 +51,9 @@ namespace impl {
|
||||
pod::Vector3f triangleNormal( const pod::TriangleWithNormal& tri );
|
||||
pod::TriangleWithNormal fetchTriangle( const uf::Mesh& mesh, size_t triID, const pod::PhysicsBody& body );
|
||||
|
||||
float getMaterialTransmittance( const uf::stl::string& materialName );
|
||||
uf::stl::string getMaterialName( const pod::PhysicsBody& body, uint32_t triID );
|
||||
|
||||
/*FORCE_INLINE*/ bool aabbOverlap( const pod::AABB& a, const pod::AABB& b );
|
||||
/*FORCE_INLINE*/ float aabbSurfaceArea( const pod::AABB& aabb );
|
||||
/*FORCE_INLINE*/ pod::AABB computeSegmentAABB( const pod::Vector3f& p1, const pod::Vector3f p2, float r );
|
||||
|
||||
@ -57,7 +57,8 @@ namespace uf {
|
||||
pod::World& UF_API getWorld();
|
||||
const pod::CollisionEvent::events_t& UF_API getCollisionEvents( const pod::World& world );
|
||||
pod::CollisionEvent::events_t UF_API getCollisionEvents( const pod::PhysicsBody& body );
|
||||
|
||||
uf::stl::string UF_API getCollisionMaterialName( const pod::CollisionEvent& event );
|
||||
|
||||
pod::PhysicsBody& UF_API create( uf::Object&, float mass = 0.0f, const pod::Vector3f& = {}, const pod::Quaternion<>& = {0,0,0,1} );
|
||||
pod::PhysicsBody& UF_API create( pod::World&, uf::Object&, float mass = 0.0f, const pod::Vector3f& = {}, const pod::Quaternion<>& = {0,0,0,1} );
|
||||
void UF_API destroy( uf::Object& );
|
||||
|
||||
@ -22,5 +22,9 @@ namespace uf {
|
||||
pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::PhysicsBody&, float = FLT_MAX );
|
||||
pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::World&, float = FLT_MAX );
|
||||
pod::RayQuery UF_API rayCast( const pod::Ray&, const pod::World&, const pod::PhysicsBody*, float = FLT_MAX );
|
||||
|
||||
float UF_API occlusion( const pod::Vector3f& to, const pod::Vector3f& from );
|
||||
uf::stl::string UF_API getRayMaterialName( const pod::RayQuery& query );
|
||||
pod::AcousticBounce UF_API acousticReflection( const pod::Vector3f& sourcePos, const pod::Vector3f& rayDirection, const pod::Vector3f& listenerPos, float maxDistance );
|
||||
}
|
||||
}
|
||||
@ -217,6 +217,13 @@ namespace pod {
|
||||
pod::Vector3f rB;
|
||||
pod::Vector3f n;
|
||||
};
|
||||
|
||||
struct AcousticBounce {
|
||||
bool valid = false;
|
||||
float totalDistance = 0.0f;
|
||||
float retainedEnergy = 1.0f;
|
||||
pod::Vector3f hitPoint;
|
||||
};
|
||||
}
|
||||
|
||||
// BVH
|
||||
|
||||
@ -302,4 +302,11 @@ namespace std {
|
||||
};
|
||||
}
|
||||
|
||||
// maths
|
||||
namespace uf {
|
||||
namespace math {
|
||||
uf::stl::vector<pod::Vector3f> UF_API fibonacciSphere( int points );
|
||||
}
|
||||
}
|
||||
|
||||
#include "vector/vector.inl"
|
||||
@ -591,7 +591,7 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
metadata.audio.footstep.timer -= uf::physics::time::delta;
|
||||
if ( metadata.audio.footstep.timer <= 0.0f ) {
|
||||
// player/footsteps
|
||||
uf::stl::vector<uf::stl::string> surfaces = {
|
||||
static uf::stl::vector<uf::stl::string> surfaces = {
|
||||
"chainlink",
|
||||
"concrete",
|
||||
"dirt",
|
||||
@ -609,30 +609,22 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
|
||||
"wood",
|
||||
"woodpanel",
|
||||
};
|
||||
uf::stl::string filename = "concrete";
|
||||
uf::stl::string surface = "concrete";
|
||||
|
||||
auto events = uf::physics::getCollisionEvents( physicsBody );
|
||||
for ( const auto& event : events ) {
|
||||
if ( event.normal.y > -0.7f ) continue;
|
||||
auto triID = MIN( event.featureA, event.featureB );
|
||||
auto* other = event.a != &physicsBody ? event.a : event.b;
|
||||
if ( triID == (uint32_t)(-1) ) continue;
|
||||
if ( other->collider.type != pod::ShapeType::MESH ) continue;
|
||||
// to-do: sugar it up with a helper function
|
||||
auto& mesh = *other->collider.mesh.mesh;
|
||||
auto drawCommand = uf::mesh::fetchDrawCommand( mesh, triID );
|
||||
auto instance = uf::graph::getInstance( graph, drawCommand.instanceID );
|
||||
auto materialName = uf::graph::getMaterialName( graph, instance.materialID );
|
||||
if ( event.normal.y > -0.7f ) continue; // to-do: reorient?
|
||||
auto materialName = uf::physics::getCollisionMaterialName( event );
|
||||
|
||||
for ( auto& key : surfaces ) {
|
||||
if ( !uf::string::contains( materialName, key ) ) continue;
|
||||
filename = key;
|
||||
surface = key;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
filename = ::fmt::format("valve://sound/player/footsteps/{}{}.wav", filename, (rand() % 3) + 1 );
|
||||
uf::stl::string filename = ::fmt::format("valve://sound/player/footsteps/{}{}.wav", surface, (rand() % 3) + 1 );
|
||||
if ( !uf::asset::has(filename) ) {
|
||||
auto payload = uf::asset::resolveToPayload(filename, "");
|
||||
payload.type = uf::asset::Type::AUDIO;
|
||||
|
||||
@ -1298,8 +1298,12 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
loadJson["assets"].emplace_back("ent://scripts/io.lua");
|
||||
}
|
||||
|
||||
// bind ambient
|
||||
if ( node.name.starts_with("ambient_") ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/ambient_generic.lua");
|
||||
loadJson["behaviors"].emplace_back("AudioEmitterBehavior");
|
||||
// bind door script
|
||||
if ( ext::json::isObject( metadataValve["door"] ) ) {
|
||||
} else if ( ext::json::isObject( metadataValve["door"] ) ) {
|
||||
node.metadata["door"] = metadataValve["door"];
|
||||
loadJson["imports"].emplace_back("ent://door.json");
|
||||
}
|
||||
@ -1335,7 +1339,14 @@ void uf::graph::process( pod::Graph& graph, int32_t index, uf::Object& parent )
|
||||
}
|
||||
|
||||
// check if trigger
|
||||
if ( 0 <= node.mesh && node.mesh < graph.meshes.size() ) {
|
||||
if ( node.name.starts_with("trigger_") ) {
|
||||
loadJson["assets"].emplace_back("ent://scripts/trigger.lua");
|
||||
// signal to assign a physics body
|
||||
if ( ext::json::isNull( node.metadata["physics"] ) ) {
|
||||
node.metadata["physics"]["type"] = "bounding box";
|
||||
node.metadata["physics"]["category"] = "trigger";
|
||||
}
|
||||
} else if ( 0 <= node.mesh && node.mesh < graph.meshes.size() ) {
|
||||
auto& primitives = storage.primitives.map[graph.primitives[node.mesh]];
|
||||
for ( auto& primitive : primitives ) {
|
||||
auto materialID = primitive.instance.materialID;
|
||||
|
||||
@ -32,7 +32,7 @@ void ext::al::initialize() {
|
||||
}
|
||||
|
||||
ALboolean enumeration = alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");
|
||||
if (enumeration == AL_FALSE) {
|
||||
if ( enumeration == AL_FALSE ) {
|
||||
// do something
|
||||
UF_EXCEPTION("Device enumeration not available");
|
||||
}
|
||||
@ -42,6 +42,11 @@ void ext::al::initialize() {
|
||||
UF_EXCEPTION(ext::al::getError());
|
||||
}
|
||||
#endif
|
||||
|
||||
ALboolean hasEfx = alcIsExtensionPresent(::device, "ALC_EXT_EFX");
|
||||
if ( hasEfx == AL_FALSE ) {
|
||||
UF_MSG_WARNING("AL_EXT_EFX not supported! Spatial audio will lack occlusion.");
|
||||
}
|
||||
}
|
||||
void ext::al::destroy() {
|
||||
#if UF_USE_ALUT
|
||||
@ -94,6 +99,7 @@ void ext::al::Source::get( ALenum name, ALint& x ) const { AL_CHECK_RESULT_ENUM(
|
||||
void ext::al::Source::get( ALenum name, ALint& x, ALint& y, ALint& z ) const { AL_CHECK_RESULT_ENUM(alGetSource3i, this->m_index, name, &x, &y, &z ); }
|
||||
void ext::al::Source::get( ALenum name, ALint* f ) const { AL_CHECK_RESULT_ENUM(alGetSourceiv, this->m_index, name, f ); }
|
||||
|
||||
// string=>enum not used internally at the moment
|
||||
void ext::al::Source::get( const uf::stl::string& string, ALfloat& x ) const {
|
||||
// alSourcef
|
||||
if ( string == "PITCH" ) return this->get( AL_PITCH, x );
|
||||
@ -241,4 +247,66 @@ void ext::al::Buffer::buffer(ALenum format, const ALvoid* data, ALsizei size, AL
|
||||
if ( !this->initialized() ) this->initialize();
|
||||
AL_CHECK_RESULT(alBufferData(this->m_indices[i], format, data, size, frequency));
|
||||
}
|
||||
|
||||
void ext::al::Filter::initialize() {
|
||||
if ( this->m_index ) this->destroy();
|
||||
AL_CHECK_RESULT(alGenFilters(1, &this->m_index));
|
||||
AL_CHECK_RESULT(alFilteri(this->m_index, AL_FILTER_TYPE, AL_FILTER_LOWPASS));
|
||||
}
|
||||
void ext::al::Filter::destroy() {
|
||||
if ( this->m_index && alIsFilter(this->m_index) ) {
|
||||
AL_CHECK_RESULT(alDeleteFilters(1, &this->m_index));
|
||||
}
|
||||
this->m_index = 0;
|
||||
}
|
||||
ALuint ext::al::Filter::getIndex() const {
|
||||
return this->m_index;
|
||||
}
|
||||
void ext::al::Filter::set( ALenum name, ALfloat x ) {
|
||||
AL_CHECK_RESULT_ENUM( alFilterf, this->m_index, name, x );
|
||||
}
|
||||
void ext::al::Filter::set( ALenum name, ALint x ) {
|
||||
AL_CHECK_RESULT_ENUM( alFilteri, this->m_index, name, x );
|
||||
}
|
||||
|
||||
void ext::al::Effect::initialize() {
|
||||
if ( this->m_index ) this->destroy();
|
||||
|
||||
AL_CHECK_RESULT(alGenEffects(1, &this->m_index));
|
||||
AL_CHECK_RESULT(alEffecti(this->m_index, AL_EFFECT_TYPE, AL_EFFECT_REVERB));
|
||||
}
|
||||
|
||||
void ext::al::Effect::destroy() {
|
||||
if ( this->m_index && alIsEffect(this->m_index) ) {
|
||||
AL_CHECK_RESULT(alDeleteEffects(1, &this->m_index));
|
||||
}
|
||||
this->m_index = 0;
|
||||
}
|
||||
|
||||
ALuint ext::al::Effect::getIndex() const { return this->m_index; }
|
||||
void ext::al::Effect::set( ALenum name, ALfloat x ) {
|
||||
AL_CHECK_RESULT_ENUM( alEffectf, this->m_index, name, x );
|
||||
}
|
||||
void ext::al::Effect::set( ALenum name, ALint x ) {
|
||||
AL_CHECK_RESULT_ENUM( alEffecti, this->m_index, name, x );
|
||||
}
|
||||
|
||||
void ext::al::EffectSlot::initialize() {
|
||||
if ( this->m_index ) this->destroy();
|
||||
AL_CHECK_RESULT(alGenAuxiliaryEffectSlots(1, &this->m_index));
|
||||
}
|
||||
void ext::al::EffectSlot::destroy() {
|
||||
if ( this->m_index && alIsAuxiliaryEffectSlot(this->m_index) ) {
|
||||
AL_CHECK_RESULT(alDeleteAuxiliaryEffectSlots(1, &this->m_index));
|
||||
}
|
||||
this->m_index = 0;
|
||||
}
|
||||
ALuint ext::al::EffectSlot::getIndex() const { return this->m_index; }
|
||||
void ext::al::EffectSlot::set( ALenum name, ALfloat x ) {
|
||||
AL_CHECK_RESULT_ENUM( alAuxiliaryEffectSlotf, this->m_index, name, x );
|
||||
}
|
||||
void ext::al::EffectSlot::set( ALenum name, ALint x ) {
|
||||
AL_CHECK_RESULT_ENUM( alAuxiliaryEffectSloti, this->m_index, name, x );
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -595,11 +595,7 @@ namespace impl {
|
||||
if ( classname.starts_with("func_door") ) {
|
||||
auto& metadataDoor = metadata["door"];
|
||||
|
||||
metadataDoor["speed"] = metadata["speed"].as<float>(100.0f);
|
||||
metadataDoor["wait"] = metadata["wait"].as<float>(4.0f);
|
||||
metadataDoor["lip"] = metadata["lip"].as<float>(8.0f);
|
||||
metadataDoor["spawnflags"] = metadata["spawnflags"].as<int>(0);
|
||||
|
||||
float scale = impl::sourceToMeters;
|
||||
if ( classname == "func_door" ) {
|
||||
auto movedirStr = metadata["movedir"].as<uf::stl::string>("");
|
||||
if ( movedirStr == "" && metadata["angle"].as<float>() ) {
|
||||
@ -610,14 +606,20 @@ namespace impl {
|
||||
}
|
||||
|
||||
if ( movedirStr != "" ) {
|
||||
pod::Transform<> t;
|
||||
auto pyr = impl::str2vec<pod::Vector3f>( movedirStr ) * -DEG_2_RAD;
|
||||
t.orientation = uf::quaternion::euler( pyr );
|
||||
auto axes = uf::transform::axes( t );
|
||||
auto pyr = impl::str2vec<pod::Vector3f>( movedirStr );
|
||||
|
||||
metadataDoor["direction"] = uf::vector::encode( axes.forward );
|
||||
float pitch = pyr.x * DEG_2_RAD;
|
||||
float yaw = pyr.y * DEG_2_RAD;
|
||||
|
||||
pod::Vector3f slideDir;
|
||||
slideDir.x = cos(yaw) * cos(pitch);
|
||||
slideDir.z = -(sin(yaw) * cos(pitch));
|
||||
slideDir.y = -sin(pitch);
|
||||
|
||||
metadataDoor["direction"] = uf::vector::encode( uf::vector::normalize(slideDir) );
|
||||
}
|
||||
} else if ( classname == "func_door_rotating" ) {
|
||||
scale = 1.0f;
|
||||
metadataDoor["distance"] = metadata["distance"].as<float>(90.0f);
|
||||
|
||||
int flags = metadataDoor["spawnflags"].as<int>();
|
||||
@ -627,6 +629,11 @@ namespace impl {
|
||||
|
||||
metadataDoor["axis"] = uf::vector::encode( axis );
|
||||
}
|
||||
|
||||
metadataDoor["speed"] = metadata["speed"].as<float>(100.0f) * scale;
|
||||
metadataDoor["wait"] = metadata["wait"].as<float>(4.0f);
|
||||
metadataDoor["lip"] = metadata["lip"].as<float>(8.0f) * scale;
|
||||
metadataDoor["spawnflags"] = metadata["spawnflags"].as<int>(0);
|
||||
}
|
||||
|
||||
// parse parent
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include <uf/utils/audio/audio.h>
|
||||
#include <uf/utils/string/ext.h>
|
||||
#include <uf/utils/thread/thread.h>
|
||||
#include <uf/utils/math/physics.h>
|
||||
|
||||
#if UF_USE_OPENAL
|
||||
#include <uf/ext/openal/openal.h>
|
||||
@ -16,6 +17,11 @@
|
||||
bool uf::audio::muted = true;
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
ext::al::Filter occlusionFilter;
|
||||
pod::Transform<> listener;
|
||||
}
|
||||
|
||||
bool uf::audio::asyncUpdate = false;
|
||||
bool uf::audio::streamsByDefault = true;
|
||||
uint8_t uf::audio::buffers = 4;
|
||||
@ -36,9 +42,11 @@ void uf::audio::initialize( pod::AudioSource& source ) {
|
||||
source.alSource.initialize();
|
||||
source.alSource.set( AL_PITCH, 1.0f );
|
||||
source.alSource.set( AL_GAIN, 1.0f );
|
||||
}
|
||||
|
||||
//
|
||||
#if !UF_ENV_DREAMCAST
|
||||
source.alFilter.initialize();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool uf::audio::load( pod::AudioClip& clip, const uf::stl::string& filename, bool streamed ) {
|
||||
uf::audio::destroy( clip );
|
||||
@ -118,8 +126,11 @@ void uf::audio::destroy( pod::AudioSource& source ) {
|
||||
else if ( source.clip->extension == "pcm" ) ext::pcm::close( source );
|
||||
}
|
||||
|
||||
source.alSource.destroy();
|
||||
source.clip = nullptr;
|
||||
source.alSource.destroy();
|
||||
#if !UF_ENV_DREAMCAST
|
||||
source.alFilter.destroy();
|
||||
#endif
|
||||
}
|
||||
|
||||
void uf::audio::destroy( pod::AudioClip& clip ) {
|
||||
@ -131,6 +142,7 @@ void uf::audio::destroy( pod::AudioClip& clip ) {
|
||||
|
||||
void uf::audio::listener( const pod::Transform<>& transform ) {
|
||||
if ( uf::audio::muted ) return;
|
||||
::listener = transform;
|
||||
auto axes = uf::transform::axes( transform );
|
||||
axes.forward *= -1;
|
||||
float o[6] = { axes.forward.x, axes.forward.y, axes.forward.z, axes.up.x, axes.up.y, axes.up.z };
|
||||
@ -147,11 +159,12 @@ void uf::audio::loop( pod::AudioSource& source, bool state ) {
|
||||
}
|
||||
|
||||
void uf::audio::position( pod::AudioSource& source, const pod::Vector3f& v ) {
|
||||
source.transform.position = v;
|
||||
source.alSource.set( AL_POSITION, v[0], v[1], v[2] );
|
||||
}
|
||||
|
||||
void uf::audio::orientation( pod::AudioSource& source, const pod::Quaternion<>& q ) {
|
||||
// to-do: set
|
||||
source.transform.orientation = q;
|
||||
}
|
||||
|
||||
float uf::audio::time( const pod::AudioSource& source ) {
|
||||
@ -191,4 +204,40 @@ float uf::audio::maxDistance( const pod::AudioSource& source ) {
|
||||
}
|
||||
void uf::audio::maxDistance( pod::AudioSource& source, float v ) {
|
||||
source.alSource.set( AL_MAX_DISTANCE, v );
|
||||
}
|
||||
|
||||
//
|
||||
float uf::audio::occlusion( const pod::Vector3f& position ) {
|
||||
return uf::physics::occlusion( ::listener.position, position );
|
||||
}
|
||||
|
||||
void uf::audio::occlude( pod::AudioSource& source, float factor ) {
|
||||
#if UF_ENV_DREAMCAST
|
||||
uf::audio::gain( source, factor );
|
||||
#else
|
||||
if ( factor >= 0.99f ) {
|
||||
source.alSource.set( AL_DIRECT_FILTER, AL_FILTER_NULL );
|
||||
} else {
|
||||
ALuint filterId = source.alFilter.getIndex();
|
||||
|
||||
AL_CHECK_RESULT(alFilterf( filterId, AL_LOWPASS_GAINHF, factor ));
|
||||
AL_CHECK_RESULT(alFilterf( filterId, AL_LOWPASS_GAIN, 0.8f ));
|
||||
|
||||
source.alSource.set( AL_DIRECT_FILTER, (ALint)filterId );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void uf::audio::acoustics( const pod::Vector3f& position, const pod::Quaternion<>& orientation, float& energy, float& distance, int& bounces ) {
|
||||
#if !UF_ENV_DREAMCAST
|
||||
static uf::stl::vector<pod::Vector3f> sphere = uf::math::fibonacciSphere( 32 );
|
||||
for ( const auto& dir : sphere ) {
|
||||
auto bounce = uf::physics::acousticReflection( position, dir, ::listener.position, 50.0f );
|
||||
if ( bounce.valid ) {
|
||||
energy += bounce.retainedEnergy;
|
||||
distance += bounce.totalDistance;
|
||||
bounces++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -36,16 +36,83 @@ void uf::AudioEmitter::update() {
|
||||
}
|
||||
|
||||
void uf::AudioEmitter::update( const pod::Vector3f& position, const pod::Quaternion<>& orientation ) {
|
||||
bool spatial = false;
|
||||
for ( auto& pair : this->m_container ) {
|
||||
for ( auto& source : pair.second ) {
|
||||
if ( source.alSource.playing() && source.settings.spatial ) {
|
||||
spatial = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( spatial ) break;
|
||||
}
|
||||
|
||||
float occlusionValue = 1.0f;
|
||||
if ( spatial ) {
|
||||
occlusionValue = uf::audio::occlusion( position );
|
||||
|
||||
if ( this->m_acousticTimer.elapsed().asDouble() >= 0.1 ) {
|
||||
this->updateAcoustics( position, orientation );
|
||||
this->m_acousticTimer.reset();
|
||||
}
|
||||
}
|
||||
|
||||
for ( auto& pair : this->m_container ) {
|
||||
for ( auto& source : pair.second ) {
|
||||
if ( !source.alSource.playing() ) continue;
|
||||
uf::audio::position( source, position );
|
||||
uf::audio::orientation( source, orientation );
|
||||
|
||||
if ( source.settings.spatial ) {
|
||||
uf::audio::position( source, position );
|
||||
uf::audio::orientation( source, orientation );
|
||||
uf::audio::occlude( source, occlusionValue );
|
||||
|
||||
if ( m_efxInitialized ) {
|
||||
AL_CHECK_RESULT(alSource3i( source.alSource.getIndex(), AL_AUXILIARY_SEND_FILTER, (ALint)m_effectSlot.getIndex(), 0, AL_FILTER_NULL ));
|
||||
}
|
||||
} else {
|
||||
if ( m_efxInitialized ) {
|
||||
AL_CHECK_RESULT(alSource3i( source.alSource.getIndex(), AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL ));
|
||||
}
|
||||
}
|
||||
|
||||
uf::audio::update( source );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uf::AudioEmitter::updateAcoustics( const pod::Vector3f& position, const pod::Quaternion<>& orientation ) {
|
||||
|
||||
if ( !m_efxInitialized ) {
|
||||
m_effect.initialize();
|
||||
m_effectSlot.initialize();
|
||||
m_efxInitialized = true;
|
||||
}
|
||||
|
||||
float energy = 0.0f;
|
||||
float distance = 0.0f;
|
||||
int bounces = 0;
|
||||
|
||||
uf::audio::acoustics( position, orientation, energy, distance, bounces );
|
||||
|
||||
if ( bounces == 0 ) {
|
||||
m_effect.set( AL_REVERB_GAIN, 0.0f );
|
||||
m_effectSlot.set( AL_EFFECTSLOT_EFFECT, (ALint)m_effect.getIndex() );
|
||||
return;
|
||||
}
|
||||
|
||||
float avgEnergy = energy / bounces;
|
||||
float avgDistance = distance / bounces;
|
||||
float decayTime = std::max( 0.1f, std::min( (avgDistance / 15.0f) * avgEnergy, 1.5f ) );
|
||||
float gain = avgEnergy * 0.3f;
|
||||
float hfRatio = std::max( 0.1f, 1.0f - (avgDistance / 30.0f) );
|
||||
|
||||
m_effect.set( AL_REVERB_DECAY_TIME, decayTime );
|
||||
m_effect.set( AL_REVERB_GAIN, gain );
|
||||
m_effect.set( AL_REVERB_DECAY_HFRATIO, hfRatio );
|
||||
m_effect.set( AL_REVERB_DIFFUSION, 1.0f );
|
||||
m_effectSlot.set( AL_EFFECTSLOT_EFFECT, (ALint)m_effect.getIndex() );
|
||||
}
|
||||
|
||||
void uf::AudioEmitter::cleanup( bool purge ) {
|
||||
for ( auto mapIt = this->m_container.begin(); mapIt != this->m_container.end(); ) {
|
||||
auto& pool = mapIt->second;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#include <uf/utils/math/math.h>
|
||||
#include <uf/utils/math/vector.h>
|
||||
|
||||
uint16_t uf::math::quantizeShort( float v ) {
|
||||
union { float f; uint32_t ui; } u = {v};
|
||||
@ -13,4 +14,24 @@ uint16_t uf::math::quantizeShort( float v ) {
|
||||
}
|
||||
float uf::math::unquantize( uint16_t v ) {
|
||||
return v;
|
||||
}
|
||||
|
||||
uf::stl::vector<pod::Vector3f> uf::math::fibonacciSphere( int num ) {
|
||||
uf::stl::vector<pod::Vector3f> points;
|
||||
points.reserve( num );
|
||||
|
||||
const float goldenAngle = M_PI * ( 3.0f - std::sqrt( 5.0f ) );
|
||||
for ( auto i = 0; i < num; ++i ) {
|
||||
float y = 1.0f - ( i / float( num - 1 ) ) * 2.0f;
|
||||
float radius = std::sqrt( 1.0f - y * y );
|
||||
float theta = goldenAngle * i;
|
||||
|
||||
points.emplace_back( pod::Vector3f{
|
||||
.x = std::cos( theta ) * radius,
|
||||
.y = y,
|
||||
.z = std::sin( theta ) * radius,
|
||||
});
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
#include <uf/utils/math/physics/common.h>
|
||||
#include <uf/engine/graph/graph.h>
|
||||
|
||||
// create ID from pointers
|
||||
size_t impl::makePairKey( const pod::PhysicsBody& a, const pod::PhysicsBody& b ) {
|
||||
@ -791,4 +792,33 @@ pod::AABB impl::transformAabbToLocal( const pod::AABB& box, const pod::Transform
|
||||
out.max = uf::vector::max( out.max, local );
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
float impl::getMaterialTransmittance( const uf::stl::string& materialName ) {
|
||||
if ( uf::string::contains(materialName, "chainlink") ||
|
||||
uf::string::contains(materialName, "metalgrate") ) return 0.9f;
|
||||
|
||||
// woods
|
||||
if ( uf::string::contains(materialName, "wood") ) return 0.4f;
|
||||
|
||||
// hollow metal
|
||||
if ( uf::string::contains(materialName, "duct") ) return 0.7f;
|
||||
|
||||
// solid dense materials
|
||||
if ( uf::string::contains(materialName, "concrete") ||
|
||||
uf::string::contains(materialName, "metal") ||
|
||||
uf::string::contains(materialName, "dirt") ) return 0.15f;
|
||||
|
||||
return 0.2f;
|
||||
}
|
||||
uf::stl::string impl::getMaterialName( const pod::PhysicsBody& body, uint32_t triID ) {
|
||||
if ( triID == (uint32_t)(-1) ) return "";
|
||||
if ( body.collider.type != pod::ShapeType::MESH ) return "";
|
||||
auto& scene = uf::scene::getCurrentScene();
|
||||
auto& graph = scene.getComponent<pod::Graph>();
|
||||
|
||||
auto& mesh = *body.collider.mesh.mesh;
|
||||
auto drawCommand = uf::mesh::fetchDrawCommand( mesh, triID );
|
||||
auto instance = uf::graph::getInstance( graph, drawCommand.instanceID );
|
||||
return uf::graph::getMaterialName( graph, instance.materialID );
|
||||
}
|
||||
@ -8,6 +8,7 @@
|
||||
#include <uf/utils/math/physics/constraints.h>
|
||||
|
||||
#include <uf/engine/scene/scene.h>
|
||||
#include <uf/engine/graph/graph.h>
|
||||
|
||||
#define UF_PHYSICS_TEST 0
|
||||
|
||||
@ -476,7 +477,11 @@ 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 );
|
||||
body.velocity = v;
|
||||
if ( body.inverseMass == 0.0f ) {
|
||||
body.transform->position += v * uf::physics::time::delta;
|
||||
} else {
|
||||
body.velocity = v;
|
||||
}
|
||||
}
|
||||
void uf::physics::applyVelocity( pod::PhysicsBody& body, const pod::Vector3f& v ) {
|
||||
impl::wakeBody( body );
|
||||
@ -543,6 +548,12 @@ pod::CollisionEvent::events_t uf::physics::getCollisionEvents( const pod::Physic
|
||||
}
|
||||
return res;
|
||||
}
|
||||
uf::stl::string uf::physics::getCollisionMaterialName( const pod::CollisionEvent& event ) {
|
||||
auto triID = MIN( event.featureA, event.featureB );
|
||||
auto* other = event.a->collider.type == pod::ShapeType::MESH ? event.a : event.b;
|
||||
return impl::getMaterialName( *other, triID );
|
||||
}
|
||||
|
||||
// body creation
|
||||
pod::PhysicsBody& uf::physics::create( pod::World& world, uf::Object& object, float mass, const pod::Vector3f& positionOffset, const pod::Quaternion<>& orientationOffset ) {
|
||||
auto& root = object.getComponent<pod::PhysicsBody>();
|
||||
|
||||
@ -348,4 +348,69 @@ pod::RayQuery uf::physics::rayCast( const pod::Ray& ray, const pod::World& world
|
||||
if ( uf::physics::settings.debugDraw.rays ) impl::drawRay( ray, rayHit );
|
||||
|
||||
return rayHit;
|
||||
}
|
||||
|
||||
float uf::physics::occlusion( const pod::Vector3f& to, const pod::Vector3f& from ) {
|
||||
pod::Vector3f dir = from - to;
|
||||
float mag = uf::vector::magnitude( dir );
|
||||
if ( mag <= EPS ) return 1.0f;
|
||||
float dist = std::sqrt( mag );
|
||||
dir /= dist;
|
||||
|
||||
pod::Ray ray;
|
||||
ray.origin = from;
|
||||
ray.direction = dir;
|
||||
|
||||
pod::RayQuery hit = uf::physics::rayCast( ray, uf::physics::getWorld(), dist );
|
||||
|
||||
if ( hit.contact.penetration >= dist ) return 1.0f;
|
||||
|
||||
auto materialName = uf::physics::getRayMaterialName( hit );
|
||||
return impl::getMaterialTransmittance( materialName );
|
||||
}
|
||||
uf::stl::string uf::physics::getRayMaterialName( const pod::RayQuery& query ) {
|
||||
return impl::getMaterialName( *query.body, query.contact.featureA );
|
||||
}
|
||||
|
||||
pod::AcousticBounce uf::physics::acousticReflection( const pod::Vector3f& sourcePos, const pod::Vector3f& rayDirection, const pod::Vector3f& listenerPos, float maxDistance ) {
|
||||
pod::AcousticBounce result;
|
||||
|
||||
pod::Ray primaryRay;
|
||||
primaryRay.origin = sourcePos;
|
||||
primaryRay.direction = rayDirection;
|
||||
|
||||
pod::RayQuery firstHit = uf::physics::rayCast( primaryRay, uf::physics::getWorld(), maxDistance );
|
||||
|
||||
if ( firstHit.contact.penetration >= maxDistance ) return result;
|
||||
|
||||
auto materialName = uf::physics::getRayMaterialName( firstHit );
|
||||
float transmittance = impl::getMaterialTransmittance( materialName );
|
||||
|
||||
float reflectance = 1.0f - transmittance;
|
||||
|
||||
pod::Vector3f N = firstHit.contact.normal;
|
||||
pod::Vector3f D = rayDirection;
|
||||
|
||||
float dotProduct = uf::vector::dot( D, N );
|
||||
pod::Vector3f reflectDir = D - (N * (2.0f * dotProduct));
|
||||
uf::vector::normalize( reflectDir );
|
||||
|
||||
pod::Vector3f bounceOrigin = firstHit.contact.point + (N * EPS);
|
||||
pod::Vector3f toListener = listenerPos - bounceOrigin;
|
||||
float distToListener = uf::vector::magnitude( toListener );
|
||||
|
||||
pod::Ray secondaryRay;
|
||||
secondaryRay.origin = bounceOrigin;
|
||||
secondaryRay.direction = toListener / distToListener;
|
||||
|
||||
pod::RayQuery secondHit = uf::physics::rayCast( secondaryRay, uf::physics::getWorld(), distToListener );
|
||||
|
||||
if ( secondHit.contact.penetration >= distToListener ) {
|
||||
result.valid = true;
|
||||
result.totalDistance = firstHit.contact.penetration + distToListener;
|
||||
result.retainedEnergy = reflectance;
|
||||
result.hitPoint = firstHit.contact.point;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user