FPoint3 CreateUnitLinkVector(FPoint3 p1, FPoint3 p2) { FPoint3 v = p2 - p1; v.y = 0; v.Normalize(); return v; }
void vtLevel::GetEdgePlane(uint i, FPlane &plane) { vtEdge *edge = m_Edges[i]; int islope = edge->m_iSlope; float slope = (islope / 180.0f * PIf); int index = i; int ring = m_LocalFootprint.WhichRing(index); FLine3 &loop = m_LocalFootprint[ring]; uint ring_edges = loop.GetSize(); int next = (index+1 == ring_edges) ? 0 : index+1; // get edge vector FPoint3 vec = loop[next] - loop[index]; vec.Normalize(); // get perpendicular (upward pointing) vector FPoint3 perp; perp.Set(0, 1, 0); // create rotation matrix to rotate it upward FMatrix4 mat; mat.Identity(); mat.AxisAngle(vec, slope); // create normal FPoint3 norm; mat.TransformVector(perp, norm); plane.Set(loop[index], norm); }
FPoint3 LinkGeom::FindPointAlongRoad(float fDistance) { FPoint3 v; float length = 0.0f; if (fDistance <= 0) { static int c = 0; c++; return m_centerline[0]; } // compute 2D length of this link, by adding up the 2d link segment lengths for (uint j = 0; j < GetSize()-1; j++) { // consider length of next segment v.x = m_centerline[j+1].x - m_centerline[j].x; v.y = 0; v.z = m_centerline[j+1].z - m_centerline[j].z; length = v.Length(); if (fDistance <= length) { float fraction = fDistance / length; FPoint3 p0, p1, diff; p0 = m_centerline[j]; v *= fraction; return p0 + v; } fDistance -= length; } // if we pass the end of line, just return the last point return m_centerline[GetSize()-1]; }
FPoint3 vtTin::GetTriangleNormal(int iTriangle) const { FPoint3 wp0, wp1, wp2; _GetLocalTrianglePoints(iTriangle, wp0, wp1, wp2); FPoint3 norm = (wp1 - wp0).Cross(wp2 - wp0); norm.Normalize(); return norm; }
FPoint3 SidewaysVector(const FPoint3 &p0, const FPoint3 &p1) { FPoint3 diff = p1 - p0; FPoint3 up(0,1,0); FPoint3 cross = diff.Cross(up); cross.Normalize(); return cross; }
// // helper: given two points along a link, produce a vector // along to that link, parallel to the ground plane, // with length corresponding to the supplied width // FPoint3 CreateRoadVector(const FPoint3 &p1, const FPoint3 &p2, const float w) { FPoint3 v = p2 - p1; v.y = 0; v.Normalize(); v.x *= (w / 2.0f); v.z *= (w / 2.0f); return v; }
void AdaptiveLayerHeights::calculateMeshTriangleSlopes() { // loop over all mesh faces (triangles) and find their slopes for (const Mesh& mesh : Application::getInstance().current_slice->scene.current_mesh_group->meshes) { // Skip meshes that are not printable if (mesh.settings.get<bool>("infill_mesh") || mesh.settings.get<bool>("cutting_mesh") || mesh.settings.get<bool>("anti_overhang_mesh")) { continue; } for (const MeshFace& face : mesh.faces) { const MeshVertex& v0 = mesh.vertices[face.vertex_index[0]]; const MeshVertex& v1 = mesh.vertices[face.vertex_index[1]]; const MeshVertex& v2 = mesh.vertices[face.vertex_index[2]]; const FPoint3 p0 = v0.p; const FPoint3 p1 = v1.p; const FPoint3 p2 = v2.p; float min_z = p0.z; min_z = std::min(min_z, p1.z); min_z = std::min(min_z, p2.z); float max_z = p0.z; max_z = std::max(max_z, p1.z); max_z = std::max(max_z, p2.z); // calculate the angle of this triangle in the z direction const FPoint3 n = FPoint3(p1 - p0).cross(p2 - p0); const FPoint3 normal = n.normalized(); AngleRadians z_angle = std::acos(std::abs(normal.z)); // prevent flat surfaces from influencing the algorithm if (z_angle == 0) { z_angle = M_PI; } face_min_z_values.push_back(min_z * 1000); face_max_z_values.push_back(max_z * 1000); face_slopes.push_back(z_angle); } } }
// // Return the 2D length of this link segment in world units // float LinkGeom::Length() { FPoint3 v; v.y = 0; float length = 0.0f; // compute 2D length of this link, by adding up the 2d link segment lengths for (uint j = 0; j < GetSize(); j++) { if (j > 0) { // increment length v.x = m_centerline[j].x - m_centerline[j-1].x; v.z = m_centerline[j].z - m_centerline[j-1].z; float l = v.Length(); if (l < 0) { assert(false); } length += l; } } return length; }
// // Test code // void EnviroFrame::DoTestCode() { SetMode(MM_SLOPE); #if 0 // Shadow tests const int ReceivesShadowTraversalMask = 0x1; const int CastsShadowTraversalMask = 0x2; osg::ref_ptr<osgShadow::ShadowedScene> shadowedScene = new osgShadow::ShadowedScene; shadowedScene->setReceivesShadowTraversalMask(ReceivesShadowTraversalMask); shadowedScene->setCastsShadowTraversalMask(CastsShadowTraversalMask); #if 0 osg::ref_ptr<osgShadow::ShadowMap> sm = new osgShadow::ShadowMap; shadowedScene->setShadowTechnique(sm.get()); int mapres = 1024; sm->setTextureSize(osg::Vec2s(mapres,mapres)); #else osg::ref_ptr<osgShadow::ShadowTexture> sm = new osgShadow::ShadowTexture; shadowedScene->setShadowTechnique(sm.get()); #endif osg::Group* cessna1 = (osg::Group*) osgDB::readNodeFile("cessna.osg"); if (!cessna1) return; cessna1->setNodeMask(CastsShadowTraversalMask); cessna1->getChild(0)->setNodeMask(CastsShadowTraversalMask); osg::Group* cessna2 = (osg::Group*) osgDB::readNodeFile("cessna.osg"); if (!cessna2) return; int flags_off = ~(CastsShadowTraversalMask | ReceivesShadowTraversalMask); cessna2->setNodeMask(flags_off); cessna2->getChild(0)->setNodeMask(flags_off); osg::MatrixTransform* positioned = new osg::MatrixTransform; positioned->setDataVariance(osg::Object::STATIC); positioned->setMatrix(osg::Matrix::rotate(osg::inDegrees(-90.0f),0.0f,1.0f,0.0f) *osg::Matrix::translate(40,40,0)); positioned->addChild(cessna1); //osg::ref_ptr<osg::Group> shadowedScene = new osg::Group; shadowedScene->addChild(positioned); shadowedScene->addChild(cessna2); // osg::ref_ptr<osg::Group> container = new osg::Group; // container->addChild(positioned); // container->addChild(cessna2); vtGroup *vtg = GetCurrentTerrain()->GetTerrainGroup(); vtg->GetOsgGroup()->addChild(shadowedScene.get()); // vtg->GetOsgGroup()->addChild(container.get()); vtLogGraph(shadowedScene.get()); #endif #if 0 if (pTerr && g_App.m_bSelectedStruct) { vtStructureArray3d *sa = pTerr->GetStructureLayer(); int i = 0; while (!sa->GetAt(i)->IsSelected()) i++; vtBuilding3d *bld = sa->GetBuilding(i); // (Do something to the building as a test) sa->ConstructStructure(bld); } #endif #if 0 { // Read points from a text file, create OBJ file with geometry at that locations FILE *fp = fopen("test.txt", "r"); if (!fp) return; char buf[80]; float depth, x, y; // Add the geometry and materials to the shape vtGeode *pGeode = new vtGeode; vtMaterialArray *pMats = new vtMaterialArray; pMats->AddRGBMaterial(RGBf(1.0f, 1.0f, 1.0f), false, false, false); pGeode->SetMaterials(pMats); vtMesh *mesh = new vtMesh(osg::PrimitiveSet::TRIANGLES, VT_Normals | VT_Colors, 4000); int line = 0; fgets(buf, 80, fp); // skip first while (fgets(buf, 80, fp) != NULL) { sscanf(buf, "%f\t%f\t%f", &depth, &x, &y); int idx = mesh->NumVertices(); for (int i = 0; i < 20; i++) { double angle = (double)i / 20.0 * PI2d; FPoint3 vec; vec.x = x/2 * cos(angle); vec.y = 0.0f; vec.z = y/2 * sin(angle); // normal FPoint3 norm = vec; norm.Normalize(); // color RGBAf col(1.0f, 1.0f, 1.0f, 1.0f); if (x > y) { float frac = (x-y)/1.5f; // typical: 0 - 1.2 col.g -= frac; col.b -= frac; } else if (y > x) { float frac = (y-x)/1.5f; // typical: 0 - 1.2 col.r -= frac; col.g -= frac; } int add = mesh->AddVertexN(vec.x, /*650*/-depth, vec.z, norm.x, norm.y, norm.z); mesh->SetVtxColor(add, col); } if (line != 0) { for (int i = 0; i < 20; i++) { int next = (i+1)%20; mesh->AddTri(idx-20 + i, idx + i, idx-20 + next); mesh->AddTri(idx + i, idx + next, idx-20 + next); } } line++; } pGeode->AddMesh(mesh, 0); WriteGeomToOBJ(pGeode, "bore.obj"); vtTransform *model = new vtTransform; model->addChild(pGeode); DPoint3 pos; g_App.m_pTerrainPicker->GetCurrentEarthPos(pos); GetCurrentTerrain()->AddNode(model); GetCurrentTerrain()->PlantModelAtPoint(model, DPoint2(pos.x, pos.y)); } #endif }
/* Core code contributed by Kevin Behilo, 2/20/04. * * Possible TODO: add code to soften and blend shadow edges * (see aliasing comments in source). * * Definite TODO: the whole thing can be sped up by precalculating the * surface normals once. In fact that should be placed in a separate Shading * Context, so that it could be re-used for quickly re-shading multiple times. */ void vtHeightFieldGrid3d::ShadowCastDib(vtBitmapBase *pBM, const FPoint3 &light_dir, float fLightFactor, float fAmbient, bool progress_callback(int)) const { const IPoint2 bitmap_size = pBM->GetSize(); // Compute area that we will sample for shading, bounded by the texel // centers, which are 1/2 texel in from the grid extents. const DPoint2 texel_size(m_EarthExtents.Width() / bitmap_size.x, m_EarthExtents.Height() / bitmap_size.y); DRECT texel_area = m_EarthExtents; texel_area.Grow(-texel_size.x/2, -texel_size.y/2); const DPoint2 texel_base(texel_area.left, texel_area.bottom); const bool b8bit = (pBM->GetDepth() == 8); // These values are hardcoded here but could be exposed in the GUI const float sun = 0.7f; // If we have light that's pointing UP, rather than down at the terrain, // then it's only going to take a really long time to produce a // completely dark terrain. We can catch this case up front. if (light_dir.y > 0) { for (int i = 0; i < bitmap_size.x; i++) { for (int j = 0; j < bitmap_size.y; j++) { if (b8bit) pBM->ScalePixel8(i, j, fAmbient); else pBM->ScalePixel24(i, j, fAmbient); } } return; } // Create array to hold flags LightMap lightmap(bitmap_size.x, bitmap_size.y); // This factor is used when applying shading to non-shadowed areas to // try and keep the "contrast" down to a min. (still get "patches" of // dark/light spots though). // It is initialized to 1.0, because in case there are no shadows at all // (such as at noon) we still need a reasonable value. float darkest_shadow = 1.0; // For the vector used to cast shadows, we need it in grid coordinates, // which are (Column,Row) where Row is north. But the direction passed // in uses OpenGL coordinates where Z is south. So flip Z. FPoint3 grid_light_dir = light_dir; grid_light_dir.z = -grid_light_dir.z; // Scale the light vector such that the X or Z component (whichever is // larger) is 1. This is will serve as our direction vector in grid // coordinates, when drawing a line across the grid to cast the shadow. // // Code adapted from aaron_torpy: // http://www.geocities.com/aaron_torpy/algorithms.htm // float f, HScale; if ( fabs(grid_light_dir.x) > fabs(grid_light_dir.z) ) { HScale = m_fStep.x; f = fabs(light_dir.x); } else { HScale = m_fStep.y; f = fabs(light_dir.z); } grid_light_dir /= f; int i_init, i_final, i_incr; int j_init, j_final, j_incr; if (grid_light_dir.x > 0) { i_init=0; i_final=bitmap_size.x; i_incr=1; } else { i_init=bitmap_size.x-1; i_final=-1; i_incr=-1; } if (grid_light_dir.z > 0) { j_init=0; j_final=bitmap_size.y; j_incr=1; } else { j_init=bitmap_size.y-1; j_final=-1; j_incr=-1; } // First pass: find each point that it is in shadow. DPoint2 pos; float shadowheight, elevation; FPoint3 normal; FPoint3 p3; int x, z; float shade; for (int j = j_init; j != j_final; j += j_incr) { if (progress_callback != NULL && (j%20) == 0) progress_callback(abs(j-j_init) * 100 / bitmap_size.y); for (int i = i_init; i != i_final; i += i_incr) { pos = GridPos(texel_base, texel_size, i, j); FindAltitudeOnEarth(pos, shadowheight, true); if (shadowheight == INVALID_ELEVATION) { // set a flag so we won't visit this one again lightmap.Set(i, j, 1); continue; } bool Under_Out = false; for (int k = 1; Under_Out == false; k++) { x = (int) (i + grid_light_dir.x*k + 0.5f); z = (int) (j + grid_light_dir.z*k + 0.5f); shadowheight += grid_light_dir.y * HScale; if ((x<0) || (x>bitmap_size.x-1) || (z<0) || (z>bitmap_size.y-1)) { Under_Out = true; // Out of the grid break; } pos = GridPos(texel_base, texel_size, x, z); FindAltitudeOnEarth(pos, elevation, true); // skip holes in the grid if (elevation == INVALID_ELEVATION) continue; if (elevation > shadowheight) { if (k>1) Under_Out = true; // Under the terrain break; } // Combine color and shading. // Only do shadow if we have not shaded this i,j before. if (lightmap.Get(x,z) < 1) { // 3D elevation query to get slope m_LocalCS.EarthToLocal(pos, p3.x, p3.z); FindAltitudeAtPoint(p3, p3.y, true, 0, &normal); //***************************************** // Here the Sun(r, g, b) = 0 because we are in the shade // therefore I(r, g, b) = Amb(r, g, b) * (0.5*N[z] + 0.5) // shade = sun*normal.Dot(-light_direction) + fAmbient * (0.5f*normal.y + 0.5f); shade = fAmbient * (0.5f*normal.y + 0.5f); //***************************************** //***************************************** if (darkest_shadow > shade) darkest_shadow = shade; // Rather than doing the shading at this point we may want to // simply save the value into the LightMap array. Then apply // some anti-aliasing or edge softening algorithm to the LightMap. // Once that's done, apply the whole LightMap to the DIB. if (b8bit) pBM->ScalePixel8(x, bitmap_size.y-1-z, shade); else pBM->ScalePixel24(x, bitmap_size.y-1-z, shade); // set a flag to show that this texel has been shaded. // (or set to value of the shading - see comment above) lightmap.Set(x, z, lightmap.Get(x, z)+1); } } } //for i } //for j // For dot-product lighting, we use the normal 3D vector, only inverted // so that we can compare it to the upward-pointing ground normals. const FPoint3 inv_light_dir = -light_dir; // Second pass. Now we are going to loop through the LightMap and apply // the full lighting formula to each texel that has not been shaded yet. for (int j = 0; j < bitmap_size.y; j++) { if (progress_callback != NULL && (j%20) == 0) progress_callback(j * 100 / bitmap_size.y); for (int i = 0; i < bitmap_size.x; i++) { if (lightmap.Get(i, j) > 0) continue; pos = GridPos(texel_base, texel_size, i, j); // 2D elevation query to check for holes in the grid FindAltitudeOnEarth(pos, elevation, true); if (elevation == INVALID_ELEVATION) continue; // 3D elevation query to get slope m_LocalCS.EarthToLocal(pos, p3.x, p3.z); FindAltitudeAtPoint(p3, p3.y, true, 0, &normal); //***************************************** //***************************************** //shade formula based on: //http://www.geocities.com/aaron_torpy/algorithms.htm#calc_intensity // The Amb value was arbitrarily chosen // Need to experiment more to determine the best value // Perhaps calculating Sun(r, g, b) and Amb(r, g, b) for a // given time of day (e.g. warmer colors close to sunset) // or give control to user since textures will differ // I(r, g, b) = Sun(r, g, b) * scalarprod(N, v) + Amb(r, g, b) * (0.5*N[z] + 0.5) shade = sun * normal.Dot(inv_light_dir); // It's a reasonable assuption that an angle of 45 degrees is // sufficient to fully illuminate the ground. shade /= .7071f; // Now add ambient component shade += fAmbient * (0.5f*normal.y + 0.5f); // Maybe clipping values can be exposed to the user as well. // Clip - don't shade down below lowest ambient level if (shade < darkest_shadow) shade = darkest_shadow; else if (shade > 1.2f) shade = 1.2f; // Push the value of 'shade' toward 1.0 by the fLightFactor factor. // This means that fLightFactor=0 means no lighting, 1 means full lighting. float diff = 1 - shade; diff = diff * (1 - fLightFactor); shade += diff; // Rather than doing the shading at this point we may want to // simply save the value into the LightMap array. Then apply // some anti-aliasing or edge softening algorithm to the LightMap. // Once that's done, apply the whole LightMap to the DIB. // LightMap[I][J]= shade; // set to value of the shading - see comment above) if (b8bit) pBM->ScalePixel8(i, bitmap_size.y-1-j, shade); else pBM->ScalePixel24(i, bitmap_size.y-1-j, shade); } } // Possible TODO: Apply edge softening algorithm (?) }
/** * Create a set of points on the heightfield for a 2D polyline by draping the point onto * the surface. * * \param line The 2D line to drape, in Earth coordinates. * \param fSpacing The approximate spacing of the surface tessellation, used to * decide how finely to tessellate the line. * \param fOffset An offset to elevate each point in the resulting geometry, * useful for keeping it visibly above the ground. * \param bInterp True to interpolate between the vertices of the input * line. This is generally desirable when the ground is much more finely * spaced than the input line. * \param bCurve True to interpret the vertices of the input line as * control points of a curve. The created geometry will consist of * a draped line which passes through the control points. * \param bTrue True to use the true elevation of the terrain, ignoring * whatever scale factor is being used to exaggerate elevation for * display. * \param output Received the points. * \return The approximate length of the resulting 3D polyline. */ float vtHeightField3d::LineOnSurface(const DLine2 &line, float fSpacing, float fOffset, bool bInterp, bool bCurve, bool bTrue, FLine3 &output) { uint i, j; FPoint3 v1, v2, v; float fTotalLength = 0.0f; int iVerts = 0; uint points = line.GetSize(); if (bCurve) { DPoint2 p2, last(1E9,1E9); DPoint3 p3; int spline_points = 0; CubicSpline spline; for (i = 0; i < points; i++) { p2 = line[i]; if (i > 1 && p2 == last) continue; p3.Set(p2.x, p2.y, 0); spline.AddPoint(p3); spline_points++; last = p2; } spline.Generate(); // Estimate how many steps to subdivide this line into const double dLinearLength = line.Length(); float fLinearLength, dummy; m_LocalCS.VectorEarthToLocal(DPoint2(dLinearLength, 0.0), fLinearLength, dummy); double full = (double) (spline_points-1); int iSteps = (uint) (fLinearLength / fSpacing); if (iSteps < 3) iSteps = 3; double dStep = full / iSteps; FPoint3 last_v; for (double f = 0; f <= full; f += dStep) { spline.Interpolate(f, &p3); m_LocalCS.EarthToLocal(p3.x, p3.y, v.x, v.z); FindAltitudeAtPoint(v, v.y, bTrue); v.y += fOffset; output.Append(v); iVerts++; // keep a running total of approximate ground length if (f > 0) fTotalLength += (v - last_v).Length(); last_v = v; } } else { // not curved: straight line in earth coordinates FPoint3 last_v; for (i = 0; i < points; i++) { if (bInterp) { v1 = v2; m_LocalCS.EarthToLocal(line[i].x, line[i].y, v2.x, v2.z); if (i == 0) continue; // estimate how many steps to subdivide this segment into FPoint3 diff = v2 - v1; float fLen = diff.Length(); uint iSteps = (uint) (fLen / fSpacing); if (iSteps < 1) iSteps = 1; for (j = (i == 1 ? 0:1); j <= iSteps; j++) { // simple linear interpolation of the ground coordinate v.Set(v1.x + diff.x / iSteps * j, 0.0f, v1.z + diff.z / iSteps * j); FindAltitudeAtPoint(v, v.y, bTrue); v.y += fOffset; output.Append(v); iVerts++; // keep a running total of approximate ground length if (j > 0) fTotalLength += (v - last_v).Length(); last_v = v; } } else { m_LocalCS.EarthToLocal(line[i], v.x, v.z); FindAltitudeAtPoint(v, v.y, bTrue); v.y += fOffset; output.Append(v); } } } return fTotalLength; }
void vtIcoGlobe::BuildSphericalPoints(GlobeLayer *glay, float fSize) { vtFeatureSet *feat = glay->m_pSet; int i, j, size; vtArray<FSphere> spheres; size = feat->GetNumEntities(); spheres.SetSize(size); vtFeatureSetPoint2D *pSetP2 = dynamic_cast<vtFeatureSetPoint2D*>(feat); if (!pSetP2) return; DPoint2 p; for (i = 0; i < size; i++) { pSetP2->GetPoint(i, p); if (p.x == 0.0 && p.y == 0.0) // ignore some continue; FPoint3 loc; geo_to_xyz(1.0, p, loc); spheres[i].center = loc; spheres[i].radius = fSize; } FPoint3 diff; // volume of sphere, 4/3 PI r^3 // surface area of sphere, 4 PI r^2 // area of circle of sphere as seen from distance, PI r^2 int merges; do { merges = 0; // Try merging overlapping points together, so that information // is not lost in the overlap. // To consider: do we combine the blobs based on their 2d radius, // their 2d area, their 3d radius, or their 3d volume? See // Tufte, http://www.edwardtufte.com/ // Implemented here: preserve 2d area for (i = 0; i < size-1; i++) { for (j = i+1; j < size; j++) { if (spheres[i].radius == 0.0f || spheres[j].radius == 0.0f) continue; diff = spheres[i].center - spheres[j].center; // if one sphere contains the center of the other if (diff.Length() < spheres[i].radius || diff.Length() < spheres[j].radius) { // combine float area1 = PIf * spheres[i].radius * spheres[i].radius; float area2 = PIf * spheres[j].radius * spheres[j].radius; float combined = (area1 + area2); float newrad = sqrtf( combined / PIf ); // larger eats the smaller if (area1 > area2) { spheres[i].radius = newrad; spheres[j].radius = 0.0f; } else { spheres[j].radius = newrad; spheres[i].radius = 0.0f; } merges++; break; } } } } while (merges != 0); // Now create and place the little geometry objects to represent the // point data. #if 0 // create simple hemisphere mesh int res = 6; vtMesh *mesh = new vtMesh(osg::PrimitiveSet::TRIANGLE_STRIP, 0, res*res*2); FPoint3 scale(1.0f, 1.0f, 1.0f); mesh->CreateEllipsoid(scale, res, true); #else // create cylinder mesh instead int res = 14; int verts = res * 2; vtMesh *mesh = new vtMesh(osg::PrimitiveSet::TRIANGLE_STRIP, 0, verts); mesh->CreateCylinder(1.0f, 1.0f, res, true, false, false); #endif // use Area to show amount, otherwise height bool bArea = true; // create and place the geometries size = spheres.GetSize(); for (i = 0; i < size; i++) { if (spheres[i].radius == 0.0f) continue; vtGeode *geode = new vtGeode; geode->SetMaterials(m_coremats); geode->AddMesh(mesh, m_yellow); vtMovGeode *mgeom = new vtMovGeode(geode); mgeom->setName("GlobeShape"); mgeom->PointTowards(spheres[i].center); mgeom->RotateLocal(FPoint3(1,0,0), -PID2f); mgeom->SetTrans(spheres[i].center); if (bArea) { // scale just the radius of the cylinder mgeom->Scale3(spheres[i].radius, 0.001f, spheres[i].radius); } else { // scale just the height of the cylinder double area = PIf * spheres[i].radius * spheres[i].radius; mgeom->Scale3(0.002f, (float)area*1000, 0.002f); } m_SurfaceGroup->addChild(mgeom); glay->addChild(mgeom); } }
/*! Returns the index of the 'other' face connected to the edge between vertices with indices idx0 and idx1. In case more than two faces are connected via the same edge, the next face in a counter-clockwise ordering (looking from idx1 to idx0) is returned. \cond DOXYGEN_EXCLUDE [NON-RENDERED COMENTS] For two faces abc and abd with normals n and m, we have that: \f{eqnarray*}{ n &=& \frac{ab \times ac}{\|ab \times ac\|} \\ m &=& \frac{ab \times ad}{\|ab \times ad\|} \\ n \times m &=& \|n\| \cdot \|m\| \mathbf{p} \sin \alpha \\ && (\mathbf{p} \perp n \wedge \mathbf{p} \perp m) \\ \sin \alpha &=& \|n \times m \| &=& \left\| \frac{(ab \times ac) \times (ab \times ad)}{\|ab \times ac\| \cdot \|ab \times ad\|} \right\| \\ &=& \left\| \frac{ (ab \cdot (ac \times ad)) ab }{\|ab \times ac\| \cdot \|ab \times ad\|} \right\| \\ &=& \frac{ (ab \cdot (ac \times ad)) \left\| ab \right\| }{\|ab\| \|ac\| \sin bac \cdot \|ab\| \|ad\| \sin bad} \\ &=& \frac{ ab \cdot (ac \times ad) }{\|ab\| \|ac\| \|ad\| \sin bac \sin bad} \\ \f}} \endcond See <a href="http://stackoverflow.com/questions/14066933/direct-way-of-computing-clockwise-angle-between-2-vectors">Direct way of computing clockwise angle between 2 vectors</a> */ int Mesh::getFaceIdxWithPoints(int idx0, int idx1, int notFaceIdx) const { std::vector<int> candidateFaces; // in case more than two faces meet at an edge, multiple candidates are generated int notFaceVertexIdx = -1; // index of the third vertex of the face corresponding to notFaceIdx for(int f : vertices[idx0].connected_faces) // search through all faces connected to the first vertex and find those that are also connected to the second { if (f == notFaceIdx) { for (int i = 0; i<3; i++) // find the vertex which is not idx0 or idx1 if (faces[f].vertex_index[i] != idx0 && faces[f].vertex_index[i] != idx1) notFaceVertexIdx = faces[f].vertex_index[i]; continue; } if ( faces[f].vertex_index[0] == idx1 // && faces[f].vertex_index[1] == idx0 // next face should have the right direction! || faces[f].vertex_index[1] == idx1 // && faces[f].vertex_index[2] == idx0 || faces[f].vertex_index[2] == idx1 // && faces[f].vertex_index[0] == idx0 ) candidateFaces.push_back(f); } if (candidateFaces.size() == 0) { cura::logError("Couldn't find face connected to face %i.\n", notFaceIdx); return -1; } if (candidateFaces.size() == 1) { return candidateFaces[0]; } if (notFaceVertexIdx < 0) { cura::logError("Couldn't find third point on face %i.\n", notFaceIdx); return -1; } if (candidateFaces.size() % 2 == 0) cura::log("Warning! Edge with uneven number of faces connecting it!(%i)\n", candidateFaces.size()+1); FPoint3 vn = vertices[idx1].p - vertices[idx0].p; FPoint3 n = vn / vn.vSize(); // the normal of the plane in which all normals of faces connected to the edge lie => the normalized normal FPoint3 v0 = vertices[idx1].p - vertices[idx0].p; // the normals below are abnormally directed! : these normals all point counterclockwise (viewed from idx1 to idx0) from the face, irrespective of the direction of the face. FPoint3 n0 = FPoint3(vertices[notFaceVertexIdx].p - vertices[idx0].p).cross(v0); if (n0.vSize() <= 0) cura::log("Warning! Face %i has zero area!", notFaceIdx); double smallestAngle = 1000; // more then 2 PI (impossible angle) int bestIdx = -1; for (int candidateFace : candidateFaces) { int candidateVertex; {// find third vertex belonging to the face (besides idx0 and idx1) for (candidateVertex = 0; candidateVertex<3; candidateVertex++) if (faces[candidateFace].vertex_index[candidateVertex] != idx0 && faces[candidateFace].vertex_index[candidateVertex] != idx1) break; } FPoint3 v1 = vertices[candidateVertex].p -vertices[idx0].p; FPoint3 n1 = v1.cross(v0); double dot = n0 * n1; double det = n * n0.cross(n1); double angle = std::atan2(det, dot); if (angle < 0) angle += 2*M_PI; // 0 <= angle < 2* M_PI if (angle == 0) { cura::log("Warning! Overlapping faces: face %i and face %i.\n", notFaceIdx, candidateFace); std::cerr<< n.vSize() <<"; "<<n1.vSize()<<";"<<n0.vSize() <<std::endl; } if (angle < smallestAngle) { smallestAngle = angle; bestIdx = candidateFace; } } if (bestIdx < 0) cura::logError("Couldn't find face connected to face %i.\n", notFaceIdx); return bestIdx; }
void vtFence3d::AddProfileConnectionMesh(const FLine3 &p3) { uint i, j, npoints = p3.GetSize(), prof_points = m_Profile.GetSize(); // Must have at least 2 points in the profile if (prof_points < 2) return; // Each segment of the profile becomes a long triangle strip. // If there are no shared vertices between segments, the number of // vertices is, for a profile of N points and a line of P points: // P * (N-1) * 2, for the sides // N*2, for the end caps (or more if we have to tessellate) // int iEstimateVerts = npoints * (prof_points-1) * 2 + (prof_points * 2); vtMesh *pMesh = new vtMesh(osg::PrimitiveSet::TRIANGLE_STRIP, VT_TexCoords | VT_Normals, iEstimateVerts); vtMaterialDescriptor *desc = GetMatDescriptor(m_Params.m_ConnectMaterial); if (!desc) { VTLOG1("Warning: could not find material: "); VTLOG1(m_Params.m_ConnectMaterial); VTLOG1("\n"); return; } FPoint2 uvscale = desc->GetUVScale(); // determine side-pointing vector vtArray<float> ExtraElevation(npoints); FLine3 sideways(npoints); for (j = 0; j < npoints; j++) { // determine side-pointing vector if (j == 0) sideways[j] = SidewaysVector(p3[j], p3[j+1]); else if (j > 0 && j < npoints-1) { AngleSideVector(p3[j-1], p3[j], p3[j+1], sideways[j]); sideways[j] = -sideways[j]; // We want a vector pointing left, not right } else if (j == npoints-1) sideways[j] = SidewaysVector(p3[j-1], p3[j]); ExtraElevation[j] = 0.0f; if (m_Params.m_bConstantTop) ExtraElevation[j] = m_fMaxGroundY - p3[j].y; } float u; float v1, v2; for (i = 0; i < prof_points-1; i++) { float y1, y2; float z1, z2; FPoint3 pos, normal; // determine v texture coordinate float seg_length = m_Profile.SegmentLength(i); if (uvscale.y == -1) { v1 = 0.0f; v2 = 1.0f; } else { if (i == 0) { v1 = 0.0f; v2 = seg_length / uvscale.y; } else { v1 = v2; v2 += seg_length / uvscale.y; } } // determine Y and Z values y1 = m_Profile[i].y; y2 = m_Profile[i+1].y; z1 = m_Profile[i].x; z2 = m_Profile[i+1].x; u = 0.0f; int start = pMesh->NumVertices(); for (j = 0; j < npoints; j++) { // determine vertex normal (for shading) float diffy = y2-y1; float diffz = z2-z1; float dy = -diffz; float dz = diffy; FPoint3 n1, n2; n1.Set(0, y1, 0); n1 += (sideways[j] * z1); n2.Set(0, y1 + dy, 0); n2 += (sideways[j] * (z1 + dz)); normal = n2 - n1; normal.Normalize(); // determine the two points of this segment edge, and add them pos = p3[j]; pos.y += y2; pos.y += ExtraElevation[j]; pos += (sideways[j] * z2); pMesh->AddVertexNUV(pos, normal, FPoint2(u, v2)); pos = p3[j]; pos.y += y1; pos.y += ExtraElevation[j]; pos += (sideways[j] * z1); pMesh->AddVertexNUV(pos, normal, FPoint2(u, v1)); if (j < npoints-1) { // increment u based on the length of each fence segment float length_meters = (p3[j+1] - p3[j]).Length(); u += (length_meters / uvscale.x); } } pMesh->AddStrip2(npoints * 2, start); } // We must assume the profile is interpreted as a closed polygon, which // may not be convex. Hence it must be triangulated. FLine2 result; Triangulate_f::Process(m_Profile, result); uint tcount = result.GetSize()/3; int ind[3]; int line_point; FPoint3 normal; // add cap at beginning line_point = 0; normal = p3[0] - p3[1]; normal.Normalize(); for (i=0; i<tcount; i++) { for (j = 0; j < 3; j++) { FPoint2 p2 = result[i*3+j]; FPoint3 pos = p3[line_point]; pos.y += p2.y; pos.y += ExtraElevation[line_point]; pos += (sideways[line_point] * p2.x); FPoint2 uv = p2; if (uvscale.y != -1) uv.Div(uvscale); // divide meters by [meters/uv] to get uv ind[j] = pMesh->AddVertexNUV(pos, normal, uv); } pMesh->AddTri(ind[0], ind[1], ind[2]); } // add cap at end line_point = npoints-1; normal = p3[npoints-1] - p3[npoints-2]; normal.Normalize(); for (i=0; i<tcount; i++) { for (j = 0; j < 3; j++) { FPoint2 p2 = result[i*3+j]; FPoint3 pos = p3[line_point]; pos.y += p2.y; pos.y += ExtraElevation[line_point]; pos += (sideways[line_point] * p2.x); FPoint2 uv = p2; if (uvscale.y != -1) uv.Div(uvscale); // divide meters by [meters/uv] to get uv ind[j] = pMesh->AddVertexNUV(pos, normal, uv); } pMesh->AddTri(ind[0], ind[2], ind[1]); } m_pFenceGeom->AddMesh(pMesh, GetMatIndex(desc)); }