Files
vimix/SourceCallback.cpp
Bruno Herbelin 39b61fe331 Reimplementation of SourceInputCallbacks into Session
Session should be the object holding the list of inputs parameters (e.g. synchrony) and the list of source callbacks. This also avoids mixing input when copying sources.  Code could be improved but is operational.
2022-03-07 00:23:24 +01:00

694 lines
16 KiB
C++

/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
**/
#include "defines.h"
#include "Source.h"
#include "UpdateCallback.h"
#include "Visitor.h"
#include "Log.h"
#include "SourceCallback.h"
SourceCallback *SourceCallback::create(CallbackType type)
{
SourceCallback *loadedcallback = nullptr;
switch (type) {
case SourceCallback::CALLBACK_ALPHA:
loadedcallback = new SetAlpha;
break;
case SourceCallback::CALLBACK_LOOM:
loadedcallback = new Loom;
break;
case SourceCallback::CALLBACK_GEOMETRY:
loadedcallback = new SetGeometry;
break;
case SourceCallback::CALLBACK_GRAB:
loadedcallback = new Grab;
break;
case SourceCallback::CALLBACK_RESIZE:
loadedcallback = new Resize;
break;
case SourceCallback::CALLBACK_TURN:
loadedcallback = new Turn;
break;
case SourceCallback::CALLBACK_DEPTH:
loadedcallback = new SetDepth;
break;
case SourceCallback::CALLBACK_PLAY:
loadedcallback = new Play;
break;
case SourceCallback::CALLBACK_REPLAY:
loadedcallback = new RePlay;
break;
case SourceCallback::CALLBACK_RESETGEO:
loadedcallback = new ResetGeometry;
break;
case SourceCallback::CALLBACK_LOCK:
loadedcallback = new Lock;
break;
default:
break;
}
return loadedcallback;
}
bool SourceCallback::overlap( SourceCallback *a, SourceCallback *b)
{
bool ret = false;
if (a->type() == b->type()) {
// same type means overlap
ret = true;
// but there are some exceptions..
switch (a->type()) {
case SourceCallback::CALLBACK_GRAB:
{
const Grab *_a = static_cast<Grab*>(a);
const Grab *_b = static_cast<Grab*>(b);
// there is no overlap if the X or Y of a vector is zero
if ( ABS(_a->value().x) < EPSILON || ABS(_b->value().x) < EPSILON )
ret = false;
else if ( ABS(_a->value().y) < EPSILON || ABS(_b->value().y) < EPSILON )
ret = false;
}
break;
case SourceCallback::CALLBACK_RESIZE:
{
const Resize *_a = static_cast<Resize*>(a);
const Resize *_b = static_cast<Resize*>(b);
if ( ABS(_a->value().x) < EPSILON || ABS(_b->value().x) < EPSILON )
ret = false;
else if ( ABS(_a->value().y) < EPSILON || ABS(_b->value().y) < EPSILON )
ret = false;
}
break;
default:
break;
}
}
return ret;
}
SourceCallback::SourceCallback(): status_(PENDING), delay_(0.f), elapsed_(0.f)
{
}
void SourceCallback::accept(Visitor& v)
{
v.visit(*this);
}
void SourceCallback::update (Source *s, float dt)
{
if (s != nullptr) {
// time passed
elapsed_ += dt;
// wait for delay to start
if ( status_ == PENDING && elapsed_ > delay_ )
status_ = READY;
}
// invalid
else
status_ = FINISHED;
}
void ResetGeometry::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
if (s->locked())
status_ = FINISHED;
// apply when ready
if ( status_ == READY ){
s->group(View::GEOMETRY)->scale_ = glm::vec3(1.f);
s->group(View::GEOMETRY)->rotation_.z = 0;
s->group(View::GEOMETRY)->crop_ = glm::vec3(1.f);
s->group(View::GEOMETRY)->translation_ = glm::vec3(0.f);
s->touch();
status_ = FINISHED;
}
}
SourceCallback *ResetGeometry::clone() const
{
return new ResetGeometry;
}
SetAlpha::SetAlpha(float alpha, float ms, bool revert) : SourceCallback(),
duration_(ms), alpha_(alpha), bidirectional_(revert)
{
alpha_ = CLAMP(alpha_, 0.f, 1.f);
start_ = glm::vec2();
target_ = glm::vec2();
}
void SetAlpha::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
if (s->locked())
status_ = FINISHED;
// set start position on first time it is ready
if ( status_ == READY ){
// initial mixing view position
start_ = glm::vec2(s->group(View::MIXING)->translation_);
// step in diagonal by default
glm::vec2 step = glm::normalize(glm::vec2(1.f, 1.f));
// step in direction of source translation if possible
if ( glm::length(start_) > DELTA_ALPHA)
step = glm::normalize(start_);
//
// target mixing view position
//
// special case Alpha = 0
if (alpha_ < DELTA_ALPHA) {
target_ = step;
}
// special case Alpha = 1
else if (alpha_ > 1.f - DELTA_ALPHA) {
target_ = step * 0.005f;
}
// general case
else {
// converge to reduce the difference of alpha using dichotomic algorithm
target_ = start_;
float delta = 1.f;
do {
target_ += step * (delta / 2.f);
delta = SourceCore::alphaFromCordinates(target_.x, target_.y) - alpha_;
} while (glm::abs(delta) > DELTA_ALPHA);
}
status_ = ACTIVE;
}
if ( status_ == ACTIVE ) {
// time passed since start
float progress = elapsed_ - delay_;
// perform movement
if ( ABS(duration_) > 0.f)
s->group(View::MIXING)->translation_ = glm::vec3(start_ + (progress/duration_)*(target_ - start_), s->group(View::MIXING)->translation_.z);
// time-out
if ( progress > duration_ ) {
// apply alpha to target
s->group(View::MIXING)->translation_ = glm::vec3(target_, s->group(View::MIXING)->translation_.z);
// done
status_ = FINISHED;
}
}
}
void SetAlpha::multiply (float factor)
{
alpha_ *= factor;
}
SourceCallback *SetAlpha::clone() const
{
return new SetAlpha(alpha_, duration_, bidirectional_);
}
SourceCallback *SetAlpha::reverse(Source *s) const
{
return bidirectional_ ? new SetAlpha(s->alpha(), duration_) : nullptr;
}
void SetAlpha::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}
Lock::Lock(bool on) : lock_(on)
{
}
void Lock::update(Source *s, float)
{
if (s)
s->setLocked(lock_);
status_ = FINISHED;
}
SourceCallback *Lock::clone() const
{
return new Lock(lock_);
}
Loom::Loom(float speed, float ms) : SourceCallback(),
speed_(speed), duration_(ms)
{
pos_ = glm::vec2();
step_ = glm::normalize(glm::vec2(1.f, 1.f)); // step in diagonal by default
}
void Loom::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
if (s->locked())
status_ = FINISHED;
// set start on first time it is ready
if ( status_ == READY ){
// initial position
pos_ = glm::vec2(s->group(View::MIXING)->translation_);
// step in direction of source translation if possible
if ( glm::length(pos_) > DELTA_ALPHA)
step_ = glm::normalize(pos_);
status_ = ACTIVE;
}
if ( status_ == ACTIVE ) {
// time passed since start
float progress = elapsed_ - delay_;
// move target by speed vector (in the direction of step_, amplitude of speed * time (in second))
pos_ -= step_ * ( speed_ * dt * 0.001f );
// apply alpha if pos in range [0 MIXING_MIN_THRESHOLD]
float l = glm::length( pos_ );
if ( (l > 0.01f && speed_ > 0.f ) || (l < MIXING_MIN_THRESHOLD && speed_ < 0.f ) )
s->group(View::MIXING)->translation_ = glm::vec3(pos_, s->group(View::MIXING)->translation_.z);
else
status_ = FINISHED;
// time-out
if ( progress > duration_ )
// done
status_ = FINISHED;
}
}
void Loom::multiply (float factor)
{
speed_ *= factor;
}
SourceCallback *Loom::clone() const
{
return new Loom(speed_, duration_);
}
SourceCallback *Loom::reverse(Source *) const
{
return new Loom(speed_, 0.f);
}
void Loom::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}
SetDepth::SetDepth(float target, float ms, bool revert) : SourceCallback(),
duration_(ms), start_(0.f), target_(target), bidirectional_(revert)
{
target_ = CLAMP(target_, MIN_DEPTH, MAX_DEPTH);
}
void SetDepth::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
if (s->locked())
status_ = FINISHED;
// set start position on first time it is ready
if ( status_ == READY ){
start_ = s->group(View::LAYER)->translation_.z;
status_ = ACTIVE;
}
if ( status_ == ACTIVE ) {
// time passed since start
float progress = elapsed_ - delay_;
// perform movement
if ( ABS(duration_) > 0.f)
s->group(View::LAYER)->translation_.z = start_ + (progress/duration_) * (target_ - start_);
// time-out
if ( progress > duration_ ) {
// apply depth to target
s->group(View::LAYER)->translation_.z = target_;
// ensure reordering of view
++View::need_deep_update_;
// done
status_ = FINISHED;
}
}
}
void SetDepth::multiply (float factor)
{
target_ *= factor;
}
SourceCallback *SetDepth::clone() const
{
return new SetDepth(target_, duration_, bidirectional_);
}
SourceCallback *SetDepth::reverse(Source *s) const
{
return bidirectional_ ? new SetDepth(s->depth(), duration_) : nullptr;
}
void SetDepth::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}
Play::Play(bool on, bool revert) : SourceCallback(), play_(on), bidirectional_(revert)
{
}
void Play::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
// toggle play status when ready
if ( status_ == READY ){
if (s->playing() != play_)
// call play function
s->play(play_);
status_ = FINISHED;
}
}
SourceCallback *Play::clone() const
{
return new Play(play_, bidirectional_);
}
SourceCallback *Play::reverse(Source *s) const
{
return bidirectional_ ? new Play(s->playing()) : nullptr;
}
void Play::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}
RePlay::RePlay() : SourceCallback()
{
}
void RePlay::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
// apply when ready
if ( status_ == READY ){
// call replay function
s->replay();
status_ = FINISHED;
}
}
SourceCallback *RePlay::clone() const
{
return new RePlay;
}
SetGeometry::SetGeometry(const Group *g, float ms, bool revert) : SourceCallback(),
duration_(ms), bidirectional_(revert)
{
setTarget(g);
}
void SetGeometry::getTarget (Group *g) const
{
if (g!=nullptr)
g->copyTransform(&target_);
}
void SetGeometry::setTarget (const Group *g)
{
if (g!=nullptr)
target_.copyTransform(g);
}
void SetGeometry::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
if (s->locked())
status_ = FINISHED;
// set start position on first time it is ready
if ( status_ == READY ){
start_.copyTransform(s->group(View::GEOMETRY));
status_ = ACTIVE;
}
if ( status_ == ACTIVE ) {
// time passed since start
float progress = elapsed_ - delay_;
// perform movement
if ( ABS(duration_) > 0.f){
float ratio = progress / duration_;
Group intermediate;
intermediate.translation_ = (1.f - ratio) * start_.translation_ + ratio * target_.translation_;
intermediate.scale_ = (1.f - ratio) * start_.scale_ + ratio * target_.scale_;
intermediate.rotation_ = (1.f - ratio) * start_.rotation_ + ratio * target_.rotation_;
// apply geometry
s->group(View::GEOMETRY)->copyTransform(&intermediate);
s->touch();
}
// time-out
if ( progress > duration_ ) {
// apply target
s->group(View::GEOMETRY)->copyTransform(&target_);
s->touch();
// done
status_ = FINISHED;
}
}
}
void SetGeometry::multiply (float factor)
{
// TODO
}
SourceCallback *SetGeometry::clone() const
{
return new SetGeometry(&target_, duration_, bidirectional_);
}
SourceCallback *SetGeometry::reverse(Source *s) const
{
return bidirectional_ ? new SetGeometry( s->group(View::GEOMETRY), duration_) : nullptr;
}
void SetGeometry::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}
Grab::Grab(float dx, float dy, float ms) : SourceCallback(),
speed_(glm::vec2(dx, dy)), duration_(ms)
{
}
void Grab::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
if (s->locked())
status_ = FINISHED;
// set start on first time it is ready
if ( status_ == READY ) {
// initial position
pos_ = glm::vec2(s->group(View::GEOMETRY)->translation_);
status_ = ACTIVE;
}
if ( status_ == ACTIVE ) {
// move target by speed vector * time (in second)
pos_ += speed_ * ( dt * 0.001f);
s->group(View::GEOMETRY)->translation_ = glm::vec3(pos_, s->group(View::GEOMETRY)->translation_.z);
// time-out
if ( (elapsed_ - delay_) > duration_ )
// done
status_ = FINISHED;
}
}
void Grab::multiply (float factor)
{
speed_ *= factor;
}
SourceCallback *Grab::clone() const
{
return new Grab(speed_.x, speed_.y, duration_);
}
SourceCallback *Grab::reverse(Source *) const
{
return new Grab(speed_.x, speed_.y, 0.f);
}
void Grab::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}
Resize::Resize(float dx, float dy, float ms) : SourceCallback(),
speed_(glm::vec2(dx, dy)), duration_(ms)
{
}
void Resize::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
if (s->locked())
status_ = FINISHED;
if ( status_ == READY )
status_ = ACTIVE;
if ( status_ == ACTIVE ) {
// move target by speed vector * time (in second)
glm::vec2 scale = glm::vec2(s->group(View::GEOMETRY)->scale_) + speed_ * ( dt * 0.001f );
s->group(View::GEOMETRY)->scale_ = glm::vec3(scale, s->group(View::GEOMETRY)->scale_.z);
// time-out
if ( (elapsed_ - delay_) > duration_ )
status_ = FINISHED;
}
}
void Resize::multiply (float factor)
{
speed_ *= factor;
}
SourceCallback *Resize::clone() const
{
return new Resize(speed_.x, speed_.y, duration_);
}
SourceCallback *Resize::reverse(Source *) const
{
return new Resize(speed_.x, speed_.y, 0.f);
}
void Resize::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}
Turn::Turn(float speed, float ms) : SourceCallback(),
speed_(speed), duration_(ms)
{
}
void Turn::update(Source *s, float dt)
{
SourceCallback::update(s, dt);
if (s->locked())
status_ = FINISHED;
// set start on first time it is ready
if ( status_ == READY ){
// initial position
angle_ = s->group(View::GEOMETRY)->rotation_.z;
status_ = ACTIVE;
}
if ( status_ == ACTIVE ) {
// perform movement
angle_ -= speed_ * ( dt * 0.001f );
s->group(View::GEOMETRY)->rotation_.z = angle_;
// timeout
if ( (elapsed_ - delay_) > duration_ )
// done
status_ = FINISHED;
}
}
void Turn::multiply (float factor)
{
speed_ *= factor;
}
SourceCallback *Turn::clone() const
{
return new Turn(speed_, duration_);
}
SourceCallback *Turn::reverse(Source *) const
{
return new Turn(speed_, 0.f);
}
void Turn::accept(Visitor& v)
{
SourceCallback::accept(v);
v.visit(*this);
}