Open Collections

UBC Theses and Dissertations

UBC Theses Logo

UBC Theses and Dissertations

A programming library for the construction of 3-D wdigets Lau, Tony Tat Chung 1994

Your browser doesn't seem to have a PDF viewer, please download the PDF to view this item.

Item Metadata

Download

Media
831-ubc_1994-0279.pdf [ 1.39MB ]
Metadata
JSON: 831-1.0051294.json
JSON-LD: 831-1.0051294-ld.json
RDF/XML (Pretty): 831-1.0051294-rdf.xml
RDF/JSON: 831-1.0051294-rdf.json
Turtle: 831-1.0051294-turtle.txt
N-Triples: 831-1.0051294-rdf-ntriples.txt
Original Record: 831-1.0051294-source.json
Full Text
831-1.0051294-fulltext.txt
Citation
831-1.0051294.ris

Full Text

A Programming Library for the Construction of3-D WidgetsbyTONY TAT CHUNG LAUB.A.Sc., University of British Columbia, 1989A THESIS SUBMITTED IN PARTIAL FULFILLMENT OFTHE REQUIREMENTS FOR THE DECREE OFMASTER OF SCIENCEIN THE FACULTY OF GRADUATE STUDIESDEPARTMENT OF COMPUTER SCIENCEWe accept this thesis as conforming to the required standardTHE UNIVERSITY OF BRITISHApril, 1994COLUMBIA© Tony T. Lan, 1994In presenting this thesis in partial fulfilment of the requirements for an advanced degree at theUniversity of British Columbia, I agree that the Library shall make it freely available for reference and study. I further agree that permission for extensive copying of this thesis for scholarlypurposes may be granted by the head of my department or by his or her representatives. Itis understood that copying or publication of this thesis for financial gain shall not be allowedwithout my written permission.Department of Computer ScienceThe University of British Columbia2366 Main MallVancouver, B.C.Canada V6T 1Z4Date: 4 2 ‘i”1i-f,Abstract3-D graphical user interfaces (3-D GUTs) may be beneficial to application programs thatneed to manipulate 3-D objects or multi-dimensional data. However, most existing 3-D graphicsprogramming systems do not provide primitives for building 3-D GUTs; instead, programmershave to deal directly with input device events and 3-D graphics. Systems that do either areresearch systems that are not available to application programmers or are difficult to extend.For these reasons, explorations in the use of 3-D GUTs have been difficult.An extensible and object-oriented 3-D Widget Programming Library is implemented. It isan extension to Inventor (a widely available 3-D programming library) and lets programmerscontruct new widgets (3-D scene objects with interactive behaviors) using four types of high-level components that are responsible for user interface, visual feedback, application interfaceand general computation. A widget built with this library is able to control and display oneor more application states, interact with users in a click-drag-release fashion, and convey theapplication states through the relative positions and orientations among the widget’s parts.A widget interfaces with an application program through either direct attachments to sceneobjects or callback functions.11ContentsAbstract iiTable of Contents iiiList of Tables viList of Figures viiAcknowledgement viii1 Introduction 11.1 Motivation 11.2 Related Works 21.2.1 The Forms Library 31.2.2 Three-Dimensional Widgets 41.2.3 Inventor 61.3 Objectives 81.4 Thesis Organization 82 3-D Widget Programming Library— an Overview 92.1 What is a 3-D Widget? 92.2 What is the Library? 102.3 The Anatomy of a Widget 112.3.1 Widget Components 112.3.2 Creation of a New Widget Class 172.4 How Does a Widget Work? 181112.4.1 Using the Widget.212.4.2 A Typical User Interaction 223 Design and Implementation 243.1 General Design Issues for the 3-D Widget Programming Library 243.1.1 Inventor as the Basis 243.1.2 Motion Hierarchy as the Means of Visual Feedback 243.1.3 High-Level Data-Flow Components as Building Blocks of Widgets . . . 253.1.4 Multiple Controls and Support of Five Data Types 263.2 Design of the Classes 273.2.1 The Widget Base Class 273.2.2 The Basic Widget Component Classes 323.2.3 The Widget Slot Classes 343.2.4 The Widget Part Classes 383.2.5 The Widget Space Classes 434 Discussion 474.1 Accomplishments 474.2 Future Work 48Bibliography 50A Using a Widget in an Application 52A.1 Including a Widget and Registering a Callback 52A.1.1 The Main Loop 52A.1.2 Creating the Scene Graph arid Registering the Callback Function . . . 53A.1.3 The Callback Function 54A.2 Using Attachments 54A.2.1 Creating Attachments 55A.2.2 Creating a Custom Conversion Function 57ivA.3 Customizing a Widget. 58B Implementing a Widget from Existing PartsB.l Defining RackWidgetB.2 Default GeometriesB.3 Initializing the WidgetB.4 Constructors and DestructorsC Implementing a General ComponentC.1 Defining the ComponentC.2 Constructors and DestructorsC.3 The Member Function invokeD Implementing a PartD.l Defining the PartD.2 Default GeometriesD.3 Constructors and DestrnctorsD.4 The Member Function updateMatrixPortD.5 The Member Function invokeD.6 The Member Functions for ManipulationsE Relevant Enhancements in the New Release of Inventor 81Manipulators and DraggersEngines and Fields3-D Widgets and Inventor 2.060606263646868697071727374757677E.lE.2E.3818282vList of Tables4.1 Lines of code usage in the implementation of the Rack Widget 48viList of Figures2.1 The structure of the Rack Widget 122.2 A detailed view of the last figure, showing internal structures of bendFeedback,bendDial and bendValueSlot as well as port connections, attachments and callback functions 132.3 The Rack Widget in its default state 192.4 Manipulations of the taper and taper offset sliders 192.5 The manipulation of the bend dial 202.6 The manipulation of the twist dial 203.1 The class hierarchy for the 3-D Widget Programming Library 283.2 The Inventor subgraph maintained by WPart 393.3 The Inventor subgraph maintained by WCustomSpace 45vi’AcknowledgementsThere are many people without which this research would not have been accomplished. Iwould like to thank:• Dr. David Forsey of UBC (University of British Columbia), my supervisor, for suggestingthe thesis topic, sharing his insights, giving guidance and encouragement along the way,and bearing with all the drafts he had to read;• Dr. John Dill of SFU (Simon Fraser University), the second reader, for giving valuablecomments on the final draft of the thesis;• Mr. Tien Truong, the student reader, for going through several drafts of the thesis andgiving valuable suggestions on both the content and the writing;• Mr. Raza Khan and Mr. Chris Healey, for sharing their knowledge on formatting thethesis;• Mr. James Harrison, for asking difficult questions during the design stage of the work;• Mr. Paul Lalonde and Mr. Bob Lewis, for sharing their knowledge on data-flow systems;• Mr. Raza Khan (again) and Mr. Vishwa Ranjan, for being supportive and helpful friends;• last but not least, my parents and sisters, for their encouragement and support.This research was supported in part by post-graduate scholarships from NSERC (the NaturalSciences and Engineering Research Council of Canada) and UBC.viiiChapter 1Introduction1.1 MotivationRecently there has been a growing interest in three-dimensional graphical user interfaces (3-DGUTs) that allow users to visualize and directly manipulate objects in a computer-generatedthree-dimensional environment on a 2-D screen.A number of applications — such as computer aided design, computer animation, scientificdata visualization and virtual reality may benefit from 3-D GUTs because these applicationsdeal with objects or data with three or more dimensions. There is also some investigation inusing 3-D GUTs for visualization and manipulation of data that is not inherently 3-D suchas the use of a cone tree in [TO] to represent the hierarchy of an organization in the hope ofmaximizing the utility of the finite screen space and to shift some cognitive load to the humanperceptual system.However, the creation of a 3-D GUI is a difficult task. The following is some of the reasons:• Unlike 2-D GUI designers who can base their designs on now-common metaphors suchas windows, menus, sliders, dials and buttons, 3-D GUI designers do not have a wellestablished repertoire of interaction techniques to draw from.1Chapter 1. Introduction 2• 3-D GUI designers have to worry about complications that do not exist in 2-D GUTs.Examples are object occlusion and view projection.• There is a mismatch between 3-D GUTs and common input devices such as the mouse.A 3-D GUT has more degrees of freedom than these devices specify.• •There are a number of 2-D GUI programming libraries available; e.g. Garnet[8], InterViews [6]and Forms[9]. However, only a few 3-D graphics programming libraries provide supportfor 3-D GUTs; e.g. Inventor[15j and UGA[16J. Other 3-D graphics libraries such as GL[ll]and HOOPS[4] leave the task of providing 3-D user interaction up to the programmer.This involves handling input device events and calculating the transformations that mapthe events from the 2-D screen space to the 3-D space of the object being manipulated.The 3-D Widget Programming Library developed in this work alleviates some of the abovedifficulties. By encapsulating details of 3-D graphics and interaction techniques into high-level components, the library simplifies the creation of interactive 3-D scene objects calledwidgets, the building blocks of 3-D GUTs. Programmers can then devote more time on thedesign of the GUIs and on the exploration of alternatives through prototyping, rather thanon implementation details. The library is an extension to Inventor[15], a widely available 3-Dgraphics programming library.In the following sections, the merits and deficiencies of some GUI programming systemsare studied. Then the goals for the 3-D Widget Programming Library are listed. Finally, anoverview of the thesis is given.1.2 Related WorksIt is helpful to study 2-D GUI programming libraries because they provide useful models forbuilding 3-D GUI programming libraries. The Forms Library[9] is chosen to illustrate thestructure and facilities of a typical 2-D GUT programming toolkit.Chapter 1. Introduction 3Apparently, there are only two 3-D graphics programming systems that support the creationof high-level 3-D interaction objects: UGA[16] from the Brown University and Inventor[15]from Silicon Graphics Inc. The facilities and deficiencies, in terms of 3-D GUI support, of bothsystems are discussed.1.2.1 The Forms LibraryThe Forms library[9J is a programming library for the construction of 2D graphical user interfaces on Silicon Graphics workstations. A form (a visual panel for interaction) is composed ofhigh-level objects. There are several general types of predefined objects:• Static objects are not interactive and are used for visual effects or presenting data. Theyinclude boxes, text, bitmaps, clocks and charts.• Buttons are pushed by the mouse. There are several types of buttons with differentbehaviors and appearances.• Valuators let a user set a value between some fixed bounds by dragging the mouse.Examples are sliders and dials.• Input objects allow users to input text or numbers with the keyboard.• Choice objects let a user choose from a set of possibilities. Examples are menus andbrowsers.There are functions for adding, deleting, hiding, showing, activating, deactivating, andgrouping1 objects in a form. There are similar functions for manipulating forms. Other functions modify an object’s attributes such as its color and its label.An application program obtains information about the status of form objects by callingappropriate functions that poll or wait for an object that changes its state. A more elegant‘Grouping lets an operation be applied to all objects within the group. It also makes certain interactivebehaviors, such as radio buttons for mutually exclusive choices, possible.Chapter 1. Introduction 4way2 is to register callback functions to individual objects. A callback function is called whenthe object to which the function is registered changes state.New interaction techniques are added to an application program in three ways: by implementing a window that directly interacts with the user, by adding a free object to theapplication, or by creating a new object in the Forms Library. A free object is different fromother predefined objects in that the application handles the drawing and the interaction forit. If the new interaction technique is specific to a particular application, then a free objectshould be used. If the interaction technique is useful in many applications, then a new objectshould be implemented. New objects are implemented by conventional programming in C. Theprogrammer needs to implement functions for drawing the objects and handling interaction.The Forms Library comes with an interactive forms design tool. With this tool, a GUIdesigner composes forms visually with predefined objects. Some operations for compositioninclude addition, deletion, sizing, positioning and grouping. Attributes of an object such as itsstyle, color, label and callback function are set by filling the object’s attribute form.The user of the forms design tool tests the forms in a special mode in which the formsbehave as they would in an application program. The tool indicates the objects manipulatedand the callback functions called while the user is testing the form.The tool creates description files and C source files when forms are saved. The C source filescontain functions that build the forms as designed, and are subsequently incorporated into theapplication program. Description files can be reloaded into the forms design tool for editing.1.2.2 Three-Dimensional WidgetsThe Brown Graphics Group has published several papers on 3D widgets[2] [14j [17]. 3D widgets and application programs are constructed with the Unified Graphics Architecture (UGA)system{16].2As long as the application program is single-threaded.Chapter 1. Introduction 5UGA is an object-oriented system in which a new object is created via delegation (i.e. thenew object is a clone of an existing object and shares the attributes of the existing parentobject. However, the new object can override the attributes. Changes made to attributesin the parent object are also made to the new object if those attributes are not overridden).Objects in UGA may have geometric, algorithmic or interactive properties. UGA provides arich set of modeling primitives and operations. One-way constraints, called dependencies, allowusers to describe relationships between objects. Multi-way constraints and cyclical constraintsare made possible via controllers, whose purpose is to control other objects. Since 3D widgetsare first class objects in UGA, they take advantage of the modeling primitives and operationsin constructing their geometric components, and make use of dependencies and controllers fordescribing their behaviors.The 3D widget framework in [2] used controllers to construct relationships between application objects and 3D widgets. A new dialog model, based on a modified model of theaugmented transition network (ATN), was used for describing the state transitions in a userinterface caused by user interactions with 3D widgets. This ATN model allowed disconnectedcomponents and more than one active state in a state graph. These properties facilitated theseparation of sub-parts in a user interface. f 14] explored 3D widget design issues through thetask of deforming geometric objects. This paper showed how a rack widget displayed parameters of the deformation as well as allowed users to control those parameters intuitively viadirect manipulation of the widget.A 3D widget construction toolkit was presented in [17]. The toolkit allowed interactiveconstruction of new 3D widgets from a set of primitives via the linking operation. The shapeof a primitive suggests its purpose, while its ports encapsulate constraint values and interactivetechniques. By linking the ports of a primitive to the ports of other primitives, constraintsamong primitives are formed. The authors chose a “coordinate system” metaphor as the basisfor the primitives. The primitives cover the concepts of position, orientation, measure, and2D and 3D Cartesian coordinate systems with the point, ray, length, angle, plane and spaceprimitives. Programmers extend the set of primitives by building black boxes. A black box mayChapter 1. Introduction 6have arbitrary behaviors and an arbitrary nnmber of ports.While the 3-D widget framework and the interactive widget construction toolkit are impressive, there is one major problem: they are integral parts of TJGA, a research developmentenvironment. Therefore, programmers who want to develop 3D interactive applications forother environments cannot take advantage of the framework nor the toolkit.1.2.3 InventorIRIS Inventor[l5] [12] [13] is an object-oriented programming library for 3-D graphics. It containsa library of objects used for building 3-D scenes and user interfaces. Inventor objects may beclassified into the following types:• primitive scene nodes such as cameras, lights, shapes, properties and grouping nodes.• smart scene nodes capable of handling events. These include the selection node andmanipulators.• components such as the color editor and the examiner viewer.New objects are added to the library by subclassing existing object classes.A 3-D scene is described by a scene graph, a directed acyclic graph of scene nodes. Variousactions, such as rendering, picking and searching, can be applied to scene graphs or individualscene nodes.3-D user interaction is provided through the selection mechanism, event handlers and manipulators. Manipulators are scene objects that are directly manipulated with the mouse. Eachmanipulator consists of a 4x4 matrix called the delta matrix that represents the state of themanipulator. Manipulations performed on a manipulator cause its delta matrix to be edited.When a manipulator is attached to a scene node, usually a transformation node, modificationsmade to the delta matrix trigger updates to the attached node. Manipulators for common 3-Dinteraction techniques, such as the virtual trackball, are provided.Chapter 1. Introduction 7Not oniy can a manipulator affect the scene node it is attached to, an application programcan also register a callback function with the manipulator so that the application program isnotified of changes made to the delta matrix of the manipulator.The default appearance of a manipulator can be overridden during instantiation withoutreprogramming. Some parts of the manipulator can be made invisible or non-functional in thesame way. A new manipulator class is created in two ways: by combining existing manipulatorclasses if they support the interactive behaviors required by the new manipulator class, or bysubclassing the manipulator base class and programming in C++ if new behaviors are required.However, the ability to create new powerful manipulators is restricted3 because:• Each manipulator controls at most one scene node if no callback function is used. Furthermore, the control of a manipulator over the attached node is hard-coded in Inventorand cannot be altered by the programmer.• A manipulator is restricted to represent its state with a single 4x4 matrix, the deltamatrix; therefore, a manipulator cannot manipulate or display several application dataitems.• There is no notion of motion hierarchy within a manipulator; i.e. there is no facilityin the manipulator base class that assists a programmer in building manipulators withinternal moving parts. This is true even when the manipulator is constructed from severalexisting manipulators.• Creating manipulators with new behaviors is difficult because the programmer has toimplement member functions to interpret input device events and update the delta matrix.The programmer may also need to update the geometry of the manipulator for visualfeedback. Moreover, the new behavior is not easily shared with other new manipulatorsbecause there is no notion of reusable components in the manipulator base class.3The recently released Inventor 2.0 alleviates some of these restrictions. Please refer to Appendix E for moredetails.Chapter 1. Introduction 81.3 ObjectivesDue to the difficulties in the creation of 3-D GUTs, and the lack of powerful and available toolsfor doing so, the goal of this research is to produce a programming library for the creationof 3-D widgets, the building blocks of 3-D GUTs, for Inventor-based application programs. Awidget encapsulates geometries and behaviors for displaying and controlling application data.This programming library is designed to meet the following goals:• New widget types should be easy to implement. This research uses the approach ofbuilding new widgets from a collection of reusable high-level components handling userinteraction, visual feedback, application interfacing and computation.• The library of components should be extensible and object-oriented.• Widgets produced should be compatible with a widely available 3-D graphics programming library; as a result, this programming library is designed as an extension to theInventor programming library mentioned in Section 1.2.3.• Widgets should have a standard interface to application objects. However, programmersshould be able to bypass the standard method when greater control over the usage of thewidget is required.• A widget should be able to display and control one or more application states.1.4 Thesis OrganizationChapter 2 gives an overview on the features of the programming library and the structureof a widget. Then, the mechanics of a widget are explained. Chapter 3 concentrates on thedesign and implementation issues brought forward by the overall goals of the system. Chapter4 discusses the system in terms of its successes or failures in meeting the goals. It also looksinto possible future work.Chapter 23-D Widget Programming Libraryan Overview2.1 What is a 3-D Widget?3-D widgets are special objects that share the scene with other 3-D objects in an applicationprogram. They serve one or more of the following purposes:• provide visual feedback on application program data.• allow users to modify application program data through direct manipulation of the widgets that represent those data items.An example of a 3-D widget is the rack widget described in [14]. One version of it, implementedwith the 3-D Widget Programming Library, is shown in Figures 2.3 to 2.6. The orientationsof the twist dial and bend dial convey the amount of twist and bend applied to the object,while the height of the taper slider and the position of the taper offset slider show the amountand the scope of the taper. The user modifies the four parameters by clicking the appropriatepart of the widget with the mouse, dragging the part to the desired position, then releasingthe mouse button. During the manipulation, both the shape of the object and the shape of thewidget change interactively to reflect the current state of the application program.9Chapter 2. 3-D Widget Programming Library an Overview 102.2 What is the Library?The 3-D Widget Programming Library is an object-oriented programming library for the creation of new 3-D widget classes. The programming library is an extension to IRIS Inventor, acommercially available 3-D graphics programming library; therefore, 3-D widgets are compatible with application programs developed with IRIS Inventor.The library consists of the widget base class SoWidget and a collection of high-level component classes from which new widget classes are constructed.The widget base class SoWidget is derived from SoDragManip, the base class of all existingInventor manipulator classes. The SoWidget class allows a programmer to construct new widgetclasses from high-level components.The four types of high-level components, called parts, spaces, slots and general components,are responsible for geometry and behavior, motion hierarchy, interface to application programs,and general computation respectively. Components pass data from one to another via connections between their ports. Thus, the behavior of a widget is described by its components, theconnections among the components, and the motion hierarchy within the widget.The object-oriented property of the library makes it easily extensible; new componentclasses are added by subclassing appropriate base classes and programming in C++.Each 3-D widget built with this library has the following properties:• User interaction is through the mouse, in the click-drag-release fashion.• Meta-keys (i.e. the SHIFT, CTRL and ALT keys) may be utilized to alter the responseto user manipulation.• The geometries of widget parts, and the relative positions (or orientations) among themin a motion hierarchy, form the visual feedback for a widget.• A widget controls and displays one or more Inventor nodes/fields independently throughChapter 2. 3-D Widget Programming Library an Overview 11attachments. An attachment defines the relationship between a widget slot and an Inventor node/field. The relationship is modifiable by the application programmer.An application program enables reactions to changes in a widget by registering callbackfunctions with widget slots.2.3 The Anatomy of a WidgetA widget is built from high-level components. This section first explains the functionality ofwidget components, especially the specialized types including parts, spaces and slots. Then,the structure of a widget is revealed through a typical creation process of a new widget class.Figure 2.1 shows the overall structure of a widget (in this case the rack widget). Figure 2.2gives a more detailed view of the relationships among widget components in the widget. Thesefigures are discussed in detail in Section 2.4.2.3.1 Widget ComponentsWidgets are made up of high-level components. There are four types of components:• A part incorporates some geometry and zero or more interactive behaviors.• A space represents a coordinate frame. Each space contains a 4x4 matrix describing thetransformation from this space to its parent. Spaces are mainly used for building themotion hierarchy within a widget.• A slot contains a piece of data that the widget displays or controls.• General components, unlike the three types above, do not perform specific tasks in awidget. General components are used for calculation, data type conversion, or the controlof other components. Appendix C describes how to implement a general component.Chapter . 3-D Widget Programming Library an Overview 12(mainAxis ]locaiSpace-twistOffset- - bendOffsetI twistFeedback bendFeedbacktwistDial> data flow> motion hierarchy___________space]part-.slotFigure 2J: The structure of the Rack WidgetChapter 2. 3-D Widget Programming Library an Overview 13Matrix (rotation) +Reference to SpaceAttachmentsto InventorNodes/FieldsReferencesto CallbackFunctionsFigure 2.2: A detailed view of the last figure, showing internal structures of bendFeedback,bendDial and bendValueSlot as well as port connections, attachments and callback functions‘ifbendFeedbackWPortedSpace Ports:delMatrixln absMatrixlO‘iibendDialWDialPart Ports:absLowln absFloatlOabsRangeln deiFloatOutabsScalelnWPart Ports:absPickablelnabsVisiblelnabsActivelnabsMatrixlOdelMatrixOutabsPickedOutFloat (rotation)WFloatSlot Ports:absFloatlOList of Attachments:attributes node/fieldattributes node/fieldList of Callback Functions:Lreference to callback[reference to callback LChapter 2. 3-D Widget Programming Library an Overview 14A component communicates with other components via connections between their ports thatallow data to flow into or out of components, or both. Data that flows between componentsis one of the following types: boolean, integer, float, 3-D float vector, or 4x4 float matrix. Aport handles either absolute or delta (relative) data. Absolute data reflects the true data value,while delta data reflects the change in the data value from the last update.Two ports can be connected only if the followings are satisfied: they are of the same datatype, they both handle absolute or delta data, and one port is input-capable while the other isoutput-capable.Each component class must implement a member function called invoke that is called whenone of the input data ports is modified. The function invoke is responsible for updating thestate of the component and its output ports. Moreover, parts and slots update their states andoutput ports upon recieving events such as user input and changes in Inventor nodes/fields.In the following sections, the three specialized types of components (i.e. parts, spaces andslots) are described in more detail.PartsA part consists of a shape, or geometry, that should suggest its usage (e.g. the bend dial shownin Figure 2.6). Most parts also encapsulate one or more interactive behaviors; i.e. when auser clicks on a part in a widget and drags it, the part modifies its own state and updates itsoutput ports based on the manipulation. Parts that do not have interactive behaviors are usedeither for displaying application data or to give the widget the “right look” (e.g. the main axispart shown in Figure 2.6). A typical widget includes one or more parts, arranged in a motionhierarchy related by spaces (described in the following section). Parts and spaces are arrangedin a tree-like structure in which each part has a parent space.A widget displays data through the relative positions and orientations of its parts’ geometries. Each part owns two ports that output an absolute transformation and a delta transforChapter 2. 3-D Widget Programming Library an Overview 15mation to reflect its internal state. Usually, one of these ports is connected to a feedback spacethat should be an ancestor of the part in the motion hierarchy. The transformation appliedto the feedback space induces movements of part geometries under the feedback space. Thisserves as the visual feedback for the state of the part.Individual part classes support different sets of ports, depending on the designs of the partclasses. These ports usually allow other components to access the internal state of the partthrough more useful parameters than the transformation matrix. For example, an instance ofthe dial class WDialPart consists of a port with a float value that represents the the amount ofrotation. Some of these ports allow other components to modify the internal state of the part.The invoke function of the part, when triggered by changes in the illput ports of the part, isresponsible for updating the internal state of the part, the feedback transformation ports andany other output ports it has.Adding new part classes to the library generally involves subclassing the WPart base class,designing the geometries, deciding on the types and number of ports, and implementing a fewessential member functions that determine its interactive behaviors and reaction to changes ininput ports.Appendix D describes how to implement a new part.SpacesSpaces represent space transformations. There are two major categories of spaces: standardspaces and custom spaces. Standard spaces include the world space, the local space and theedit space; each widget has one of each of the above. The world space represents the globalspace that is not transformed in any way. The local space is the coordinate frame in whichthe widget operates; it is the root of the widget’s motion hierarchy. The edit space representsthe delta matrix, the only externally accessible state of an Inventor manipulator, inheritedby the widget base class SoWidget from the manipulator base class SoDragManip. The deltaChapter 2. 3-D Widget Programming Library— an Overview 16matrix is typically attached to an Inventor node (typically a transformation node) so that themanipulator can control the node through making changes to the delta matrix, and vice versa.In a widget, the delta matrix is modified through the delta input matrix port of the edit space.Please refer to Chapter 13 of the “IRIS Inventor Programming Guide - Volume I” [12] forinformation about the delta matrix.Custom spaces are the building blocks of a widget’s motion hierarchy. Each custom spacecontains a transformation matrix that is modified through either of the two matrix ports (adelta input port and an absolute input/output port). To add a custom space to a widget, itsparent space, which is either an existing custom space or the local space of the widget, mustbe specified. A widget may have as many custom spaces as required. Figure 2.1 shows themotion hierarchy of the rack widget, with the bendOff set space being the parent space ofbendFeedback space, for example.Member functions are provided by all spaces for obtaining the matrices describing thetransformations between the spaces and the world space. These matrices are used internally bywidget components for performing conversions between spaces. A vector or matrix data item(in a data port or slot) carries with it a reference to the space under which the data resides;therefore, the recipient of the data item can perform a space transformation on the data ifnecessary.SlotsSlots are “terminals” through which a widget communicates with the application program.Each slot contains a value that the widget controls or displays. Five slot classes support thesame data types as data ports of widget components. In fact, each slot of a particular datatype contains an absolute input/output port of the same data type.A slot communicates internally with other components via its data port. The application program accesses a slot either by creating attachments between the slot and InventorChapter 2. 3-D Widget Programming Library an Overview 17nodes/fields, or registering callback functions with the slot.An attachment between a slot and an Inventor node/field is responsible for updating theslot data or the node/field based on changes to the other side of the attachment. The dataflow can be one-way or both-ways. The update to either side of the attachment is performedby the data conversion function registered with the attachment. The fact that the data conversion function is NOT hard-coded to the attachment means an application program can usecustomized conversion functions, instead of the default conversion functions (one for each datatype). Appendix A shows an example of a customized conversion function.A callback function is used when the application object affected by the widget is not anInventor node/field, or the application program wants to access several slots at the same time.A callback function is called when the data of the slot it is registered to changes.Each slot supplies two member functions for accessing its data: getData and setData. Forvector and matrix slots, these two functions transmit space information as well as data. Thesefunctions are mainly used in callback functions and data conversion functions.2.3.2 Creation of a New Widget ClassThe functionality of a widget is broken down into the following aspects:• the interactive behavior that is defined by the interactive behaviors of the parts.• the visual feedback that is determined by the geometries of the parts, the motion hierarchyformed by the parts and the spaces, and the connections between the parts and theirfeedback spaces.• the behavior and interface to the application program that is defined by the slots andthe connections between widget components (i.e. slots, parts, spaces and general components).Chapter 2. 3-D Widget Programming Library an Overview 18The widget base class, SoWidget, provides member functions that allow a widget programmerto:• create a new space in a widget as the child of an existing space.• add a new part to a widget as a child of an existing space and specify an existing spaceas its feedback space.• add a new slot to a widget.• add a general component to a widget.The data port base class, DFPort, provides a member function for making a connection betweentwo compatible ports.Together, the above-mentioned member functions and the widget compollents fully specifythe functions of a new widget. Typically, the components are created, added to the widget, andconnected in the constructor of the new widget class. The widget keeps track of the componentsadded to it, and deletes them when the widget is destroyed.2.4 How Does a Widget Work?The internal mechanism of a widget is probably best illustrated with an example. A rackwidget class, as described in section 2.1, is created with existing components in the 3-D WidgetProgramming Library. The overall structure of the widget is illustrated in Figure 2.1. InFigure 2.2, the relationships among widget components in the widget is shown in more detail.Figure 2.3 to Figure 2.6 show the rack widget in action.Both twjstDjal and bendDial are instances of WDialPart. The feedback matrix of a dialpart is a rotation around the Z axis of the part’s space. A dial part also has a port that inputsand outputs a float value proportional to the amount of rotation. There are other input portsfor controlling the scaling factor and the range of the rotation, as shown in Figure 2.2.Chapter 2. 3-D Widget Programming Library an Overview 19Figure 2.3: The Rack Widget in its default stateFigure 2.4: Manipulations of the taper and taper offset slidersChapter . 3-D Widget Programming Library an Overview 20Figure 2.5: The manipulation of the bend dialFigure 2.6: The manipulation of the twist dialChapter 2. S-D Widget Programming Library— an Overview 21Both taperSlider and taperflffsetSlider are instances of WSliderPart. The feedbackmatrix of a slider part is a translation along the Y axis of the part’s space. A slider communicates the value of the slider through a float port. Like a dial part, a slider part also has inputports for controlling the scaling factor and the range of translation.mainAxis is an instance of the WGeomPart that has no interactive behavior. Here it providesthe geometry for the main axis of the widget.The motion hierarchy of the rack widget, shown in Figure 2.1, is quite straight forward.The twistOff set space and bendOff set space place twistDial and bendDial in the desiredpositions and orientations. The parents of the two dials, twistFeedback and bendFeedback,are also the feedback spaces for the two parts.The set-up for taperSlider is slightly more complex. taperOff set, the feedback space forthe taperOffsetSlider, determines the position of taperSlider and taperOffsetSlideron the main axis. taperOffsetOffset, the parent space of taperOffsetSlider, orientstaperQffsetSlider so that it slides on the Z axis in the widget’s local space. taperFeedback,the parent space and feedback space of taperSlider, determines the position of taperSlideron taperlJffsetSlider.The data ports of the parts are connected bidirectionally to the ports of the appropriateslots. Application programs thus have access and control of all four parameters of deformation.The actual implementation of the rack widget is listed in Appendix B.2.4.1 Using the WidgetTo incorporate the rack widget into an application program, a programmer needs to include aninstance of the rack widget in the scene-graph for the program. The procedure is basically thesame as including an Inventor manipulator in the program, except that a widget also controlsand displays application states through its slots in addition to the delta matrix inherited fromthe manipulator base class SoDragManip.Chapter 2. 3-D Widget Programming Library— an Overview 22Appendix A goes through an example of the addition of the widget to an applicationprogram and the use of a callback function. It also describes how to use attachments and howto customize widgets.2.4.2 A Typical User InteractionThe rack widget illustrates what happens to a widget when it is manipulated. For example,when a user wants to bend an object using the rack widget, he or she would click on thebendDial, drag it to the desired orientation, then release the button.When the user clicks on the bendDial:• the widget determines which part is being picked (in this case it is the bendDial). Thenit calls the manipulateStart member function of the part.• manipulateStart prepares for subsequent manipulation. Typically this involves determining the current view volume and projecting the mouse position to a coordinate in thepart space for later use. It also updates a boolean output port (as shown in Figure 2.2)to indicate that this part is picked. For example, bendDail projects the mouse positiononto the plane that passes through the origin of its space and perpendicular to the Z axisof the space.When the user drags the mouse pointer:• the widget calls the manipulate member function of the part.• manipulate, like manipulateStart, determines the view volume and projects the mouseposition to a coordinate in the part space. In addition, it calculates the amount ofmanipulation based on the difference between the new projection and the last projection.The difference is used to update the internal state of the part, the part’s ports, andthe feedback matrix ports. For example, bendDial calculates the angle sustained by theChapter 2. 3-D Widget Programming Library— an Overview 23new projection and the previous projection and updates the internal value and the portsaccordingly.• Whenever a port is updated, the input-capable ports connected to it are also updated.Each input-capable port, once updated, immediately calls the invoke member functionof the component that owns it. Receiving ports that are output-capable will propagatethe change to input-capable ports connected to them.• In the rack widget, the data port of the bendDial is connected to the bendValueS lot. Aslot’s invoke typically updates the internal data of the slot, then updates all the Inventornodes/fields attached to the slot through the data conversion functions registered in theattachments. Then it calls all the callback functions registered to the slot.• A space’s invoke function performs a space conversion on the received matrix beforeapplying it to the space’s internal transformation. It updates the absolute feedbackmatrix port if it receives data from the delta feedback matrix port.• Inventor automatically redraws the scene when it detects changes in the scene graph. Forexample, a modification of the transformation in the bendFeedback space will trigger aredraw.When the user releases the mouse button:• the widget calls the manipulateFinish member function of the part.• manipulateFinish typically updates the boolean output port (mentioned above) to indicate that this part is not picked anymore.Chapter 3Design and Implementation3.1 General Design Issues for the 3-D Widget ProgrammingLibrary3.1.1 Inventor as the BasisThe IRIS Inventor was chosen as the basis for the 3-D Widget Programming Library for severalreasons. First, Inventor is a commercially available programming library; therefore, the librarycan be utilized in programming projects using Inventor. Second, Inventor is an extensibleobject-oriented library. Third, the manipulator base classes are a good basis for 3-D widgetsbecause they perform many book-keeping tasks and isolate mundane event handling details.Last, Inventor has many useful basic classes that aid 3-D graphics programming. Some areuseful data structures such as vectors and matrices, while others simplify calculations such asprojecting a screen coordinate to the 3-D scene space.3.1.2 Motion Hierarchy as the Means of Visual FeedbackThis library supports the use of a motion hierarchy within a widget as the sole mechanism forvisual feedback (besides highlighting of parts). The rack widget in [14] shows that the relative24Chapter 3. Design and Implementation 25positions and motions among widget parts provide good visual feedback for the values thewidget is controlling. The motion of parts during manipulation gives the user a sense of being“in direct control” of the widget.However, there are cases where the motion hierarchy is not adequate. For example, a motionhierarchy within a widget is useless when the widget needs to track the coordinates of otherobjects in the scene; e.g, a length widget whose two ends follow the centers of two movingobjects. In some cases, other means of visual feedback are more appropriate; e.g. color for acolor editor.Nevertheless, motion hierarchies are reasonably useful for widgets that display or controldiscrete pieces of data. One more advantage is that the support for motion hierarchy is relativelystraight-forward to design and implement. Other types of visual feedback are possible, but aremuch harder to implement with this library.3.1.3 High-Level Data-Flow Components as Building Blocks of WidgetsWith the 3-D Widget Programming Library, widgets are built with high-level components. TheForms Library[9] is a good example of a GUI programming library that simplifies GUI programming by providing high-level objects. High-level components allow a programmer to concentrate on the functionality of the widget rather than low-level details such as event handlingand rendering. Moreover, high-level components, though potentially difficult to implement,can be used in many widgets once they are added to the library.The data-flow programming model, in which a program is implemented by making connections between data ports of components, is adopted as the programming model for 3-D widgets.This model allows each widget component to be self-contained, with a well-defined interface toother components.A widget performs the following tasks: interfaces with the application program, handlesuser interaction, provides visual feedback, and performs general computation. The four classesChapter 3. Design and Implementation 26of high-level components mentioned in Section 2.3.1 share the above tasks in the following way:• Parts respond to user interaction and help to define the shape of the widget.• The motion hierarchy, built with spaces, provide visual feedback of the widget’s states.• Slots are the widget’s interface to the application program.• General components perform general computations for the widget.This classification is reflected in the class hierarchy of the 3-D Widget Programming Library.The class hierarchy, discussed in Section 3.2, allows the addition of new components to thelibrary by subclassing appropriate base classes.3.1.4 Multiple Controls and Support of Five Data TypesIn Inventor, a manipulator interfaces with the application program through its delta matrix.There are two problems with this method: first, a manipulator controls or displays only onepiece of application data; second, the support of only one data type is restrictive.In the 3-D Widget Programming Library, a widget displays and controls many data itemsvia the slots owned by the widget. A slot communicates internally with widget componentsand externally with Inventor nodes/fields and the application program. Slots support five datatypes: boolean, integer, float, 3-D float vector and 4x4 float matrix. Each vector or matrixdata item carries with it a reference to the space (or coordinate frame) under which the dataitem resides. The support of these data types should be adequate for most applications. Thesedata types are supported for both internal communication among widget components, andexternal communication with Inventor nodes/fields and the application program. To keep theprogramming library simple, more advanced data types, such as arbitrary data structures,functions and pointers are currently not supported.Chapter 3. Design and Implementation 273.2 Design of the ClassesThe 3-D Widget Programming Library is structured as a C++ class hierarchy, as shown inFigure 3.1, defining the base classes for widget classes (Sowidget) and component classes(DFNode). Component classes are further specialized to part classes (WPart), space classes(WSpace) and slot classes (WSlot).Data ports for widget components are encapsulated in the DFPort class and its five descendants. The attachments between widget slots and Inventor nodes/fields are defined by theWAttach class and its descendants.DLList and Element provide double-linked list capabilities for the library. Lists are used,for example, in SoWidget for keeping track of widget components owned by a widget.The following sections describe the design of the above classes in detail.3.2.1 The Widget Base ClassThe base class for all 3-D widgets is SoWidget, a descendent class of the Inventor manipulatorbase class SoDragManip from which it inherits many useful properties:• As a descendent class of SoDragManip, it only needs to provide callback functions for thethree stages of interaction with the mouse, i.e. click, drag, and release.• It has access to useful information such as the mouse position, the picked object, thetrigger event, and viewing parameters.• SoDragmanip manages dictionaries of name/geometry pairs. This capability allows thegeometries of a 3-D widget to be altered without re-compilation.The design of widget slots and the attach mechanism (discussed later) is also influenced by theattach mechanism in SoDragManip.Chapter 3. Design and Implementation 28SoDragManip SoWidget RackWidgetOneLengthWidgetThreeDialWidgetESDialWidgetDFNode WPart WDialPartWSliderPartWGeomPartWLengthPartWSpace WRootedSpace WLocalSpace,H WCustomSpaceWPortedSpace / WEditSpaceWWorldSpaceWSlot WBoolSlotWIntSlotWFIoatSlotWVec3fSlotWMatrixSlotDFBoolNodeDffloatNodeDFFloatVec3fNodeDFPort DFBoolPort WAttach WBoolAttachDFtntPort WlntAttachDFfLoatPort WFloatAttachDFVec3fPort WVec3fAttachDFMatrixPort WMatrixAttachDLListElementFigure 3.1: The class hierarchy for the 3-D Widget Programming LibraryChapter 3. Design and Implementation 29SoWidget provides further abstractions to simplify widget implementation. The differentroles of SoWidget are discussed in the following sections.The Keeper of Widget Components and GeometriesA programmer adds widget components to a widget by calling the appropriate member functions listed below:WPart *addPart( WPart *part, II part to be added.const SbName &name, /1 name of part.mt style, II style of part.WRootedSpace *parent, 7/ parent space.WPortedSpace *feedback, /7 feedback space.SbBool isAbsolute, /7 absolute/delta feedback matrix.SbDict *classDict);// dictionary for geometries.WCustomSpace *createSpace( const SbNaine &name, /1 name of space.WRootedSpace *parent, // parent space.const SbMatrix &init );// initial matrix value.WSlot *addSlot( WSlot *slot, // slot to be added.const SbName &name );// name of slot.DFNode *addDFNode( DFNode *node );// component to be added.These functions initialize the widget components and incorporate them in the appropriatelists kept by SoWidget. These lists allow SoWidget to delete all the widget components thatbelong to a widget in its destructor . Except for space components, which can only be instancesof the class WCustomSpace. all components have to be created before being added to a widget.Space components are added to the widget simply through the createSpace function listedabove.Each derived class of SoWidget defines a static dictionary called classDictionary to maintain name/geometry pairs for the class, and each instance of the class maintains a local dictionary called userDictionary, which is a member variable of SoWidget. A name/geometry pair‘The implication is that a widget component should only have one owner.Chapter 3. Design and Implementation 30contains an Inventor subgraph that is either a part geometry or the visual style of a part. An entry in userDictionary takes precedence over an entry of the same name in classDictionary.Entries of the dictionaries for a widget are inherited from SoDragManip and SoWidget, andadditional entries can be added to widget classes and individual widget instances. Section A.3in Appendix A shows how new entries are added to the dictionaries for various levels of widget customization. Sections B.1 and B.4 in Appendix B show how classDictionary anduserDictionary are initialized for a widget.Some arguments of addPart need explanation:• style determines the “style”, or visual property, of the part. The integer value styleis used to search in the widget dictionaries for the Inventor subgraphs, with names“style<style>” and “style<style>Active”, that will become the inactive and active stylesfor the part.• isAbsolute determines how the part affects the feedback space. More details are givenin Section 3.2.4.• classDict should point to the classDictionary of the derived widget class. If addPartfinds an entry in userDictionary or classDict with a name that matches the nameargument, it replaces the default geometry of the part with the geometry of the entry.The classDict argument is needed because addPart is a member function of SoWidget;addPart has no access to classDictionary of the derived widget class.The Coordinator of Widget PartsSoWidget registers with SoDragManip callback functions for the three phases of interaction.These functions are called when SoDragManip detects a mouse click on the widget, a mousedrag, or a mouse button release:II Calibacks registered with SoDragManip using:II addStartCallback( &SoWidget::startCB );Chapter 3. Design and Implementation 31II addMotionCallback( &SoWidget: :motionCB );7/ addFinishCallback( &SoWidget::finishCB );//static void startCB( void *, SoDragManip * ); II mouse button click.static void motionCB( void *, SoDragManip * ); // drag.static void finishCB( void *, SoDragManip * ); II mouse button release.II Functions that work for the static callback functions above.void rnanipulateStartO; II mouse button click.void manipulateO; 7/ drag.void manipulateFinishO; II mouse butoon release.These functions do not handle the interaction themselves, but instead determine whichwidget part is picked, then call the appropriate member functions of the part to handle eachphase of the interaction. Section 3.2.4 discusses those member functions in more detail.The Provider of InformationDescendants of SoDragManip, such as SoWidget, have access to useful information about interactions. However, a widget does not handle the interaction itself; it asks the picked widget partto handle the interaction. Due to this arrangement, the base class of all parts, WPart, is madea friend2 of SoWidget, and includes the member functions for accessing useful information forhandling interactions. Section 3.2.4 describes those functions in more detail.The Root of the Motion HierarchySoWidget is derived from SoSeparator (a group node that is allowed to have child nodes),which allows a widget to be the root of an Inventor subgraph. In fact, this property lets amotion hierarchy be built inside3 a widget by inserting transformation nodes and separator21n C++, a friend of a class, be it a function or another class, has full access to all members variablesand functions of the class, including members that are private (only accessible by the class) or protected (onlyaccessible by descendants of the class).may be more appropriate if we view the widget as the root of a graph.Chapter 3. Design and Implementation 32nodes at the right places within the widget’s subgraph. The addition of space components toa widget does exactly that. Section 3.2.5 looks into space components.3.2.2 The Basic Widget Component ClassesAs mentioned in Section 3.1.3, the programming model for 3-D widgets is the data-flow model;i.e. the behaviors of a widget is determined by its components and the connections amongthem4. Each component is a data-flow node with one or more data ports that define thecomponent’s interface to other components. When any input port of a component is updated,the component has to update its internal state and its output ports.The Widget Component Base ClassIn the 3-D Widget Programming Library, DFNode is the base class for all widget componentclasses, including the three specialized base classes: WSlot, WPart and WSpace. To be useful, awidget component class should define one or more pointers to data ports in its class definition.Typically, the data ports of a component are created in the constructor of the component. Thecomponent class should also define the invoke function, whose duty is to respond to updatesin input data ports. invoke is declared in DFNode as follows:virtual void invoke( void *userData, II auxiliary data.DFPort *byPort ); II the updated input port.The Data Port ClassesDFPort and its five descendants— DFBoolPort, DFlntPort, DFFloatPort, DFVec3fPort, andDFMatrixPort— define the data ports. Two attributes control the behavior of a data port.The first attribute is the direction of data flow. To a component, a data port is either an inputport, an output port, or both. The second attribute is the nature of the data. The data that4Plus the motion hierarchy formed by parts and spaces, strictly speaking.Chapter 8. Design and Implementation 33a port carries can be absolute or delta (relative)5.These attributes are specified in the secondargument to the constructor as a bit mask, using the definitions in DFPortType:enum DFPortType {ABS_PORT = 1, DEL_PORT = 2,IMP_PORT = 4, OUT_PORT = 8};DF<dataType>Port( DFNode *own, II component owning the port.mt port_type );// port attributes bitmask.// e.g. ABS_PORT I IMP_PORT I OUT_PORTA data port communicates with other components by making connections with their ports.Member functions connect and disconnect of DFPort manage connections. They are definedbelow:SbBool connect( DFPort *port ); II connect to port.void disconnect( DFPort *port ); /1 disconnect from port.connect makes sure the two ports are compatible (i.e. one is input-capable and the otheroutput-capable, are of the same data type, and both handle absolute or delta values), thenupdates the connection lists of both ports. Finally it updates the data in the input port withthe data in the output port. If both ports are capable of input AND output, then the portwhose connect is called is updated by the port specified in the argument.Port classes provide the following functions for accessing their data:void setData( <dataType> d );// set data (used by the port’s owner).void setData( DFPort *p ); II set data (used by connected ports).<dataType> getDataO; II return data (used by the port’s owner).setData( <dataType> d ) is called by the owner of the port. It is responsible for updatingthe port and all the input ports connected to the port by calling setData( DFPort *p ) ofthe input ports. It DOES NOT call the invoke function of the port’s owner.5The data represents the true value or an increment of the value from the last update, respectively.Chapter 3. Design and Implementation 34setData( DFPort *p ) of a receiving port is called by the sending port p to update thereceiving port with the data in p. The function calls invoke of the receiving port’s owner. Ifthe receiving port is output-capable, then it also updates the input-capable ports (except p)connected to the port.getData is used by the port’s owner to obtain the data stored in the port. If the data isa delta valne, then getOata resets the data to the identity valne so that subsequent calls togetData will return the identity value. The identity values are set by the member functionsetlden (except for boolean ports, which have no identity values):void setlden( <dataType> d );// set the identity value.Since DFVec3fPort and DFMatrixPort carry information about space, their setData andgetData are slightly different:void setData( <dataType> d,WSpace *s ); // space from which data comes from.void setData( DFPort *p ); II same as other ports.<dataType> getData( WSpace *&s ); II space from which data comes from.3.2.3 The Widget Slot ClassesThe relationship between slots and widgets is similar to that between ports and components;the slots of a widget define its interface to the application program. Slots are specialized widgetcomponents that communicate data to and from the application program for the widget.The application program interacts with a slot either by registering a callback function withthe slot or by attaching Inventor nodes/fields to the slot. The slot also interacts with widgetcomponents through an absolute bidirectional data port of the same data type as the slot itself.Both callback functions and attachments access the data in a slot with the following slotmember functions:void setData( <dataType> dat, // data.Chapter 3. Design and Implementation 35void *src); II source of data. e.g. Inventor node/field.<dataType> getDataO; 1/ return the data.setData updates the slot’s data and the data port, then processes all attachments andcallback functions (except the source of data, if specified). getData simply returns the datastored in the slot. For WVec3fSlot and WMatrixSlot, the functions have one more argumentfor space information. In addition, the programmer must specify the space under which theslot should store the data:W<dataType>Slot( WSpace *spa );// constructor that sets the space.void setSpace( WSpace *spa ); II set the space for the slot.// NULL means data is used as is with// no conversion.void setData( <dataType> dat, 1/ data.WSpace *spa, 1/ space from which data comes from.void *src ); /1 source of data.void setData( <dataType> dat,SoPath *pat, // path which defines a space transformation.void *src );<dataType> getData( WSpace *&spa );<dataType> getData( SoPath *pat );Here, getData returns the data transformed to the space specified in the argument. If theargument is (WSpace *) NULL, getData returns the data and the space as stored in the slot.If the argument is (SoPath *) NULL, getData returns only the data stored in the slot.There are slot classes supporting five data types: WBoolSlot, WIntSlot, WFloatSlot,WVec3f Slot, and WMatrixSlot.Callback functionsCallback functions must be in the form:typedef void WS1otCB( void *data, // auxiliary data.WSlot *slot ); II the slot triggering the callback.Chapter 3. Design and Implementation 36Callback functions are registered with or removed from a slot with the slot member functions:void addCallback C WS1otCB *f, II the callback function.void *data ); II auxiliary data.void removeCallback( WS1otCB *f, // the callback function.void *data ); II auxiliary data (for identification).The application program can register more than one callback functions with each slot.Callback functions are called when the slot data is updated with setData, as described before.AttachmentsThe attachment of Inventor nodes/fields to a slot is modeled after the attach member function of the manipulator classes for coupling an Inventor node with the delta matrix of themanipulator.Four attributes control the behavior of an attachment: the direction of data flow (input,output, or both), space conversion between the space of the slot data and the space of theattached node/field (on or off), the data conversion function (described below), and an indexfor a node/field with many elements. A programmer creates or removes an attachment withthe slot member functions:enum WAttachMode {INPUT, OUTPUT, BOTHHtypedef void WAttachConvertFn( WAttach *att, II the attachment.WAttachMode i_o );// direction of data flow.II Attachment functions for nodes.SbBool createAttach( SoPath *wh, II path to attached node.WAttachMode i_o, // input, output, or both?WAttachMode mi, // direction of data initialization.SbBool tra, II space transform performed?WAttachConvertFn *fun, II data conversion function.mt md); II index into multi-element node.Chapter 3. Design and Implementation 37SbBool removeAttach( SoPath *wh, // path to attached node.mt md ); I,! index into multi-element node.// Attachment functions for fields.SbBool createAttach( SoPath *wh,<fieldType> *fie,// attached field.WAttachMode i_o,WAttachMode mi,SbBool tra,WAttachConvertFn *fun,mt md ; 1/ index into multi—element field.SbBool removeAttach( SoField *fie, II attached fieldmt md II index into multi—element field.More than one ncde/field can be attached to a slot. createAttach creates an attachmentand inserts it in the attachment list of the slot. Five classes of attachments— WBoolAttach,WlntAttach, WFloatAttach, WVec3fAttach, and WMatrixAttach correspond to the slotclasses of the same data types. The attachment keeps track of the slot and the node/field thatit attaches as well as its own attributes. If the attachment is capable of receiving data for theslot (i.e. the data flow direction is input or both), the attachment also creates an Inventor datasensor to monitor changes in the attached node/field.The purpose of the data conversion function is to obtain data from either the slot or thenode/field, perform some calculations, then update the other end of the attachment with theresult. Typically, a data conversion function is written for a specific slot data type, and handlesdata conversions between the slot and several node/field classes.If the attachment is capable of output, the data conversion function is invoked when theslot’s setData is called. If the attachment is capable of input, then sensorCB, a memberfunction of the attachment that is registered with the data sensor, invokes the data conversionfunction when the node/field is updated.A default data conversion function called defaultConvertFn is provided with each attachment class for registering with attachments of that class. However, a programmer can registera customized conversion function with each attachment.Chapter 3. Design and Implementation 38Connections to Widget ComponentsA slot is a widget component with a single data port that both inputs and outputs absolutedata. The port is updated by setData, as described in Section 3.2.2. When the port is modifiedby output ports connected to it, the slot’s invoke function calls the slot’s setOata function,which invokes the callback functions and updates the attached nodes/fields.3.2.4 The Widget Part ClassesParts, together with the motion hierarchy formed with spaces, define a widget’s interactivebehaviors and visual feedback. Each part incorporates the geometry that should ideally suggestits usage, and possibly one or more interactive behaviors.All part classes are descendants of the base class WPart. WPart provides several services toits descendants, as discussed in the following sections.Maintaining GeometryThe constructor of WPart builds an Inventor subgraph as shown in Figure 3.2. The pick nodedetermines whether the part can be picked. The appSwitch node allows switching among thethree possible children for the part’s appearance: invisible, inactive, or active. The leaves ofthe subgraph are the actual active and inactive styles and geometries of the part. The picknode and the appSwitch node are controlled externally via input ports (discussed later). Theconstructor of WPart is defined as:WPart( const char *fileName, /1 file to obtain geometry from.const char *defaultBuffer[], II buffer to obtain geometry from.mt numFunc ); II number of behaviors.The constructor reads both the buffer and the file (both provided by the constructor of thederived class) to look for Inventor subgraphs labeled as “inactiveGeom” and “activeGeom”.Chapter 3. Design and Implementation 39Figure 3.2: The Inventor subgraph maintained by WPartThe subgraphs become the inactive and active geometries of the part respectively. Note thatthe geometries in the file override those in the buffer, so that a user can easily change theappearance of the part by providing a file with the right name. numFunc defines the number ofdifferent interactive behaviors the part has.The functions setGeom and setStyle are used by a widget to modify the geometries andto set the visual styles of the part. A visual style is an Inventor subgraph that describes thevisual property of the part. The functions are defined as follows:void setGeom C SoNode *inactive, II inactive geometry.SoNode *active ) II active geometry.void setStyle( SoNode *inactive, II inactive style.SoNode *actjve ) II active style.Building the Motion Hierarchy and Providing FeedbackThe motion hierarchy of a widget is built with spaces and parts. setParent sets the parentspace of the part by adding the Inventor subgraph of the part as a child to the root node ofKey to Diagram:parent to childchild to childan SoSeparator nodean SoSwitch nodean SoDrawStyle nodean SoPickStyle nodean Inventor subgraphwitchinvisibleactive activestyle geometryChapter 3. Design and Implementation 40the space:void setParent( WRootedSpace *par ); // parent space.Spaces will be discussed in detail in Section 3.2.5.A part provides visual feedback by altering the matrix of its feedback space , which should bean ancestor of the part in the motion hierarchy. WPart defines two standard ports for exporting(or sometimes importing) the feedback matrix:DFMatrixPort *absMatrixlO; II input/output port for absolute feedback.DFMatrixPort *delMatrixOut; 1/ output port for delta feedback.The function setFeedback is responsible for making a connection between the absolute ordelta ports on the part and the feedback space:void setFeedback( WPortedSpace *fee, /1 feedback space.SbBool isAbsolute ); /1 absolute versus delta data.The reason for having the delta matrix output in addition to the absolute output is two-fold:first, a delta matrix is required for updating the edit space; second, the delta output allowsparts to accumulate their visual feedbacks in a single feedback space.WPart declares a member function called updateMatrixPorts, to be defined in the derivedpart class, for updating the feedback matrix ports based on the current state of the part:void updateMatrixPortsO; // update the feedback matrix ports.The function is used by some WPart member functions that affect either the motion hierarchy or the feedback space, such as setParent and setFeedback, to make sure the visualfeedback remain consistent with the application’s states after strllctural changes are madewithin the widget. The function is needed because WPart has no access to its descendants’internal states.Chapter 3. Design and Implementation 41Providing Information about InteractionWPart declares three member functions that the widget who owns the part calls. These functionsmust be provided by the derived part class:void manipulateStartO; II prepare for subsequent manipulation.void manipulateO; II handle manipulation.void manipulateFinishO;!! cleanup after manipulation.Typically, manipulateStart obtains information about the mouse location and meta-keys,then sets the initial state of the part. It also sets the part’s standard output port absPickedflutto indicate that the part is picked for manipulation:DFBoolPort *absPickedOut; II the part is picked.manipulate obtains the same information as manipulateStart, computes the new stateof the part based on the current input and the previous state, then updates the state, thefeedback matrix ports, and other output data ports supported by the part.manipulateFinish is for cleaning up after a manipulation. It is responsible for resettingabsPickedOut.WPart defines member functions for obtaining information about the manipulation:SbVec2s getLocaterPositionO; II mouse location in pixel,II relative to view port.SbVec2s getLocaterStartPositionO; II mouse location at theII beginning of manipulation.SbVec2f getNormalizedLocaterPositionO; // mouse location normalized toIf between 0 and 1.SbVec2f getNormalizedLocaterStartPosition() ;// normalized mouse locationII at the start of manipulation.void setStartLocaterPosition( SbVec2s p ); // set the starting location.SbVec3f getLocalDetailPointO; II the hit point in the part’s space.const SoDetail *getDetailQ; II details about the hit point.const SoPath *getPickPathO; II the path leading to the picked node.const SoEvent *getEventO; // the event triggering the manipulation.Chapter 3. Design and Implementation 42Every function above has a direct correspondence in the definition of SoDragManip. BecauseWPart is a friend of SoWidget, the above functions simply call their corresponding functions inthe widget that owns the part to obtain the information.Providing Controls to the PartWPart defines three standard input ports to allow external control of some of its functions:DFBoolPort *absPickableln; II can the part be picked?DFBoolPort *absVisibleln; II is the part visible?DFBoolPort *absActiveln; II should the active style be used?Input ports are handled by the invoke function, just like other widget components. invokemust be provided by the part class to handle the above input ports plus other input ports thepart class defines. WPart provides member functions to simplify the handling of the standardinput ports:void updateAppearanceO; II set the appSwitch node (the switching nodeII controlling the appearance of the part),II based on absVisibleln and absActiveln.void updatePickabilityQ;// set the pick node based on absPickableln.Maintaining MetaKey-to-Behavior MappingPart classes may support several interactive behaviors. WPart provides functions for mapping meta-key combinations (of shift, alt, and ctrl keys) to integers that represent individualbehaviors. These functions simplify the implementation of multi-behavior parts:enum WMetaKey {NO_KEY = 0, ANY_KEY = 1, SHIFT_KEY = 2, CTRL_KEY = 4,ALT_KEY = 8, ALL_KEY = 15};// map a function number with a key combination.void setFunc( mt func, II the function number. Must be smaller thanChapter 3. Design and Implementation 43II the numFunc argument given in the constructor.mt keys ); // the key combination bit-mask.// e.g. SHIFT_KEY I CTRL_KEY mean bothII shift and ctrl keys are required.II get the function number given a key combination.mt getFunc( mt keys );3.2.5 The Widget Space ClassesSpace components serve two purposes: they simplify space transformation calculations, andform the skeleton of the motion hierarchy for a widget. There are four space componentclasses: WWorldSpace, WEditSpace, WLocalSpace, and WCustomSpace.All space components are capable of returning the transformation matrices that transformfrom the spaces they represent to the world space and vice versa. These functions are definedas:SbMatrix getConversionToWoridO;SbMatrix getConversionFromWorldO;A rooted space can be a part of a widget’s motion hierarchy. Each has a root node thatmakes it capable of becoming a parent of other spaces and widget parts. The root node of sucha space is obtained by calling:SoSeparator *getRootO; II returns the root for the space.A ported space contains a modifiable transformation matrix. Each has two matrix dataports to allow other components to access the matrix:DFMatrixPort *absMatrixlO; II absolute matrix input/output port.DFMatrixPort *delMatrixln; /1 delta matrix input port.Two functions related to spaces are provided. transformNatrix transforms a matrix ina source space to an equivalent matrix that has the same effect (as observed from the world)Chapter 8. Design and Implementation 44in the target space. getConversionMatrix returns the matrix that converts the source spaceto the target space. They are both overloaded to accept both WSpace * and SoPath * asarguments:void transforruMatrix( const SbMatrix &fromNatrix, SbMatrix &toMatrix,WSpace *fromSpace, WSpace *toSpace );void transformMatrix( const SbMatrix &fromllatrix, SbMatrix &toMatrix,SoPath *fromPath, Wspace *toSpace );void tratisformNatrix( const SbMatrix &fromMatrix, SbMatrix &toMatrix,WSpace *fromSpace, SoPath *toPath );void transformMatrix( const SbMatrix &fromMatrix, SbMatrix &toMatrix,SoPath *fromPath, SoPath *toPath );SbNatrix getConversionMatrix ( WSpace *fromSpace, WSpace *toSpace );SbMatrix getConversionMatrix C SoPath *froniPath, WSpace *toSpace );SbMatrix getConversionMatrix C WSpace *fromSpace, SoPath *toPath );SbMatrix getConversionMatrix C SoPath *fromPath, SoPath *toPath );WWorldSpace, WEditSpace and WLocalSpaceThese are the standard space classes, as opposed to the custom space class, because every widgethas a worldSpace, an editSpace, and a locaiSpace, which are instances of WWorldSpace,WEditSpace and WLocalSpace respectively.worldSpace represents the space with no transformation. locaiSpace represents the spaceunder which the widget operates; it is the root of the widget’s motion hierarchy, the root nodebeing a child SoSeparator node of the widget that owns it.editSpace represents the delta matrix inherited from the manipulator base class; the deltamatrix of the widget is modified via the delMatrixln port of editSpace. The invoke functionof editSpace transforms the incoming matrix to the equivalent matrix in the edit space beforeconcatenating it to the delta matrix.Chapter 3. Design and Implementation 45transformFigure 3.3: The Inventor subgraph maintained by WCustomSpaceWCustomSpaceInstances of WCustomSpace in a widget form the motion hierarchy of the widget. A customspace is both a rooted space and a ported space. During instantiation, a custom space acquiresits name, its parent space, and the initial value of its matrix:WCustomSpace( const SbName &nam, II name of the space.WRootedSpace *par, II parent space.const SbMatrix &init ); II initial value.The constructor first creates an Inventor subgraph, consisting of an SoSeparator as theroot node and an SoMatrixTransform as the first child of the root node. Then the constructormakes the root node of this space a child of par’s root node by calling setParent:void setParent( WRootedSpace *par ); /1 become a child of parent.Figure 3.3 shows the Inventor subgraph created by WCustomSpace and how children areadded to the space.A custom space responds to updates from both delMatrixln and absMatrixlO. WhendelMatrixln is updated, invoke transforms the incoming matrix to the equivalent matrix inKey to Diagram:parent to childchild to child(J) an SoSeparator nodean SoMatrixTransform,,P\an Inventor subgraphchild spaceorchild partChapter 3. Design and Implementation 46this custom space before coucatenating it to the matrix of the transformation node. WhenabsMatrixlO is updated, invoke transforms the incoming matrix to the equivalent matrix inthe parent space of this space (rather than this space, because the new transformation is toREPLACE the existing transformation of the space. The effect of the existing transformationshould be ignored), then replaces the matrix of the transformation node with it.Chapter 4Discussion4.1 AccomplishmentsThe goals listed in Section 1.3 — i.e. ease of widget creation, extensibility of component library,compatibility with an available 3-D graphics library’, flexible interface between widgets andapplication programs, and ability for widgets to control and display multiple application dataitems are met through the object-oriented 3-D Widget Programming Library based onInventor, a widely available 3-D graphics library. This library supports the creation of new 3-Dwidgets from high-level components. Moreover, the library can be extended by adding new,user-defined components.A widget built with this library controls one or more states of the application program, anddisplays the states via relative positions and orientations among geometric parts in a motionhierarchy. A widget interfaces with the application program through callback functions orconfigurable attachments with Inventor nodes/fields.A number of widget components are implemented: slots of different data types, standardand custom spaces, several general components such as data converters or data providers, andseveral parts including a dial, a slider, and a length measure. Using these components, the‘More precisely, this library is an extension of the 3-D graphics library, Inventor.47Chapter 4. Discussion 48Widget Class Definition 25Geometry Definition 1Component Inclusions 5 Parts 107 Spaces 74 Slots 8Port Connections 8Initialization spaces 6ports 37Others 21TOTAL 123]Table 4.1: Lines of code usage in the implementation of the Rack Widgetimplementation of the rack widget (discussed in Section 2.4 and listed in Appendix B) needsfewer than 130 lines of C++ code2. The breakdown is shown in Table 4.1. The abstractionsprovided by the components, which are stand-alone objects with well-defined interfaces withother components, simplify the design of widgets.4.2 Future WorkThe 3-D Widget Programming Library provides the groundwork for building a library of generalcomponents and parts. The next step is to determine what constitutes a comprehensive anduseful set of functions and interactive behaviors that satisfies most users of 3-D GUTs, and thusminimizes the programming efforts of widget designers.Using this library, a new widget class is created through conventional programming in C++,with most of the code dedicated to creating widget components and connecting them. A widgetand component description language would make widget creation more straight-forward and2By counting semi-colons.Chapter . Discussion 49error-free. An even better tool would be a visual 3-D widget designer similar to the formdesign tool for the Forms Library described in Section 1.2.1. Another possibility would be toconstruct and modify widgets dynamically at run-time, similar to the 3D widget constructiontoolkit described in [17], either by the user or by changes in the structure of application data.Currently, a programmer cannot encapsulate a useful widget into a part to be incorporatedinto a more complex widget. The ability to build super-parts— ported mini-widgets builtfrom primative components and/or other super-parts - would allow complex and often-usedconstructs to be added to the library and shared among widgets.The programming library limits visual feedback to relative positions and orientations ofparts plus highlighting; for other types of visual feedback, such as color and text, the programmer has to implement them. More investigation is needed in determining other useful visualfeedback mechanisms to be incorporated.A general constraint system for describing relations internally among widget componentsand externally among widgets and application objects, as opposed to the data flow and datacoupling model used in this programming library, should be explored because it would allowmore general components to be built. For example, with the virtual trackball part, the dial partis not necessary because the virtual trackball can be constrained to rotate around a choosenaxis. The constraint system would also help programmers create application programs thatrequire relations among numerous objects be maintained and controlled. For example, a CADsystem that allows the designer to specify spatial relationships among objects would need theconstraint system to maintain the relationships after objects are manipulated.Bibliography[1] Alan H. Barr, “Global and Local Deformations of Solid Primitives”, Computer Graphics(SIGGRAPH ‘8.4 Proceedings), 18(3):21-30, July 1984.[2] D. Brookshire Conner, Scott S. Snibbe, Kenneth P. Herndon, Daniel C. Robbins, RobertC. Zeleznik, and Andries van Dam, “Three-Dimensional Widgets”, Computer Graphics(1992 Symposium on Interactive 3D Graphics), 25(2):183-188, March 1992.[3] James D. Foley, Andries van Dam, Steven Feiner, and John F. Hughes, Computer Graphics:Principles and Practice, Addison-Wesley, 2nd edition, 1990.[4] Ithaca Software, HOOPS Graphics System Reference Manual, version 3.2, 1991.[5] Michael Kass, “CONDOR: Constraint-Based Dataflow”, Computer Graphics (‘SIGGRAPH‘92 Proceedings), 26(2):321-330, July 1992.[6] Mark A. Linton, John M. Vlissides, and Paul R. Calder, “Composing User Interfaces withInterViews”, IEEE Computer, 22(2):8-22, February 1989.[7] Aaron Marcus and Andries van Dam, “User-Interface Developments for the Nineties”,IEEE Computer, 24(9):49-57, September 1991.[8] Brad A. Myers, Dario A. Giuse, Roger B. Dannenberg, Brad Vander Zanden, David S.Kosbie, Edward Pervin, Andrew Mickish, and Philippe Marchal, “Garnet: ComprehensiveSupport for Graphical, Highly Interactive User Interfaces”, IEEE Computer, 23(11):71-85,November 1990.[9] Mark H. Overmars, Forms Library - A Graphical User Interface Toolkit for Silicon Graphics Workstations, version 2.1, November, 1992.[10] George G. Robertson, Jock D. Mackinlay, and Stuart K. Card, “Cone Trees: Animated 3DVisualizations of Hierarchical Information”, SIGCHI ‘91 Proceedings, pp. 189-194, 1991.[11] Silicon Graphics Inc., Graphics Library Programming Guide, 1991.[12] Silicon Graphics Inc., IRIS Inventor Programming Guide - Volume I: Using the Toolkit,June 1992.50[13] Silicon Graphics Inc., IRIS Inventor Programming Guide - Volume II: Extending theToolkit, June 1992.[14] Scott S. Snibbe, Kenneth P. Herndon, Daniel C. Robbins, D. Brookshire Conner, and An-dries van Dam, “Using Deformations to Explore 3D Widget Design”, Computer Graphics(SIG GRAPH ‘92 Proceedings), 26(2):351-352, July 1992.[15] Paul S. Strauss and Rikk Carey, “An Object-Oriented 3D Graphics Toolkit”, ComputerGraphics (SIGGRAPH ‘92 Proceedings), 26(2):341-349, July 1992.[16] Robert C. Zeleznik, D. Brookshire Conner, Matthias M. Wloka, Daniel G. Aliaga, NathanT. Huang, Philip M. Hubbard, Brian Knep, Henry Kaufman, John F. Hughes, and Andriesvan Dam, “An Object-Oriented Framework for the Integration of Interactive AnimationTechniques”, Computer Graphics (SIGGRAPH ‘91 Proceedings), 25(4):105-112, July 1991.[17] Robert C. Zeleznik, Kenneth P. Herndon, Daniel C. Robbins, Nate Huang, Tom Meyer,Noah Parker, and John F. Hughes, “An Interactive 3D Toolkit for Constucting 3D Widgets”, Computer Graphics (SIGGRAPH ‘93 Proceedings), pp. 81-84, August 1993.51Appendix AUsing a Widget in an ApplicationThe following sections go through a number of examples of using widgets. The first sectionshows an application program that communicates with a rack widget through a callback function. The second section focuses on the use of attachments and data conversion functions. Thethird section dicusses the procedure for customizing widgets.A.1 Including a Widget and Registering a CallbackThe following example shows how a programmer includes a rack widget (as described in Section 2.4) in an application for deforming an object.A.1.1 The Main Loopmain is responsible for creating the display and initializing the widget classes. It calls anotherfunction to create the scene graph. The function main follows:main()///////////////////////////////////////////////////////////////////////////////{// create an X window.Widget appWindow SoXt::init( ‘Deform” );if ( appWindow == NULL ) exit( 1 );II initalize the widget classes. These must be called beforeII widgets are created.SoWidget: :initClassO;RackWidget: :initClassO;52Using a Widget in an Application 53II create the scene graph for the application.scene = create_sceneC;II create an Inventor viewer for viewing the scene.viewer = new SoXtExaniinerViewer;viewer—>setSceneGraph( scene );(void) viewer->build( appWindow );viewer—>showQ;SoXt::show( appWindow );II start the interaction loop.SoXt: :mainLoopO;}A.1.2 Creating the Scene Graph and Registering the Callback FunctionThe function create_scene creates the scene graph and sets up the communication betweenthe rack widget and the rest of the application program:static SoNode *create_scene(void)///////////////////////////////////////////////////////////////////////////////{// build the scene graph with a light, a camera,II an object to be deformed, and a rack widget.// Both the shape and the widget is located at theII origin of the world space. The scene is builtII with Inventor nodes.SoSeparator *root = new SoSeparator;root—>ref 0;SoPerspectiveCamera *camera new SoPerspectiveCamera;root->addChild( cameraroot->addChild( new SoDirectionalLight );// create the shape for the object.SoNode *shape create_shapeQ;root->addChild( shape );RackWidget *widget = new RackWidget;root->addChild( widget );camera—>viewAll( root );Using a Widget in an Application 54// register callback functions to all the slots of the widget.widget->twistValueSlot->addCallback( deformCB, 0 );widget->taperValueSlot->addCallback( deformCB, 0 );widget->taperPositionSlot->addCallback( deformCB, 0 );widget->bendValueSlot->addCallback( deforrnCB, 0 );return root;}A.1.3 The Callback FunctionThe callback function deformCB obtains all the deformation values, then modifies the shape ofthe object accordingly:static void deformCB( void *data, WSlot *slot )II determine the widget.RackWidget *widget = 0;if ( slot )widget = (RackWidget *)slot->getOwnerQ;if ( !widget )return;7/ obtain deformation values.float twist = widget—>twistValueSlot—>getDataQ;float taper = widget->taperValueSlot->getDataQ;float taperPos = widget->taperPositionSlot->getDataQ;float bend = widget->bendValueSlot->getDataQ;II deform the shape accordingly.}A.2 Using AttachmentsTwo aspects of attachment usage, creation of attachments and custom data conversion functions, are covered in the following two sections.Using a Widget in am Application 55A.2.1 Creating AttachmentsIn the following example, a dial widget controls the rotations of a cube and a cone. The cuberotates around its own z axis, whereas the cone rotates around the z axis of the widget. Thelength widget displays a rod connecting the centers of the cube and the cone as well as thedistance between them.The following code segment shows the construction of the scene graph, the creation ofattachments, and the placement of the widget:SoSeparator *root = new SoSeparator; // the root.SoPerspectiveCainera *canlera = new SoPerspectiveCamera; /1 the camera.SoSeparator *coneSpace = new SoSeparator; II cone subgraph.SoTransform *coneOffset new SoTransformO; II cone offset.coneOffset->translation.setValue( 3.0, 0.0, 0.0 ); II 3 to the right.SoMatrixTransform *coneXform = new SoMatrixTransform;// transform.SoMaterial *coneMat = new SoMaterial; II cone material.coneMat—>diffuseColor.setValue( .8, 0, 0 ); // red.coneMat->transparency.setValue( .2 ); II transparent.SoSeparator *cubeSpace new SoSeparator; II cube subgraph.SoTransform *cubeOffset= new SoTrarisformO; II cube offsetcubeOffset—>translation.setValue( —3.0, 0.0, 0.0 ); II 3 to the left.SoMatrixTransform *cubelform = new SoMatrixTransform;// transform.SoMaterial *cubeMat = new SoMaterial; II cube material.cubeMat—>diffuseColor.setvalue( 0 , 0.8, 0 ); II green.cubeMat->transparency.setValue( .2 ); II transparent.SoTransform *widgetOffset new SoTransformO; II widget offset.widgetOffset—>rotation. setValue( II 45 deg.SbVec3f( 0.0, 1.0, 0.0 ),—3.141592654/4.0 );DialWidget *dialWidget = new DialWidget; II dial widget.LengthWidget *lengthWidget = new LengthWidget; II length widget.// build the scene graph with a camera, a light, a cone on the right,// a cube on the left, a dial widget in the centre, and a lengthII widget connecting the centers of the cone and the cube.root—>ref 0;root->addChild(camera);root->addChild( new SoDirectionalLight );root—>addChild( coneSpace );coneSpace—>addChild( coneOff set );coneSpace->addChild( coneXform );coneSpace->addChild( coneMat );Using a Widget in an Application 56coneSpace->addChild( new SoCone );root->addChild( cubeSpace );cubeSpace->addchild( cubeflff set );cubeSpace->addChild( cubeXform );cubeSpace->addChild( cubeNat );cubeSpace->addChild( new SoCube );root—>addChild( widgetOff set );root->addChild( dialWidget ); // NOTE: still under world space!!root->addChild( lengthWidget );camera—>viewAll( root );/1 rotate the dial widget by 45 degrees by settingII the work space of the widget.SoPath *workPath = new SoPath( root );workPath->append( widgetOff set );workPath->ref C);dialWidget->setWorkSpacePath( workPath );workPath->unref 0;II attach the cube’s transform to dialWidget’s rotationSlot.SoPath *cubePath = new SoPath( root );cubePath->ref 0;cubePath—>append( cubeSpace );cubePath->append( cubeXform );testWidget->rotationSlot->createAttach (cubePath,OUTPUT, If slot outputs to transform.OUTPUT, If initialize transform with slot.FALSE, II no space transformation.&WNatrixAttach: :defaultConvertFn,// use default conversion function.o );II attach the cone’s transform to dialWidget’s rotationSlot.// Same as the above except space transformation is active; i.e. theII cone rotates around the widget’s Z axis.SoPath *conePath = new SoPath( root );conePath—>ref C);conePath->append( coneSpace );conePath->append( coneXform );testWidget—>getSlot(”rotationSlot”)—>createAttach( conePath,OUTPUT, OUTPUT, TRUE, &WMatrixAttach: : defaultConvertFn, 0 );If attach the cone’s and cube’s transforms to the two slots of theII length widget.Using a Widget in an Application 57lengthWidget->point iSlot->createAttach (conePath,INPUT, II slot reads transform.INPUT, II transforms initialize slot.TRUE, II space transformation performed.&WVec3fAttach: :defaultConvertFn,// use default conversion function.0);conePath->unref 0;lengthWidget->point2Slot->createAttach( cubePath,INPUT, INPUT, TRUE, &WVec3fAttach::defaultConvertFn, 0 );cubePath->unref C);A.2.2 Creating a Custom Conversion FunctionSuppose the default conversion function for vector slots does not support conversion to andfrom SoSFColor fields, and the programmer would like to use a vector slot to control the colorof an object. The following conversion function will perform the task:void newVec3fConvertFn( WAttach *att, WAttachMode i_o )//II — need to include “WFieldType.h” for definitions of field types such asII SFColor below.{WVec3f Attach *a = ( WVec3f Attach * ) att; II the attachment.WVec3fSlot *0 = ( WVec3f Slot * ) a—>owner; II owner of the attachment.SoNode *n = a->who->getTailO; // the attached node.switch ( i_o ){case INPUT:if ( a—>field && a->fieldType == SFColor ){SbColor c( ( C SoSFColor * ) a—>field )—>getValue() );o—>setData( c, (SoPath *)NULL, a );return;}breakcase OUTPUT:if ( a->field && a->fieldType == SFColor ){SbVec3f vS = o->getData( (SoPath *)NULL );( ( SoSFColor * ) a—>field )—>setValue( vS );return;}Using a Widget in an Application 58break;default:// do nothing.}II let the default conversion function handle the rest.WVec3fAttach::defaultConvertFn( att, i_o );}The following call to createAttach attaches the vec3fSlot of a widget to the diffuseColorfield of the materialNode.widget->vec3fSlot->createAttach (materialNodePath, 7/ path to material node.&(materialNode—>diffuseColor), 7/ field to be attached.BOTH, II slot both displays and controls.INPUT, 7/ field initializes slot.FALSE, 7/ no space transformation.newVec3fConvertFn, II use customized conversion function.0 );A.3 Customizing a WidgetA programmer may not be satisfied with the default appearance of a widget. For example, aprogrammer wants to modify a rack widget so that the dial handles are longer than the defaultlength of 1. There are three ways to accomplish the task; the three methods differ mainly inthe scopes of modification.The first method redefines the geometries of WDialPart; it affects all instances of the partclass. The programmer creates a file WDialPart.iv that describes the active and/or inactivegeometries of instances of WDialPart. The subgraphs of the active and inactive geometry shouldbe labeled activeGeom and inactiveGeom. The file should be located in the current directoryor the directory indicated by the SO_MANIP_DIR environment variable. The file content is asfollows:#Inventor V1.0 asciiSeparator {Label { label “activeGeom” }Label { label “inactiveGeom” }Translation { translation 0 1.0 0 }Cylinder { parts SIDES height 2.0 radius 0.1 }Translation { translation 0 0.5 0 }Sphere { radius 0.15 }Using a Widget in an Application 59Translation { translation 0 -1.0 0 }RotationXYZ { axis X angle 1.5707963 }Cylinder { height 0.15 radius 0.15 }}The second method defines the geometries of RackWidget; it affects all instances of thewidget class. The programmer creates a file Rack Widget.iv that modifies the active and/orinactive part geometries for the widget class. Modifications made with this method overridesthose made with the first method. The following example modifies geometries of the bendDialpart of the rack widget:#Inventor V1.0 asciiSeparator {Label { label “bendDialActive” }Label { label “bendDial” }# the geometry as listed in the previous example.}The third method modifies an instance of RackWidget. This is useful when the programmerwants to use several rack widgets in an application program but wants them to look differently.The programmer instantiates a widget using one of the special constructors that asks foreither a geometry file, a scene graph, or a dictionary that contains name/geometry pairs.The constructors for the rack widget are:RackWidget( const char *userGeomFile );RackWidget( SoGroup *userCeom );RackWidget( SbDict *userDict );The geometry file, scene graph, or dictionary should contain subgraphs with labels corresponding to the names of parts in the widget, just like the Rack Widget.iv example above.Modifications made with this method overrides those made with the two previous methods.For more information, please read Chapter 7 of the “IRIS Inventor Programming Guide -Volume II” for a detailed discussion.Appendix BImplementing a Widget fromExisting PartsThe implementation of the rack widget in Section 2.4 will be described. It consists of fourinteractive parts two dial parts and two slider parts that provide controls for the fourparameters. A dial part provides rotation feedback on its z axis. A slider part providestranslation feedback along its y axis. Both parts output float values that represent the amountof rotation or translation. A non-interactive part is included to provide the geometry for thewidget’s main axis.The rack widget needs four float slots so that the application program can access the fourdeformation parameters and act on changes caused by manipulations of the widget.The procedure for creating a new widget class is very similar to creating a new manipulator.You may want to refer to Chapter 8 of the “IRIS Inventor Programming Guide - Volume II”for more information.B.1 Defining RackWidgetThe widget class RackWidget is defined in the header file Rack Widget.h. In general, a definitionof a widget class should do the following:• invoke the SO_SUBNODE_ID_HEADER macro.• declare the member function initCiass. It is required for all Inventor node classes.• declare four constructors: one with no argument, and the other three with one argument which is either a name of a geometry file, a pointer to an Inventor group node,60Implementing a Widget from Existing Parts 61or a pointer to a dictionary. The latter three constructors allow an instance of the widget class to obtain its geometries from sources other than its default geometry buffer(RackWidget: :rackWidgetGeomBuffer[]) and default geometry file (Rack Widget.iv).• define pointers to all the parts and slots for the widget class.• define a pointer to classDictionary. It stores named Inventor nodes or subgraphs tobe included in the widget.• define a pointer to the geometry buffer. It holds a description of the default geometriesof widget parts.• declare the destructor.• declare or define other class-specific members.This is the listing of Rack Widget.h:#ifndef RACKWIDGET_H#def me RACKWIDGET_H#include “SoWidget .h”class WDialPart;class WSliderPart;class WGeomPart;class WFloatSlot;class RackWidget : public SoWidget{SO_SUBNODE_ID_HEADER( RackWidget ); // Define required typeld and name stuff.public:static void initClassQ; // initializes the class. To be called// after Solnteraction::initQ.7/ constructors.RackWidgetO;RackWidget( const char *userGeomFile );RackWidget( SoGroup *userGeom );RackWidget( SbDict *userDict );II parts.WDialPart *bendDial; // controls the bend.WDialPart *twistDial; /7 controls the twist.WSliderPart *taperSlider; II controls the taper.WSliderPart *taperOffsetSlider; II controls where the taper ends.WGeomPart *mainAxis; II geometry for the main axis.7/ slots.Implementing a Widget from Existing Parts 62WFloatSlot *bendlJalueSlot; // amount of bend from the bend handle.WFloatSlot *twistValueSlot; II amount of twist from the twist handle.WFloatSlot *taperValueSlot; II amount of taper from the taper handle.WFloatSlot *taperPositionSlot; II where the taper ends.protected:static SbDict *classDictionary; II dictionary for this class.void constructorSubO; II common constructor code.private:static char *rackWidgetGeomBuffer[]; II geometry buffer.“RackWidgetO; II destructor.static const double pi; II the value pi.};#endifB.2 Default GeometriesThe file Rack WidgetGeom.h defines the geometry buffer RackWidget: : rackWidgetGeomBuffer,the default geometries of parts in the widget. The buffer content is in the Inventor file format.Many part classes provide default geometries for their instances; therefore a widget programmer needs not design geometry for a part unless he or she wants to override the defaultgeometry of the part. Some part classes— e.g. SoGeomPart do not provide default geometries and therefore their instances are invisible by default.For the rack widget, the part mainAxis needs a geometry that represents the main axis ofthe widget, and the default geometry for taperPositionSlider has to be modified because itwould be obscured by the object to be deformed otherwise; therefore, new geometries for thesetwo parts have to be specified. The default geometries are used for other parts.Note that both the active and inactive geometries should be specified for a part. The twogeometries can be the same, as shown in RackWidgetGeom.h:#ifndef RACKWIDGETGEOM_H#def me RACKWIDGETGEOM_Hchar *RackWidget: :rackWidgetGeomBuffer[J =1:“#Inventor Vi.O ascii\n\Separator {\n\Label { label \“mainAxis\” }\n\Label { label \“mainAxisActive\” }\n\RotationXYZ { axis X angle 1.5707963 }\n\Cylinder { height 2.0 radius 0.10 }\n\} \n”,Implementing a Widget from Existing Parts 63‘Separator {\n\Label-C label \“taperoffsetSlider\” }\n\Label { label \“taperOffsetSliderActive\” }\n\RotationXYZ { axis X angle —1.5707963 }\n\Translation { translation 0 0.5 0 }\n\Cylinder { height 1.0 radius 0.075 }\n\}\n”,II#endifB3 Initializing the WidgetThe first section of Rack Widget.c++ invokes the macros needed by all Inventor node classes,and defines the static member variables in the class. The member function initClass is alsoimplemented:#include <iostream. h>#include <Inventor/SoDS. h>#include “DFBoolNode. h’#include “DFFloatNode .h”#include “WDialPart .h”#include “WSliderPart . h”#include “WGeomPart .h”#include “WFloatSlot .h”#include “WSpace.h”#include “RackWidget .h”#include “RackWidgetGeom h’// Initialize static dictionary that contains the geometryII and style resources for this class.SbDict *RackWidget::classDictionary = 0;II Macro which defines required variables for subclasses of SoNode.S0_SUBNOD&ID_VARS (RackWidget);II Macro which defines required methods for subclasses of SoNode.SOSUBNODE.JD.METHODS (RackWidget);II Constant needed in calculationconst double RackWidget::pi = 3.141592654;void RackWidget: :initClass()Implementing a Widget from Existing Parts 64{SD_SUBNODE_INIT_ID (RackWidget, II an example instance“RackWidget”, // class nameSoWidget ); II parent class}B.4 Constructors and DestructorsThe four constructors of RackWidget differ only in the way they construct SoWidget; therefore,they all call a common function constructorSub that does the real work for the widget class.Here are the four constructors:II No arguments.RackWidget: :RackWidget() : SoWidget(){ constructorSubO; }II Take a file name.RackWidget: :RackWidget( const char *userGeoinFile ) : SoWidget( userGeomFile ){ constructorSubO; }// Take a scene graph.RackWidget::RackWidget( SoGroup *userGeom ) : SoWidget( userGeom ){ constructorSubO; }II Take a dictionary.RackWidget: :RackWidget( SbDict *userDict ) SoWidget( userDict ){ constructorSubO; }The SoWidget constructor used determines the extra source of part geometries for a widgetinstance besides the geometries common to all instances.constructorSub is responsible for the following:• setting up the class dictionary.• creating the spaces, slots, parts, and general components, and adding them to the widget.• connecting ports of components to generate the desired behavior and initializing portsthat should remain constant.void RackWidget: : constructorSubOImplementing a Widget from Existing Parts 65{SQ_SUBN0DE_BECIN_PRQTOTYPE (RackWidget);II Read the default geometry for this widget. This willII only happen once.classDictionary = createDictionary(“RackWidget.iv”, II default geom filerackWidgetGeomBuffer, II’ compiled—in defaultsSoWidget::classDictionary ); /7 parent dictionarySOSUBNODE_ENDPROTOTYPE 0;II This will be the dictionary for use by all instances of this class.setClassDictionary( classDictionary );II Create the spaces.SbMatrix matrix, matrix2;matrix.setTranslate( SbVec3f( 0.0,0.0,—1.0 ) );WCustomSpace *twist0ffset = createSpace( “twistOff set”, localSpace, matrix );matrix = SbMatrix::identity0;WCustomSpace *taperOff set = createSpace( “taperOff set”, localSpace, matrix );matrix.setTranslate( Sbvec3f( 0.0,0.0,1.0) );matrix2.setRotate( SbRotation( SbVec3f( 1.0,0.0,0.0 ), pi/2.O ) );matrix.multLeft( matrix2 );matrix2.setRotate( SbRotation( SbVec3f( 0.0,1.0,0.0 ), —pi/2.0 ) );matrix.multLeft( matrix2 );WCustomSpace *bend0ff set = createSpace( “bendOff set”, localSpace, matrix );matrix.setRotate( SbRotation( SbVec3f( 1.0,0.0,0.0 ), pi/2.0 ) );WCustomSpace *taperOffset0ff set = createSpace( “taperOffsetOff set”,taperOffset,matrix );matrix = SbMatrix::identityQ;WCustomspace *twistFeedback = createSpace(”twistFeedback” ,twistQff set ,matrix);WCustomspace *taperFeedback = createSpace(”taperFeedback” ,taperOff set ,matrix);WCustomspace *bendFeedback = createSpace(”bendFeedback”, bendOff set, matrix);II Create the slots.twistvalueSlot = new WFloatSlotQ;addslot( twistValueSlot, “twistValueSlot” );taperValueSlot = new WFloatSlotQ;addSlot( taperValueSlot, “taperValueSlot” );taperPositionSiot = new WFloatSlotQ;addSlot( taperPositionSlot, “taperPositionSlot” );bendValueSlot = new WFloatSlotQ;addSlot( bendValueSlot, “bendValueSlot” );/7 Create the parts.Implementing a Widget from Existing Parts 66WDialPart *twistDial = new WDialPartQ;addPart( twistDial, “twistDial” , 1,twistFeedback,twistFeedback,TRUE,classDictionary );WSliderPart *taperSlider = new WSliderPartO;addPart( taperSlider, ‘taperSlider” , 1 ,taperFeedback,taperFeedback,TRUE,classDictionary );WSliderPart *taperOffsetSlider = new WSliderPartO;addPart( taperOffsetSlider, “taperOffsetSlider” , 1 ,taperOffsetOffset,taperOffset,TRUE,classDictionary );WDialPart *bendDjal = new WDialPartQ;addPart( bendDial, “bendDial’ , 1,bendFeedback,bendFeedback,TRUE,classDictionary ),WGeomPart *mainAxis = new WGeomPartO;addPart( mainAxis, “mainAxis’ .1, localSpace ,NULL,TRUE,classDictionary );II create temporary general componentsII for persistent general components, addNode should be called.DFBoolNode *boolNode = new DFBoolNode( TRUE );DFFloatNode *floatNode = new DFFloatNode( 0.0 );// initialize ports using “setData( WPort *port )“// and make connections with “connect( WPort *port )“.I/I NOTE: “setData( <dataType> data )“ is not used here becauseII it is intended to be used by the owner of the port andII thus does not update the internal state of the port owner.IItwistDial—>absLowln—>setData( floatNode->absFloatl0 );twistDial->absRangeln->setData( floatNode->absFloatlO );twistDial—>absFloatlO—>setData( floatNode—>absFloatlO );floatNode—>setData( 2*pi );twistDial—>absScaleln—>setData( floatNode—>absFloatlO );twistDial—>absPickableln—>setData( boolNode—>absBoollO );twistDial->absVisibleln->setData( boolNode—>absBoollO );twistDial—>absActiveln—>connect( twistDial—>absPickedOut );IItwistValueSlot—>absFloatlO—>connect( twistDial—>absFloatlO );floatNode—>setData( 0.0 );taperSlider—>absLowln->setData( floatNode->absFloatlO );floatNode—>setData( 1.0 );taperSlider->absRangeln->setData( floatNode->absFloatlO );taperSlider->absScaleln->setData( floatNode->absFloatlO );taperSlider->absFloatlO->setData( floatNode->absFloatlO );taperSlider—>absPickableln—>setData( boolNode—>absBoollO );taperSlider—>absvisibleln—>setData( boolNode->absBoollO );//taperSlider—>absActiveln—>connect( taperSlider->absPickedOut );Implementing a Widget from Existing Parts 67taperValueSlot—>absFloatlO—>connect( taperSlider->absFloatlO );floatNode—>setData(—1.0 );taperoffsetSlider->absLowln->setData( floatNode->absFloatlO );floatNode—>setData( 2.0 );taperoffsetSlider—>absRangeln—>setData( floatNode—>absFloatl0 );floatNode—>setData( 1.0 );taperOffsetSlider—>absScaleln—>setData( floatNode—>absFloatlO );floatNode—>setData( 0.0 );taperOffsetSlider->absFloatlo->setData( floatNode->absFloatlO );taperOffsetSlider—>absPickableln—>setData( boolNode->absBoollO );taperOffsetSlider—>absVisibleln—>setData( boolNode—>absBoollO );IItaperflffsetSlider—>absActiveln-->connect( taperOffsetSlider—>absPickedOut );taperPositionSlot—>absFloatlO—>connect( taperflffsetSlider—>absFloatlO );floatNode—>setData(—pi );bendDial->absLowln—>setData( floatNode-)absFloatl0 );floatNode—>setData( 2*pi );bendDial->absRangeln->setData( floatNode->absFloatlfl );bendDial—>absScaleln—>setData( floatNode—>absFloatlO );floatNode—>setData( 0.0 );bendDial->absFloatlO—>setData( floatNode—>absFloatlO );bendDial—>absPickableln->setData( boolNode->absBoollO );bendDial—>absVisibleln—>setData( boolNode—>absBooll0 );//bendDial->absActiveln->connect( bendDial->absPicked0ut );bendValueSlot—>absFloatl0—>connect( bendDial—>absFloatl0 );niainAxis—>absVisibleln—>setData( boolNode—>absBooll0 );boolNode->setData( FALSE );mainAxis->absPickableln->setData( boolNode->absBoollO );delete boolNode;delete floatNode;}The destructor should release any resources the widget has acquired. However, the destructor of SoWidget already handles all the spaces, parts, slots and general components added tothe widget. Therefore, we only need a very simple destructor:RackWidget: : RackWidget 0{}Appendix CImplementing a GeneralComponentThe only requirement for a new general component class is that it is derived from the baseclass DFNode, the ancestor of all widget components. For a general component to be useful,it should contain at least one port capable of output so as to affect other components. Everyderived class of DFNode must implement the invoke member function.We shall create a new general component class DFFloatVec3fNode that converts betweenthree float values and a 3-D vector both ways.C.1 Defining the ComponentDFFloatVec3fNode.h defines the class. The class definition should do the following:• declare a constructor and a destructor.• declare the invoke function.• define the pointers to all the ports for the component.The header file is listed below:#ifndef DFFLOATVEC3FNODE_H#def me DFFLOATVEC3FNODE_H#include “DFNode .h”#include “DFPort .h”68Implementing a General Component 69class DFFloatVec3f Node : public DFNode{public:II constructor and destructor.DFFloatVec3fNodeQ;DFFloatVec3f Node 0;II data flow invoke.void invoke( void *, DFPort *port );II ports.DFFloatPort *absXIO; II X component of vector.DFFloatPort *absYIO; II Y component of vector.DFFloatPort *absZIO; II Z component of vector.DFVec3f Port *absVec3f ID;!! vector.#endifThe names of the ports reflect the facts that they all hold absolute values (as opposed todelta values) and they are all capable of both input and output.C.2 Constructors and DestructorsThe constructor is responsible for creating the ports and the destructor should delete them.The first section of DFFloatVec3f Node . C++ follows:#include ‘DFFloatVec3f Node .h’DFFloatVec3f Node: : DFFloatVec3fNode 0{absVec3f 10 = new DFVec3fPort( this, ABS_PORT I INP_PORTI OUT_PORT );absXIO = new DFFloatPort( this, ABS_PORT I INP_PORTI OUT_PORT );absYlO = new DFFloatPort( this, ABS_PORT I INP_PORTI OUT_PORT );absZIO = new DFFloatPort( this, ABS_PORT I INP_PORTI OUT_PORT );absVec3fIO>setData( SbVec3f( 0.0, 0.0, 0.0 ), NULL );absXIO—>setData( 0.0 );absYIO—>setData( 0.0 );absZIO—>setData( 0.0 );}DFFloatVec3f Node: : DFFloatVec3fNode(){delete absVec3f 10;Implementing a General Component 70delete absXIO;delete absYlO;delete absZIO;}C.3 The Member Function invokeThe invoke member function is called when the data in one of the input ports is changed.It finds out which port is modified, then modifies the component’s internal state and outputports:void DFFloatVec3fNode: :invoke( void *, DFPort *port )///////////////////////////////////////////////////////////////////////////////{WSpace *s;SbVec3f vec absVec3flO—>getData( s );if ( (void *) port == absVec3f 10 ){II check if the data actually changes.// If not, do not “setData” in order to cutII unnecessary data flow.if ( vec[O] absXIO->getData() )absXIO—>setData( vec[O] );if ( vec[1] != absYIO—>getData() )absYIO—>setData( vec[1] );if ( vec[2] absZIO—>getData() )absZIO—>setData( vec[2] );}else if ( (void *) port == absXIO && vecCO] absXIO->getData() )absVec3fl0—>setData( SbVec3f( absXIO—>getDataO, vec[1], vec[2] ), s );else if ( (void *) port == absYlO && vecEl] 1= absYIO—>getData() )absVec3fl0—>setData( SbVec3f( vec[0], absYIO—>getDataO, vec[2] ), s );else if C (void *) port == absZIO && vec[2] absZIO—>getData() )absVec3fl0—>setData( SbVec3f( vec[OJ, vec[1], absZIO—>getData() ), s );}Appendix DImplementing a PartEach new part class must implement the following methods:• manipulateStart prepares the part for manipulation.• manipulate updates the internal state of the part and the output ports based on themanipulation.• manipulateFinish finishes the manipulation.• invoke is called when one of the input ports is modified. It then updates the part’sinternal state and output ports accordingly.• updateMatrixPorts updates delMatrixOut and absMatrixlO based on the part’s currentstate.The example part is WDialPart. This part shows a handle that can be grabbed and rotatedaround the z axis in the part’s local space. It has the following ports:• absFloatlO and deiFloatOut output the internal state absValue and the change in thestate respectively. absValue is the amount of rotation scaled by the value obtained fromabsScaleln (see below). absFloatlO is also an input port that can alter absValue.• absLowln sets the lower limit for absValue.• absRangeln sets the range of absValue, starting from the lower limit. A value of 0 meansthat the range is infinite.• absScaleln sets the scaling factor for absValue. absValue is equal to the amount ofrotation (number of turns) times the scaling factor.71Implementing a Part 72D.1 Defining the PartWDialPart.h defines the class. The class definition:• declares a constructor and a destructor.• declares the required member functions.• defines the pointers to all the ports for this part.• declares the geometry buffer.• defines a plane projector that is used for calculating the amount of rotation caused bythe manipulation.• defines member variables which store the part’s internal state.The header file is listed below:#ifndef WDIALPART_H#define WDIALPART_H#include “Inventor/proj ectors/SbPlaneProj ector. h’#include “DFPort .h”#include “WPart .h///////////////////////////////////////////////////////////////////////////////class WDialPart : public WPart///////////////////////////////////////////////////////////////////////////////{public:WDialPartQ; // constructor.WDialPart() ;// destructor.void updateMatrixPortsQ; II update matrix ports based on internal states.void manipulateStartO; // prepare for manipulation.void manipulateO; II update ports and internal states.void manipulateFinishO; II finish.void invoke( void *data, DFPort *port );DFFloatPort *absFloatlo; II I/O port for value.DFFloatPort *delFloatOut;// output port for change in value.DFFloatPort *absLowln; // lower limit for value.DFFloatPort *absRangeln; II range of value.DFFloatPort *absScaleln; // value of one revolution.private:Implementing a Part 73void setData( double value, void *source = NULL ); II used by otherII member functions.static char *wDialPartGeomBufferE];// the default geometry.double absValue; // the current state of the dial.double absLow, II the lower limit.absRange, II the range.absScale; /1 the scale (value per revolution).static const double pi;SbPlaneProjector planeProjector;// the projector used.SbVec3f prevProj; II previous projection into 3D space.SbVec3f axisofRotation;// axis of rotation in the feedback space.#endifD.2 Default GeometriesThe file WDialPartGeom.h initializes the geometry buffer WDialPart: :wDialPartGeomBufferthat describes the default geometries of the part. The description is written in the Inventorfile format.The geometry of this part represents a handle. Both the active and inactive geometries(labeled as activeGeom and inactiveGeom) must be provided for a part. The two geometriescan be the same, as shown in this example:#ifndef WDIALPARTGEOM_H#def me WDIALPARTGEOM_Hchar *WDialPart: :wDialPartCeomBuffer[] =“#Inventor V1.O ascii\n\Separator {\n\Label { label \‘activeGeom\” }\n\Label { label \“inactiveGeom\” }\n\Translation { translation 0 0.5 0 }\n\Cylinder { parts SIDES height 1.0 radius 0.1 }\n\Translation { translation 0 0.5 0 }\n\Sphere { radius 0.15 }\n\Translation { translation 0 —1.0 0 }\n\RotationXYZ { axis X angle 1.5707963 }\n\Cylinder { height 0.15 radius 0.15 }\n\}\n”,\0};#endifImplementing a Part 74D.3 Constructors and DestructorsThe constructor needs to create a plane projector and initialize WPart. It is also responsiblefor initializing the internal state and creating the required ports.The destructor deallocates all the ports and resources allocated for the part.#include <iostreain.#include “SoWidget .h”#include “WSpace . h’#include “WDialPart .h”#include “WDialPartGeom.h”const double WDialPart::pi = 3.141592654;WDialPart: :WDialPart()///////////////////////////////////////////////////////////////////////////////planeProjector( SbPlane( SbVec3f( 0.0, 0.0, 1.0 ), 0 ) ),WPart( “WDialPart.iv”, wDialPartGeomBuffer ){absFloatlO = new DFFloatPort( this, ABS_PORT I INP_PORT I OUT_PORT );delFloatOut= new DFFloatPort( this, DEL_PORT I OUT_PORT );absLowln = new DFFloatPort( this, ABS_PORT I INP_PORT );absRangeln new DFFloatPort( this, ABS_PORT I INP_PORT );absScaleln = new DFFloatPort( this, ABS_PORT I INP_PORT );absValue = 0;absFloatlO->setData( absValue );delFloatOut—>setData( 0.0 );absLow = 0;absLowln->setData( absLow );absRange = 0; II 0 = no limitabsRangeln->setData( absRange );absScale = 2*pi;absScaleln—>setData( absScale );absMatrixlO->setData( SbMatrix: identity 0, NULL );delMatrixOut->setData( SbMatrix: : identity 0, NULL );}WDialPart: : WDialPart C){delete deiFloatOut;delete absFloatlO;delete absLowln;Implementing a Part 75delete absRangeln;delete absScaleln;}D.4 The Member Function updateMatrixPortupdateMatrixPort is required by WPart for setting the two matrix ports delMatrixOut andabsMatrixl0 based on the current internal state of the part. In this example, the function setsdelMatrixOut to the identity matrix because this function does not change the state of thepart. absMatrixDut is set to a matrix that represents a rotation proportional to absValue,the state of the part representing the amount of rotation.void WDialPart: :updateMatrixPorts(){II convert the z-axis in part space to the feedback space.if C parent && feedback )getConversionMatrix( parent, feedback ) .multVecMatrix(SbVec3f( 0.0, 0.0, 1.0 ), axisOfRotation );elseaxisOfRotation = SbVec3f( 0.0, 1.0, 0.0 );SbMatrix matrix;SbRotation rotation;II delMatrixOut is set to the identity matrix since there isII no change in the internal state “absValue”.matrix = SbMatrix::identityO;if ( feedback )delMatrixOut->setData( matrix, feedback );elsedelMatrixOut->setData( matrix, NULL );II calculate the rotation in the feedback space.float absAngle = absValue*(2*pi)/absScale;rotation. setValue( axisOfRotation, absAngle );matrix.setRotate( rotation );7/ The new matrix is intended for the parent of the feedback space.if ( feedback )absMatrixlO->setData( matrix, feedback->getParent() );elseabsMatrixl0->setData( matrix, NULL );}Implementing a Part 76D.5 The Member Function invokeinvoke responds to the input ports in the following ways:• absFloatln triggers updates to the member variable absValue and output ports.• absMatrixln is ignored.• absLowln, absRangeln and absScaleln trigger updates to the member variables absLowor absRange or absScale and output ports.• absVisibleln and absActiveln cause WPart: :updateAppearance to be called.• absPickableln causes WPart: :updatePickabilityto be called.void WDialPart::invoke( void *data, DFPort *port ){if ( parent && feedback )getConversionMatrix( parent, feedback ) .multVecMatrix(SbVec3f( 0.0, 0.0, 1.0 ), axisOfRotation );elseaxisOfRotation = SbVec3f( 0.0, 1.0, 0.0 );if ( port ( DFPort * ) absFloatlO ){// set the internal state of the part with absFloatlO’s data.// Also indicate the absFloatlO is the source of data in the// second parameter.setData( absFloatlo—>getDataO, absFloatlO );}else if ( port == ( DFPort * ) absMatrixlO ){// This part ignores input from this port.}else if C port == C DFPort * ) absLowln )absLow = absLowln->getDataO;setData( absValue );}else if C port == ( DFPort * ) absRangeln ){absRange = absRangeln->getDataO;if ( absRange < 0 ){absLow = absLow + absRange;Implementing a Part 77absRange = - absRange;}setData( absValue );}else if ( port ( DFPort * ) absScaleln ){if ( absScaleln—>getData() == 0.0 ){cerr << “WDialPart::invoke()— absScaleln cannot be zero, defaults to one”<< endl;absScale = 1.0;absScaleln—>setData( absScale );}elseabsScale = absScaleln->getDataO;setData( absValue );}else if ( port == ( DFPort * ) absVisibleln I Iport == ( DFPort * ) absActiveln )updateAppearanceQ;else if ( port == ( DFPort * ) &absPickableln )updatePickabilityO;}D.6 The Member Functions for ManipulationsmanipulateStart prepares the dial part for subsequent manipulations. It:• sets up the plane projector.• records the initial point of projection.• determines the axis of rotation in the feedback space.• sets the data in the absPickedOut port to indicate that the part is picked.void WDialPart: :manipulateStart(){planeProjector.setViewVolunte ( getviewvoluiue() );planeProjector.setWorkingSpace( parent—>getConversionToWorld() );prevProj = planeProjector.project( getNormalizedLocaterPosition() );Implementing a Fart 78II obtain the rotation axis in the feedback space.if ( feedback )getConversionMatrix( parent, feedback ) .multVecMatrix(SbVec3f( 0.0, 0.0, 1.0 ), axisOfRotation );elseaxisOfRotation = SbVec3f( 0.0, 1.0, 0.0 );absPickedout->setData( TRUE );}manipulate determines the amount of rotation, and calculates the new absValue based onthe rotation, the lower limit, the range, and the scaling factor. It then updates absValue andthe output ports.void WDialPart: :manipulate()////////////////////////////////////,////////////////////////J/////////////////,1/ just in case the view changedplaneProjector.setViewvolume( getviewvolume() );II find the delta angle.Sbvec3f currProj = planeProjector.project( getNormalizedLocaterPosition() );SbRotation rotation( prevProj, currProj );SbVec3f axis;float delAngle;rotation.getValue( axis, delAngle );delAngle = ( axis[2] > 0 ) 7 delAngle : —delAngle;float delValue = delAngle*absScale/(2*pi);setData( absValue + delValue );// the working space may have changed due to the changes above.planeProjector.setWorkingSpace( parent->getConversionToWorld() );prevProj = planeProjector.project( getNormalizedLocaterPosition() );}manipulateFinish sets the port absPicked0ut to indicate that this part is no longer picked.//////////////////////////////////////i/i//i//ii/i//////i//ii//i/i/////////////void WDialPart: :manipulateFinish()Implementing a Part 79{absPickedOut->setData( FALSE );}setData updates the member variable absValue and the output ports deiFloatOut, absFloatlO,delMatrixOut and absMatrixlO. setData avoids excessive data flow by not setting the portthat causes setData to be called. setData is a convenience member function called only bymanipulate and invoke; therefore, it is made a private member function.void WDialPart: :setData( float value, void *source ){II check for overflow.float tmpValue = value;if ( absRange 0 )if ( value < absLow )tmpValue = absLow;else if ( value > absLow + absRange )tmpValue = absLow + absaange;if ( tmpValue absValue ) // no change.return;float delValue = tmpValue- absvalue;absValue = tmpValue;II update the FloatOuts.delFloatOut—>setData( delValue );if ( source != absFloatlO II absValue absFloatlO->getData() )absFloatlO—>setData( absValue );// update the MatrixOuts.SbMatrix matrix;SbRotation rotation;II set delMatrixOut.float delAngle = delValue*(2*pi)/absScale;rotation.setValue( axisOfRotation, delAngle );matrix.setRotate( rotation );if ( feedback )delMatrixQut->setData( matrix, feedback );elsedelMatrixOut->setData( matrix, NULL );II set absMatrixl0. Ignore the possibility that it may be a sourceImplementing a Part 80// because we are not responding to this port in invoke 0.float absAngle = absValue*(2*pi)/absScale;rotation.setValue( axisOfRotation, absAngle );matrix.setRotate( rotation );if ( feedback )absMatrixlO->setData( matrix, feedback->getParent() );elseabsMatrixlo->setData( matrix, NULL );}Appendix ERelevant Enhancements in the NewRelease of InventorInventor 2.0 has recently been released. The following sections briefly describe the new andenhanced features in the new version that are relevant to the 3-D Widget Programming Library.The information is obtained from the book “the Inventor Mentor” by Josie Wernecke, publishedby the Addison-Wesley Publishing Company.The last section relates this new release to the 3-D Widget Programming Library.E.1 Manipulators and DraggersInventor 2.0 introduces a new class of interactive nodes called draggers, and enhances themanipulators.Draggers, like the old manipulators in Inventor 1.x, support the click-drag-release interactivestyle. New features that draggers support, but not available in old manipulators, are:• Each dragger contains fields that allow other nodes and the application program to accessits data (more on fields later). This is easier to use than the delta matrices provided byold manipulators.• A dragger provides its own visual feedback (e.g. motion) during manipulation. An oldmanipulator has to rely on its edit path (set by the attach mechanism or explicitly by amember function) for visual feedback.• Draggers can be combined to form compound draggers. Since each dragger manages itsown motion, a motion hierarchy can be formed in a compound dragger. The combination81Relevant Enhancements in the New Release of Inventor 82of old manipulators into compound manipulators is much more restricted, as describedin Section 1.2.3.The working space of a dragger is determined by its position in the scene graph. Foran old manipulator, the working space has to be specified (unless the world space, thedefault, is desired) either through the attach mechanism or explicitly through a memberfunction.The main difference between the new manipulators (in Inventor 2.0) and the old manipulators (in Inventor 1.x) is that new manipulators replace the nodes they affect rather thanattach to them. For example, the new trackball manipulator is a transformation node that canbe rotated interactively and the new spotlight manipulator is a spotlight node whose position,direction and spread can be manipulated interactively. The interactivity of a manipulator isprovided by a dragger the fields of the dragger are used to edit the fields of the manipulator.Each manipulator supplies member functions for replacing a node in a scene graph with themanipulator and vice versa.E.2 Engines and FieldsInventor 2.0 introduces a new class of light-weight nodes called engines. Engines are mainlyused for animation and constraining one part of a scene in relation to other parts of the samescene. An engine obtains input data from its fields (if any), performs its function, and outputsthrough its engine outputs (fields capable of output only).Fields are enhanced in version 2.0 to allow connections from other fields (using the memberfunction connectFrom). A connection allows a field to be updated by another field it connectsfrom. The connection mechanism allows cycles and multiple outputs from a single field, butnot multiple inputs to a field. Connections are capable of converting between data typesautomatically and they can be deactivated without being broken.E.3 3-D Widgets and Inventor 2.0The following list compares the 3-D Widget Programming Library and the above-mentionedfeatures in Inventor 2.0:• The slot/attachment mechanism of widgets and the port/connection mechanism of widgetcomponents in the 3-D Widget Programming Library are very similar to the field/connectionmechanism in Inventor 2.0. The field/connection mechanism in Inventor 2.0 is more general (it works on all fields in all nodes) and more consistent (there is no distinction betweenconnections and attachments as in the 3-D Widget Programming Library).Relevant Enhancements in the New Release of Inventor 83• General components of widgets are similar to Inventor engines, except that engines connect to scene nodes while general components only connect to other components withina widget.• Both allow a motion hierarchy to be built within a widget or dragger. However, sincethe 3-D Widget Programming Library decouples the coordinate frames (spaces) and theinteractive components (parts), visual feedbacks of several parts can be combined intoa single feedback space. Another advantage of the 3-D Widget Programming Library isthat it provides functions for transforming between coordinate frames; it is not clear howInventor 2.0 provide this capability.Other comparisons, such as the ease of building new draggers (widgets) from existing draggers (components) and the ease of building new draggers (parts) with new behaviors, cannotbe made until the book “The Inventor Toolmaker”, also from Addison-Wesley, is available.Due to the changes made to the manipulator classes, the 3-D Widget Programming Libraryis not compatible with Inventor 2.0.

Cite

Citation Scheme:

        

Citations by CSL (citeproc-js)

Usage Statistics

Share

Embed

Customize your widget with the following options, then copy and paste the code below into the HTML of your page to embed this item in your website.
                        
                            <div id="ubcOpenCollectionsWidgetDisplay">
                            <script id="ubcOpenCollectionsWidget"
                            src="{[{embed.src}]}"
                            data-item="{[{embed.item}]}"
                            data-collection="{[{embed.collection}]}"
                            data-metadata="{[{embed.showMetadata}]}"
                            data-width="{[{embed.width}]}"
                            async >
                            </script>
                            </div>
                        
                    
IIIF logo Our image viewer uses the IIIF 2.0 standard. To load this item in other compatible viewers, use this url:
http://iiif.library.ubc.ca/presentation/dsp.831.1-0051294/manifest

Comment

Related Items