// Copyright 2016 The Draco Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // TODO(hemmer): Make this a wrapper using DynamicIntegerPointsKdTreeEncoder. #ifndef DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_INTEGER_POINTS_KD_TREE_ENCODER_H_ #define DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_INTEGER_POINTS_KD_TREE_ENCODER_H_ #include #include #include #include #include "draco/compression/bit_coders/adaptive_rans_bit_encoder.h" #include "draco/compression/bit_coders/direct_bit_encoder.h" #include "draco/compression/bit_coders/folded_integer_bit_encoder.h" #include "draco/compression/bit_coders/rans_bit_encoder.h" #include "draco/compression/point_cloud/algorithms/point_cloud_types.h" #include "draco/compression/point_cloud/algorithms/queuing_policy.h" #include "draco/core/bit_utils.h" #include "draco/core/encoder_buffer.h" #include "draco/core/math_utils.h" namespace draco { // This policy class provides several configurations for the encoder that allow // to trade speed vs compression rate. Level 0 is fastest while 10 is the best // compression rate. The decoder must select the same level. template struct IntegerPointsKdTreeEncoderCompressionPolicy : public IntegerPointsKdTreeEncoderCompressionPolicy {}; template <> struct IntegerPointsKdTreeEncoderCompressionPolicy<0> { typedef DirectBitEncoder NumbersEncoder; typedef DirectBitEncoder AxisEncoder; typedef DirectBitEncoder HalfEncoder; typedef DirectBitEncoder RemainingBitsEncoder; static constexpr bool select_axis = false; template using QueuingStrategy = Stack; }; template <> struct IntegerPointsKdTreeEncoderCompressionPolicy<2> : public IntegerPointsKdTreeEncoderCompressionPolicy<1> { typedef RAnsBitEncoder NumbersEncoder; }; template <> struct IntegerPointsKdTreeEncoderCompressionPolicy<4> : public IntegerPointsKdTreeEncoderCompressionPolicy<3> { typedef FoldedBit32Encoder NumbersEncoder; }; template <> struct IntegerPointsKdTreeEncoderCompressionPolicy<6> : public IntegerPointsKdTreeEncoderCompressionPolicy<5> { static constexpr bool select_axis = true; }; template <> struct IntegerPointsKdTreeEncoderCompressionPolicy<8> : public IntegerPointsKdTreeEncoderCompressionPolicy<7> { typedef FoldedBit32Encoder NumbersEncoder; template using QueuingStrategy = Queue; }; template <> struct IntegerPointsKdTreeEncoderCompressionPolicy<10> : public IntegerPointsKdTreeEncoderCompressionPolicy<9> { template using QueuingStrategy = PriorityQueue; }; // This class encodes a given integer point cloud based on the point cloud // compression algorithm in: // Olivier Devillers and Pierre-Marie Gandoin // "Geometric compression for interactive transmission" // // In principle the algorithm keeps on splitting the point cloud in the middle // while alternating the axes. In 3D this results in an Octree like structure. // In each step we encode the number of points in the first half. // The algorithm does not preserve the order of points. // // However, the algorithm here differs from the original as follows: // The algorithm keeps on splitting the point cloud in the middle of the axis // that keeps the point cloud as clustered as possible, which gives a better // compression rate. // The number of points is encode by the deviation from the half of the points // in the smaller half of the two. This results in a better compression rate as // there are more leading zeros, which is then compressed better by the // arithmetic encoding. // // |PointDiT| is a type representing a point with uint32_t coordinates. // must provide construction from three uint32_t and operator[]. template class IntegerPointsKdTreeEncoder { typedef IntegerPointsKdTreeEncoderCompressionPolicy Policy; typedef typename Policy::NumbersEncoder NumbersEncoder; typedef typename Policy::AxisEncoder AxisEncoder; typedef typename Policy::HalfEncoder HalfEncoder; typedef typename Policy::RemainingBitsEncoder RemainingBitsEncoder; public: IntegerPointsKdTreeEncoder() : bit_length_(0) {} // Encodes an integer point cloud given by [begin,end) into buffer. // |bit_length| gives the highest bit used for all coordinates. template bool EncodePoints(RandomAccessIteratorT begin, RandomAccessIteratorT end, const uint32_t &bit_length, EncoderBuffer *buffer); // Encodes an integer point cloud given by [begin,end) into buffer. template bool EncodePoints(RandomAccessIteratorT begin, RandomAccessIteratorT end, EncoderBuffer *buffer) { return EncodePoints(begin, end, 32, buffer); } private: // For the sack of readability of code, we decided to make this exception // from the naming scheme. static constexpr int D = PointTraits::Dimension(); template uint32_t GetAxis(RandomAccessIteratorT begin, RandomAccessIteratorT end, const PointDiT &old_base, std::array levels, uint32_t last_axis); template void EncodeInternal(RandomAccessIteratorT begin, RandomAccessIteratorT end, PointDiT old_base, std::array levels, uint32_t last_axis); class Splitter { public: Splitter(int axis, uint32_t value) : axis_(axis), value_(value) {} bool operator()(const PointDiT &a) { return a[axis_] < value_; } private: int axis_; uint32_t value_; }; void EncodeNumber(int nbits, uint32_t value) { numbers_encoder_.EncodeLeastSignificantBits32(nbits, value); } template struct EncodingStatus { EncodingStatus( RandomAccessIteratorT begin_, RandomAccessIteratorT end_, const PointDiT &old_base_, std::array::Dimension()> levels_, uint32_t last_axis_) : begin(begin_), end(end_), old_base(old_base_), levels(levels_), last_axis(last_axis_) { num_remaining_points = end - begin; } RandomAccessIteratorT begin; RandomAccessIteratorT end; PointDiT old_base; std::array levels; uint32_t last_axis; uint32_t num_remaining_points; friend bool operator<(const EncodingStatus &l, const EncodingStatus &r) { return l.num_remaining_points < r.num_remaining_points; } }; uint32_t bit_length_; uint32_t num_points_; NumbersEncoder numbers_encoder_; RemainingBitsEncoder remaining_bits_encoder_; AxisEncoder axis_encoder_; HalfEncoder half_encoder_; }; template template bool IntegerPointsKdTreeEncoder::EncodePoints( RandomAccessIteratorT begin, RandomAccessIteratorT end, const uint32_t &bit_length, EncoderBuffer *buffer) { bit_length_ = bit_length; num_points_ = end - begin; buffer->Encode(bit_length_); buffer->Encode(num_points_); if (num_points_ == 0) { return true; } numbers_encoder_.StartEncoding(); remaining_bits_encoder_.StartEncoding(); axis_encoder_.StartEncoding(); half_encoder_.StartEncoding(); EncodeInternal(begin, end, PointTraits::Origin(), PointTraits::ZeroArray(), 0); numbers_encoder_.EndEncoding(buffer); remaining_bits_encoder_.EndEncoding(buffer); axis_encoder_.EndEncoding(buffer); half_encoder_.EndEncoding(buffer); return true; } template template uint32_t IntegerPointsKdTreeEncoder::GetAxis( RandomAccessIteratorT begin, RandomAccessIteratorT end, const PointDiT &old_base, std::array levels, uint32_t last_axis) { if (!Policy::select_axis) { return DRACO_INCREMENT_MOD(last_axis, D); } // For many points this function selects the axis that should be used // for the split by keeping as many points as possible bundled. // In the best case we do not split the point cloud at all. // For lower number of points, we simply choose the axis that is refined the // least so far. DRACO_DCHECK_EQ(true, end - begin != 0); uint32_t best_axis = 0; if (end - begin < 64) { for (uint32_t axis = 1; axis < D; ++axis) { if (levels[best_axis] > levels[axis]) { best_axis = axis; } } } else { const uint32_t size = (end - begin); std::array num_remaining_bits = PointTraits::ZeroArray(); for (int i = 0; i < D; i++) { num_remaining_bits[i] = bit_length_ - levels[i]; } PointDiT split(old_base); for (int i = 0; i < D; i++) { if (num_remaining_bits[i]) { split[i] += 1 << (num_remaining_bits[i] - 1); } } std::array deviations = PointTraits::ZeroArray(); for (auto it = begin; it != end; ++it) { for (int i = 0; i < D; i++) { deviations[i] += ((*it)[i] < split[i]); } } for (int i = 0; i < D; i++) { deviations[i] = std::max(size - deviations[i], deviations[i]); } uint32_t max_value = 0; best_axis = 0; for (int i = 0; i < D; i++) { // If axis can be subdivided. if (num_remaining_bits[i]) { // Check if this is the better axis. if (max_value < deviations[i]) { max_value = deviations[i]; best_axis = i; } } } axis_encoder_.EncodeLeastSignificantBits32(4, best_axis); } return best_axis; } template template void IntegerPointsKdTreeEncoder::EncodeInternal( RandomAccessIteratorT begin, RandomAccessIteratorT end, PointDiT old_base, std::array levels, uint32_t last_axis) { EncodingStatus init_status(begin, end, old_base, levels, last_axis); typename Policy::template QueuingStrategy< EncodingStatus> status_q; status_q.push(init_status); while (!status_q.empty()) { EncodingStatus status = status_q.front(); status_q.pop(); begin = status.begin; end = status.end; old_base = status.old_base; levels = status.levels; last_axis = status.last_axis; const uint32_t axis = GetAxis(begin, end, old_base, levels, last_axis); const uint32_t level = levels[axis]; const uint32_t num_remaining_points = end - begin; // If this happens all axis are subdivided to the end. if ((bit_length_ - level) == 0) { continue; } // Fast encoding of remaining bits if number of points is 1. // Doing this also for 2 gives a slight additional speed up. if (num_remaining_points <= 2) { std::array axes; axes[0] = axis; for (int i = 1; i < D; i++) { axes[i] = DRACO_INCREMENT_MOD(axes[i - 1], D); } std::array num_remaining_bits; for (int i = 0; i < D; i++) { num_remaining_bits[i] = bit_length_ - levels[axes[i]]; } for (uint32_t i = 0; i < num_remaining_points; ++i) { const PointDiT &p = *(begin + i); for (int j = 0; j < D; j++) { if (num_remaining_bits[j]) { remaining_bits_encoder_.EncodeLeastSignificantBits32( num_remaining_bits[j], p[axes[j]]); } } } continue; } const uint32_t num_remaining_bits = bit_length_ - level; const uint32_t modifier = 1 << (num_remaining_bits - 1); PointDiT new_base(old_base); new_base[axis] += modifier; const RandomAccessIteratorT split = std::partition(begin, end, Splitter(axis, new_base[axis])); DRACO_DCHECK_EQ(true, (end - begin) > 0); // Encode number of points in first and second half. const int required_bits = MostSignificantBit(num_remaining_points); const uint32_t first_half = split - begin; const uint32_t second_half = end - split; const bool left = first_half < second_half; if (first_half != second_half) { half_encoder_.EncodeBit(left); } if (left) { EncodeNumber(required_bits, num_remaining_points / 2 - first_half); } else { EncodeNumber(required_bits, num_remaining_points / 2 - second_half); } levels[axis] += 1; if (split != begin) { status_q.push(EncodingStatus( begin, split, old_base, levels, axis)); } if (split != end) { status_q.push(EncodingStatus(split, end, new_base, levels, axis)); } } } extern template class IntegerPointsKdTreeEncoder; extern template class IntegerPointsKdTreeEncoder; extern template class IntegerPointsKdTreeEncoder; extern template class IntegerPointsKdTreeEncoder; extern template class IntegerPointsKdTreeEncoder; extern template class IntegerPointsKdTreeEncoder; extern template class IntegerPointsKdTreeEncoder; extern template class IntegerPointsKdTreeEncoder; extern template class IntegerPointsKdTreeEncoder; extern template class IntegerPointsKdTreeEncoder; extern template class IntegerPointsKdTreeEncoder; } // namespace draco #endif // DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_INTEGER_POINTS_KD_TREE_ENCODER_H_