// // Circle3DOverlay.cpp // interface/src/ui/overlays // // 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 // #include "Circle3DOverlay.h" #include #include #include QString const Circle3DOverlay::TYPE = "circle3d"; Circle3DOverlay::Circle3DOverlay() {} Circle3DOverlay::Circle3DOverlay(const Circle3DOverlay* circle3DOverlay) : Planar3DOverlay(circle3DOverlay), _startAt(circle3DOverlay->_startAt), _endAt(circle3DOverlay->_endAt), _outerRadius(circle3DOverlay->_outerRadius), _innerRadius(circle3DOverlay->_innerRadius), _innerStartColor(circle3DOverlay->_innerStartColor), _innerEndColor(circle3DOverlay->_innerEndColor), _outerStartColor(circle3DOverlay->_outerStartColor), _outerEndColor(circle3DOverlay->_outerEndColor), _innerStartAlpha(circle3DOverlay->_innerStartAlpha), _innerEndAlpha(circle3DOverlay->_innerEndAlpha), _outerStartAlpha(circle3DOverlay->_outerStartAlpha), _outerEndAlpha(circle3DOverlay->_outerEndAlpha), _hasTickMarks(circle3DOverlay->_hasTickMarks), _majorTickMarksAngle(circle3DOverlay->_majorTickMarksAngle), _minorTickMarksAngle(circle3DOverlay->_minorTickMarksAngle), _majorTickMarksLength(circle3DOverlay->_majorTickMarksLength), _minorTickMarksLength(circle3DOverlay->_minorTickMarksLength), _majorTickMarksColor(circle3DOverlay->_majorTickMarksColor), _minorTickMarksColor(circle3DOverlay->_minorTickMarksColor) { } Circle3DOverlay::~Circle3DOverlay() { auto geometryCache = DependencyManager::get(); if (geometryCache) { if (_quadVerticesID) { geometryCache->releaseID(_quadVerticesID); } if (_lineVerticesID) { geometryCache->releaseID(_lineVerticesID); } if (_majorTicksVerticesID) { geometryCache->releaseID(_majorTicksVerticesID); } if (_minorTicksVerticesID) { geometryCache->releaseID(_minorTicksVerticesID); } } } void Circle3DOverlay::render(RenderArgs* args) { if (!_renderVisible) { return; // do nothing if we're not visible } float alpha = getAlpha(); if (alpha == 0.0f) { return; // do nothing if our alpha is 0, we're not visible } bool geometryChanged = _dirty; _dirty = false; const float FULL_CIRCLE = 360.0f; const float SLICES = 180.0f; // The amount of segment to create the circle const float SLICE_ANGLE = FULL_CIRCLE / SLICES; const float MAX_COLOR = 255.0f; auto geometryCache = DependencyManager::get(); Q_ASSERT(args->_batch); auto& batch = *args->_batch; DependencyManager::get()->bindSimpleProgram(batch, false, isTransparent(), false, !getIsSolid(), true); batch.setModelTransform(getRenderTransform()); // for our overlay, is solid means we draw a ring between the inner and outer radius of the circle, otherwise // we just draw a line... if (getIsSolid()) { if (!_quadVerticesID) { _quadVerticesID = geometryCache->allocateID(); } if (geometryChanged) { QVector points; QVector colors; float pulseLevel = updatePulse(); vec4 pulseModifier = vec4(1); if (_alphaPulse != 0.0f) { pulseModifier.a = (_alphaPulse >= 0.0f) ? pulseLevel : (1.0f - pulseLevel); } if (_colorPulse != 0.0f) { float pulseValue = (_colorPulse >= 0.0f) ? pulseLevel : (1.0f - pulseLevel); pulseModifier = vec4(vec3(pulseValue), pulseModifier.a); } vec4 innerStartColor = vec4(toGlm(_innerStartColor), _innerStartAlpha) * pulseModifier; vec4 outerStartColor = vec4(toGlm(_outerStartColor), _outerStartAlpha) * pulseModifier; vec4 innerEndColor = vec4(toGlm(_innerEndColor), _innerEndAlpha) * pulseModifier; vec4 outerEndColor = vec4(toGlm(_outerEndColor), _outerEndAlpha) * pulseModifier; if (_innerRadius <= 0) { _solidPrimitive = gpu::TRIANGLE_FAN; points << vec2(); colors << innerStartColor; for (float angle = _startAt; angle <= _endAt; angle += SLICE_ANGLE) { float range = (angle - _startAt) / (_endAt - _startAt); float angleRadians = glm::radians(angle); points << glm::vec2(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); colors << glm::mix(outerStartColor, outerEndColor, range); } } else { _solidPrimitive = gpu::TRIANGLE_STRIP; for (float angle = _startAt; angle <= _endAt; angle += SLICE_ANGLE) { float range = (angle - _startAt) / (_endAt - _startAt); float angleRadians = glm::radians(angle); points << glm::vec2(cosf(angleRadians) * _innerRadius, sinf(angleRadians) * _innerRadius); colors << glm::mix(innerStartColor, innerEndColor, range); points << glm::vec2(cosf(angleRadians) * _outerRadius, sinf(angleRadians) * _outerRadius); colors << glm::mix(outerStartColor, outerEndColor, range); } } geometryCache->updateVertices(_quadVerticesID, points, colors); } geometryCache->renderVertices(batch, _solidPrimitive, _quadVerticesID); } else { if (!_lineVerticesID) { _lineVerticesID = geometryCache->allocateID(); } if (geometryChanged) { QVector points; float angle = _startAt; float angleInRadians = glm::radians(angle); glm::vec2 firstPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << firstPoint; while (angle < _endAt) { angle += SLICE_ANGLE; angleInRadians = glm::radians(angle); glm::vec2 thisPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << thisPoint; if (getIsDashedLine()) { angle += SLICE_ANGLE / 2.0f; // short gap angleInRadians = glm::radians(angle); glm::vec2 dashStartPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << dashStartPoint; } } // get the last slice portion.... angle = _endAt; angleInRadians = glm::radians(angle); glm::vec2 lastPoint(cosf(angleInRadians) * _outerRadius, sinf(angleInRadians) * _outerRadius); points << lastPoint; geometryCache->updateVertices(_lineVerticesID, points, vec4(toGlm(getColor()), getAlpha())); } if (getIsDashedLine()) { geometryCache->renderVertices(batch, gpu::LINES, _lineVerticesID); } else { geometryCache->renderVertices(batch, gpu::LINE_STRIP, _lineVerticesID); } } // draw our tick marks // for our overlay, is solid means we draw a ring between the inner and outer radius of the circle, otherwise // we just draw a line... if (getHasTickMarks()) { if (!_majorTicksVerticesID) { _majorTicksVerticesID = geometryCache->allocateID(); } if (!_minorTicksVerticesID) { _minorTicksVerticesID = geometryCache->allocateID(); } if (geometryChanged) { QVector majorPoints; QVector minorPoints; // draw our major tick marks if (getMajorTickMarksAngle() > 0.0f && getMajorTickMarksLength() != 0.0f) { float tickMarkAngle = getMajorTickMarksAngle(); float angle = _startAt - fmodf(_startAt, tickMarkAngle) + tickMarkAngle; float angleInRadians = glm::radians(angle); float tickMarkLength = getMajorTickMarksLength(); float startRadius = (tickMarkLength > 0.0f) ? _innerRadius : _outerRadius; float endRadius = startRadius + tickMarkLength; while (angle <= _endAt) { angleInRadians = glm::radians(angle); glm::vec2 thisPointA(cosf(angleInRadians) * startRadius, sinf(angleInRadians) * startRadius); glm::vec2 thisPointB(cosf(angleInRadians) * endRadius, sinf(angleInRadians) * endRadius); majorPoints << thisPointA << thisPointB; angle += tickMarkAngle; } } // draw our minor tick marks if (getMinorTickMarksAngle() > 0.0f && getMinorTickMarksLength() != 0.0f) { float tickMarkAngle = getMinorTickMarksAngle(); float angle = _startAt - fmodf(_startAt, tickMarkAngle) + tickMarkAngle; float angleInRadians = glm::radians(angle); float tickMarkLength = getMinorTickMarksLength(); float startRadius = (tickMarkLength > 0.0f) ? _innerRadius : _outerRadius; float endRadius = startRadius + tickMarkLength; while (angle <= _endAt) { angleInRadians = glm::radians(angle); glm::vec2 thisPointA(cosf(angleInRadians) * startRadius, sinf(angleInRadians) * startRadius); glm::vec2 thisPointB(cosf(angleInRadians) * endRadius, sinf(angleInRadians) * endRadius); minorPoints << thisPointA << thisPointB; angle += tickMarkAngle; } } xColor majorColorX = getMajorTickMarksColor(); glm::vec4 majorColor(majorColorX.red / MAX_COLOR, majorColorX.green / MAX_COLOR, majorColorX.blue / MAX_COLOR, alpha); geometryCache->updateVertices(_majorTicksVerticesID, majorPoints, majorColor); xColor minorColorX = getMinorTickMarksColor(); glm::vec4 minorColor(minorColorX.red / MAX_COLOR, minorColorX.green / MAX_COLOR, minorColorX.blue / MAX_COLOR, alpha); geometryCache->updateVertices(_minorTicksVerticesID, minorPoints, minorColor); } geometryCache->renderVertices(batch, gpu::LINES, _majorTicksVerticesID); geometryCache->renderVertices(batch, gpu::LINES, _minorTicksVerticesID); } } const render::ShapeKey Circle3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder().withoutCullFace(); if (isTransparent()) { builder.withTranslucent(); } if (!getIsSolid()) { builder.withUnlit().withDepthBias(); } return builder.build(); } template T fromVariant(const QVariant& v, bool& valid) { valid = v.isValid(); return qvariant_cast(v); } template<> xColor fromVariant(const QVariant& v, bool& valid) { return xColorFromVariant(v, valid); } template bool updateIfValid(const QVariantMap& properties, const char* key, T& output) { bool valid; T result = fromVariant(properties[key], valid); if (!valid) { return false; } // Don't signal updates if the value was already set if (result == output) { return false; } output = result; return true; } // Multicast, many outputs template bool updateIfValid(const QVariantMap& properties, const char* key, std::initializer_list> outputs) { bool valid; T value = fromVariant(properties[key], valid); if (!valid) { return false; } bool updated = false; for (T& output : outputs) { if (output != value) { output = value; updated = true; } } return updated; } // Multicast, multiple possible inputs, in order of preference template bool updateIfValid(const QVariantMap& properties, const std::initializer_list keys, T& output) { for (const char* key : keys) { if (updateIfValid(properties, key, output)) { return true; } } return false; } void Circle3DOverlay::setProperties(const QVariantMap& properties) { Planar3DOverlay::setProperties(properties); _dirty |= updateIfValid(properties, "alpha", { _innerStartAlpha, _innerEndAlpha, _outerStartAlpha, _outerEndAlpha }); _dirty |= updateIfValid(properties, "Alpha", { _innerStartAlpha, _innerEndAlpha, _outerStartAlpha, _outerEndAlpha }); _dirty |= updateIfValid(properties, "startAlpha", { _innerStartAlpha, _outerStartAlpha }); _dirty |= updateIfValid(properties, "endAlpha", { _innerEndAlpha, _outerEndAlpha }); _dirty |= updateIfValid(properties, "innerAlpha", { _innerStartAlpha, _innerEndAlpha }); _dirty |= updateIfValid(properties, "outerAlpha", { _outerStartAlpha, _outerEndAlpha }); _dirty |= updateIfValid(properties, "innerStartAlpha", _innerStartAlpha); _dirty |= updateIfValid(properties, "innerEndAlpha", _innerEndAlpha); _dirty |= updateIfValid(properties, "outerStartAlpha", _outerStartAlpha); _dirty |= updateIfValid(properties, "outerEndAlpha", _outerEndAlpha); _dirty |= updateIfValid(properties, "color", { _innerStartColor, _innerEndColor, _outerStartColor, _outerEndColor }); _dirty |= updateIfValid(properties, "startColor", { _innerStartColor, _outerStartColor } ); _dirty |= updateIfValid(properties, "endColor", { _innerEndColor, _outerEndColor } ); _dirty |= updateIfValid(properties, "innerColor", { _innerStartColor, _innerEndColor } ); _dirty |= updateIfValid(properties, "outerColor", { _outerStartColor, _outerEndColor } ); _dirty |= updateIfValid(properties, "innerStartColor", _innerStartColor); _dirty |= updateIfValid(properties, "innerEndColor", _innerEndColor); _dirty |= updateIfValid(properties, "outerStartColor", _outerStartColor); _dirty |= updateIfValid(properties, "outerEndColor", _outerEndColor); _dirty |= updateIfValid(properties, "startAt", _startAt); _dirty |= updateIfValid(properties, "endAt", _endAt); _dirty |= updateIfValid(properties, { "radius", "outerRadius" }, _outerRadius); _dirty |= updateIfValid(properties, "innerRadius", _innerRadius); _dirty |= updateIfValid(properties, "hasTickMarks", _hasTickMarks); _dirty |= updateIfValid(properties, "majorTickMarksAngle", _majorTickMarksAngle); _dirty |= updateIfValid(properties, "minorTickMarksAngle", _minorTickMarksAngle); _dirty |= updateIfValid(properties, "majorTickMarksLength", _majorTickMarksLength); _dirty |= updateIfValid(properties, "minorTickMarksLength", _minorTickMarksLength); _dirty |= updateIfValid(properties, "majorTickMarksColor", _majorTickMarksColor); _dirty |= updateIfValid(properties, "minorTickMarksColor", _minorTickMarksColor); if (_innerStartAlpha < 1.0f || _innerEndAlpha < 1.0f || _outerStartAlpha < 1.0f || _outerEndAlpha < 1.0f) { // Force the alpha to 0.5, since we'll ignore it in the presence of these other values, but we need // it to be non-1 in order to get the right pipeline and non-0 in order to render at all. _alpha = 0.5f; } } // Overlay's color and alpha properties are overridden. And the dimensions property is not used. /**jsdoc * These are the properties of a circle3d {@link Overlays.OverlayType|OverlayType}. * @typedef {object} Overlays.Circle3DProperties * * @property {string} type=circle3d - Has the value "circle3d". Read-only. * @property {number} pulseMax=0 - The maximum value of the pulse multiplier. * @property {number} pulseMin=0 - The minimum value of the pulse multiplier. * @property {number} pulsePeriod=1 - The duration of the color and alpha pulse, in seconds. A pulse multiplier value goes from * pulseMin to pulseMax, then pulseMax to pulseMin in one period. * @property {number} alphaPulse=0 - If non-zero, the alpha of the overlay is pulsed: the alpha value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {number} colorPulse=0 - If non-zero, the color of the overlay is pulsed: the color value is multiplied by the * current pulse multiplier value each frame. If > 0 the pulse multiplier is applied in phase with the pulse period; if < 0 * the pulse multiplier is applied out of phase with the pulse period. (The magnitude of the property isn't otherwise * used.) * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. * @property {string} anchor="" - If set to "MyAvatar" then the overlay is attached to your avatar, moving and * rotating as you move your avatar. * * @property {string} name="" - A friendly name for the overlay. * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. * @property {Quat} rotation - The orientation of the overlay. Synonym: orientation. * @property {Quat} localRotation - The orientation of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as rotation. * @property {boolean} isSolid=false - Synonyms: solid, isFilled, and filled * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. * @property {boolean} ignoreRayIntersection=false - If true, * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. * @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to. * @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if * parentID is an avatar skeleton. A value of 65535 means "no joint". * * @property {Vec2} dimensions=1,1 - The dimensions of the overlay. Synonyms: scale, size. * Not used. * * @property {number} startAt=0 - The counter-clockwise angle from the overlay's x-axis that drawing starts at, in degrees. * @property {number} endAt=360 - The counter-clockwise angle from the overlay's x-axis that drawing ends at, in degrees. * @property {number} outerRadius=1 - The outer radius of the overlay, in meters. Synonym: radius. * @property {number} innerRadius=0 - The inner radius of the overlay, in meters. * @property {Color} color=255,255,255 - The color of the overlay. Setting this value also sets the values of * innerStartColor, innerEndColor, outerStartColor, and outerEndColor. * @property {Color} startColor - Sets the values of innerStartColor and outerStartColor. * Write-only. * @property {Color} endColor - Sets the values of innerEndColor and outerEndColor. * Write-only. * @property {Color} innerColor - Sets the values of innerStartColor and innerEndColor. * Write-only. * @property {Color} outerColor - Sets the values of outerStartColor and outerEndColor. * Write-only. * @property {Color} innerStartcolor - The color at the inner start point of the overlay. Write-only. * @property {Color} innerEndColor - The color at the inner end point of the overlay. Write-only. * @property {Color} outerStartColor - The color at the outer start point of the overlay. Write-only. * @property {Color} outerEndColor - The color at the outer end point of the overlay. Write-only. * @property {number} alpha=0.5 - The opacity of the overlay, 0.0 - 1.0. Setting this value also sets * the values of innerStartAlpha, innerEndAlpha, outerStartAlpha, and * outerEndAlpha. Synonym: Alpha; write-only. * @property {number} startAlpha - Sets the values of innerStartAlpha and outerStartAlpha. * Write-only. * @property {number} endAlpha - Sets the values of innerEndAlpha and outerEndAlpha. * Write-only. * @property {number} innerAlpha - Sets the values of innerStartAlpha and innerEndAlpha. * Write-only. * @property {number} outerAlpha - Sets the values of outerStartAlpha and outerEndAlpha. * Write-only. * @property {number} innerStartAlpha=0 - The alpha at the inner start point of the overlay. Write-only. * @property {number} innerEndAlpha=0 - The alpha at the inner end point of the overlay. Write-only. * @property {number} outerStartAlpha=0 - The alpha at the outer start point of the overlay. Write-only. * @property {number} outerEndAlpha=0 - The alpha at the outer end point of the overlay. Write-only. * @property {boolean} hasTickMarks=false - If true, tick marks are drawn. * @property {number} majorTickMarksAngle=0 - The angle between major tick marks, in degrees. * @property {number} minorTickMarksAngle=0 - The angle between minor tick marks, in degrees. * @property {number} majorTickMarksLength=0 - The length of the major tick marks, in meters. A positive value draws tick marks * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. * @property {number} minorTickMarksLength=0 - The length of the minor tick marks, in meters. A positive value draws tick marks * outwards from the inner radius; a negative value draws tick marks inwards from the outer radius. * @property {Color} majorTickMarksColor=0,0,0 - The color of the major tick marks. * @property {Color} minorTickMarksColor=0,0,0 - The color of the minor tick marks. */ QVariant Circle3DOverlay::getProperty(const QString& property) { if (property == "startAt") { return _startAt; } if (property == "endAt") { return _endAt; } if (property == "radius") { return _outerRadius; } if (property == "outerRadius") { return _outerRadius; } if (property == "innerRadius") { return _innerRadius; } if (property == "innerStartColor") { return xColorToVariant(_innerStartColor); } if (property == "innerEndColor") { return xColorToVariant(_innerEndColor); } if (property == "outerStartColor") { return xColorToVariant(_outerStartColor); } if (property == "outerEndColor") { return xColorToVariant(_outerEndColor); } if (property == "innerStartAlpha") { return _innerStartAlpha; } if (property == "innerEndAlpha") { return _innerEndAlpha; } if (property == "outerStartAlpha") { return _outerStartAlpha; } if (property == "outerEndAlpha") { return _outerEndAlpha; } if (property == "hasTickMarks") { return _hasTickMarks; } if (property == "majorTickMarksAngle") { return _majorTickMarksAngle; } if (property == "minorTickMarksAngle") { return _minorTickMarksAngle; } if (property == "majorTickMarksLength") { return _majorTickMarksLength; } if (property == "minorTickMarksLength") { return _minorTickMarksLength; } if (property == "majorTickMarksColor") { return xColorToVariant(_majorTickMarksColor); } if (property == "minorTickMarksColor") { return xColorToVariant(_minorTickMarksColor); } return Planar3DOverlay::getProperty(property); } bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { // Scale the dimensions by the diameter glm::vec2 dimensions = getOuterRadius() * 2.0f * getDimensions(); bool intersects = findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), dimensions, distance); if (intersects) { glm::vec3 hitPosition = origin + (distance * direction); glm::vec3 localHitPosition = glm::inverse(getWorldOrientation()) * (hitPosition - getWorldPosition()); localHitPosition.x /= getDimensions().x; localHitPosition.y /= getDimensions().y; float distanceToHit = glm::length(localHitPosition); intersects = getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius(); } return intersects; } Circle3DOverlay* Circle3DOverlay::createClone() const { return new Circle3DOverlay(this); }