// SPDX-License-Identifier: GPL-2.0-or-later /** @file * Implementation of coverage with floating-point values. *//* * Authors: * see git history * Fred * * Copyright (C) 2018 Authors * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #ifdef faster_flatten # include // std::abs(float) #endif #include #include "livarot/float-line.h" #include "livarot/int-line.h" #include FloatLigne::FloatLigne() { s_first = s_last = -1; } FloatLigne::~FloatLigne() = default; /// Reset the line to empty (boundaries and runs). void FloatLigne::Reset() { bords.clear(); runs.clear(); s_first = s_last = -1; } /** * Add a coverage portion. * * \param guess Position from where we should try to insert the first * boundary, or -1 if we don't have a clue. */ int FloatLigne::AddBord(float spos, float sval, float epos, float eval, int guess) { // if ( showCopy ) printf("b= %f %f -> %f %f \n",spos,sval,epos,eval); if ( spos >= epos ) { return -1; } float pente = (eval - sval) / (epos - spos); #ifdef faster_flatten if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) { return -1; epos = spos; pente = 0; } #endif if ( guess >= int(bords.size()) ) { guess = -1; } // add the left boundary float_ligne_bord b; int n = bords.size(); b.pos = spos; b.val = sval; b.start = true; b.other = n + 1; b.pente = pente; b.s_prev = b.s_next = -1; bords.push_back(b); // insert it in the doubly-linked list InsertBord(n, spos, guess); // add the right boundary n = bords.size(); b.pos = epos; b.val = eval; b.start = false; b.other = n-1; b.pente = pente; b.s_prev = b.s_next = -1; bords.push_back(b); // insert it in the doubly-linked list, knowing that boundary at index n-1 is not too far before me InsertBord(n, epos, n - 1); return n; } /** * Add a coverage portion. * * \param guess Position from where we should try to insert the first * boundary, or -1 if we don't have a clue. */ int FloatLigne::AddBord(float spos, float sval, float epos, float eval, float pente, int guess) { // if ( showCopy ) printf("b= %f %f -> %f %f \n",spos,sval,epos,eval); if ( spos >= epos ) { return -1; } #ifdef faster_flatten if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) { return -1; epos = spos; pente = 0; } #endif if ( guess >= int(bords.size()) ) { guess=-1; } float_ligne_bord b; int n = bords.size(); b.pos = spos; b.val = sval; b.start = true; b.other = n + 1; b.pente = pente; b.s_prev = b.s_next = -1; bords.push_back(b); n = bords.size(); b.pos = epos; b.val = eval; b.start = false; b.other = n - 1; b.pente = pente; b.s_prev = b.s_next = -1; bords.push_back(b); InsertBord(n - 1, spos, guess); InsertBord(n, epos, n - 1); /* if ( bords[n-1].s_next < 0 ) { bords[n].s_next=-1; s_last=n; bords[n].s_prev=n-1; bords[n-1].s_next=n; } else if ( bords[bords[n-1].s_next].pos >= epos ) { bords[n].s_next=bords[n-1].s_next; bords[bords[n].s_next].s_prev=n; bords[n].s_prev=n-1; bords[n-1].s_next=n; } else { int c=bords[bords[n-1].s_next].s_next; while ( c >= 0 && bords[c].pos < epos ) c=bords[c].s_next; if ( c < 0 ) { bords[n].s_prev=s_last; bords[s_last].s_next=n; s_last=n; } else { bords[n].s_prev=bords[c].s_prev; bords[bords[n].s_prev].s_next=n; bords[n].s_next=c; bords[c].s_prev=n; } }*/ return n; } /** * Add a coverage portion. * * \param guess Position from where we should try to insert the last * boundary, or -1 if we don't have a clue. */ int FloatLigne::AddBordR(float spos, float sval, float epos, float eval, float pente, int guess) { // if ( showCopy ) printf("br= %f %f -> %f %f \n",spos,sval,epos,eval); // return AddBord(spos,sval,epos,eval,pente,guess); if ( spos >= epos ){ return -1; } #ifdef faster_flatten if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) { return -1; epos = spos; pente = 0; } #endif if ( guess >= int(bords.size()) ) { guess=-1; } float_ligne_bord b; int n = bords.size(); b.pos = spos; b.val = sval; b.start = true; b.other = n + 1; b.pente = pente; b.s_prev = b.s_next = -1; bords.push_back(b); n = bords.size(); b.pos = epos; b.val = eval; b.start = false; b.other = n - 1; b.pente = pente; b.s_prev = b.s_next = -1; bords.push_back(b); InsertBord(n, epos, guess); InsertBord(n - 1, spos, n); /* if ( bords[n].s_prev < 0 ) { bords[n-1].s_prev=-1; s_first=n-1; bords[n-1].s_next=n; bords[n].s_prev=n-1; } else if ( bords[bords[n].s_prev].pos <= spos ) { bords[n-1].s_prev=bords[n].s_prev; bords[bords[n-1].s_prev].s_next=n-1; bords[n-1].s_next=n; bords[n].s_prev=n-1; } else { int c=bords[bords[n].s_prev].s_prev; while ( c >= 0 && bords[c].pos > spos ) c=bords[c].s_prev; if ( c < 0 ) { bords[n-1].s_next=s_first; bords[s_first].s_prev=n-1; s_first=n-1; } else { bords[n-1].s_next=bords[c].s_next; bords[bords[n-1].s_next].s_prev=n-1; bords[n-1].s_prev=c; bords[c].s_next=n-1; } }*/ return n - 1; } /** * Add a coverage portion by appending boundaries at the end of the list. * * This works because we know they are on the right. */ int FloatLigne::AppendBord(float spos, float sval, float epos, float eval, float pente) { // if ( showCopy ) printf("b= %f %f -> %f %f \n",spos,sval,epos,eval); // return AddBord(spos,sval,epos,eval,pente,s_last); if ( spos >= epos ) { return -1; } #ifdef faster_flatten if ( std::abs(epos - spos) < 0.001 || std::abs(pente) > 1000 ) { return -1; epos = spos; pente = 0; } #endif int n = bords.size(); float_ligne_bord b; b.pos = spos; b.val = sval; b.start = true; b.other = n + 1; b.pente = pente; b.s_prev = s_last; b.s_next = n + 1; bords.push_back(b); if ( s_last >= 0 ) { bords[s_last].s_next = n; } if ( s_first < 0 ) { s_first = n; } n = bords.size(); b.pos = epos; b.val = eval; b.start = false; b.other = n - 1; b.pente = pente; b.s_prev = n - 1; b.s_next = -1; bords.push_back(b); s_last = n; return n; } // insertion in a boubly-linked list. nothing interesting here void FloatLigne::InsertBord(int no, float /*p*/, int guess) { // TODO check if ignoring p is bad if ( no < 0 || no >= int(bords.size()) ) { return; } if ( s_first < 0 ) { s_first = s_last = no; bords[no].s_prev = -1; bords[no].s_next = -1; return; } if ( guess < 0 || guess >= int(bords.size()) ) { int c = s_first; while ( c >= 0 && c < int(bords.size()) && CmpBord(bords[c], bords[no]) < 0 ) { c = bords[c].s_next; } if ( c < 0 || c >= int(bords.size()) ) { bords[no].s_prev = s_last; bords[s_last].s_next = no; s_last = no; } else { bords[no].s_prev = bords[c].s_prev; if ( bords[no].s_prev >= 0 ) { bords[bords[no].s_prev].s_next = no; } else { s_first = no; } bords[no].s_next = c; bords[c].s_prev = no; } } else { int c = guess; int stTst = CmpBord(bords[c], bords[no]); if ( stTst == 0 ) { bords[no].s_prev = bords[c].s_prev; if ( bords[no].s_prev >= 0 ) { bords[bords[no].s_prev].s_next = no; } else { s_first = no; } bords[no].s_next = c; bords[c].s_prev = no; } else if ( stTst > 0 ) { while ( c >= 0 && c < int(bords.size()) && CmpBord(bords[c], bords[no]) > 0 ) { c = bords[c].s_prev; } if ( c < 0 || c >= int(bords.size()) ) { bords[no].s_next = s_first; bords[s_first].s_prev =no; // s_first != -1 s_first = no; } else { bords[no].s_next = bords[c].s_next; if ( bords[no].s_next >= 0 ) { bords[bords[no].s_next].s_prev = no; } else { s_last = no; } bords[no].s_prev = c; bords[c].s_next = no; } } else { while ( c >= 0 && c < int(bords.size()) && CmpBord(bords[c],bords[no]) < 0 ) { c = bords[c].s_next; } if ( c < 0 || c >= int(bords.size()) ) { bords[no].s_prev = s_last; bords[s_last].s_next = no; s_last = no; } else { bords[no].s_prev = bords[c].s_prev; if ( bords[no].s_prev >= 0 ) { bords[bords[no].s_prev].s_next = no; } else { s_first = no; } bords[no].s_next = c; bords[c].s_prev = no; } } } } /** * Computes the sum of the coverages of the runs currently being scanned, * of which there are "pending". */ float FloatLigne::RemainingValAt(float at, int pending) { float sum = 0; /* int no=firstAc; while ( no >= 0 && no < bords.size() ) { int nn=bords[no].other; sum+=bords[nn].val+(at-bords[nn].pos)*bords[nn].pente; // sum+=((at-bords[nn].pos)*bords[no].val+(bords[no].pos-at)*bords[nn].val)/(bords[no].pos-bords[nn].pos); // sum+=ValAt(at,bords[nn].pos,bords[no].pos,bords[nn].val,bords[no].val); no=bords[no].next; }*/ // for each portion being scanned, compute coverage at position "at" and sum. // we could simply compute the sum of portion coverages as a "f(x)=ux+y" and evaluate it at "x=at", // but there are numerical problems with this approach, and it produces ugly lines of incorrectly // computed alpha values, so i reverted to this "safe but slow" version for (int i=0; i < pending; i++) { int const nn = bords[i].pend_ind; sum += bords[nn].val + (at - bords[nn].pos) * bords[nn].pente; } return sum; } /** * Extract a set of non-overlapping runs from the boundaries. * * We scan the boundaries left to right, maintaining a set of coverage * portions currently being scanned. For each such portion, the function * will add the index of its first boundary in an array; but instead of * allocating another array, it uses a field in float_ligne_bord: pend_ind. * The outcome is that an array of float_ligne_run is produced. */ void FloatLigne::Flatten() { if ( int(bords.size()) <= 1 ) { Reset(); return; } runs.clear(); // qsort(bords,bords.size(),sizeof(float_ligne_bord),FloatLigne::CmpBord); // SortBords(0,bords.size()-1); float totPente = 0; float totStart = 0; float totX = bords[0].pos; bool startExists = false; float lastStart = 0; float lastVal = 0; int pending = 0; // for (int i=0;i=0 && i < int(bords.size()) ;) { float cur = bords[i].pos; // position of the current boundary (there may be several boundaries at this position) float leftV = 0; // deltas in coverage value at this position float rightV = 0; float leftP = 0; // deltas in coverage increase per unit length at this position float rightP = 0; // more precisely, leftV is the sum of decreases of coverage value, // while rightV is the sum of increases, so that leftV+rightV is the delta. // idem for leftP and rightP // start by scanning all boundaries that end a portion at this position while ( i >= 0 && i < int(bords.size()) && bords[i].pos == cur && bords[i].start == false ) { leftV += bords[i].val; leftP += bords[i].pente; #ifndef faster_flatten // we need to remove the boundary that started this coverage portion for the pending list if ( bords[i].other >= 0 && bords[i].other < int(bords.size()) ) { // so we use the pend_inv "array" int const k = bords[bords[i].other].pend_inv; if ( k >= 0 && k < pending ) { // and update the pend_ind array and its inverse pend_inv bords[k].pend_ind = bords[pending - 1].pend_ind; bords[bords[k].pend_ind].pend_inv = k; } } #endif // one less portion pending pending--; // and we move to the next boundary in the doubly linked list i=bords[i].s_next; //i++; } // then scan all boundaries that start a portion at this position while ( i >= 0 && i < int(bords.size()) && bords[i].pos == cur && bords[i].start ) { rightV += bords[i].val; rightP += bords[i].pente; #ifndef faster_flatten bords[pending].pend_ind=i; bords[i].pend_inv=pending; #endif pending++; i = bords[i].s_next; //i++; } // coverage value at end of the run will be "start coverage"+"delta per unit length"*"length" totStart = totStart + totPente * (cur - totX); if ( startExists ) { // add that run AddRun(lastStart, cur, lastVal, totStart, totPente); } // update "delta coverage per unit length" totPente += rightP - leftP; // not really needed here totStart += rightV - leftV; // update position totX = cur; if ( pending > 0 ) { startExists = true; #ifndef faster_flatten // to avoid accumulation of numerical errors, we compute an accurate coverage for this position "cur" totStart = RemainingValAt(cur, pending); #endif lastVal = totStart; lastStart = cur; } else { startExists = false; totStart = 0; totPente = 0; } } } /// Debug dump of the instance. void FloatLigne::Affiche() { printf("%lu : \n", (long unsigned int) bords.size()); for (auto & bord : bords) { printf("(%f %f %f %i) ",bord.pos,bord.val,bord.pente,(bord.start?1:0)); // localization ok } printf("\n"); printf("%lu : \n", (long unsigned int) runs.size()); for (auto & run : runs) { printf("(%f %f -> %f %f / %f)", run.st, run.vst, run.en, run.ven, run.pente); // localization ok } printf("\n"); } int FloatLigne::AddRun(float st, float en, float vst, float ven) { return AddRun(st, en, vst, ven, (ven - vst) / (en - st)); } int FloatLigne::AddRun(float st, float en, float vst, float ven, float pente) { if ( st >= en ) { return -1; } int const n = runs.size(); float_ligne_run r; r.st = st; r.en = en; r.vst = vst; r.ven = ven; r.pente = pente; runs.push_back(r); return n; } void FloatLigne::Copy(FloatLigne *a) { if ( a->runs.empty() ) { Reset(); return; } bords.clear(); runs = a->runs; } void FloatLigne::Copy(IntLigne *a) { if ( a->nbRun ) { Reset(); return; } bords.clear(); runs.resize(a->nbRun); for (int i = 0; i < int(runs.size()); i++) { runs[i].st = a->runs[i].st; runs[i].en = a->runs[i].en; runs[i].vst = a->runs[i].vst; runs[i].ven = a->runs[i].ven; } } /// Cuts the parts having less than tresh coverage. void FloatLigne::Min(FloatLigne *a, float tresh, bool addIt) { Reset(); if ( a->runs.empty() ) { return; } bool startExists = false; float lastStart=0; float lastEnd = 0; for (auto runA : a->runs) { if ( runA.vst <= tresh ) { if ( runA.ven <= tresh ) { if ( startExists ) { if ( lastEnd >= runA.st - 0.00001 ) { lastEnd = runA.en; } else { if ( addIt ) { AddRun(lastStart, lastEnd, tresh, tresh); } lastStart = runA.st; lastEnd = runA.en; } } else { lastStart = runA.st; lastEnd = runA.en; } startExists = true; } else { float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven); if ( startExists ) { if ( lastEnd >= runA.st - 0.00001 ) { if ( addIt ) { AddRun(lastStart, cutPos, tresh, tresh); } AddRun(cutPos,runA.en, tresh, runA.ven); } else { if ( addIt ) { AddRun(lastStart, lastEnd, tresh, tresh); } if ( addIt ) { AddRun(runA.st, cutPos, tresh, tresh); } AddRun(cutPos, runA.en, tresh, runA.ven); } } else { if ( addIt ) { AddRun(runA.st, cutPos, tresh, tresh); } AddRun(cutPos, runA.en, tresh, runA.ven); } startExists = false; } } else { if ( runA.ven <= tresh ) { float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh - runA.vst)) / (runA.ven - runA.vst); if ( startExists ) { if ( addIt ) { AddRun(lastStart, lastEnd, tresh, tresh); } } AddRun(runA.st, cutPos, runA.vst, tresh); startExists = true; lastStart = cutPos; lastEnd = runA.en; } else { if ( startExists ) { if ( addIt ) { AddRun(lastStart, lastEnd, tresh, tresh); } } startExists = false; AddRun(runA.st, runA.en, runA.vst, runA.ven); } } } if ( startExists ) { if ( addIt ) { AddRun(lastStart, lastEnd, tresh, tresh); } } } /** * Cuts the coverage a in 2 parts. * * over will receive the parts where coverage > tresh, while the present * FloatLigne will receive the parts where coverage <= tresh. */ void FloatLigne::Split(FloatLigne *a, float tresh, FloatLigne *over) { Reset(); if ( a->runs.empty() ) { return; } for (auto runA : a->runs) { if ( runA.vst >= tresh ) { if ( runA.ven >= tresh ) { if ( over ) { over->AddRun(runA.st, runA.en, runA.vst, runA.ven); } } else { float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven); if ( over ) { over->AddRun(runA.st, cutPos, runA.vst, tresh); } AddRun(cutPos, runA.en, tresh, runA.ven); } } else { if ( runA.ven >= tresh ) { float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh-runA.vst)) / (runA.ven - runA.vst); AddRun(runA.st, cutPos, runA.vst, tresh); if ( over ) { over->AddRun(cutPos, runA.en, tresh, runA.ven); } } else { AddRun(runA.st, runA.en, runA.vst, runA.ven); } } } } /** * Clips the coverage runs to tresh. * * If addIt is false, it only leaves the parts that are not entirely under * tresh. If addIt is true, it's the coverage clamped to tresh. */ void FloatLigne::Max(FloatLigne *a, float tresh, bool addIt) { Reset(); if ( a->runs.empty() <= 0 ) { return; } bool startExists = false; float lastStart = 0; float lastEnd = 0; for (auto runA : a->runs) { if ( runA.vst >= tresh ) { if ( runA.ven >= tresh ) { if ( startExists ) { if ( lastEnd >= runA.st-0.00001 ) { lastEnd = runA.en; } else { if ( addIt ) { AddRun(lastStart,lastEnd,tresh,tresh); } lastStart = runA.st; lastEnd = runA.en; } } else { lastStart = runA.st; lastEnd = runA.en; } startExists = true; } else { float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven); if ( startExists ) { if ( lastEnd >= runA.st-0.00001 ) { if ( addIt ) { AddRun(lastStart, cutPos, tresh, tresh); } AddRun(cutPos, runA.en, tresh, runA.ven); } else { if ( addIt ) { AddRun(lastStart, lastEnd, tresh, tresh); } if ( addIt ) { AddRun(runA.st, cutPos, tresh, tresh); } AddRun(cutPos, runA.en, tresh, runA.ven); } } else { if ( addIt ) { AddRun(runA.st, cutPos, tresh, tresh); } AddRun(cutPos, runA.en, tresh, runA.ven); } startExists = false; } } else { if ( runA.ven >= tresh ) { float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh - runA.vst)) / (runA.ven - runA.vst); if ( startExists ) { if ( addIt ) { AddRun(lastStart,lastEnd,tresh,tresh); } } AddRun(runA.st, cutPos, runA.vst, tresh); startExists = true; lastStart = cutPos; lastEnd = runA.en; } else { if ( startExists ) { if ( addIt ) { AddRun(lastStart,lastEnd,tresh,tresh); } } startExists = false; AddRun(runA.st, runA.en, runA.vst, runA.ven); } } } if ( startExists ) { if ( addIt ) { AddRun(lastStart, lastEnd, tresh, tresh); } } } /// Extract the parts where coverage > tresh. void FloatLigne::Over(FloatLigne *a, float tresh) { Reset(); if ( a->runs.empty() ) { return; } bool startExists = false; float lastStart = 0; float lastEnd = 0; for (auto runA : a->runs) { if ( runA.vst >= tresh ) { if ( runA.ven >= tresh ) { if ( startExists ) { if ( lastEnd >= runA.st - 0.00001 ) { lastEnd = runA.en; } else { AddRun(lastStart, lastEnd, tresh, tresh); lastStart = runA.st; lastEnd = runA.en; } } else { lastStart = runA.st; lastEnd = runA.en; } startExists = true; } else { float cutPos = (runA.st * (tresh - runA.ven) + runA.en * (runA.vst - tresh)) / (runA.vst - runA.ven); if ( startExists ) { if ( lastEnd >= runA.st - 0.00001 ) { AddRun(lastStart, cutPos, tresh, tresh); } else { AddRun(lastStart, lastEnd, tresh, tresh); AddRun(runA.st, cutPos, tresh, tresh); } } else { AddRun(runA.st, cutPos, tresh, tresh); } startExists = false; } } else { if ( runA.ven >= tresh ) { float cutPos = (runA.st * (runA.ven - tresh) + runA.en * (tresh - runA.vst)) / (runA.ven - runA.vst); if ( startExists ) { AddRun(lastStart, lastEnd, tresh, tresh); } startExists = true; lastStart = cutPos; lastEnd = runA.en; } else { if ( startExists ) { AddRun(lastStart, lastEnd, tresh, tresh); } startExists = false; } } } if ( startExists ) { AddRun(lastStart, lastEnd, tresh, tresh); } } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :