// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2011 Werner Mayer * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #ifdef FC_OS_WIN32 # include #endif #ifdef FC_OS_MACOSX # include #else # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SoBrepEdgeSet.h" #include "SoBrepFaceSet.h" #include "ViewProviderExt.h" #include using namespace PartGui; SO_NODE_SOURCE(SoBrepEdgeSet) struct SoBrepEdgeSet::SelContext: Gui::SoFCSelectionContext { std::vector hl, sl; }; void SoBrepEdgeSet::initClass() { SO_NODE_INIT_CLASS(SoBrepEdgeSet, SoIndexedLineSet, "IndexedLineSet"); } SoBrepEdgeSet::SoBrepEdgeSet() : selContext(std::make_shared()) , selContext2(std::make_shared()) { SO_NODE_CONSTRUCTOR(SoBrepEdgeSet); } void SoBrepEdgeSet::GLRender(SoGLRenderAction* action) { auto state = action->getState(); selCounter.checkRenderCache(state); SelContextPtr ctx2; SelContextPtr ctx = Gui::SoFCSelectionRoot::getRenderContext(this, selContext, ctx2); if (ctx2 && ctx2->selectionIndex.empty()) { return; } bool hasContextHighlight = ctx && !ctx->hl.empty(); bool hasFaceHighlight = viewProvider && viewProvider->isFaceHighlightActive(); bool hasAnyHighlight = hasContextHighlight || hasFaceHighlight; if (Gui::Selection().isClarifySelectionActive() && !Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths && hasAnyHighlight) { // if we are using clarifyselection - add this to delayed paths with priority // as we want to get this rendered on top of everything if (viewProvider) { viewProvider->setFaceHighlightActive(true); } Gui::SoDelayedAnnotationsElement::addDelayedPath( action->getState(), action->getCurPath()->copy(), 200 ); return; } if (selContext2->checkGlobal(ctx)) { if (selContext2->isSelectAll()) { selContext2->sl.clear(); selContext2->sl.push_back(-1); } else if (ctx) { selContext2->sl = ctx->sl; } if (selContext2->highlightIndex == std::numeric_limits::max()) { selContext2->hl.clear(); selContext2->hl.push_back(-1); } else if (ctx) { selContext2->hl = ctx->hl; } ctx = selContext2; } if (ctx && ctx->highlightIndex == std::numeric_limits::max()) { if (ctx->selectionIndex.empty() || ctx->isSelectAll()) { if (ctx2) { ctx2->selectionColor = ctx->highlightColor; renderSelection(action, ctx2); } else { renderHighlight(action, ctx); } } else { if (!action->isRenderingDelayedPaths()) { renderSelection(action, ctx); } if (ctx2) { ctx2->selectionColor = ctx->highlightColor; renderSelection(action, ctx2); } else { renderHighlight(action, ctx); } if (action->isRenderingDelayedPaths()) { renderSelection(action, ctx); } } return; } if (!action->isRenderingDelayedPaths()) { renderHighlight(action, ctx); } if (ctx && !ctx->selectionIndex.empty()) { if (ctx->isSelectAll()) { if (ctx2) { ctx2->selectionColor = ctx->selectionColor; renderSelection(action, ctx2); } else if (ctx->isSelectAll()) { renderSelection(action, ctx); } if (action->isRenderingDelayedPaths()) { renderHighlight(action, ctx); } return; } if (!action->isRenderingDelayedPaths()) { renderSelection(action, ctx); } } if (ctx2 && !ctx2->selectionIndex.empty()) { renderSelection(action, ctx2, false); } else if (Gui::Selection().isClarifySelectionActive() && !Gui::SoDelayedAnnotationsElement::isProcessingDelayedPaths && hasAnyHighlight) { state->push(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDepthMask(false); glDisable(GL_DEPTH_TEST); inherited::GLRender(action); state->pop(); } else { inherited::GLRender(action); } // Workaround for #0000433 // #if !defined(FC_OS_WIN32) if (!action->isRenderingDelayedPaths()) { renderHighlight(action, ctx); } if (ctx && !ctx->selectionIndex.empty()) { renderSelection(action, ctx); } if (action->isRenderingDelayedPaths()) { renderHighlight(action, ctx); } // #endif } void SoBrepEdgeSet::GLRenderBelowPath(SoGLRenderAction* action) { inherited::GLRenderBelowPath(action); } void SoBrepEdgeSet::getBoundingBox(SoGetBoundingBoxAction* action) { SelContextPtr ctx2 = Gui::SoFCSelectionRoot::getSecondaryActionContext(action, this); if (!ctx2 || (ctx2->sl.size() == 1 && ctx2->sl[0] < 0)) { inherited::getBoundingBox(action); return; } if (ctx2->sl.empty()) { return; } auto state = action->getState(); auto coords = SoCoordinateElement::getInstance(state); const SbVec3f* coords3d = coords->getArrayPtr3(); if (!validIndexes(coords, ctx2->sl)) { return; } SbBox3f bbox; int32_t i; const int32_t* cindices = &ctx2->sl[0]; const int32_t* end = cindices + ctx2->sl.size(); while (cindices < end) { bbox.extendBy(coords3d[*cindices++]); i = (cindices < end) ? *cindices++ : -1; while (i >= 0) { bbox.extendBy(coords3d[i]); i = cindices < end ? *cindices++ : -1; } } if (!bbox.isEmpty()) { action->extendBy(bbox); } } void SoBrepEdgeSet::renderShape( const SoGLCoordinateElement* const coords, const int32_t* cindices, int numindices ) { const SbVec3f* coords3d = coords->getArrayPtr3(); int32_t i; int previ; const int32_t* end = cindices + numindices; while (cindices < end) { glBegin(GL_LINE_STRIP); previ = *cindices++; i = (cindices < end) ? *cindices++ : -1; while (i >= 0) { glVertex3fv((const GLfloat*)(coords3d + previ)); glVertex3fv((const GLfloat*)(coords3d + i)); previ = i; i = cindices < end ? *cindices++ : -1; } glEnd(); } } void SoBrepEdgeSet::renderHighlight(SoGLRenderAction* action, SelContextPtr ctx) { if (!ctx || ctx->highlightIndex < 0) { return; } SoState* state = action->getState(); state->push(); // SoLineWidthElement::set(state, this, 4.0f); SoLazyElement::setEmissive(state, &ctx->highlightColor); packedColor = ctx->highlightColor.getPackedValue(0.0); SoLazyElement::setPacked(state, this, 1, &packedColor, false); const SoCoordinateElement* coords; const SbVec3f* normals; const int32_t* cindices; int numcindices; const int32_t* nindices; const int32_t* tindices; const int32_t* mindices; SbBool normalCacheUsed; this->getVertexData( state, coords, normals, cindices, nindices, tindices, mindices, numcindices, false, normalCacheUsed ); SoMaterialBundle mb(action); mb.sendFirst(); // make sure we have the correct material int num = (int)ctx->hl.size(); if (num > 0) { if (ctx->hl[0] < 0) { renderShape(static_cast(coords), cindices, numcindices); } else { const int32_t* id = &(ctx->hl[0]); if (!validIndexes(coords, ctx->hl)) { SoDebugError::postWarning( "SoBrepEdgeSet::renderHighlight", "highlightIndex out of range" ); } else { renderShape(static_cast(coords), id, num); } } } state->pop(); } void SoBrepEdgeSet::renderSelection(SoGLRenderAction* action, SelContextPtr ctx, bool push) { SoState* state = action->getState(); if (push) { state->push(); // SoLineWidthElement::set(state, this, 4.0f); SoLazyElement::setEmissive(state, &ctx->selectionColor); packedColor = ctx->selectionColor.getPackedValue(0.0); SoLazyElement::setPacked(state, this, 1, &packedColor, false); } const SoCoordinateElement* coords; const SbVec3f* normals; const int32_t* cindices; int numcindices; const int32_t* nindices; const int32_t* tindices; const int32_t* mindices; SbBool normalCacheUsed; this->getVertexData( state, coords, normals, cindices, nindices, tindices, mindices, numcindices, false, normalCacheUsed ); SoMaterialBundle mb(action); mb.sendFirst(); // make sure we have the correct material int num = (int)ctx->sl.size(); if (num > 0) { if (ctx->sl[0] < 0) { renderShape(static_cast(coords), cindices, numcindices); } else { cindices = &(ctx->sl[0]); numcindices = (int)ctx->sl.size(); if (!validIndexes(coords, ctx->sl)) { SoDebugError::postWarning( "SoBrepEdgeSet::renderSelection", "selectionIndex out of range" ); } else { renderShape(static_cast(coords), cindices, numcindices); } } } if (push) { state->pop(); } } bool SoBrepEdgeSet::validIndexes(const SoCoordinateElement* coords, const std::vector& pts) const { for (int32_t it : pts) { if (it >= coords->getNum()) { return false; } } return true; } void SoBrepEdgeSet::doAction(SoAction* action) { if (action->getTypeId() == Gui::SoHighlightElementAction::getClassTypeId()) { Gui::SoHighlightElementAction* hlaction = static_cast(action); selCounter.checkAction(hlaction); if (!hlaction->isHighlighted()) { SelContextPtr ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext, false); if (ctx) { ctx->highlightIndex = -1; ctx->hl.clear(); touch(); } return; } const SoDetail* detail = hlaction->getElement(); if (!detail) { SelContextPtr ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext); ctx->highlightColor = hlaction->getColor(); ctx->highlightIndex = std::numeric_limits::max(); ctx->hl.clear(); ctx->hl.push_back(-1); touch(); return; } if (!detail->isOfType(SoLineDetail::getClassTypeId())) { SelContextPtr ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext, false); if (ctx) { ctx->highlightIndex = -1; ctx->hl.clear(); touch(); } return; } SelContextPtr ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext); ctx->highlightColor = hlaction->getColor(); int index = static_cast(detail)->getLineIndex(); const int32_t* cindices = this->coordIndex.getValues(0); int numcindices = this->coordIndex.getNum(); ctx->hl.clear(); for (int section = 0, i = 0; i < numcindices; i++) { if (cindices[i] < 0) { if (++section > index) { break; } } else if (section == index) { ctx->hl.push_back(cindices[i]); } } if (!ctx->hl.empty()) { ctx->highlightIndex = index; } else { ctx->highlightIndex = -1; } touch(); return; } else if (action->getTypeId() == Gui::SoSelectionElementAction::getClassTypeId()) { Gui::SoSelectionElementAction* selaction = static_cast(action); switch (selaction->getType()) { case Gui::SoSelectionElementAction::None: { if (selaction->isSecondary()) { if (Gui::SoFCSelectionRoot::removeActionContext(action, this)) { touch(); } } else { SelContextPtr ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext, false); if (ctx) { ctx->selectionIndex.clear(); ctx->sl.clear(); touch(); } } return; } case Gui::SoSelectionElementAction::All: { SelContextPtr ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext); selCounter.checkAction(selaction, ctx); ctx->selectionColor = selaction->getColor(); ctx->selectionIndex.clear(); ctx->selectionIndex.insert(-1); // all ctx->sl.clear(); ctx->sl.push_back(-1); touch(); return; } case Gui::SoSelectionElementAction::Append: case Gui::SoSelectionElementAction::Remove: { const SoDetail* detail = selaction->getElement(); if (!detail || !detail->isOfType(SoLineDetail::getClassTypeId())) { if (selaction->isSecondary()) { // For secondary context, a detail of different type means // the user may want to partial render only other type of // geometry. So we call below to obtain a action context. // If no secondary context exist, it will create an empty // one, and an empty secondary context inhibites drawing // here. auto ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext); selCounter.checkAction(selaction, ctx); touch(); } return; } int index = static_cast(detail)->getLineIndex(); SelContextPtr ctx; if (selaction->getType() == Gui::SoSelectionElementAction::Append) { ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext); selCounter.checkAction(selaction, ctx); ctx->selectionColor = selaction->getColor(); if (ctx->isSelectAll()) { ctx->selectionIndex.clear(); } if (!ctx->selectionIndex.insert(index).second) { return; } } else { ctx = Gui::SoFCSelectionRoot::getActionContext(action, this, selContext, false); if (!ctx || !ctx->removeIndex(index)) { return; } } ctx->sl.clear(); if (!ctx->selectionIndex.empty()) { const int32_t* cindices = this->coordIndex.getValues(0); int numcindices = this->coordIndex.getNum(); auto it = ctx->selectionIndex.begin(); for (int section = 0, i = 0; i < numcindices; i++) { if (section == *it) { ctx->sl.push_back(cindices[i]); } if (cindices[i] < 0) { if (++section > *it) { if (++it == ctx->selectionIndex.end()) { break; } } } } } touch(); break; } default: break; } return; } inherited::doAction(action); } SoDetail* SoBrepEdgeSet::createLineSegmentDetail( SoRayPickAction* action, const SoPrimitiveVertex* v1, const SoPrimitiveVertex* v2, SoPickedPoint* pp ) { SoDetail* detail = inherited::createLineSegmentDetail(action, v1, v2, pp); SoLineDetail* line_detail = static_cast(detail); int index = line_detail->getLineIndex(); line_detail->setPartIndex(index); return detail; }