void SoCylinder::rayPick(SoRayPickAction *action) // //////////////////////////////////////////////////////////////////////// { // First see if the object is pickable if (! shouldRayPick(action)) return; int curParts =(parts.isIgnored() ? ALL : parts.getValue()); SbLine pickLine; float radius, halfHeight; SbVec3f enterPoint, exitPoint, normal; SbVec4f texCoord; SoPickedPoint *pp; SoCylinderDetail *detail; SbBool materialPerPart; int numHits = 0; // Compute the picking ray in our current object space computeObjectSpaceRay(action); // Get size of this cylinder getSize(radius, halfHeight); // Construct an infinite cylinder to test sides for intersection SbCylinder infiniteCyl; infiniteCyl.setRadius(radius); SoMaterialBindingElement::Binding mbe = SoMaterialBindingElement::get(action->getState()); materialPerPart = (mbe == SoMaterialBindingElement::PER_PART_INDEXED || mbe == SoMaterialBindingElement::PER_PART); // See if the line intersects the cylinder if (HAS_PART(curParts, SIDES) && infiniteCyl.intersect(action->getLine(), enterPoint, exitPoint)) { // See if the enter point is within the real cylinder and is // between the near and far clipping planes. if (enterPoint[1] <= halfHeight && enterPoint[1] >= -halfHeight) { numHits++; if (action->isBetweenPlanes(enterPoint) && (pp = action->addIntersection(enterPoint)) != NULL) { // The normal at the point is the same as the point but // with a 0 y coordinate normal.setValue(enterPoint[0], 0.0, enterPoint[2]); normal.normalize(); pp->setObjectNormal(normal); texCoord.setValue(atan2f(enterPoint[0], enterPoint[2]) * (1.0 / (2.0 * M_PI)) + 0.5, (enterPoint[1] + halfHeight) / (2.0 * halfHeight), 0.0, 1.0); pp->setObjectTextureCoords(texCoord); detail = new SoCylinderDetail(); detail->setPart(SIDES); pp->setDetail(detail, this); } } // Do same for exit point if (exitPoint[1] <= halfHeight && exitPoint[1] >= -halfHeight) { numHits++; if (action->isBetweenPlanes(exitPoint) && (pp = action->addIntersection(exitPoint)) != NULL) { normal.setValue(exitPoint[0], 0.0, exitPoint[2]); normal.normalize(); pp->setObjectNormal(normal); texCoord.setValue(atan2f(exitPoint[0], exitPoint[2]) * (1.0 / (2.0 * M_PI)) + 0.5, (exitPoint[1] + halfHeight) / (2.0 * halfHeight), 0.0, 1.0); pp->setObjectTextureCoords(texCoord); detail = new SoCylinderDetail(); detail->setPart(SIDES); pp->setDetail(detail, this); } } } // If we haven't hit the cylinder twice already, check for an // intersection with the top face if (numHits < 2 && HAS_PART(curParts, TOP)) { SbVec3f norm(0.0, 1.0, 0.0); // Construct a plane containing the top face SbPlane topFacePlane(norm, halfHeight); // See if the ray hits this plane if (topFacePlane.intersect(action->getLine(), enterPoint)) { // See if the intersection is within the correct radius // and is within the clipping planes float distFromYAxisSquared = (enterPoint[0] * enterPoint[0] + enterPoint[2] * enterPoint[2]); if (distFromYAxisSquared <= radius * radius) { numHits++; if (action->isBetweenPlanes(enterPoint) && (pp = action->addIntersection(enterPoint)) != NULL) { pp->setObjectNormal(norm); texCoord.setValue(0.5 + enterPoint[0] / (2.0 * radius), 0.5 - enterPoint[2] / (2.0 * radius), 0.0, 1.0); pp->setObjectTextureCoords(texCoord); if (materialPerPart) pp->setMaterialIndex(1); detail = new SoCylinderDetail(); detail->setPart(TOP); pp->setDetail(detail, this); } } } } // If we haven't hit the cylinder twice already, check for an // intersection with the bottom face if (numHits < 2 && HAS_PART(curParts, BOTTOM)) { SbVec3f norm(0.0, -1.0, 0.0); // Construct a plane containing the bottom face SbPlane bottomFacePlane(norm, halfHeight); // See if the ray hits this plane if (bottomFacePlane.intersect(action->getLine(), enterPoint)) { // See if the intersection is within the correct radius // and is within the clipping planes float distFromYAxisSquared = (enterPoint[0] * enterPoint[0] + enterPoint[2] * enterPoint[2]); if (distFromYAxisSquared <= radius * radius && action->isBetweenPlanes(enterPoint) && (pp = action->addIntersection(enterPoint)) != NULL) { pp->setObjectNormal(norm); texCoord.setValue(0.5 + enterPoint[0] / (2.0 * radius), 0.5 + enterPoint[2] / (2.0 * radius), 0.0, 1.0); pp->setObjectTextureCoords(texCoord); if (materialPerPart) pp->setMaterialIndex(2); detail = new SoCylinderDetail(); detail->setPart(BOTTOM); pp->setDetail(detail, this); } } } }
void SoCylinder::generatePrimitives(SoAction *action) // //////////////////////////////////////////////////////////////////////// { SbBool materialPerPart; int curParts, numSides, numSections, side, section; float yTop, yBot, dy; float s, ds, tTop, tBot, dt; float outerRadius, innerRadius, dRadius; SbVec2f *ringCoords; SbVec3f pt, norm; float radius, halfHeight; SbVec4f tex; SbBool genTexCoords; SoPrimitiveVertex pv; SoCylinderDetail detail; const SoTextureCoordinateElement *tce; SoMaterialBindingElement::Binding mbe = SoMaterialBindingElement::get(action->getState()); materialPerPart = (mbe == SoMaterialBindingElement::PER_PART_INDEXED || mbe == SoMaterialBindingElement::PER_PART); curParts = (parts.isIgnored() ? ALL : parts.getValue()); // Compute number of sides and sections to use to represent // cylinder, then compute ring of x,z coordinates around cylinder // and store in ringCoords. computeRing(action, numSides, numSections, ringCoords); pv.setDetail(&detail); // Determine whether we should generate our own texture coordinates switch (SoTextureCoordinateElement::getType(action->getState())) { case SoTextureCoordinateElement::EXPLICIT: genTexCoords = TRUE; break; case SoTextureCoordinateElement::FUNCTION: genTexCoords = FALSE; break; } // If we're not generating our own coordinates, we'll need the // texture coordinate element to get coords based on points/normals. if (! genTexCoords) tce = SoTextureCoordinateElement::getInstance(action->getState()); else { tex[2] = 0.0; tex[3] = 1.0; } getSize(radius, halfHeight); if (HAS_PART(curParts, SIDES)) { // Draw each section of sides as a triangle mesh, from top to bottom yTop = 1.0; dy = -2.0 / numSections; tTop = 1.0; dt = -1.0 / numSections; ds = -1.0 / numSides; for (section = 0; section < numSections; section++) { yBot = yTop + dy; tBot = tTop + dt; s = 1.0; detail.setPart(SIDES); beginShape(action, TRIANGLE_STRIP); for (side = 0; side < numSides; side++) { pt[0] = ringCoords[side][0]; pt[2] = ringCoords[side][1]; // Deal with normal norm.setValue(pt[0], 0.0, pt[2]); pv.setNormal(norm); // Point at bottom of section pt[1] = yBot; pt[0] *= radius; pt[1] *= halfHeight; pt[2] *= radius; if (genTexCoords) { tex[0] = s; tex[1] = tBot; } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); // Point at top of section pt[1] = yTop; pt[1] *= halfHeight; if (genTexCoords) { tex[0] = s; tex[1] = tTop; } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); s += ds; } // Join end of strip back to beginning side = 0; s = 0.0; pt[0] = ringCoords[side][0]; pt[2] = ringCoords[side][1]; // Deal with normal norm.setValue(pt[0], 0.0, pt[2]); pv.setNormal(norm); // Point at bottom of section pt[1] = yBot; pt[0] *= radius; pt[1] *= halfHeight; pt[2] *= radius; if (genTexCoords) { tex[0] = s; tex[1] = tBot; } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); // Point at top of section pt[1] = yTop; pt[1] *= halfHeight; if (genTexCoords) { tex[0] = s; tex[1] = tTop; } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); s += ds; endShape(); // Prepare for next section down yTop = yBot; tTop = tBot; } } // Draw top face as a series of concentric rings. The number of // rings is the same as the number of sections of the sides of the // cylinder. if (HAS_PART(curParts, TOP)) { norm.setValue(0.0, 1.0, 0.0); pt[1] = halfHeight; if (materialPerPart) pv.setMaterialIndex(1); pv.setNormal(norm); detail.setPart(TOP); // Start at the outside and work in outerRadius = 1.0; dRadius = -1.0 / numSections; for (section = numSections - 1; section >= 0; --section) { innerRadius = outerRadius + dRadius; // Innermost ring is treated as a triangle fan. This not // only gets better shading (because the center vertex is // sent), but also avoids the problem of having a polygon // with too many vertices. if (section == 0) { beginShape(action, TRIANGLE_FAN); // Center point comes first pt[0] = pt[2] = 0.0; if (genTexCoords) tex[0] = tex[1] = 0.5; else tex = tce->get(norm, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); // Send all vertices around ring. Go in reverse order // so that vertex ordering is correct for (side = numSides - 1; side >= 0; side--) { pt[0] = outerRadius * ringCoords[side][0]; pt[2] = outerRadius * ringCoords[side][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = TOP_TEX_S(pt[0]); tex[1] = TOP_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); } // Send first vertex again pt[0] = outerRadius * ringCoords[numSides - 1][0]; pt[2] = outerRadius * ringCoords[numSides - 1][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = TOP_TEX_S(pt[0]); tex[1] = TOP_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); endShape(); } // Other rings are triangle strips else { beginShape(action, TRIANGLE_STRIP); for (side = 0; side < numSides; side++) { // Send points on outer and inner rings pt[0] = outerRadius * ringCoords[side][0]; pt[2] = outerRadius * ringCoords[side][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = TOP_TEX_S(pt[0]); tex[1] = TOP_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); pt[0] = innerRadius * ringCoords[side][0]; pt[2] = innerRadius * ringCoords[side][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = TOP_TEX_S(pt[0]); tex[1] = TOP_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); } // Join end of strip back to beginning pt[0] = outerRadius * ringCoords[0][0]; pt[2] = outerRadius * ringCoords[0][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = TOP_TEX_S(pt[0]); tex[1] = TOP_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); pt[0] = innerRadius * ringCoords[0][0]; pt[2] = innerRadius * ringCoords[0][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = TOP_TEX_S(pt[0]); tex[1] = TOP_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); endShape(); // Prepare for next ring outerRadius = innerRadius; } } } // Draw bottom face the same way as the top if (HAS_PART(curParts, BOTTOM)) { norm.setValue(0.0, -1.0, 0.0); pt[1] = -halfHeight; if (materialPerPart) pv.setMaterialIndex(2); pv.setNormal(norm); detail.setPart(BOTTOM); // Start at the outside and work in outerRadius = 1.0; dRadius = -1.0 / numSections; for (section = numSections - 1; section >= 0; --section) { innerRadius = outerRadius + dRadius; // Innermost ring is drawn as a triangle fan. This not // only gets better shading (because the center vertex is // sent), but also avoids the problem of having a polygon // with too many vertices. if (section == 0) { beginShape(action, TRIANGLE_FAN); // Center point comes first pt[0] = pt[2] = 0.0; if (genTexCoords) tex[0] = tex[1] = 0.5; else tex = tce->get(norm, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); // Send all vertices around ring for (side = 0; side < numSides; side++) { pt[0] = outerRadius * ringCoords[side][0]; pt[2] = outerRadius * ringCoords[side][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = BOT_TEX_S(pt[0]); tex[1] = BOT_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); } // Send first vertex again pt[0] = outerRadius * ringCoords[0][0]; pt[2] = outerRadius * ringCoords[0][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = BOT_TEX_S(pt[0]); tex[1] = BOT_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); endShape(); } // Other rings are triangle strips else { beginShape(action, TRIANGLE_STRIP); // Go in reverse order so that vertex ordering is correct for (side = numSides - 1; side >= 0; side--) { // Send points on outer and inner rings pt[0] = outerRadius * ringCoords[side][0]; pt[2] = outerRadius * ringCoords[side][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = BOT_TEX_S(pt[0]); tex[1] = BOT_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); pt[0] = innerRadius * ringCoords[side][0]; pt[2] = innerRadius * ringCoords[side][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = BOT_TEX_S(pt[0]); tex[1] = BOT_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); } // Join end of strip back to beginning side = numSides - 1; pt[0] = outerRadius * ringCoords[side][0]; pt[2] = outerRadius * ringCoords[side][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = BOT_TEX_S(pt[0]); tex[1] = BOT_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); pt[0] = innerRadius * ringCoords[side][0]; pt[2] = innerRadius * ringCoords[side][1]; pt[0] *= radius; pt[2] *= radius; if (genTexCoords) { tex[0] = BOT_TEX_S(pt[0]); tex[1] = BOT_TEX_T(pt[2]); } else tex = tce->get(pt, norm); pv.setPoint(pt); pv.setTextureCoords(tex); shapeVertex(&pv); endShape(); // Prepare for next ring outerRadius = innerRadius; } } } }
static void generate_cylinder(const float radius, const float height, const int numslices, const unsigned int flags, SoShape * const shape, SoAction * const action) { int i; int slices = numslices; if (slices > 128) slices = 128; if (slices < 4) slices = 4; float h2 = height * 0.5f; SbVec3f coords[129]; SbVec3f normals[130]; SbVec2f texcoords[129]; sogenerate_generate_3d_circle(coords, slices, radius, -h2); coords[slices] = coords[0]; sogenerate_generate_3d_circle(normals, slices, 1.0f, 0.0f); normals[slices] = normals[0]; normals[slices+1] = normals[1]; int matnr = 0; SoPrimitiveVertex vertex; SoCylinderDetail sideDetail; SoCylinderDetail bottomDetail; SoCylinderDetail topDetail; sideDetail.setPart(SoCylinder::SIDES); bottomDetail.setPart(SoCylinder::BOTTOM); topDetail.setPart(SoCylinder::TOP); if (flags & SOGEN_GENERATE_SIDE) { shape->beginShape(action, SoShape::QUAD_STRIP); vertex.setDetail(&sideDetail); vertex.setMaterialIndex(matnr); i = 0; float t = 0.0; float inc = 1.0f / slices; while (i <= slices) { vertex.setTextureCoords(SbVec2f(t, 1.0f)); vertex.setNormal(normals[i]); SbVec3f c = coords[i]; vertex.setPoint(SbVec3f(c[0], h2, c[2])); shape->shapeVertex(&vertex); vertex.setTextureCoords(SbVec2f(t, 0.0f)); vertex.setPoint(c); shape->shapeVertex(&vertex); i++; t += inc; } if (flags & SOGEN_MATERIAL_PER_PART) matnr++; shape->endShape(); } if (flags & (SOGEN_GENERATE_BOTTOM | SOGEN_GENERATE_TOP)) { sogenerate_generate_2d_circle(texcoords, slices, 0.5f); texcoords[slices] = texcoords[0]; } if (flags & SOGEN_GENERATE_TOP) { vertex.setMaterialIndex(matnr); vertex.setDetail(&topDetail); vertex.setNormal(SbVec3f(0.0f, 1.0f, 0.0f)); shape->beginShape(action, SoShape::TRIANGLE_FAN); for (i = 0; i < slices; i++) { vertex.setTextureCoords(SbVec2f(texcoords[i][0] + 0.5f, 1.0f - texcoords[i][1] - 0.5f)); const SbVec3f &c = coords[i]; vertex.setPoint(SbVec3f(c[0], h2, c[2])); shape->shapeVertex(&vertex); } shape->endShape(); if (flags & SOGEN_MATERIAL_PER_PART) matnr++; } if (flags & SOGEN_GENERATE_BOTTOM) { vertex.setMaterialIndex(matnr); vertex.setDetail(&bottomDetail); shape->beginShape(action, SoShape::TRIANGLE_FAN); vertex.setNormal(SbVec3f(0.0f, -1.0f, 0.0f)); for (i = slices-1; i >= 0; i--) { vertex.setTextureCoords(texcoords[i] + SbVec2f(0.5f, 0.5f)); vertex.setPoint(coords[i]); shape->shapeVertex(&vertex); } shape->endShape(); } }