/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2018 * * * * Permission is hereby granted, free of charge, to any person obtaining a copy of this * * software and associated documentation files (the "Software"), to deal in the Software * * without restriction, including without limitation the rights to use, copy, modify, * * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * * permit persons to whom the Software is furnished to do so, subject to the following * * conditions: * * * * The above copyright notice and this permission notice shall be included in all copies * * or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * ****************************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { constexpr const char* KeyBody = "Body"; constexpr const char* KeyFrame = "Frame"; // const char* KeyColor = "RGB"; constexpr const char* KeyInstrument = "Instrument"; constexpr const char* KeyInstrumentName = "Name"; constexpr const char* KeyInstrumentAberration = "Aberration"; constexpr const char* KeyPotentialTargets = "PotentialTargets"; constexpr const char* KeyFrameConversions = "FrameConversions"; constexpr const char* KeyBoundsSimplification = "SimplifyBounds"; const int InterpolationSteps = 5; const double Epsilon = 1e-4; static const openspace::properties::Property::PropertyInfo LineWidthInfo = { "LineWidth", "Line Width", "This value determines width of the lines connecting the instrument to the " "corners of the field of view." }; static const openspace::properties::Property::PropertyInfo DrawSolidInfo = { "SolidDraw", "Solid Draw", "This value determines whether the field of view should be rendered as a solid " "or as lines only." }; static const openspace::properties::Property::PropertyInfo StandoffDistanceInfo = { "StandOffDistance", "Standoff Distance Factor", "This value determines the standoff distance factor which influences the " "distance of the plane to the focus object. If this value is '1', the field of " "view will be rendered exactly on the surface of, for example, a planet. With a " "value of smaller than 1, the field of view will hover of ther surface, thus " "making it more visible." }; static const openspace::properties::Property::PropertyInfo DefaultStartColorInfo = { "Colors.DefaultStart", "Start of default color", "This value determines the color of the field of view frustum close to the " "instrument. The final colors are interpolated between this value and the end " "color." }; static const openspace::properties::Property::PropertyInfo DefaultEndColorInfo = { "Colors.DefaultEnd", "End of default color", "This value determines the color of the field of view frustum close to the " "target. The final colors are interpolated between this value and the start " "color." }; static const openspace::properties::Property::PropertyInfo ActiveColorInfo = { "Colors.Active", "Active Color", "This value determines the color that is used when the instrument's field of " "view is active." }; static const openspace::properties::Property::PropertyInfo TargetInFovInfo = { "Colors.TargetInFieldOfView", "Target in field-of-view Color", "This value determines the color that is used if the target is inside the field " "of view of the instrument but the instrument is not yet active." }; static const openspace::properties::Property::PropertyInfo IntersectionStartInfo = { "Colors.IntersectionStart", "Start of the intersection", "This value determines the color that is used close to the instrument if one of " "the field of view corners is intersecting the target object. The final color is " "retrieved by interpolating between this color and the intersection end color." }; static const openspace::properties::Property::PropertyInfo IntersectionEndInfo = { "Colors.IntersectionEnd", "End of the intersection", "This value determines the color that is used close to the target if one of the " "field of view corners is intersecting the target object. The final color is " "retrieved by interpolating between this color and the intersection begin color." }; static const openspace::properties::Property::PropertyInfo SquareColorInfo = { "Colors.Square", "Orthogonal Square", "This value determines the color that is used for the field of view square in " "the case that there is no intersection and that the instrument is not currently " "active." }; } // namespace namespace openspace { documentation::Documentation RenderableFov::Documentation() { using namespace documentation; return { "RenderableFieldOfView", "newhorizons_renderable_fieldofview", { { KeyBody, new StringVerifier, Optional::No, "The SPICE name of the source body for which the field of view should be " "rendered." }, { KeyFrame, new StringVerifier, Optional::No, "The SPICE name of the source body's frame in which the field of view " "should be rendered." }, { KeyInstrument, new TableVerifier({ { KeyInstrumentName, new StringVerifier, Optional::No, "The SPICE name of the instrument that is rendered" }, { KeyInstrumentAberration, new StringInListVerifier({ // Taken from SpiceManager::AberrationCorrection "NONE", "LT", "LT+S", "CN", "CN+S", "XLT", "XLT+S", "XCN", "XCN+S" }), Optional::Yes, "The aberration correction that is used for this field of view. " "The default is 'NONE'." } }), Optional::No, "A table describing the instrument whose field of view should be " "rendered." }, { KeyPotentialTargets, new StringListVerifier, Optional::No, "A list of potential targets (specified as SPICE names) that the field " "of view should be tested against." }, { KeyFrameConversions, new TableVerifier({ { DocumentationEntry::Wildcard, new StringVerifier, Optional::No } }), Optional::Yes, "A list of frame conversions that should be registered with the " "SpiceManager." }, { LineWidthInfo.identifier, new DoubleVerifier, Optional::Yes, LineWidthInfo.description }, { StandoffDistanceInfo.identifier, new DoubleVerifier, Optional::Yes, StandoffDistanceInfo.description }, { KeyBoundsSimplification, new BoolVerifier, Optional::Yes, "If this value is set to 'true' the field-of-views bounds values will be " "simplified on load. Bound vectors will be removed if they are the " "strict linear interpolation between the two neighboring vectors. This " "value is disabled on default." } } }; } RenderableFov::RenderableFov(const ghoul::Dictionary& dictionary) : Renderable(dictionary) , _lineWidth(LineWidthInfo, 1.f, 1.f, 20.f) , _drawSolid(DrawSolidInfo, false) , _standOffDistance(StandoffDistanceInfo, 0.9999, 0.99, 1.0, 0.000001) , _programObject(nullptr) , _drawFOV(false) , _colors({ { DefaultStartColorInfo, glm::vec4(0.4f) }, { DefaultEndColorInfo, glm::vec4(0.85f, 0.85f, 0.85f, 1.f) }, { ActiveColorInfo, glm::vec4(0.f, 1.f, 0.f, 1.f) }, { TargetInFovInfo, glm::vec4(0.f, 0.5f, 0.7f, 1.f) }, { IntersectionStartInfo, glm::vec4(1.f, 0.89f, 0.f, 1.f) }, { IntersectionEndInfo, glm::vec4(1.f, 0.29f, 0.f, 1.f) }, { SquareColorInfo, glm::vec4(0.85f, 0.85f, 0.85f, 1.f) } }) { documentation::testSpecificationAndThrow( Documentation(), dictionary, "RenderableFov" ); _instrument.spacecraft = dictionary.value(KeyBody); _instrument.referenceFrame = dictionary.value(KeyFrame); _instrument.name = dictionary.value( std::string(KeyInstrument) + "." + KeyInstrumentName ); std::string ia = std::string(KeyInstrument) + "." + KeyInstrumentAberration; if (dictionary.hasKey(ia)) { std::string ac = dictionary.value(ia); _instrument.aberrationCorrection = SpiceManager::AberrationCorrection(ac); } ghoul::Dictionary pt = dictionary.value(KeyPotentialTargets); _instrument.potentialTargets.reserve(pt.size()); for (size_t i = 1; i <= pt.size(); ++i) { std::string target = pt.value(std::to_string(i)); _instrument.potentialTargets.push_back(target); } if (dictionary.hasKey(KeyFrameConversions)) { ghoul::Dictionary fc = dictionary.value(KeyFrameConversions); for (const std::string& key : fc.keys()) { openspace::SpiceManager::ref().addFrame( key, fc.value(key) ); } } if (dictionary.hasKey(LineWidthInfo.identifier)) { _lineWidth = static_cast(dictionary.value( LineWidthInfo.identifier )); } if (dictionary.hasKey(StandoffDistanceInfo.identifier)) { _standOffDistance = static_cast(dictionary.value( StandoffDistanceInfo.identifier )); } if (dictionary.hasKey(KeyBoundsSimplification)) { _simplifyBounds = dictionary.value(KeyBoundsSimplification); } addProperty(_lineWidth); addProperty(_drawSolid); addProperty(_standOffDistance); addProperty(_colors.defaultStart); addProperty(_colors.defaultEnd); addProperty(_colors.active); addProperty(_colors.targetInFieldOfView); addProperty(_colors.intersectionStart); addProperty(_colors.intersectionEnd); addProperty(_colors.square); } void RenderableFov::initializeGL() { RenderEngine& renderEngine = OsEng.renderEngine(); _programObject = renderEngine.buildRenderProgram( "FovProgram", absPath("${MODULE_SPACECRAFTINSTRUMENTS}/shaders/fov_vs.glsl"), absPath("${MODULE_SPACECRAFTINSTRUMENTS}/shaders/fov_fs.glsl") ); _uniformCache.modelViewProjection = _programObject->uniformLocation( "modelViewProjectionTransform" ); _uniformCache.defaultColorStart = _programObject->uniformLocation( "defaultColorStart" ); _uniformCache.defaultColorEnd = _programObject->uniformLocation("defaultColorEnd"); _uniformCache.activeColor = _programObject->uniformLocation("activeColor"); _uniformCache.targetInFieldOfViewColor = _programObject->uniformLocation( "targetInFieldOfViewColor" ); _uniformCache.intersectionStartColor = _programObject->uniformLocation( "intersectionStartColor" ); _uniformCache.intersectionEndColor = _programObject->uniformLocation( "intersectionEndColor" ); _uniformCache.squareColor = _programObject->uniformLocation("squareColor"); _uniformCache.interpolation = _programObject->uniformLocation("interpolation"); // Fetch information about the specific instrument SpiceManager::FieldOfViewResult res = SpiceManager::ref().fieldOfView( _instrument.name ); // Right now, we can only deal with rectangles or polygons. Circles and ellipses only // return one or two bound vectors that have to used to construct an approximation const bool supportedShape = res.shape == SpiceManager::FieldOfViewResult::Shape::Polygon || res.shape == SpiceManager::FieldOfViewResult::Shape::Rectangle; if (!supportedShape) { throw ghoul::RuntimeError( "'" + _instrument.name + "' has unsupported shape", "RenderableFov" ); } if (_simplifyBounds) { const size_t sizeBefore = res.bounds.size(); for (size_t i = 1; i < res.bounds.size() - 1; ++i) { const glm::dvec3& prev = res.bounds[i - 1]; const glm::dvec3& curr = res.bounds[i]; const glm::dvec3& next = res.bounds[i + 1]; const double area = glm::length( glm::cross((curr - prev), (next - prev)) ); const bool isCollinear = area < Epsilon; if (isCollinear) { res.bounds.erase(res.bounds.begin() + i); // Need to subtract one as we have shortened the array and the next // item is now on the current position --i; } } const size_t sizeAfter = res.bounds.size(); LINFOC(_instrument.name, "Simplified from " << sizeBefore << " to " << sizeAfter); } _instrument.bounds = std::move(res.bounds); _instrument.boresight = std::move(res.boresightVector); // These vectors hold the data that we will want to render. We need to subdivide the // range as an intersection test between the corners and the object might fail for // sufficiently small objects: // // x---------------------x Field of view // | | // | | // | ***** | // | * * | // x-----*-------*-------x // * * // ***** Target object // // The orthogonal plane shows the footprint of the instrument on the surface of the // object. Since it should follow the potential curvature, we might need to // interpolate, hence the extra storage _orthogonalPlane.data.resize(_instrument.bounds.size() * InterpolationSteps); // The field of views are originating from the space craft, so the location of the // space craft has to be repeated for each vertex, hence the * 2. On the other hand, // the field of view does **not** need to be interpolated _fieldOfViewBounds.data.resize(2 * _instrument.bounds.size()); // Field of view boundaries glGenVertexArrays(1, &_fieldOfViewBounds.vao); glBindVertexArray(_fieldOfViewBounds.vao); glGenBuffers(1, &_fieldOfViewBounds.vbo); glBindBuffer(GL_ARRAY_BUFFER, _fieldOfViewBounds.vbo); glBufferData( GL_ARRAY_BUFFER, _fieldOfViewBounds.data.size() * sizeof(RenderInformation::VBOData), nullptr, GL_STREAM_DRAW ); glEnableVertexAttribArray(0); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, sizeof(RenderInformation::VBOData), nullptr ); glEnableVertexAttribArray(1); glVertexAttribIPointer( 1, 1, GL_INT, sizeof(RenderInformation::VBOData), reinterpret_cast(offsetof(RenderInformation::VBOData, color)) ); // Orthogonal Plane glGenVertexArrays(1, &_orthogonalPlane.vao); glGenBuffers(1, &_orthogonalPlane.vbo); glBindVertexArray(_orthogonalPlane.vao); glBindBuffer(GL_ARRAY_BUFFER, _orthogonalPlane.vbo); glBufferData( GL_ARRAY_BUFFER, _orthogonalPlane.data.size() * sizeof(RenderInformation::VBOData), nullptr, GL_STREAM_DRAW ); glEnableVertexAttribArray(0); glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, sizeof(RenderInformation::VBOData), nullptr ); glEnableVertexAttribArray(1); glVertexAttribIPointer( 1, 1, GL_INT, sizeof(RenderInformation::VBOData), reinterpret_cast(offsetof(RenderInformation::VBOData, color)) ); glBindVertexArray(0); } void RenderableFov::deinitializeGL() { OsEng.renderEngine().removeRenderProgram(_programObject); _programObject = nullptr; glDeleteBuffers(1, &_orthogonalPlane.vbo); glDeleteVertexArrays(1, &_orthogonalPlane.vao); glDeleteBuffers(1, &_fieldOfViewBounds.vbo); glDeleteVertexArrays(1, &_fieldOfViewBounds.vao); } bool RenderableFov::isReady() const { return _programObject != nullptr && !_instrument.bounds.empty(); } // Orthogonal projection next to planets surface glm::dvec3 RenderableFov::orthogonalProjection(const glm::dvec3& vecFov, double time, const std::string& target) const { glm::dvec3 vecToTarget = SpiceManager::ref().targetPosition( target, _instrument.spacecraft, _instrument.referenceFrame, _instrument.aberrationCorrection, time ); glm::dvec3 fov = SpiceManager::ref().frameTransformationMatrix( _instrument.name, _instrument.referenceFrame, time ) * vecFov; glm::dvec3 p = glm::proj(vecToTarget, fov); return p * 1000.0; // km -> m } template double bisect(const glm::dvec3& p1, const glm::dvec3& p2, Func testFunction, const glm::dvec3& previousHalf = glm::dvec3(std::numeric_limits::max())) { const double Tolerance = 0.00000001; const glm::dvec3 half = glm::mix(p1, p2, 0.5); if (glm::distance(previousHalf, half) < Tolerance) { // The two points are so close to each other that we can stop return 0.5; } if (testFunction(half)) { return 0.5 + 0.5 * bisect(half, p2, testFunction, half); } else { return 0.5 * bisect(p1, half, testFunction, half); } } void RenderableFov::computeIntercepts(const UpdateData& data, const std::string& target, bool isInFov) { auto makeBodyFixedReferenceFrame = [&target](std::string ref) -> std::pair { bool convert = (ref.find("IAU_") == std::string::npos); if (convert) { return { SpiceManager::ref().frameFromBody(target), true }; } else { return { ref, false }; } }; //std::vector intersects(_instrument.bounds.size()); // First we fill the field-of-view bounds array by testing each bounds vector against // the object. We need to test it against the object (rather than using a fixed // distance) as the field of view rendering should stop at the surface and not // continue for (size_t i = 0; i < _instrument.bounds.size(); ++i) { const glm::dvec3& bound = _instrument.bounds[i]; RenderInformation::VBOData& first = _fieldOfViewBounds.data[2 * i]; RenderInformation::VBOData& second = _fieldOfViewBounds.data[2 * i + 1]; // Regardless of what happens next, the position of every second element is going // to be the same. Only the color attribute might change first = { { 0.f, 0.f, 0.f }, RenderInformation::VertexColorTypeDefaultStart }; if (!isInFov) { // If the target is not in the field of view, we don't need to perform any // surface intercepts glm::vec3 o = orthogonalProjection(bound, data.time.j2000Seconds(), target); second = { { o.x, o.y, o.z }, // This had a different color (0.4) before ---abock RenderInformation::VertexColorTypeDefaultEnd }; } else { // The target is in the field of view, but not the entire field of view has to // be filled by the target auto ref = makeBodyFixedReferenceFrame(_instrument.referenceFrame); SpiceManager::SurfaceInterceptResult r = SpiceManager::ref().surfaceIntercept( target, _instrument.spacecraft, _instrument.name, ref.first, _instrument.aberrationCorrection, data.time.j2000Seconds(), bound ); //intersects[i] = r.interceptFound; if (r.interceptFound) { // This point intersected the target first.color = RenderInformation::VertexColorTypeIntersectionStart; // If we had to convert the reference frame into a body-fixed frame, we // need to apply this change here: if (ref.second) { r.surfaceVector = SpiceManager::ref().frameTransformationMatrix( ref.first, _instrument.referenceFrame, data.time.j2000Seconds() ) * r.surfaceVector; } // Convert the KM scale that SPICE uses to meter glm::vec3 srfVec = r.surfaceVector * 1000.0; // Standoff distance, we would otherwise end up *exactly* on the surface srfVec *= _standOffDistance; second = { { srfVec.x, srfVec.y, srfVec.z }, RenderInformation::VertexColorTypeIntersectionEnd }; } else { // This point did not intersect the target though others did glm::vec3 o = orthogonalProjection( bound, data.time.j2000Seconds(), target ); second = { { o.x, o.y, o.z }, RenderInformation::VertexColorTypeInFieldOfView }; } } } // After finding the positions for the field of view boundaries, we can create the // vertices for the orthogonal plane as well, reusing the computations we performed // earlier // Each boundary in _instrument.bounds has 'InterpolationSteps' steps between auto indexForBounds = [](size_t idx) -> size_t { return idx * InterpolationSteps; }; //auto boundsForIndex = [](size_t bnds) -> size_t { // return bnds % InterpolationSteps; //}; auto copyFieldOfViewValues = [&](size_t iBound, size_t begin, size_t end) -> void { std::fill( _orthogonalPlane.data.begin() + begin, _orthogonalPlane.data.begin() + end, _fieldOfViewBounds.data[2 * iBound + 1] ); }; // An early out for when the target is not in field of view if (!isInFov) { for (size_t i = 0; i < _instrument.bounds.size(); ++i) { // If none of the points are able to intersect with the target, we can just // copy the values from the field-of-view boundary. So we take each second // item (the first one is (0,0,0)) and replicate it 'InterpolationSteps' times copyFieldOfViewValues(i, indexForBounds(i), indexForBounds(i + 1)); } } else { // At least one point will intersect for (size_t i = 0; i < _instrument.bounds.size(); ++i) { // Wrap around the array index to 0 const size_t j = (i == _instrument.bounds.size() - 1) ? 0 : i + 1; const glm::dvec3& iBound = _instrument.bounds[i]; const glm::dvec3& jBound = _instrument.bounds[j]; auto intercepts = [&](const glm::dvec3& probe) -> bool { return SpiceManager::ref().surfaceIntercept( target, _instrument.spacecraft, _instrument.name, makeBodyFixedReferenceFrame(_instrument.referenceFrame).first, _instrument.aberrationCorrection, data.time.j2000Seconds(), probe ).interceptFound; }; // Computes the intercept vector between the 'probe' and the target // the intercept vector is in meter and contains a standoff distance offset auto interceptVector = [&](const glm::dvec3& probe) -> glm::dvec3 { auto ref = makeBodyFixedReferenceFrame(_instrument.referenceFrame); SpiceManager::SurfaceInterceptResult r = SpiceManager::ref().surfaceIntercept( target, _instrument.spacecraft, _instrument.name, ref.first, _instrument.aberrationCorrection, data.time.j2000Seconds(), probe ); if (ref.second) { r.surfaceVector = SpiceManager::ref().frameTransformationMatrix( ref.first, _instrument.referenceFrame, data.time.j2000Seconds() ) * r.surfaceVector; } // Convert the KM scale that SPICE uses to meter // Standoff distance, we would otherwise end up *exactly* on the surface return r.surfaceVector * 1000.0 * _standOffDistance.value(); }; for (size_t m = 0; m < InterpolationSteps; ++m) { const double t = static_cast(m) / (InterpolationSteps); const glm::dvec3 tBound = glm::mix(iBound, jBound, t); if (intercepts(tBound)) { const glm::vec3 icpt = interceptVector(tBound); _orthogonalPlane.data[indexForBounds(i) + m] = { { icpt.x, icpt.y, icpt.z }, RenderInformation::VertexColorTypeSquare }; } else { const glm::vec3 o = orthogonalProjection( tBound, data.time.j2000Seconds(), target ); _orthogonalPlane.data[indexForBounds(i) + m] = { { o.x, o.y, o.z }, RenderInformation::VertexColorTypeSquare }; } } } } #ifdef DEBUG_THIS // At least one point will intersect for (size_t i = 0; i < _instrument.bounds.size(); ++i) { // Wrap around the array index to 0 const size_t j = (i == _instrument.bounds.size() - 1) ? 0 : i + 1; const glm::dvec3& iBound = _instrument.bounds[i]; const glm::dvec3& jBound = _instrument.bounds[j]; auto intercepts = [&](const glm::dvec3& probe) -> bool { return SpiceManager::ref().surfaceIntercept( target, _instrument.spacecraft, _instrument.name, makeBodyFixedReferenceFrame(_instrument.referenceFrame).first, _instrument.aberrationCorrection, data.time, probe ).interceptFound; }; static const uint8_t NoIntersect = 0b00; static const uint8_t ThisIntersect = 0b01; static const uint8_t NextIntersect = 0b10; static const uint8_t BothIntersect = 0b11; const uint8_t type = (intersects[i] ? 1 : 0) + (intersects[j] ? 2 : 0); switch (type) { case NoIntersect: { // If both points don't intercept, the target might still pass between // them, so we need to check the intermediate point const glm::dvec3 half = glm::mix(iBound, jBound, 0.5); if (intercepts(half)) { // The two outer points do not intersect, but the middle point // does; so we need to find the intersection points const double t1 = bisect(half, iBound, intercepts); const double t2 = 0.5 + bisect(half, jBound, intercepts); // // The target is sticking out somewhere between i and j, so we // have three regions here: // The first (0,t1) and second (t2,1) are not intersecting // The third between (t1,t2) is intersecting // // i p1 p2 j // ***** // x-------* *-------x // 0 t1 t2 1 // OBS: i and j are in bounds-space, p1, p2 are in // _orthogonalPlane-space const size_t p1 = static_cast( indexForBounds(i) + t1 * InterpolationSteps ); const size_t p2 = static_cast( indexForBounds(i) + t2 * InterpolationSteps ); // We can copy the non-intersecting parts copyFieldOfViewValues(i, indexForBounds(i), p1); copyFieldOfViewValues(i, p2, indexForBounds(j)); // Are recompute the intersecting ones for (size_t k = 0; k <= (p2 - p1); ++k) { const double t = t1 + k * (t2 - t1); const glm::dvec3 interpolated = glm::mix(iBound, jBound, t); const glm::vec3 icpt = interceptVector(interpolated); _orthogonalPlane.data[p1 + k] = { icpt.x, icpt.y, icpt.z, RenderInformation::VertexColorTypeSquare }; } } else { copyFieldOfViewValues( i, indexForBounds(i), indexForBounds(i + 1) ); } break; } case ThisIntersect: case NextIntersect: case BothIntersect: break; default: throw ghoul::MissingCaseException(); } } } //size_t k = (i + 1 > _instrument.bounds.size() - 1) ? 0 : i + 1; //glm::dvec3 mid; //glm::dvec3 interpolated; //const glm::dvec3& current = _instrument.bounds[i]; //const glm::dvec3& next = _instrument.bounds[k]; //if (intercepts[i] == false) { // If point is non-interceptive, project it. // insertPoint( // _fovPlane, // glm::vec4( // orthogonalProjection(current, data.time, target), // 0.0 // ), // tmp // ); // _rebuild = true; // if (intercepts[i + 1] == false) { // // IFF incident point is also non-interceptive BUT something is // // within FOV // // we need then to check if this segment makes contact with surface // glm::dvec3 half = interpolate(current, next, 0.5f); // std::string bodyfixed = "IAU_"; // bool convert = (_instrument.referenceFrame.find(bodyfixed) == // std::string::npos); // if (convert) { // bodyfixed = SpiceManager::ref().frameFromBody(target); // } // else { // bodyfixed = _instrument.referenceFrame; // } // SpiceManager::SurfaceInterceptResult res = // SpiceManager::ref().surfaceIntercept( // target, // _instrument.spacecraft, // _instrument.name, // bodyfixed, // _instrument.aberrationCorrection, // data.time, // half // ); // if (convert) { // res.surfaceVector = // SpiceManager::ref().frameTransformationMatrix( // bodyfixed, // _instrument.referenceFrame, // data.time // ) * res.surfaceVector; // } // bool intercepted = res.interceptFound; // if (intercepted) { // // find the two outer most points of intersection // glm::dvec3 root1 = bisection(half, current, data.time, target); // glm::dvec3 root2 = bisection(half, next, data.time, target); // insertPoint( // _fovPlane, // glm::vec4( // orthogonalProjection( // root1, // data.time, // target // ), // 0.0 // ), // squareColor(diffTime) // ); // for (int j = 1; j < InterpolationSteps; ++j) { // float t = (static_cast(j) / InterpolationSteps); // interpolated = interpolate(root1, root2, t); // glm::dvec3 ivec = checkForIntercept( // interpolated, // data.time, // target // ); // insertPoint( // _fovPlane, // glm::vec4(ivec, 0.0), // squareColor(diffTime) // ); // } // insertPoint( // _fovPlane, // glm::vec4( // orthogonalProjection(root2, data.time, target), 0.0 // ), // squareColor(diffTime) // ); // } // } //} //if (interceptTag[i] == true && interceptTag[i + 1] == false) { // // current point is interceptive, next is not // // find outer most point for interpolation // mid = bisection(current, next, data.time, target); // for (int j = 1; j <= InterpolationSteps; ++j) { // float t = (static_cast(j) / InterpolationSteps); // interpolated = interpolate(current, mid, t); // glm::dvec3 ivec = // (j < InterpolationSteps) ? // checkForIntercept(interpolated, data.time, target) : // orthogonalProjection(interpolated, data.time, target); // insertPoint(_fovPlane, glm::vec4(ivec, 0.0), squareColor(diffTime)); // _rebuild = true; // } //} //if (interceptTag[i] == false && interceptTag[i + 1] == true) { // // current point is non-interceptive, next is // mid = bisection(next, current, data.time, target); // for (int j = 1; j <= InterpolationSteps; ++j) { // float t = (static_cast(j) / InterpolationSteps); // interpolated = interpolate(mid, next, t); // glm::dvec3 ivec = // (j > 1) ? // checkForIntercept(interpolated, data.time, target) : // orthogonalProjection(interpolated, data.time, target); // insertPoint(_fovPlane, glm::vec4(ivec, 0.0), squareColor(diffTime)); // _rebuild = true; // } //} //if (interceptTag[i] == true && interceptTag[i + 1] == true) { // // both points intercept // for (int j = 0; j <= InterpolationSteps; ++j) { // float t = (static_cast(j) / InterpolationSteps); // interpolated = interpolate(current, next, t); // glm::dvec3 ivec = checkForIntercept( // interpolated, // data.time, // target // ); // insertPoint(_fovPlane, glm::vec4(ivec, 0.0), squareColor(diffTime)); // _rebuild = true; // } //} //// @CLEANUP-END //} //} #endif } #if 0 void RenderableFov::computeIntercepts(const UpdateData& data, const std::string& target, bool inFOV) { double t2 = (openspace::ImageSequencer::ref().getNextCaptureTime()); double diff = (t2 - data.time); float diffTime = 0.0; float interpolationStart = 7.0; //seconds before if (diff <= interpolationStart) diffTime = static_cast(1.0 - (diff / interpolationStart)); if (diff < 0.0) diffTime = 0.f; //PerfMeasure("computeIntercepts"); // for each FOV vector bool interceptTag[35]; _fovBounds.clear(); for (int i = 0; i <= _instrument.bounds.size(); ++i) { int r = (i == _instrument.bounds.size()) ? 0 : i; std::string bodyfixed = "IAU_"; bool convert = (_instrument.referenceFrame.find(bodyfixed) == std::string::npos); if (convert) { bodyfixed = SpiceManager::ref().frameFromBody(target); } else { bodyfixed = _instrument.referenceFrame; } SpiceManager::SurfaceInterceptResult res = SpiceManager::ref().surfaceIntercept( target, _instrument.spacecraft, _instrument.name, bodyfixed, _instrument.aberrationCorrection, data.time, _instrument.bounds[r] ); if (convert) { res.surfaceVector = SpiceManager::ref().frameTransformationMatrix( bodyfixed, _instrument.referenceFrame, data.time ) * res.surfaceVector; } interceptTag[r] = res.interceptFound; // if not found, use the orthogonal projected point glm::dvec3 b; if (!interceptTag[r]) { b = orthogonalProjection(_instrument.bounds[r], data.time, target); } //This will have to be fixed once spacecraft is 1:1! glm::vec4 fovOrigin = glm::vec4(0); if (interceptTag[r]) { // INTERCEPTIONS insertPoint(_fovBounds, fovOrigin, _colors.intersectionStart); insertPoint( _fovBounds, glm::vec4(res.surfaceVector, 0.0), endColor(diffTime) ); } else if (inFOV) { // OBJECT IN FOV, NO INTERCEPT FOR THIS FOV-RAY insertPoint(_fovBounds, fovOrigin, glm::vec4(0, 0, 1, 1)); insertPoint(_fovBounds, glm::vec4(b, 0.0), _colors.targetInFieldOfView); } else { //glm::vec4 corner(_bounds[r][0], _bounds[r][1], _bounds[r][2], 8); ////glm::vec4 corner = _projectionBounds[r].vec4(); //corner = _spacecraftRotation*corner; //// NONE OF THE FOV-RAYS INTERCEPT AND NO OBJECT IN FOV //insertPoint(_fovBounds, fovOrigin, col_gray); //insertPoint(_fovBounds, corner, glm::vec4(0)); insertPoint(_fovBounds, fovOrigin, _colors.default); insertPoint(_fovBounds, glm::vec4(b, 0.0), glm::vec4(0.4)); } } interceptTag[_instrument.bounds.size()] = interceptTag[0]; //fovSurfaceIntercept(_interceptTag, _bounds, data.time); // FOV SURFACE INTERCEPT auto bounds = _instrument.bounds; _rebuild = false; _fovPlane.clear(); // empty the array glm::dvec3 mid; glm::dvec3 interpolated; glm::dvec3 current; glm::dvec3 next; glm::vec4 tmp(1); if (bounds.size() > 1) { for (int i = 0; i < bounds.size(); ++i) { int k = (i + 1 > bounds.size() - 1) ? 0 : i + 1; current = bounds[i]; next = bounds[k]; if (interceptTag[i] == false) { // If point is non-interceptive, project it. insertPoint( _fovPlane, glm::vec4(orthogonalProjection(current, data.time, target), 0.0), tmp ); _rebuild = true; if (interceptTag[i + 1] == false && inFOV) { // IFF incident point is also non-interceptive BUT something is within // FOV // we need then to check if this segment makes contact with surface glm::dvec3 half = interpolate(current, next, 0.5f); std::string bodyfixed = "IAU_"; bool convert = (_instrument.referenceFrame.find(bodyfixed) == std::string::npos); if (convert) { bodyfixed = SpiceManager::ref().frameFromBody(target); } else { bodyfixed = _instrument.referenceFrame; } SpiceManager::SurfaceInterceptResult res = SpiceManager::ref().surfaceIntercept( target, _instrument.spacecraft, _instrument.name, bodyfixed, _instrument.aberrationCorrection, data.time, half ); if (convert) { res.surfaceVector = SpiceManager::ref().frameTransformationMatrix( bodyfixed, _instrument.referenceFrame, data.time ) * res.surfaceVector; } bool intercepted = res.interceptFound; if (intercepted) { // find the two outer most points of intersection glm::dvec3 root1 = bisection(half, current, data.time, target); glm::dvec3 root2 = bisection(half, next, data.time, target); insertPoint( _fovPlane, glm::vec4( orthogonalProjection(root1, data.time, target), 0.0 ), squareColor(diffTime) ); for (int j = 1; j < InterpolationSteps; ++j) { float t = (static_cast(j) / InterpolationSteps); interpolated = interpolate(root1, root2, t); glm::dvec3 ivec = checkForIntercept( interpolated, data.time, target ); insertPoint( _fovPlane, glm::vec4(ivec,0.0), squareColor(diffTime) ); } insertPoint( _fovPlane, glm::vec4( orthogonalProjection(root2, data.time, target), 0.0 ), squareColor(diffTime) ); } } } if (interceptTag[i] == true && interceptTag[i + 1] == false) { // current point is interceptive, next is not // find outer most point for interpolation mid = bisection(current, next, data.time, target); for (int j = 1; j <= InterpolationSteps; ++j) { float t = (static_cast(j) / InterpolationSteps); interpolated = interpolate(current, mid, t); glm::dvec3 ivec = (j < InterpolationSteps) ? checkForIntercept(interpolated, data.time, target) : orthogonalProjection(interpolated, data.time, target); insertPoint(_fovPlane, glm::vec4(ivec, 0.0), squareColor(diffTime)); _rebuild = true; } } if (interceptTag[i] == false && interceptTag[i + 1] == true) { // current point is non-interceptive, next is mid = bisection(next, current, data.time, target); for (int j = 1; j <= InterpolationSteps; ++j) { float t = (static_cast(j) / InterpolationSteps); interpolated = interpolate(mid, next, t); glm::dvec3 ivec = (j > 1) ? checkForIntercept(interpolated, data.time, target) : orthogonalProjection(interpolated, data.time, target); insertPoint(_fovPlane, glm::vec4(ivec, 0.0), squareColor(diffTime)); _rebuild = true; } } if (interceptTag[i] == true && interceptTag[i + 1] == true) { // both points intercept for (int j = 0; j <= InterpolationSteps; ++j) { float t = (static_cast(j) / InterpolationSteps); interpolated = interpolate(current, next, t); glm::dvec3 ivec = checkForIntercept(interpolated, data.time, target); insertPoint(_fovPlane, glm::vec4(ivec, 0.0), squareColor(diffTime)); _rebuild = true; } } } } if (_rebuild) { //update size etc; _orthogonalPlane.size = static_cast(_fovPlane.size()); } // glm::mat4 spacecraftRotation = glm::mat4( SpiceManager::ref().positionTransformMatrix( _instrument.name, _instrument.referenceFrame, data.time ) ); glm::vec3 aim = (spacecraftRotation * glm::vec4(_instrument.boresight, 1)); double lt; glm::dvec3 position = SpiceManager::ref().targetPosition( target, _instrument.spacecraft, _instrument.referenceFrame, _instrument.aberrationCorrection, data.time, lt ); psc p = PowerScaledCoordinate::CreatePowerScaledCoordinate( position.x, position.y, position.z ); pss length = p.length(); if (length[0] < DBL_EPSILON) { _drawFOV = false; return; } //if aimed 80 deg away from target, dont draw white square if (glm::dot(glm::normalize(aim), glm::normalize(p.vec3())) < 0.2) { _drawFOV = false; } } #endif void RenderableFov::render(const RenderData& data, RendererTasks&) { if (_drawFOV) { _programObject->activate(); // Model transform and view transform needs to be in double precision glm::dmat4 modelTransform = glm::translate(glm::dmat4(1.0), data.modelTransform.translation) * glm::dmat4(data.modelTransform.rotation) * glm::scale(glm::dmat4(1.0), glm::dvec3(data.modelTransform.scale)); glm::mat4 modelViewProjectionTransform = data.camera.projectionMatrix() * glm::mat4(data.camera.combinedViewMatrix() * modelTransform); _programObject->setUniform( _uniformCache.modelViewProjection, modelViewProjectionTransform ); _programObject->setUniform(_uniformCache.defaultColorStart, _colors.defaultStart); _programObject->setUniform(_uniformCache.defaultColorEnd, _colors.defaultEnd); _programObject->setUniform(_uniformCache.activeColor, _colors.active); _programObject->setUniform( _uniformCache.targetInFieldOfViewColor, _colors.targetInFieldOfView ); _programObject->setUniform( _uniformCache.intersectionStartColor, _colors.intersectionStart ); _programObject->setUniform( _uniformCache.intersectionEndColor, _colors.intersectionEnd ); _programObject->setUniform(_uniformCache.squareColor, _colors.square); _programObject->setUniform(_uniformCache.interpolation, _interpolationTime); GLenum mode = _drawSolid ? GL_TRIANGLE_STRIP : GL_LINES; glLineWidth(_lineWidth); glBindVertexArray(_fieldOfViewBounds.vao); glDrawArrays(mode, 0, static_cast(_fieldOfViewBounds.data.size())); glLineWidth(2.f); glBindVertexArray(_orthogonalPlane.vao); glDrawArrays(GL_LINE_LOOP, 0, static_cast(_orthogonalPlane.data.size())); glBindVertexArray(0); glLineWidth(1.f); _programObject->deactivate(); } } void RenderableFov::update(const UpdateData& data) { _drawFOV = false; if (openspace::ImageSequencer::ref().isReady()) { _drawFOV = ImageSequencer::ref().instrumentActive(_instrument.name); } if (_drawFOV && !data.time.paused()) { auto t = determineTarget(data.time.j2000Seconds()); std::string target = t.first; bool inFOV = t.second; computeIntercepts(data, target, inFOV); updateGPU(); double t2 = (ImageSequencer::ref().getNextCaptureTime()); double diff = (t2 - data.time.j2000Seconds()); _interpolationTime = 0.0; float interpolationStart = 7.0; //seconds before if (diff <= interpolationStart) { _interpolationTime = static_cast(1.0 - (diff / interpolationStart)); } if (diff < 0.0) { _interpolationTime = 0.f; } } if (_programObject->isDirty()) { _programObject->rebuildFromFile(); _uniformCache.modelViewProjection = _programObject->uniformLocation( "modelViewProjectionTransform" ); _uniformCache.defaultColorStart = _programObject->uniformLocation( "defaultColorStart" ); _uniformCache.defaultColorEnd = _programObject->uniformLocation( "defaultColorEnd" ); _uniformCache.activeColor = _programObject->uniformLocation("activeColor"); _uniformCache.targetInFieldOfViewColor = _programObject->uniformLocation( "targetInFieldOfViewColor" ); _uniformCache.intersectionStartColor = _programObject->uniformLocation( "intersectionStartColor" ); _uniformCache.intersectionEndColor = _programObject->uniformLocation( "intersectionEndColor" ); _uniformCache.squareColor = _programObject->uniformLocation("squareColor"); _uniformCache.interpolation = _programObject->uniformLocation("interpolation"); } } std::pair RenderableFov::determineTarget(double time) { SpiceManager::UseException oldValue = SpiceManager::ref().exceptionHandling(); defer { SpiceManager::ref().setExceptionHandling(oldValue); }; // First, for all potential targets, check whether they are in the field of view for (const std::string& pt : _instrument.potentialTargets) { bool inFOV = SpiceManager::ref().isTargetInFieldOfView( pt, _instrument.spacecraft, _instrument.name, SpiceManager::FieldOfViewMethod::Ellipsoid, _instrument.aberrationCorrection, time ); if (inFOV) { _previousTarget = pt; return { pt, true }; } } // If none of the targets is in field of view, either use the last target or if there // hasn't been one, find the closest target if (_previousTarget.empty()) { // If we reached this, we haven't found a target in field of view and we don't // have a previously selected target, so the next best heuristic for a target is // the closest one std::vector distances(_instrument.potentialTargets.size()); std::transform( _instrument.potentialTargets.begin(), _instrument.potentialTargets.end(), distances.begin(), [&o = _instrument.spacecraft, &f = _instrument.referenceFrame, &t = time] (const std::string& pt) { double lt; glm::dvec3 p = SpiceManager::ref().targetPosition(pt, o, f, {}, t, lt); return glm::length(p); } ); // The iterator points to the item with the minimal distance auto iterator = std::min_element(distances.begin(), distances.end()); // Since the two vectors are ordered the same, we can use the distance as offset _previousTarget = _instrument.potentialTargets[ std::distance(distances.begin(), iterator) ]; } return { _previousTarget, false }; } void RenderableFov::updateGPU() { // @SPEEDUP: Only upload the part of the data that has changed ---abock glBindBuffer(GL_ARRAY_BUFFER, _fieldOfViewBounds.vbo); glBufferData( GL_ARRAY_BUFFER, _fieldOfViewBounds.data.size() * sizeof(RenderInformation::VBOData), _fieldOfViewBounds.data.data(), GL_STREAM_DRAW ); glBindBuffer(GL_ARRAY_BUFFER, _orthogonalPlane.vbo); glBufferData( GL_ARRAY_BUFFER, _orthogonalPlane.data.size() * sizeof(RenderInformation::VBOData), _orthogonalPlane.data.data(), GL_STREAM_DRAW ); //glBindBuffer(GL_ARRAY_BUFFER, _bounds.vbo); //glBufferSubData( // GL_ARRAY_BUFFER, // 0, // _bounds.size * sizeof(GLfloat), // _fovBounds.data() //); ////LINFOC(_instrument, _boundsV.size); //if (!_rebuild) { // // no new points // glBindBuffer(GL_ARRAY_BUFFER, _orthogonalPlane.vbo); // glBufferSubData( // GL_ARRAY_BUFFER, // 0, // _orthogonalPlane.size * sizeof(GLfloat), // _fovPlane.data() // ); //} //else { // // new points - memory change // glBindVertexArray(_orthogonalPlane.vao); // glBindBuffer(GL_ARRAY_BUFFER, _orthogonalPlane.vbo); // glBufferData( // GL_ARRAY_BUFFER, // _orthogonalPlane.size * sizeof(GLfloat), // NULL, // GL_STATIC_DRAW // ); // orphaning the buffer, sending NULL data. // glBufferSubData( // GL_ARRAY_BUFFER, // 0, // _orthogonalPlane.size * sizeof(GLfloat), // _fovPlane.data() // ); // GLsizei st = sizeof(GLfloat) * Stride; // glEnableVertexAttribArray(0); // glEnableVertexAttribArray(1); // glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, st, (void*)0); // glVertexAttribPointer( // 1, // 4, // GL_FLOAT, // GL_FALSE, // st, // (void*)(4 * sizeof(GLfloat)) // ); //} //glBindVertexArray(0); } } // namespace openspace