// // Spanner.h // libraries/metavoxels/src // // Created by Andrzej Kapolka on 11/10/14. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #ifndef hifi_Spanner_h #define hifi_Spanner_h #include #include "AttributeRegistry.h" #include "MetavoxelUtil.h" class AbstractHeightfieldNodeRenderer; class Heightfield; class HeightfieldColor; class HeightfieldHeight; class HeightfieldMaterial; class HeightfieldNode; class HeightfieldStack; class SpannerRenderer; /// An object that spans multiple octree cells. class Spanner : public SharedObject { Q_OBJECT Q_PROPERTY(Box bounds MEMBER _bounds WRITE setBounds NOTIFY boundsChanged DESIGNABLE false) Q_PROPERTY(float placementGranularity MEMBER _placementGranularity DESIGNABLE false) Q_PROPERTY(float voxelizationGranularity MEMBER _voxelizationGranularity DESIGNABLE false) Q_PROPERTY(bool willBeVoxelized MEMBER _willBeVoxelized DESIGNABLE false) public: /// Returns the value of the global visit counter and increments it. static int getAndIncrementNextVisit() { return _nextVisit.fetchAndAddOrdered(1); } Spanner(); void setBounds(const Box& bounds); const Box& getBounds() const { return _bounds; } void setPlacementGranularity(float granularity) { _placementGranularity = granularity; } float getPlacementGranularity() const { return _placementGranularity; } void setVoxelizationGranularity(float granularity) { _voxelizationGranularity = granularity; } float getVoxelizationGranularity() const { return _voxelizationGranularity; } void setMerged(bool merged) { _merged = merged; } bool isMerged() const { return _merged; } void setWillBeVoxelized(bool willBeVoxelized) { _willBeVoxelized = willBeVoxelized; } bool getWillBeVoxelized() const { return _willBeVoxelized; } /// Checks whether we've visited this object on the current traversal. If we have, returns false. /// If we haven't, sets the last visit identifier and returns true. bool testAndSetVisited(int visit); /// Returns a pointer to the renderer, creating it if necessary. SpannerRenderer* getRenderer(); /// Checks whether this is a heightfield. virtual bool isHeightfield() const; /// Finds the height at the specified location, or returns -FLT_MAX for none. virtual float getHeight(const glm::vec3& location) const; /// Finds the intersection between the described ray and this spanner. virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; /// Attempts to paint on the spanner. /// \return the modified spanner, or this if no modification was performed virtual Spanner* paintMaterial(const glm::vec3& position, float radius, const SharedObjectPointer& material, const QColor& color); /// Attempts to modify the spanner's height. /// \return the modified spanner, or this if no modification was performed virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height); /// Attempts to "sculpt" with the supplied spanner. /// \return the modified spanner, or this if no modification was performed virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, const QColor& color); /// Checks whether this spanner has its own colors. virtual bool hasOwnColors() const; /// Checks whether this spanner has its own materials. virtual bool hasOwnMaterials() const; /// Checks whether the spanner contains the specified point. virtual bool contains(const glm::vec3& point); /// Retrieves the color at the specified point. virtual QRgb getColorAt(const glm::vec3& point); /// Retrieves the material at the specified point. virtual int getMaterialAt(const glm::vec3& point); /// Retrieves a reference to the list of materials. virtual QVector& getMaterials(); /// Finds the intersection, if any, between the specified line segment and the spanner. virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); signals: void boundsWillChange(); void boundsChanged(const Box& bounds); protected: SpannerRenderer* _renderer; /// Returns the name of the class to instantiate in order to render this spanner. virtual QByteArray getRendererClassName() const; private: Box _bounds; float _placementGranularity; float _voxelizationGranularity; bool _merged; bool _willBeVoxelized; QHash _lastVisits; ///< last visit identifiers for each thread QMutex _lastVisitsMutex; static QAtomicInt _nextVisit; ///< the global visit counter }; /// Base class for objects that can render spanners. class SpannerRenderer : public QObject { Q_OBJECT public: Q_INVOKABLE SpannerRenderer(); virtual void init(Spanner* spanner); virtual void simulate(float deltaTime); virtual void render(const MetavoxelLOD& lod = MetavoxelLOD(), bool contained = false, bool cursor = false); virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; protected: Spanner* _spanner; }; /// An object with a 3D transform. class Transformable : public Spanner { Q_OBJECT Q_PROPERTY(glm::vec3 translation MEMBER _translation WRITE setTranslation NOTIFY translationChanged) Q_PROPERTY(glm::quat rotation MEMBER _rotation WRITE setRotation NOTIFY rotationChanged) Q_PROPERTY(float scale MEMBER _scale WRITE setScale NOTIFY scaleChanged) public: Transformable(); void setTranslation(const glm::vec3& translation); const glm::vec3& getTranslation() const { return _translation; } void setRotation(const glm::quat& rotation); const glm::quat& getRotation() const { return _rotation; } void setScale(float scale); float getScale() const { return _scale; } signals: void translationChanged(const glm::vec3& translation); void rotationChanged(const glm::quat& rotation); void scaleChanged(float scale); private: glm::vec3 _translation; glm::quat _rotation; float _scale; }; /// A transformable object with a color. class ColorTransformable : public Transformable { Q_OBJECT Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged DESIGNABLE false) public: ColorTransformable(); void setColor(const QColor& color); const QColor& getColor() const { return _color; } signals: void colorChanged(const QColor& color); protected: QColor _color; }; /// A sphere. class Sphere : public ColorTransformable { Q_OBJECT public: Q_INVOKABLE Sphere(); virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; virtual bool contains(const glm::vec3& point); virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); protected: virtual QByteArray getRendererClassName() const; private slots: void updateBounds(); }; /// A cuboid. class Cuboid : public ColorTransformable { Q_OBJECT Q_PROPERTY(float aspectY MEMBER _aspectY WRITE setAspectY NOTIFY aspectYChanged) Q_PROPERTY(float aspectZ MEMBER _aspectZ WRITE setAspectZ NOTIFY aspectZChanged) public: Q_INVOKABLE Cuboid(); void setAspectY(float aspectY); float getAspectY() const { return _aspectY; } void setAspectZ(float aspectZ); float getAspectZ() const { return _aspectZ; } virtual bool contains(const glm::vec3& point); virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); signals: void aspectYChanged(float aspectY); void aspectZChanged(float aspectZ); protected: virtual QByteArray getRendererClassName() const; private slots: void updateBoundsAndPlanes(); private: float _aspectY; float _aspectZ; static const int PLANE_COUNT = 6; glm::vec4 _planes[PLANE_COUNT]; }; /// A static 3D model loaded from the network. class StaticModel : public Transformable { Q_OBJECT Q_PROPERTY(QUrl url MEMBER _url WRITE setURL NOTIFY urlChanged) public: Q_INVOKABLE StaticModel(); void setURL(const QUrl& url); const QUrl& getURL() const { return _url; } virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; signals: void urlChanged(const QUrl& url); protected: virtual QByteArray getRendererClassName() const; private: QUrl _url; }; /// Base class for heightfield data blocks. class HeightfieldData : public DataBlock { public: static const int SHARED_EDGE; HeightfieldData(int width = 0); int getWidth() const { return _width; } protected: int _width; }; typedef QExplicitlySharedDataPointer HeightfieldHeightPointer; /// A block of height data associated with a heightfield. class HeightfieldHeight : public HeightfieldData { public: static const int HEIGHT_BORDER; static const int HEIGHT_EXTENSION; HeightfieldHeight(int width, const QVector& contents); HeightfieldHeight(Bitstream& in, int bytes); HeightfieldHeight(Bitstream& in, int bytes, const HeightfieldHeightPointer& reference); QVector& getContents() { return _contents; } void write(Bitstream& out); void writeDelta(Bitstream& out, const HeightfieldHeightPointer& reference); private: void read(Bitstream& in, int bytes); QVector _contents; }; Q_DECLARE_METATYPE(HeightfieldHeightPointer) Bitstream& operator<<(Bitstream& out, const HeightfieldHeightPointer& value); Bitstream& operator>>(Bitstream& in, HeightfieldHeightPointer& value); template<> void Bitstream::writeRawDelta(const HeightfieldHeightPointer& value, const HeightfieldHeightPointer& reference); template<> void Bitstream::readRawDelta(HeightfieldHeightPointer& value, const HeightfieldHeightPointer& reference); /// Allows editing heightfield height blocks. class HeightfieldHeightEditor : public QWidget { Q_OBJECT Q_PROPERTY(HeightfieldHeightPointer height MEMBER _height WRITE setHeight NOTIFY heightChanged USER true) public: HeightfieldHeightEditor(QWidget* parent = NULL); const HeightfieldHeightPointer& getHeight() const { return _height; } signals: void heightChanged(const HeightfieldHeightPointer& height); public slots: void setHeight(const HeightfieldHeightPointer& height); private slots: void select(); void clear(); private: HeightfieldHeightPointer _height; QPushButton* _select; QPushButton* _clear; }; typedef QExplicitlySharedDataPointer HeightfieldColorPointer; /// A block of color data associated with a heightfield. class HeightfieldColor : public HeightfieldData { public: HeightfieldColor(int width, const QByteArray& contents); HeightfieldColor(Bitstream& in, int bytes); HeightfieldColor(Bitstream& in, int bytes, const HeightfieldColorPointer& reference); QByteArray& getContents() { return _contents; } void write(Bitstream& out); void writeDelta(Bitstream& out, const HeightfieldColorPointer& reference); private: void read(Bitstream& in, int bytes); QByteArray _contents; }; Q_DECLARE_METATYPE(HeightfieldColorPointer) Bitstream& operator<<(Bitstream& out, const HeightfieldColorPointer& value); Bitstream& operator>>(Bitstream& in, HeightfieldColorPointer& value); template<> void Bitstream::writeRawDelta(const HeightfieldColorPointer& value, const HeightfieldColorPointer& reference); template<> void Bitstream::readRawDelta(HeightfieldColorPointer& value, const HeightfieldColorPointer& reference); /// Allows editing heightfield color blocks. class HeightfieldColorEditor : public QWidget { Q_OBJECT Q_PROPERTY(HeightfieldColorPointer color MEMBER _color WRITE setColor NOTIFY colorChanged USER true) public: HeightfieldColorEditor(QWidget* parent = NULL); const HeightfieldColorPointer& getColor() const { return _color; } signals: void colorChanged(const HeightfieldColorPointer& color); public slots: void setColor(const HeightfieldColorPointer& color); private slots: void select(); void clear(); private: HeightfieldColorPointer _color; QPushButton* _select; QPushButton* _clear; }; typedef QExplicitlySharedDataPointer HeightfieldMaterialPointer; /// A block of material data associated with a heightfield. class HeightfieldMaterial : public HeightfieldData { public: HeightfieldMaterial(int width, const QByteArray& contents, const QVector& materials); HeightfieldMaterial(Bitstream& in, int bytes); HeightfieldMaterial(Bitstream& in, int bytes, const HeightfieldMaterialPointer& reference); QByteArray& getContents() { return _contents; } QVector& getMaterials() { return _materials; } void write(Bitstream& out); void writeDelta(Bitstream& out, const HeightfieldMaterialPointer& reference); private: void read(Bitstream& in, int bytes); QByteArray _contents; QVector _materials; }; Q_DECLARE_METATYPE(HeightfieldMaterialPointer) Bitstream& operator<<(Bitstream& out, const HeightfieldMaterialPointer& value); Bitstream& operator>>(Bitstream& in, HeightfieldMaterialPointer& value); template<> void Bitstream::writeRawDelta(const HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference); template<> void Bitstream::readRawDelta(HeightfieldMaterialPointer& value, const HeightfieldMaterialPointer& reference); typedef QExplicitlySharedDataPointer HeightfieldStackPointer; /// A single column within a stack block. class StackArray : public QByteArray { public: #pragma pack(push, 1) /// A single entry within the array. class Entry { public: quint32 color; uchar material; quint32 hermiteX; quint32 hermiteY; quint32 hermiteZ; Entry(); bool isSet() const { return qAlpha(color) != 0; } bool isZero() const; bool isMergeable(const Entry& other) const; void setHermiteX(const glm::vec3& normal, float position); float getHermiteX(glm::vec3& normal) const; void setHermiteY(const glm::vec3& normal, float position); float getHermiteY(glm::vec3& normal) const; void setHermiteZ(const glm::vec3& normal, float position); float getHermiteZ(glm::vec3& normal) const; }; #pragma pack(pop) static int getSize(int entries) { return (entries == 0) ? 0 : sizeof(quint16) + sizeof(Entry) * entries; } StackArray() : QByteArray() { } StackArray(int entries) : QByteArray(getSize(entries), 0) { } StackArray(const QByteArray& other) : QByteArray(other) { } StackArray(const char* src, int bytes) : QByteArray(src, bytes) { } int getPosition() const { return *(const quint16*)constData(); } void setPosition(int position) { *(quint16*)data() = position; } quint16& getPositionRef() { return *(quint16*)data(); } int getEntryCount() const { return isEmpty() ? 0 : (size() - sizeof(quint16)) / sizeof(Entry); } Entry* getEntryData() { return (Entry*)(data() + sizeof(quint16)); } const Entry* getEntryData() const { return (const Entry*)(constData() + sizeof(quint16)); } int getEntryAlpha(int y) const; Entry& getEntry(int y); const Entry& getEntry(int y) const; void getExtents(int& minimumY, int& maximumY) const; void removeEntries(int position, int count) { remove(sizeof(quint16) + position * sizeof(Entry), count * sizeof(Entry)); } }; /// A block of stack data associated with a heightfield. class HeightfieldStack : public HeightfieldData { public: HeightfieldStack(int width, const QVector& contents, const QVector& materials); HeightfieldStack(Bitstream& in, int bytes); HeightfieldStack(Bitstream& in, int bytes, const HeightfieldStackPointer& reference); QVector& getContents() { return _contents; } QVector& getMaterials() { return _materials; } void write(Bitstream& out); void writeDelta(Bitstream& out, const HeightfieldStackPointer& reference); private: void read(Bitstream& in, int bytes); QVector _contents; QVector _materials; }; Q_DECLARE_METATYPE(HeightfieldStackPointer) Bitstream& operator<<(Bitstream& out, const HeightfieldStackPointer& value); Bitstream& operator>>(Bitstream& in, HeightfieldStackPointer& value); template<> void Bitstream::writeRawDelta(const HeightfieldStackPointer& value, const HeightfieldStackPointer& reference); template<> void Bitstream::readRawDelta(HeightfieldStackPointer& value, const HeightfieldStackPointer& reference); typedef QExplicitlySharedDataPointer HeightfieldNodePointer; /// Holds the base state used in streaming heightfield data. class HeightfieldStreamBase { public: Bitstream& stream; const MetavoxelLOD& lod; const MetavoxelLOD& referenceLOD; }; /// Holds the state used in streaming a heightfield node. class HeightfieldStreamState { public: HeightfieldStreamBase& base; glm::vec2 minimum; float size; bool shouldSubdivide() const; bool shouldSubdivideReference() const; bool becameSubdivided() const; bool becameSubdividedOrCollapsed() const; void setMinimum(const glm::vec2& lastMinimum, int index); }; /// A node in a heightfield quadtree. class HeightfieldNode : public QSharedData { public: static const int CHILD_COUNT = 4; HeightfieldNode(const HeightfieldHeightPointer& height = HeightfieldHeightPointer(), const HeightfieldColorPointer& color = HeightfieldColorPointer(), const HeightfieldMaterialPointer& material = HeightfieldMaterialPointer(), const HeightfieldStackPointer& stack = HeightfieldStackPointer()); HeightfieldNode(const HeightfieldNode& other); ~HeightfieldNode(); void setContents(const HeightfieldHeightPointer& height, const HeightfieldColorPointer& color, const HeightfieldMaterialPointer& material, const HeightfieldStackPointer& stack); void setHeight(const HeightfieldHeightPointer& height) { _height = height; } const HeightfieldHeightPointer& getHeight() const { return _height; } void setColor(const HeightfieldColorPointer& color) { _color = color; } const HeightfieldColorPointer& getColor() const { return _color; } void setMaterial(const HeightfieldMaterialPointer& material) { _material = material; } const HeightfieldMaterialPointer& getMaterial() const { return _material; } void setStack(const HeightfieldStackPointer& stack) { _stack = stack; } const HeightfieldStackPointer& getStack() const { return _stack; } void setRenderer(AbstractHeightfieldNodeRenderer* renderer) { _renderer = renderer; } AbstractHeightfieldNodeRenderer* getRenderer() const { return _renderer; } bool isLeaf() const; void setChild(int index, const HeightfieldNodePointer& child) { _children[index] = child; } const HeightfieldNodePointer& getChild(int index) const { return _children[index]; } float getHeight(const glm::vec3& location) const; bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; HeightfieldNode* paintMaterial(const glm::vec3& position, const glm::vec3& radius, const SharedObjectPointer& material, const QColor& color); void getRangeAfterHeightPaint(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const glm::vec3& position, float radius, float height, float& minimum, float& maximum) const; HeightfieldNode* paintHeight(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const glm::vec3& position, float radius, float height, float normalizeScale, float normalizeOffset); void getRangeAfterEdit(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, const Box& editBounds, float& minimum, float& maximum) const; HeightfieldNode* setMaterial(const glm::vec3& translation, const glm::quat& rotation, const glm::vec3& scale, Spanner* spanner, const SharedObjectPointer& material, const QColor& color, float normalizeScale, float normalizeOffset); void read(HeightfieldStreamState& state); void write(HeightfieldStreamState& state) const; void readDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state); void writeDelta(const HeightfieldNodePointer& reference, HeightfieldStreamState& state) const; HeightfieldNode* readSubdivision(HeightfieldStreamState& state); void writeSubdivision(HeightfieldStreamState& state) const; void readSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, const HeightfieldNode* ancestor); void writeSubdivided(HeightfieldStreamState& state, const HeightfieldStreamState& ancestorState, const HeightfieldNode* ancestor) const; private: void clearChildren(); void mergeChildren(bool height = true, bool colorMaterial = true); QRgb getColorAt(const glm::vec3& location) const; int getMaterialAt(const glm::vec3& location) const; void maybeRenormalize(const glm::vec3& scale, float normalizeScale, float normalizeOffset, int innerStackWidth, QVector& heightContents, QVector& stackContents); HeightfieldHeightPointer _height; HeightfieldColorPointer _color; HeightfieldMaterialPointer _material; HeightfieldStackPointer _stack; HeightfieldNodePointer _children[CHILD_COUNT]; AbstractHeightfieldNodeRenderer* _renderer; }; /// Base class for heightfield node rendering. class AbstractHeightfieldNodeRenderer { public: virtual ~AbstractHeightfieldNodeRenderer(); }; /// A heightfield represented as a spanner. class Heightfield : public Transformable { Q_OBJECT Q_PROPERTY(float aspectY MEMBER _aspectY WRITE setAspectY NOTIFY aspectYChanged) Q_PROPERTY(float aspectZ MEMBER _aspectZ WRITE setAspectZ NOTIFY aspectZChanged) Q_PROPERTY(HeightfieldHeightPointer height MEMBER _height WRITE setHeight NOTIFY heightChanged STORED false) Q_PROPERTY(HeightfieldColorPointer color MEMBER _color WRITE setColor NOTIFY colorChanged STORED false) Q_PROPERTY(HeightfieldMaterialPointer material MEMBER _material WRITE setMaterial NOTIFY materialChanged STORED false DESIGNABLE false) Q_PROPERTY(HeightfieldStackPointer stack MEMBER _stack WRITE setStack NOTIFY stackChanged STORED false DESIGNABLE false) public: Q_INVOKABLE Heightfield(); void setAspectY(float aspectY); float getAspectY() const { return _aspectY; } void setAspectZ(float aspectZ); float getAspectZ() const { return _aspectZ; } void setHeight(const HeightfieldHeightPointer& height); const HeightfieldHeightPointer& getHeight() const { return _height; } void setColor(const HeightfieldColorPointer& color); const HeightfieldColorPointer& getColor() const { return _color; } void setMaterial(const HeightfieldMaterialPointer& material); const HeightfieldMaterialPointer& getMaterial() const { return _material; } void setStack(const HeightfieldStackPointer& stack); const HeightfieldStackPointer& getStack() const { return _stack; } void setRoot(const HeightfieldNodePointer& root) { _root = root; } const HeightfieldNodePointer& getRoot() const { return _root; } MetavoxelLOD transformLOD(const MetavoxelLOD& lod) const; virtual SharedObject* clone(bool withID = false, SharedObject* target = NULL) const; virtual bool isHeightfield() const; virtual float getHeight(const glm::vec3& location) const; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; virtual Spanner* paintMaterial(const glm::vec3& position, float radius, const SharedObjectPointer& material, const QColor& color); virtual Spanner* paintHeight(const glm::vec3& position, float radius, float height); virtual Spanner* setMaterial(const SharedObjectPointer& spanner, const SharedObjectPointer& material, const QColor& color); virtual bool hasOwnColors() const; virtual bool hasOwnMaterials() const; virtual QRgb getColorAt(const glm::vec3& point); virtual int getMaterialAt(const glm::vec3& point); virtual QVector& getMaterials(); virtual bool contains(const glm::vec3& point); virtual bool intersects(const glm::vec3& start, const glm::vec3& end, float& distance, glm::vec3& normal); virtual void writeExtra(Bitstream& out) const; virtual void readExtra(Bitstream& in); virtual void writeExtraDelta(Bitstream& out, const SharedObject* reference) const; virtual void readExtraDelta(Bitstream& in, const SharedObject* reference); virtual void maybeWriteSubdivision(Bitstream& out); virtual SharedObject* readSubdivision(Bitstream& in); signals: void aspectYChanged(float aspectY); void aspectZChanged(float aspectZ); void heightChanged(const HeightfieldHeightPointer& height); void colorChanged(const HeightfieldColorPointer& color); void materialChanged(const HeightfieldMaterialPointer& material); void stackChanged(const HeightfieldStackPointer& stack); protected: virtual QByteArray getRendererClassName() const; private slots: void updateBounds(); void updateRoot(); private: Heightfield* prepareEdit(float minimumValue, float maximumValue, float& normalizeScale, float& normalizeOffset); float _aspectY; float _aspectZ; HeightfieldHeightPointer _height; HeightfieldColorPointer _color; HeightfieldMaterialPointer _material; HeightfieldStackPointer _stack; HeightfieldNodePointer _root; }; #endif // hifi_Spanner_h