/****************************************************************************** * Renders the geometry. ******************************************************************************/ void OpenGLArrowPrimitive::render(SceneRenderer* renderer) { OVITO_REPORT_OPENGL_ERRORS(); OVITO_ASSERT(_contextGroup == QOpenGLContextGroup::currentContextGroup()); OVITO_ASSERT(_elementCount >= 0); OVITO_ASSERT(_mappedChunkIndex == -1); ViewportSceneRenderer* vpRenderer = dynamic_object_cast<ViewportSceneRenderer>(renderer); if(_elementCount <= 0 || !vpRenderer) return; vpRenderer->rebindVAO(); if(shadingMode() == NormalShading) { if(renderingQuality() == HighQuality && shape() == CylinderShape) renderWithElementInfo(vpRenderer); else renderWithNormals(vpRenderer); } else if(shadingMode() == FlatShading) { renderWithElementInfo(vpRenderer); } OVITO_REPORT_OPENGL_ERRORS(); }
/****************************************************************************** * Returns the actual rendering quality used to render the given particles. ******************************************************************************/ ParticlePrimitive::RenderingQuality ParticleDisplay::effectiveRenderingQuality(SceneRenderer* renderer, ParticlePropertyObject* positionProperty) const { ParticlePrimitive::RenderingQuality renderQuality = renderingQuality(); if(renderQuality == ParticlePrimitive::AutoQuality) { if(!positionProperty) return ParticlePrimitive::HighQuality; size_t particleCount = positionProperty->size(); if(particleCount < 2000 || renderer->isInteractive() == false) renderQuality = ParticlePrimitive::HighQuality; else if(particleCount < 100000) renderQuality = ParticlePrimitive::MediumQuality; else renderQuality = ParticlePrimitive::LowQuality; } return renderQuality; }
/****************************************************************************** * Renders the geometry as with extra information passed to the vertex shader. ******************************************************************************/ void OpenGLArrowPrimitive::renderWithElementInfo(ViewportSceneRenderer* renderer) { QOpenGLShaderProgram* shader = renderer->isPicking() ? _pickingShader : _shader; if(!shader) return; if(!shader->bind()) throw Exception(QStringLiteral("Failed to bind OpenGL shader.")); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); shader->setUniformValue("modelview_matrix", (QMatrix4x4)renderer->modelViewTM()); shader->setUniformValue("modelview_uniform_scale", (float)pow(std::abs(renderer->modelViewTM().determinant()), (FloatType(1.0/3.0)))); shader->setUniformValue("modelview_projection_matrix", (QMatrix4x4)(renderer->projParams().projectionMatrix * renderer->modelViewTM())); shader->setUniformValue("projection_matrix", (QMatrix4x4)renderer->projParams().projectionMatrix); shader->setUniformValue("inverse_projection_matrix", (QMatrix4x4)renderer->projParams().inverseProjectionMatrix); shader->setUniformValue("is_perspective", renderer->projParams().isPerspective); AffineTransformation viewModelTM = renderer->modelViewTM().inverse(); Vector3 eye_pos = viewModelTM.translation(); shader->setUniformValue("eye_pos", eye_pos.x(), eye_pos.y(), eye_pos.z()); Vector3 viewDir = viewModelTM * Vector3(0,0,1); shader->setUniformValue("parallel_view_dir", viewDir.x(), viewDir.y(), viewDir.z()); GLint viewportCoords[4]; glGetIntegerv(GL_VIEWPORT, viewportCoords); shader->setUniformValue("viewport_origin", (float)viewportCoords[0], (float)viewportCoords[1]); shader->setUniformValue("inverse_viewport_size", 2.0f / (float)viewportCoords[2], 2.0f / (float)viewportCoords[3]); GLint pickingBaseID; if(renderer->isPicking()) { pickingBaseID = renderer->registerSubObjectIDs(elementCount()); renderer->activateVertexIDs(shader, _chunkSize * _verticesPerElement, true); OVITO_CHECK_OPENGL(shader->setUniformValue("verticesPerElement", (GLint)_verticesPerElement)); } for(int chunkIndex = 0; chunkIndex < _verticesWithElementInfo.size(); chunkIndex++, pickingBaseID += _chunkSize) { int chunkStart = chunkIndex * _chunkSize; int chunkSize = std::min(_elementCount - chunkStart, _chunkSize); if(renderer->isPicking()) shader->setUniformValue("pickingBaseID", pickingBaseID); _verticesWithElementInfo[chunkIndex].bindPositions(renderer, shader, offsetof(VertexWithElementInfo, pos)); _verticesWithElementInfo[chunkIndex].bind(renderer, shader, "cylinder_base", GL_FLOAT, offsetof(VertexWithElementInfo, base), 3, sizeof(VertexWithElementInfo)); _verticesWithElementInfo[chunkIndex].bind(renderer, shader, "cylinder_axis", GL_FLOAT, offsetof(VertexWithElementInfo, dir), 3, sizeof(VertexWithElementInfo)); _verticesWithElementInfo[chunkIndex].bind(renderer, shader, "cylinder_radius", GL_FLOAT, offsetof(VertexWithElementInfo, radius), 1, sizeof(VertexWithElementInfo)); if(!renderer->isPicking()) _verticesWithElementInfo[chunkIndex].bindColors(renderer, shader, 4, offsetof(VertexWithElementInfo, color)); if(_usingGeometryShader && (shadingMode() == FlatShading || renderingQuality() == HighQuality)) { OVITO_CHECK_OPENGL(glDrawArrays(GL_POINTS, 0, chunkSize)); } else { int stripPrimitivesPerElement = _stripPrimitiveVertexCounts.size() / _chunkSize; OVITO_CHECK_OPENGL(renderer->glMultiDrawArrays(GL_TRIANGLE_STRIP, _stripPrimitiveVertexStarts.data(), _stripPrimitiveVertexCounts.data(), stripPrimitivesPerElement * chunkSize)); int fanPrimitivesPerElement = _fanPrimitiveVertexCounts.size() / _chunkSize; OVITO_CHECK_OPENGL(renderer->glMultiDrawArrays(GL_TRIANGLE_FAN, _fanPrimitiveVertexStarts.data(), _fanPrimitiveVertexCounts.data(), fanPrimitivesPerElement * chunkSize)); } _verticesWithElementInfo[chunkIndex].detachPositions(renderer, shader); _verticesWithElementInfo[chunkIndex].detach(renderer, shader, "cylinder_base"); _verticesWithElementInfo[chunkIndex].detach(renderer, shader, "cylinder_axis"); _verticesWithElementInfo[chunkIndex].detach(renderer, shader, "cylinder_radius"); if(!renderer->isPicking()) _verticesWithElementInfo[chunkIndex].detachColors(renderer, shader); } shader->enableAttributeArray("cylinder_base"); shader->enableAttributeArray("cylinder_axis"); shader->enableAttributeArray("cylinder_radius"); if(renderer->isPicking()) renderer->deactivateVertexIDs(shader, true); shader->release(); }
/****************************************************************************** * Creates the geometry for a single cylinder element. ******************************************************************************/ void OpenGLArrowPrimitive::createCylinderElement(int index, const Point3& pos, const Vector3& dir, const ColorA& color, FloatType width) { if(_usingGeometryShader && (shadingMode() == FlatShading || renderingQuality() == HighQuality)) { OVITO_ASSERT(_mappedVerticesWithElementInfo); OVITO_ASSERT(_verticesPerElement == 1); VertexWithElementInfo* vertex = _mappedVerticesWithElementInfo + index; vertex->pos = vertex->base = pos; vertex->dir = dir; vertex->color = color; vertex->radius = width; return; } if(shadingMode() == NormalShading) { // Build local coordinate system. Vector_3<float> t, u, v; float length = dir.length(); if(length != 0) { t = dir / length; if(dir.y() != 0 || dir.x() != 0) u = Vector_3<float>(dir.y(), -dir.x(), 0); else u = Vector_3<float>(-dir.z(), 0, dir.x()); u.normalize(); v = u.cross(t); } else { t.setZero(); u.setZero(); v.setZero(); } ColorAT<float> c = color; Point_3<float> v1 = pos; Point_3<float> v2 = v1 + dir; if(renderingQuality() != HighQuality) { OVITO_ASSERT(_mappedVerticesWithNormals); VertexWithNormal* vertex = _mappedVerticesWithNormals + (index * _verticesPerElement); // Generate vertices for cylinder mantle. for(int i = 0; i <= _cylinderSegments; i++) { Vector_3<float> n = _cosTable[i] * u + _sinTable[i] * v; Vector_3<float> d = n * width; vertex->pos = v1 + d; vertex->normal = n; vertex->color = c; vertex++; vertex->pos = v2 + d; vertex->normal = n; vertex->color = c; vertex++; } // Generate vertices for first cylinder cap. for(int i = 0; i < _cylinderSegments; i++) { Vector_3<float> n = _cosTable[i] * u + _sinTable[i] * v; Vector_3<float> d = n * width; vertex->pos = v1 + d; vertex->normal = Vector_3<float>(0,0,-1); vertex->color = c; vertex++; } // Generate vertices for second cylinder cap. for(int i = _cylinderSegments - 1; i >= 0; i--) { Vector_3<float> n = _cosTable[i] * u + _sinTable[i] * v; Vector_3<float> d = n * width; vertex->pos = v2 + d; vertex->normal = Vector_3<float>(0,0,1); vertex->color = c; vertex++; } } else { // Create bounding box geometry around cylinder for raytracing. OVITO_ASSERT(_mappedVerticesWithElementInfo); VertexWithElementInfo* vertex = _mappedVerticesWithElementInfo + (index * _verticesPerElement); OVITO_ASSERT(_verticesPerElement == 14); u *= width; v *= width; Point3 corners[8] = { v1 - u - v, v1 - u + v, v1 + u - v, v1 + u + v, v2 - u - v, v2 - u + v, v2 + u + v, v2 + u - v }; const static size_t stripIndices[14] = { 3,2,6,7,4,2,0,3,1,6,5,4,1,0 }; for(int i = 0; i < 14; i++, vertex++) { vertex->pos = corners[stripIndices[i]]; vertex->base = v1; vertex->dir = dir; vertex->color = c; vertex->radius = width; } } } else if(shadingMode() == FlatShading) { Vector_3<float> t; float length = dir.length(); if(length != 0) t = dir / length; else t.setZero(); ColorAT<float> c = color; Point_3<float> base = pos; OVITO_ASSERT(_mappedVerticesWithElementInfo); VertexWithElementInfo* vertices = _mappedVerticesWithElementInfo + (index * _verticesPerElement); vertices[0].pos = Point_3<float>(0, width, 0); vertices[1].pos = Point_3<float>(0, -width, 0); vertices[2].pos = Point_3<float>(length, -width, 0); vertices[3].pos = Point_3<float>(length, width, 0); for(int i = 0; i < _verticesPerElement; i++, ++vertices) { vertices->base = base; vertices->dir = t; vertices->color = c; } } }
/****************************************************************************** * Allocates a particle buffer with the given number of elements. ******************************************************************************/ void OpenGLArrowPrimitive::startSetElements(int elementCount) { OVITO_ASSERT(elementCount >= 0); OVITO_ASSERT(QOpenGLContextGroup::currentContextGroup() == _contextGroup); OVITO_ASSERT(_mappedChunkIndex == -1); _verticesWithNormals.clear(); _verticesWithElementInfo.clear(); _elementCount = elementCount; bool renderMesh = true; int stripsPerElement; int fansPerElement; int verticesPerStrip; int verticesPerFan; if(shadingMode() == NormalShading) { verticesPerStrip = _cylinderSegments * 2 + 2; verticesPerFan = _cylinderSegments; if(shape() == ArrowShape) { stripsPerElement = 2; fansPerElement = 2; } else { stripsPerElement = 1; fansPerElement = 2; if(renderingQuality() == HighQuality) { if(_usingGeometryShader) { verticesPerStrip = 1; stripsPerElement = 1; } else { verticesPerStrip = 14; } fansPerElement = verticesPerFan = 0; renderMesh = false; } } } else if(shadingMode() == FlatShading) { fansPerElement = 1; stripsPerElement = 0; verticesPerStrip = 0; if(shape() == ArrowShape) verticesPerFan = 7; else verticesPerFan = 4; if(_usingGeometryShader && shape() == CylinderShape) { verticesPerFan = 1; } renderMesh = false; } else OVITO_ASSERT(false); // Determine the VBO chunk size. _verticesPerElement = stripsPerElement * verticesPerStrip + fansPerElement * verticesPerFan; int bytesPerVertex = renderMesh ? sizeof(VertexWithNormal) : sizeof(VertexWithElementInfo); _chunkSize = std::min(_maxVBOSize / _verticesPerElement / bytesPerVertex, _elementCount); // Allocate VBOs. for(int i = _elementCount; i > 0; i -= _chunkSize) { if(renderMesh) { OpenGLBuffer<VertexWithNormal> buffer; buffer.create(QOpenGLBuffer::StaticDraw, std::min(i, _chunkSize), _verticesPerElement); _verticesWithNormals.push_back(buffer); } else { OpenGLBuffer<VertexWithElementInfo> buffer; buffer.create(QOpenGLBuffer::StaticDraw, std::min(i, _chunkSize), _verticesPerElement); _verticesWithElementInfo.push_back(buffer); } } OVITO_REPORT_OPENGL_ERRORS(); // Prepare arrays to be passed to the glMultiDrawArrays() function. _stripPrimitiveVertexStarts.resize(_chunkSize * stripsPerElement); _stripPrimitiveVertexCounts.resize(_chunkSize * stripsPerElement); _fanPrimitiveVertexStarts.resize(_chunkSize * fansPerElement); _fanPrimitiveVertexCounts.resize(_chunkSize * fansPerElement); std::fill(_stripPrimitiveVertexCounts.begin(), _stripPrimitiveVertexCounts.end(), verticesPerStrip); std::fill(_fanPrimitiveVertexCounts.begin(), _fanPrimitiveVertexCounts.end(), verticesPerFan); auto ps_strip = _stripPrimitiveVertexStarts.begin(); auto ps_fan = _fanPrimitiveVertexStarts.begin(); for(GLint index = 0, baseIndex = 0; index < _chunkSize; index++) { for(int p = 0; p < stripsPerElement; p++, baseIndex += verticesPerStrip) *ps_strip++ = baseIndex; for(int p = 0; p < fansPerElement; p++, baseIndex += verticesPerFan) *ps_fan++ = baseIndex; } // Precompute cos() and sin() functions. if(shadingMode() == NormalShading) { _cosTable.resize(_cylinderSegments+1); _sinTable.resize(_cylinderSegments+1); for(int i = 0; i <= _cylinderSegments; i++) { float angle = (FLOATTYPE_PI * 2 / _cylinderSegments) * i; _cosTable[i] = std::cos(angle); _sinTable[i] = std::sin(angle); } } }