From 6874d6f4101a393e56b9d631b3ced7a7b4a2a43f Mon Sep 17 00:00:00 2001 From: harfang3dadmin Date: Fri, 31 Dec 2021 16:59:26 +0100 Subject: [PATCH] 3.1.1 release. --- binding/bind_harfang.py | 11 ++- doc/doc/GetSourceDuration.md | 1 + doc/doc/SetSourceTimecode.md | 1 + doc/doc/man.Assets.md | 10 +-- doc/doc/man.CPython.md | 2 +- doc/doc/man.Overview.md | 4 +- harfang/engine/audio.cpp | 45 +++++++--- harfang/engine/audio.h | 26 +++--- harfang/engine/component.cpp | 4 +- harfang/engine/node.h | 4 +- harfang/engine/scene.cpp | 8 +- harfang/engine/scene.h | 10 +-- harfang/engine/scene_bullet3_physics.cpp | 20 +++-- harfang/engine/scene_load_binary.cpp | 22 ++++- harfang/engine/scene_load_json.cpp | 18 ++++ harfang/engine/wav_audio_stream.cpp | 3 +- harfang/tests/t_audio.cpp | 106 +++++++++++++++++++---- harfang/tests/t_scene.cpp | 60 ++++++++----- harfang/version.txt | 2 +- readme.md | 28 +++++- release-notes.md | 19 ++++ 21 files changed, 298 insertions(+), 106 deletions(-) create mode 100644 doc/doc/GetSourceDuration.md create mode 100644 doc/doc/SetSourceTimecode.md diff --git a/binding/bind_harfang.py b/binding/bind_harfang.py index d07c535..5e7abbe 100644 --- a/binding/bind_harfang.py +++ b/binding/bind_harfang.py @@ -971,8 +971,8 @@ def bind_scene(gen): gen.bind_method(rigid_body, 'GetLinearDamping', 'float', []) gen.bind_method(rigid_body, 'SetLinearDamping', 'void', ['float damping']) - gen.bind_method(rigid_body, 'GetAngularDamping', 'hg::Vec3', []) - gen.bind_method(rigid_body, 'SetAngularDamping', 'void', ['const hg::Vec3 &damping']) + gen.bind_method(rigid_body, 'GetAngularDamping', 'float', []) + gen.bind_method(rigid_body, 'SetAngularDamping', 'void', ['float damping']) gen.bind_method(rigid_body, 'GetRestitution', 'float', []) gen.bind_method(rigid_body, 'SetRestitution', 'void', ['float restitution']) gen.bind_method(rigid_body, 'GetFriction', 'float', []) @@ -2132,6 +2132,7 @@ static void _SetViewTransform(bgfx::ViewId view_id, const hg::Mat4 &view, const ('TF_SamplerMagAnisotropic', 'BGFX_SAMPLER_MAG_ANISOTROPIC'), ('TF_BlitDestination', 'BGFX_TEXTURE_BLIT_DST'), ('TF_ReadBack', 'BGFX_TEXTURE_READ_BACK'), + ('TF_RenderTarget', 'BGFX_TEXTURE_RT') ], 'TextureFlags') gen.bind_function('hg::LoadTextureFlagsFromFile', 'uint64_t', ['const std::string &path'], {'rval_constants_group': 'TextureFlags'}) @@ -2430,10 +2431,11 @@ static void _FrameBuffer_GetTextures(hg::FrameBuffer &framebuffer, hg::Texture & color = hg::GetColorTexture(framebuffer); depth = hg::GetDepthTexture(framebuffer); } +static bool _IsValid(const hg::FrameBuffer &fb) { return bgfx::isValid(fb.handle); } ''') gen.bind_function('GetTextures', 'void', ['hg::FrameBuffer &framebuffer', 'hg::Texture &color', 'hg::Texture &depth'], {'route': route_lambda('_FrameBuffer_GetTextures'), 'arg_out': ['color', 'depth']}) gen.bind_function('hg::DestroyFrameBuffer', 'void', ['hg::FrameBuffer &frameBuffer']) - + gen.bind_function('hg::IsValid', 'bool', ['const hg::FrameBuffer &fb'], {'route': route_lambda('_IsValid')}) # vertices = gen.begin_class('hg::Vertices') gen.bind_constructor(vertices, ['const bgfx::VertexLayout &decl', 'size_t count']) @@ -4166,8 +4168,9 @@ static hg::SpatializedSourceState *__ConstructSpatializedSourceState(hg::Mat4 mt gen.bind_function('hg::StreamWAVFileSpatialized', 'hg::SourceRef', ['const char *path', 'const hg::SpatializedSourceState &state'], {'rval_constants_group': 'SourceRef'}) gen.bind_function('hg::StreamWAVAssetSpatialized', 'hg::SourceRef', ['const char *name', 'const hg::SpatializedSourceState &state'], {'rval_constants_group': 'SourceRef'}) + gen.bind_function('hg::GetSourceDuration', 'hg::time_ns', ['hg::SourceRef source'], {'constants_group': {'source': 'SourceRef'}}) gen.bind_function('hg::GetSourceTimecode', 'hg::time_ns', ['hg::SourceRef source'], {'constants_group': {'source': 'SourceRef'}}) - # [unimplemented] gen.bind_function('hg::SetSourceStreamTimecode', 'bool', ['hg::SourceRef source', 'hg::time_ns t']) + gen.bind_function('hg::SetSourceTimecode', 'bool', ['hg::SourceRef source', 'hg::time_ns t'], {'constants_group': {'source': 'SourceRef'}}) gen.bind_function('hg::SetSourceVolume', 'void', ['hg::SourceRef source', 'float volume'], {'constants_group': {'source': 'SourceRef'}}) gen.bind_function('hg::SetSourcePanning', 'void', ['hg::SourceRef source', 'float panning'], {'constants_group': {'source': 'SourceRef'}}) diff --git a/doc/doc/GetSourceDuration.md b/doc/doc/GetSourceDuration.md new file mode 100644 index 0000000..b674c80 --- /dev/null +++ b/doc/doc/GetSourceDuration.md @@ -0,0 +1 @@ + Return the duration of an audio source. \ No newline at end of file diff --git a/doc/doc/SetSourceTimecode.md b/doc/doc/SetSourceTimecode.md new file mode 100644 index 0000000..ca50282 --- /dev/null +++ b/doc/doc/SetSourceTimecode.md @@ -0,0 +1 @@ +Set timecode of the audio source. \ No newline at end of file diff --git a/doc/doc/man.Assets.md b/doc/doc/man.Assets.md index 29756aa..77d3961 100644 --- a/doc/doc/man.Assets.md +++ b/doc/doc/man.Assets.md @@ -1,22 +1,22 @@ .title Resources & Assets -By convention, production files are called: **resources** (eg. *the project resources*). +By convention, production files are called **resources** (eg. *the project resources*). -Files issued from the compilation of production files for a specific target are called: **assets** (eg. *the project assets for Windows PC*). +Files issued from the compilation of production files for a specific target are called **assets** (eg. *the project assets for Windows PC*). [TOC] ## Resource Formats -During development resources are stored in *production formats*, these formats are meant for efficient editing. Before they can be loaded at runtime, resources must be compiled into their *runtime formats* as assets which are specific to the target platform. +During development resources are stored in *production formats*, these formats are meant for editing. Before they can be loaded at runtime, resources must be compiled into their *runtime formats* as assets specific to the target platform. To compile a project resources use: ```text -assetc [] +assetc [] ``` -If no output folder is specified the input folder suffixed with *_compiled* is implied as output. +If no output folder is specified the input folder suffixed with *_compiled* is implied. Upon execution the assets compiler will scan the input folder and compile all supported resource types to assets. Unsupported resources are copied untransformed to the output folder. diff --git a/doc/doc/man.CPython.md b/doc/doc/man.CPython.md index ba18617..a165150 100644 --- a/doc/doc/man.CPython.md +++ b/doc/doc/man.CPython.md @@ -4,7 +4,7 @@ Harfang for Python is distributed as a wheel package `(.whl)` compatible with of ## Installation -* On Windows, Install from the command line by typing `pip install harfang`. +* On Windows, install from the command line by typing `pip install harfang`. * On Linux, download the wheel for your distribution then install from a terminal by typing `pip install /path/to/harfang.whl`. ## Testing your Installation diff --git a/doc/doc/man.Overview.md b/doc/doc/man.Overview.md index 1974714..9c20008 100644 --- a/doc/doc/man.Overview.md +++ b/doc/doc/man.Overview.md @@ -2,11 +2,11 @@ Harfang is a high-level software library to create applications that display 2D/3D visuals and play sound/music. -It provides a unified API to write programs using different programming languages and is available for Windows and Linux (for more details, please refer to the [man.Requirements]). +It provides a unified API to write programs using different programming languages and is available for Windows and Linux. For more details, see [man.Requirements]. ## Supported Programming Languages -For installation instructions for each supported language please refer to the corresponding manual page: +The installation instructions for each supported language are found in the following manual pages: * [man.CPython] * [man.Lua] diff --git a/harfang/engine/audio.cpp b/harfang/engine/audio.cpp index 03ed248..6d38554 100644 --- a/harfang/engine/audio.cpp +++ b/harfang/engine/audio.cpp @@ -96,7 +96,18 @@ static bool CheckALSuccess(const char *file = "unknown", ALuint line = 0) { } while (0); // -static const ALenum AFF_ALFormat[AFF_Count] = {AL_FORMAT_MONO16, AL_FORMAT_MONO16, AL_FORMAT_STEREO16, AL_FORMAT_STEREO16}; +static inline int AFF_ALFormat(AudioFrameFormat fmt) { + switch (fmt) { + case AFF_LPCM_44KHZ_S16_Mono: + case AFF_LPCM_48KHZ_S16_Mono: + return AL_FORMAT_MONO16; + case AFF_LPCM_44KHZ_S16_Stereo: + case AFF_LPCM_48KHZ_S16_Stereo: + return AL_FORMAT_STEREO16; + default: + return 0; + }; +} // static void AllocStream(ALStream &stream) { @@ -172,16 +183,19 @@ static bool UpdateSourceStream(SourceRef src_ref) { stream.buffers_format[stream.put] = pcm_format; __AL_CALL( - alBufferData(stream.buffers[stream.put], AFF_ALFormat[pcm_format], pcm_buffer, numeric_cast(pcm_size), AFF_Frequency[pcm_format])); + alBufferData(stream.buffers[stream.put], AFF_ALFormat(pcm_format), pcm_buffer, numeric_cast(pcm_size), AFF_Frequency[pcm_format])); __AL_CALL(alSourceQueueBuffers(src, 1, &stream.buffers[stream.put])); stream.put = (stream.put + 1) % stream.buffers.size(); --stream.free_buffer_count; } else { if (stream.streamer.IsEnded(stream.ref)) { // stream is EOF - if (stream.loop) // handle loop + if (stream.loop) { // handle loop if (!stream.streamer.Seek(stream.ref, 0)) // FIXME seek to sample_start return false; + } else { + break; // EOF + } } else { return false; // stream error } @@ -347,7 +361,7 @@ static SoundRef LoadSound(IAudioStreamer streamer, const char *path) { sound.buffers.push_back(AL_INVALID_VALUE); __AL_CALL(alGenBuffers(1, &sound.buffers.back())); __AL_CALL(alBufferData( - sound.buffers.back(), AFF_ALFormat[pcm_format], (const ALvoid *)pcm_buffer, numeric_cast(pcm_size), AFF_Frequency[pcm_format])); + sound.buffers.back(), AFF_ALFormat(pcm_format), (const ALvoid *)pcm_buffer, numeric_cast(pcm_size), AFF_Frequency[pcm_format])); } streamer.Close(stream_ref); @@ -463,7 +477,6 @@ time_ns GetSourceDuration(SourceRef src_ref) { if (src_ref < 0 || src_ref >= max_source) return 0; - std::lock_guard lock(al_mixer.lock); ALStream &stream = al_mixer.streams[src_ref]; if (stream.ref == InvalidAudioStreamRef) return 0; @@ -472,12 +485,21 @@ time_ns GetSourceDuration(SourceRef src_ref) { return stream.streamer.GetDuration(stream.ref); } -#if 0 -// [todo] -bool SetSourceStreamTimecode(SourceRef src_ref, time_ns t) { - return false; +bool SetSourceTimecode(SourceRef src_ref, time_ns t) { + std::lock_guard mixer_lock(al_mixer.lock); + if (src_ref < 0 || src_ref >= max_source) + return false; + + ALStream &stream = al_mixer.streams[src_ref]; + if (stream.ref == InvalidAudioStreamRef) + return 0; + + if (stream.streamer.Seek(stream.ref, t) == 0) { + return false; + } + + return true; } -#endif void SetSourceVolume(SourceRef src_ref, float volume) { if (src_ref < 0 || src_ref >= max_source) @@ -546,6 +568,8 @@ SourceState GetSourceState(SourceRef src_ref) { return SS_Playing; if (state == AL_PAUSED) return SS_Paused; + if (state == AL_STOPPED) + return SS_Stopped; return SS_Invalid; } @@ -570,7 +594,6 @@ void StopSource(SourceRef src_ref) { __AL_CALL(alSourceStop(src)); __AL_CALL(alSourcei(src, AL_BUFFER, 0)); - __AL_CALL(alSourceRewind(src)); auto &stream = al_mixer.streams[src_ref]; diff --git a/harfang/engine/audio.h b/harfang/engine/audio.h index 666ca24..678d3c9 100644 --- a/harfang/engine/audio.h +++ b/harfang/engine/audio.h @@ -43,14 +43,14 @@ static const SoundRef InvalidSoundRef = -1; SoundRef LoadWAVSoundFile(const char *path); SoundRef LoadWAVSoundAsset(const char *name); -void UnloadSound(SoundRef snd); +void UnloadSound(SoundRef snd_ref); // using SourceRef = int; static const SourceRef InvalidSourceRef = -1; -SourceRef PlayStereo(SoundRef snd, const StereoSourceState &state); -SourceRef PlaySpatialized(SoundRef snd, const SpatializedSourceState &state); +SourceRef PlayStereo(SoundRef snd_ref, const StereoSourceState &state); +SourceRef PlaySpatialized(SoundRef snd_ref, const SpatializedSourceState &state); SourceRef StreamWAVFileStereo(const char *path, const StereoSourceState &state); SourceRef StreamWAVAssetStereo(const char *name, const StereoSourceState &state); @@ -58,24 +58,24 @@ SourceRef StreamWAVFileSpatialized(const char *path, const SpatializedSourceStat SourceRef StreamWAVAssetSpatialized(const char *name, const SpatializedSourceState &state); // -// time_ns GetSourceDuration(SourceRef source); -time_ns GetSourceTimecode(SourceRef src); -// [unimplemented] bool SetSourceStreamTimecode(SourceRef source, time_ns t); +time_ns GetSourceDuration(SourceRef src_ref); +time_ns GetSourceTimecode(SourceRef src_ref); +bool SetSourceTimecode(SourceRef src_ref, time_ns t); // -void SetSourceVolume(SourceRef src, float volume); -void SetSourcePanning(SourceRef src, float panning); -void SetSourceRepeat(SourceRef src, SourceRepeat repeat); -void SetSourceTransform(SourceRef src, const hg::Mat4 &world, const hg::Vec3 &velocity); +void SetSourceVolume(SourceRef src_ref, float volume); +void SetSourcePanning(SourceRef src_ref, float panning); +void SetSourceRepeat(SourceRef src_ref, SourceRepeat repeat); +void SetSourceTransform(SourceRef src_ref, const hg::Mat4 &world, const hg::Vec3 &velocity); // enum SourceState { SS_Initial, SS_Playing, SS_Paused, SS_Stopped, SS_Invalid }; -SourceState GetSourceState(SourceRef src); +SourceState GetSourceState(SourceRef src_ref); -void PauseSource(SourceRef src); +void PauseSource(SourceRef src_ref); -void StopSource(SourceRef src); +void StopSource(SourceRef src_ref); void StopAllSources(); // [todo] loop point? diff --git a/harfang/engine/component.cpp b/harfang/engine/component.cpp index 6e1fc06..eb4fd8e 100644 --- a/harfang/engine/component.cpp +++ b/harfang/engine/component.cpp @@ -867,9 +867,9 @@ void RigidBody::SetLinearDamping(float damping) { scene_ref->scene->SetRigidBodyLinearDamping(ref, damping); } -Vec3 RigidBody::GetAngularDamping() const { return scene_ref && scene_ref->scene ? scene_ref->scene->GetRigidBodyAngularDamping(ref) : Vec3{}; } +float RigidBody::GetAngularDamping() const { return scene_ref && scene_ref->scene ? scene_ref->scene->GetRigidBodyAngularDamping(ref) : 0.f; } -void RigidBody::SetAngularDamping(const Vec3 &damping) { +void RigidBody::SetAngularDamping(float damping) { if (scene_ref && scene_ref->scene) scene_ref->scene->SetRigidBodyAngularDamping(ref, damping); } diff --git a/harfang/engine/node.h b/harfang/engine/node.h index a7b7cc2..abc6120 100644 --- a/harfang/engine/node.h +++ b/harfang/engine/node.h @@ -180,8 +180,8 @@ struct RigidBody { // 16B on 64 bit float GetLinearDamping() const; void SetLinearDamping(float damping); - Vec3 GetAngularDamping() const; - void SetAngularDamping(const Vec3 &damping); + float GetAngularDamping() const; + void SetAngularDamping(float damping); float GetRestitution() const; void SetRestitution(float restitution); diff --git a/harfang/engine/scene.cpp b/harfang/engine/scene.cpp index 3bf1d32..4e69a37 100644 --- a/harfang/engine/scene.cpp +++ b/harfang/engine/scene.cpp @@ -832,15 +832,15 @@ void Scene::SetRigidBodyLinearDamping(ComponentRef ref, float damping) { rb->linear_damping = pack_float(damping); } -Vec3 Scene::GetRigidBodyAngularDamping(ComponentRef ref) const { +float Scene::GetRigidBodyAngularDamping(ComponentRef ref) const { if (auto rb = GetComponent_(rigid_bodies, ref)) - return {unpack_float(rb->angular_damping[0]), unpack_float(rb->angular_damping[1]), unpack_float(rb->angular_damping[2])}; + return unpack_float(rb->angular_damping); return {}; } -void Scene::SetRigidBodyAngularDamping(ComponentRef ref, const Vec3 &damping) { +void Scene::SetRigidBodyAngularDamping(ComponentRef ref, float damping) { if (auto rb = GetComponent_(rigid_bodies, ref)) - rb->angular_damping = {pack_float(damping.x), pack_float(damping.y), pack_float(damping.z)}; + rb->angular_damping = pack_float(damping); } float Scene::GetRigidBodyRestitution(ComponentRef ref) const { diff --git a/harfang/engine/scene.h b/harfang/engine/scene.h index a77004c..dab95d4 100644 --- a/harfang/engine/scene.h +++ b/harfang/engine/scene.h @@ -407,8 +407,8 @@ public: float GetRigidBodyLinearDamping(ComponentRef ref) const; void SetRigidBodyLinearDamping(ComponentRef ref, float damping); - Vec3 GetRigidBodyAngularDamping(ComponentRef ref) const; - void SetRigidBodyAngularDamping(ComponentRef ref, const Vec3 &damping); + float GetRigidBodyAngularDamping(ComponentRef ref) const; + void SetRigidBodyAngularDamping(ComponentRef ref, float damping); float GetRigidBodyRestitution(ComponentRef ref) const; void SetRigidBodyRestitution(ComponentRef ref, float restitution); @@ -730,8 +730,8 @@ private: Mat4 prv{Mat4::Identity}, cur{Mat4::Identity}; Vec3 scl{Vec3::One}; - uint8_t linear_damping{pack_float(1.f)}; - std::array angular_damping = {pack_float(1.f), pack_float(1.f), pack_float(1.f)}; + uint8_t linear_damping{pack_float(0.f)}; + uint8_t angular_damping{pack_float(0.f)}; float restitution{0.f}, friction{0.5f}, rolling_friction{0.f}; }; @@ -785,7 +785,7 @@ private: friend void LoadComponent(Object_ *data_, const Reader &ir, const Handle &h, const Reader &deps_ir, const ReadProvider &deps_ip, PipelineResources &resources, const PipelineInfo &pipeline, bool queue_texture_loads, bool do_not_load_resources, uint32_t version); friend void LoadComponent(Light_ *data_, const Reader &ir, const Handle &h); - friend void LoadComponent(RigidBody_ *data_, const Reader &ir, const Handle &h); + friend void LoadComponent(RigidBody_ *data_, const Reader &ir, const Handle &h, uint32_t version); friend void LoadComponent(Collision_ *data_, const Reader &ir, const Handle &h); friend void LoadComponent(Instance_ *data_, const Reader &ir, const Handle &h); friend void LoadComponent(Script_ *data_, const Reader &ir, const Handle &h); diff --git a/harfang/engine/scene_bullet3_physics.cpp b/harfang/engine/scene_bullet3_physics.cpp index f7ed65d..016a480 100644 --- a/harfang/engine/scene_bullet3_physics.cpp +++ b/harfang/engine/scene_bullet3_physics.cpp @@ -286,25 +286,27 @@ void SceneBullet3Physics::NodeCreatePhysics(const Node &node, const Reader &ir, rb_info.m_friction = rb.GetFriction(); rb_info.m_rollingFriction = rb.GetRollingFriction(); rb_info.m_startWorldTransform = bt_trs; + rb_info.m_linearDamping = rb.GetLinearDamping(); + rb_info.m_angularDamping = rb.GetAngularDamping(); _node.body = new btRigidBody(rb_info); _node.body->setCollisionShape(root_shape); _node.body->setUserIndex(node.ref.idx); // ref back to node - world->addRigidBody(_node.body); - // configure const auto type = rb.GetType(); const auto flags = _node.body->getCollisionFlags(); - if (type == RBT_Dynamic) { + if (type == RBT_Dynamic) _node.body->setCollisionFlags(flags & ~(btRigidBody::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT)); - } else if (type == RBT_Kinematic) { + else if (type == RBT_Kinematic) _node.body->setCollisionFlags((flags | btRigidBody::CF_KINEMATIC_OBJECT) & ~btCollisionObject::CF_STATIC_OBJECT); - } else { + else _node.body->setCollisionFlags((flags & ~btRigidBody::CF_KINEMATIC_OBJECT) | btCollisionObject::CF_STATIC_OBJECT); - } + + // add to world + world->addRigidBody(_node.body); } } @@ -426,9 +428,9 @@ void SceneBullet3Physics::SyncBodiesFromScene(const Scene &scene) { const auto flags = body->getCollisionFlags(); if (flags == btRigidBody::CF_KINEMATIC_OBJECT) { - const auto world = hg::Orthonormalize(trs.GetWorld()); - // const auto world = TransformationMat4(GetT(w), GetR(w), Vec3::One); - body->setWorldTransform(to_btTransform(world)); + const auto w = trs.GetWorld(); + const auto w2 = TransformationMat4(GetT(w), GetR(w), Vec3::One); + body->setWorldTransform(to_btTransform(w2)); } else if (flags == btRigidBody::CF_DYNAMIC_OBJECT) { const auto world = body->getInterpolationWorldTransform(); trs.SetWorld(from_btTransform(world)); diff --git a/harfang/engine/scene_load_binary.cpp b/harfang/engine/scene_load_binary.cpp index 15bd7f7..21e3578 100644 --- a/harfang/engine/scene_load_binary.cpp +++ b/harfang/engine/scene_load_binary.cpp @@ -57,6 +57,12 @@ void SaveComponent(const Scene::Light_ *data_, const Writer &iw, const Handle &h void SaveComponent(const Scene::RigidBody_ *data_, const Writer &iw, const Handle &h) { Write(iw, h, data_->type); iw.write(h, &data_->cur.m[0][0], sizeof(float) * 4 * 3); + Write(iw, h, data_->scl); + Write(iw, h, data_->linear_damping); + Write(iw, h, data_->angular_damping); + Write(iw, h, data_->restitution); + Write(iw, h, data_->friction); + Write(iw, h, data_->rolling_friction); } void SaveComponent(const Scene::Script_ *data_, const Writer &iw, const Handle &h) { @@ -143,9 +149,18 @@ void LoadComponent(Scene::Light_ *data_, const Reader &ir, const Handle &h) { Read(ir, h, data_->shadow_bias); } -void LoadComponent(Scene::RigidBody_ *data_, const Reader &ir, const Handle &h) { +void LoadComponent(Scene::RigidBody_ *data_, const Reader &ir, const Handle &h, uint32_t version) { Read(ir, h, data_->type); ir.read(h, &data_->cur.m[0][0], sizeof(float) * 4 * 3); + + if (version >= 5) { + Read(ir, h, data_->scl); + Read(ir, h, data_->linear_damping); + Read(ir, h, data_->angular_damping); + Read(ir, h, data_->restitution); + Read(ir, h, data_->friction); + Read(ir, h, data_->rolling_friction); + } } void LoadComponent(Scene::Script_ *data_, const Reader &ir, const Handle &h) { @@ -179,7 +194,7 @@ void LoadComponent(Scene::Instance_ *data_, const Reader &ir, const Handle &h) { } // -uint32_t GetSceneBinaryFormatVersion() { return 4; } +uint32_t GetSceneBinaryFormatVersion() { return 5; } bool Scene::Save_binary( const Writer &iw, const Handle &h, const PipelineResources &resources, uint32_t save_flags, const std::vector *nodes_to_save) const { @@ -195,6 +210,7 @@ bool Scene::Save_binary( version 2: add skinning support in Object component version 3: add support for arbitrary number of bones version 4: light intensity factors + version 5: save rigid body properties */ const auto version = GetSceneBinaryFormatVersion(); Write(iw, h, version); @@ -520,7 +536,7 @@ bool Scene::Load_binary(const Reader &ir, const Handle &h, const char *name, con rigid_body_refs.resize(rigid_body_count); for (size_t i = 0; i < rigid_body_count; ++i) { const auto ref = rigid_body_refs[i] = CreateRigidBody().ref; - LoadComponent(&rigid_bodies[ref.idx], ir, h); + LoadComponent(&rigid_bodies[ref.idx], ir, h, version); } } diff --git a/harfang/engine/scene_load_json.cpp b/harfang/engine/scene_load_json.cpp index 761068b..b1ad37d 100644 --- a/harfang/engine/scene_load_json.cpp +++ b/harfang/engine/scene_load_json.cpp @@ -71,6 +71,12 @@ void SaveComponent(const Scene::Light_ *data_, json &js) { void SaveComponent(const Scene::RigidBody_ *data_, json &js) { js["type"] = data_->type; js["world"] = data_->cur; + js["scale"] = data_->scl; + js["linear_damping"] = data_->linear_damping; + js["angular_damping"] = data_->angular_damping; + js["restitution"] = data_->restitution; + js["friction"] = data_->friction; + js["rolling_friction"] = data_->rolling_friction; } void SaveComponent(const Scene::Script_ *data_, json &js) { @@ -186,6 +192,18 @@ void LoadComponent(Scene::Light_ *data_, const json &js) { void LoadComponent(Scene::RigidBody_ *data_, const json &js) { data_->type = js.at("type"); data_->cur = js.at("world"); + if (js.find("scale") != std::end(js)) + data_->scl = js.at("scale"); + if (js.find("linear_damping") != std::end(js)) + data_->linear_damping = js.at("linear_damping"); + if (js.find("angular_damping") != std::end(js)) + data_->angular_damping = js.at("angular_damping"); + if (js.find("restitution") != std::end(js)) + data_->restitution = js.at("restitution"); + if (js.find("friction") != std::end(js)) + data_->friction = js.at("friction"); + if (js.find("rolling_friction") != std::end(js)) + data_->rolling_friction = js.at("rolling_friction"); } void LoadComponent(Scene::Script_ *data_, const json &js) { diff --git a/harfang/engine/wav_audio_stream.cpp b/harfang/engine/wav_audio_stream.cpp index 0a2ea07..7235199 100644 --- a/harfang/engine/wav_audio_stream.cpp +++ b/harfang/engine/wav_audio_stream.cpp @@ -221,8 +221,9 @@ static int WavAudioStreamGetFrame(AudioStreamRef ref, uintptr_t *data, int *size const auto data_cursor = stream.reader->tell(stream.handle) - stream.data_offset; // position in data chunk const auto data_left = stream.data_size - data_cursor; // data left in chunk - if (data_left == 0) + if (data_left == 0) { return 0; + } const auto data_to_read = Min(data_left, stream.frame.capacity()); // amount of data to read if (stream.reader->read(stream.handle, stream.frame.data(), data_to_read) != data_to_read) diff --git a/harfang/tests/t_audio.cpp b/harfang/tests/t_audio.cpp index 0e2f9aa..09ac47d 100644 --- a/harfang/tests/t_audio.cpp +++ b/harfang/tests/t_audio.cpp @@ -13,37 +13,109 @@ using namespace hg; -TEST(Audio, InitShutdown) { - start_timer(); +class Audio : public ::testing::Test { +protected: + bool initialized; - EXPECT_TRUE(AudioInit()); - AudioShutdown(); + void SetUp() override { + start_timer(); + initialized = AudioInit(); + } - stop_timer(); -} + void TearDown() override { + AudioShutdown(); + stop_timer(); + } +}; -TEST(Audio, PlayWAV) { - EXPECT_TRUE(AudioInit()); +TEST_F(Audio, InitShutdown) { EXPECT_TRUE(initialized); } + +TEST_F(Audio, PlayWAV) { + EXPECT_TRUE(initialized); const auto snd = LoadWAVSoundFile(GetResPath("audio/sine_48S16Stereo.wav").c_str()); - const auto src = PlayStereo(snd, {20.f, SR_Once, 0.5f}); + EXPECT_NE(snd, InvalidSourceRef); + + auto src = PlayStereo(snd, {20.f, SR_Once, 0.5f}); + EXPECT_NE(snd, InvalidSoundRef); + EXPECT_EQ(GetSourceState(src), SS_Playing); while (GetSourceState(src) == SS_Playing) std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(GetSourceState(src), SS_Stopped); - AudioShutdown(); + // replay it + src = PlayStereo(snd, {20.f, SR_Once, 0.5f}); + EXPECT_NE(snd, InvalidSoundRef); + EXPECT_EQ(GetSourceState(src), SS_Playing); + + while (GetSourceState(src) == SS_Playing) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + EXPECT_EQ(GetSourceState(src), SS_Stopped); } -TEST(Audio, StreamWAV) { - start_timer(); - - EXPECT_TRUE(AudioInit()); +TEST_F(Audio, StreamWAV) { + EXPECT_TRUE(initialized); const auto src = StreamWAVFileStereo(GetResPath("audio/sine_48S16Stereo.wav").c_str(), {20.f, SR_Once, 0.5f}); + EXPECT_NE(src, InvalidSourceRef); + while (GetSourceState(src) == SS_Initial) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + EXPECT_EQ(GetSourceState(src), SS_Playing); while (GetSourceState(src) == SS_Playing) std::this_thread::sleep_for(std::chrono::milliseconds(100)); - AudioShutdown(); - - stop_timer(); + EXPECT_EQ(GetSourceState(src), SS_Stopped); +} + +TEST_F(Audio, Timestamps) { + EXPECT_TRUE(initialized); + + const auto src = StreamWAVFileStereo(GetResPath("audio/sine_48S16Stereo.wav").c_str(), {20.f, SR_Once, 0.5f}); + EXPECT_NE(src, InvalidSourceRef); + + time_ns duration = GetSourceDuration(src); + EXPECT_EQ(duration, hg::time_from_sec(2)); // WARNING! Don't forget to change this test if you modify the input wav file. + + hg::SourceState state; + + while ((state = GetSourceState(src)) == SS_Initial) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + EXPECT_EQ(state, SS_Playing); + + hg::time_ns constexpr t_break = hg::time_from_ms(1200); + hg::time_ns constexpr t_rewind = hg::time_from_ms(200); + hg::time_ns t_elapsed = 0; + + while ((state == SS_Playing) && (t_elapsed < t_break)) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + state = GetSourceState(src); + t_elapsed = GetSourceTimecode(src); + } + + // Go back to t = 200ms. + EXPECT_TRUE(SetSourceTimecode(src, t_rewind)); + + // Remember! This is asynchronous. It means that if we call GetSourceTimecode just after, it may not return 200ms. + // This loop should run until the timestamp is set or the call was ignored and the stream ended. The latter being an error. + int t_wait_ms = 50; + while ((state == SS_Playing) && (t_elapsed > t_break)) { + std::this_thread::sleep_for(std::chrono::milliseconds(t_wait_ms)); + state = GetSourceState(src); + t_elapsed = GetSourceTimecode(src); + } + + EXPECT_EQ(state, SS_Playing); + EXPECT_LT(t_elapsed, t_break); + // We must be closer to t_rewind than t_break. + EXPECT_LT(abs(t_elapsed - t_rewind), abs(t_elapsed - t_break)); + + // Play the remaining of the audio stream. + while (GetSourceState(src) == SS_Playing) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + EXPECT_EQ(GetSourceState(src), SS_Stopped); } diff --git a/harfang/tests/t_scene.cpp b/harfang/tests/t_scene.cpp index 587eb72..b59ec83 100644 --- a/harfang/tests/t_scene.cpp +++ b/harfang/tests/t_scene.cpp @@ -13,6 +13,9 @@ #include "engine/scene_forward_pipeline.h" #include "engine/scene_lua_vm.h" #include "engine/scene_systems.h" +#if HG_ENABLE_BULLET3_SCENE_PHYSICS +#include "engine/scene_bullet3_physics.h" +#endif #include "gtest/gtest.h" @@ -604,18 +607,20 @@ TEST(Scene, LuaScriptWriteToG) { } // -#if HG_ENABLE_SCENE_PHYSICS_API +#if HG_ENABLE_BULLET3_SCENE_PHYSICS TEST(Scene, PhysicDynamicRigidBodyFreefall) { Scene scene; auto sphere = CreatePhysicSphere(scene, 0.5, Mat4::Identity, {}, {}, 1.f); - SceneNewtonPhysics physics; - SceneSyncToSystemsFromAssets(scene, &physics); + SceneBullet3Physics physics; + SceneSyncToSystemsFromAssets(scene, physics); SceneClocks clocks; - for (int i = 0; i < 16; ++i) - SceneUpdateSystems(scene, clocks, time_from_ms(16), &physics); - + time_ns physics_step = time_from_ms(10); + int max_physics_step = 64; + for (int i = 0; i < 16; ++i) { + SceneUpdateSystems(scene, clocks, time_from_ms(16), physics, physics_step, max_physics_step); + } EXPECT_LT(GetT(sphere.GetTransform().GetWorld()).y, -0.3f); } @@ -624,13 +629,14 @@ TEST(Scene, PhysicKinematicRigidBodyNoFreefall) { auto sphere = CreatePhysicSphere(scene, 0.5, Mat4::Identity, {}, {}, 1.f); sphere.GetRigidBody().SetType(RBT_Kinematic); - SceneNewtonPhysics physics; + SceneBullet3Physics physics; physics.SceneCreatePhysicsFromAssets(scene); + time_ns dt = time_from_ms(10); for (int i = 0; i < 16; ++i) { scene.ReadyWorldMatrices(); - physics.StepSimulation(); - physics.SyncDynamicBodiesToScene(scene); + physics.StepSimulation(dt); + physics.SyncBodiesFromScene(scene); } EXPECT_EQ(GetT(sphere.GetTransform().GetWorld()).y, 0.f); @@ -641,13 +647,14 @@ TEST(Scene, PhysicDynamicVsStaticRigidBodyCollisionCallback) { auto sphere = CreatePhysicSphere(scene, 0.5, TranslationMat4({0, 5, 0}), {}, {}, 1.f); auto ground = CreatePhysicCube(scene, {10, 1, 10}, TranslationMat4({0, -0.5f, 0}), {}, {}, 0.f); - SceneNewtonPhysics physics; + SceneBullet3Physics physics; physics.SceneCreatePhysicsFromAssets(scene); physics.NodeStartTrackingCollisionEvents(sphere.ref); + time_ns dt = time_from_ms(10); size_t collision_count = 0; for (int i = 0; i < 256; ++i) { - physics.StepSimulation(); + physics.StepSimulation(dt); NodeNodeContacts node_node_contacts; physics.CollectCollisionEvents(scene, node_node_contacts); collision_count += node_node_contacts.size(); @@ -662,21 +669,20 @@ TEST(Scene, PhysicKinematicRigidBodyCollideWorld) { sphere.GetRigidBody().SetType(RBT_Kinematic); auto ground = CreatePhysicCube(scene, {10, 1, 10}, TranslationMat4({0, -0.5f, 0}), {}, {}, 0.f); - SceneNewtonPhysics physics; + SceneBullet3Physics physics; physics.SceneCreatePhysicsFromAssets(scene); - const auto collisions = physics.NodeCollideWorld(sphere.ref, sphere.GetTransform().GetWorld()); + const auto collisions = physics.NodeCollideWorld(sphere, sphere.GetTransform().GetWorld()); EXPECT_GT(collisions.size(), 0); } -// TEST(Scene, PhysicRaycastFirstHit) { Scene scene; CreatePhysicCube(scene, {1, 1, 1}, Mat4::Identity, InvalidModelRef, {}, 0.f); const auto closest_node = CreatePhysicCube(scene, {.1f, .1f, .1f}, TranslationMat4({0, 0, -1.f}), InvalidModelRef, {}, 0.f); CreatePhysicCube(scene, {.1f, .1f, .1f}, TranslationMat4({0, 0, 1.f}), InvalidModelRef, {}, 0.f); - SceneNewtonPhysics physics; + SceneBullet3Physics physics; physics.SceneCreatePhysicsFromAssets(scene); const auto out = physics.RaycastFirstHit(scene, {0, 0, -10.f}, {0, 0, 10.f}); @@ -690,7 +696,7 @@ TEST(Scene, PhysicRaycastFirstHitOutOfReach) { const auto closest_node = CreatePhysicCube(scene, {.1f, .1f, .1f}, TranslationMat4({0, 0, -1.f}), InvalidModelRef, {}, 0.f); CreatePhysicCube(scene, {.1f, .1f, .1f}, TranslationMat4({0, 0, 1.f}), InvalidModelRef, {}, 0.f); - SceneNewtonPhysics physics; + SceneBullet3Physics physics; physics.SceneCreatePhysicsFromAssets(scene); const auto out = physics.RaycastFirstHit(scene, {0, 0, -10.f}, {0, 0, -2.f}); @@ -703,14 +709,24 @@ TEST(Scene, PhysicRaycastAllHits) { const auto closest_node = CreatePhysicCube(scene, {.1f, .1f, .1f}, TranslationMat4({0, 0, -1.f}), InvalidModelRef, {}, 0.f); const auto farthest_node = CreatePhysicCube(scene, {.1f, .1f, .1f}, TranslationMat4({0, 0, 1.f}), InvalidModelRef, {}, 0.f); - SceneNewtonPhysics physics; + SceneBullet3Physics physics; physics.SceneCreatePhysicsFromAssets(scene); const auto out = physics.RaycastAllHits(scene, {0, 0, -10.f}, {0, 0, 10.f}); EXPECT_EQ(out.size(), 3); - EXPECT_EQ(out[0].node, closest_node); - EXPECT_EQ(out[1].node, middle_node); - EXPECT_EQ(out[2].node, farthest_node); + + // Nodes are not returned in distance order. + // Here we just check if there's no duplicates/weird result (it should not). + int found = 0; + const hg::Node nodes[] = {middle_node, closest_node, farthest_node}; + for (int j = 0; j < 3; j++) { + for (int i = 0; i < out.size(); i++) { + if (out[i].node == nodes[j]) { + found++; + } + } + EXPECT_EQ(found, j+1); + } } TEST(Scene, PhysicRaycastAllHitsOutOfReach) { @@ -719,13 +735,13 @@ TEST(Scene, PhysicRaycastAllHitsOutOfReach) { CreatePhysicCube(scene, {.1f, .1f, .1f}, TranslationMat4({0, 0, -1.f}), InvalidModelRef, {}, 0.f); CreatePhysicCube(scene, {.1f, .1f, .1f}, TranslationMat4({0, 0, 1.f}), InvalidModelRef, {}, 0.f); - SceneNewtonPhysics physics; + SceneBullet3Physics physics; physics.SceneCreatePhysicsFromAssets(scene); const auto out = physics.RaycastAllHits(scene, {0, 0, -10.f}, {0, 0, -2.f}); EXPECT_TRUE(out.empty()); } -#endif +#endif // HG_ENABLE_BULLET3_SCENE_PHYSICS // TEST(Scene, ComponentGarbageCollection) { diff --git a/harfang/version.txt b/harfang/version.txt index fd2a018..94ff29c 100644 --- a/harfang/version.txt +++ b/harfang/version.txt @@ -1 +1 @@ -3.1.0 +3.1.1 diff --git a/readme.md b/readme.md index 9162bea..db91e93 100644 --- a/readme.md +++ b/readme.md @@ -55,13 +55,13 @@ Audio API ## Screenshots -The following screenshots were captured on a 1080GTX in 1080P running at 60FPS. +The following screenshots were captured on a 2070RTX in 1080P running at 60FPS, GI is performed using screen space raytracing and does not require RTX capable hardware. -![alt text](https://raw.githubusercontent.com/harfang3d/image-storage/main/portfolio/2.0.111/sun_temple_aaa.png) +![alt text](https://raw.githubusercontent.com/harfang3d/image-storage/main/portfolio/3.1.1/cyber_city_aaa.png) -![alt text](https://raw.githubusercontent.com/harfang3d/image-storage/main/portfolio/2.0.111/sun_temple_aaa_2.png) +![alt text](https://raw.githubusercontent.com/harfang3d/image-storage/main/portfolio/3.1.1/cyber_city_aaa_2.png) -*(Sun Temple, courtesy of the Open Research Content Archive (ORCA))* +*(CyberPunk City, CyberPunk Girl and Robot R32 by art-equilibrium, ILranch and ZeroArt3d)* # Download @@ -229,3 +229,23 @@ Given a version number MAJOR.MINOR.PATCH, increment the: Harfang is licensed under the GPLv3, LGPLv3 and a commercial license:
https://www.harfang3d.com/licenses/ +# More screenshots... + +![alt text](https://raw.githubusercontent.com/harfang3d/image-storage/main/portfolio/3.1.1/sun_temple_aaa.png) + +![alt text](https://raw.githubusercontent.com/harfang3d/image-storage/main/portfolio/3.1.1/sun_temple_aaa_2.png) + +*(Sun Temple, courtesy of the Open Research Content Archive (ORCA))* + +![alt text](https://raw.githubusercontent.com/harfang3d/image-storage/main/portfolio/3.1.1/cafe_exterior_aaa.png) + +![alt text](https://raw.githubusercontent.com/harfang3d/image-storage/main/portfolio/3.1.1/cafe_exterior_aaa_2.png) + +*(Bistro, courtesy of the Open Research Content Archive (ORCA))* + +![alt text](https://raw.githubusercontent.com/harfang3d/image-storage/main/portfolio/3.1.1/sponza_atrium_aaa.png) + +![alt text](https://raw.githubusercontent.com/harfang3d/image-storage/main/portfolio/3.1.1/sponza_atrium_aaa_2.png) + +*(Sponza Atrium GLTF, courtesy of Crytek/Themaister)* + diff --git a/release-notes.md b/release-notes.md index f2d130a..84a1319 100644 --- a/release-notes.md +++ b/release-notes.md @@ -1,3 +1,22 @@ +# [3.1.1] - 2021-12-31 + +This minor release brings several fixes, mainly in the Bullet Physics API. + +### Engine + +- Added a missing texture render target flag (`TF_RenderTarget`). +- Added `SetSourceStreamTimecode()` to the audio API. + +### Physics + +- Updated the Physics unit tests. +- Fixed an issue in the `Kinematic` physics matrix. +- `SetRigidBodyAngularDamping()` now takes a `float` instead of a `Vec3`. + +### Documentation + +- Fixed several typos in the documentation. + # [3.1.0] - 2021-12-13 This minor release brings several improvements and fixes, mainly in the Bullet Physics API.