/* * * Copyright (C) 2000 Silicon Graphics, Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Further, this software is distributed without any warranty that it is * free of the rightful claim of any third person regarding infringement * or the like. Any license provided herein, whether implied or * otherwise, applies only to this software file. Patent licenses, if * any, provided herein do not apply to combinations of this program with * other software, or any other product whatsoever. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy, * Mountain View, CA 94043, or: * * http://www.sgi.com * * For further information regarding this notice, see: * * http://oss.sgi.com/projects/GenInfo/NoticeExplan/ * */ // // Program to transform one quad mesh into another. Linear // interpolation of the vertices is done. Transparent blending is // also done to interpolate colors or textures on the objects (again, // linearly). // // Written by Gavin Bell for Silicon Graphics // #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 "../../samples/common/InventorLogo.h" #include "QuadThing.h" #include "Background.h" #include #include #include #include #include #include #include // // The list of things we'll morph between // static QuadThingList objectList; // // The interpolated shape we'll constantly modify // static QuadThing *TheShape; // // Scale for slider bars // static const int SLIDER_MAX = 1000; // // Sensor to do automatic animation // static SoIdleSensor *animationSensor; FlashBackground *background; static int nobackground = 0; static int notextures = 0; // // Create a scene graph that looks something like this: // // Separator root // / \ // / \ // Separator ToStuff Separator FromStuff // / | | | | // ToTexSwitch | FromTexSwitch TransparentMaterial // / \ | / \ | // Texture Material | Texture Material / // \ / // \ / // ...Separator for quadmesh shape... // // ... So the quadMesh gets drawn twice, to blend the two sets of // colors. The transparency field in the TransparentMaterial is used // to control how much of the 'From' object is blended into the 'To' // object. // static SoSeparator *ToStuff, *FromStuff; static SoGroup *placeHolder; static SoGroup *ToTexGroup, *FromTexGroup; static SoGroup *ToMatGroup, *FromMatGroup; static SoMaterial *TransparentMaterial; static SoSwitch *ToTexSwitch, *FromTexSwitch; static SoTexture2 *noopTexture; static SoTextureCoordinatePlane *noopTextureCoord; // // Setup the scene graph when we change the shapes we are // interpolating. The topology of the scene graph is always the same, // but we may replace nodes like materials or textures on the fly (if // an object has no material or texture a placeholder node that does // not affect anything is used). // void setToFromStuff(int fromObject) { int toObject = (fromObject+1) % objectList.getLength(); SoNode *t; // Texture image: if ((t = objectList[fromObject]->getTexture2()) != NULL) { FromTexGroup->replaceChild(0, t); if (FromTexSwitch->whichChild.getValue() != 1) FromTexSwitch->whichChild = 0; } else { FromTexGroup->replaceChild(0, noopTexture); if (FromTexSwitch->whichChild.getValue() != 1) FromTexSwitch->whichChild = SO_SWITCH_ALL; } // Texture coordinates: if ((t = objectList[fromObject]->getTexCoord()) != NULL) FromTexGroup->replaceChild(1, t); else FromTexGroup->replaceChild(1, noopTextureCoord); // Texture coordinate binding: if ((t = objectList[fromObject]->getTexBinding()) != NULL) FromTexGroup->replaceChild(2, t); else FromTexGroup->replaceChild(2, placeHolder); // No texture, has materials (maybe) if ((t = objectList[fromObject]->getMaterial()) != NULL) FromMatGroup->replaceChild(0, t); else FromMatGroup->replaceChild(0, placeHolder); if ((t = objectList[fromObject]->getMatBinding()) != NULL) FromMatGroup->replaceChild(1, t); else FromMatGroup->replaceChild(1, placeHolder); if ((t = objectList[toObject]->getTexture2()) != NULL) { ToTexGroup->replaceChild(0, t); if (ToTexSwitch->whichChild.getValue() != 1) ToTexSwitch->whichChild = 0; } else { ToTexGroup->replaceChild(0, noopTexture); if (ToTexSwitch->whichChild.getValue() != 1) ToTexSwitch->whichChild = SO_SWITCH_ALL; } if ((t = objectList[toObject]->getTexCoord()) != NULL) ToTexGroup->replaceChild(1, t); else ToTexGroup->replaceChild(1, noopTextureCoord); if ((t = objectList[toObject]->getTexBinding()) != NULL) ToTexGroup->replaceChild(2, t); else ToTexGroup->replaceChild(2, placeHolder); if ((t = objectList[toObject]->getMaterial()) != NULL) ToMatGroup->replaceChild(0, t); else ToMatGroup->replaceChild(0, placeHolder); if ((t = objectList[toObject]->getMatBinding()) != NULL) ToMatGroup->replaceChild(1, t); else ToMatGroup->replaceChild(1, placeHolder); } // // A bunch of stuff used to control the animation // static const int COLOR = 0; static const int SHAPE = 1; static int lastColor = (-1); static double animationTime[2] = { 0.0, 0.0 }; static int whatIsAnimating[2] = { 1, 1 }; static Widget sliders[2]; static SbTime lastTime; // // Stop animation. what is either COLOR or SHAPE. Called by the UI // widgets (the animation buttons or to stop the animation when a // slider is moved). // void stopAnimating(int what) { whatIsAnimating[what] = 0; if (!(whatIsAnimating[0] || whatIsAnimating[1])) animationSensor->unschedule(); } void startAnimating(int what) { whatIsAnimating[what] = 1; animationSensor->schedule(); lastTime = SbTime::getTimeOfDay(); } // // Change the COLOR or SHAPE to the given value. Called by the slider // widgets. // void setValue(int what, double value) { animationTime[what] = fmod(value, objectList.getLength()); double t = fmod(animationTime[what], 1.0); if (what == SHAPE) { int object1 = int(animationTime[SHAPE]); int object2 = (object1+1) % objectList.getLength(); TheShape->interp(objectList[object1], objectList[object2], t); } else { // If we'll be using the colors of another shape, must change // scene graph: if (int(animationTime[COLOR]) != lastColor) { lastColor = int(animationTime[COLOR]); setToFromStuff(lastColor); } TransparentMaterial->transparency.setValue(t); } } // // Called by a sensor to automatically change the objects into one // another. This also updates the slider widgets. // void morfCallback(void *, SoSensor *sensor) { static const double transition_time = 4.0; // Seconds SbTime now = SbTime::getTimeOfDay(); SbTime dt = (now - lastTime) / transition_time; static SbTime bgShape, bgColor; if (whatIsAnimating[COLOR]) { setValue(COLOR, animationTime[COLOR] + dt.getValue()); double t = animationTime[COLOR] / objectList.getLength(); Arg resources[1]; XtSetArg(resources[0], XmNvalue, t * SLIDER_MAX); XtSetValues(sliders[COLOR], resources, 1); bgColor += dt; if (!nobackground) background->animateColor(bgColor.getValue()); } if (whatIsAnimating[SHAPE]) { setValue(SHAPE, animationTime[SHAPE] + dt.getValue()); double t = animationTime[SHAPE] / objectList.getLength(); Arg resources[1]; XtSetArg(resources[0], XmNvalue, t * SLIDER_MAX); XtSetValues(sliders[SHAPE], resources, 1); bgShape += dt / 3.0; if (!nobackground) background->animateShape(bgShape.getValue()); } lastTime = now; sensor->schedule(); } // // These two callbacks cooperate to figure out if rendering with // textures turned on is too slow. The first is called from a // callback node at the top of the scene graph, and notes the time // when rendering starts. The second is a render abort callback, and // will turn off texturing if it is determined to be too slow. // They both only check the first few frames so they don't slow // traversal down excessively. // static int TimeSet = 0; static SbTime renderTime; static void setTimeCallback(void *, SoAction *action) { if (action->isOfType(SoGLRenderAction::getClassTypeId()) && TimeSet < 4) { renderTime.setToTimeOfDay(); if (TimeSet == 0) { animationSensor->schedule(); lastTime = renderTime; } // This node must not be cached! SoCacheElement::invalidate(action->getState()); ++TimeSet; } } static SoGLRenderAction::AbortCode checkTimeCallback(void *data) { static int turnedOff = 0; // If textures aren't turned off already... if ((TimeSet > 1) && (TimeSet < 4) && !turnedOff) { SbTime delta = SbTime::getTimeOfDay() - renderTime; if (delta > SbTime(.5)) { fprintf(stderr, "Disabling textures, " "they are too slow\n"); // Turn off textures... FromTexSwitch->whichChild = 1; ToTexSwitch->whichChild = 1; turnedOff = TRUE; // And turn off render abort callback SoGLRenderAction *ra = (SoGLRenderAction *)data; ra->setAbortCallback(NULL, NULL); return SoGLRenderAction::ABORT; } } return SoGLRenderAction::CONTINUE; } void parse_args(int argc, char **argv) { int err = 0; // Flag: error in options? int c; // Note: optind and optarg are declared in getopt.h while ((c = getopt(argc, argv, "bth")) != -1) { switch(c) { case 'b': nobackground = 1; break; case 't': notextures = 1; break; case 'h': // Help default: err = 1; break; } } // Handle optional filenames for (; optind < argc; optind++) { QuadThing *q = new QuadThing(argv[optind]); if (q->getSceneGraph() != NULL) objectList.append(q); else delete q; } if (objectList.getLength() < 2) { err = 1; } if (err) { fprintf(stderr, "Usage: %s [-bth] file file [file ...]\n", argv[0]); fprintf(stderr, "-b : No background\n"); fprintf(stderr, "-t : No textures\n"); fprintf(stderr, "-h : This message (help)\n"); fprintf(stderr, "At least two files must be given, and " "they must contain QuadMeshes (qmorf can\n"); fprintf(stderr, "only morph QuadMesh nodes).\n"); fprintf(stderr, "The directory /usr/share/data/models/CyberHeads " "contains good data to morph.\n"); exit(7); } } // // Callback for quit button // void quitCallback(Widget, XtPointer, XtPointer) { exit(0); } // // Callback for 'About...' button // void showAboutDialog(Widget, XtPointer, XtPointer) { if (access("/usr/demos/Inventor/qmorf.about", R_OK) != 0) { system("xconfirm -t 'Sorry, could not find " "/usr/demos/Inventor/qmorf.about' > /dev/null"); return; } char command[100]; sprintf(command, "showcase -v /usr/demos/Inventor/qmorf.about"); int err = system(command); if (err) { system("xconfirm -t 'You must install showcase" " for this function to work' > /dev/null"); return; } } // // Callback for the animation buttons // static void toggleAnimation(Widget, XtPointer mydata, XtPointer) { if ((long)mydata == COLOR) { if (whatIsAnimating[COLOR]) stopAnimating(COLOR); else startAnimating(COLOR); } else { if (whatIsAnimating[SHAPE]) stopAnimating(SHAPE); else startAnimating(SHAPE); } } // // Callbacks for color and shape sliders // static void dragColorSlider(Widget, XtPointer mydata, XtPointer cbstruct) { if (whatIsAnimating[COLOR]) { stopAnimating(COLOR); Arg resources[1]; XtSetArg(resources[0], XmNset, 0); XtSetValues((Widget)mydata, resources, 1); } int value = ((XmScrollBarCallbackStruct *)cbstruct)->value; double fv = value * objectList.getLength() / (double)SLIDER_MAX; setValue(COLOR, fv); } static void dragShapeSlider(Widget, XtPointer mydata, XtPointer cbstruct) { if (whatIsAnimating[SHAPE]) { stopAnimating(SHAPE); Arg resources[1]; XtSetArg(resources[0], XmNset, 0); XtSetValues((Widget)mydata, resources, 1); } int value = ((XmScrollBarCallbackStruct *)cbstruct)->value; double fv = value * objectList.getLength() / (double)SLIDER_MAX; setValue(SHAPE, fv); } //////////////////////////////////////////////////////////////////////// // // Draw the Inventor logo in the overlays. // //////////////////////////////////////////////////////////////////////// static void logoCB(void *, SoAction *action) { if (action->isOfType(SoGLRenderAction::getClassTypeId())) glViewport(0, 0, 80, 80); // See Dave Mott for details! } static void setOverlayLogo(SoXtRenderArea *ra) { static SoSeparator *logo = NULL; if (logo == NULL) { SoInput in; in.setBuffer((void *)ivLogo, ivLogoSize); // see common directory logo = SoDB::readAll(&in); logo->ref(); SoCallback *cb = new SoCallback; // sets GL viewport cb->setCallback(logoCB); logo->insertChild(cb, 0); } SbColor col(1, 1, 1); ra->setOverlayColorMap(1, 1, &col); ra->setOverlaySceneGraph(logo); } ////////////////////////////////////////////////////////////////////// // // Create and layout all of the UI stuff (I'm really not very fond of // Motif). // static SoXtExaminerViewer * buildUI( Widget appWindow, SoNode *root, SoPerspectiveCamera *c) ////////////////////////////////////////////////////////////////////// { // // Form that everything is part of... // Arg resources[20]; int n = 0; XtSetArg(resources[n], "width", 600); n++; XtSetArg(resources[n], "height", 600); n++; Widget form = XmCreateForm(appWindow, "form", resources, n); n = 0; // // 8 widgets at bottom of window: // 2 buttons, 2 labels, 2 sliders, and 2 toggle buttons // Widget w[8]; #define STRING(a) XmStringCreateSimple(a) XtSetArg(resources[n], XmNlabelString, STRING("Animate")); ++n; XtSetArg(resources[n], XmNrightAttachment, XmATTACH_FORM); ++n; XtSetArg(resources[n], XmNbottomAttachment, XmATTACH_FORM); ++n; XtSetArg(resources[n], XmNset, 1); ++n; w[2] = XmCreateToggleButtonGadget(form, "colorAnimate", resources, n); XtAddCallback(w[2], XmNvalueChangedCallback, toggleAnimation, (XtPointer)COLOR); n = 0; XtSetArg(resources[n], XmNlabelString, STRING("Animate")); ++n; XtSetArg(resources[n], XmNrightAttachment, XmATTACH_FORM); ++n; XtSetArg(resources[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n; XtSetArg(resources[n], XmNbottomWidget, w[2]); n++; XtSetArg(resources[n], XmNset, 1); ++n; w[5] = XmCreateToggleButtonGadget(form, "shapeAnimate", resources, n); XtAddCallback(w[5], XmNvalueChangedCallback, toggleAnimation, (XtPointer)SHAPE); n = 0; XtSetArg(resources[n], XmNlabelString, STRING("Quit")); ++n; XtSetArg(resources[n], XmNleftAttachment, XmATTACH_FORM); ++n; XtSetArg(resources[n], XmNbottomAttachment, XmATTACH_FORM); ++n; w[7] = XmCreatePushButtonGadget(form, "quit", resources, n); XtAddCallback(w[7], XmNactivateCallback, quitCallback, NULL); n = 0; XtSetArg(resources[n], XmNlabelString, STRING("About...")); ++n; XtSetArg(resources[n], XmNleftAttachment, XmATTACH_FORM); ++n; XtSetArg(resources[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n; XtSetArg(resources[n], XmNbottomWidget, w[7]); ++n; w[6] = XmCreatePushButtonGadget(form, "about", resources, n); XtAddCallback(w[6], XmNactivateCallback, showAboutDialog, NULL); n = 0; XtSetArg(resources[n], XmNlabelString, STRING("Color")); ++n; XtSetArg(resources[n], XmNleftAttachment, XmATTACH_WIDGET); ++n; XtSetArg(resources[n], XmNleftWidget, w[6]); n++; XtSetArg(resources[n], XmNbottomAttachment, XmATTACH_FORM); ++n; w[0] = XmCreateLabelGadget(form, "colorLabel", resources, n); n = 0; XtSetArg(resources[n], XmNlabelString, STRING("Shape")); ++n; XtSetArg(resources[n], XmNleftAttachment, XmATTACH_WIDGET); ++n; XtSetArg(resources[n], XmNleftWidget, w[6]); n++; XtSetArg(resources[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n; XtSetArg(resources[n], XmNbottomWidget, w[2]); n++; XtSetArg(resources[n], XmNbottomOffset, 4); ++n; w[3] = XmCreateLabelGadget(form, "shapeLabel", resources, n); n = 0; XtSetArg(resources[n], XmNmaximum, SLIDER_MAX + SLIDER_MAX/20); ++n; XtSetArg(resources[n], XmNsliderSize, SLIDER_MAX/20); ++n; XtSetArg(resources[n], XmNincrement, SLIDER_MAX/40); ++n; XtSetArg(resources[n], XmNpageIncrement, SLIDER_MAX/20); ++n; XtSetArg(resources[n], XmNshowArrows, FALSE); ++n; XtSetArg(resources[n], XmNorientation, XmHORIZONTAL); ++n; int tn = n; XtSetArg(resources[n], XmNleftAttachment, XmATTACH_WIDGET); ++n; XtSetArg(resources[n], XmNleftWidget, w[3]); n++; XtSetArg(resources[n], XmNrightAttachment, XmATTACH_WIDGET); ++n; XtSetArg(resources[n], XmNrightWidget, w[2]); n++; XtSetArg(resources[n], XmNbottomAttachment, XmATTACH_FORM); ++n; XtSetArg(resources[n], XmNbottomOffset, 4); ++n; w[1] = XmCreateScrollBar(form, "colorScrollbar", resources, n); sliders[COLOR] = w[1]; XtAddCallback(w[1], XmNdragCallback, dragColorSlider, (XtPointer)w[2]); XtAddCallback(w[1], XmNvalueChangedCallback, dragColorSlider, (XtPointer)w[2]); n = tn; XtSetArg(resources[n], XmNleftAttachment, XmATTACH_WIDGET); ++n; XtSetArg(resources[n], XmNleftWidget, w[3]); n++; XtSetArg(resources[n], XmNrightAttachment, XmATTACH_WIDGET); ++n; XtSetArg(resources[n], XmNrightWidget, w[5]); n++; XtSetArg(resources[n], XmNbottomAttachment, XmATTACH_WIDGET); ++n; XtSetArg(resources[n], XmNbottomWidget, w[1]); n++; XtSetArg(resources[n], XmNbottomOffset, 10); ++n; w[4] = XmCreateScrollBar(form, "shapeScrollbar", resources, n); sliders[SHAPE] = w[4]; XtAddCallback(w[4], XmNdragCallback, dragShapeSlider, (XtPointer)w[5]); XtAddCallback(w[4], XmNvalueChangedCallback, dragShapeSlider, (XtPointer)w[5]); n = 0; // // Create viewer: // SoXtExaminerViewer *viewer = new SoXtExaminerViewer(form); viewer->setSceneGraph(root); viewer->setCamera(c); viewer->setTransparencyType(SoGLRenderAction::BLEND); viewer->setDecoration(FALSE); viewer->setPopupMenuEnabled(FALSE); viewer->setBorder(TRUE); viewer->setFeedbackVisibility(FALSE); viewer->setAutoClipping(FALSE); // Set the draw style to as is, otherwise we'll never see // any texture mapping on systems without graphics hardware viewer->setDrawStyle(SoXtViewer::STILL, SoXtViewer::VIEW_AS_IS); // Add Inventor logo to viewer //setOverlayLogo( viewer ); XtSetArg(resources[n], XmNtopAttachment, XmATTACH_FORM); n++; XtSetArg(resources[n], XmNleftAttachment, XmATTACH_FORM); n++; XtSetArg(resources[n], XmNrightAttachment, XmATTACH_FORM); n++; XtSetArg(resources[n], XmNbottomAttachment, XmATTACH_WIDGET); n++; XtSetArg(resources[n], XmNbottomWidget, w[6]); n++; XtSetValues(viewer->getWidget(), resources, n); n = 0; viewer->show(); XtManageChildren(w, 8); XtManageChild(form); return viewer; } main(int argc, char **argv) { Widget appWindow = SoXt::init(argv[0]); if ( appWindow == NULL ) exit( 1 ); parse_args(argc, argv); TheShape = new QuadThing(objectList[0]); TheShape->interp(objectList[0], objectList[1], 0.0); SoSeparator *root = new SoSeparator; root->ref(); // Add a shapeHints node SoShapeHints *hints = new SoShapeHints; hints->vertexOrdering = SoShapeHints::COUNTERCLOCKWISE; hints->faceType = SoShapeHints::CONVEX; root->insertChild(hints, 0); // Set up a callback node at the beginning of the scene graph that // will set renderTime, which is used to figure out if texturing // is too slow and should be turned off. SoCallback *setTime = new SoCallback; root->addChild(setTime); setTime->setCallback(setTimeCallback, NULL); SoPerspectiveCamera *camera = new SoPerspectiveCamera; root->addChild(camera); // And set up a no-op texture node. The GL doesn't guarantee // pixel exactness if textured and non-textured polygons are // blended together, so we'll make sure all shapes are textured by // default (with a 1 by 1 all-white modulated texture, which won't // affect the shape's color at all!). noopTexture = new SoTexture2; noopTexture->ref(); unsigned char whitePix[1]; whitePix[0] = 0xff; noopTexture->image.setValue(SbVec2s(1,1), 1, whitePix); // And an aribrary function for coords: noopTextureCoord = new SoTextureCoordinatePlane; noopTextureCoord->ref(); ToStuff = new SoSeparator; root->addChild(ToStuff); FromStuff = new SoSeparator; root->addChild(FromStuff); placeHolder = new SoGroup; placeHolder->ref(); ToTexSwitch = new SoSwitch; if (notextures) ToTexSwitch->whichChild = 1; else ToTexSwitch->whichChild = 0; ToStuff->addChild(ToTexSwitch); ToTexGroup = new SoGroup; ToTexSwitch->addChild(ToTexGroup); ToTexGroup->addChild(noopTexture); ToTexGroup->addChild(noopTextureCoord); ToTexGroup->addChild(placeHolder); // Room for texCoordBinding ToMatGroup = new SoGroup; ToTexSwitch->addChild(ToMatGroup); ToMatGroup->addChild(placeHolder); // Room for material ToMatGroup->addChild(placeHolder); // Room for MaterialBinding ToStuff->addChild(TheShape->getSceneGraph()); FromTexSwitch = new SoSwitch; FromStuff->addChild(FromTexSwitch); FromTexGroup = new SoGroup; FromTexSwitch->addChild(FromTexGroup); if (notextures) FromTexSwitch->whichChild = 1; else FromTexSwitch->whichChild = 0; FromTexGroup->addChild(noopTexture); FromTexGroup->addChild(noopTextureCoord); FromTexGroup->addChild(placeHolder); // Room for texCoordBinding FromMatGroup = new SoGroup; FromTexSwitch->addChild(FromMatGroup); FromMatGroup->addChild(placeHolder); // Room for material FromMatGroup->addChild(placeHolder); // Room for materialBinding TransparentMaterial = new SoMaterial; TransparentMaterial->diffuseColor.setIgnored(TRUE); TransparentMaterial->ambientColor.setIgnored(TRUE); TransparentMaterial->specularColor.setIgnored(TRUE); TransparentMaterial->emissiveColor.setIgnored(TRUE); TransparentMaterial->shininess.setIgnored(TRUE); TransparentMaterial->transparency.setValue(0.0); FromStuff->addChild(TransparentMaterial); FromStuff->addChild(TheShape->getSceneGraph()); setToFromStuff(0); camera->viewAll(root, SbVec2s(1,1)); camera->nearDistance = 0.1; camera->farDistance = 500.0; if (!nobackground) { background = new FlashBackground(20); root->addChild(background->getSceneGraph()); } SoXtExaminerViewer *viewer = buildUI(appWindow, root, camera); // Setup a render abort to turn off texturing if it is too slow: viewer->getGLRenderAction()->setAbortCallback(checkTimeCallback, viewer->getGLRenderAction()); animationSensor = new SoIdleSensor(morfCallback, NULL); // The animation sensor is scheduled the first time the scene is // rendered. SoXt::show( appWindow ); SoXt::mainLoop(); return 0; }