//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(GeometryBuilderFaceList, AddVertices) { Vec3fArray inputVertexArr; inputVertexArr.reserve(3); inputVertexArr.add(Vec3f::X_AXIS); inputVertexArr.add(Vec3f::Y_AXIS); inputVertexArr.add(Vec3f::Z_AXIS); GeometryBuilderFaceList b; int indexOfFirstVertex = b.addVertices(inputVertexArr); EXPECT_EQ(0, indexOfFirstVertex); EXPECT_EQ(3u, b.vertices()->size()); indexOfFirstVertex = b.addVertices(inputVertexArr); EXPECT_EQ(3, indexOfFirstVertex); ref<Vec3fArray> va = b.vertices(); EXPECT_EQ(6u, va->size()); EXPECT_TRUE(va->get(0) == Vec3f::X_AXIS); EXPECT_TRUE(va->get(1) == Vec3f::Y_AXIS); EXPECT_TRUE(va->get(2) == Vec3f::Z_AXIS); EXPECT_TRUE(va->get(3) == Vec3f::X_AXIS); EXPECT_TRUE(va->get(4) == Vec3f::Y_AXIS); EXPECT_TRUE(va->get(5) == Vec3f::Z_AXIS); }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(ArrayTest, CopyConvertedData) { // Double array to float { DoubleArray ad; ad.resize(4); ad[0] = 0.0; ad[1] = 1.0; ad[2] = 2.0; ad[3] = 3.0; // Copy full array FloatArray af; af.resize(4); af.copyConvertedData(ad, 4, 0, 0); EXPECT_FLOAT_EQ(0.0f, af[0]); EXPECT_FLOAT_EQ(1.0f, af[1]); EXPECT_FLOAT_EQ(2.0f, af[2]); EXPECT_FLOAT_EQ(3.0f, af[3]); // Copy partial array to float array af.resize(2); af.setAll(0); af.copyConvertedData(ad, 2, 0, 1); EXPECT_FLOAT_EQ(1.0f, af[0]); EXPECT_FLOAT_EQ(2.0f, af[1]); } // Vec3d to Vec3f and Vec3i { Vec3dArray ad; ad.resize(2); ad[0].set(1.1, 2.5, 3.9); ad[1].set(11.1, 12.5, 13.9); Vec3fArray af; af.resize(2); af.copyConvertedData(ad, 2, 0, 0); EXPECT_FLOAT_EQ(1.1f, af[0].x()); EXPECT_FLOAT_EQ(2.5f, af[0].y()); EXPECT_FLOAT_EQ(3.9f, af[0].z()); EXPECT_FLOAT_EQ(11.1f, af[1].x()); EXPECT_FLOAT_EQ(12.5f, af[1].y()); EXPECT_FLOAT_EQ(13.9f, af[1].z()); Array<Vec3i> ai; ai.resize(2); ai.copyConvertedData(ad, 2, 0, 0); EXPECT_EQ(1, ai[0].x()); EXPECT_EQ(2, ai[0].y()); EXPECT_EQ(3, ai[0].z()); EXPECT_EQ(11, ai[1].x()); EXPECT_EQ(12, ai[1].y()); EXPECT_EQ(13, ai[1].z()); } }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void UniformFloat::setArray(const Vec3fArray& values) { size_t numValues = values.size(); CVF_ASSERT(numValues > 0); m_type = FLOAT_VEC3; m_data.resize(3*numValues); m_data.copyData(values.ptr()->ptr(), 3*numValues, 0); }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void GeometryBuilder::addTriangleByVertices(const Vec3f& v0, const Vec3f& v1, const Vec3f& v2) { Vec3fArray verts; verts.resize(3); verts[0] = v0; verts[1] = v1; verts[2] = v2; uint firstVertexIdx = addVertices(verts); addTriangle(firstVertexIdx, firstVertexIdx + 1, firstVertexIdx + 2); }
//-------------------------------------------------------------------------------------------------- /// Create a disk with a hole in the middle //-------------------------------------------------------------------------------------------------- void GeometryUtils::createDisc(double outerRadius, double innerRadius, uint numSlices, GeometryBuilder* builder) { CVF_ASSERT(numSlices >= 4); CVF_ASSERT(builder); double da = 2*PI_D/numSlices; Vec3fArray verts; verts.reserve(2*numSlices); Vec3f point = Vec3f::ZERO; uint i; for (i = 0; i < numSlices; i++) { // Precompute this one (A = i*da;) double sinA = Math::sin(i*da); double cosA = Math::cos(i*da); point.x() = static_cast<float>(-sinA*innerRadius); point.y() = static_cast<float>( cosA*innerRadius); verts.add(point); point.x() = static_cast<float>(-sinA*outerRadius); point.y() = static_cast<float>( cosA*outerRadius); verts.add(point); } uint baseNodeIdx = builder->addVertices(verts); uint conn[3] = { baseNodeIdx, 0, 0}; for (i = 0; i < numSlices - 1; ++i) { uint startIdx = baseNodeIdx + 2*i; conn[0] = startIdx + 0; conn[1] = startIdx + 3; conn[2] = startIdx + 1; builder->addTriangle(conn[0], conn[1], conn[2]); conn[0] = startIdx + 2; conn[1] = startIdx + 3; conn[2] = startIdx + 0; builder->addTriangle(conn[0], conn[1], conn[2]); } builder->addTriangle(baseNodeIdx + 0, baseNodeIdx + 1, baseNodeIdx + numSlices*2 - 1); builder->addTriangle(baseNodeIdx + 0, baseNodeIdx + numSlices*2 - 1, baseNodeIdx + numSlices*2 - 2); }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void GeometryBuilder::addQuadByVertices(const Vec3f& v0, const Vec3f& v1, const Vec3f& v2, const Vec3f& v3) { Vec3fArray verts; verts.resize(4); verts[0] = v0; verts[1] = v1; verts[2] = v2; verts[3] = v3; uint firstVertexIdx = addVertices(verts); addQuad(firstVertexIdx, firstVertexIdx + 1, firstVertexIdx + 2, firstVertexIdx + 3); }
//-------------------------------------------------------------------------------------------------- /// Create a disc centered at origin with its normal along positive z-axis /// /// \param radius Outer radius of the disc /// \param numSlices The number of subdivisions around the z-axis. Must be >= 4 /// \param builder Geometry builder to use when creating geometry /// /// Creates a disc on the z = 0 plane, centered at origin and with its surface normal pointing /// along the positive z-axis. /// /// The disk is subdivided around the z axis into numSlices (as in pizza slices). /// /// The sourceNodes that will be produced by this method: /// <PRE> /// 1 /// /-----\ 8 /// 2/\ | /\ |y /// / \ | / \ | /// | \|/ | | /// 3|----0----|7 | /// | /|\ | *-----x /// \ / | \ / / /// 4\/ | \/6 /z /// \-----/ /// 5 </PRE> /// /// The following triangle connectivities will be produced:\n /// <TT> (0,1,2) (0,2,3) (0,3,4) ... (0,8,1)</TT> //-------------------------------------------------------------------------------------------------- void GeometryUtils::createDisc(double radius, uint numSlices, GeometryBuilder* builder) { CVF_ASSERT(numSlices >= 4); CVF_ASSERT(builder); double da = 2*PI_D/numSlices; Vec3fArray verts; verts.reserve(numSlices + 1); // Center of disc verts.add(Vec3f::ZERO); Vec3f point = Vec3f::ZERO; uint i; for (i = 0; i < numSlices; i++) { // Precompute this one (A = i*da;) double sinA = Math::sin(i*da); double cosA = Math::cos(i*da); point.x() = static_cast<float>(-sinA*radius); point.y() = static_cast<float>( cosA*radius); verts.add(point); } uint baseNodeIdx = builder->addVertices(verts); // Vec3fArray myArray; // myArray.resize(10); // generatePointsOnCircle(radius, numSlices, &myArray); uint conn[3] = { baseNodeIdx, 0, 0}; for (i = numSlices; i > 0; i--) { conn[1] = baseNodeIdx + i + 1; conn[2] = baseNodeIdx + i + 0; if (i == numSlices) conn[1] = baseNodeIdx + 1; builder->addTriangle(conn[0], conn[1], conn[2]); } }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void BoundingBox::add(const Vec3fArray& points) { size_t i; for (i = 0; i < points.size(); i++) { add(points[i]); } }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(ArrayTest, SetPtr) { float* f = new float[6]; f[0] = 1; f[1] = 2; f[2] = 3; f[3] = 10; f[4] = 11; f[5] = 12; Vec3fArray af; af.setPtr(reinterpret_cast<Vec3f*> (f), 2); // Naughty! How to do this differently f = 0; // af has owership ASSERT_FLOAT_EQ(1.0f, af[0].x()); ASSERT_FLOAT_EQ(2.0f, af[0].y()); ASSERT_FLOAT_EQ(3.0f, af[0].z()); ASSERT_EQ(true, af[1] == Vec3f(10,11,12)); }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(ArrayTest, AssignFromRawArray) { float* f = new float[6]; f[0] = 1; f[1] = 2; f[2] = 3; f[3] = 10; f[4] = 11; f[5] = 12; Vec3fArray af; af.assign(reinterpret_cast<Vec3f*> (f), 2); // Naughty! How to do this differently delete[] f; f = 0; ASSERT_FLOAT_EQ(1.0f, af[0].x()); ASSERT_FLOAT_EQ(2.0f, af[0].y()); ASSERT_FLOAT_EQ(3.0f, af[0].z()); ASSERT_EQ(true, af[1] == Vec3f(10,11,12)); }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(ModelBasicListTest, MergePartsWithTransformation) { Vec3fArray* verts = new Vec3fArray; verts->reserve(3); verts->add(Vec3f(0, 0, 0)); verts->add(Vec3f(1, 0, 0)); verts->add(Vec3f(1, 1, 0)); Vec3fArray* norms = new Vec3fArray; norms->resize(3); norms->set(0, Vec3f::Z_AXIS); norms->set(1, Vec3f::Z_AXIS); norms->set(2, Vec3f::Z_AXIS); DrawableGeo* myGeo = new DrawableGeo; myGeo->setFromTriangleVertexArray(verts); myGeo->setNormalArray(norms); Part* myPart = new Part; myPart->setDrawable(myGeo); Part* myPart2 = new Part; myPart2->setDrawable(myGeo); ref<ModelBasicList> myModel = new ModelBasicList; myModel->addPart(myPart); myModel->addPart(myPart2); EXPECT_EQ(2, myModel->partCount()); Mat4d matrix; matrix.setTranslation(Vec3d(10, 20, 30)); Transform* transform = new Transform; transform->setLocalTransform(matrix); myPart2->setTransform(transform); myModel->mergeParts(1000, 1000); EXPECT_EQ(1, myModel->partCount()); Part* mergedPart = myModel->part(0); DrawableGeo* mergedGeo = dynamic_cast<DrawableGeo*>(mergedPart->drawable()); const Vec3fArray* vertices = mergedGeo->vertexArray(); EXPECT_EQ(6, vertices->size()); Vec3f v5 = vertices->get(5); EXPECT_EQ(11, v5.x()); EXPECT_EQ(21, v5.y()); EXPECT_EQ(30, v5.z()); }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void DrawableVectors::createUploadBufferObjectsGPU(OpenGLContext* oglContext) { if (!m_renderWithVBO || m_vertexArray->size() == 0 || m_vectorGlyph.isNull() || m_vectorGlyphPrimSet.isNull()) { return; } if (m_glyphVerticesAndNormalsBO.isNull() || !m_glyphVerticesAndNormalsBO->isUploaded()) { // Build a interleaved VBO for glyphs vertices and normals to get better performance // during the main shader based draw path size_t numVertices = m_vectorGlyph->vertexArray()->size(); Vec3fArray data; data.reserve(numVertices*2); size_t i; for (i = 0; i < numVertices; i++) { data.add(m_vectorGlyph->vertexArray()->get(i)); data.add(m_vectorGlyph->normalArray()->get(i)); } GLuint uiSizeInBytes = static_cast<GLuint>(data.size()*3*sizeof(float)); m_glyphVerticesAndNormalsBO = oglContext->resourceManager()->getOrCreateManagedBufferObject(oglContext, GL_ARRAY_BUFFER, uiSizeInBytes, data.ptr()->ptr()); } if (m_indicesBO.isNull() || !m_indicesBO->isUploaded()) { const UShortArray* indices = m_vectorGlyphPrimSet->indices(); size_t numIndices = indices->size(); if (numIndices > 0) { GLuint uiSizeInBytes = static_cast<GLuint>(numIndices*sizeof(GLushort)); m_indicesBO = oglContext->resourceManager()->getOrCreateManagedBufferObject(oglContext, GL_ELEMENT_ARRAY_BUFFER, uiSizeInBytes, indices->ptr()); CVF_CHECK_OGL(oglContext); } } }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(ArrayTest, ptrToIdx) { // Vec3f array Vec3fArray vA; vA.resize(4); ASSERT_EQ(4u, vA.size()); vA[0] = Vec3f(1,2,3); vA[1] = Vec3f(1.1f, 2.2f, 3.3f); vA[2] = Vec3f(0,0,0); vA[3] = Vec3f(4,5,6); Vec3f* p1 = vA.ptr(1); ASSERT_FLOAT_EQ(1.1f, p1->x()); ASSERT_FLOAT_EQ(2.2f, p1->y()); ASSERT_FLOAT_EQ(3.3f, p1->z()); Vec3f* p3 = vA.ptr(3); ASSERT_FLOAT_EQ(4, p3->x()); ASSERT_FLOAT_EQ(5, p3->y()); ASSERT_FLOAT_EQ(6, p3->z()); }
//-------------------------------------------------------------------------------------------------- /// Create a 2D patch /// /// \param origin The start point of the patch /// \param uUnit Direction vector u. First point 'to the right of' origin is origin + uUnit. /// \param vUnit Direction vector v. Coordinates of first point 'above' origin is origin + vunit. /// \param uCellCount The number of cells/quads to generate along the uUnit dimension. /// \param vCellCount The number of cells/quads to generate along the vUnit dimension. /// \param builder Geometry builder to use when creating geometry /// /// The figure below illustrates how the patch is constructed from the specified parameters. /// /// <PRE> /// v8-----v9----v10----v11 Parameters: Resulting vertices: /// | | | | origin = (10,20,0) v0 = (10,20,0) /// origin | | | | uUnit = (2,0,0) v1 = (12,20,0) /// + vunit v4-----v5-----v6-----v7 |y vUnit = (0,1,0) v2 = (14,20,0) /// | | | | | uCellCount = 3 v3 = (16,20,0) /// | | | | | vCellCount = 2 v4 = (10,21,0) /// v0-----v1-----v2-----v3 *----x v5 = (12,21,0) /// origin origin : /// + uUnit </PRE> /// /// The following quad connectivities will be produced:\n /// <TT> (v4,v0,v1,v5) (v5,v1,v2,v6) (v6,v2,v3,v5) ... (v10,v6,v7,v11)</TT> //-------------------------------------------------------------------------------------------------- void GeometryUtils::createPatch(const Vec3f& origin, const Vec3f& uUnit, const Vec3f& vUnit, uint uCellCount, uint vCellCount, GeometryBuilder* builder) { CVF_ASSERT(uCellCount > 0); CVF_ASSERT(vCellCount > 0); uint numVertices = (uCellCount + 1)*(vCellCount + 1); uint numQuads = uCellCount*vCellCount; Vec3fArray vertices; vertices.reserve(numVertices); uint u, v; for (v = 0; v <= vCellCount; v++) { for (u = 0; u <= uCellCount; u++) { vertices.add(origin + static_cast<float>(u)*uUnit + static_cast<float>(v)*vUnit); } } uint baseNodeIdx = builder->addVertices(vertices); UIntArray conn; conn.reserve(4*numQuads); for (v = 0; v < vCellCount; v++) { for (u = 0; u < uCellCount; u++) { conn.add(baseNodeIdx + u + (v + 1)*(uCellCount + 1)); conn.add(baseNodeIdx + u + v*(uCellCount + 1)); conn.add(baseNodeIdx + u + 1 + v*(uCellCount + 1)); conn.add(baseNodeIdx + u + 1 + (v + 1)*(uCellCount + 1)); } } builder->addQuads(conn); }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(ArrayTest, SharedData) { float* f = new float[6]; f[0] = 1; f[1] = 2; f[2] = 3; f[3] = 10; f[4] = 11; f[5] = 12; { Vec3fArray af; af.setSharedPtr(reinterpret_cast<Vec3f*> (f), 2); // Naughty! How to do this differently ASSERT_FLOAT_EQ(1.0f, af[0].x()); ASSERT_FLOAT_EQ(2.0f, af[0].y()); ASSERT_FLOAT_EQ(3.0f, af[0].z()); ASSERT_EQ(true, af[1] == Vec3f(10,11,12)); } ASSERT_FLOAT_EQ(1.0f, f[0]); ASSERT_FLOAT_EQ(2.0f, f[1]); ASSERT_FLOAT_EQ(3.0f, f[2]); }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(ModelBasicListTest, MergeParts) { Vec3fArray* verts = new Vec3fArray; verts->reserve(3); verts->add(Vec3f(0, 0, 0)); verts->add(Vec3f(1, 0, 0)); verts->add(Vec3f(1, 1, 0)); Vec3fArray* norms = new Vec3fArray; norms->resize(3); norms->set(0, Vec3f::Z_AXIS); norms->set(1, Vec3f::Z_AXIS); norms->set(2, Vec3f::Z_AXIS); DrawableGeo* myGeo = new DrawableGeo; myGeo->setFromTriangleVertexArray(verts); myGeo->setNormalArray(norms); Part* myPart = new Part; myPart->setDrawable(myGeo); Part* myPart2 = new Part; myPart2->setDrawable(myGeo); ref<ModelBasicList> myModel = new ModelBasicList; myModel->addPart(myPart); myModel->addPart(myPart2); EXPECT_EQ(2, myModel->partCount()); myModel->mergeParts(1000, 1000); EXPECT_EQ(1, myModel->partCount()); Part* mergedPart = myModel->part(0); DrawableGeo* mergedGeo = dynamic_cast<DrawableGeo*>(mergedPart->drawable()); const Vec3fArray* vertices = mergedGeo->vertexArray(); EXPECT_EQ(6, vertices->size()); Vec3f v5 = vertices->get(5); EXPECT_EQ(1, v5.x()); EXPECT_EQ(1, v5.y()); EXPECT_EQ(0, v5.z()); }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(ArrayTest, BasicVec3fArray) { // Vec3f array Vec3fArray vA; vA.resize(4); ASSERT_EQ(4u, vA.size()); vA[0] = Vec3f(1,2,3); vA[1] = Vec3f(1.1f, 2.2f, 3.3f); vA[2] = Vec3f(0,0,0); vA[3] = Vec3f(4,5,6); ASSERT_EQ(true, vA[0] == Vec3f(1, 2, 3)); ASSERT_EQ(true, vA[1] == Vec3f(1.1f, 2.2f, 3.3f)); ASSERT_EQ(true, vA[2] == Vec3f(0, 0, 0)); ASSERT_EQ(true, vA[3] == Vec3f(4, 5, 6)); const float* pf = vA.ptr()->ptr(); ASSERT_FLOAT_EQ(1.0f, pf[0]); ASSERT_FLOAT_EQ(2.0f, pf[1]); ASSERT_FLOAT_EQ(3.0f, pf[2]); ASSERT_FLOAT_EQ(1.1f, pf[3]); ASSERT_FLOAT_EQ(2.2f, pf[4]); ASSERT_FLOAT_EQ(3.3f, pf[5]); ASSERT_FLOAT_EQ(0.0f, pf[6]); ASSERT_FLOAT_EQ(0.0f, pf[7]); ASSERT_FLOAT_EQ(0.0f, pf[8]); ASSERT_FLOAT_EQ(4.0f, pf[9]); ASSERT_FLOAT_EQ(5.0f, pf[10]); ASSERT_FLOAT_EQ(6.0f, pf[11]); vA.clear(); ASSERT_EQ(0u, vA.size()); }
//-------------------------------------------------------------------------------------------------- /// Create a (possibly oblique) cylinder oriented along the z-axis /// /// \param bottomRadius Bottom radius of cylinder /// \param topRadius Top radius of cylinder /// \param height Height of cylinder /// \param topOffsetX Offset top disc relative to bottom in X direction /// \param topOffsetY Offset top disc relative to bottom in Y direction /// \param numSlices Number of slices /// \param normalsOutwards true to generate polygons with outward facing normals. /// \param closedBot true to close the bottom of the cylinder with a disc /// \param closedTop true to close the top of the cylinder with a disc /// \param numPolysZDir Number of (subdivisions) polygons along the Z axis. /// \param builder Geometry builder to use when creating geometry /// /// An oblique cylinder is a cylinder with bases that are not aligned one directly above the other /// The base of the cylinder is placed at z = 0, and the top at z = height. /// Cylinder is subdivided around the z-axis into slices. /// Use the cone functions instead of setting one of the radius params to 0 //-------------------------------------------------------------------------------------------------- void GeometryUtils::createObliqueCylinder(float bottomRadius, float topRadius, float height, float topOffsetX, float topOffsetY, uint numSlices, bool normalsOutwards, bool closedBot, bool closedTop, uint numPolysZDir, GeometryBuilder* builder) { // Create cylinder... Vec3f centBot(0, 0, 0); Vec3f centTop(topOffsetX, topOffsetY, height); // Create vertices uint zPoly; for (zPoly = 0; zPoly <= numPolysZDir; zPoly++) { float fT = static_cast<float>((1.0/numPolysZDir)*(zPoly)); float radius = bottomRadius + fT*(topRadius - bottomRadius); Vec3f center(fT*topOffsetX, fT*topOffsetY, fT*height); Vec3fArray verts; verts.reserve(numSlices); Vec3f point = Vec3f::ZERO; double da = 2*PI_D/numSlices; uint i; for (i = 0; i < numSlices; i++) { // Precompute this one (A = i*da;) double sinA = Math::sin(i*da); double cosA = Math::cos(i*da); point.x() = static_cast<float>(-sinA*radius); point.y() = static_cast<float>( cosA*radius); point.z() = 0; point += center; verts.add(point); } uint baseNodeIdx = builder->addVertices(verts); // First time we only create the sourceNodes if (zPoly != 0) { uint offset = baseNodeIdx - numSlices; uint piConn[4] = { 0, 0, 0, 0 }; // Normals facing outwards if (normalsOutwards) { uint i; for (i = 0; i < numSlices; i++) { piConn[0] = offset + i; piConn[1] = offset + i + 1; piConn[2] = offset + i + numSlices + 1; piConn[3] = offset + i + numSlices; if (i == numSlices - 1) { piConn[1] = offset; piConn[2] = offset + numSlices; } builder->addQuad(piConn[0], piConn[1], piConn[2], piConn[3]); } } // Normals facing inwards else { uint i; for (i = 0; i < numSlices; i++) { piConn[0] = offset + i + 1; piConn[1] = offset + i; piConn[2] = offset + i + numSlices; piConn[3] = offset + i + numSlices + 1; if (i == numSlices - 1) { piConn[0] = offset; piConn[3] = offset + numSlices; } builder->addQuad(piConn[0], piConn[1], piConn[2], piConn[3]); } } } } if (closedBot) { createDisc(bottomRadius, numSlices, builder); } if (closedTop) { uint startIdx = builder->vertexCount(); createDisc(topRadius, numSlices, builder); uint endIdx = builder->vertexCount() - 1; // Translate the top disc sourceNodes, also flip it to get the normals the right way Mat4f mat = Mat4f::fromRotation(Vec3f(1.0f, 0.0f, 0.0f), Math::toRadians(180.0f)); mat.translatePreMultiply(Vec3f(topOffsetX, topOffsetY, height)); builder->transformVertexRange(startIdx, endIdx, mat); } }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(ModelBasicListTest, MergePartsCheckBB) { Vec3fArray* verts = new Vec3fArray; verts->reserve(3); verts->add(Vec3f(0, 0, 0)); verts->add(Vec3f(1, 0, 0)); verts->add(Vec3f(1, 1, 0)); Vec3fArray* norms = new Vec3fArray; norms->resize(3); norms->set(0, Vec3f::Z_AXIS); norms->set(1, Vec3f::Z_AXIS); norms->set(2, Vec3f::Z_AXIS); DrawableGeo* myGeo = new DrawableGeo; myGeo->setFromTriangleVertexArray(verts); myGeo->setNormalArray(norms); ref<ModelBasicList> myModel = new ModelBasicList; { Part* myPart = new Part; myPart->setDrawable(myGeo); Mat4d matrix; matrix.setTranslation(Vec3d(10, 20, 30)); Transform* transform = new Transform; transform->setLocalTransform(matrix); myPart->setTransform(transform); myModel->addPart(myPart); } { Part* myPart2 = new Part; myPart2->setDrawable(myGeo); Mat4d matrix; matrix.setTranslation(Vec3d(20, 20, 30)); Transform* transform2 = new Transform; transform2->setLocalTransform(matrix); myPart2->setTransform(transform2); myModel->addPart(myPart2); } { Part* myPart3 = new Part; myPart3->setDrawable(myGeo); Mat4d matrix; matrix.setTranslation(Vec3d(100, 20, 30)); Transform* transform3 = new Transform; transform3->setLocalTransform(matrix); myPart3->setTransform(transform3); myModel->addPart(myPart3); } { Part* myPart4 = new Part; myPart4->setDrawable(myGeo); Mat4d matrix; matrix.setTranslation(Vec3d(110, 20, 30)); Transform* transform4 = new Transform; transform4->setLocalTransform(matrix); myPart4->setTransform(transform4); myModel->addPart(myPart4); } EXPECT_EQ(4, myModel->partCount()); myModel->mergeParts(1, 1000); EXPECT_EQ(4, myModel->partCount()); myModel->mergeParts(20, 1000); EXPECT_EQ(2, myModel->partCount()); myModel->mergeParts(200, 1000); EXPECT_EQ(1, myModel->partCount()); }
//-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- TEST(VertexCompactorTest, Quads) { // Vertices, three possible quads // 4------5------6------7 // | Q1 | Q2 | Q3 | // | | | | // 0------1------2------3 Vec3fArray orgVertices; orgVertices.reserve(8); orgVertices.add(Vec3f(0, 0, 0)); orgVertices.add(Vec3f(1, 0, 0)); orgVertices.add(Vec3f(2, 0, 0)); orgVertices.add(Vec3f(3, 0, 0)); orgVertices.add(Vec3f(0, 1, 0)); orgVertices.add(Vec3f(1, 1, 0)); orgVertices.add(Vec3f(2, 1, 0)); orgVertices.add(Vec3f(3, 1, 0)); const cvf::uint connQ1[4] = { 0, 1, 5, 4 }; const cvf::uint connQ2[4] = { 1, 2, 6, 5 }; //const cvf::uint connQ3[4] = { 2, 3, 7, 6 }; // Q2 { UIntArray orgConn(connQ2, 4); VertexCompactor vc(orgConn, orgVertices); ref<UIntArray> indices = vc.indices(); ASSERT_EQ(4, indices->size()); EXPECT_EQ(0, indices->get(0)); EXPECT_EQ(1, indices->get(1)); EXPECT_EQ(2, indices->get(2)); EXPECT_EQ(3, indices->get(3)); ref<Vec3fArray> va = vc.vertexArray(); ASSERT_EQ(4, va->size()); EXPECT_TRUE(Vec3f(1, 0, 0) == va->get(0)); EXPECT_TRUE(Vec3f(2, 0, 0) == va->get(1)); EXPECT_TRUE(Vec3f(2, 1, 0) == va->get(2)); EXPECT_TRUE(Vec3f(1, 1, 0) == va->get(3)); ref<UIntArray> vertexSourceIndices = vc.perVertexOriginalIndices(); ASSERT_EQ(4, vertexSourceIndices->size()); EXPECT_EQ(1, vertexSourceIndices->get(0)); EXPECT_EQ(2, vertexSourceIndices->get(1)); EXPECT_EQ(6, vertexSourceIndices->get(2)); EXPECT_EQ(5, vertexSourceIndices->get(3)); } // Q1 + Q2 { UIntArray orgConn(8); orgConn.copyData(connQ1, 4, 0); orgConn.copyData(connQ2, 4, 4); VertexCompactor vc(orgConn, orgVertices); ref<UIntArray> indices = vc.indices(); ASSERT_EQ(8, indices->size()); EXPECT_EQ(0, indices->get(0)); EXPECT_EQ(1, indices->get(1)); EXPECT_EQ(2, indices->get(2)); EXPECT_EQ(3, indices->get(3)); EXPECT_EQ(1, indices->get(4)); EXPECT_EQ(4, indices->get(5)); EXPECT_EQ(5, indices->get(6)); EXPECT_EQ(2, indices->get(7)); ref<Vec3fArray> va = vc.vertexArray(); ASSERT_EQ(6, va->size()); EXPECT_TRUE(Vec3f(0, 0, 0) == va->get(0)); EXPECT_TRUE(Vec3f(1, 0, 0) == va->get(1)); EXPECT_TRUE(Vec3f(1, 1, 0) == va->get(2)); EXPECT_TRUE(Vec3f(0, 1, 0) == va->get(3)); EXPECT_TRUE(Vec3f(2, 0, 0) == va->get(4)); EXPECT_TRUE(Vec3f(2, 1, 0) == va->get(5)); ref<UIntArray> vertexSourceIndices = vc.perVertexOriginalIndices(); ASSERT_EQ(6, vertexSourceIndices->size()); EXPECT_EQ(0, vertexSourceIndices->get(0)); EXPECT_EQ(1, vertexSourceIndices->get(1)); EXPECT_EQ(5, vertexSourceIndices->get(2)); EXPECT_EQ(4, vertexSourceIndices->get(3)); EXPECT_EQ(2, vertexSourceIndices->get(4)); EXPECT_EQ(6, vertexSourceIndices->get(5)); } }
//-------------------------------------------------------------------------------------------------- /// Create a sphere with center in origin /// /// \param radius Radius of sphere /// \param numSlices The number of subdivisions around the z-axis (similar to lines of longitude). /// \param numStacks The number of subdivisions along the z-axis (similar to lines of latitude). /// \param builder Geometry builder to use when creating geometry //-------------------------------------------------------------------------------------------------- void GeometryUtils::createSphere(double radius, uint numSlices, uint numStacks, GeometryBuilder* builder) { // Code is strongly inspired by mesa. // From GLviewAPI: // float nsign = bNormalsOutwards ? 1.0f : -1.0f; // Could be added as a param if needed (e.g. dome) const double nsign = 1.0; double rho = PI_D/numStacks; double theta = 2.0*PI_D/static_cast<double>(numSlices); // Array to receive the node coordinates Vec3fArray vertices; uint vertexCount = 1 + 2*numSlices + (numStacks - 2)*numSlices; vertices.reserve(vertexCount); // Create the +Z end as triangles Vec3d vTop(0.0, 0.0, nsign*radius); vertices.add(Vec3f(vTop)); ref<UIntArray> triangleFan = new UIntArray; triangleFan->reserve(numSlices + 2); triangleFan->add(0); uint j; for (j = 0; j < numSlices; j++) { double localTheta = j * theta; Vec3d v; v.x() = -Math::sin(localTheta) * Math::sin(rho); v.y() = Math::cos(localTheta) * Math::sin(rho); v.z() = nsign * Math::cos(rho); v *= radius; vertices.add(Vec3f(v)); triangleFan->add(j + 1); } // Close top fan triangleFan->add(1); builder->addTriangleFan(*triangleFan); // Intermediate stacks as quad-strips // First and last stacks are handled separately ref<UIntArray> quadStrip = new UIntArray; quadStrip->reserve(numSlices*2 + 2); uint i; for (i = 1; i < numStacks - 1; i++) { double localRho = i * rho; quadStrip->setSizeZero(); for (j = 0; j < numSlices; j++) { double localTheta = j * theta; Vec3d v; v.x() = -Math::sin(localTheta) * Math::sin(localRho + rho); v.y() = Math::cos(localTheta) * Math::sin(localRho + rho); v.z() = nsign * Math::cos(localRho + rho); v *= radius; vertices.add(Vec3f(v)); uint iC1 = (i*numSlices) + 1 + j; uint iC0 = iC1 - numSlices; quadStrip->add(iC0); quadStrip->add(iC1); } // Close quad-strip uint iStartC1 = (i*numSlices) + 1; uint iStartC0 = iStartC1 - numSlices; quadStrip->add(iStartC0); quadStrip->add(iStartC1); builder->addQuadStrip(*quadStrip); } // Create -Z end as triangles Vec3d vBot( 0.0, 0.0, -radius*nsign ); vertices.add(Vec3f(vBot)); uint endNodeIndex = static_cast<uint>(vertices.size()) - 1; triangleFan->setSizeZero(); triangleFan->add(endNodeIndex); for (j = 0; j < numSlices; j++) { triangleFan->add(endNodeIndex - j - 1); } // Close bottom fan triangleFan->add(endNodeIndex - 1); builder->addTriangleFan(*triangleFan); builder->addVertices(vertices); }
//-------------------------------------------------------------------------------------------------- /// Create a cone oriented along the z-axis /// /// \param bottomRadius Bottom radius of cone /// \param height Height of cone /// \param numSlices Number of slices /// \param normalsOutwards true to generate polygons with outward facing normals. /// \param closedBot true to close the bottom of the cone with a disc /// \param singleTopNode Specify if a single top node should be used, or if each side triangle /// should have its own top node. /// \param builder Geometry builder to use when creating geometry /// //-------------------------------------------------------------------------------------------------- void GeometryUtils::createCone(float bottomRadius, float height, uint numSlices, bool normalsOutwards, bool closedBot, bool singleTopNode, GeometryBuilder* builder) { Vec3fArray verts; if (singleTopNode) { verts.reserve(numSlices + 1); } else { verts.reserve(numSlices*2); } Vec3f point = Vec3f::ZERO; double da = 2*PI_D/numSlices; uint i; for (i = 0; i < numSlices; i++) { // Precompute this one (A = i*da;) double sinA = Math::sin(i*da); double cosA = Math::cos(i*da); point.x() = static_cast<float>(-sinA*bottomRadius); point.y() = static_cast<float>( cosA*bottomRadius); verts.add(point); } if (singleTopNode) { verts.add(Vec3f(0, 0, height)); } else { // Unique sourceNodes at apex of cone Vec3f topNode(0, 0, height); uint i; for (i = 0; i < numSlices; i++) { verts.add(topNode); } } uint baseNodeIdx = builder->addVertices(verts); uint piConn[3] = { 0, 0, 0 }; // Normals facing outwards if (normalsOutwards) { uint i; for (i = 0; i < numSlices; i++) { piConn[0] = baseNodeIdx + i; piConn[1] = baseNodeIdx + i + 1; piConn[2] = singleTopNode ? baseNodeIdx + numSlices : baseNodeIdx + i + numSlices; if (i == numSlices - 1) { piConn[1] = baseNodeIdx; } if (normalsOutwards) { builder->addTriangle(piConn[0], piConn[1], piConn[2]); } else { builder->addTriangle(piConn[1], piConn[0], piConn[2]); } } } if (closedBot) { createDisc(bottomRadius, numSlices, builder); } }