source: GTP/trunk/Lib/Vis/Preprocessing/src/Pvs.h @ 1786

Revision 1786, 16.4 KB checked in by mattausch, 18 years ago (diff)
RevLine 
[177]1#ifndef __PVS_H
2#define __PVS_H
3
4#include <map>
[469]5#include <vector>
[1189]6#include "common.h"
[177]7
[860]8namespace GtpVisibilityPreprocessor {
9
[177]10class KdNode;
[240]11class BspNode;
[191]12class Ray;
[308]13class Intersectable;
[1077]14class ViewCell;
[177]15
[1077]16
[1740]17/** Information stored with a PVS entry. Consists of the number
18        the object was seen from the view cell.
19*/
20template<typename T, typename S>
21class PvsEntry
[1189]22{
[1740]23public:
24
[1741]25        PvsEntry() {}
26
[1740]27        PvsEntry(T sample, const S &data): mObject(sample), mData(data) {}
28
29        T mObject;
30        S mData;
31
32        template<typename T, typename S>
33        friend int operator< (const PvsEntry<T, S> &a, const PvsEntry<T, S> &b);
34};
35
36
37template<typename T, typename S>
38int operator< (const PvsEntry<T, S> &a, const PvsEntry<T, S> &b)
39{
40        return a.mObject < b.mObject;
41}
42
43
44template<typename T, typename S>
45struct LtSample
46{
47    bool operator()(const PvsEntry<T, S> &a, const PvsEntry<T, S> &b) const
[310]48    {
[1740]49                return a.mObject < b.mObject;
[310]50        }
51};
52
[1740]53
[469]54/** Information stored with a PVS entry. Consists of the number
55        the object was seen from the view cell.
56*/
[1189]57class PvsData {
58public:
[1706]59        PvsData() {}
60        PvsData(const float sumPdf):
[1189]61        mSumPdf(sumPdf) {}
[1740]62       
[1706]63        // $$JB in order to return meaningfull values
64        // it assumes that the sum pdf has been normalized somehow!!!
[1740]65        inline float GetVisibility()
[1706]66        {
67                return mSumPdf;
68        }
[1740]69
70        /// sum of probability density of visible sample rays
71        float mSumPdf;
[1189]72};
73
74
75class MailablePvsData
76{
[1740]77public:
78        // sum of probability density of visible sample rays
79        float mSumPdf;
80        int mCounter;
81
82        MailablePvsData() {}
83        MailablePvsData(const float sumPdf):
84        mSumPdf(sumPdf) {}
85
86        // $$JB in order to return meaningfull values
87        // it assumes that the sum pdf has been normalized somehow!!!
88        float GetVisibility()
89        {
90                return mSumPdf;
91        }
92
[1706]93        ////////////////////////////
94        //  Mailing stuff
[1184]95
[1706]96        // last mail id -> warning not thread safe!
97        // both mailId and mailbox should be unique for each thread!!!
98        static int sMailId;
99        static int sReservedMailboxes;
100
[1184]101        static void NewMail(const int reserve = 1) {
102                sMailId += sReservedMailboxes;
103                sReservedMailboxes = reserve;
104        }
[1706]105
[1184]106        void Mail() { mMailbox = sMailId; }
107        bool Mailed() const { return mMailbox == sMailId; }
108
109        void Mail(const int mailbox) { mMailbox = sMailId + mailbox; }
110        bool Mailed(const int mailbox) const { return mMailbox == sMailId + mailbox; }
111
112        int IncMail() { return ++ mMailbox - sMailId; }
[1740]113       
[1706]114        //////////////////////////////////////////
[1184]115
[1740]116protected:
[1184]117
[1740]118        int mMailbox;
[556]119
[310]120};
[1740]121
122
[1742]123template<typename T, typename S>
124class PvsIterator
125{
126public:
127PvsIterator<T, S>(){}
128        PvsIterator<T, S>(const typename vector<PvsEntry<T, S> >::const_iterator &itCurrent,
129                                          const typename vector<PvsEntry<T, S> >::const_iterator &itEnd):
130        mItCurrent(itCurrent), mItEnd(itEnd)
131        {
132        }
133
134        bool HasMoreEntries() const
135        {
136                return (mItCurrent != mItEnd);
137        }
138
[1744]139        const PvsEntry<T, S> &Next()
[1742]140        {
141                return *(mItCurrent ++);
142        }
143       
144private:
145        typename vector<PvsEntry<T, S> >::const_iterator mItCurrent;
146        typename vector<PvsEntry<T, S> >::const_iterator mItEnd;
[1740]147};
148
149
[469]150/** Template class representing the Potentially Visible Set (PVS)
151        mainly from a view cell, but also e.g., from objects.
152*/
[1189]153template<typename T, typename S>
[311]154class Pvs
[310]155{
[1740]156        template<typename T, typename S>
157        friend class PvsIterator;
158
[310]159public:
[1738]160
[1757]161        Pvs(): mSamples(0), mEntries(), mLastSorted(0) {}
[1738]162
[1742]163        /** creates pvs and initializes it with the given entries.
164                Assumes that entries are sorted-
165        */
[1740]166        Pvs(const vector<PvsEntry<T, S> > &samples);
[1742]167        virtual ~Pvs() {};
[492]168
[1706]169        /** Compresses PVS lossless or lossy.
170        */
171        int Compress() {return 0;}
172        int GetSize() const {return (int)mEntries.size();}
173        bool Empty() const {return mEntries.empty();}
[469]174
[1740]175        /** Normalize the visibility of entries in order to get
176                comparable results.
[1706]177        */
178        void NormalizeMaximum();
[1184]179
[1706]180        /** Merges pvs of a into this pvs.
[1740]181                Warning: very slow!
[1706]182        */
[1740]183        void MergeInPlace(const Pvs<T, S> &a);
[752]184
[1706]185        /** Difference of pvs to pvs b.
186                @returns number of different entries.
187        */
188        int Diff(const Pvs<T, S> &b);
[469]189
[1706]190        /** Finds sample in PVS.
[1740]191                @returns iterator on the sample.
[1706]192        */
[1740]193        typename vector<PvsEntry<T, S> >::iterator Find(T sample);
[469]194
[1706]195        bool GetSampleContribution(T sample, const float pdf, float &contribution);
[485]196
[1706]197        /** Adds sample to PVS.
198                @contribution contribution of sample (0 or 1)
199                @returns true if sample was not already in PVS.
200        */
201        bool AddSample(T sample, const float pdf, float &contribution);
[677]202
[1706]203        /** Adds sample to PVS.
204                @returns contribution of sample (0 or 1)
205        */
206        float AddSample(T sample, const float pdf);
[1757]207
208  /** Adds sample to PVS without checking for presence of the sample
209          pvs remians unsorted!
210  */
211  void AddSampleDirty(T sample, const float pdf);
212
213  /** Sort pvs entries - this should always be called after a
214          sequence of AddSampleDirty calls */
215  void Sort();
216 
[1742]217        /** Adds sample to PVS. Assumes that the pvs is sorted
[1740]218                @returns contribution of sample (0 or 1)
219        */
220        float AddSamples(const vector<PvsEntry<T, S> > &samples);
[752]221
[1706]222        /** Adds sample to PVS.
223                @returns PvsData
224        */
[1740]225        typename std::vector<PvsEntry<T, S> >::iterator AddSample2(T sample, const float pdf);
[1667]226
[1706]227        /** Subtracts one pvs from another one.
228                WARNING: could contains bugs
229                @returns new pvs size
230        */
231        int SubtractPvs(const Pvs<T, S> &pvs);
[1740]232
[1706]233        /** Returns PVS data, i.e., how often it was seen from the view cell,
234                and the object itsef.
235        */
236        void GetData(const int index, T &entry, S &data);
237
238        /** Collects the PVS entries and returns them in the vector.
239        */
240        void CollectEntries(std::vector<T> &entries);
241
242        /** Removes sample from PVS if reference count is zero.
243                @param visibleSamples number of references to be removed
244        */
245        bool RemoveSample(T sample, const float pdf);
246
247        /** Compute continuous PVS difference
248        */
[1740]249        void ComputeContinuousPvsDifference(Pvs<T, S> &pvs,
250                                                                                float &pvsReduction,
251                                                                                float &pvsEnlargement);
[1706]252
253        /** Clears the pvs.
254        */
[1750]255        void Clear(const bool trim = true);
[1706]256
[1750]257        void Trim();
258
[1706]259        static int GetEntrySizeByte();
260        static float GetEntrySize();
261
[1740]262        /** Compute continuous PVS difference
263        */
264        float GetPvsHomogenity(Pvs<T, S> &pvs);
[1706]265
[1740]266        static void Merge(Pvs<T, S> &mergedPvs, const Pvs<T, S> &a, const Pvs<T, S> &b);
[1706]267
[1742]268        int GetSamples() const
269        {
270                return mSamples;
271        }
[1738]272
[1742]273        typename PvsIterator<T, S> GetIterator() const;
274
275protected:
276
[1738]277        /// vector of PVS entries
[1740]278        vector<PvsEntry<T, S> > mEntries;
[1736]279       
280        /// Number of samples used to create the PVS
281        int mSamples;
[1757]282 
283  /// Last sorted entry in the pvs (important for find and merge
284  int mLastSorted;
285 
[310]286};
287
[581]288
[1740]289template <typename T, typename S>
290Pvs<T, S>::Pvs(const vector<PvsEntry<T, S> > &samples)
291{
292        mEntries.reserve(samples.size());
293        mEntries = samples;
[1757]294        mLastSorted = 0;
[1740]295}
[677]296
[1757]297template <typename T, typename S>
298void Pvs<T, S>::Sort()
299{
300  std::vector<PvsEntry<T, S> >::iterator it = mEntries.begin();
301  it.inc(mLastSorted);
302  sort(it, mEntries.end());
303  mLastSorted = mEntries.size() - 1;
304}
[1740]305
[695]306/**
307   Compute continuous PVS difference of 'b' with respect to the base PVS (*this).
308   Provides separatelly PVS reduction from PVS enlargement.
309
310*/
[1189]311template <typename T, typename S>
[677]312void
[1189]313Pvs<T, S>::ComputeContinuousPvsDifference(Pvs<T, S> &b,
[1740]314                                                                                  float &pvsReduction,
315                                                                                  float &pvsEnlargement)
[677]316{
[705]317        pvsReduction = 0.0f;
318        pvsEnlargement = 0.0f;
[1740]319
[1738]320        // Uses sum of log differences, which corresponds to entropy
[1740]321        std::vector<PvsEntry<T, S> >::iterator it;
[1738]322
323        for (it = b.mEntries.begin(); it != b.mEntries.end(); ++ it)
[1189]324        {
[1740]325                float bSumPdf = (*it).mData.mSumPdf;
[1738]326                float aSumPdf = 0.0f;
[713]327
[1740]328                vector<PvsEntry<T, S> >::iterator oit = Find((*it).mObject);           
329
330                const bool entryFound = (it != mEntries.end()) && ((*it).mObject == (*oit).mObject);
331
332                if (entryFound)
[1738]333                {
[1740]334                        aSumPdf = (*it).mData.mSumPdf;
335
[1738]336                        // mark this entry as processed to avoid double counting
[1740]337                        (*it).mData.mSumPdf = -aSumPdf;
[1738]338                }
339
[713]340#if 0
[1740]341                const float diff = bSumPdf - aSumPdf;
[1738]342
343                if (diff > 0.0f) {
344                        pvsEnlargement += diff;
345                } else {
346                        pvsReduction += -diff;
347                }
[713]348#else
[1740]349                if (!entryFound)
[1738]350                        pvsEnlargement += 1.0f;
[713]351#endif
[1738]352        }
353
[1740]354        for (it = mEntries.begin(); it != mEntries.end(); ++ it)
355        {
356                float aSumPdf = (*it).mData.mSumPdf;
[1738]357                float bSumPdf = 0.0f;
358                if (aSumPdf < 0.0f) {
[1740]359               
[1738]360                        // this entry was already accounted for!
361                        // just revert it back
[1740]362                        (*it).mData.mSumPdf = -aSumPdf;
[1738]363                } else {
[1740]364                        vector<PvsEntry<T, S> >::iterator oit = b.Find((*it).mObject);
365
366                        const bool entryFound = (it != mEntries.end()) && ((*it).mObject == (*oit).mObject);
367                       
368                        if (entryFound) {
369                                bSumPdf = (*oit).mData.mSumPdf;
[1738]370                        }
[713]371#if 0
[1740]372                        const float diff = bSumPdf - aSumPdf;
[713]373
[1738]374                        if (diff > 0.0f) {
375                                pvsEnlargement += diff;
376                        } else {
377                                pvsReduction += -diff;
378                        }
379
[713]380#else
[1740]381                        if (!entryFound)
[1738]382                                pvsReduction += 1.0f;
[713]383#endif
[1738]384                }
[695]385        }
[677]386}
387
[1738]388
[1189]389template <typename T, typename S>
390int Pvs<T, S>::Diff(const Pvs<T, S> &b)
[362]391{
392        int dif = 0;
393
[1740]394        std::vector<PvsEntry<T, S> >::const_iterator it;
[362]395
396        for (it = b.mEntries.begin(); it != b.mEntries.end(); ++ it)
397        {
[1740]398                std::vector<PvsEntry<T, S> >::const_iterator bit = Find((*it).first);           
399                if (bit == mEntries.end()) ++ dif;
[362]400        }
401
402        return dif;
403}
404
[1740]405
406template <typename T, typename S>
407void Pvs<T, S>::MergeInPlace(const Pvs<T, S> &a)
[341]408{
[1786]409        // early exit
410        if (a.Empty())
411        {
412                return;
413        }
414        else if (Empty())
415        {
416                mEntries.reserve(a.GetSize());
417                mEntries = a.mEntries;
418                mSamples = a.mSamples;
419                return;
420        }
421
[1740]422        ObjectPvs interPvs;
423       
424        Merge(interPvs, *this, a);
425       
426        mEntries.reserve(interPvs.GetSize());
427        mEntries = interPvs.mEntries;
[1751]428        mSamples = interPvs.mSamples;
[1740]429}
430
431
432template <typename T, typename S>
433void Pvs<T, S>::Merge(Pvs<T, S> &mergedPvs, const Pvs<T, S> &a, const Pvs<T, S> &b)
434{
435        std::vector<PvsEntry<T, S> >::const_iterator ait = a.mEntries.begin(), ait_end = a.mEntries.end();
436        std::vector<PvsEntry<T, S> >::const_iterator bit = b.mEntries.begin(), bit_end = b.mEntries.end();
[1741]437       
438        for (; (ait != ait_end); ++ ait)
439        {
440                Intersectable *aObj = (*ait).mObject;
441                Intersectable *bObj = NULL;
442                PvsEntry<T, S> aEntry = (*ait);
[1740]443
[1741]444                for (; (bit != bit_end) && ((*bit).mObject <= (*ait).mObject); ++ bit)
445                {
446                        bObj = (*bit).mObject;
447
448                        // object found => add up probabilities
[1742]449                        if (bObj == aEntry.mObject)
[1741]450                        {
451                                PvsData newData(aEntry.mData.mSumPdf + (*bit).mData.mSumPdf);
452                                PvsEntry<T, S> entry(bObj, newData);
453                                mergedPvs.mEntries.push_back(entry);
454                        }
455                        else
456                        {
457                                mergedPvs.mEntries.push_back(*bit);
458                        }
459                }
460
461                // only push back if objects different
462                // (equal case is handled by second loop)
463                if (aObj != bObj)
464                {
465                        mergedPvs.mEntries.push_back(*ait);
466                }
467        }
468
469        // add the rest
470        for (; (bit != bit_end); ++ bit)
[1740]471        {
[1741]472                mergedPvs.mEntries.push_back(*bit);
473        }
[1750]474        mergedPvs.mSamples = a.mSamples + b.mSamples;
[1740]475}
476
477
[1750]478template <typename T, typename S> void Pvs<T, S>::Clear(const bool trim = true)
[752]479{
480        mEntries.clear();
[1750]481        mSamples = 0;
[1786]482
483        if (trim)
484        {
485                vector<PvsEntry<T,S> >().swap(mEntries);
486        }
[752]487}
488
489
[1750]490template <typename T, typename S> void Pvs<T, S>::Trim()
491{
[1786]492        vector<PvsEntry<T,S> >(mEntries).swap(mEntries);
[1750]493}
494
495
[1189]496template <typename T, typename S>
[1740]497typename std::vector<PvsEntry<T, S> >::iterator Pvs<T, S>::Find(T sample)
[310]498{
[1740]499        PvsEntry<T, S> dummy(sample, PvsData());
500        vector<PvsEntry<T, S> >::iterator it = lower_bound(mEntries.begin(), mEntries.end(), dummy);
501                               
502        return it;
[310]503}
504
[1740]505
[1189]506template <typename T, typename S>
[1740]507void Pvs<T, S>::GetData(const int index, T &entry, S &data)
[310]508{
[1740]509        std::vector<PvsEntry<T, S> >::iterator i = mEntries.begin();
[1738]510        for (int k = 0; k != index && i != mEntries.end(); ++ i, ++ k);
[310]511
[1738]512        entry = (*i).first;
513        data = (*i).second;
[310]514}
515
[1738]516
[1189]517template <typename T, typename S>
[1740]518float Pvs<T, S>::AddSample(T sample, const float pdf)
[177]519{
[1738]520        ++ mSamples;
[1740]521        std::vector<PvsEntry<T, S> >::iterator it = Find(sample);
[1738]522
[1740]523        if ((it != mEntries.end()) && ((*it).mObject == sample))
[1189]524        {
[1740]525                S &data = (*it).mData;
526                data.mSumPdf += pdf;
527                return data.mSumPdf;
[1189]528        }
[1738]529        else
[1189]530        {
[1740]531                PvsEntry<T, S> entry(sample, pdf);
532                mEntries.insert(it, entry);
[1738]533                return pdf;
[1189]534        }
[466]535}
[177]536
[1189]537
538template <typename T, typename S>
[1757]539void Pvs<T, S>::AddSampleDirty(T sample, const float pdf)
540{
541  ++ mSamples;
542  mEntries.push_back(PvsEntry<T, S>(sample, pdf));
543}
544                                         
545
546template <typename T, typename S>
[1740]547typename vector< PvsEntry<T, S> >::iterator Pvs<T, S>::AddSample2(T sample, const float pdf)
[1184]548{
[1740]549        ++ mSamples;
550        std::vector<PvsEntry<T, S> >::iterator it = Find(sample);
551
[1742]552        if ((it != mEntries.end()) && ((*it).mObject == sample))
[1189]553        {
[1740]554                S &data = (*it).second;
555                data->mSumPdf += pdf;
[1189]556        }
[1740]557        else
[1189]558        {
[1740]559                PvsEntry<T, S> entry(sample, pdf);
560                mEntries.insert(it, entry);
[1189]561        }
[1740]562
563        return it;
[1184]564}
565
[1740]566
[1189]567template <typename T, typename S>
568bool Pvs<T, S>::AddSample(T sample,
[1740]569                                                  const float pdf,
570                                                  float &contribution)
[466]571{
[1738]572        ++ mSamples;
[1740]573
574        std::vector<PvsEntry<T, S> >::iterator it = Find(sample);
575
576        if ((it != mEntries.end()) && ((*it).mObject == sample))
[1738]577        {
[1740]578                S &data = (*it).mData;
579
580                data.mSumPdf += pdf;
581                contribution = pdf / data.mSumPdf;
582
[1738]583                return false;
584        }
[1740]585        else
586        {
587                PvsEntry<T, S> entry(sample, pdf);
588
589                mEntries.insert(it, entry);
[1738]590                contribution = 1.0f;
[1740]591
[1738]592                return true;
593        }
[311]594}
[308]595
[492]596
[1189]597template <typename T, typename S>
[1740]598bool Pvs<T, S>::GetSampleContribution(T sample,
599                                                                          const float pdf,
600                                                                          float &contribution)
[485]601{
[1740]602        std::vector<PvsEntry<T, S> >::iterator it = Find(sample);
[1189]603
[1740]604        if (it != mEntries.end() && ((*it).mObject == sample)) 
605        {
606                S &data = (*it).mData;
607                contribution = pdf / (data.mSumPdf + pdf);
608                return false;
609        }
610        else
611        {
612                contribution = 1.0f;
613                return true;
614        }
[485]615}
616
[1740]617
[1189]618template <typename T, typename S>
[1740]619bool Pvs<T, S>::RemoveSample(T sample, const float pdf)
[485]620{
[1740]621        -- mSamples;
[1737]622
[1740]623        std::vector<PvsEntry<T, S> >::iterator it = Find(sample);
[1737]624
[1740]625        if (it == mEntries.end())
626                return false;
627
628        S &data = (*it).mData;
629
630        data.mSumPdf -= pdf;
631
632        if (data.mSumPdf <= 0.0f)
633        {
634                mEntries.erase(it);
635        }
636
637        return true;
[485]638}
[1740]639
640
[1189]641template <typename T, typename S>
642int Pvs<T, S>::SubtractPvs(const Pvs<T, S> &pvs)
[485]643{
[1738]644        const int samples = mSamples - pvs.mSamples;
[1740]645
646        std::vector<PvsEntry<T, S> >::
[1738]647                const_iterator it, it_end = pvs.mEntries.end();
[1737]648
[1738]649        // output PVS of view cell
650        for (it = pvs.mEntries.begin(); it != it_end; ++ it)
[1740]651                RemoveSample((*it).mObject, (*it).mData.mSumPdf);
[1738]652
653        mSamples = samples;
[1740]654
[1738]655        return GetSize();
[485]656}
657
[1740]658
[1189]659template <typename T, typename S>
660void Pvs<T, S>::CollectEntries(std::vector<T> &entries)
[469]661{
[1740]662        std::vector<PvsEntry<T, S> >::
[485]663                const_iterator it, it_end = mEntries.end();
[469]664
665        // output PVS of view cell
666        for (it = mEntries.begin(); it != it_end; ++ it)
667                entries.push_back((*it)->first);
668}
669
[1740]670
[1189]671template <typename T, typename S>
672void Pvs<T, S>::NormalizeMaximum()
[556]673{
[1740]674        std::vector<PvsEntry<T, S> >::
675                const_iterator it, it_end = mEntries.end();
[556]676
[1740]677        float maxPdfSum = -1.0f;
[556]678
[1740]679        // output PVS of view cell
680        for (it = mEntries.begin(); it != it_end; ++ it) {
681                float sum = (*it)->second.sumPdf;
682                if (sum > maxSum)
683                        maxSum = sum;
684        }
[556]685
[1740]686        maxSum = 1.0f / maxSum;
[556]687
[1740]688        for (it = mEntries.begin(); it != it_end; ++ it) {
689                (*it)->second.sumPdf *= maxSum;
690        }
691
[556]692}
693
694
[1667]695template <typename T, typename S>
696float Pvs<T, S>::GetEntrySize()
697{
[1673]698        return (float)(sizeof(T) + sizeof(S)) / float(1024 * 1024);
[1667]699}
700
701
702template <typename T, typename S>
703int Pvs<T, S>::GetEntrySizeByte()
704{
705        return sizeof(T) + sizeof(S);
706}
707
708
[1740]709template <typename T, typename S>
710float Pvs<T, S>::GetPvsHomogenity(Pvs<T, S> &pvs)
711{
712        float pvsReduction, pvsEnlargement;
713
714        ComputeContinuousPvsDifference(pvs,     pvsReduction, pvsEnlargement);
715
716        return pvsReduction + pvsEnlargement;
717}
718
719
[1742]720template <typename T, typename S>
721typename PvsIterator<T, S> Pvs<T, S>::GetIterator() const
722{
723        PvsIterator<T, S> pit(mEntries.begin(), mEntries.end());
724
725        return pit;
726}
727
728
[1667]729///////////////////////////////////////
730
[311]731/** Class instantiating the Pvs template for kd tree nodes.
732*/
[1189]733class KdPvs: public Pvs<KdNode *, PvsData>
[308]734{
[1141]735public:
[311]736        int Compress();
[308]737};
738
[1077]739
[1189]740class ObjectPvs: public Pvs<Intersectable *, PvsData>
[1141]741{
742public:
[1586]743        /** Counts object int the pvs. Different to method "GetSize", not
[1141]744                only the raw container size is returned,
745                but the individual contributions of the entries are summed up.
746        */
[1707]747        float EvalPvsCost() const;
[1141]748};
749
[1586]750
751////////////
[1077]752//-- typedefs
753
[1742]754typedef PvsEntry<Intersectable *, PvsData> ObjectPvsEntry;
755typedef std::vector<ObjectPvsEntry> ObjectPvsEntries;
[1189]756typedef Pvs<ViewCell *, MailablePvsData> ViewCellPvs;
[1742]757typedef PvsIterator<Intersectable *, PvsData> ObjectPvsIterator;
[860]758}
[469]759
[177]760#endif
761
Note: See TracBrowser for help on using the repository browser.