#include "externals.hh" #include "sync.hh" #include "globals.hh" namespace { const std::map< std::string , T_SyncSegment::E_SegmentType > SegmentTypes_( ([] { std::map< std::string , T_SyncSegment::E_SegmentType > t; t.emplace( "linear" , T_SyncSegment::LINEAR ); t.emplace( "ramp" , T_SyncSegment::RAMP ); t.emplace( "smooth" , T_SyncSegment::SMOOTH ); return t; })()); } /*= T_SyncTime ===============================================================*/ void T_SyncTime::setDuration( const float uDuration , const uint32_t iDuration ) { this->uDuration = std::max( 1e-3f , uDuration ); this->iDuration = std::max( 1u , iDuration ); time = std::min( time , duration( ) ); } /*= T_SyncCurves =============================================================*/ void T_SyncCurves::clear( ) { curves.clear( ); } void T_SyncCurves::setCurve( T_SyncCurve curve ) { curves.set( std::move( curve ) ); } int32_t T_SyncCurves::indexOf( T_String const& name ) noexcept { const auto idx( curves.indexOf( name ) ); return idx == ebcl::T_HashIndex::INVALID_INDEX ? -1 : int32_t( idx ); } /*= T_SyncCurveCache =========================================================*/ T_SyncCurveCache::T_SyncCurveCache( T_SyncTime const& time , T_SyncCurves const& curves , const uint32_t curve ) noexcept : curve( curve ) , curPos( 0 ) { auto const& c( curves.curves[ curve ] ); const auto ns( c.segments.size( ) ); assert( ns > 0 ); uint32_t s = 0; for ( auto i = 0u ; i < ns ; i ++ ) { auto const& v( c.segments[ i ] ); assert( v.nPoints >= 2 ); assert( v.durations.size( ) == v.nPoints - 1 ); const auto nd( v.nPoints - 1 ); for ( auto j = 0u ; j < nd ; j ++ ) { const auto sStart( s * time.uDuration ); if ( time.time >= sStart ) { curPos = segStarts.size( ); } segStarts.add( sStart ); segRefs.add( std::make_pair( i , j ) ); s += v.durations[ j ]; segEnds.add( std::min( s , time.iDuration ) * time.uDuration ); if ( s > time.iDuration ) { return; } } } } /*----------------------------------------------------------------------------*/ uint32_t T_SyncCurveCache::findSegment( const float time ) const noexcept { const auto ns( segStarts.size( ) ); for ( auto i = 0u ; i < ns ; i ++ ) { if ( segStarts[ i ] <= time && segEnds[ i ] > time ) { return i; } } return ns; } /*----------------------------------------------------------------------------*/ float T_SyncCurveCache::valueAt( T_SyncTime const& time , T_SyncCurves const& curves , const float position ) const noexcept { return segmentValue( time.time , findSegment( position ) , curves.curves[ curve ].segments ); } float T_SyncCurveCache::value( T_SyncTime const& time , T_SyncCurves const& curves ) noexcept { const auto t( time.time ); // Check / update curPos const float ss0( curPos == segStarts.size( ) ? time.duration( ) : segStarts[ curPos ] ); if ( ss0 > t ) { curPos = findSegment( t ); } else { while ( curPos < segStarts.size( ) && t >= segEnds[ curPos ] ) { curPos ++; } } // We got the actual index in curPos, now compute the value. return segmentValue( t , curPos , curves.curves[ curve ].segments ); } /*----------------------------------------------------------------------------*/ float T_SyncCurveCache::segmentValue( float time , uint32_t segIndex , T_Array< T_SyncSegment > const& segments ) const noexcept { const auto sss( segStarts.size( ) ); if ( segIndex >= sss ) { assert( sss != 0 ); segIndex = sss - 1; time = segEnds[ segIndex ]; } auto const& idxp( segRefs[ segIndex ] ); auto const& seg( segments[ idxp.first ] ); // Interpolation factor const float st( segStarts[ segIndex ] ); const float et( segEnds[ segIndex ] ); const float v0 = ( time - st ) / ( et - st ); float v = v0; if ( seg.type != T_SyncSegment::LINEAR ) { v *= v0; if ( seg.type == T_SyncSegment::SMOOTH ) { v *= 3 - 2 * v0; } } const auto pid( idxp.second ); const float sv( seg.values[ pid ] ); const float ev( seg.values[ pid + 1 ] ); #if 0 printf( "[%.2f] gidx %d - seg %d idx %d - %f\n" , time , segIndex , idxp.first , idxp.second , v * ( ev - sv ) + sv ); #endif return v * ( ev - sv ) + sv; } /*= T_SyncValues =============================================================*/ T_SyncValues::T_SyncValues( ) { values.add( 0 ); } // --------------------------------------------------------------------- void T_SyncValues::clear( ) { index.clear( ); identifiers.clear( ); values.clear( ); overriden.clear( ); values.add( 0 ); } bool T_SyncValues::addValue( T_String const& name , const float initial ) noexcept { const uint32_t hash{ ComputeHash( name ) }; uint32_t existing{ index.first( hash ) }; while ( existing != T_HashIndex::INVALID_INDEX ) { if ( name == identifiers[ existing ] ) { return false; } existing = index.next( existing ); } const auto li( values.size( ) ); index.add( hash ); identifiers.add( name ); values.add( initial ); std::swap( values[ li ] , values[ li - 1 ] ); overriden.add( false ); return true; } uint32_t T_SyncValues::indexOf( T_String const& name ) const noexcept { const uint32_t hash{ ComputeHash( name ) }; uint32_t existing{ index.first( hash ) }; while ( existing != T_HashIndex::INVALID_INDEX ) { if ( name == identifiers[ existing ] ) { return existing; } existing = index.next( existing ); } return values.size( ) - 1; } /*= T_SyncManager ============================================================*/ void T_SyncManager::setDuration( const float uDuration , const uint32_t iDuration ) { time_.setDuration( uDuration , iDuration ); updateCurveCaches( ); } void T_SyncManager::setTime( const float time ) { time_.setTime( time ); updateValues( ); } /*----------------------------------------------------------------------------*/ void T_SyncManager::checkCurveFile( ) { if ( watcher_ ) { return; } printf( "CURVE INIT\n" ); bool missing; if ( loadCurves_( missing ) || !missing ) { watcher_ = std::make_unique< T_WatchedFiles >( Globals::Watcher( ) , [this] { curvesChanged_( ); } ); watcher_->watch( "curves.json" ); } printf( "INIT MISSING IS %c\n" , missing ? 'Y' : 'N' ); } void T_SyncManager::clearCurves( ) { curves_.clear( ); updateCurveCaches( ); } void T_SyncManager::setCurve( T_SyncCurve curve ) { curves_.setCurve( curve ); updateCurveCaches( ); } void T_SyncManager::curvesChanged_( ) { bool missing; if ( !loadCurves_( missing ) && missing ) { watcher_.reset( ); } } bool T_SyncManager::loadCurves_( bool& missing ) { using T_STI_ = std::istream_iterator< char >; std::ifstream file( "curves.json" ); picojson::value root; try { missing = !file.is_open( ); if ( missing ) { return false; } std::string errors; picojson::parse( root , T_STI_( file ) , T_STI_( ) , &errors ); if ( !errors.empty( ) ) { printf( "Failed to parse 'curves.json':\n%s\n" , errors.c_str( ) ); return false; } } catch ( std::ios_base::failure const& e ) { printf( "I/O error while reading 'curves.json'\n%s\n" , e.what( ) ); missing = false; return false; } T_SyncCurves nCurves; if ( !loadCurvesData_( nCurves , root ) ) { return false; } curves_ = std::move( nCurves ); updateCurveCaches( ); return true; } bool T_SyncManager::loadCurvesData_( T_SyncCurves& curves , picojson::value const& root ) { if ( !root.is< T_JSONObject >( ) ) { printf( "Curves data: root is not a JSON object\n" ); return false; } auto const& r( root.get< T_JSONObject >( ) ); bool ok( true ); for ( auto const& item : r ) { if ( curves.indexOf( item.first.c_str( ) ) != -1 ) { printf( "Curves data: duplicate curve '%s'\n" , item.first.c_str( ) ); ok = false; continue; } if ( !item.second.is< T_JSONArray >( ) ) { printf( "Curves data: entry for curve '%s' is not an array\n" , item.first.c_str( ) ); ok = false; continue; } T_SyncCurve nsc{ item.first.c_str( ) }; bool segsOk( true ); for ( auto const& v : item.second.get< T_JSONArray >( ) ) { if ( !v.is< T_JSONObject >( ) ) { printf( "Curves data: curve '%s': invalid segment\n" , item.first.c_str( ) ); segsOk = false; continue; } T_SyncSegment segment; try { if ( !loadSegmentData_( segment , (char*) nsc.name.toOSString( ).data( ) , v.get< T_JSONObject >( ) ) ) { segsOk = false; continue; } } catch ( X_JsonGetFailed const& ) { printf( "Curves data: curve '%s': could not parse segment data\n" , item.first.c_str( ) ); segsOk = false; continue; } nsc.segments.add( std::move( segment ) ); } ok = ok && segsOk; if ( nsc.segments.size( ) == 0 && segsOk ) { printf( "Curves data: curve '%s': no segments\n" , item.first.c_str( ) ); ok = false; } curves.setCurve( std::move( nsc ) ); } return ok; } bool T_SyncManager::loadSegmentData_( T_SyncSegment& segment , std::string const& curve , T_JSONObject const& sd ) { auto const& sType( jsonGet< std::string >( sd , "type" ) ); const auto p( SegmentTypes_.find( sType ) ); if ( p == SegmentTypes_.end( ) ) { printf( "Curves data: curve '%s': invalid segment type '%s'\n" , curve.c_str( ) , sType.c_str( ) ); return false; } segment.type = p->second; auto const& vList( jsonGet< T_JSONArray >( sd , "values" ) ); auto const& dList( jsonGet< T_JSONArray >( sd , "durations" ) ); if ( vList.size( ) < 2 ) { printf( "Curves data: curve '%s': segment doesn't have enough values\n" , curve.c_str( ) ); return false; } if ( vList.size( ) != dList.size( ) + 1 ) { printf( "Curves data: curve '%s': segment values / durations count mismatch\n" , curve.c_str( ) ); return false; } for ( auto const& vv : vList ) { if ( !vv.is< double >( ) ) { printf( "Curves data: curve '%s': non-numeric entry in segment values\n" , curve.c_str( ) ); return false; } segment.values.add( vv.get< double >( ) ); } for ( auto const& dv : dList ) { if ( !dv.is< double >( ) ) { printf( "Curves data: curve '%s': non-numeric entry in segment durations\n" , curve.c_str( ) ); return false; } const double dvn( dv.get< double >( ) ); if ( fmod( dvn , 1.0 ) != 0.0 || dvn <= 0 ) { printf( "Curves data: curve '%s': invalid segment duration %f\n" , curve.c_str( ) , dvn ); return false; } segment.durations.add( dvn ); } segment.nPoints = segment.values.size( ); return true; } /*----------------------------------------------------------------------------*/ void T_SyncManager::updateCurveCaches( ) { curveCaches_.clear( ); const uint32_t nv( values_.identifiers.size( ) ); for ( auto i = 0u ; i < nv ; i ++ ) { auto const& id( values_.identifiers[ i ] ); const auto cp( curves_.indexOf( id ) ); if ( cp < 0 ) { curveCaches_.addNew( ); } else { curveCaches_.add( NewOwned< T_SyncCurveCache >( time_ , curves_ , cp ) ); } } updateValues( ); } void T_SyncManager::updateValues( ) { const auto nv( values_.identifiers.size( ) ); assert( nv == curveCaches_.size( ) ); for ( auto i = 0u ; i < nv ; i ++ ) { auto const& cc( curveCaches_[ i ] ); if ( !cc || values_.overriden[ i ] ) { continue; } values_.values[ i ] = cc->value( time_ , curves_ ); } } /*============================================================================*/ #if 0 void T_SyncManager::makeUI( ) { auto const& dspSize( ImGui::GetIO( ).DisplaySize ); if ( wOverrides ) { ImGui::SetNextWindowSize( ImVec2( 300 , dspSize.y - 300 ) , ImGuiSetCond_Once ); ImGui::SetNextWindowPos( ImVec2( 0 , 150 ) , ImGuiSetCond_Once ); ImGui::Begin( "Input overrides" ); displayOvSections( uiRoot , true ); ImGui::End( ); } if ( wCurves ) { ImGui::SetNextWindowSize( ImVec2( dspSize.x , 150 ) , ImGuiSetCond_Once ); ImGui::SetNextWindowPos( ImVec2( 0 , dspSize.y - 150 ) , ImGuiSetCond_Once ); ImGui::Begin( "Curve editor" ); // XXX contents ImGui::End( ); } } void T_SyncManager::displayOvSections( T_SyncUISections& sections , const bool topLevel ) { for ( auto& s : sections ) { const bool display( topLevel ? ImGui::CollapsingHeader( s->title.c_str( ) ) : ImGui::TreeNode( s->title.c_str( ) ) ); if ( !display ) { continue; } displayOvSections( s->subsections ); if ( s->subsections.size( ) && s->overrides.size( ) ) { ImGui::Separator( ); } displayOvControls( s->overrides ); if ( !topLevel ) { ImGui::TreePop( ); } } } void T_SyncManager::displayOvControls( T_SyncUIOverrides& overrides ) { for ( auto& o : overrides ) { // XXX enable override checkbox should be selected and disabled // if there is no curve const bool changed( ImGui::Checkbox( "" , &o->enabled ) ); if ( changed ) { // XXX mark the inputs as coming from the UI / the curves } ImGui::SameLine( ); switch ( o->type ) { case T_SyncUIOverride::FLOAT: case T_SyncUIOverride::VEC2: case T_SyncUIOverride::VEC3: case T_SyncUIOverride::VEC4: case T_SyncUIOverride::INT: case T_SyncUIOverride::COLOR: case T_SyncUIOverride::COLOR_GRADING: case T_SyncUIOverride::CAMERA: break; } } } #endif