Files
vimix/Source.cpp

919 lines
32 KiB
C++

/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2020-2021 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 <algorithm>
#include <locale>
#include <tinyxml2.h>
#include <glm/gtc/matrix_access.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "defines.h"
#include "FrameBuffer.h"
#include "Decorations.h"
#include "Resource.h"
#include "SearchVisitor.h"
#include "ImageShader.h"
#include "ImageProcessingShader.h"
#include "BaseToolkit.h"
#include "SystemToolkit.h"
#include "Log.h"
#include "MixingGroup.h"
#include "Source.h"
SourceCore::SourceCore()
{
// default nodes
groups_[View::RENDERING] = new Group;
groups_[View::RENDERING]->visible_ = false;
groups_[View::MIXING] = new Group;
groups_[View::MIXING]->visible_ = false;
groups_[View::GEOMETRY] = new Group;
groups_[View::GEOMETRY]->visible_ = false;
groups_[View::LAYER] = new Group;
groups_[View::LAYER]->visible_ = false;
groups_[View::TEXTURE] = new Group;
groups_[View::TEXTURE]->visible_ = false;
groups_[View::TRANSITION] = new Group;
// temp node
stored_status_ = new Group;
// filtered image shader (with texturing and processing) for rendering
processingshader_ = new ImageProcessingShader;
// default rendering with image processing disabled
renderingshader_ = static_cast<Shader *>(new ImageShader);
}
SourceCore::SourceCore(SourceCore const& other) : SourceCore()
{
copy(other);
}
SourceCore::~SourceCore()
{
// all groups and their children are deleted in the scene
// this deletes renderingshader_ (and all source-attached nodes
// e.g. rendersurface_, overlays, blendingshader_, etc.)
delete groups_[View::RENDERING];
delete groups_[View::MIXING];
delete groups_[View::GEOMETRY];
delete groups_[View::LAYER];
delete groups_[View::TEXTURE];
delete groups_[View::TRANSITION];
delete stored_status_;
// don't forget that the processing shader
// could be created but not used and not deleted above
if ( renderingshader_ != processingshader_ )
delete processingshader_;
groups_.clear();
}
void SourceCore::copy(SourceCore const& other)
{
// copy groups properties
// groups_[View::RENDERING]->copyTransform( other.group(View::RENDERING) );
groups_[View::MIXING]->copyTransform( other.group(View::MIXING) );
groups_[View::GEOMETRY]->copyTransform( other.group(View::GEOMETRY) );
groups_[View::LAYER]->copyTransform( other.group(View::LAYER) );
groups_[View::TEXTURE]->copyTransform( other.group(View::TEXTURE) );
groups_[View::TRANSITION]->copyTransform( other.group(View::TRANSITION) );
stored_status_->copyTransform( other.stored_status_ );
// copy shader properties
processingshader_->copy(*other.processingshader_);
renderingshader_->copy(*other.renderingshader_);
}
void SourceCore::store (View::Mode m)
{
stored_status_->copyTransform(groups_[m]);
}
SourceCore& SourceCore::operator= (SourceCore const& other)
{
if (this != &other) // no self assignment
copy(other);
return *this;
}
Source::Source(uint64_t id) : SourceCore(), id_(id), ready_(false), symbol_(nullptr),
active_(true), locked_(false), need_update_(true), dt_(16.f), workspace_(STAGE)
{
// create unique id
if (id_ == 0)
id_ = BaseToolkit::uniqueId();
sprintf(initials_, "__");
name_ = "Source";
mode_ = Source::UNINITIALIZED;
// create groups and overlays for each view
// default mixing nodes
groups_[View::MIXING]->scale_ = glm::vec3(0.15f, 0.15f, 1.f);
groups_[View::MIXING]->translation_ = glm::vec3(DEFAULT_MIXING_TRANSLATION, 0.f);
frames_[View::MIXING] = new Switch;
Frame *frame = new Frame(Frame::ROUND, Frame::THIN, Frame::DROP);
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
frames_[View::MIXING]->attach(frame);
frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::DROP);
frame->translation_.z = 0.01;
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
frames_[View::MIXING]->attach(frame);
groups_[View::MIXING]->attach(frames_[View::MIXING]);
overlays_[View::MIXING] = new Group;
overlays_[View::MIXING]->translation_.z = 0.1;
overlays_[View::MIXING]->visible_ = false;
Symbol *center = new Symbol(Symbol::CIRCLE_POINT, glm::vec3(0.f, 0.f, 0.1f));
overlays_[View::MIXING]->attach(center);
groups_[View::MIXING]->attach(overlays_[View::MIXING]);
overlay_mixinggroup_ = new Switch;
overlay_mixinggroup_->translation_.z = 0.1;
center = new Symbol(Symbol::CIRCLE_POINT, glm::vec3(0.f, 0.f, 0.1f));
center->scale_= glm::vec3(1.6f, 1.6f, 1.f);
center->color = glm::vec4( COLOR_MIXING_GROUP, 0.96f);
overlay_mixinggroup_->attach(center);
rotation_mixingroup_ = new Symbol(Symbol::ROTATION, glm::vec3(0.f, 0.f, 0.1f));
rotation_mixingroup_->color = glm::vec4( COLOR_MIXING_GROUP, 0.94f);
rotation_mixingroup_->scale_ = glm::vec3(3.f, 3.f, 1.f);
overlay_mixinggroup_->attach(rotation_mixingroup_);
groups_[View::MIXING]->attach(overlay_mixinggroup_);
// default geometry nodes
frames_[View::GEOMETRY] = new Switch;
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.8f);
frames_[View::GEOMETRY]->attach(frame);
frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::GLOW);
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
frames_[View::GEOMETRY]->attach(frame);
groups_[View::GEOMETRY]->attach(frames_[View::GEOMETRY]);
overlays_[View::GEOMETRY] = new Group;
overlays_[View::GEOMETRY]->translation_.z = 0.15;
overlays_[View::GEOMETRY]->visible_ = false;
handles_[View::GEOMETRY][Handles::RESIZE] = new Handles(Handles::RESIZE);
handles_[View::GEOMETRY][Handles::RESIZE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::RESIZE]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE]);
handles_[View::GEOMETRY][Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
handles_[View::GEOMETRY][Handles::RESIZE_H]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::RESIZE_H]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE_H]);
handles_[View::GEOMETRY][Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
handles_[View::GEOMETRY][Handles::RESIZE_V]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::RESIZE_V]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::RESIZE_V]);
handles_[View::GEOMETRY][Handles::ROTATE] = new Handles(Handles::ROTATE);
handles_[View::GEOMETRY][Handles::ROTATE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::ROTATE]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::ROTATE]);
handles_[View::GEOMETRY][Handles::SCALE] = new Handles(Handles::SCALE);
handles_[View::GEOMETRY][Handles::SCALE]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::SCALE]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::SCALE]);
handles_[View::GEOMETRY][Handles::MENU] = new Handles(Handles::MENU);
handles_[View::GEOMETRY][Handles::MENU]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::MENU]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::MENU]);
handles_[View::GEOMETRY][Handles::CROP] = new Handles(Handles::CROP);
handles_[View::GEOMETRY][Handles::CROP]->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
handles_[View::GEOMETRY][Handles::CROP]->translation_.z = 0.1;
overlays_[View::GEOMETRY]->attach(handles_[View::GEOMETRY][Handles::CROP]);
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 0.7f);
overlays_[View::GEOMETRY]->attach(frame);
groups_[View::GEOMETRY]->attach(overlays_[View::GEOMETRY]);
// default layer nodes
groups_[View::LAYER]->translation_.z = -1.f;
frames_[View::LAYER] = new Switch;
frame = new Frame(Frame::ROUND, Frame::THIN, Frame::PERSPECTIVE);
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.95f);
frames_[View::LAYER]->attach(frame);
frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::PERSPECTIVE);
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 1.f);
frames_[View::LAYER]->attach(frame);
groups_[View::LAYER]->attach(frames_[View::LAYER]);
overlays_[View::LAYER] = new Group;
overlays_[View::LAYER]->translation_.z = 0.15;
overlays_[View::LAYER]->visible_ = false;
groups_[View::LAYER]->attach(overlays_[View::LAYER]);
// default appearance node
frames_[View::TEXTURE] = new Switch;
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE);
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 0.7f);
frames_[View::TEXTURE]->attach(frame);
frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::NONE);
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
frames_[View::TEXTURE]->attach(frame);
groups_[View::TEXTURE]->attach(frames_[View::TEXTURE]);
overlays_[View::TEXTURE] = new Group;
overlays_[View::TEXTURE]->translation_.z = 0.1;
overlays_[View::TEXTURE]->visible_ = false;
handles_[View::TEXTURE][Handles::RESIZE] = new Handles(Handles::RESIZE);
handles_[View::TEXTURE][Handles::RESIZE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::TEXTURE][Handles::RESIZE]->translation_.z = 0.1;
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::RESIZE]);
handles_[View::TEXTURE][Handles::RESIZE_H] = new Handles(Handles::RESIZE_H);
handles_[View::TEXTURE][Handles::RESIZE_H]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::TEXTURE][Handles::RESIZE_H]->translation_.z = 0.1;
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::RESIZE_H]);
handles_[View::TEXTURE][Handles::RESIZE_V] = new Handles(Handles::RESIZE_V);
handles_[View::TEXTURE][Handles::RESIZE_V]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::TEXTURE][Handles::RESIZE_V]->translation_.z = 0.1;
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::RESIZE_V]);
handles_[View::TEXTURE][Handles::ROTATE] = new Handles(Handles::ROTATE);
handles_[View::TEXTURE][Handles::ROTATE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::TEXTURE][Handles::ROTATE]->translation_.z = 0.1;
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::ROTATE]);
handles_[View::TEXTURE][Handles::SCALE] = new Handles(Handles::SCALE);
handles_[View::TEXTURE][Handles::SCALE]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::TEXTURE][Handles::SCALE]->translation_.z = 0.1;
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::SCALE]);
handles_[View::TEXTURE][Handles::MENU] = new Handles(Handles::MENU);
handles_[View::TEXTURE][Handles::MENU]->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 1.f);
handles_[View::TEXTURE][Handles::MENU]->translation_.z = 0.1;
overlays_[View::TEXTURE]->attach(handles_[View::TEXTURE][Handles::MENU]);
groups_[View::TEXTURE]->attach(overlays_[View::TEXTURE]);
// locker switch button : locked / unlocked icons
locker_ = new Switch;
lock_ = new Handles(Handles::LOCKED);
lock_->color.a = 0.6;
locker_->attach(lock_);
unlock_ = new Handles(Handles::UNLOCKED);
unlock_->color.a = 0.6;
locker_->attach(unlock_);
// simple image shader (with texturing) for blending
blendingshader_ = new ImageShader;
// mask produced by dedicated shader
maskshader_ = new MaskShader;
masksurface_ = new Surface(maskshader_);
// for drawing in mixing view
mixingshader_ = new ImageShader;
mixingshader_->stipple = 1.0f;
mixinggroup_ = nullptr;
// create media surface:
// - textured with original texture from media player
// - crop & repeat UV can be managed here
// - additional custom shader can be associated
texturesurface_ = new Surface(renderingshader_);
// will be created at init
renderbuffer_ = nullptr;
rendersurface_ = nullptr;
mixingsurface_ = nullptr;
activesurface_ = nullptr;
maskbuffer_ = nullptr;
maskimage_ = nullptr;
mask_need_update_ = false;
}
Source::~Source()
{
// inform links that they lost their target
while ( !links_.empty() )
links_.front()->disconnect();
// inform clones that they lost their origin
for (auto it = clones_.begin(); it != clones_.end(); ++it)
(*it)->detach();
clones_.clear();
// delete objects
if (renderbuffer_)
delete renderbuffer_;
if (maskbuffer_)
delete maskbuffer_;
if (maskimage_)
delete maskimage_;
if (masksurface_)
delete masksurface_; // deletes maskshader_
delete texturesurface_;
overlays_.clear();
frames_.clear();
handles_.clear();
}
void Source::setName (const std::string &name)
{
if (!name.empty())
name_ = BaseToolkit::transliterate(name);
initials_[0] = std::toupper( name_.front(), std::locale("C") );
initials_[1] = std::toupper( name_.back(), std::locale("C") );
}
void Source::accept(Visitor& v)
{
v.visit(*this);
}
Source::Mode Source::mode() const
{
return mode_;
}
void Source::setMode(Source::Mode m)
{
// make visible on first time
if ( mode_ == Source::UNINITIALIZED ) {
for (auto g = groups_.begin(); g != groups_.end(); ++g)
(*g).second->visible_ = true;
}
// choose frame 0 if visible, 1 if selected
uint index_frame = m == Source::VISIBLE ? 0 : 1;
for (auto f = frames_.begin(); f != frames_.end(); ++f)
(*f).second->setActive(index_frame);
// show overlay if current
bool current = m >= Source::CURRENT;
for (auto o = overlays_.begin(); o != overlays_.end(); ++o)
(*o).second->visible_ = (current && !locked_);
// the lock icon
locker_->setActive( locked_ ? 0 : 1);
// the mixing group overlay
overlay_mixinggroup_->visible_ = mixinggroup_!= nullptr && !locked_;
overlay_mixinggroup_->setActive(current);
// show in appearance view if current
groups_[View::TEXTURE]->visible_ = m > Source::VISIBLE;
mode_ = m;
}
void Source::setImageProcessingEnabled (bool on)
{
// avoid repeating
if ( on == imageProcessingEnabled() )
return;
// set pointer
if (on) {
// set the current rendering shader to be the
// (previously prepared) processing shader
renderingshader_ = static_cast<Shader *>(processingshader_);
}
else {
// clone the current Image processing shader
// (because the one currently attached to the source
// will be deleted in replaceRenderngShader().)
ImageProcessingShader *tmp = new ImageProcessingShader(*processingshader_);
// loose reference to current processing shader (to delete)
// and keep reference to the newly created one
// and keep it for later
processingshader_ = tmp;
// set the current rendering shader to a simple one
renderingshader_ = static_cast<Shader *>(new ImageShader);
}
// apply to nodes in subclasses
// this calls replaceShader() on the Primitive and
// will delete the previously attached shader
texturesurface_->replaceShader(renderingshader_);
}
void Source::setTextureMirrored (bool on)
{
texturesurface_->setMirrorTexture(on);
}
bool Source::textureMirrored ()
{
return texturesurface_->mirrorTexture();
}
bool Source::imageProcessingEnabled()
{
return ( renderingshader_ == processingshader_ );
}
void Source::render()
{
if ( renderbuffer_ == nullptr )
init();
else {
// render the view into frame buffer
renderbuffer_->begin();
texturesurface_->draw(glm::identity<glm::mat4>(), renderbuffer_->projection());
renderbuffer_->end();
ready_ = true;
}
}
void Source::attach(FrameBuffer *renderbuffer)
{
// invalid argument
if (renderbuffer == nullptr)
return;
// replace renderbuffer_
if (renderbuffer_)
delete renderbuffer_;
renderbuffer_ = renderbuffer;
// create the surfaces to draw the frame buffer in the views
rendersurface_ = new FrameBufferSurface(renderbuffer_, blendingshader_);
groups_[View::RENDERING]->attach(rendersurface_);
groups_[View::GEOMETRY]->attach(rendersurface_);
// for mixing and layer views, add another surface to overlay
// (stippled view on top with transparency)
mixingsurface_ = new FrameBufferSurface(renderbuffer_, mixingshader_);
groups_[View::MIXING]->attach(mixingsurface_);
groups_[View::LAYER]->attach(mixingsurface_);
// for views showing a scaled mixing surface, a dedicated transparent surface allows grabbing
activesurface_ = new Surface();
activesurface_->setTextureIndex(Resource::getTextureTransparent());
groups_[View::TEXTURE]->attach(activesurface_);
groups_[View::MIXING]->attach(activesurface_);
groups_[View::LAYER]->attach(activesurface_);
// Transition group node is optionnal
if (groups_[View::TRANSITION]->numChildren() > 0)
groups_[View::TRANSITION]->attach(mixingsurface_);
// if a symbol is available, add it to overlay
if (symbol_) {
overlays_[View::MIXING]->attach( symbol_ );
overlays_[View::LAYER]->attach( symbol_ );
// hack to place the symbols in the corner independently of aspect ratio
symbol_->translation_.x += 0.1f * (renderbuffer_->aspectRatio()-1.f);
}
// add lock icon to views (displayed on front)
groups_[View::LAYER]->attach( locker_ );
groups_[View::MIXING]->attach( locker_ );
groups_[View::GEOMETRY]->attach( locker_ );
groups_[View::TEXTURE]->attach( locker_ );
// scale all icon nodes to match aspect ratio
for (int v = View::MIXING; v < View::INVALID; v++) {
NodeSet::iterator node;
for (node = groups_[(View::Mode) v]->begin();
node != groups_[(View::Mode) v]->end(); ++node) {
(*node)->scale_.x = renderbuffer_->aspectRatio();
}
}
// (re) create the masking buffer
if (maskbuffer_)
delete maskbuffer_;
maskbuffer_ = new FrameBuffer( glm::vec3(0.5) * renderbuffer->resolution() );
// make the source visible
if ( mode_ == UNINITIALIZED )
setMode(VISIBLE);
// request update
need_update_ = true;
}
void Source::setActive (bool on)
{
active_ = on;
// do not disactivate if a clone depends on it
for(auto clone = clones_.begin(); clone != clones_.end(); ++clone) {
if ( (*clone)->active() )
active_ = true;
}
// an inactive source is visible only in the MIXING view
groups_[View::RENDERING]->visible_ = active_;
groups_[View::GEOMETRY]->visible_ = active_;
groups_[View::LAYER]->visible_ = active_;
}
void Source::setLocked (bool on)
{
locked_ = on;
setMode(mode_);
}
// Transfer functions from coordinates to alpha (1 - transparency)
// linear distance
float linear_(float x, float y) {
return 1.f - CLAMP( sqrt( ( x * x ) + ( y * y ) ), 0.f, 1.f );
}
// quadratic distance
float quad_(float x, float y) {
return 1.f - CLAMP( ( x * x ) + ( y * y ), 0.f, 1.f );
}
// best alpha transfer function: quadratic sinusoidal shape
float sin_quad_(float x, float y) {
float D = sqrt( ( x * x ) + ( y * y ) );
return 0.5f + 0.5f * cos( M_PI * CLAMP( D * sqrt(D), 0.f, 1.f ) );
// return 0.5f + 0.5f * cos( M_PI * CLAMP( ( ( x * x ) + ( y * y ) ), 0.f, 1.f ) );
}
float Source::depth() const
{
return group(View::RENDERING)->translation_.z;
}
void Source::setDepth(float d)
{
groups_[View::LAYER]->translation_.z = CLAMP(d, MIN_DEPTH, MAX_DEPTH);
touch();
}
float Source::alpha() const
{
return blendingShader()->color.a;
}
void Source::setAlpha(float a)
{
glm::vec2 dist = glm::vec2(groups_[View::MIXING]->translation_);
glm::vec2 step = glm::normalize(glm::vec2(1.f, 1.f));// step in diagonal by default
// step in direction of source translation if possible
if ( glm::length(dist) > DELTA_ALPHA)
step = glm::normalize(dist);
// converge to reduce the difference of alpha
// using dichotomic algorithm
float delta = sin_quad_(dist.x, dist.y) - CLAMP(a, 0.f, 1.f);
while ( glm::abs(delta) > DELTA_ALPHA ){
dist += step * (delta / 2.f);
delta = sin_quad_(dist.x, dist.y) - CLAMP(a, 0.f, 1.f);
}
// apply new mixing coordinates
groups_[View::MIXING]->translation_.x = dist.x;
groups_[View::MIXING]->translation_.y = dist.y;
touch();
}
void Source::update(float dt)
{
// keep delta-t
dt_ = dt;
// update nodes if needed
if (renderbuffer_ && mixingsurface_ && maskbuffer_ && need_update_)
{
// ADJUST alpha based on MIXING node
// read position of the mixing node and interpret this as transparency of render output
glm::vec2 dist = glm::vec2(groups_[View::MIXING]->translation_);
// use the sinusoidal transfer function
blendingshader_->color = glm::vec4(1.f, 1.f, 1.f, sin_quad_( dist.x, dist.y ));
mixingshader_->color = blendingshader_->color;
// CHANGE update status based on limbo
bool a = glm::length(dist) < MIXING_LIMBO_SCALE;
setActive( a );
// adjust scale of mixing icon : smaller if not active
groups_[View::MIXING]->scale_ = glm::vec3(MIXING_ICON_SCALE) - ( a ? glm::vec3(0.f, 0.f, 0.f) : glm::vec3(0.03f, 0.03f, 0.f) );
// MODIFY geometry based on GEOMETRY node
groups_[View::RENDERING]->translation_ = groups_[View::GEOMETRY]->translation_;
groups_[View::RENDERING]->rotation_ = groups_[View::GEOMETRY]->rotation_;
glm::vec3 s = groups_[View::GEOMETRY]->scale_;
// avoid any null scale
s.x = CLAMP_SCALE(s.x);
s.y = CLAMP_SCALE(s.y);
s.z = 1.f;
groups_[View::GEOMETRY]->scale_ = s;
groups_[View::RENDERING]->scale_ = s;
// MODIFY CROP projection based on GEOMETRY crop
renderbuffer_->setProjectionArea( glm::vec2(groups_[View::GEOMETRY]->crop_) );
// Mixing and layer icons scaled based on GEOMETRY crop
mixingsurface_->scale_ = groups_[View::GEOMETRY]->crop_;
mixingsurface_->scale_.x *= renderbuffer_->aspectRatio();
mixingsurface_->update(dt_);
// Layers icons are displayed in Perspective (diagonal)
groups_[View::LAYER]->translation_.x = -groups_[View::LAYER]->translation_.z;
groups_[View::LAYER]->translation_.y = groups_[View::LAYER]->translation_.x / LAYER_PERSPECTIVE;
// Update workspace based on depth, and
// adjust vertical position of icon depending on workspace
if (groups_[View::LAYER]->translation_.x < -LAYER_FOREGROUND) {
groups_[View::LAYER]->translation_.y -= 0.3f;
workspace_ = Source::FOREGROUND;
}
else if (groups_[View::LAYER]->translation_.x < -LAYER_BACKGROUND) {
groups_[View::LAYER]->translation_.y -= 0.15f;
workspace_ = Source::STAGE;
}
else
workspace_ = Source::BACKGROUND;
// MODIFY depth based on LAYER node
groups_[View::MIXING]->translation_.z = groups_[View::LAYER]->translation_.z;
groups_[View::GEOMETRY]->translation_.z = groups_[View::LAYER]->translation_.z;
groups_[View::RENDERING]->translation_.z = groups_[View::LAYER]->translation_.z;
// MODIFY texture projection based on APPEARANCE node
// UV to node coordinates
static glm::mat4 UVtoScene = GlmToolkit::transform(glm::vec3(1.f, -1.f, 0.f),
glm::vec3(0.f, 0.f, 0.f),
glm::vec3(-2.f, 2.f, 1.f));
// Aspect Ratio correction transform : coordinates of Appearance Frame are scaled by render buffer width
glm::mat4 Ar = glm::scale(glm::identity<glm::mat4>(), glm::vec3(renderbuffer_->aspectRatio(), 1.f, 1.f) );
// Translation : same as Appearance Frame (modified by Ar)
glm::mat4 Tra = glm::translate(glm::identity<glm::mat4>(), groups_[View::TEXTURE]->translation_);
// Scaling : inverse scaling (larger UV when smaller Appearance Frame)
glm::vec2 scale = glm::vec2(groups_[View::TEXTURE]->scale_.x,groups_[View::TEXTURE]->scale_.y);
scale = glm::sign(scale) * glm::max( glm::vec2(glm::epsilon<float>()), glm::abs(scale));
glm::mat4 Sca = glm::scale(glm::identity<glm::mat4>(), glm::vec3(scale, 1.f));
// Rotation : same angle than Appearance Frame, inverted axis
glm::mat4 Rot = glm::rotate(glm::identity<glm::mat4>(), groups_[View::TEXTURE]->rotation_.z, glm::vec3(0.f, 0.f, -1.f) );
// Combine transformations (non transitive) in this order:
// 1. switch to Scene coordinate system
// 2. Apply the aspect ratio correction
// 3. Apply the translation
// 4. Apply the rotation (centered after translation)
// 5. Revert aspect ration correction
// 6. Apply the Scaling (independent of aspect ratio)
// 7. switch back to UV coordinate system
texturesurface_->shader()->iTransform = glm::inverse(UVtoScene) * glm::inverse(Sca) * glm::inverse(Ar) * Rot * Tra * Ar * UVtoScene;
// if a mask image was given to be updated
if (mask_need_update_) {
// fill the mask buffer (once)
if (maskbuffer_->fill(maskimage_) )
mask_need_update_ = false;
}
// otherwise, render the mask buffer
else
{
// draw mask in mask frame buffer
maskbuffer_->begin(false);
// loopback maskbuffer texture for painting
masksurface_->setTextureIndex(maskbuffer_->texture());
// fill surface with mask texture
masksurface_->draw(glm::identity<glm::mat4>(), maskbuffer_->projection());
maskbuffer_->end();
}
// set the rendered mask as mask for blending
blendingshader_->mask_texture = maskbuffer_->texture();
// inform mixing group
if (mixinggroup_)
mixinggroup_->setAction(MixingGroup::ACTION_UPDATE);
// do not update next frame
need_update_ = false;
}
if (processingshader_link_.connected() && imageProcessingEnabled()) {
Source *ref_source = processingshader_link_.source();
if (ref_source!=nullptr) {
if (ref_source->imageProcessingEnabled())
processingshader_->copy( *ref_source->processingShader() );
else
processingshader_link_.disconnect();
}
}
}
FrameBuffer *Source::frame() const
{
if ( mode_ > Source::UNINITIALIZED && renderbuffer_)
{
return renderbuffer_;
}
else {
static FrameBuffer *black = new FrameBuffer(640,480);
return black;
}
}
bool Source::contains(Node *node) const
{
if ( node == nullptr )
return false;
hasNode tester(node);
return tester(this);
}
void Source::storeMask(FrameBufferImage *img)
{
// free the output mask storage
if (maskimage_ != nullptr) {
delete maskimage_;
maskimage_ = nullptr;
}
// if no image is provided
if (img == nullptr) {
// if ready
if (maskbuffer_!=nullptr) {
// get & store image from mask buffer
maskimage_ = maskbuffer_->image();
}
}
else
// store the given image
maskimage_ = img;
// maskimage_ can now be accessed with Source::getStoredMask
}
void Source::setMask(FrameBufferImage *img)
{
// if a valid image is given
if (img != nullptr && img->width>0 && img->height>0) {
// remember this new image as the current mask
// NB: will be freed when replaced
storeMask(img);
// ask Source::update to use it at next update for filling mask buffer
mask_need_update_ = true;
// ask to update the source
touch();
}
else
mask_need_update_ = false;
}
bool Source::hasNode::operator()(const Source* elem) const
{
if (_n && elem)
{
// quick case (most frequent and easy to answer)
if (elem->rendersurface_ == _n)
return true;
// general case: traverse tree of all Groups recursively using a SearchVisitor
SearchVisitor sv(_n);
// search in groups for all views
for (auto g = elem->groups_.begin(); g != elem->groups_.end(); ++g) {
(*g).second->accept(sv);
if (sv.found())
return true;
}
// search in overlays for all views
for (auto g = elem->overlays_.begin(); g != elem->overlays_.end(); ++g) {
(*g).second->accept(sv);
if (sv.found())
return true;
}
}
return false;
}
void Source::clearMixingGroup()
{
mixinggroup_ = nullptr;
overlay_mixinggroup_->visible_ = false;
}
CloneSource *Source::clone(uint64_t id)
{
CloneSource *s = new CloneSource(this, id);
clones_.push_back(s);
return s;
}
CloneSource::CloneSource(Source *origin, uint64_t id) : Source(id), origin_(origin)
{
name_ = origin->name();
// set symbol
symbol_ = new Symbol(Symbol::CLONE, glm::vec3(0.75f, 0.75f, 0.01f));
symbol_->scale_.y = 1.5f;
}
CloneSource::~CloneSource()
{
if (origin_)
origin_->clones_.remove(this);
}
CloneSource *CloneSource::clone(uint64_t id)
{
// do not clone a clone : clone the original instead
if (origin_)
return origin_->clone(id);
else
return nullptr;
}
void CloneSource::init()
{
if (origin_ && origin_->mode_ > Source::UNINITIALIZED) {
// get the texture index from framebuffer of view, apply it to the surface
texturesurface_->setTextureIndex( origin_->texture() );
// create Frame buffer matching size of session
FrameBuffer *renderbuffer = new FrameBuffer( origin_->frame()->resolution(), true);
// set the renderbuffer of the source and attach rendering nodes
attach(renderbuffer);
// deep update to reorder
++View::need_deep_update_;
// done init
Log::Info("Source %s cloning source %s.", name().c_str(), origin_->name().c_str() );
}
}
void CloneSource::setActive (bool on)
{
active_ = on;
groups_[View::RENDERING]->visible_ = active_;
groups_[View::GEOMETRY]->visible_ = active_;
groups_[View::LAYER]->visible_ = active_;
if (origin_) {
if ( mode_ > Source::UNINITIALIZED)
origin_->touch();
// change visibility of active surface (show preview of origin when inactive)
if (activesurface_) {
if (active_)
activesurface_->setTextureIndex(Resource::getTextureTransparent());
else
activesurface_->setTextureIndex(origin_->texture());
}
}
}
uint CloneSource::texture() const
{
if (origin_ != nullptr)
return origin_->texture();
else
return Resource::getTextureBlack();
}
void CloneSource::accept(Visitor& v)
{
Source::accept(v);
if (!failed())
v.visit(*this);
}