// // ConvertToInventor converts OSG scene graph to Inventor or VRML 1 scene graph // // It requires OSG and Inventor compatible library, such as Coin, // SGI Inventor , or TGS Inventor. // // // Autor: PCJohn (peciva _at fit.vutbr.cz) // // License: public domain // // // THIS SOFTWARE IS NOT COPYRIGHTED // // This source code is offered for use in the public domain. // You may use, modify or distribute it freely. // // This source code is distributed in the hope that it will be useful but // WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY // DISCLAIMED. This includes but is not limited to warranties of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // // If you find the source code useful, authors will kindly welcome // if you give them credit and keep their names with their source code, // but you are not forced to do so. // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __COIN__ #include #include #include #endif #include "ConvertToInventor.h" #include #define DEBUG_IV_WRITER 1 // Considerations for VRML1 support: // // SoSeparator: supported (only renderCulling field) // SoCoordinate3: supported // SoCoordinate4: no -> conversion to Coordinate3 required <---- missing // SoTextureCoordinate2: supported // SoTextureCoordinate3: no - 3D textures not supported by VRML1 // SoTextureCoordinateEnvironment no <- should texturing be disabled in this case? // SoTextureCoordinateBinding: no -> indexing is probably mandatory // SoNormal: supported // SoNormalBinding: supported (all modes supported, including non-indexed modes) // SoMaterial: supported // SoMaterialBinding: supported (all modes supported, including non-indexed modes) // SoBaseColor: no -> using SoMaterial instead // SoPointSet: supported // SoLineSet: no -> using SoIndexedListSet instead // SoIndexedLineSet: supported // SoIndexedTriangleStripSet: no -> using SoIndexedFaceSet instead // SoTriangleStripSet: no -> using SoIndexedFaceSet instead // SoMatrixTransform: supported // SoTransform: supported // SoTransparencyType: no <---- missing // SoShapeHints: supported // SoCone,SoCube,SoCylinder,SoSphere supported // SoTranslation supported // SoLightModel: ? // SoLOD: supported // SoLevelOfDetail: no // Node list from VRML1 documentation: // shape nodes: AsciiText, Cone, Cube, Cylinder, IndexedFaceSet, IndexedLineSet, PointSet, Sphere // properties nodes: Coordinate3, FontStyle, Info, Material, MaterialBinding, Normal, NormalBinding, // Texture2, Texture2Transform, TextureCoordinate2, ShapeHints // transformantion nodes: MatrixTransform, Rotation, Scale, Transform, Translation // group nodes: Separator, Switch, WWWAnchor, LOD // cameras: OrthographicCamera, PerspectiveCamera // lights: DirectionalLight, PointLight, SpotLight // others: WWWInline ConvertToInventor::ConvertToInventor() : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN) { // Enable/disable using of some extensions introduced by TGS and Coin. // // For instance, GL_REPLACE texturing mode is extension // and it will not be used if extensions are not enabled. #ifdef __COIN__ useIvExtensions = true; #else useIvExtensions = false; #endif vrml1Conversion = false; uniqueIdGenerator = 1; // Scene root ivRoot = new SoSeparator; ivRoot->ref(); // OSG -> Inventor coordinate system conversion SoMatrixTransform *mt = new SoMatrixTransform; mt->matrix.setValue(1.f, 0.f, 0.f, 0.f, 0.f, 0.f, -1.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f); ivRoot->addChild(mt); #if 0 // Setting everything to defaults may not be correct option // because the user may want to have model whose material,etc. // will be specified up in the scene graph and he may, for instance, // reuse the model with different materials. // However, I am not sure anyway. PCJohn-2007-08-27 // OSG defaults to: Inventor defaults to: // GL_LIGHTING enabled enabled SoLightModel *lm = new SoLightModel; lm->model = SoLightModel::PHONG; ivRoot->addChild(lm); // OSG defaults to: Inventor defaults to: // lighting: one side one side lighting // frontFace: COUNTER_CLOCKWISE COUNTER_CLOCKWISE // culling: DISABLED DISABLED // faceCulling: GL_BACK GL_BACK SoShapeHints *sh = new SoShapeHints; sh->vertexOrdering = SoShapeHints::UNKNOWN_ORDERING; ivRoot->addChild(sh); // Make sure texturing is OFF ivRoot->addChild(new SoTexture2D); // Put default material ivRoot->addChild(new SoMaterial); #endif // initialize Inventor state stack ivStack.push(InventorState::createTopLevelState(ivRoot)); } ConvertToInventor::~ConvertToInventor() { assert(ivStack.size() == 1 && "Not all InventorStates were popped from ivStack."); if (ivRoot) ivRoot->unref(); } SoNode* ConvertToInventor::getIvSceneGraph() const { return ivRoot; } void ConvertToInventor::apply(osg::Node &node) { #ifdef DEBUG_IV_WRITER osg::notify(osg::INFO) << "IvWriter: node traversed" << std::endl; #endif traverse(node); } template void osgArray2ivMField_template(const osg::Array *array, fieldClass &field, int startIndex = 0, int stopIndex = 0, int numItemsUntilMinusOne = 0) { int i,num = array->getNumElements(); if (startIndex!=0 || stopIndex!=0) { num = stopIndex - startIndex; assert(stopIndex >= startIndex); assert(startIndex >= 0 && stopIndex >= 0); assert(stopIndex <= int(array->getNumElements())); } // update num if numItemsUntilMinusOne > 0 if (numItemsUntilMinusOne > 0 && num >= 1) num += (num-1) / numItemsUntilMinusOne; field.setNum(num); ivType *a = field.startEditing(); osgType *ptr = (osgType*)array->getDataPointer() + startIndex; if (numItemsUntilMinusOne <= 0) for (i=0; i void osgArray2ivMField_composite_template_worker(ivType *dest, osgType *src, int num, int numItemsUntilMinusOne = 0) { for (int i=0; i void osgArray2ivMField_composite_template_worker(SbColor *dest, GLubyte *src, int num, int numItemsUntilMinusOne) { for (int i=0; i void osgArray2ivMField_composite_template_worker(SbVec3f *dest, float *src, int num, int numItemsUntilMinusOne) { for (int i=0; i void osgArray2ivMField_composite_template(const osg::Array *array, fieldClass &field, int startIndex = 0, int stopIndex = 0, int numItemsUntilMinusOne = 0) { int num = array->getNumElements(); if (startIndex!=0 || stopIndex!=0) { num = stopIndex - startIndex; assert(stopIndex >= startIndex); assert(startIndex >= 0 && stopIndex >= 0); assert(stopIndex <= int(array->getNumElements())); } assert(numItemsUntilMinusOne <= 0 && "Composite template must have numItemsUntilMinusOne set to 0."); field.setNum(num); ivType *a = field.startEditing(); osgType *ptr = (osgType*)array->getDataPointer() + startIndex; osgArray2ivMField_composite_template_worker(a, ptr, num, 0); field.finishEditing(); } template void osgArray2ivMField_pack_template(const osg::Array *array, fieldClass &field, osgType mul, osgType max, osgType min, int startIndex = 0, int stopIndex = 0, int numItemsUntilMinusOne = 0) { int i,num = array->getNumElements(); if (startIndex!=0 || stopIndex!=0) { num = stopIndex - startIndex; assert(stopIndex >= startIndex); assert(startIndex >= 0 && stopIndex >= 0); assert(stopIndex <= int(array->getNumElements())); } assert(numItemsUntilMinusOne <= 0 && "Pack template must have numItemsUntilMinusOne set to 0."); field.setNum(num); ivType *a = field.startEditing(); osgType *ptr = (osgType*)array->getDataPointer() + startIndex; for (i=0; i max) tmp = max; if (tmp < min) tmp = min; a[i] |= ivType(tmp) << (((numComponents-1)*8)-(j*8)); } } field.finishEditing(); } template bool ivApplicateIntType(const osg::Array *array, fieldClass &field, int startIndex, int stopIndex, int numItemsUntilMinusOne) { if (field.isOfType(fieldClass::getClassTypeId())) { switch (array->getType()) { case osg::Array::ByteArrayType: osgArray2ivMField_template (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true; case osg::Array::UByteArrayType: osgArray2ivMField_template (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true; case osg::Array::ShortArrayType: osgArray2ivMField_template (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true; case osg::Array::UShortArrayType: osgArray2ivMField_template (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true; case osg::Array::IntArrayType: osgArray2ivMField_template (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true; case osg::Array::UIntArrayType: osgArray2ivMField_template (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true; case osg::Array::FloatArrayType: osgArray2ivMField_template (array, field, startIndex, stopIndex, numItemsUntilMinusOne); return true; case osg::Array::Vec4bArrayType: // FIXME: should signed values be handled differently? Like -128..127? case osg::Array::Vec4ubArrayType: osgArray2ivMField_pack_template (array, field, 1, 255, 0, startIndex, stopIndex, numItemsUntilMinusOne); return true; case osg::Array::Vec4ArrayType: osgArray2ivMField_pack_template (array, field, 255.f, 255.f, 0.f, startIndex, stopIndex, numItemsUntilMinusOne); return true; default: break; } } return false; } static void osgArray2ivMField(const osg::Array *array, SoMField &field, int startIndex = 0, int stopIndex = 0, int numItemsUntilMinusOne = 0) { if (field.isOfType(SoMFFloat::getClassTypeId())) { switch (array->getType()) { case osg::Array::FloatArrayType: osgArray2ivMField_template (array, (SoMFFloat&)field, startIndex, stopIndex, numItemsUntilMinusOne); return; default: break; } } else if (ivApplicateIntType(array, (SoMFInt32&) field, startIndex, stopIndex, numItemsUntilMinusOne)) return; else if (ivApplicateIntType(array, (SoMFUInt32&)field, startIndex, stopIndex, numItemsUntilMinusOne)) return; else if (ivApplicateIntType(array, (SoMFShort&) field, startIndex, stopIndex, numItemsUntilMinusOne)) return; else if (ivApplicateIntType(array, (SoMFUShort&)field, startIndex, stopIndex, numItemsUntilMinusOne)) return; else if (field.isOfType(SoMFVec2f::getClassTypeId())) { switch (array->getType()) { case osg::Array::Vec2ArrayType: osgArray2ivMField_composite_template (array, (SoMFVec2f&)field, startIndex, stopIndex, numItemsUntilMinusOne); return; default: break; } } else if (field.isOfType(SoMFVec3f::getClassTypeId())) { switch (array->getType()) { case osg::Array::Vec3ArrayType: osgArray2ivMField_composite_template (array, (SoMFVec3f&)field, startIndex, stopIndex, numItemsUntilMinusOne); return; case osg::Array::Vec2ArrayType: osgArray2ivMField_composite_template (array, (SoMFVec3f&)field, startIndex, stopIndex, numItemsUntilMinusOne); return; default: break; } } else if (field.isOfType(SoMFVec4f::getClassTypeId())) { switch (array->getType()) { case osg::Array::Vec4ArrayType: osgArray2ivMField_composite_template (array, (SoMFVec4f&)field, startIndex, stopIndex, numItemsUntilMinusOne); return; default: break; } } else if (field.isOfType(SoMFColor::getClassTypeId())) { switch (array->getType()) { case osg::Array::Vec3ArrayType: osgArray2ivMField_composite_template (array, (SoMFColor&)field, startIndex, stopIndex, numItemsUntilMinusOne); return; case osg::Array::Vec4ArrayType: osgArray2ivMField_composite_template (array, (SoMFColor&)field, startIndex, stopIndex, numItemsUntilMinusOne); return; case osg::Array::Vec4ubArrayType: osgArray2ivMField_composite_template (array, (SoMFColor&)field, startIndex, stopIndex, numItemsUntilMinusOne); return; default: break; } }; osg::notify(osg::WARN) << "IvWriter: No direct conversion for array. " << "The file may be broken." << std::endl; } template bool ivDeindex(variableType *dest, const variableType *src, const int srcNum, const indexType *indices, const int numToProcess) { for (int i=0; i=srcNum) return false; dest[i] = src[index]; } return true; } template bool ivDeindex(variableType *dest, const variableType *src, const int srcNum, const osg::Array *indices, const int numToProcess) { if (int(indices->getNumElements()) < numToProcess) { assert(0 && "Something is wrong: indices array is shorter than numToProcess."); return false; } switch (indices->getType()) { case osg::Array::ByteArrayType: case osg::Array::UByteArrayType: return ivDeindex(dest, src, srcNum, (GLbyte*)indices->getDataPointer(), numToProcess); break; case osg::Array::ShortArrayType: case osg::Array::UShortArrayType: return ivDeindex(dest, src, srcNum, (GLshort*)indices->getDataPointer(), numToProcess); break; case osg::Array::IntArrayType: case osg::Array::UIntArrayType: return ivDeindex(dest, src, srcNum, (GLint*)indices->getDataPointer(), numToProcess); break; default: assert(0 && "Index of strange type."); return false; } } template bool ivProcessArray(const osg::Array *indices, const osg::Array *drawElemIndices, fieldType *destField, const fieldType *srcField, int startIndex, int numToProcess) { bool ok = true; if (indices || drawElemIndices) { // "deindex" original data if (indices && !drawElemIndices) ok = ivDeindex(destField->startEditing(), srcField->getValues(startIndex), srcField->getNum(), indices, numToProcess); else if (!indices && drawElemIndices) ok = ivDeindex(destField->startEditing(), srcField->getValues(startIndex), srcField->getNum(), drawElemIndices, numToProcess); else { osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl; assert(0); // FIXME: } destField->finishEditing(); if (!ok) osg::notify(osg::WARN) << "IvWriter: Can not deindex - bug in model: index out of range." << std::endl; } else { // copy required part of original data const variableType *src = srcField->getValues(startIndex); assert(startIndex+numToProcess <= srcField->getNum() && "Index out of bounds."); variableType *dest = destField->startEditing(); for (int i=0; ifinishEditing(); } return ok; } static void processIndices(const osg::Array *indices, const osg::Array *drawElemIndices, SoMFInt32 &ivIndices, int startIndex, int stopIndex, int numItemsUntilMinusOne) { if (indices || drawElemIndices) { if (indices && !drawElemIndices) osgArray2ivMField(indices, ivIndices, startIndex, stopIndex, numItemsUntilMinusOne); else if (!indices && drawElemIndices) osgArray2ivMField(drawElemIndices, ivIndices, startIndex, stopIndex, numItemsUntilMinusOne); else { osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl; assert(0); // FIXME: } } else { int num = stopIndex-startIndex; if (numItemsUntilMinusOne != 0 && num >= 1) num += (num-1)/numItemsUntilMinusOne; ivIndices.setNum(num); int32_t *a = ivIndices.startEditing(); if (numItemsUntilMinusOne <= 0) for (int i=0,j=startIndex; jgetNum(); int newNum = origNum + drawArrayLengths->size()-1; field->setNum(newNum); int32_t *a = field->startEditing(); int32_t *src = a + origNum; int32_t *dst = a + newNum; for (osg::DrawArrayLengths::const_reverse_iterator primItr = drawArrayLengths->rbegin(); primItr!=drawArrayLengths->rend()-1; ++primItr) { int c = *primItr; src -= c; dst -= c; memmove(dst, src, sizeof(int32_t)*c); dst--; *dst = -1; } field->finishEditing(); } static void postProcessField(const SbIntList &runLengths, osg::PrimitiveSet::Mode primType, SoMFInt32 *field, osg::Geometry::AttributeBinding binding) { if (binding==osg::Geometry::BIND_OFF || binding==osg::Geometry::BIND_OVERALL || binding==osg::Geometry::BIND_PER_PRIMITIVE_SET) return; // make copy of array const int32_t *fieldArray = field->getValues(0); int origNum = field->getNum(); int32_t *tmpArray = new int32_t[origNum]; memcpy(tmpArray, fieldArray, origNum*sizeof(int32_t)); // compute new number of indices int newNum = origNum; const int l = runLengths.getLength(); switch (binding) { case osg::Geometry::BIND_PER_VERTEX: for (int i=0; isetNum(newNum); int32_t *src = tmpArray; int32_t *dst = field->startEditing(); // int32_t *dst2 = dst; switch (binding) { case osg::Geometry::BIND_PER_VERTEX: for (int i=0; ifinishEditing(); // free resources delete tmpArray; } static void postProcessTriangleSeparation(SoIndexedShape *shape, osg::PrimitiveSet::Mode primType, osg::Geometry::AttributeBinding normalBinding, osg::Geometry::AttributeBinding colorBinding) { // compute runLengths SbIntList runLengths; const int32_t *a = shape->coordIndex.getValues(0); int origNum = shape->coordIndex.getNum(); int l = 0; for (int i=0; icoordIndex, osg::Geometry::BIND_PER_VERTEX); postProcessField(runLengths, primType, &shape->normalIndex, normalBinding); postProcessField(runLengths, primType, &shape->materialIndex, colorBinding); bool notUseTexCoords = shape->textureCoordIndex.getNum()==0 || (shape->textureCoordIndex.getNum()==1 && shape->textureCoordIndex[0] == -1); if (!notUseTexCoords) postProcessField(runLengths, primType, &shape->textureCoordIndex, osg::Geometry::BIND_PER_VERTEX); } static SoMaterialBinding* createMaterialBinding(const osg::Geometry *g, bool isMaterialIndexed) { SoMaterialBinding *materialBinding = new SoMaterialBinding; switch (g->getColorBinding()) { case osg::Geometry::BIND_OFF: // OFF means use material from state set (if any) for whole geometry case osg::Geometry::BIND_OVERALL: case osg::Geometry::BIND_PER_PRIMITIVE_SET: materialBinding->value = SoMaterialBinding::OVERALL; break; case osg::Geometry::BIND_PER_PRIMITIVE: materialBinding->value = (isMaterialIndexed) ? SoMaterialBinding::PER_PART_INDEXED : SoMaterialBinding::PER_PART; break; case osg::Geometry::BIND_PER_VERTEX: materialBinding->value = (isMaterialIndexed) ? SoMaterialBinding::PER_VERTEX_INDEXED : SoMaterialBinding::PER_VERTEX; break; default: assert(0); } return materialBinding; } static SoNormalBinding* createNormalBinding(const osg::Geometry *g, bool areNormalsIndexed) { // Convert normal binding SoNormalBinding *normalBinding = new SoNormalBinding; switch (g->getNormalBinding()) { case osg::Geometry::BIND_OFF: // FIXME: what to do with BIND_OFF value? case osg::Geometry::BIND_OVERALL: case osg::Geometry::BIND_PER_PRIMITIVE_SET: normalBinding->value = SoNormalBinding::OVERALL; break; case osg::Geometry::BIND_PER_PRIMITIVE: normalBinding->value = (areNormalsIndexed) ? SoNormalBinding::PER_PART_INDEXED : SoNormalBinding::PER_PART; break; case osg::Geometry::BIND_PER_VERTEX: normalBinding->value = (areNormalsIndexed) ? SoNormalBinding::PER_VERTEX_INDEXED : SoNormalBinding::PER_VERTEX; break; default: assert(0); } return normalBinding; } static SoTextureCoordinateBinding* createTexCoordBinding(SbBool useIndexing) { SoTextureCoordinateBinding *b = new SoTextureCoordinateBinding; b->value.setValue(useIndexing ? SoTextureCoordinateBinding::PER_VERTEX_INDEXED : SoTextureCoordinateBinding::PER_VERTEX); return b; } static SoTexture2::Model convertTexEnvMode(osg::TexEnv::Mode osgMode, bool useIvExtensions) { switch (osgMode) { case GL_MODULATE: return SoTexture2::MODULATE; case GL_REPLACE: return (SoTexture2::Model)(useIvExtensions ? GL_REPLACE : GL_MODULATE); case GL_BLEND: return SoTexture2::BLEND; case GL_DECAL: return SoTexture2::DECAL; default: assert(0); return SoTexture2::MODULATE; } } static SoTexture2::Wrap convertTextureWrap(osg::Texture::WrapMode osgWrap) { // notes on support of CLAMP_TO_BORDER, CLAMP_TO_EDGE, and MIRRORED_REPEAT: // original SGI Inventor: no // Coin: no (until current version Coin 2.5.0b3) // TGS Inventor: introduced in TGS Inventor 5.0 (available in SoTexture class) // note: Coin (since 2.0) uses CLAMP_TO_EDGE for rendering if SoTexture2::CLAMP is specified. switch (osgWrap) { case osg::Texture::CLAMP: case osg::Texture::CLAMP_TO_BORDER: case osg::Texture::CLAMP_TO_EDGE: return SoTexture2::CLAMP; case osg::Texture::REPEAT: case osg::Texture::MIRROR: return SoTexture2::REPEAT; default: assert(0); return SoTexture2::REPEAT; } } static void setSoTransform(SoTransform *tr, const osg::Vec3 &translation, const osg::Quat &rotation, const osg::Vec3 &scale = osg::Vec3(1.,1.,1.)) { tr->translation.setValue(translation.ptr()); tr->rotation.setValue(rotation.x(), rotation.y(), rotation.z(), rotation.w()); tr->scaleFactor.setValue(scale.ptr()); //tr->scaleCenter.setValue(osg::Vec3f(node.getPivotPoint())); <- testing required on this line } static bool updateMode(bool &flag, const osg::StateAttribute::GLModeValue value) { if (value & osg::StateAttribute::INHERIT) return flag; else return (flag = (value & osg::StateAttribute::ON)); } ConvertToInventor::InventorState* ConvertToInventor::createInventorState(const osg::StateSet *ss) { // Push on stack const InventorState *ivPrevState = &ivStack.top(); ivStack.push(*ivPrevState); InventorState *ivState = &ivStack.top(); // Inventor graph ivState->ivHead = new SoSeparator; ivPrevState->ivHead->addChild(ivState->ivHead); if (ss) { // // Lighting // // enable/disable lighting updateMode(ivState->osgLighting, ss->getMode(GL_LIGHTING)); if (ivState->osgLighting != ivPrevState->osgLighting) { SoLightModel *lm = new SoLightModel; lm->model = (ivState->osgLighting) ? SoLightModel::PHONG : SoLightModel::BASE_COLOR; ivState->ivHead->addChild(lm); } // two-sided lighting const osg::LightModel *osgLM = dynamic_cast(ss->getAttribute(osg::StateAttribute::LIGHTMODEL)); if (osgLM) ivState->osgTwoSided = osgLM->getTwoSided(); // front face const osg::FrontFace *osgFF = dynamic_cast(ss->getAttribute(osg::StateAttribute::FRONTFACE)); if (osgFF) ivState->osgFrontFace = osgFF->getMode(); // face culling updateMode(ivState->osgCullFaceEnabled, ss->getMode(GL_CULL_FACE)); const osg::CullFace *osgCF = dynamic_cast(ss->getAttribute(osg::StateAttribute::CULLFACE)); if (osgCF) ivState->osgCullFace = osgCF->getMode(); // detect state change if (ivState->osgTwoSided != ivPrevState->osgTwoSided || ivState->osgFrontFace != ivPrevState->osgFrontFace || ivState->osgCullFaceEnabled != ivPrevState->osgCullFaceEnabled || ivState->osgCullFace != ivPrevState->osgCullFace) { // new SoShapeHints SoShapeHints *sh = new SoShapeHints; if (ivState->osgTwoSided) { // warn if face culling is on if (ivState->osgCullFaceEnabled) osg::notify(osg::WARN) << "IvWriter: Using face culling and two-sided lighting together! " "Ignoring face culling." << std::endl; // set two-sided lighting and backface culling off sh->vertexOrdering = ivState->osgFrontFace==osg::FrontFace::COUNTER_CLOCKWISE ? SoShapeHints::COUNTERCLOCKWISE : SoShapeHints::CLOCKWISE; sh->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE; } else { // set one-sided lighting and backface optionally if (ivState->osgCullFaceEnabled) { // determine vertex ordering bool ccw = ivState->osgFrontFace==osg::FrontFace::COUNTER_CLOCKWISE; if (ivState->osgCullFace != osg::CullFace::BACK) ccw = !ccw; if (ccw) // Warn if culling the lit faces while rendering unlit faces. // Inventor does not support this setup and it lits the unculled faces only. osg::notify(osg::WARN) << "IvWriter: Culling was set in a way that one-sided lighting will lit the culled sides of faces. " "Using lighting on correct faces." << std::endl; // face culling on sh->vertexOrdering = ccw ? SoShapeHints::COUNTERCLOCKWISE : SoShapeHints::CLOCKWISE; sh->shapeType = SoShapeHints::SOLID; } else // no face culling sh->shapeType = SoShapeHints::UNKNOWN_SHAPE_TYPE; } ivState->ivHead->addChild(sh); } // // Texturing // // FIXME: handle 1D and 3D textures // get OSG state ivState->osgTexture = dynamic_cast(ss->getTextureAttribute(0, osg::StateAttribute::TEXTURE)); ivState->osgTexEnv = dynamic_cast(ss->getTextureAttribute(0, osg::StateAttribute::TEXENV)); updateMode(ivState->osgTexture2Enabled, ss->getTextureMode(0, GL_TEXTURE_2D)); // detect state changes if (ivState->osgTexture2Enabled != ivPrevState->osgTexture2Enabled || ivState->osgTexture != ivPrevState->osgTexture || ivState->osgTexEnv != ivPrevState->osgTexEnv) { if (!ivState->osgTexture2Enabled || ivState->osgTexture==NULL || ivState->osgTexture->getImage(0)==NULL) // empty texture disables texturing ivState->ivTexture = new SoTexture2; else { // reuse texture if possible ivState->ivTexture = ivTexturesMap[ivState->osgTexture][ivState->osgTexEnv]; // if nothing for reusing, create new if (!ivState->ivTexture) { // create texture ivState->ivTexture = new SoTexture2; ivTexturesMap[ivState->osgTexture][ivState->osgTexEnv] = ivState->ivTexture; // texture file name const std::string &textureName = ivState->osgTexture->getImage(0)->getFileName(); ivState->ivTexture->filename.setValue(textureName.c_str()); // FIXME: handle inlined texture data in the files // wrap ivState->ivTexture->wrapS.setValue(convertTextureWrap( ivState->osgTexture->getWrap(osg::Texture::WRAP_S))); ivState->ivTexture->wrapT.setValue(convertTextureWrap( ivState->osgTexture->getWrap(osg::Texture::WRAP_T))); // texture environment if (ivState->osgTexEnv) { ivState->ivTexture->model.setValue(convertTexEnvMode( ivState->osgTexEnv->getMode(), useIvExtensions)); osg::Vec4 color = ivState->osgTexEnv->getColor(); ivState->ivTexture->blendColor.setValue(color.r(), color.g(), color.b()); } // notes on support of borderColor and borderWidth: // original SGI Inventor: no // Coin: no (until current version 2.5.0b3) // TGS Inventor: introduced in version 5.0 (as SoTexture::borderColor) // FIXME: implement support for texture filtering } } } // Texture coordinate generation updateMode(ivState->osgTexGenS, ss->getTextureMode(0, GL_TEXTURE_GEN_S)); updateMode(ivState->osgTexGenT, ss->getTextureMode(0, GL_TEXTURE_GEN_T)); ivState->osgTexGen = dynamic_cast(ss->getTextureAttribute(0, osg::StateAttribute::TEXGEN)); // Material parameters const osg::Material *osgMaterial = dynamic_cast(ss->getAttribute(osg::StateAttribute::MATERIAL)); if (osgMaterial) ivState->osgMaterial = osgMaterial; if (ivState->osgMaterial != ivPrevState->osgMaterial) { ivState->ivMaterial = new SoMaterial; assert(ivState->osgMaterial); // Warn if using two side materials // FIXME: The geometry can be probably doubled, or some estimation can be made // whether only front or back faces are used. if (ivState->osgMaterial->getAmbientFrontAndBack() == false || ivState->osgMaterial->getDiffuseFrontAndBack() == false || ivState->osgMaterial->getSpecularFrontAndBack() == false || ivState->osgMaterial->getEmissionFrontAndBack() == false || ivState->osgMaterial->getShininessFrontAndBack() == false) osg::notify(osg::WARN) << "IvWriter: Model contains different materials for front and " "back faces. This is not handled properly. Using front material only." << std::endl; // Convert colors // OSG represents colors by: Vec3, Vec4,Vec4ub // Inventor by: uint32 (RGBA, by SoPackedColor), SbColor (Vec3f, by SoMaterial and SoBaseColor) // note: Inventor can use DIFFUSE component inside a shape only. Material is set for whole shape. // Although SoMaterial is used only, SoPackedColor may bring some possibilities on per-vertex // alpha and SoBaseColor may be useful on pre-lit scene. if (ivState->osgMaterial->getColorMode() != osg::Material::DIFFUSE && ivState->osgMaterial->getColorMode() != osg::Material::OFF) { if (ivState->osgMaterial->getColorMode() == osg::Material::AMBIENT_AND_DIFFUSE) osg::notify(osg::WARN) << "IvWriter: The model is using AMBIENT_AND_DIFFUSE material " "mode while Inventor supports DIFFUSE mode only. " "The model colors may not much exactly." << std::endl; else osg::notify(osg::WARN) << "IvWriter: The model is not using DIFFUSE material mode and " "Inventor supports DIFFUSE mode only. " "The model colors may not be correct." << std::endl; } // Convert material components // FIXME: Transparency can be specified for each component in OSG // and just globally in Inventor. // Solutions? It can be averaged or just diffuse can be used. ((SoMaterial*)ivState->ivMaterial)->ambientColor.setValue(osgMaterial->getAmbient( osgMaterial->getAmbientFrontAndBack() ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT).ptr()); ((SoMaterial*)ivState->ivMaterial)->diffuseColor.setValue(osgMaterial->getDiffuse( osgMaterial->getDiffuseFrontAndBack() ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT).ptr()); ((SoMaterial*)ivState->ivMaterial)->specularColor.setValue(osgMaterial->getSpecular( osgMaterial->getSpecularFrontAndBack() ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT).ptr()); ((SoMaterial*)ivState->ivMaterial)->emissiveColor.setValue(osgMaterial->getEmission( osgMaterial->getEmissionFrontAndBack() ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT).ptr()); ((SoMaterial*)ivState->ivMaterial)->shininess.setValue(osgMaterial->getShininess( osgMaterial->getShininessFrontAndBack() ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT)); ((SoMaterial*)ivState->ivMaterial)->transparency.setValue(1.f - osgMaterial->getDiffuse( osgMaterial->getDiffuseFrontAndBack() ? osg::Material::FRONT_AND_BACK : osg::Material::FRONT).a()); } // note on "headlight": // OSG is using HEADLIGHT or SKY_LIGHT. In both cases it sets these defaults: // osg::LightModel::ambientIntensity(0.1, 0.1, 0.1, 1.0); // osg::Light::num(0) // osg::Light::ambient(0,0,0,1) // osg::Light::diffuse(0.8,0.8,0.8, 1) // osg::Light::specular(1,1,1,1) // // Inventor uses different settings: // SoEnvironment::ambientIntensity(0.2) // SoEnvironment::ambientColor(1,1,1) // SoDirectionalLight::intensity(1) // SoDirectionalLight::color(1,1,1) // // note on Inventor light handling: // ambient is set to 0,0,0,1 // diffuse to color*intensity // specular to color*intensity #ifdef __COIN__ // // Handle transparency // // OSG supports GL_BLEND and GL_ALPHA_TEST // Related classes: BlendFunc, BlendEquation, BlendColor, AlphaFunc // // Inventor is more difficult and not so robust. According to Inventor 2.1 standard, // just SoMaterial::transparency, SoTexture2 with alpha channel carry transparency information // that is controlled by SoGLRenderAction::transparency type that is set to SCREEN_DOOR by default // (dither pattern). So, if the user does not select better transparency type, there is no // possiblity to control transparency type from file format. // // However, SoTransparencyType node was introduced to overcome this historical limitation // because transparency was performance expensive long time ago. // Support by different Inventor branches: // // SGI Inventor: no // Coin: since 2.0 // TGS Inventor: since 5.0 // // Alpha test was introduced in TGS 4.0, but as SoGLRenderArea::setAlphaTest. So, no direct // control in the file format. // // Conclusion: // - Alpha test is not supported and all pixels will be drawn // - Blending - current strategy is following: // ADD x BLEND - ADD is used if destination-blending-factor is GL_ONE // DELAYED rendering is used if transparency bin is used by OSG // SORTED_OBJECT is used if ... // updateMode(ivState->osgBlendEnabled, ss->getMode(GL_BLEND)); ivState->osgBlendFunc = dynamic_cast(ss->getAttribute(osg::StateAttribute::BLENDFUNC)); if (useIvExtensions) if (ivState->osgBlendEnabled != ivPrevState->osgBlendEnabled || ivState->osgBlendFunc != ivPrevState->osgBlendFunc || (ivState->osgBlendFunc && ivPrevState->osgBlendFunc && ivState->osgBlendFunc->getDestinationRGB() != ivPrevState->osgBlendFunc->getDestinationRGB())) { const SoTransparencyType::Type transparencyTable[8] = { SoTransparencyType::BLEND, SoTransparencyType::ADD, SoTransparencyType::DELAYED_BLEND, SoTransparencyType::DELAYED_ADD, // FIXME: add sorted modes and test previous four }; int index = 0; if (!ivState->osgBlendFunc) index |= 0; else index = (ivState->osgBlendFunc->getDestinationRGB() == osg::BlendFunc::ONE) ? 1 : 0; index |= (ss->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN) ? 2 : 0; SoTransparencyType *ivTransparencyType = new SoTransparencyType; ivTransparencyType->value = transparencyTable[index]; ivState->ivHead->addChild(ivTransparencyType); } #endif } // ref Inventor nodes that are required when processing Drawables if (ivState->ivTexture) ivState->ivTexture->ref(); if (ivState->ivMaterial) ivState->ivMaterial->ref(); return ivState; } void ConvertToInventor::popInventorState() { InventorState *ivState = &ivStack.top(); // unref Inventor nodes if (ivState->ivTexture) ivState->ivTexture->unref(); if (ivState->ivMaterial) ivState->ivMaterial->unref(); ivStack.pop(); } static bool processPrimitiveSet(const osg::Geometry *g, const osg::PrimitiveSet *pset, osg::UIntArray *drawElemIndices, bool needSeparateTriangles, int elementsCount, int primSize, const int startIndex, int stopIndex, int &normalIndex, int &colorIndex, SoNode *ivCoords, SoNormal *ivNormals, SoNode *ivMaterial, SoNode *ivTexCoords, SoNode *ivTexture, SoShape *shape, SoSeparator *&indexedRoot, SoSeparator *&nonIndexedRoot) { bool ok = true; const osg::DrawArrayLengths *drawArrayLengths = (elementsCount == -1) ? dynamic_cast(pset) : NULL; int drawArrayLengthsElems = 0; if (drawArrayLengths) { // compute elementsCount elementsCount = 0; drawArrayLengthsElems = 0; for (osg::DrawArrayLengths::const_iterator primItr=drawArrayLengths->begin(); primItr!=drawArrayLengths->end(); ++primItr, drawArrayLengthsElems++) elementsCount += *primItr; // update stopIndex stopIndex = startIndex + elementsCount; } // NonIndexed data for nonIndexed shapes SoNode *nonIndexedCoords = NULL; SoNode *nonIndexedTexCoords = NULL; SoNormal *nonIndexedNormals = NULL; SoNode *nonIndexedMaterial = NULL; // Normal indexing int normalStart = g->getNormalBinding() == osg::Geometry::BIND_PER_VERTEX ? startIndex : normalIndex; int numNormalsUsed = 0; switch (g->getNormalBinding()) { case osg::Geometry::BIND_OFF: // FIXME: what is meaning of OFF value? case osg::Geometry::BIND_OVERALL: numNormalsUsed = 0; break; case osg::Geometry::BIND_PER_PRIMITIVE_SET: numNormalsUsed = 1; break; case osg::Geometry::BIND_PER_PRIMITIVE: numNormalsUsed = primSize!=0 ? (stopIndex-startIndex)/primSize : (drawArrayLengths ? drawArrayLengths->size() : 1); break; case osg::Geometry::BIND_PER_VERTEX: numNormalsUsed = stopIndex-startIndex; break; } normalIndex += numNormalsUsed; // Color indexing int colorStart = g->getColorBinding() == osg::Geometry::BIND_PER_VERTEX ? startIndex : colorIndex; int numColorsUsed = 0; switch (g->getColorBinding()) { case osg::Geometry::BIND_OFF: case osg::Geometry::BIND_OVERALL: numColorsUsed = 0; break; case osg::Geometry::BIND_PER_PRIMITIVE_SET: numColorsUsed = 1; break; case osg::Geometry::BIND_PER_PRIMITIVE: numColorsUsed = primSize!=0 ? (stopIndex-startIndex)/primSize : (drawArrayLengths ? drawArrayLengths->size() : 1); break; case osg::Geometry::BIND_PER_VERTEX: numColorsUsed = stopIndex-startIndex; break; } colorIndex += numColorsUsed; if (shape->isOfType(SoIndexedShape::getClassTypeId())) { // Convert to SoIndexedShape processIndices(g->getVertexIndices(), drawElemIndices, ((SoIndexedShape*)shape)->coordIndex, startIndex, stopIndex, primSize); if (ivNormals) processIndices(g->getNormalIndices(), drawElemIndices, ((SoIndexedShape*)shape)->normalIndex, normalStart, normalStart+(numNormalsUsed==0 ? 1 : numNormalsUsed), g->getNormalBinding()==osg::Geometry::BIND_PER_VERTEX ? primSize : 0); if (ivMaterial) processIndices(g->getColorIndices(), drawElemIndices, ((SoIndexedShape*)shape)->materialIndex, colorStart, colorStart+(numColorsUsed==0 ? 1 : numColorsUsed), g->getColorBinding()==osg::Geometry::BIND_PER_VERTEX ? primSize : 0); if (ivTexCoords && !ivTexCoords->isOfType(SoTextureCoordinateFunction::getClassTypeId())) processIndices(g->getTexCoordIndices(0), drawElemIndices, ((SoIndexedShape*)shape)->textureCoordIndex, startIndex, stopIndex, primSize); // Post-processing for DrawArrayLengths if (drawArrayLengths && primSize==0 && drawArrayLengths->size()>=2) { postProcessDrawArrayLengths(drawArrayLengths, &((SoIndexedShape*)shape)->coordIndex); if (ivNormals && g->getNormalBinding()==osg::Geometry::BIND_PER_VERTEX) postProcessDrawArrayLengths(drawArrayLengths, &((SoIndexedShape*)shape)->normalIndex); if (ivMaterial && g->getColorBinding()==osg::Geometry::BIND_PER_VERTEX) postProcessDrawArrayLengths(drawArrayLengths, &((SoIndexedShape*)shape)->materialIndex); if (ivTexCoords && !ivTexCoords->isOfType(SoTextureCoordinateFunction::getClassTypeId())) postProcessDrawArrayLengths(drawArrayLengths, &((SoIndexedShape*)shape)->textureCoordIndex); } if (needSeparateTriangles) postProcessTriangleSeparation((SoIndexedShape*)shape, (osg::PrimitiveSet::Mode)pset->getMode(), g->getNormalBinding(), g->getColorBinding()); } else { // Convert to SoNonIndexedShape assert(shape->isOfType(SoNonIndexedShape::getClassTypeId()) && "Shape must be non-indexed."); int i,n = stopIndex-startIndex; // create alternate coordinates if (ivCoords->isOfType(SoCoordinate4::getClassTypeId())) { nonIndexedCoords = new SoCoordinate4; if (ok) { ((SoCoordinate4*)nonIndexedCoords)->point.setNum(n); ok = ivProcessArray(g->getVertexIndices(), drawElemIndices, &((SoCoordinate4*)nonIndexedCoords)->point, &((SoCoordinate4*)ivCoords)->point, startIndex, n); } } else { nonIndexedCoords = new SoCoordinate3; if (ok) { ((SoCoordinate3*)nonIndexedCoords)->point.setNum(n); ok = ivProcessArray(g->getVertexIndices(), drawElemIndices, &((SoCoordinate3*)nonIndexedCoords)->point, &((SoCoordinate3*)ivCoords)->point, startIndex, n); } } // create alternate texture coordinates if (ivTexCoords) { if (ivTexCoords->isOfType(SoTextureCoordinate2::getClassTypeId())) { nonIndexedTexCoords = new SoTextureCoordinate2; if (ok) { ((SoTextureCoordinate2*)nonIndexedTexCoords)->point.setNum(n); ok = ivProcessArray(g->getTexCoordIndices(0), drawElemIndices, &((SoTextureCoordinate2*)nonIndexedTexCoords)->point, &((SoTextureCoordinate2*)ivTexCoords)->point, startIndex, n); } } else #ifdef __COIN__ if (ivTexCoords->isOfType(SoTextureCoordinate3::getClassTypeId())) { nonIndexedTexCoords = new SoTextureCoordinate3; if (ok) { ((SoTextureCoordinate3*)nonIndexedTexCoords)->point.setNum(n); ok = ivProcessArray(g->getTexCoordIndices(0), drawElemIndices, &((SoTextureCoordinate3*)nonIndexedTexCoords)->point, &((SoTextureCoordinate3*)ivCoords)->point, startIndex, n); } } else #endif // __COIN__ nonIndexedTexCoords = ivTexCoords; } // create alternate normals if (ivNormals) { nonIndexedNormals = new SoNormal; if (ok) { nonIndexedNormals->vector.setNum(numNormalsUsed==0 ? 1 : numNormalsUsed); ok = ivProcessArray(g->getNormalIndices(), g->getNormalBinding()==osg::Geometry::BIND_PER_VERTEX ? drawElemIndices : NULL, &nonIndexedNormals->vector, &ivNormals->vector, normalStart, numNormalsUsed==0 ? 1 : numNormalsUsed); } } // create alternate material if (ivMaterial) { SoMFColor *dstColorField; if (ivMaterial->isOfType(SoMaterial::getClassTypeId())) { nonIndexedMaterial = new SoMaterial; dstColorField = &((SoMaterial*)nonIndexedMaterial)->diffuseColor; } else { nonIndexedMaterial = new SoBaseColor; dstColorField = &((SoBaseColor*)nonIndexedMaterial)->rgb; } if (ok) { // FIXME: diffuse only? SoMFColor *srcColorField = (ivMaterial->isOfType(SoMaterial::getClassTypeId())) ? &((SoMaterial*)ivMaterial)->diffuseColor : &((SoBaseColor*)ivMaterial)->rgb; dstColorField->setNum(numColorsUsed==0 ? 1 : numColorsUsed); ok = ivProcessArray(g->getColorIndices(), g->getColorBinding()==osg::Geometry::BIND_PER_VERTEX ? drawElemIndices : NULL, dstColorField, srcColorField, colorStart, numColorsUsed==0 ? 1 : numColorsUsed); } } if (shape->isOfType(SoPointSet::getClassTypeId())) ((SoPointSet*)shape)->numPoints.setValue(elementsCount); else if (shape->isOfType(SoLineSet::getClassTypeId())) { switch (pset->getMode()) { case GL_LINES: assert(elementsCount % 2 == 0 && "elementsCount is not multiple of 2."); n = elementsCount/2; ((SoLineSet*)shape)->numVertices.setNum(n); for (i=0; inumVertices.set1Value(i, 2); break; case GL_LINE_STRIP: if (drawArrayLengths) { ((SoLineSet*)shape)->numVertices.setNum(drawArrayLengthsElems); int i=0; for (osg::DrawArrayLengths::const_iterator primItr=drawArrayLengths->begin(); primItr!=drawArrayLengths->end(); ++primItr, i++) ((SoLineSet*)shape)->numVertices.set1Value(i, *primItr); } else { ((SoLineSet*)shape)->numVertices.setNum(1); ((SoLineSet*)shape)->numVertices.set1Value(0, elementsCount); } break; default: osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl; assert(0); } } else if (shape->isOfType(SoTriangleStripSet::getClassTypeId())) { switch (pset->getMode()) { case GL_TRIANGLES: n = elementsCount/3; assert(n*3 == elementsCount && "elementsCount is not multiple of 3."); ((SoTriangleStripSet*)shape)->numVertices.setNum(n); for (i=0; inumVertices.set1Value(i, 3); break; case GL_TRIANGLE_STRIP: if (drawArrayLengths) { ((SoTriangleStripSet*)shape)->numVertices.setNum(drawArrayLengthsElems); int i=0; for (osg::DrawArrayLengths::const_iterator primItr=drawArrayLengths->begin(); primItr!=drawArrayLengths->end(); ++primItr, i++) ((SoTriangleStripSet*)shape)->numVertices.set1Value(i, *primItr); } else { ((SoTriangleStripSet*)shape)->numVertices.setNum(1); ((SoTriangleStripSet*)shape)->numVertices.set1Value(0, elementsCount); } break; case GL_TRIANGLE_FAN: osg::notify(osg::WARN) << "IvWriter: GL_TRIANGLE_FAN NOT IMPLEMENTED" << std::endl; ((SoTriangleStripSet*)shape)->numVertices.setNum(1); ((SoTriangleStripSet*)shape)->numVertices.set1Value(0, elementsCount); break; case GL_QUAD_STRIP: assert(elementsCount % 2 == 0 && "elementsCount is not multiple of 2."); if (drawArrayLengths) { ((SoTriangleStripSet*)shape)->numVertices.setNum(drawArrayLengthsElems); int i=0; for (osg::DrawArrayLengths::const_iterator primItr=drawArrayLengths->begin(); primItr!=drawArrayLengths->end(); ++primItr, i++) ((SoTriangleStripSet*)shape)->numVertices.set1Value(i, *primItr); } else { ((SoTriangleStripSet*)shape)->numVertices.setNum(1); ((SoTriangleStripSet*)shape)->numVertices.set1Value(0, elementsCount); } break; default: osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl; assert(0); } } else if (shape->isOfType(SoFaceSet::getClassTypeId())) { switch (pset->getMode()) { case GL_QUADS: n = elementsCount/4; assert(n*4 == elementsCount && "elementsCount is not multiple of 4."); ((SoFaceSet*)shape)->numVertices.setNum(n); for (i=0; inumVertices.set1Value(i, 4); break; case GL_POLYGON: if (drawArrayLengths) { ((SoFaceSet*)shape)->numVertices.setNum(drawArrayLengthsElems); int i=0; for (osg::DrawArrayLengths::const_iterator primItr=drawArrayLengths->begin(); primItr!=drawArrayLengths->end(); ++primItr, i++) ((SoFaceSet*)shape)->numVertices.set1Value(i, *primItr); } else { ((SoFaceSet*)shape)->numVertices.setNum(1); ((SoFaceSet*)shape)->numVertices.set1Value(0, elementsCount); } break; default: osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl; assert(0); } } else { osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl; assert(0 && "Unknown non-indexed shape type."); } } // Construct graph // Each osg::Drawable will have its own SoSeparator (render caching, culling, etc.) SoSeparator *sep = new SoSeparator; if (nonIndexedCoords) { assert(shape->isOfType(SoNonIndexedShape::getClassTypeId()) && "Not nonIndexed shape."); if (!ok) { // handle errors nonIndexedCoords->ref(); nonIndexedCoords->unref(); if (nonIndexedTexCoords) { nonIndexedTexCoords->ref(); nonIndexedTexCoords->unref(); } if (nonIndexedNormals) { nonIndexedNormals->ref(); nonIndexedNormals->unref(); } nonIndexedMaterial->ref(); nonIndexedMaterial->unref(); shape->ref(); shape->unref(); sep->ref(); sep->unref(); } else { // make scene graph if (nonIndexedRoot == NULL) { nonIndexedRoot = new SoSeparator; if (ivTexture) nonIndexedRoot->addChild(ivTexture); if (ivTexture) nonIndexedRoot->addChild(createTexCoordBinding(FALSE)); nonIndexedRoot->addChild(createMaterialBinding(g, FALSE)); if (ivNormals) nonIndexedRoot->addChild(createNormalBinding(g, FALSE)); } if (nonIndexedMaterial) sep->addChild(nonIndexedMaterial); sep->addChild(nonIndexedCoords); if (nonIndexedNormals) sep->addChild(nonIndexedNormals); if (nonIndexedTexCoords) sep->addChild(nonIndexedTexCoords); sep->addChild(shape); nonIndexedRoot->addChild(sep); } } else { assert(shape->isOfType(SoIndexedShape::getClassTypeId()) && "Not indexed shape."); assert(nonIndexedCoords==NULL && nonIndexedNormals==NULL && nonIndexedMaterial==NULL); if (indexedRoot == NULL) { indexedRoot = new SoSeparator; if (ivTexture) indexedRoot->addChild(ivTexture); if (ivTexture) indexedRoot->addChild(createTexCoordBinding(TRUE)); if (ivMaterial) indexedRoot->addChild(ivMaterial); indexedRoot->addChild(createMaterialBinding(g, TRUE)); indexedRoot->addChild(ivCoords); if (ivNormals) indexedRoot->addChild(ivNormals); if (ivNormals) indexedRoot->addChild(createNormalBinding(g, TRUE)); if (ivTexCoords) indexedRoot->addChild(ivTexCoords); } sep->addChild(shape); indexedRoot->addChild(sep); } return ok; } void ConvertToInventor::processGeometry(const osg::Geometry *g, InventorState *ivState) { int normalIndex = 0; int colorIndex = 0; // Inventor scene graph roots SoSeparator *indexedRoot = NULL; SoSeparator *nonIndexedRoot = NULL; // Active material SoMaterial *ivStateMaterial = ivState->ivMaterial; SoNode *ivMaterial = NULL; if (ivState->osgLighting || vrml1Conversion) // SoMaterial if (g->getColorArray()) if (ivStateMaterial) ivMaterial = ivStateMaterial->copy(); else ivMaterial = new SoMaterial; // FIXME: check default values of SoMaterial and OSG lighting else if (ivStateMaterial) ivMaterial = ivStateMaterial; else ivMaterial = NULL; else // SoBaseColor if (g->getColorArray()) if (ivStateMaterial) { ivMaterial = new SoBaseColor; ((SoBaseColor*)ivMaterial)->rgb.setValue(ivStateMaterial->diffuseColor[0]); // copy first value } else ivMaterial = new SoBaseColor; // FIXME: check default values of SoBaseColor and OSG pre-lit scene else if (ivStateMaterial) { ivMaterial = new SoBaseColor; ((SoBaseColor*)ivMaterial)->rgb.setValue(ivStateMaterial->diffuseColor[0]); // copy first value } else ivMaterial = NULL; // Convert color array into the SoMaterial if (g->getColorArray()) { assert(ivMaterial); // Choose correct color field SoMFColor *colorField; if (ivMaterial->isOfType(SoMaterial::getClassTypeId())) { if (vrml1Conversion && ivState->osgLighting==false) { // special case of pre-lit VRML1 scene ((SoMaterial*)ivMaterial)->ambientColor.setValue(0.f,0.f,0.f); ((SoMaterial*)ivMaterial)->diffuseColor.setValue(0.f,0.f,0.f); ((SoMaterial*)ivMaterial)->specularColor.setValue(0.f,0.f,0.f); colorField = &((SoMaterial*)ivMaterial)->emissiveColor; } else // regular diffuse color colorField = &((SoMaterial*)ivMaterial)->diffuseColor; } else // Using of SoBaseColor colorField = &((SoBaseColor*)ivMaterial)->rgb; // Color array with material if (ivState->osgMaterial == NULL || ivState->osgMaterial->getColorMode() == osg::Material::DIFFUSE || ivState->osgMaterial->getColorMode() == osg::Material::AMBIENT_AND_DIFFUSE) osgArray2ivMField(g->getColorArray(), *colorField); else; // FIXME: implement some workaround for non-diffuse cases? // note: Warning was already shown in createInventorState(). // note2: There is no effect to convert SoMaterial::[ambient|specular|emissive]color // here because Inventor does not set them per-vertex (performance reasons). See // Inventor documentation for more details. } // Convert coordinates // OSG represents coordinates by: Vec2, Vec3, Vec4 // Inventor by: SbVec3f, SbVec4f SoNode *coords; if (g->getVertexArray()->getDataSize() == 4) { coords = new SoCoordinate4; osgArray2ivMField(g->getVertexArray(), ((SoCoordinate4*)coords)->point); } else { coords = new SoCoordinate3; osgArray2ivMField(g->getVertexArray(), ((SoCoordinate3*)coords)->point); } coords->ref(); // Convert texture coordinates SoNode *texCoords = NULL; if (ivState->ivTexture) { if (ivState->osgTexGenS && ivState->osgTexGenT && ivState->osgTexGen && ivState->osgTexGen->getMode()==osg::TexGen::SPHERE_MAP) texCoords = new SoTextureCoordinateEnvironment; else if (g->getTexCoordArray(0)) { if (g->getTexCoordArray(0)->getDataSize() <= 2) { texCoords = new SoTextureCoordinate2; osgArray2ivMField(g->getTexCoordArray(0), ((SoTextureCoordinate2*)texCoords)->point); } #ifdef __COIN__ else { texCoords = new SoTextureCoordinate3; osgArray2ivMField(g->getTexCoordArray(0), ((SoTextureCoordinate3*)texCoords)->point); } #endif // __COIN__ } if (texCoords) texCoords->ref(); } // Convert normals // OSG represents normals by: Vec3,Vec3s,Vec3b // and can handle: Vec4s,Vec4b by truncating them to three components // Inventor by: SbVec3f SoNormal *normals = NULL; if (g->getNormalArray()) { normals = new SoNormal; osgArray2ivMField(g->getNormalArray(), normals->vector); normals->ref(); } // Convert osg::PrimitiveSets to Inventor's SoShapes int psetIndex,numPsets = g->getNumPrimitiveSets(); for (psetIndex=0; psetIndexgetPrimitiveSet(psetIndex); osg::PrimitiveSet::Type type = pset->getType(); GLenum mode = pset->getMode(); // Create appropriate SoShape bool useIndices = g->getVertexIndices() != NULL || vrml1Conversion; bool needSeparateTriangles = false; SoVertexShape *shape = NULL; switch (mode) { case GL_POINTS: shape = new SoPointSet; break; case GL_LINES: case GL_LINE_STRIP: case GL_LINE_LOOP: if (useIndices) shape = new SoIndexedLineSet; else shape = new SoLineSet; break; case GL_TRIANGLES: case GL_TRIANGLE_STRIP: case GL_QUAD_STRIP: if (useIndices) if (vrml1Conversion) { shape = new SoIndexedFaceSet; needSeparateTriangles = true; } else shape = new SoIndexedTriangleStripSet; else shape = new SoTriangleStripSet; break; case GL_TRIANGLE_FAN: needSeparateTriangles = true; shape = (vrml1Conversion) ? (SoVertexShape*)new SoIndexedFaceSet : new SoIndexedTriangleStripSet; break; case GL_QUADS: case GL_POLYGON: if (useIndices) shape = new SoIndexedFaceSet; else shape = new SoFaceSet; break; default: assert(0); } // Size of single geometric primitive int primSize; switch (mode) { case GL_LINES: primSize = 2; break; case GL_TRIANGLES: primSize = 3; break; case GL_QUADS: primSize = 4; break; default: primSize = 0; }; bool ok = true; switch (type) { case osg::PrimitiveSet::DrawArraysPrimitiveType: { const osg::DrawArrays *drawArrays = dynamic_cast(pset); int startIndex = drawArrays->getFirst(); int stopIndex = startIndex + drawArrays->getCount(); // FIXME: Am I using startIndex for all bundles that are PER_VERTEX? ok = processPrimitiveSet(g, pset, NULL, needSeparateTriangles, drawArrays->getCount(), primSize, startIndex, stopIndex, normalIndex, colorIndex, coords, normals, ivMaterial, texCoords, ivState->ivTexture, shape, indexedRoot, nonIndexedRoot); break; } case osg::PrimitiveSet::DrawArrayLengthsPrimitiveType: { const osg::DrawArrayLengths *drawArrayLengths = dynamic_cast(pset); int startIndex = drawArrayLengths->getFirst(); ok = processPrimitiveSet(g, pset, NULL, needSeparateTriangles, -1, primSize, startIndex, -1, normalIndex, colorIndex, coords, normals, ivMaterial, texCoords, ivState->ivTexture, shape, indexedRoot, nonIndexedRoot); break; } case osg::PrimitiveSet::DrawElementsUBytePrimitiveType: case osg::PrimitiveSet::DrawElementsUShortPrimitiveType: case osg::PrimitiveSet::DrawElementsUIntPrimitiveType: { osg::ref_ptr drawElemIndices = new osg::UIntArray; switch (type) { case osg::PrimitiveSet::DrawElementsUBytePrimitiveType: { const osg::DrawElementsUByte *drawElements = dynamic_cast(pset); for(osg::DrawElementsUByte::const_iterator primItr = drawElements->begin(); primItr!=drawElements->end(); ++primItr) drawElemIndices->push_back(*primItr); break; } case osg::PrimitiveSet::DrawElementsUShortPrimitiveType: { const osg::DrawElementsUShort *drawElements = dynamic_cast(pset); for(osg::DrawElementsUShort::const_iterator primItr = drawElements->begin(); primItr!=drawElements->end(); ++primItr) drawElemIndices->push_back(*primItr); break; } case osg::PrimitiveSet::DrawElementsUIntPrimitiveType: { const osg::DrawElementsUInt *drawElements = dynamic_cast(pset); for(osg::DrawElementsUInt::const_iterator primItr = drawElements->begin(); primItr!=drawElements->end(); ++primItr) drawElemIndices->push_back(*primItr); break; } default: assert(0); } ok = processPrimitiveSet(g, pset, drawElemIndices.get(), needSeparateTriangles, drawElemIndices->getNumElements(), primSize, 0, drawElemIndices->getNumElements(), normalIndex, colorIndex, coords, normals, ivMaterial, texCoords, ivState->ivTexture, shape, indexedRoot, nonIndexedRoot); break; } default: osg::notify(osg::WARN) << "IvWriter: NOT IMPLEMENTED" << std::endl; } } if (indexedRoot) ivState->ivHead->addChild(indexedRoot); if (nonIndexedRoot) ivState->ivHead->addChild(nonIndexedRoot); coords->unref(); if (texCoords) texCoords->unref(); if (normals) normals->unref(); } void ConvertToInventor::processShapeDrawable(const osg::ShapeDrawable *d, InventorState *ivState) { // visitor for converting ShapeDrawables class MyShapeVisitor : public osg::ConstShapeVisitor { public: void processNode(SoNode *ivNode, const osg::Vec3& center, osg::Quat rotation, SoGroup *root) { // convert rotation rotation = osg::Quat(-M_PI_2, osg::Vec3(0.,1.,0.)) * osg::Quat(M_PI_2, osg::Vec3(1.,0.,0.)) * rotation; if (center.length2()==0. && rotation.zeroRotation() && ivState->ivTexture==NULL) // optimized handling of single node root->addChild(ivNode); else { SoSeparator *root2 = new SoSeparator; // handle transformation if (center.length2()!=0. || !rotation.zeroRotation()) { SoTransform *ivTransform = new SoTransform; setSoTransform(ivTransform, center, rotation); root2->addChild(ivTransform); } // handle texture if (ivState->ivTexture) root2->addChild(ivState->ivTexture); // build graph root2->addChild(ivNode); root->addChild(root2); } } virtual void apply(const osg::Sphere &s) { SoSphere *ivSphere = new SoSphere; ivSphere->radius.setValue(s.getRadius()); processNode(ivSphere, s.getCenter(), osg::Quat(0., osg::Vec3(1.,0.,0.)), ivState->ivHead); } virtual void apply(const osg::Box &b) { SoCube *ivCube = new SoCube; ivCube->width = 2 * b.getHalfLengths().y(); ivCube->height = 2 * b.getHalfLengths().z(); ivCube->depth = 2 * b.getHalfLengths().x(); processNode(ivCube, b.getCenter(), b.getRotation(), ivState->ivHead); } virtual void apply(const osg::Cone &c) { SoCone *ivCone = new SoCone; ivCone->bottomRadius = c.getRadius(); ivCone->height = c.getHeight(); osg::Vec3 newCenter(c.getCenter()); newCenter.ptr()[2] -= c.getBaseOffset(); processNode(ivCone, newCenter, c.getRotation(), ivState->ivHead); } virtual void apply(const osg::Cylinder &c) { SoCylinder *ivCylinder = new SoCylinder; ivCylinder->radius = c.getRadius(); ivCylinder->height = c.getHeight(); processNode(ivCylinder, c.getCenter(), c.getRotation(), ivState->ivHead); } void warnNonSupported() { osg::notify(osg::WARN) << "IvWriter: Not supported ShapeDrawable found. Skipping it." << std::endl; } virtual void apply(const osg::Capsule&) { warnNonSupported(); } virtual void apply(const osg::InfinitePlane&) { warnNonSupported(); } virtual void apply(const osg::TriangleMesh&) { warnNonSupported(); } virtual void apply(const osg::ConvexHull&) { warnNonSupported(); } virtual void apply(const osg::HeightField&) { warnNonSupported(); } virtual void apply(const osg::CompositeShape&) { warnNonSupported(); } InventorState *ivState; MyShapeVisitor(InventorState *ivState) { this->ivState = ivState; } } shapeVisitor(ivState); // convert ShapeDrawable const osg::Shape *shape = d->getShape(); if (shape) shape->accept(shapeVisitor); } void ConvertToInventor::processDrawable(osg::Drawable *d) { osg::Geometry *g = d->asGeometry(); // FIXME: other drawables have to be handled also osg::ShapeDrawable *sd; // Create SoSeparator and convert StateSet for Drawable InventorState *ivDrawableState = createInventorState(d->getStateSet()); if (g != NULL) processGeometry(g, ivDrawableState); else if ((sd = dynamic_cast(d)) != NULL) { processShapeDrawable(sd, ivDrawableState); } else osg::notify(osg::WARN) << "IvWriter: Unsupported drawable found: \"" << d->className() << "\". Skipping it." << std::endl; // Restore state popInventorState(); } void ConvertToInventor::apply(osg::Geode &node) { #ifdef DEBUG_IV_WRITER osg::notify(osg::INFO) << "IvWriter: Geode traversed" << std::endl; #endif // Create SoSeparator and convert StateSet for Geode /*InventorState *ivGeodeState = */createInventorState(node.getStateSet()); // Convert drawables const int numDrawables = node.getNumDrawables(); for (int i=0; iivHead; // Process drawables const int numDrawables = node.getNumDrawables(); for (int i=0; iaxisOfRotation.setValue(axis); #else billboard->axisOfRotation.setValue(SbVec3f(0.f,0.f,0.f)); #endif SoTranslation *translation = new SoTranslation; translation->translation.setValue(node.getPosition(i).ptr()); // Rotate billboard correctly (OSG->IV conversion) // Note: use SoTransform instead of SoRotation because SoRotation is not supported by VRML1. SoTransform *transform = new SoTransform; transform->rotation = SbRotation(SbVec3f(1.f,0.f,0.f), -M_PI_2); SoSeparator *separator = new SoSeparator; separator->addChild(translation); separator->addChild(billboard); billboard->addChild(transform); root->addChild(separator); ivState->ivHead = billboard; processDrawable(node.getDrawable(i)); traverse((osg::Node&)node); } popInventorState(); } else apply((osg::Geode&)node); #else apply((osg::Geode&)node); #endif } void ConvertToInventor::apply(osg::MatrixTransform& node) { #ifdef DEBUG_IV_WRITER osg::notify(osg::INFO) << "IvWriter: MatrixTransform traversed" << std::endl; #endif // Convert matrix SoMatrixTransform *ivTransform = new SoMatrixTransform; SbMatrix ivMatrix; const osg::Matrix::value_type *src = node.getMatrix().ptr(); float *dest = ivMatrix[0]; for (int i=0; i<16; i++,dest++,src++) *dest = *src; ivTransform->matrix.setValue(ivMatrix); // Create SoSeparator and convert StateSet InventorState *ivState = createInventorState(node.getStateSet()); ivState->ivHead->addChild(ivTransform); traverse((osg::Node&)node); popInventorState(); } void ConvertToInventor::apply(osg::PositionAttitudeTransform& node) { #ifdef DEBUG_IV_WRITER osg::notify(osg::INFO) << "IvWriter: PositionAttitudeTransform traversed" << std::endl; #endif // Convert matrix SoTransform *ivTransform = new SoTransform; setSoTransform(ivTransform, node.getPosition(), node.getAttitude(), node.getScale()); // Create SoSeparator and convert StateSet InventorState *ivState = createInventorState(node.getStateSet()); ivState->ivHead->addChild(ivTransform); traverse((osg::Node&)node); popInventorState(); } void ConvertToInventor::apply(osg::LOD& node) { #ifdef DEBUG_IV_WRITER osg::notify(osg::INFO) << "IvWriter: LOD traversed" << std::endl; #endif // Convert LOD SoGroup *ivLOD = NULL; osg::LOD::RangeMode rangeMode = node.getRangeMode(); if (rangeMode == osg::LOD::DISTANCE_FROM_EYE_POINT) { // use SoLOD for DISTANCE_FROM_EYE_POINT SoLOD *lod = new SoLOD; // copy ranges int i,c=node.getNumRanges(); for (i=0; irange.set1Value(i, node.getMaxRange(i)); // set center osg::Vec3f center(node.getCenter()); lod->center.setValue(center.ptr()); ivLOD = lod; } else if (rangeMode == osg::LOD::PIXEL_SIZE_ON_SCREEN) { // use SoLevelOfDetail for PIXEL_SIZE_ON_SCREEN SoLevelOfDetail *lod = new SoLevelOfDetail; // copy ranges int i,c=node.getNumRanges(); for (i=0; iscreenArea.set1Value(i, node.getMaxRange(i)); ivLOD = lod; } else { // undefined mode -> put warning osg::notify(osg::WARN) << "IvWriter: Undefined LOD::RangeMode value." << std::endl; ivLOD = new SoGroup; } // Create SoSeparator and convert StateSet InventorState *ivState = createInventorState(node.getStateSet()); ivState->ivHead->addChild(ivLOD); ivState->ivHead = ivLOD; traverse((osg::Node&)node); popInventorState(); }