Merge pull request #4 from harfang3dadmin/main

3.1.1 release.
This commit is contained in:
HARFANG 3D (admin) 2021-12-31 17:07:35 +01:00 committed by GitHub
commit e57798947d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 298 additions and 106 deletions

View File

@ -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'}})

View File

@ -0,0 +1 @@
Return the duration of an audio source.

View File

@ -0,0 +1 @@
Set timecode of the audio source.

View File

@ -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 <input_resources_folder> [<output_assets_folder>]
assetc <project_resources_folder> [<optional_project_assets_folder>]
```
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.

View File

@ -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

View File

@ -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]

View File

@ -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<ALsizei>(pcm_size), AFF_Frequency[pcm_format]));
alBufferData(stream.buffers[stream.put], AFF_ALFormat(pcm_format), pcm_buffer, numeric_cast<ALsizei>(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<ALsizei>(pcm_size), AFF_Frequency[pcm_format]));
sound.buffers.back(), AFF_ALFormat(pcm_format), (const ALvoid *)pcm_buffer, numeric_cast<ALsizei>(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<std::mutex> 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<std::mutex> 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];

View File

@ -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?

View File

@ -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);
}

View File

@ -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);

View File

@ -832,15 +832,15 @@ void Scene::SetRigidBodyLinearDamping(ComponentRef ref, float damping) {
rb->linear_damping = pack_float<uint8_t>(damping);
}
Vec3 Scene::GetRigidBodyAngularDamping(ComponentRef ref) const {
float Scene::GetRigidBodyAngularDamping(ComponentRef ref) const {
if (auto rb = GetComponent_(rigid_bodies, ref))
return {unpack_float<uint8_t>(rb->angular_damping[0]), unpack_float<uint8_t>(rb->angular_damping[1]), unpack_float<uint8_t>(rb->angular_damping[2])};
return unpack_float<uint8_t>(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<uint8_t>(damping.x), pack_float<uint8_t>(damping.y), pack_float<uint8_t>(damping.z)};
rb->angular_damping = pack_float<uint8_t>(damping);
}
float Scene::GetRigidBodyRestitution(ComponentRef ref) const {

View File

@ -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<uint8_t>(1.f)};
std::array<uint8_t, 3> angular_damping = {pack_float<uint8_t>(1.f), pack_float<uint8_t>(1.f), pack_float<uint8_t>(1.f)};
uint8_t linear_damping{pack_float<uint8_t>(0.f)};
uint8_t angular_damping{pack_float<uint8_t>(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);

View File

@ -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));

View File

@ -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<NodeRef> *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<uint32_t>(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);
}
}

View File

@ -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) {

View File

@ -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)

View File

@ -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);
}

View File

@ -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) {

View File

@ -1 +1 @@
3.1.0
3.1.1

View File

@ -55,13 +55,13 @@ Audio API
<a name="subsection_1b"></a>
## 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)*
<a name="section_2"></a>
# 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:<br>
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)*

View File

@ -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.