GameApi 28
Library used by GameApi Builder tool
Loading...
Searching...
No Matches
normal_compression_utils.h
Go to the documentation of this file.
1// Copyright 2016 The Draco Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// Utilities for converting unit vectors to octahedral coordinates and back.
16// For more details about octahedral coordinates, see for example Cigolle
17// et al.'14 “A Survey of Efficient Representations for Independent Unit
18// Vectors”.
19//
20// In short this is motivated by an octahedron inscribed into a sphere. The
21// direction of the normal vector can be defined by a point on the octahedron.
22// On the right hemisphere (x > 0) this point is projected onto the x = 0 plane,
23// that is, the right side of the octahedron forms a diamond like shape. The
24// left side of the octahedron is also projected onto the x = 0 plane, however,
25// in this case we flap the triangles of the diamond outward. Afterwards we
26// shift the resulting square such that all values are positive.
27//
28// Important values in this file:
29// * q: number of quantization bits
30// * max_quantized_value: the max value representable with q bits (odd)
31// * max_value: max value of the diamond = max_quantized_value - 1 (even)
32// * center_value: center of the diamond after shift
33//
34// Note that the parameter space is somewhat periodic, e.g. (0, 0) ==
35// (max_value, max_value), which is also why the diamond is one smaller than the
36// maximal representable value in order to have an odd range of values.
37
38#ifndef DRACO_COMPRESSION_ATTRIBUTES_NORMAL_COMPRESSION_UTILS_H_
39#define DRACO_COMPRESSION_ATTRIBUTES_NORMAL_COMPRESSION_UTILS_H_
40
41#include <inttypes.h>
42
43#include <algorithm>
44#include <cmath>
45
46#include "draco/core/macros.h"
47
48namespace draco {
49
51 public:
53 : quantization_bits_(-1),
54 max_quantized_value_(-1),
55 max_value_(-1),
56 dequantization_scale_(1.f),
57 center_value_(-1) {}
58
59 bool SetQuantizationBits(int32_t q) {
60 if (q < 2 || q > 30) {
61 return false;
62 }
63 quantization_bits_ = q;
64 max_quantized_value_ = (1u << quantization_bits_) - 1;
65 max_value_ = max_quantized_value_ - 1;
66 dequantization_scale_ = 2.f / max_value_;
67 center_value_ = max_value_ / 2;
68 return true;
69 }
70 bool IsInitialized() const { return quantization_bits_ != -1; }
71
72 // Convert all edge points in the top left and bottom right quadrants to
73 // their corresponding position in the bottom left and top right quadrants.
74 // Convert all corner edge points to the top right corner.
75 inline void CanonicalizeOctahedralCoords(int32_t s, int32_t t, int32_t *out_s,
76 int32_t *out_t) const {
77 if ((s == 0 && t == 0) || (s == 0 && t == max_value_) ||
78 (s == max_value_ && t == 0)) {
79 s = max_value_;
80 t = max_value_;
81 } else if (s == 0 && t > center_value_) {
82 t = center_value_ - (t - center_value_);
83 } else if (s == max_value_ && t < center_value_) {
84 t = center_value_ + (center_value_ - t);
85 } else if (t == max_value_ && s < center_value_) {
86 s = center_value_ + (center_value_ - s);
87 } else if (t == 0 && s > center_value_) {
88 s = center_value_ - (s - center_value_);
89 }
90
91 *out_s = s;
92 *out_t = t;
93 }
94
95 // Converts an integer vector to octahedral coordinates.
96 // Precondition: |int_vec| abs sum must equal center value.
97 inline void IntegerVectorToQuantizedOctahedralCoords(const int32_t *int_vec,
98 int32_t *out_s,
99 int32_t *out_t) const {
101 std::abs(int_vec[0]) + std::abs(int_vec[1]) + std::abs(int_vec[2]),
102 center_value_);
103 int32_t s, t;
104 if (int_vec[0] >= 0) {
105 // Right hemisphere.
106 s = (int_vec[1] + center_value_);
107 t = (int_vec[2] + center_value_);
108 } else {
109 // Left hemisphere.
110 if (int_vec[1] < 0) {
111 s = std::abs(int_vec[2]);
112 } else {
113 s = (max_value_ - std::abs(int_vec[2]));
114 }
115 if (int_vec[2] < 0) {
116 t = std::abs(int_vec[1]);
117 } else {
118 t = (max_value_ - std::abs(int_vec[1]));
119 }
120 }
121 CanonicalizeOctahedralCoords(s, t, out_s, out_t);
122 }
123
124 template <class T>
125 void FloatVectorToQuantizedOctahedralCoords(const T *vector, int32_t *out_s,
126 int32_t *out_t) const {
127 const double abs_sum = std::abs(static_cast<double>(vector[0])) +
128 std::abs(static_cast<double>(vector[1])) +
129 std::abs(static_cast<double>(vector[2]));
130
131 // Adjust values such that abs sum equals 1.
132 double scaled_vector[3];
133 if (abs_sum > 1e-6) {
134 // Scale needed to project the vector to the surface of an octahedron.
135 const double scale = 1.0 / abs_sum;
136 scaled_vector[0] = vector[0] * scale;
137 scaled_vector[1] = vector[1] * scale;
138 scaled_vector[2] = vector[2] * scale;
139 } else {
140 scaled_vector[0] = 1.0;
141 scaled_vector[1] = 0;
142 scaled_vector[2] = 0;
143 }
144
145 // Scale vector such that the sum equals the center value.
146 int32_t int_vec[3];
147 int_vec[0] =
148 static_cast<int32_t>(floor(scaled_vector[0] * center_value_ + 0.5));
149 int_vec[1] =
150 static_cast<int32_t>(floor(scaled_vector[1] * center_value_ + 0.5));
151 // Make sure the sum is exactly the center value.
152 int_vec[2] = center_value_ - std::abs(int_vec[0]) - std::abs(int_vec[1]);
153 if (int_vec[2] < 0) {
154 // If the sum of first two coordinates is too large, we need to decrease
155 // the length of one of the coordinates.
156 if (int_vec[1] > 0) {
157 int_vec[1] += int_vec[2];
158 } else {
159 int_vec[1] -= int_vec[2];
160 }
161 int_vec[2] = 0;
162 }
163 // Take care of the sign.
164 if (scaled_vector[2] < 0) {
165 int_vec[2] *= -1;
166 }
167
168 IntegerVectorToQuantizedOctahedralCoords(int_vec, out_s, out_t);
169 }
170
171 // Normalize |vec| such that its abs sum is equal to the center value;
172 template <class T>
173 void CanonicalizeIntegerVector(T *vec) const {
174 static_assert(std::is_integral<T>::value, "T must be an integral type.");
175 static_assert(std::is_signed<T>::value, "T must be a signed type.");
176 const int64_t abs_sum = static_cast<int64_t>(std::abs(vec[0])) +
177 static_cast<int64_t>(std::abs(vec[1])) +
178 static_cast<int64_t>(std::abs(vec[2]));
179
180 if (abs_sum == 0) {
181 vec[0] = center_value_; // vec[1] == v[2] == 0
182 } else {
183 vec[0] =
184 (static_cast<int64_t>(vec[0]) * static_cast<int64_t>(center_value_)) /
185 abs_sum;
186 vec[1] =
187 (static_cast<int64_t>(vec[1]) * static_cast<int64_t>(center_value_)) /
188 abs_sum;
189 if (vec[2] >= 0) {
190 vec[2] = center_value_ - std::abs(vec[0]) - std::abs(vec[1]);
191 } else {
192 vec[2] = -(center_value_ - std::abs(vec[0]) - std::abs(vec[1]));
193 }
194 }
195 }
196
197 inline void QuantizedOctahedralCoordsToUnitVector(int32_t in_s, int32_t in_t,
198 float *out_vector) const {
199 OctahedralCoordsToUnitVector(in_s * dequantization_scale_ - 1.f,
200 in_t * dequantization_scale_ - 1.f,
201 out_vector);
202 }
203
204 // |s| and |t| are expected to be signed values.
205 inline bool IsInDiamond(const int32_t &s, const int32_t &t) const {
206 // Expect center already at origin.
207 DRACO_DCHECK_LE(s, center_value_);
208 DRACO_DCHECK_LE(t, center_value_);
209 DRACO_DCHECK_GE(s, -center_value_);
210 DRACO_DCHECK_GE(t, -center_value_);
211 const uint32_t st =
212 static_cast<uint32_t>(std::abs(s)) + static_cast<uint32_t>(std::abs(t));
213 return st <= center_value_;
214 }
215
216 void InvertDiamond(int32_t *s, int32_t *t) const {
217 // Expect center already at origin.
218 DRACO_DCHECK_LE(*s, center_value_);
219 DRACO_DCHECK_LE(*t, center_value_);
220 DRACO_DCHECK_GE(*s, -center_value_);
221 DRACO_DCHECK_GE(*t, -center_value_);
222 int32_t sign_s = 0;
223 int32_t sign_t = 0;
224 if (*s >= 0 && *t >= 0) {
225 sign_s = 1;
226 sign_t = 1;
227 } else if (*s <= 0 && *t <= 0) {
228 sign_s = -1;
229 sign_t = -1;
230 } else {
231 sign_s = (*s > 0) ? 1 : -1;
232 sign_t = (*t > 0) ? 1 : -1;
233 }
234
235 // Perform the addition and subtraction using unsigned integers to avoid
236 // signed integer overflows for bad data. Note that the result will be
237 // unchanged for non-overflowing cases.
238 const uint32_t corner_point_s = sign_s * center_value_;
239 const uint32_t corner_point_t = sign_t * center_value_;
240 uint32_t us = *s;
241 uint32_t ut = *t;
242 us = us + us - corner_point_s;
243 ut = ut + ut - corner_point_t;
244 if (sign_s * sign_t >= 0) {
245 uint32_t temp = us;
246 us = -ut;
247 ut = -temp;
248 } else {
249 std::swap(us, ut);
250 }
251 us = us + corner_point_s;
252 ut = ut + corner_point_t;
253
254 *s = us;
255 *t = ut;
256 *s /= 2;
257 *t /= 2;
258 }
259
260 void InvertDirection(int32_t *s, int32_t *t) const {
261 // Expect center already at origin.
262 DRACO_DCHECK_LE(*s, center_value_);
263 DRACO_DCHECK_LE(*t, center_value_);
264 DRACO_DCHECK_GE(*s, -center_value_);
265 DRACO_DCHECK_GE(*t, -center_value_);
266 *s *= -1;
267 *t *= -1;
268 this->InvertDiamond(s, t);
269 }
270
271 // For correction values.
272 int32_t ModMax(int32_t x) const {
273 if (x > this->center_value()) {
274 return x - this->max_quantized_value();
275 }
276 if (x < -this->center_value()) {
277 return x + this->max_quantized_value();
278 }
279 return x;
280 }
281
282 // For correction values.
283 int32_t MakePositive(int32_t x) const {
284 DRACO_DCHECK_LE(x, this->center_value() * 2);
285 if (x < 0) {
286 return x + this->max_quantized_value();
287 }
288 return x;
289 }
290
291 int32_t quantization_bits() const { return quantization_bits_; }
292 int32_t max_quantized_value() const { return max_quantized_value_; }
293 int32_t max_value() const { return max_value_; }
294 int32_t center_value() const { return center_value_; }
295
296 private:
297 inline void OctahedralCoordsToUnitVector(float in_s_scaled, float in_t_scaled,
298 float *out_vector) const {
299 // Background about the encoding:
300 // A normal is encoded in a normalized space <s, t> depicted below. The
301 // encoding correponds to an octahedron that is unwrapped to a 2D plane.
302 // During encoding, a normal is projected to the surface of the octahedron
303 // and the projection is then unwrapped to the 2D plane. Decoding is the
304 // reverse of this process.
305 // All points in the central diamond are located on triangles on the
306 // right "hemisphere" of the octahedron while all points outside of the
307 // diamond are on the left hemisphere (basically, they would have to be
308 // wrapped along the diagonal edges to form the octahedron). The central
309 // point corresponds to the right most vertex of the octahedron and all
310 // corners of the plane correspond to the left most vertex of the
311 // octahedron.
312 //
313 // t
314 // ^ *-----*-----*
315 // | | /|\ |
316 // | / | \ |
317 // | / | \ |
318 // | / | \ |
319 // *-----*---- *
320 // | \ | / |
321 // | \ | / |
322 // | \ | / |
323 // | \|/ |
324 // *-----*-----* --> s
325
326 // Note that the input |in_s_scaled| and |in_t_scaled| are already scaled to
327 // <-1, 1> range. This way, the central point is at coordinate (0, 0).
328 float y = in_s_scaled;
329 float z = in_t_scaled;
330
331 // Remaining coordinate can be computed by projecting the (y, z) values onto
332 // the surface of the octahedron.
333 const float x = 1.f - std::abs(y) - std::abs(z);
334
335 // |x| is essentially a signed distance from the diagonal edges of the
336 // diamond shown on the figure above. It is positive for all points in the
337 // diamond (right hemisphere) and negative for all points outside the
338 // diamond (left hemisphere). For all points on the left hemisphere we need
339 // to update their (y, z) coordinates to account for the wrapping along
340 // the edges of the diamond.
341 float x_offset = -x;
342 x_offset = x_offset < 0 ? 0 : x_offset;
343
344 // This will do nothing for the points on the right hemisphere but it will
345 // mirror the (y, z) location along the nearest diagonal edge of the
346 // diamond.
347 y += y < 0 ? x_offset : -x_offset;
348 z += z < 0 ? x_offset : -x_offset;
349
350 // Normalize the computed vector.
351 const float norm_squared = x * x + y * y + z * z;
352 if (norm_squared < 1e-6) {
353 out_vector[0] = 0;
354 out_vector[1] = 0;
355 out_vector[2] = 0;
356 } else {
357 const float d = 1.0f / std::sqrt(norm_squared);
358 out_vector[0] = x * d;
359 out_vector[1] = y * d;
360 out_vector[2] = z * d;
361 }
362 }
363
364 int32_t quantization_bits_;
365 int32_t max_quantized_value_;
366 int32_t max_value_;
367 float dequantization_scale_;
368 int32_t center_value_;
369};
370} // namespace draco
371
372#endif // DRACO_COMPRESSION_ATTRIBUTES_NORMAL_COMPRESSION_UTILS_H_
Env e
Definition Common.cc:2
void floor(const FCube &pos, FLine &l1, FLine &l2, FLine &l3, FLine &l4, FPlane &pl, FaceStore &s)
Definition Functions.cc:95
OctahedronToolBox()
Definition normal_compression_utils.h:52
int32_t max_quantized_value() const
Definition normal_compression_utils.h:292
int32_t max_value() const
Definition normal_compression_utils.h:293
void CanonicalizeIntegerVector(T *vec) const
Definition normal_compression_utils.h:173
int32_t ModMax(int32_t x) const
Definition normal_compression_utils.h:272
int32_t center_value() const
Definition normal_compression_utils.h:294
void FloatVectorToQuantizedOctahedralCoords(const T *vector, int32_t *out_s, int32_t *out_t) const
Definition normal_compression_utils.h:125
int32_t MakePositive(int32_t x) const
Definition normal_compression_utils.h:283
bool SetQuantizationBits(int32_t q)
Definition normal_compression_utils.h:59
void InvertDirection(int32_t *s, int32_t *t) const
Definition normal_compression_utils.h:260
int32_t quantization_bits() const
Definition normal_compression_utils.h:291
bool IsInDiamond(const int32_t &s, const int32_t &t) const
Definition normal_compression_utils.h:205
void IntegerVectorToQuantizedOctahedralCoords(const int32_t *int_vec, int32_t *out_s, int32_t *out_t) const
Definition normal_compression_utils.h:97
bool IsInitialized() const
Definition normal_compression_utils.h:70
void QuantizedOctahedralCoordsToUnitVector(int32_t in_s, int32_t in_t, float *out_vector) const
Definition normal_compression_utils.h:197
void CanonicalizeOctahedralCoords(int32_t s, int32_t t, int32_t *out_s, int32_t *out_t) const
Definition normal_compression_utils.h:75
void InvertDiamond(int32_t *s, int32_t *t) const
Definition normal_compression_utils.h:216
#define DRACO_DCHECK_EQ(a, b)
Definition macros.h:81
#define DRACO_DCHECK_LE(a, b)
Definition macros.h:85
#define DRACO_DCHECK_GE(a, b)
Definition macros.h:83
d
Definition meshpage.php:3895
Definition attribute_octahedron_transform.cc:21
Definition GameApi.h:42