Coverage Report

Created: 2025-11-09 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/transformations/defmodel.hpp
Line
Count
Source
1
/******************************************************************************
2
 * Project:  PROJ
3
 * Purpose:  Functionality related to deformation model
4
 * Author:   Even Rouault, <even.rouault at spatialys.com>
5
 *
6
 ******************************************************************************
7
 * Copyright (c) 2020, Even Rouault, <even.rouault at spatialys.com>
8
 *
9
 * Permission is hereby granted, free of charge, to any person obtaining a
10
 * copy of this software and associated documentation files (the "Software"),
11
 * to deal in the Software without restriction, including without limitation
12
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
13
 * and/or sell copies of the Software, and to permit persons to whom the
14
 * Software is furnished to do so, subject to the following conditions:
15
 *
16
 * The above copyright notice and this permission notice shall be included
17
 * in all copies or substantial portions of the Software.
18
 *
19
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
20
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25
 * DEALINGS IN THE SOFTWARE.
26
 *****************************************************************************/
27
28
/** This file implements the gridded deformation model proposol of
29
 * https://docs.google.com/document/d/1wiyrAmzqh8MZlzHSp3wf594Ob_M1LeFtDA5swuzvLZY
30
 * It is written in a generic way, independent of the rest of PROJ
31
 * infrastructure.
32
 *
33
 * Verbose debugging info can be turned on by setting the DEBUG_DEFMODEL macro
34
 */
35
36
#ifndef DEFMODEL_HPP
37
#define DEFMODEL_HPP
38
39
#ifdef PROJ_COMPILATION
40
#include "proj/internal/include_nlohmann_json.hpp"
41
#else
42
#include "nlohmann/json.hpp"
43
#endif
44
45
#include <algorithm>
46
#include <cmath>
47
#include <exception>
48
#include <limits>
49
#include <memory>
50
#include <string>
51
#include <vector>
52
53
#ifndef DEFORMATON_MODEL_NAMESPACE
54
#define DEFORMATON_MODEL_NAMESPACE DeformationModel
55
#endif
56
57
#include "defmodel_exceptions.hpp"
58
59
namespace DEFORMATON_MODEL_NAMESPACE {
60
61
using json = nlohmann::json;
62
63
// ---------------------------------------------------------------------------
64
65
/** Spatial extent as a bounding box. */
66
class SpatialExtent {
67
  public:
68
    /** Parse the provided object as an extent.
69
     *
70
     * @throws ParsingException in case of error.
71
     */
72
    static SpatialExtent parse(const json &j);
73
74
0
    double minx() const { return mMinx; }
75
0
    double miny() const { return mMiny; }
76
0
    double maxx() const { return mMaxx; }
77
0
    double maxy() const { return mMaxy; }
78
79
0
    double minxNormalized(bool bIsGeographic) const {
80
0
        return bIsGeographic ? mMinxRad : mMinx;
81
0
    }
82
0
    double minyNormalized(bool bIsGeographic) const {
83
0
        return bIsGeographic ? mMinyRad : mMiny;
84
0
    }
85
0
    double maxxNormalized(bool bIsGeographic) const {
86
0
        return bIsGeographic ? mMaxxRad : mMaxx;
87
0
    }
88
0
    double maxyNormalized(bool bIsGeographic) const {
89
0
        return bIsGeographic ? mMaxyRad : mMaxy;
90
0
    }
91
92
  protected:
93
    friend class MasterFile;
94
    friend class Component;
95
3
    SpatialExtent() = default;
96
97
  private:
98
    double mMinx = std::numeric_limits<double>::quiet_NaN();
99
    double mMiny = std::numeric_limits<double>::quiet_NaN();
100
    double mMaxx = std::numeric_limits<double>::quiet_NaN();
101
    double mMaxy = std::numeric_limits<double>::quiet_NaN();
102
    double mMinxRad = std::numeric_limits<double>::quiet_NaN();
103
    double mMinyRad = std::numeric_limits<double>::quiet_NaN();
104
    double mMaxxRad = std::numeric_limits<double>::quiet_NaN();
105
    double mMaxyRad = std::numeric_limits<double>::quiet_NaN();
106
};
107
108
// ---------------------------------------------------------------------------
109
110
/** Epoch */
111
class Epoch {
112
  public:
113
    /** Constructor from a ISO 8601 date-time. May throw ParsingException */
114
    explicit Epoch(const std::string &dt = std::string());
115
116
    /** Return ISO 8601 date-time */
117
0
    const std::string &toString() const { return mDt; }
118
119
    /** Return decimal year */
120
    double toDecimalYear() const;
121
122
  private:
123
    std::string mDt{};
124
    double mDecimalYear = 0;
125
};
126
127
// ---------------------------------------------------------------------------
128
129
/** Component of a deformation model. */
130
class Component {
131
  public:
132
    /** Parse the provided object as a component.
133
     *
134
     * @throws ParsingException in case of error.
135
     */
136
    static Component parse(const json &j);
137
138
    /** Get a text description of the component. */
139
0
    const std::string &description() const { return mDescription; }
140
141
    /** Get the region within which the component is defined. Outside this
142
     * region the component evaluates to 0. */
143
0
    const SpatialExtent &extent() const { return mSpatialExtent; }
144
145
    /** Get the displacement parameters defined by the component, one of
146
     * "none", "horizontal", "vertical", and "3d".  The "none" option allows
147
     *  for a component which defines uncertainty with different grids to those
148
     *  defining displacement. */
149
0
    const std::string &displacementType() const { return mDisplacementType; }
150
151
    /** Get the uncertainty parameters defined by the component,
152
     * one of "none", "horizontal", "vertical", "3d". */
153
0
    const std::string &uncertaintyType() const { return mUncertaintyType; }
154
155
    /** Get the horizontal uncertainty to use if it is not defined explicitly
156
     * in the spatial model. */
157
0
    double horizontalUncertainty() const { return mHorizontalUncertainty; }
158
159
    /** Get the vertical uncertainty to use if it is not defined explicitly in
160
     * the spatial model. */
161
0
    double verticalUncertainty() const { return mVerticalUncertainty; }
162
163
    struct SpatialModel {
164
        /** Specifies the type of the spatial model data file.  Initially it
165
         * is proposed that only "GeoTIFF" is supported. */
166
        std::string type{};
167
168
        /** How values in model should be interpolated. This proposal will
169
         * support "bilinear" and "geocentric_bilinear".  */
170
        std::string interpolationMethod{};
171
172
        /** Specifies location of the spatial model GeoTIFF file relative to
173
         * the master JSON file. */
174
        std::string filename{};
175
176
        /** A hex encoded MD5 checksum of the grid file that can be used to
177
         * validate that it is the correct version of the file. */
178
        std::string md5Checksum{};
179
    };
180
181
    /** Get the spatial model. */
182
0
    const SpatialModel &spatialModel() const { return mSpatialModel; }
183
184
    /** Generic type for a type function */
185
    struct TimeFunction {
186
        std::string type{};
187
188
        virtual ~TimeFunction();
189
190
        virtual double evaluateAt(double dt) const = 0;
191
192
      protected:
193
0
        TimeFunction() = default;
194
    };
195
    struct ConstantTimeFunction : public TimeFunction {
196
197
        virtual double evaluateAt(double dt) const override;
198
    };
199
    struct VelocityTimeFunction : public TimeFunction {
200
        /** Date/time at which the velocity function is zero. */
201
        Epoch referenceEpoch{};
202
203
        virtual double evaluateAt(double dt) const override;
204
    };
205
206
    struct StepTimeFunction : public TimeFunction {
207
        /** Epoch at which the step function transitions from 0 to 1. */
208
        Epoch stepEpoch{};
209
210
        virtual double evaluateAt(double dt) const override;
211
    };
212
213
    struct ReverseStepTimeFunction : public TimeFunction {
214
        /** Epoch at which the reverse step function transitions from 1. to 0 */
215
        Epoch stepEpoch{};
216
217
        virtual double evaluateAt(double dt) const override;
218
    };
219
220
    struct PiecewiseTimeFunction : public TimeFunction {
221
        /** One of "zero", "constant", and "linear", defines the behavior of
222
         * the function before the first defined epoch */
223
        std::string beforeFirst{};
224
225
        /** One of "zero", "constant", and "linear", defines the behavior of
226
         * the function after the last defined epoch */
227
        std::string afterLast{};
228
229
        struct EpochScaleFactorTuple {
230
            /** Defines the date/time of the data point. */
231
            Epoch epoch{};
232
233
            /** Function value at the above epoch */
234
            double scaleFactor = std::numeric_limits<double>::quiet_NaN();
235
        };
236
237
        /** A sorted array data points each defined by two elements.
238
         * The array is sorted in order of increasing epoch.
239
         * Note: where the time function includes a step it is represented by
240
         * two consecutive data points with the same epoch.  The first defines
241
         * the scale factor that applies before the epoch and the second the
242
         * scale factor that applies after the epoch. */
243
        std::vector<EpochScaleFactorTuple> model{};
244
245
        virtual double evaluateAt(double dt) const override;
246
    };
247
248
    struct ExponentialTimeFunction : public TimeFunction {
249
        /** The date/time at which the exponential decay starts. */
250
        Epoch referenceEpoch{};
251
252
        /** The date/time at which the exponential decay ends. */
253
        Epoch endEpoch{};
254
255
        /** The relaxation constant in years. */
256
        double relaxationConstant = std::numeric_limits<double>::quiet_NaN();
257
258
        /** The scale factor that applies before the reference epoch. */
259
        double beforeScaleFactor = std::numeric_limits<double>::quiet_NaN();
260
261
        /** Initial scale factor. */
262
        double initialScaleFactor = std::numeric_limits<double>::quiet_NaN();
263
264
        /** The scale factor the exponential function approaches. */
265
        double finalScaleFactor = std::numeric_limits<double>::quiet_NaN();
266
267
        virtual double evaluateAt(double dt) const override;
268
    };
269
270
    /** Get the time function. */
271
0
    const TimeFunction *timeFunction() const { return mTimeFunction.get(); }
272
273
  private:
274
0
    Component() = default;
275
276
    std::string mDescription{};
277
    SpatialExtent mSpatialExtent{};
278
    std::string mDisplacementType{};
279
    std::string mUncertaintyType{};
280
    double mHorizontalUncertainty = std::numeric_limits<double>::quiet_NaN();
281
    double mVerticalUncertainty = std::numeric_limits<double>::quiet_NaN();
282
    SpatialModel mSpatialModel{};
283
    std::unique_ptr<TimeFunction> mTimeFunction{};
284
};
285
286
0
Component::TimeFunction::~TimeFunction() = default;
287
288
// ---------------------------------------------------------------------------
289
290
/** Master file of a deformation model. */
291
class MasterFile {
292
  public:
293
    /** Parse the provided serialized JSON content and return an object.
294
     *
295
     * @throws ParsingException in case of error.
296
     */
297
    static std::unique_ptr<MasterFile> parse(const std::string &text);
298
299
    /** Get file type. Should always be "deformation_model_master_file" */
300
0
    const std::string &fileType() const { return mFileType; }
301
302
    /** Get the version of the format. At time of writing, only "1.0" is known
303
     */
304
0
    const std::string &formatVersion() const { return mFormatVersion; }
305
306
    /** Get brief descriptive name of the deformation model. */
307
0
    const std::string &name() const { return mName; }
308
309
    /** Get a string identifying the version of the deformation model.
310
     * The format for specifying version is defined by the agency
311
     * responsible for the deformation model. */
312
0
    const std::string &version() const { return mVersion; }
313
314
    /** Get a string identifying the license of the file.
315
     * e.g "Create Commons Attribution 4.0 International" */
316
0
    const std::string &license() const { return mLicense; }
317
318
    /** Get a text description of the model. Intended to be longer than name()
319
     */
320
0
    const std::string &description() const { return mDescription; }
321
322
    /** Get a text description of the model. Intended to be longer than name()
323
     */
324
0
    const std::string &publicationDate() const { return mPublicationDate; }
325
326
    /** Basic information on the agency responsible for the model. */
327
    struct Authority {
328
        std::string name{};
329
        std::string url{};
330
        std::string address{};
331
        std::string email{};
332
    };
333
334
    /** Get basic information on the agency responsible for the model. */
335
0
    const Authority &authority() const { return mAuthority; }
336
337
    /** Hyperlink related to the model. */
338
    struct Link {
339
        /** URL holding the information */
340
        std::string href{};
341
342
        /** Relationship to the dataset. e.g. "about", "source", "license",
343
         * "metadata" */
344
        std::string rel{};
345
346
        /** Mime type */
347
        std::string type{};
348
349
        /** Description of the link */
350
        std::string title{};
351
    };
352
353
    /** Get links to related information. */
354
0
    const std::vector<Link> links() const { return mLinks; }
355
356
    /** Get a string identifying the source CRS. That is the coordinate
357
     * reference system to which the deformation model applies. Typically
358
     * "EPSG:XXXX" */
359
0
    const std::string &sourceCRS() const { return mSourceCRS; }
360
361
    /** Get a string identifying the target CRS. That is, for a time
362
     * dependent coordinate transformation, the coordinate reference
363
     * system resulting from applying the deformation.
364
     * Typically "EPSG:XXXX" */
365
0
    const std::string &targetCRS() const { return mTargetCRS; }
366
367
    /** Get a string identifying the definition CRS. That is, the
368
     * coordinate reference system used to define the component spatial
369
     * models. Typically "EPSG:XXXX" */
370
0
    const std::string &definitionCRS() const { return mDefinitionCRS; }
371
372
    /** Get the nominal reference epoch of the deformation model. Formatted
373
     * as a ISO-8601 date-time. This is not necessarily used to calculate
374
     * the deformation model - each component defines its own time function. */
375
0
    const std::string &referenceEpoch() const { return mReferenceEpoch; }
376
377
    /** Get the epoch at which the uncertainties of the deformation model
378
     * are calculated. Formatted as a ISO-8601 date-time. */
379
0
    const std::string &uncertaintyReferenceEpoch() const {
380
0
        return mUncertaintyReferenceEpoch;
381
0
    }
382
383
    /** Unit of horizontal offsets. Only "metre" and "degree" are supported. */
384
0
    const std::string &horizontalOffsetUnit() const {
385
0
        return mHorizontalOffsetUnit;
386
0
    }
387
388
    /** Unit of vertical offsets. Only "metre" is supported. */
389
0
    const std::string &verticalOffsetUnit() const {
390
0
        return mVerticalOffsetUnit;
391
0
    }
392
393
    /** Type of horizontal uncertainty. e.g "circular 95% confidence limit" */
394
0
    const std::string &horizontalUncertaintyType() const {
395
0
        return mHorizontalUncertaintyType;
396
0
    }
397
398
    /** Unit of horizontal uncertainty. Only "metre" is supported. */
399
0
    const std::string &horizontalUncertaintyUnit() const {
400
0
        return mHorizontalUncertaintyUnit;
401
0
    }
402
403
    /** Type of vertical uncertainty. e.g "circular 95% confidence limit" */
404
0
    const std::string &verticalUncertaintyType() const {
405
0
        return mVerticalUncertaintyType;
406
0
    }
407
408
    /** Unit of vertical uncertainty. Only "metre" is supported. */
409
0
    const std::string &verticalUncertaintyUnit() const {
410
0
        return mVerticalUncertaintyUnit;
411
0
    }
412
413
    /** Defines how the horizontal offsets are applied to geographic
414
     * coordinates. Only "addition" and "geocentric" are supported */
415
0
    const std::string &horizontalOffsetMethod() const {
416
0
        return mHorizontalOffsetMethod;
417
0
    }
418
419
    /** Get the region within which the deformation model is defined.
420
     * It cannot be calculated outside this region */
421
0
    const SpatialExtent &extent() const { return mSpatialExtent; }
422
423
    /** Defines the range of times for which the model is valid, specified
424
     * by a first and a last value. The deformation model is undefined for
425
     * dates outside this range. */
426
    struct TimeExtent {
427
        Epoch first{};
428
        Epoch last{};
429
    };
430
431
    /** Get the range of times for which the model is valid. */
432
0
    const TimeExtent &timeExtent() const { return mTimeExtent; }
433
434
    /** Get an array of the components comprising the deformation model. */
435
0
    const std::vector<Component> &components() const { return mComponents; }
436
437
  private:
438
3
    MasterFile() = default;
439
440
    std::string mFileType{};
441
    std::string mFormatVersion{};
442
    std::string mName{};
443
    std::string mVersion{};
444
    std::string mLicense{};
445
    std::string mDescription{};
446
    std::string mPublicationDate{};
447
    Authority mAuthority{};
448
    std::vector<Link> mLinks{};
449
    std::string mSourceCRS{};
450
    std::string mTargetCRS{};
451
    std::string mDefinitionCRS{};
452
    std::string mReferenceEpoch{};
453
    std::string mUncertaintyReferenceEpoch{};
454
    std::string mHorizontalOffsetUnit{};
455
    std::string mVerticalOffsetUnit{};
456
    std::string mHorizontalUncertaintyType{};
457
    std::string mHorizontalUncertaintyUnit{};
458
    std::string mVerticalUncertaintyType{};
459
    std::string mVerticalUncertaintyUnit{};
460
    std::string mHorizontalOffsetMethod{};
461
    SpatialExtent mSpatialExtent{};
462
    TimeExtent mTimeExtent{};
463
    std::vector<Component> mComponents{};
464
};
465
466
// ---------------------------------------------------------------------------
467
468
/** Prototype for a Grid used by GridSet. Intended to be implemented
469
 * by user code */
470
struct GridPrototype {
471
    double minx = 0;
472
    double miny = 0;
473
    double resx = 0;
474
    double resy = 0;
475
    int width = 0;
476
    int height = 0;
477
478
    // cppcheck-suppress functionStatic
479
    bool getLongLatOffset(int /*ix*/, int /*iy*/, double & /*longOffsetRadian*/,
480
0
                          double & /*latOffsetRadian*/) const {
481
0
        throw UnimplementedException("getLongLatOffset unimplemented");
482
0
    }
483
484
    // cppcheck-suppress functionStatic
485
0
    bool getZOffset(int /*ix*/, int /*iy*/, double & /*zOffset*/) const {
486
0
        throw UnimplementedException("getZOffset unimplemented");
487
0
    }
488
489
    // cppcheck-suppress functionStatic
490
    bool getEastingNorthingOffset(int /*ix*/, int /*iy*/,
491
                                  double & /*eastingOffset*/,
492
0
                                  double & /*northingOffset*/) const {
493
0
        throw UnimplementedException("getEastingNorthingOffset unimplemented");
494
0
    }
495
496
    // cppcheck-suppress functionStatic
497
    bool getLongLatZOffset(int /*ix*/, int /*iy*/,
498
                           double & /*longOffsetRadian*/,
499
                           double & /*latOffsetRadian*/,
500
0
                           double & /*zOffset*/) const {
501
0
        throw UnimplementedException("getLongLatZOffset unimplemented");
502
0
#if 0
503
0
        return getLongLatOffset(ix, iy, longOffsetRadian, latOffsetRadian) &&
504
0
               getZOffset(ix, iy, zOffset);
505
0
#endif
506
0
    }
507
508
    // cppcheck-suppress functionStatic
509
    bool getEastingNorthingZOffset(int /*ix*/, int /*iy*/,
510
                                   double & /*eastingOffset*/,
511
                                   double & /*northingOffset*/,
512
0
                                   double & /*zOffset*/) const {
513
0
        throw UnimplementedException("getEastingNorthingOffset unimplemented");
514
0
#if 0
515
0
        return getEastingNorthingOffset(ix, iy, eastingOffset,
516
0
                                        northingOffset) &&
517
0
               getZOffset(ix, iy, zOffset);
518
0
#endif
519
0
    }
520
521
#ifdef DEBUG_DEFMODEL
522
    std::string name() const {
523
        throw UnimplementedException("name() unimplemented");
524
    }
525
#endif
526
};
527
528
// ---------------------------------------------------------------------------
529
530
/** Prototype for a GridSet used by EvaluatorIface. Intended to be implemented
531
 * by user code */
532
template <class Grid = GridPrototype> struct GridSetPrototype {
533
    // The return pointer should remain "stable" over time for a given grid
534
    // of a GridSet.
535
    // cppcheck-suppress functionStatic
536
    const Grid *gridAt(double /*x */, double /* y */) {
537
        throw UnimplementedException("gridAt unimplemented");
538
    }
539
};
540
541
// ---------------------------------------------------------------------------
542
543
/** Prototype for a EvaluatorIface used by Evaluator. Intended to be implemented
544
 * by user code */
545
template <class Grid = GridPrototype, class GridSet = GridSetPrototype<>>
546
struct EvaluatorIfacePrototype {
547
548
    std::unique_ptr<GridSet> open(const std::string & /* filename*/) {
549
        throw UnimplementedException("open unimplemented");
550
    }
551
552
    // cppcheck-suppress functionStatic
553
    void geographicToGeocentric(double /* lam */, double /* phi */,
554
                                double /* height*/, double /* a */,
555
                                double /* b */, double /*es*/, double & /* X */,
556
                                double & /* Y */, double & /* Z */) {
557
        throw UnimplementedException("geographicToGeocentric unimplemented");
558
    }
559
560
    // cppcheck-suppress functionStatic
561
    void geocentricToGeographic(double /* X */, double /* Y */, double /* Z */,
562
                                double /* a */, double /* b */, double /*es*/,
563
                                double & /* lam */, double & /* phi */,
564
                                double & /* height*/) {
565
        throw UnimplementedException("geocentricToGeographic unimplemented");
566
    }
567
568
    // cppcheck-suppress functionStatic
569
    bool isGeographicCRS(const std::string & /* crsDef */) {
570
        throw UnimplementedException("isGeographicCRS unimplemented");
571
    }
572
573
#ifdef DEBUG_DEFMODEL
574
    void log(const std::string & /* msg */) {
575
        throw UnimplementedException("log unimplemented");
576
    }
577
#endif
578
};
579
580
// ---------------------------------------------------------------------------
581
582
/** Internal class to offer caching services over a Component */
583
template <class Grid, class GridSet> struct ComponentEx;
584
585
// ---------------------------------------------------------------------------
586
587
/** Class to evaluate the transformation of a coordinate */
588
template <class Grid = GridPrototype, class GridSet = GridSetPrototype<>,
589
          class EvaluatorIface = EvaluatorIfacePrototype<>>
590
class Evaluator {
591
  public:
592
    /** Constructor. May throw EvaluatorException */
593
    explicit Evaluator(std::unique_ptr<MasterFile> &&model,
594
                       EvaluatorIface &iface, double a, double b);
595
596
    /** Evaluate displacement of a position given by (x,y,z,t) and
597
     * return it in (x_out,y_out_,z_out).
598
     * For geographic CRS (only supported at that time), x must be a
599
     * longitude, and y a latitude.
600
     */
601
    bool forward(EvaluatorIface &iface, double x, double y, double z, double t,
602
0
                 double &x_out, double &y_out, double &z_out) {
603
0
        return forward(iface, x, y, z, t, false, x_out, y_out, z_out);
604
0
    }
605
606
    /** Apply inverse transformation. */
607
    bool inverse(EvaluatorIface &iface, double x, double y, double z, double t,
608
                 double &x_out, double &y_out, double &z_out);
609
610
    /** Clear grid cache */
611
    void clearGridCache();
612
613
    /** Return whether the definition CRS is a geographic CRS */
614
0
    bool isGeographicCRS() const { return mIsGeographicCRS; }
615
616
  private:
617
    std::unique_ptr<MasterFile> mModel;
618
    const double mA;
619
    const double mB;
620
    const double mEs;
621
    const bool mIsHorizontalUnitDegree; /* degree vs metre */
622
    const bool mIsAddition;             /* addition vs geocentric */
623
    const bool mIsGeographicCRS;
624
625
    bool forward(EvaluatorIface &iface, double x, double y, double z, double t,
626
                 bool forInverseComputation, double &x_out, double &y_out,
627
                 double &z_out);
628
629
    std::vector<std::unique_ptr<ComponentEx<Grid, GridSet>>> mComponents{};
630
};
631
632
// ---------------------------------------------------------------------------
633
634
} // namespace DEFORMATON_MODEL_NAMESPACE
635
636
// ---------------------------------------------------------------------------
637
638
#include "defmodel_impl.hpp"
639
640
#endif // DEFMODEL_HPP