///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) Statoil ASA // Copyright (C) Ceetron Solutions AS // Copyright (C) 2011-2012 Ceetron AS // // ResInsight is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // ResInsight 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 General Public License at // for more details. // ///////////////////////////////////////////////////////////////////////////////// #include "RivPipeGeometryGenerator.h" #include "RivObjectSourceInfo.h" #include "cafEffectGenerator.h" #include "cvfDrawableGeo.h" #include "cvfPlane.h" #include "cvfPrimitiveSetIndexedUInt.h" #include "cvfRay.h" //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RivPipeGeometryGenerator::RivPipeGeometryGenerator() { m_radius = 1.0; m_crossSectionNodeCount = 8; m_minimumBendAngle = 80.0; m_bendScalingFactor = 0.00001; m_firstVisibleSegmentIndex = 0; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RivPipeGeometryGenerator::~RivPipeGeometryGenerator() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::setPipeCenterCoords(const cvf::Vec3dArray* coords) { m_originalPipeCenterCoords = coords; clearComputedData(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::setMinimumBendAngle(double degrees) { m_minimumBendAngle = degrees; clearComputedData(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::setBendScalingFactor(double scaleFactor) { m_bendScalingFactor = scaleFactor; clearComputedData(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::setRadius(double radius) { CVF_ASSERT(0 <= radius && radius < 1e100); m_radius = radius; clearComputedData(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::setCrossSectionVertexCount(size_t nodeCount) { CVF_ASSERT( 2 < nodeCount && nodeCount < 1000000); m_crossSectionNodeCount = nodeCount; clearComputedData(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvf::ref RivPipeGeometryGenerator::createPipeSurface() { if (m_radius == 0.0) { return NULL; } updateFilteredPipeCenterCoords(); cvf::ref activeCoords = new cvf::Vec3dArray(m_filteredPipeCenterCoords); return RivPipeGeometryGenerator::generateExtrudedCylinder(m_radius, m_crossSectionNodeCount, activeCoords.p()); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvf::ref RivPipeGeometryGenerator::createCenterLine() { return generateLine(m_originalPipeCenterCoords.p()); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::pipeSurfaceTextureCoords(cvf::Vec2fArray* textureCoords, const std::vector& segmentResults, const cvf::ScalarMapper* mapper) const { CVF_ASSERT(textureCoords); CVF_ASSERT(mapper); CVF_ASSERT(segmentResults.size() == m_originalPipeCenterCoords->size() - 1); size_t nodeCountPerSegment = m_crossSectionNodeCount * 4; size_t vertexCount = m_filteredPipeSegmentToResult.size() * nodeCountPerSegment; if (textureCoords->size() != vertexCount) textureCoords->resize(vertexCount); size_t i; for (i = 0; i < m_filteredPipeSegmentToResult.size(); i++) { size_t resultIndex = m_filteredPipeSegmentToResult[i]; cvf::Vec2f texCoord = mapper->mapToTextureCoord(segmentResults[resultIndex]); size_t j; for (j = 0; j < nodeCountPerSegment; j++) { textureCoords->set(i * nodeCountPerSegment + j, texCoord); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::centerlineTextureCoords(cvf::Vec2fArray* textureCoords, const std::vector& segmentResults, const cvf::ScalarMapper* mapper) const { CVF_ASSERT(textureCoords); CVF_ASSERT(mapper); CVF_ASSERT(segmentResults.size() == m_originalPipeCenterCoords->size() - 1); size_t vertexCount = segmentResults.size() * 2; if (textureCoords->size() != vertexCount) textureCoords->resize(vertexCount); for (size_t vxIdx = 0; vxIdx < vertexCount; ++vxIdx) { cvf::Vec2f texCoord = mapper->mapToTextureCoord(segmentResults[vxIdx/2]); textureCoords->set(vxIdx, texCoord); } } //-------------------------------------------------------------------------------------------------- /// The circle points are generated in positive direction with circle normal pointing along yDir ^ zDir //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::computeCircle(double radius, size_t tesselationCount, const cvf::Vec3d& center, const cvf::Vec3d& yDirection, const cvf::Vec3d& zDirection, std::vector* nodes) { cvf::Vec3d normal; cvf::Vec3d expandedCoord; double delta = (2 * cvf::PI_D) / tesselationCount; double angle = 0.0; size_t i; for (i = 0; i < tesselationCount; i++) { // These are the local coordinates on the circle double fLocalNormCoordY = cvf::Math::cos(angle); double fLocalNormCoordZ = cvf::Math::sin(angle); // Compute normal (vector going from center to the point on the circle) // Do it this way and we can use this normal directly as long as both input orientation vectors are normalized (which they should be) normal = yDirection*fLocalNormCoordY + zDirection*fLocalNormCoordZ; normal.normalize(); // get the global expanded coord by scaling by radius expandedCoord = center + radius*normal; nodes->push_back(expandedCoord); angle += delta; } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvf::ref RivPipeGeometryGenerator::generateLine(const cvf::Vec3dArray* coords) { CVF_ASSERT(coords != NULL); if (coords->size() < 2 ) return NULL; size_t duplicateVertexCount = 2 * (coords->size() - 1); cvf::ref indices = new cvf::UIntArray; indices->resize(duplicateVertexCount); size_t i; for (i = 0; i < duplicateVertexCount; i++) { indices->set(i, static_cast(i)); } cvf::ref geo = new cvf::DrawableGeo; // Convert double data to float data before sending them to ceeViz. // Sigh .... cvf::ref floatCoords = new cvf::Vec3fArray; floatCoords->resize(duplicateVertexCount); (*floatCoords)[0] = cvf::Vec3f((*coords)[0]); for (i = 1; i < coords->size() - 1; ++i) { (*floatCoords)[2 * i - 1] = cvf::Vec3f((*coords)[i]); (*floatCoords)[2 * i + 0] = cvf::Vec3f((*coords)[i]); } (*floatCoords)[duplicateVertexCount - 1] = cvf::Vec3f((*coords)[coords->size() - 1]); geo->setVertexArray(floatCoords.p()); cvf::ref prim = new cvf::PrimitiveSetIndexedUInt(cvf::PT_LINES); prim->setIndices(indices.p()); geo->addPrimitiveSet(prim.p()); return geo; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvf::ref RivPipeGeometryGenerator::generateExtrudedCylinder(double radius, size_t crossSectionNodeCount, const cvf::Vec3dArray* cylinderCenterCoords) { CVF_ASSERT(cylinderCenterCoords != NULL); if (cylinderCenterCoords->size() < 2) return NULL; std::vector crossSectionVertices; std::vector cylinderSegmentNormals; cvf::Vec3d dir = (*cylinderCenterCoords)[1] - (*cylinderCenterCoords)[0]; dir.normalize(); cvf::Vec3d orient1 = dir.perpendicularVector(); orient1.normalize(); cvf::Vec3d orient2 = dir ^ orient1; orient2.normalize(); std::vector extrudedNodes; computeCircle(radius, crossSectionNodeCount, (*cylinderCenterCoords)[0], orient1, orient2, &extrudedNodes); // Insert the first set of vertices size_t i; for (i = 0; i < extrudedNodes.size(); i++) { crossSectionVertices.push_back(cvf::Vec3f(extrudedNodes[i])); } // Calculate first valid pipe direction, to be able to handle centerNodes in the same place cvf::Vec3d lastValidPipeDirection(0,0,-1); for (i = 0; i < cylinderCenterCoords->size() - 1; i++) { cvf::Vec3d candidateDir = (*cylinderCenterCoords)[i] - (*cylinderCenterCoords)[i+1]; if (candidateDir.normalize()) { lastValidPipeDirection = candidateDir; break; } } if (i >= cylinderCenterCoords->size()-1) return NULL; // The pipe coordinates is all the same point // Loop along the cylinder center coords and calculate the cross section vertexes in each center vertex for (size_t ccIdx = 0; ccIdx < cylinderCenterCoords->size() - 1; ccIdx++) { size_t nextCcIdx = ccIdx + 1; // Calculate this and next pipe direction, and intersection plane cvf::Vec3d firstCoord = (*cylinderCenterCoords)[ccIdx]; cvf::Vec3d secondCoord = (*cylinderCenterCoords)[nextCcIdx]; cvf::Vec3d candidateDir = secondCoord - firstCoord; if (!candidateDir.normalize()) { candidateDir = lastValidPipeDirection; } else { lastValidPipeDirection = candidateDir; } cvf::Vec3d nextDir = lastValidPipeDirection; if (nextCcIdx + 1 < cylinderCenterCoords->size()) { cvf::Vec3d thirdCoord = (*cylinderCenterCoords)[nextCcIdx + 1]; nextDir = thirdCoord - secondCoord; } // If the next vector is too small, just assume the pipe is heading straight on if (!nextDir.normalize()) nextDir = candidateDir; cvf::Vec3d intersectionPlaneNormal = candidateDir + nextDir; if (intersectionPlaneNormal.lengthSquared() < 1e-10) // candidateDir == -nextDir => 180 deg turn { CVF_ASSERT(false); // This is never supposed to happen due to what's done in updateFilteredPipeCenterCoords(). So look there for the bug... intersectionPlaneNormal = nextDir; } computeExtrudedCoordsAndNormals(secondCoord, intersectionPlaneNormal, candidateDir, crossSectionNodeCount, &extrudedNodes, &crossSectionVertices, &cylinderSegmentNormals); } size_t crossSectionCount = crossSectionVertices.size() / crossSectionNodeCount; if (crossSectionCount < 2) return NULL; CVF_ASSERT(crossSectionVertices.size() - crossSectionNodeCount == cylinderSegmentNormals.size()); size_t segmentCount = crossSectionCount - 1; size_t vertexCount = segmentCount * crossSectionNodeCount * 4; cvf::ref quadVertexArray = new cvf::Vec3fArray(); quadVertexArray->reserve(vertexCount); cvf::ref quadNormalArray = new cvf::Vec3fArray(); quadNormalArray->reserve(vertexCount); size_t segmentIdx = 0; for (segmentIdx = 0; segmentIdx < segmentCount; segmentIdx++) { size_t i; for (i = 0; i < crossSectionNodeCount - 1; i++) { quadVertexArray->add(crossSectionVertices[( (segmentIdx + 0) * crossSectionNodeCount) + i + 0]); quadVertexArray->add(crossSectionVertices[( (segmentIdx + 0) * crossSectionNodeCount) + i + 1]); quadVertexArray->add(crossSectionVertices[( (segmentIdx + 1) * crossSectionNodeCount) + i + 1]); quadVertexArray->add(crossSectionVertices[( (segmentIdx + 1) * crossSectionNodeCount) + i + 0]); quadNormalArray->add(cylinderSegmentNormals[( (segmentIdx + 0) * crossSectionNodeCount) + i + 0]); quadNormalArray->add(cylinderSegmentNormals[( (segmentIdx + 0) * crossSectionNodeCount) + i + 1]); quadNormalArray->add(cylinderSegmentNormals[( (segmentIdx + 0) * crossSectionNodeCount) + i + 1]); quadNormalArray->add(cylinderSegmentNormals[( (segmentIdx + 0) * crossSectionNodeCount) + i + 0]); } // Last quad closing the cylinder quadVertexArray->add(crossSectionVertices[( (segmentIdx + 0) * crossSectionNodeCount) + crossSectionNodeCount - 1]); quadVertexArray->add(crossSectionVertices[( (segmentIdx + 0) * crossSectionNodeCount) + 0]); quadVertexArray->add(crossSectionVertices[( (segmentIdx + 1) * crossSectionNodeCount) + 0]); quadVertexArray->add(crossSectionVertices[( (segmentIdx + 1) * crossSectionNodeCount) + crossSectionNodeCount - 1]); quadNormalArray->add(cylinderSegmentNormals[( (segmentIdx + 0) * crossSectionNodeCount) + crossSectionNodeCount - 1]); quadNormalArray->add(cylinderSegmentNormals[( (segmentIdx + 0) * crossSectionNodeCount) + 0]); quadNormalArray->add(cylinderSegmentNormals[( (segmentIdx + 0) * crossSectionNodeCount) + 0]); quadNormalArray->add(cylinderSegmentNormals[( (segmentIdx + 0) * crossSectionNodeCount) + crossSectionNodeCount - 1]); } CVF_ASSERT(vertexCount == quadVertexArray->size()); CVF_ASSERT(vertexCount == quadNormalArray->size()); cvf::ref geo = new cvf::DrawableGeo; geo->setFromQuadVertexArray(quadVertexArray.p()); geo->setNormalArray(quadNormalArray.p()); return geo; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::computeExtrudedCoordsAndNormals( cvf::Vec3d intersectionCoord, cvf::Vec3d intersectionPlaneNormal, cvf::Vec3d segmentDirection, size_t crossSectionNodeCount, std::vector* extrudedNodes, std::vector* crossSectionVertices, std::vector* cylinderSegmentNormals) { cvf::Plane intersectionPlane; intersectionPlane.setFromPointAndNormal(intersectionCoord, intersectionPlaneNormal); cvf::Ray ray; ray.setDirection(segmentDirection); // Calculate next set of extruded vertices std::vector nextExtrudedNodes; for (size_t csnIdx = 0; csnIdx < crossSectionNodeCount; csnIdx++) { ray.setOrigin((*extrudedNodes)[csnIdx]); cvf::Vec3d intersection; if (ray.planeIntersect(intersectionPlane, &intersection)) { nextExtrudedNodes.push_back(intersection); } else { cvf::Ray oppositeRay(ray); oppositeRay.setDirection(-segmentDirection); if (oppositeRay.planeIntersect(intersectionPlane, &intersection)) { nextExtrudedNodes.push_back(intersection); } else { // Parallel plane and ray CVF_ASSERT(false); } } } // Calculate the normals for the cylinder segment cvf::Plane cylinderPlane; cylinderPlane.setFromPointAndNormal(intersectionCoord, segmentDirection); for (size_t csnIdx = 0; csnIdx < crossSectionNodeCount; csnIdx++) { crossSectionVertices->push_back(cvf::Vec3f(nextExtrudedNodes[csnIdx])); cvf::Vec3d pipeNormal; cylinderPlane.projectVector(nextExtrudedNodes[csnIdx] - intersectionCoord, &pipeNormal); cylinderSegmentNormals->push_back(cvf::Vec3f(pipeNormal)); } *extrudedNodes = nextExtrudedNodes; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::updateFilteredPipeCenterCoords() { if (m_originalPipeCenterCoords->size() < 2) return; if (m_filteredPipeCenterCoords.size() > 0) return; double squareDistanceTolerance = 1e-4*1e-4; const size_t lastOriginalCoordIdx = m_originalPipeCenterCoords->size() - 1; size_t firstSegmentWithLength = findFirstSegmentWithLenght(squareDistanceTolerance); // Return if we have only zero-length segments if (firstSegmentWithLength == cvf::UNDEFINED_SIZE_T) return; m_filteredPipeCenterCoords.push_back(m_originalPipeCenterCoords->get(firstSegmentWithLength)); m_filteredPipeSegmentToResult.push_back(firstSegmentWithLength); cvf::Vec3d lastValidDirectionAB; size_t lastValidSegment = 0; // Go along the line, inserting bends, and skipping zero segments. // The zero segments are skipped by ignoring the _first_ coordinate(s) equal to the next ones for (size_t coordBIdx = firstSegmentWithLength + 1; coordBIdx < lastOriginalCoordIdx; coordBIdx++) { cvf::Vec3d coordA = m_originalPipeCenterCoords->get(coordBIdx - 1); cvf::Vec3d coordB = m_originalPipeCenterCoords->get(coordBIdx + 0); cvf::Vec3d coordC = m_originalPipeCenterCoords->get(coordBIdx + 1); cvf::Vec3d directionAB = coordB - coordA; if (directionAB.lengthSquared() > squareDistanceTolerance) { lastValidDirectionAB = directionAB.getNormalized(); lastValidSegment = coordBIdx; } // Wait to store a segment until we find an endpoint that is the start point of a valid segment cvf::Vec3d directionBC = coordC - coordB; if (directionBC.lengthSquared() < squareDistanceTolerance) { continue; } // Check if the angle between AB and BC is sharper than m_minimumBendAngle (Straight == 180 deg) // Sharper angle detected, insert bending stuff double cosMinBendAngle = cvf::Math::cos(cvf::Math::toRadians(m_minimumBendAngle)); double dotProduct = lastValidDirectionAB * (-directionBC).getNormalized(); if (dotProduct > cosMinBendAngle) { bool success = false; cvf::Vec3d pipeIntermediateDirection = (lastValidDirectionAB + directionBC.getNormalized()); pipeIntermediateDirection.getNormalized(&success); if (pipeIntermediateDirection.lengthSquared() < squareDistanceTolerance) { pipeIntermediateDirection = lastValidDirectionAB.perpendicularVector(); } else { pipeIntermediateDirection.normalize(); } double bendRadius = m_bendScalingFactor * m_radius + 1.0e-30; cvf::Vec3d firstIntermediate = coordB - pipeIntermediateDirection * bendRadius; cvf::Vec3d secondIntermediate = coordB + pipeIntermediateDirection * bendRadius; m_filteredPipeCenterCoords.push_back(firstIntermediate); m_filteredPipeSegmentToResult.push_back(lastValidSegment); m_filteredPipeCenterCoords.push_back(coordB); m_filteredPipeSegmentToResult.push_back(coordBIdx); m_filteredPipeCenterCoords.push_back(secondIntermediate); m_filteredPipeSegmentToResult.push_back(coordBIdx); } else { m_filteredPipeCenterCoords.push_back(coordB); m_filteredPipeSegmentToResult.push_back(coordBIdx); } } // Add the last point, as the above loop will not end the last none-zero segment, but wait for the start of the next valid one. m_filteredPipeCenterCoords.push_back(m_originalPipeCenterCoords->get(lastOriginalCoordIdx)); CVF_ASSERT(m_filteredPipeCenterCoords.size() - 1 == m_filteredPipeSegmentToResult.size()); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- size_t RivPipeGeometryGenerator::findFirstSegmentWithLenght(double squareDistanceTolerance) { size_t segIdx; for ( segIdx = 0; segIdx < m_originalPipeCenterCoords->size() - 1; segIdx++ ) { cvf::Vec3d candidateDir = (*m_originalPipeCenterCoords)[segIdx] - (*m_originalPipeCenterCoords)[segIdx+1]; double dirLengthSq = candidateDir.lengthSquared(); if ( dirLengthSq > squareDistanceTolerance && candidateDir.normalize() ) { return segIdx; } } return cvf::UNDEFINED_SIZE_T; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::clearComputedData() { m_filteredPipeCenterCoords.clear(); m_filteredPipeSegmentToResult.clear(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- size_t RivPipeGeometryGenerator::segmentIndexFromTriangleIndex(size_t triangleIndex) const { size_t segIndex = triangleIndex / (m_crossSectionNodeCount * 2); CVF_ASSERT(segIndex < m_filteredPipeSegmentToResult.size()); size_t resultIndex = m_filteredPipeSegmentToResult[segIndex]; return resultIndex + m_firstVisibleSegmentIndex; } //-------------------------------------------------------------------------------------------------- /// Well pipes are clipped, set index to first segment in visible well path //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::setFirstVisibleSegmentIndex(size_t segmentIndex) { m_firstVisibleSegmentIndex = segmentIndex; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivPipeGeometryGenerator::cylinderWithCenterLineParts(cvf::Collection* destinationParts, const std::vector& centerCoords, const cvf::Color3f& color, double radius) { setRadius(radius); setCrossSectionVertexCount(12); cvf::ref cvfCoords = new cvf::Vec3dArray(centerCoords); setPipeCenterCoords(cvfCoords.p()); cvf::ref surfaceGeo = createPipeSurface(); if (surfaceGeo.notNull()) { cvf::Part* part = new cvf::Part; part->setDrawable(surfaceGeo.p()); caf::SurfaceEffectGenerator surfaceGen(cvf::Color4f(color), caf::PO_1); cvf::ref eff = surfaceGen.generateCachedEffect(); part->setEffect(eff.p()); destinationParts->push_back(part); } cvf::ref centerLineGeo = createCenterLine(); if (centerLineGeo.notNull()) { cvf::Part* part = new cvf::Part; part->setDrawable(centerLineGeo.p()); caf::SurfaceEffectGenerator surfaceGen(cvf::Color4f(color), caf::PO_1); cvf::ref eff = surfaceGen.generateCachedEffect(); part->setEffect(eff.p()); destinationParts->push_back(part); } }