GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
174 / 179
97.2%
Branches:
130 / 188
69.1%
Functions:
23 / 23
100.0%

scene/xform.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file xform.cpp
3 /// @brief Transform op composition, after UsdGeomXformable.
4 #include "ernie/scene/xform.h"
5
6 #include "ernie/scene/convert.h"
7 #include "ernie/scene/tokens.h"
8
9 #include <glm/gtc/matrix_transform.hpp>
10
11 #include <algorithm>
12 #include <array>
13 #include <cmath>
14 #include <stdexcept>
15 #include <string>
16 #include <vector>
17
18 namespace ernie {
19 namespace {
20
21 constexpr std::array<std::array<int, 3>, 6> kAxes{
22 {{0, 1, 2}, {0, 2, 1}, {1, 0, 2}, {1, 2, 0}, {2, 0, 1}, {2, 1, 0}}};
23 constexpr std::array<std::string_view, 6> kRotateOps{"xformOp:rotateXYZ", "xformOp:rotateXZY",
24 "xformOp:rotateYXZ", "xformOp:rotateYZX",
25 "xformOp:rotateZXY", "xformOp:rotateZYX"};
26
27 /// The axes an order turns about, first first.
28 11451 std::array<int, 3> TurnAxes(RotationOrder order) {
29 11451 return kAxes[static_cast<std::size_t>(order)];
30 }
31
32 /// A right-handed turn about one axis, built from its cosine and sine so the axis stays exact.
33 19962 glm::dmat3 AxisRotation(int axis, double degrees) {
34
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19962 times.
19962 const double c = std::cos(glm::radians(degrees));
35 19962 const double s = std::sin(glm::radians(degrees));
36 19962 const int a = (axis + 1) % 3;
37 19962 const int b = (axis + 2) % 3;
38 19962 glm::dmat3 r(1.0);
39
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19962 times.
19962 r[a][a] = c;
40 19962 r[a][b] = s;
41
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19962 times.
19962 r[b][a] = -s;
42 19962 r[b][b] = c;
43 19962 return r;
44 }
45
46 /// The op types, as `XformOpKind` orders them.
47 constexpr std::array<std::string_view, 13> kOpTypes{
48 "translate", "scale", "rotateX", "rotateY", "rotateZ", "rotateXYZ", "rotateXZY",
49 "rotateYXZ", "rotateYZX", "rotateZXY", "rotateZYX", "orient", "transform"};
50
51 /// One entry of `xformOpOrder`, its attribute viewing the order's text.
52 struct OpEntry {
53 std::string_view attribute;
54 XformOpKind kind = XformOpKind::Transform;
55 bool invert = false;
56 };
57
58 3814 OpEntry ParseOp(const Scene& scene, PrimId prim, std::string_view entry) {
59 3814 OpEntry op;
60
2/2
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 3807 times.
3814 if (entry.starts_with(tokens::kInvertPrefix)) {
61 7 op.invert = true;
62 7 entry.remove_prefix(tokens::kInvertPrefix.size());
63 }
64
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 3813 times.
3814 if (!entry.starts_with(tokens::kXformOpPrefix)) {
65
2/4
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
2 throw std::invalid_argument("xformOpOrder of '" + scene.PrimPath(prim).String() +
66
3/6
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 time.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 time.
✗ Branch 9 not taken.
5 "' lists '" + std::string(entry) + "', which is no xformOp");
67 }
68 3813 const std::optional<XformOpKind> kind = ParseXformOpKind(entry);
69
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3811 times.
3813 if (!kind) {
70
3/6
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 2 times.
✗ Branch 8 not taken.
6 throw std::invalid_argument("'" + scene.PrimPath(prim).String() + "' has transform op " +
71
2/4
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
8 std::string(entry) + ", which Ernie does not know");
72 }
73 3811 op.attribute = entry;
74 3811 op.kind = *kind;
75 3811 return op;
76 }
77
78 /// The order's ops, outermost first, without its reset token; `xformOp:transform` without one.
79 3571 template <typename Visit> void ForEachOp(const Scene& scene, PrimId prim, Visit visit) {
80 3571 const std::optional<Value> order = scene.Get(prim, tokens::kXformOpOrder);
81
4/4
void ernie::(anonymous namespace)::ForEachOp<ernie::ComposeXformOps(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}>(ernie::Scene const&, ernie::PrimId, ernie::ComposeXformOps(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}):
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 153 times.
void ernie::(anonymous namespace)::ForEachOp<ernie::XformOpsInOrder(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}>(ernie::Scene const&, ernie::PrimId, ernie::XformOpsInOrder(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}):
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3345 times.
3571 if (!order) {
82
2/4
void ernie::(anonymous namespace)::ForEachOp<ernie::ComposeXformOps(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}>(ernie::Scene const&, ernie::PrimId, ernie::ComposeXformOps(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}):
✓ Branch 1 taken 60 times.
✗ Branch 2 not taken.
void ernie::(anonymous namespace)::ForEachOp<ernie::XformOpsInOrder(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}>(ernie::Scene const&, ernie::PrimId, ernie::XformOpsInOrder(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}):
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
73 visit(OpEntry{tokens::kTransform, XformOpKind::Transform, false});
83 73 return;
84 }
85
10/12
void ernie::(anonymous namespace)::ForEachOp<ernie::ComposeXformOps(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}>(ernie::Scene const&, ernie::PrimId, ernie::ComposeXformOps(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}):
✓ Branch 0 taken 153 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 197 times.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 204 times.
✓ Branch 5 taken 147 times.
void ernie::(anonymous namespace)::ForEachOp<ernie::XformOpsInOrder(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}>(ernie::Scene const&, ernie::PrimId, ernie::XformOpsInOrder(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}):
✓ Branch 0 taken 3345 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3617 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 3621 times.
✓ Branch 5 taken 3345 times.
10815 for (const std::string& entry : *order->Get<std::vector<std::string>>()) {
86
4/4
void ernie::(anonymous namespace)::ForEachOp<ernie::ComposeXformOps(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}>(ernie::Scene const&, ernie::PrimId, ernie::ComposeXformOps(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}):
✓ Branch 0 taken 197 times.
✓ Branch 1 taken 7 times.
void ernie::(anonymous namespace)::ForEachOp<ernie::XformOpsInOrder(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}>(ernie::Scene const&, ernie::PrimId, ernie::XformOpsInOrder(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}):
✓ Branch 0 taken 3617 times.
✓ Branch 1 taken 4 times.
3825 if (entry != tokens::kResetXformStack) {
87
6/8
void ernie::(anonymous namespace)::ForEachOp<ernie::ComposeXformOps(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}>(ernie::Scene const&, ernie::PrimId, ernie::ComposeXformOps(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}):
✓ Branch 1 taken 194 times.
✓ Branch 2 taken 3 times.
✓ Branch 4 taken 191 times.
✓ Branch 5 taken 3 times.
void ernie::(anonymous namespace)::ForEachOp<ernie::XformOpsInOrder(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}>(ernie::Scene const&, ernie::PrimId, ernie::XformOpsInOrder(ernie::Scene const&, ernie::PrimId)::{lambda(ernie::(anonymous namespace)::OpEntry const&)#1}):
✓ Branch 1 taken 3617 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3617 times.
✗ Branch 5 not taken.
3814 visit(ParseOp(scene, prim, entry));
88 }
89 }
90 3565 }
91
92 3 std::invalid_argument BadOp(const Scene& scene, PrimId prim, const OpEntry& op,
93 std::string_view need) {
94
2/4
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
12 return std::invalid_argument(std::string(op.attribute) + " of '" +
95
4/8
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 3 times.
✗ Branch 12 not taken.
12 scene.PrimPath(prim).String() + "' must be " + std::string(need));
96 }
97
98 4703 glm::dvec3 ToVec3(const Scene& scene, PrimId prim, const OpEntry& op, const Value& value,
99 bool scalar) {
100
2/2
✓ Branch 0 taken 4700 times.
✓ Branch 1 taken 3 times.
4703 if (const auto vector = ToVector<3>(value)) {
101 4700 return *vector;
102 }
103
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
3 if (const auto number = ToNumber(value); scalar && number) {
104 2 return glm::dvec3(*number);
105 }
106
2/4
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 time.
✗ Branch 5 not taken.
2 throw BadOp(scene, prim, op, scalar ? "three numbers or one" : "three numbers");
107 }
108
109 /// The matrix of one op from its value, inverted if the order says so; identity without one.
110 4831 glm::dmat4 OpMatrix(const Scene& scene, PrimId prim, const OpEntry& op,
111 const std::optional<Value>& value) {
112
2/2
✓ Branch 0 taken 70 times.
✓ Branch 1 taken 4761 times.
4831 if (!value) {
113 70 return glm::dmat4(1.0);
114 }
115 4761 const auto kind = static_cast<std::uint8_t>(op.kind);
116 4761 glm::dmat4 m(1.0);
117
6/6
✓ Branch 0 taken 4326 times.
✓ Branch 1 taken 349 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 34 times.
✓ Branch 4 taken 14 times.
✓ Branch 5 taken 28 times.
4761 switch (op.kind) {
118 4326 case XformOpKind::Translate:
119 4326 m = glm::translate(m, ToVec3(scene, prim, op, *value, false));
120 4325 break;
121 349 case XformOpKind::Scale:
122 349 m = glm::scale(m, ToVec3(scene, prim, op, *value, true));
123 349 break;
124 10 case XformOpKind::RotateX:
125 10 case XformOpKind::RotateY:
126 10 case XformOpKind::RotateZ: {
127
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 9 times.
10 const std::optional<double> degrees = ToNumber(*value);
128
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 9 times.
10 if (!degrees) {
129
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 throw BadOp(scene, prim, op, "one angle in degrees");
130 }
131 9 m = glm::dmat4(
132 9 AxisRotation(kind - static_cast<std::uint8_t>(XformOpKind::RotateX), *degrees));
133 9 break;
134 }
135 34 case XformOpKind::Orient: {
136
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 33 times.
34 const auto* q = value->Get<glm::dquat>();
137 34 if (q == nullptr) {
138
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 throw BadOp(scene, prim, op, "a quaternion");
139 }
140 33 m = glm::dmat4(glm::mat3_cast(glm::normalize(*q)));
141 33 break;
142 }
143 14 case XformOpKind::Transform: {
144
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14 const auto* matrix = value->Get<glm::dmat4>();
145 14 if (matrix == nullptr) {
146
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw BadOp(scene, prim, op, "a 4 x 4 matrix");
147 }
148 14 m = *matrix;
149 14 break;
150 }
151 28 default:
152 28 m = glm::dmat4(EulerRotation(
153 56 ToVec3(scene, prim, op, *value, false),
154 28 static_cast<RotationOrder>(kind - static_cast<std::uint8_t>(XformOpKind::RotateXYZ))));
155 }
156
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 4748 times.
4758 return op.invert ? glm::inverse(m) : m;
157 }
158
159 } // namespace
160
161 77 std::string_view RotateOp(RotationOrder order) {
162 77 return kRotateOps[static_cast<std::size_t>(order)];
163 }
164
165 6651 glm::dmat3 EulerRotation(const glm::dvec3& degrees, RotationOrder order) {
166 6651 const auto [i, j, k] = TurnAxes(order);
167 6651 return AxisRotation(k, degrees[k]) * AxisRotation(j, degrees[j]) * AxisRotation(i, degrees[i]);
168 }
169
170 4800 glm::dvec3 EulerAngles(const glm::dmat3& rotation, RotationOrder order) {
171
2/2
✓ Branch 0 taken 2400 times.
✓ Branch 1 taken 2400 times.
4800 const auto [i, j, k] = TurnAxes(order);
172 // +1 for the cyclic orders (XYZ, YZX, ZXY), -1 for the others.
173
2/2
✓ Branch 0 taken 2400 times.
✓ Branch 1 taken 2400 times.
4800 const double s = (j == (i + 1) % 3) ? 1.0 : -1.0;
174
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 32400 times.
32400 const auto at = [&](int row, int col) { return rotation[col][row]; };
175
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4800 times.
4800 const double sinMiddle = std::clamp(-s * at(k, i), -1.0, 1.0);
176 4800 double first = 0.0;
177 4800 double last = 0.0;
178
2/2
✓ Branch 0 taken 4200 times.
✓ Branch 1 taken 600 times.
4800 if (std::abs(sinMiddle) < 1.0 - 1e-12) {
179 4200 first = std::atan2(s * at(k, j), at(k, k));
180 4200 last = std::atan2(s * at(j, i), at(i, i));
181 } else {
182 // Gimbal lock: the first and last axes line up, so the first takes the whole turn.
183 600 first = std::atan2(-s * at(j, k), at(j, j));
184 }
185 4800 glm::dvec3 degrees(0.0);
186 4800 degrees[i] = glm::degrees(first);
187 // atan2 against the cosine stays accurate near lock, where asin magnifies rounding.
188 4800 degrees[j] = glm::degrees(std::atan2(sinMiddle, std::hypot(at(k, j), at(k, k))));
189 4800 degrees[k] = glm::degrees(last);
190 4800 return degrees;
191 }
192
193 1541 void SetXform(Scene& scene, const Path& path, const XformOps& xform) {
194
4/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1531 times.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 9 times.
1541 if (xform.rotate && xform.orient) {
195
1/2
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
1 throw std::invalid_argument("An xform takes rotate or orient, not both");
196 }
197 1540 const PrimId prim = scene.Require(path);
198 1539 std::vector<std::string> order;
199
3/4
✓ Branch 1 taken 1539 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 1538 times.
1539 if (ResetsXformStack(scene, prim)) {
200
1/2
✓ Branch 1 taken 1 time.
✗ Branch 2 not taken.
1 order.emplace_back(tokens::kResetXformStack);
201 }
202 3222 const auto add = [&](std::string_view op, const Value& value) {
203 1683 scene.Set(prim, op, value);
204 1683 order.emplace_back(op);
205 3222 };
206
2/2
✓ Branch 0 taken 1528 times.
✓ Branch 1 taken 11 times.
1539 if (xform.translate) {
207
1/2
✓ Branch 1 taken 1528 times.
✗ Branch 2 not taken.
3056 add(tokens::kTranslate, *xform.translate);
208 }
209
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1530 times.
1539 if (xform.rotate) {
210
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
18 add(RotateOp(xform.rotationOrder), *xform.rotate);
211 }
212
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 1467 times.
1539 if (xform.orient) {
213
1/2
✓ Branch 2 taken 72 times.
✗ Branch 3 not taken.
144 add(tokens::kOrient, glm::normalize(*xform.orient));
214 }
215
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 1465 times.
1539 if (xform.scale) {
216
1/2
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
148 add(tokens::kScale, *xform.scale);
217 }
218
1/2
✓ Branch 1 taken 1539 times.
✗ Branch 2 not taken.
1539 scene.Set(prim, tokens::kXformOpOrder, std::move(order));
219 1539 }
220
221 3 glm::dmat4 LookAtTransform(const glm::dvec3& eye, const glm::dvec3& target, const glm::dvec3& up) {
222 3 const glm::dvec3 forward = target - eye;
223
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (glm::length(glm::cross(forward, up)) == 0.0) {
224
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::invalid_argument("A camera looking at its own eye or along its up vector");
225 }
226 3 return glm::inverse(glm::lookAtRH(eye, target, up));
227 }
228
229 3 glm::dmat4 DistantLightTransform(const glm::dvec3& towardLight) {
230
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if (glm::length(towardLight) == 0.0) {
231
0/2
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ throw std::invalid_argument("A distant light shining from no direction");
232 }
233 3 const glm::dvec3 z = glm::normalize(towardLight);
234 // Any axis not along z completes the frame; the light is the same turned about it.
235
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 time.
3 const glm::dvec3 helper = std::abs(z.z) < 0.9 ? glm::dvec3(0, 0, 1) : glm::dvec3(1, 0, 0);
236 3 const glm::dvec3 x = glm::normalize(glm::cross(helper, z));
237 3 const glm::dvec3 y = glm::cross(z, x);
238 3 return glm::dmat4(glm::dvec4(x, 0), glm::dvec4(y, 0), glm::dvec4(z, 0), glm::dvec4(0, 0, 0, 1));
239 }
240
241 213 glm::dmat4 ComposeXformOps(const Scene& scene, PrimId prim) {
242 213 glm::dmat4 local(1.0);
243 213 ForEachOp(scene, prim, [&](const OpEntry& op) {
244
2/2
✓ Branch 2 taken 251 times.
✓ Branch 3 taken 3 times.
254 local = local * OpMatrix(scene, prim, op, scene.Get(prim, op.attribute));
245 251 });
246 207 return local;
247 }
248
249 5033 bool ResetsXformStack(const Scene& scene, PrimId prim) {
250 5033 const std::optional<Value> order = scene.Get(prim, tokens::kXformOpOrder);
251
3/4
✓ Branch 0 taken 3432 times.
✓ Branch 1 taken 1601 times.
✓ Branch 2 taken 3432 times.
✗ Branch 3 not taken.
5033 const auto* entries = order ? order->Get<std::vector<std::string>>() : nullptr;
252
3/4
✓ Branch 0 taken 3432 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3420 times.
✓ Branch 3 taken 12 times.
8453 return entries != nullptr && !entries->empty() && entries->front() == tokens::kResetXformStack;
253 5033 }
254
255 3819 std::optional<XformOpKind> ParseXformOpKind(std::string_view attribute) {
256
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3819 times.
3819 if (!attribute.starts_with(tokens::kXformOpPrefix)) {
257 ✗ return std::nullopt;
258 }
259 3819 std::string_view type = attribute.substr(tokens::kXformOpPrefix.size());
260 3819 type = type.substr(0, type.find(':'));
261 3819 const auto found = std::ranges::find(kOpTypes, type);
262
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3817 times.
3819 if (found == kOpTypes.end()) {
263 2 return std::nullopt;
264 }
265 3817 return static_cast<XformOpKind>(found - kOpTypes.begin());
266 }
267
268 3358 std::vector<XformOp> XformOpsInOrder(const Scene& scene, PrimId prim) {
269
1/2
✓ Branch 1 taken 3358 times.
✗ Branch 2 not taken.
3358 const TokenTable& tokens = scene.Store().Tokens();
270 3358 std::vector<XformOp> ops;
271
1/2
✓ Branch 1 taken 3358 times.
✗ Branch 2 not taken.
3358 ForEachOp(scene, prim, [&](const OpEntry& op) {
272 7260 ops.push_back({std::string(op.attribute), op.kind, op.invert, tokens.Find(op.attribute)});
273
2/4
✓ Branch 2 taken 3630 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 3630 times.
✗ Branch 6 not taken.
7260 });
274 3358 return ops;
275 ✗ }
276
277 4577 glm::dmat4 XformOpMatrix(const Scene& scene, PrimId prim, const XformOp& op) {
278 // An attribute first authored after the order was read has no token in the op.
279
1/2
✓ Branch 1 taken 4577 times.
✗ Branch 2 not taken.
4577 return OpMatrix(scene, prim, {op.attribute, op.kind, op.invert},
280
2/2
✓ Branch 0 taken 4566 times.
✓ Branch 1 taken 11 times.
9154 op.token ? scene.Get(prim, *op.token) : scene.Get(prim, op.attribute));
281 }
282
283 } // namespace ernie
284