mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-11 18:34:58 +01:00
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.
694 lines
16 KiB
C++
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);
|
|
}
|