Files
vimix/src/Source.cpp
Bruno Herbelin 124415363f Improve Layers view icons and Workspace management
Added 'Workspace any' in list of workspaces to allow Geometry view to list sources from all workspaces. Updated icon for layers view, in left panel and in view.
2023-08-30 20:47:14 +02:00

1000 lines
36 KiB
C++

/*
* This file is part of vimix - video live mixer
*
* **Copyright** (C) 2019-2023 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 "MixingGroup.h"
#include "SourceCallback.h"
#include "CloneSource.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_(SourceUpdate_None), dt_(16.f), workspace_(WORKSPACE_CENTRAL)
{
// create unique id
if (id_ == 0)
id_ = BaseToolkit::uniqueId();
snprintf(initials_, 3, "__");
name_ = "Source";
mode_ = Source::UNINITIALIZED;
// create groups and overlays for each view
// default mixing nodes
groups_[View::MIXING]->scale_ = glm::vec3(MIXING_ICON_SCALE);
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); // visible
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.85f);
frames_[View::MIXING]->attach(frame);
frame = new Frame(Frame::ROUND, Frame::THIN, Frame::DROP); // selected
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 0.95f);
frames_[View::MIXING]->attach(frame);
frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::DROP); // current
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]);
// Glyphs show letters from the intials, with Font index 4 (LARGE)
initial_0_ = new Character(4);
initial_0_->translation_ = glm::vec3(0.2f, 0.8f, 0.1f);
initial_0_->scale_.y = 0.2f;
groups_[View::MIXING]->attach(initial_0_);
initial_1_ = new Character(4);
initial_1_->translation_ = glm::vec3(0.4f, 0.8f, 0.1f);
initial_1_->scale_.y = 0.2f;
groups_[View::MIXING]->attach(initial_1_);
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); // visible
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.85f);
frames_[View::GEOMETRY]->attach(frame);
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE); // selected
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 0.95f);
frames_[View::GEOMETRY]->attach(frame);
frame = new Frame(Frame::SHARP, Frame::LARGE, Frame::GLOW); // current
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); // visible
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_DEFAULT_SOURCE, 0.85f);
frames_[View::LAYER]->attach(frame);
frame = new Frame(Frame::ROUND, Frame::THIN, Frame::PERSPECTIVE); // selected
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_HIGHLIGHT_SOURCE, 0.95f);
frames_[View::LAYER]->attach(frame);
frame = new Frame(Frame::ROUND, Frame::LARGE, Frame::PERSPECTIVE); // current
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]);
groups_[View::LAYER]->attach(initial_0_);
groups_[View::LAYER]->attach(initial_1_);
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); // visible
frame->translation_.z = 0.1;
frame->color = glm::vec4( COLOR_APPEARANCE_SOURCE, 0.2f);
frames_[View::TEXTURE]->attach(frame);
frame = new Frame(Frame::SHARP, Frame::THIN, Frame::NONE); // selected
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); // current
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;
masksource_ = new SourceLink;
}
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 masksource_;
delete texturesurface_;
overlays_.clear();
frames_.clear();
handles_.clear();
// clear and delete callbacks
access_callbacks_.lock();
for (auto iter=update_callbacks_.begin(); iter != update_callbacks_.end(); ) {
SourceCallback *callback = *iter;
iter = update_callbacks_.erase(iter);
delete callback;
}
access_callbacks_.unlock();
}
void Source::setName (const std::string &name)
{
if (!name.empty())
name_ = BaseToolkit::unspace( BaseToolkit::transliterate(name) );
initials_[0] = std::toupper( name_.front(), std::locale("C") );
initials_[1] = std::toupper( name_.back(), std::locale("C") );
initial_0_->setChar(initials_[0]);
initial_1_->setChar(initials_[1]);
}
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;
}
// Switch frame between visible, selected and current modes
const uint index_frame = MAX(m, 1) - 1;
for (auto f = frames_.begin(); f != frames_.end(); ++f)
(*f).second->setActive(index_frame);
// Switch overlay if current
const bool current = m >= Source::CURRENT;
for (auto o = overlays_.begin(); o != overlays_.end(); ++o)
(*o).second->visible_ = (current && !locked_);
// the opacity of the initials changes if current
initial_0_->color.w = current ? 1.0 : 0.7;
initial_1_->color.w = current ? 1.0 : 0.7;
// the lock icon
locker_->setActive( locked_ ? 0 : 1);
locker_->child(1)->visible_ = current;
// the mixing group overlay
overlay_mixinggroup_->visible_ = mixinggroup_!= nullptr && !locked_;
overlay_mixinggroup_->setActive(current);
// show in texturing view if selected or 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 rendersurface_ only once
if ( rendersurface_ == nullptr) {
// 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_);
}
else
rendersurface_->setFrameBuffer(renderbuffer_);
// create mixingsurface_ only once
if ( mixingsurface_ == nullptr) {
// 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_);
// Transition group node is optionnal
if (groups_[View::TRANSITION]->numChildren() > 0)
groups_[View::TRANSITION]->attach(mixingsurface_);
}
else
mixingsurface_->setFrameBuffer(renderbuffer_);
// create activesurface_ only once (and other initializations done once)
if ( activesurface_ == nullptr) {
// 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_);
// if a symbol is available, add it to overlay
if (symbol_) {
overlays_[View::MIXING]->attach( symbol_ );
overlays_[View::LAYER]->attach( symbol_ );
}
// 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_ );
}
// if a symbol is available
if (symbol_)
// hack to place the symbols in the corner independently of aspect ratio
symbol_->translation_.x = (renderbuffer_->aspectRatio() - 0.3f) / renderbuffer_->aspectRatio();
// hack to place the initials in the corner independently of aspect ratio
initial_0_->translation_.x = 0.2f - renderbuffer_->aspectRatio();
initial_1_->translation_.x = 0.4f - renderbuffer_->aspectRatio();
// scale all icon nodes to match aspect ratio
for (int v = View::MIXING; v <= View::TRANSITION; 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_ |= Source::SourceUpdate_Render;
}
void Source::setActive (bool on)
{
// do not disactivate if any clone depends on it
for(auto clone = clones_.begin(); clone != clones_.end(); ++clone) {
if ( (*clone)->ready() && (*clone)->active() ) {
on = true;
break;
}
}
// request update
if (active_ != on)
need_update_ |= Source::SourceUpdate_Render;
// activate
active_ = on;
// 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::setActive (float threshold)
{
setActive( glm::length( glm::vec2(groups_[View::MIXING]->translation_) ) < threshold );
}
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 SourceCore::alphaFromCordinates(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 ) );
}
float Source::depth() const
{
return group(View::RENDERING)->translation_.z;
}
float Source::alpha() const
{
return blendingShader()->color.a;
}
bool Source::textureTransformed () const
{
return frame()->projectionArea() != glm::vec2(1.f) || // cropped
group(View::TEXTURE)->rotation_.z != 0.f || // rotation
group(View::TEXTURE)->scale_ != glm::vec3(1.f) || // scaled
group(View::TEXTURE)->translation_ != glm::vec3(0.f);// displaced
}
void Source::call(SourceCallback *callback, bool override)
{
if (callback != nullptr) {
// lock access to callbacks list
access_callbacks_.lock();
// if operation should override previous callbacks of same type
if (override) {
// finish all callbacks of the same type
for (auto iter=update_callbacks_.begin(); iter != update_callbacks_.end(); ++iter) {
if ( callback->type() == (*iter)->type() )
(*iter)->finish();
}
}
// allways add the given callback to list of callbacks
update_callbacks_.push_back(callback);
// release access to callbacks list
access_callbacks_.unlock();
}
}
void Source::finish(SourceCallback *callback)
{
if (callback != nullptr) {
// lock access to callbacks list
access_callbacks_.lock();
// make sure the given pointer is a callback listed in this source
auto cb = std::find(update_callbacks_.begin(), update_callbacks_.end(), callback);
if (cb != update_callbacks_.end())
// set found callback to finish state
(*cb)->finish();
// release access to callbacks list
access_callbacks_.unlock();
}
}
void Source::updateCallbacks(float dt)
{
// lock access to callbacks list
access_callbacks_.lock();
// call callback functions
for (auto iter=update_callbacks_.begin(); iter != update_callbacks_.end(); )
{
SourceCallback *callback = *iter;
// call update on callbacks
callback->update(this, dt);
need_update_ |= Source::SourceUpdate_Render;
// remove and delete finished callbacks
if (callback->finished()) {
iter = update_callbacks_.erase(iter);
delete callback;
}
// iterate
else
++iter;
}
// release access to callbacks list
access_callbacks_.unlock();
}
CloneSource *Source::clone(uint64_t id)
{
CloneSource *s = new CloneSource(this, id);
clones_.push_back(s);
return s;
}
void Source::update(float dt)
{
// keep delta-t
dt_ = dt;
// if update is possible
if (renderbuffer_ && mixingsurface_ && maskbuffer_)
{
// call active callbacks
updateCallbacks(dt);
// update nodes if needed
if (need_update_ & SourceUpdate_Render)
{
// do not update next frame
need_update_ &= ~SourceUpdate_Render;
// 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, SourceCore::alphaFromCordinates( dist.x, dist.y ));
mixingshader_->color = blendingshader_->color;
// adjust scale of mixing icon : smaller if not active
groups_[View::MIXING]->scale_ = glm::vec3(MIXING_ICON_SCALE) - ( active_ ? glm::vec3(0.f, 0.f, 0.f) : glm::vec3(0.03f, 0.03f, 0.f) );
// change stippling intensity of source in mixing view to indicate if shown in scene
mixingshader_->stipple = (blendingshader_->color.a > 0.f) ? 1.f : 0.75f;
// 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::WORKSPACE_FOREGROUND;
}
else if (groups_[View::LAYER]->translation_.x < -LAYER_BACKGROUND) {
groups_[View::LAYER]->translation_.y -= 0.15f;
workspace_ = Source::WORKSPACE_CENTRAL;
}
else
workspace_ = Source::WORKSPACE_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;
// inform mixing group
if (mixinggroup_)
mixinggroup_->setAction(MixingGroup::ACTION_UPDATE);
}
if (need_update_ & SourceUpdate_Mask_fill) {
// do not update Mask fill next frame
need_update_ &= ~SourceUpdate_Mask_fill;
// fill the mask buffer (once)
maskbuffer_->fill(maskimage_);
}
if (need_update_ & SourceUpdate_Mask) {
// do not update Mask next frame
need_update_ &= ~SourceUpdate_Mask;
// MODIFY Mask based on mask shader mode
// if MaskShader::SOURCE available, set a source as mask for blending
if (maskshader_->mode == MaskShader::SOURCE && masksource_->connected()) {
Source *ref_source = masksource_->source();
if (ref_source != nullptr) {
if (ref_source->ready())
// set mask texture to mask source
blendingshader_->mask_texture = ref_source->frame()->texture();
else
// retry for when source will be ready
need_update_ |= SourceUpdate_Mask;
}
}
// all other MaskShader types to update
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 mask texture to mask buffer
blendingshader_->mask_texture = maskbuffer_->texture();
}
}
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(64,64);
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 to update the source mask
touch(Source::SourceUpdate_Mask_fill);
}
}
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;
}