home *** CD-ROM | disk | FTP | other *** search
/ Avalon - 3D Objects & Resources / Avalon.iso / frmtspcs / off.ms < prev    next >
Text File  |  1995-01-01  |  40KB  |  1,158 lines

  1. .\"
  2. .\" Makefile for document
  3. .\"
  4. .\"off.ps:    off.ms
  5. .\"    pic off.ms | tbl | psroff -i -t -ms > off.ps
  6. .\"
  7. .nr VS 14
  8. .nr PO 1.25i
  9. .nr LL 6.0i
  10. .nr HM 1.25i
  11. .nr FM 1.25i
  12. .TL
  13. OFF - A 3D Object File Format
  14. .AU
  15. Randi J. Rost
  16. 6-November-1986
  17. Updated 12-October-1989
  18. .AI
  19. Digital Equipment Corporation
  20. Workstation Systems Engineering
  21. 100 Hamilton Ave.
  22. Palo Alto, Ca. 94301
  23. .DA
  24. .AB no
  25. This document describes the data format developed by WSE for the
  26. interchange and archiving of three-dimensional objects.
  27. This format, called OFF (for Object File Format), is general,
  28. flexible, and extensible.  It supports ASCII text versions of
  29. objects for the purpose of interchange, and binary versions for
  30. efficiency of reading and writing.  It is assumed that applications
  31. will develop their own, more efficient format for internal storage
  32. and operation on three-dimensional objects.
  33. .AE
  34. .PP
  35. .NH 1
  36. Introduction
  37. .PP
  38. One of the most time-consuming tasks in computer animation projects
  39. is designing the 3D models that will be used.  Many computer animation
  40. houses have found that owning a large number of databases makes it
  41. easier for them to take on new projects at a lower cost (time and $$$).
  42. The cost of initially creating an object can be amortized over the
  43. number of times it can be re-used.
  44. It is our intention to promote the use of OFF files within (and perhaps
  45. even outside of) Digital in an effort to build up our collection of useful
  46. 3D models.
  47. .PP
  48. The file format itself is not limiting:  OFF files can be used for
  49. a wide variety of object types.  None of the "policy decisions" are
  50. hard-wired in the design of the file format, or in the support library
  51. routines that allow reading and writing of OFF files.
  52. Rather, the policy decisions have been left
  53. up to the designers since the format supports "generic" object definitions.
  54. We have developed specific conventions for objects that are defined
  55. by polygons for use within WSE, and we'd encourage others to adopt
  56. these conventions as
  57. well in order to promote the interchange of useful object data bases.
  58. The \fIdxmodel\fP application (also
  59. developed by WSE) is an example of an application that permits
  60. reading and writing of OFF files.
  61. .PP
  62. This paper describes the Object File Format itself, the conventions
  63. we've adopted at WSE, and the library of support routines that can
  64. be used to read and write OFF files.
  65. .PP
  66. .NH 1
  67. Design Goals and Non-Goals
  68. .LP
  69. Design goals for the Object File Format include:
  70. .IP 1)
  71. \fBSimple.\fP  Simple cases should be simple.  It should be possible
  72. to type in all the data required for a simple object (such as a cube)
  73. by hand.
  74. .IP 2)
  75. \fBPowerful.\fP  The Object File Format should be capable of
  76. accomodating complicated objects (many vertices, polygons, and
  77. a wide range of attributes).
  78. .IP 3)
  79. \fBPortable.\fP  ASCII text file representation required for portability
  80. across operating systems and hardware.  This also allows
  81. operations on OFF files by familiar system utilities
  82. (text editors and the like).
  83. .IP 4)
  84. \fBEfficient.\fP  Binary text file representation required to allow
  85. efficient reading and writing of OFF files.  It is assumed that reading/writing
  86. an object is a costly operation, but reading and writing ASCII data is just
  87. \fItoo\fP slow.
  88. .IP 5)
  89. \fBGeneral.\fP  The format should address the general case of the
  90. three-dimensional object, not a single particular case.
  91. .IP 6)
  92. \fBExtensibile.\fP  Make sure the format can be easily extended to
  93. eventually support other primitives such as bezier patches.
  94. .IP 7)
  95. \fBNo Favors.\fP  Avoid hard-wiring policy decisions.  Rather, provide
  96. generic building blocks capable of supporting several styles and document
  97. a set of strongly encouraged conventions that we have adopted.
  98. .LP
  99. There are also things that were specifically non-goals in the design
  100. of the Object File Format.
  101. .IP 1)
  102. \fBInternal Format.\fP
  103. The Object File Format is not intended to be forced upon applications
  104. as an internal format that must be used.  Rather, it should be considered
  105. a means for inputting and outputting object descriptions in a 
  106. device-, language-, and operating system-independent format.
  107. Applications should feel free to develop and maintain the most useful/efficient
  108. data format they can, and only convert to/from OFF when input or output
  109. of a standardized object is desired.
  110. .IP 2)
  111. \fBObject Conventions.\fP
  112. OFF conventions are documented only for objects in polygonal form at
  113. this point.  It is anticipated
  114. that the Object File Format can be easily extended to handle bezier
  115. surface patches and other primitives in the future.
  116. .NH 1
  117. Objects
  118. .PP
  119. For the purposes of the Object File Format, we'll adopt a very general
  120. definition of an \fIobject\fP.
  121. An \fIobject\fP is simply a list of properties
  122. (name, description, author, copyright information, geometry data, colors,
  123. etc.)
  124. .PP
  125. The most important information about the object can be found in the
  126. \fIheader file\fP for the object.  The header file is always an ASCII text file
  127. that, by convention, is named \fIname\fP.aoff where \fIname\fP is the
  128. object name.  See Appendix B for an example of an OFF object header file.
  129. .PP
  130. A few of these properties (name, description, author, copyright, type)
  131. are common to every type of 3D object and are considered standard
  132. properties.  The standard properties are built into the routines
  133. that manipulate 3D objects.  The rest of the properties may vary
  134. with the type of object, and so are defined by convention only.
  135. .PP
  136. The \fIname\fP of an object is used to concisely describe the object
  137. itself.  For example, we have objects named "x29", "banana" and "vw".
  138. By convention, this name also becomes the prefix for OFF data filenames
  139. when an object is read or written, so it is best to keep it fairly short.
  140. .PP
  141. The \fIdescription\fP is used to more fully describe the object itself.
  142. It may contain the time and date of creation or more prose describing
  143. the object.
  144. .PP
  145. The \fIauthor\fP should be the name of the person (or company, or utility) that
  146. created the object.  We should always try to give credit where credit is due.
  147. This field tells you who to thank for spiffy objects or whose cage to rattle
  148. when a problem with an OFF file is discovered.
  149. .PP
  150. The \fIcopyright\fP field contains information dealing with the distribution
  151. of the object data.  Some object databases will be regarded
  152. by a company as proprietary.  These objects should not be copied or
  153. distributed without consent.  Other objects (vw, x29) were developed
  154. by companies or individuals and can be copied or used as long as the
  155. copyright notice appears and proper credit is given.  Still other objects
  156. (cube, sphere, etc.) have been placed in the public domain.  We have
  157. tried to be as careful as possible in preserving copyright and author
  158. information for the objects we have collected, but sometimes the information
  159. was lost or unavailable.  Be sure and honor copyright notices.  If you don't,
  160. you (or your company) could end up in big trouble.
  161. .PP
  162. The \fItype\fP field contains the type of the object.  For now, only
  163. one type of object is supported: polygon objects.
  164. It is anticipated that polyline and
  165. surface patch-type objects will be supported in
  166. the future as well.
  167. .PP
  168. Also contained in the object header file are lines that describe
  169. the various properties of the object.  Each line in the object header
  170. file that describes an attribute of the object other than a standard
  171. attribute must contain the
  172. property name, the property type, the data format
  173. and either a file name or a string containing default data,
  174. depending on the property type.  Each of these four items is
  175. an ASCII string, separated by white space.
  176. .PP
  177. The \fIproperty name\fP uniquely describes the property.  Property
  178. names for which we have defined conventions (see Appendix A) include
  179. geometry, polygon_colors, vertex_colors, back_faces, vertex_order,
  180. polygon_normals, vertex_normals,
  181. diffuse_coef, specular_coef, and specular_power.
  182. .PP
  183. OFF currently supports four \fIproperty types\fP:
  184. \fIdefault\fP, \fIgeneric\fP, \fIindexed\fP,
  185. and \fIindexed_poly\fP.  If a property is indicated to be of type
  186. \fIdefault\fP, the part of the line after the data format is assumed
  187. to contain some default data that will be applied to the entire object.
  188. For instance, it may make sense to give the entire object a default color
  189. and default diffuse and specular coefficients.
  190. .PP
  191. If the property type is either generic, indexed,
  192. or indexed_poly (described more
  193. fully below), the remainder of the line is taken to be a file name that
  194. can be opened and read to obtain the information for the property.
  195. .PP
  196. The data format indicates what type of data will be found on the remainder
  197. of the line if the property type is default, otherwise what kind of
  198. data will be found in the specified file.  The data format is a
  199. string of characters (no spaces) that indicate the order and type of
  200. the data.  Supported primitive data types are:
  201. .IP "f -"
  202. A number stored internally as a 32-bit floating point number
  203. .IP "d -"
  204. A number stored internally as a 64-bit double-precision floating point number
  205. .IP "i -"
  206. A number stored internally as a 32-bit integer value
  207. .IP "h -"
  208. A number stored internally as a 16-bit integer value
  209. .IP "b -"
  210. A number stored internally as an 8-bit integer value
  211. .IP "s -"
  212. A 32-bit pointer to a null-terminated string of characters
  213. .PP
  214. If, for instance, you were interested in using 32-bit floating values
  215. for r, g, and b default color values, you might have a line in the
  216. object header file that reads
  217. .DS
  218. polygon_colors   default   fff   1.0   0.8   0.0
  219. .DE
  220. .PP
  221. It is important to understand that in all cases, the "string" (s) data
  222. primitive will indicate a pointer to a string
  223. that is stored internally in the data block for an object and not
  224. the string itself.
  225. .NH 1
  226. ASCII Property Files
  227. .PP
  228. OFF supports ASCII text files as a way of providing for language-, hardware-,
  229. and operating system-independent object data files.  The three types of
  230. data files currently supported by OFF are generic, indexed, and indexed_poly.
  231. .NH 2
  232. Generic Files
  233. .PP
  234. Generic files contain only a \fIcount\fP value followed by \fIcount\fP
  235. data items of the
  236. type specified by the data format.
  237. Each data item can be comprised of some combination of the primitive data
  238. types described in Section 3.
  239. Generic data files are useful for storing attributes
  240. which are unique at every vertex or polygon (such as color or normals).
  241. .PP
  242. String data items in ASCII generic files may not contain spaces or
  243. other white space.  8-bit integers must be listed in the range 0-255.
  244. .PP
  245. See Appendix D for an example of an ASCII generic data file.
  246. .PP
  247. .NH 2
  248. Indexed Files
  249. .PP
  250. Indexed files make use of a list of indices in order to reduce the
  251. amount of data required to store a property, and to provide a useful
  252. level of indirection.  For instance, indexed files are commonly used
  253. to maintain per-polygon color information.  If an object has just
  254. five colors, the indexed data file would contain the list of the five
  255. colors followed by an index from one to five for each of the polygons
  256. in the object.  If an application maintains the indirection,
  257. it is possible for the user to easily select five different colors
  258. to be used on the model.
  259. .PP
  260. An indexed file begins with two integers separated by white space: the number
  261. of data items and the number of indices that will be provided.  Following
  262. these two values is the list of data items that is to be used.  Each
  263. data item in this list can be some combination of the primitive types
  264. described in Section 3.  Following the data items is a list of indices
  265. each of which is a pointer to one of the items in the list of data items.
  266. The list of data items is assumed to begin starting at one, not zero.
  267. .PP
  268. .NH 2
  269. Indexed_Poly Files
  270. .PP
  271. Indexed_poly files take advantage of a connectivity list to reduce
  272. the amount of information needed
  273. to store a list of polylines, polygons, or normals.
  274. The unique geometry items (e.g., vertices) are listed in the first
  275. part of the file.  Following this list is a connectivity list.  Each line
  276. in the connectivity list contains a \fIcount\fP value
  277. followed by \fIcount\fP indices (pointers)
  278. to information in the geometry list.  (Items in the geometry list are
  279. indexed starting from one, not zero.)
  280. .PP
  281. The first line of an indexed_poly data file contains three integers,
  282. separated by white space.  The first number on this line indicates
  283. the number of data items (vertices/normals) that follow, the second number
  284. indicates the number of polylines/polygons that follow the
  285. data list, and the
  286. third indicates the total number of edges that are contained
  287. in the polyline/polygon connectivity list.
  288. .PP
  289. String data items in ASCII indexed_poly files may not contain spaces or
  290. other white space.  8-bit integers must be listed in the range 0-255.
  291. .PP
  292. See Appendix C for an example of an ASCII indexed_poly file.
  293. .PP
  294. .NH 1
  295. Binary OFF Files
  296. .PP
  297. The same three types of data files described above are also supported
  298. in binary format.  There are a few minor differences.
  299. .PP
  300. .NH 2
  301. Generic Files
  302. .PP
  303. Binary generic files begin with the first 32-bit word equal to 
  304. OFF_GENERIC_MAGIC as defined in the include file \fIoff.h\fP.
  305. The second word in the file is the \fIcount\fP (number of data items in
  306. the file).  Following the \fIcount\fP is the data itself.
  307. .PP
  308. The data format in the header file describes the primitives that
  309. make up each data item in the list.  Within each data item,
  310. floats, doubles,
  311. 32-bit integers, and string pointers will all begin on a word boundary.
  312. 16-bit integers will all begin on a half-word boundary.
  313. Thus, if
  314. your data format for the data items in a generic data file is "bbb"
  315. (three byte values), each data item will be stored as three bytes followed
  316. by a null byte so that each data item will begin on a word boundary.
  317. Strings begin
  318. with a 32-bit \fIcount\fP followed by \fIcount\fP characters followed by a null
  319. character.  The string is null-padded so it will end on a word boundary.
  320. .PP
  321. (It is assumed that for strings, the length will be read and then
  322. the necessary memory will be allocated and the string read in.  This eliminates
  323. a problem with having variable-length data in the data files.  Anyway,
  324. strings in files are really only there for symmetry with default values,
  325. where strings are really useful.  The performance implications for files
  326. containing strings will probably be enough to prevent people from using
  327. them.)
  328. .PP
  329. .PP
  330. .NH 2
  331. Indexed Files
  332. .PP
  333. Binary indexed files begin with the first 32-bit word equal to 
  334. OFF_INDEXED_MAGIC as defined in the include file \fIoff.h\fP.
  335. The second word in the file is the \fIcount\fP (number of data items in
  336. the file).  The third word in the file is \fInum_indices\fP (number
  337. of indices in the index list).
  338. .PP
  339. Following these two integers is the data for the data item list.
  340. Each item in the data item list will begin on a word boundary.
  341. The data format in the header file describes the primitives that
  342. make up each data item in the list.  Within each data item,
  343. floats, doubles,
  344. 32-bit integers, and string pointers will all begin on a word boundary.
  345. 16-bit integers will all begin on a half-word boundary.
  346. Thus, if
  347. your data format for the data items in an indexed data file is "bbb"
  348. (three byte values), each data item will be stored as three bytes followed
  349. by a null byte so that each data item will begin on a word boundary.
  350. Strings begin
  351. with a 32-bit \fIcount\fP followed by \fIcount\fP characters followed by a null
  352. character.  The string is null-padded so it will end on a word boundary.
  353. .PP
  354. Following the data item list is a list of index values.  This list
  355. will also begin on a word boundary, however, the index values within
  356. this list are
  357. short integers and will be packed two to each 32-bit word.
  358. .PP
  359. .NH 2
  360. Indexed_Poly Files
  361. .PP
  362. Binary indexed_poly files begin with the first 32-bit word equal to 
  363. OFF_INDEXED_POLY_MAGIC as defined in the include file off.h.
  364. The second word is the number of data items
  365. in the vertex list (\fInpts\fP),
  366. the third word is the number of polylines/polygons
  367. in the list (\fInpolys\fP), and
  368. the fourth word is the number of edges contained in
  369. the connectivity list (\fInconnects\fP).
  370. .PP
  371. Starting at the fifth word in the file
  372. is a list of \fInpts\fP data items, followed by \fInpolys\fP short integers
  373. containing polyline/polygon vertex counts, followed by \fInconnects\fP
  374. short integers which are indices into the array of data items.  (This
  375. arrangement is slightly different than that used for indexed_poly files
  376. in ASCII format for efficiency reasons.)
  377. .PP
  378. The same restrictions that
  379. apply to the data types for generic binary files apply to indexed_poly
  380. binary files as well.  In addition, the vertex count array which
  381. follows the geometry data in an indexed_poly file will always begin
  382. on a word boundary.  The connectivity array that follows the vertex
  383. count array will not necessarily start on a word boundary, but will
  384. always begin \fInpolys * sizeof(short)\fP bytes after the start of the
  385. vertex count array.
  386. .PP
  387. .NH 1
  388. Off.a and Objects.h
  389. .PP
  390. An include file and a library of routines has been provided for UNIX/C
  391. programmers to more easily manipulate OFF files.  The basic concepts
  392. of "reading" and "writing" OFF files are supported in this library
  393. of routines.  The library is a software layer on top of
  394. the operating system file I/O interface, with special knowledge of
  395. OFF files.  This subroutine library provides a mechanism for accessing
  396. the syntactical elements of an object file, but makes no attempt to understand
  397. the semantics.  Higher level interfaces can be layered on top.
  398. .PP
  399. The subroutine library refers to an object as a pointer to an
  400. \fIOFFObjDesc\fP.  This structure contains
  401. a pointer to the first property in the
  402. property list.  It is defined as follows:
  403. .DS
  404. typedef struct
  405.     { 
  406.     OFFProperty    *FirstProp;    /* Pointer to first property in list */
  407.     } OFFObjDesc;
  408. .DE
  409. .PP 
  410. The information that describes the object is contained in a linked
  411. list of property structures.  The first such structure in the list
  412. is pointed at by an \fIOFFObjDesc\fP structure.  The property structures
  413. have the form:
  414. .DS
  415. typedef struct _OFFProp
  416.     {
  417.     char    PropName[40];
  418.     int        PropType;
  419.     char    PropFileName[256];
  420.     int        PropCount;
  421.     char    DataFormat[40];
  422.     char    *PropData;
  423.     struct _OFFProp *NextProp;
  424.     } OFFProperty;
  425. .DE
  426. .PP
  427. \fIPropName\fP contains a string defining one of the property types
  428. for which a convention has been defined.
  429. This includes the property names
  430. "name", "author", "description", "copyright", "comment",
  431. "geometry", "polygon_colors", "polygon_normal", etc.  For a complete
  432. list of property names, see Appendix A.
  433. (The special attribute type "comment" is 
  434. supported so that blank lines and comment lines can be preserved if 
  435. an object file is read and then written.)
  436. .PP
  437. The \fIPropType\fP field contains a value equal to \fIOFF_DEFAULT_DATA\fP,
  438. \fIOFF_GENERIC_DATA\fP, \fIOFF_INDEXED_DATA\fP,
  439. or \fIOFF_INDEXED_POLY_DATA\fP which defines
  440. the basic type for the property.
  441. .PP
  442. The \fIPropFileName\fP is required if \fIPropType\fP is something
  443. other than \fIOFF_DEFAULT_DATA\fP.  It contains a string representing the name
  444. of the file to be read/written for this attribute.  This file name
  445. should \fInot\fP contain a path leading up to the file itself, only
  446. the actual file name.  The object search path mechanism (see Section
  447. 7) should be used instead.
  448. .PP
  449. The \fIPropCount\fP indicates the actual number of data items associated
  450. with this particular attribute.  After reading in an object, properties
  451. of type \fIOFF_DEFAULT_DATA\fP will have a \fIPropCount\fP of one,
  452. properties
  453. of type \fIOFF_GENERIC_DATA\fP will have a \fIPropCount\fP equal to the number
  454. of generic data items in the list,
  455. properties
  456. of type \fIOFF_INDEXED_DATA\fP will have a \fIPropCount\fP equal to the number
  457. of items in the data item list,
  458. and properties of type
  459. \fIOFF_INDEXED_POLY_DATA\fP
  460. will have a \fIPropCount\fP equal to the number of data items in the
  461. geometry list.
  462. .PP
  463. The \fIDataFormat\fP field contains a string of characters corresponding
  464. to primitive data items.  The composite type of the data for this property
  465. can then be deduced by looking at this field and applying the rules for
  466. padding to word and half-word boundaries.
  467. .PP
  468. The \fIPropData\fP field contains a pointer to a block of memory containing
  469. the actual data for this property.  This data will have the same
  470. data alignment restrictions as a binary file has, with the exception
  471. of strings.  As strings are read in, memory is malloc'ed to hold them
  472. and a pointer to the string is stored in the appropriate field in the
  473. data list.  This means that all primitive data types will have a fixed
  474. size and lengths and alignments can be computed more easily.
  475. .PP
  476. The \fINextProp\fP field contains a pointer to the next property structure
  477. in the property list.
  478. .PP
  479. The routines contained in the subroutine library are defined below.
  480. .sp 1
  481. .LP
  482. .nf
  483. \fB#include "off.h"\fP
  484. .sp 1
  485. int \fBOFFReadObj\fP(Obj, FileName)
  486.     OFFObjDesc *Obj;
  487.     char *FileName;
  488. .sp 1
  489. int \fBOFFWriteObj\fP(Obj, FileName, Directory, FileType);
  490.     OFFObjDesc *Obj;
  491.     char *FileName;
  492.     char *Directory;
  493.     int FileType;
  494. .sp 1
  495. void \fBOFFPackObj\fP(Obj)
  496.     OFFObjDesc *Obj;
  497. .sp 1
  498. void \fBOFFPackProperty\fP(Property)
  499.     OFFProperty *Property;
  500. .sp 1
  501. int \fBOFFReadGeneric\fP(Property, FileName)
  502.     OFFProperty *Property;
  503.     char *FileName;
  504. .sp 1
  505. int \fBOFFWriteGeneric\fP(Property, FileName, FileType)
  506.     OFFProperty *Property;
  507.     char *FileName;
  508.     int FileType;
  509. .sp 1
  510. int \fBOFFReadIndexed\fP(Property, FileName)
  511.     OFFProperty *Property;
  512.     char *FileName;
  513. .sp 1
  514. int \fBOFFWriteIndexed\fP(Property, FileName, FileType)
  515.     OFFProperty *Property;
  516.     char *FileName;
  517.     int FileType;
  518. .sp 1
  519. int \fBOFFReadIndexedPoly\fP(Property, FileName)
  520.     OFFProperty *Property;
  521.     char *FileName;
  522. .sp 1
  523. int \fBOFFWriteIndexedPoly\fP(Property, FileName, FileType)
  524.     OFFProperty *Property;
  525.     char *FileName;
  526.     int FileType;
  527. .sp 1
  528. OFFObjDesc *\fBOFFCreateObj\fP()
  529. .sp 1
  530. int \fBOFFDestroyObj\fP(Obj)
  531.     OFFObjDesc *Obj;
  532. .sp 1
  533. OFFProperty *\fBOFFAddProperty\fP(Obj)
  534.     OFFObjDesc *Obj;
  535. .sp 1
  536. int \fBOFFRemoveProperty\fP(Obj, PropertyName)
  537.     OFFObjDesc *Obj;
  538.     char *PropertyName;
  539. .sp 1
  540. int \fBOFFFreeProperty\fP(Property)
  541.     OFFProperty *Property;
  542. .sp 1
  543. .PP
  544. \fIOFFReadObj\fP will attempt to open the object header file named
  545. \fIFileName\fP
  546. and read the object data it contains.  A pointer to the constructed
  547. object structure will be returned in \fIObj\fP when the object has been read.
  548. An attempt will be made to open the specified file first as given,
  549. then concatenated in turn with each of
  550. the directories specified by the environment
  551. search path variable \fIOBJ_PATH\fP.
  552. The property list for the object is built as the file is
  553. read.  Upon return, the client need only traverse the
  554. property list and select the data it needs.  This routine
  555. calls \fIOFFReadGeneric\fP and \fIOFFReadIndexedPoly\fP 
  556. in order to read associated data files.
  557. \fIOFFReadObj\fP will return 0 if the read operation was successful,
  558. -1 otherwise.
  559. .PP
  560. \fIOFFWriteObj\fP will attempt to write the object pointed at by
  561. \fIObj\fP using the filename specified by \fIFileName\fP.  The
  562. file will be written in the directory indicated by \fIDirectory\fP.
  563. If \fIFileType\fP is \fIOFF_ASCII\fP, the file will be written as an ASCII
  564. text OFF file.  If \fIFileType\fP is \fIOFF_BINARY\fP, the file will
  565. be written as a binary OFF file.
  566. The property list for the object is traversed and each
  567. property of the object is written out in turn.
  568. This routine
  569. calls \fIOFFWriteGeneric\fP and \fIOFFWriteIndexedPoly\fP 
  570. in order to write associated data files.
  571. \fIOFFWriteObj\fP will return 0 if the write operation was successful,
  572. -1 otherwise.
  573. .PP
  574. \fIOFFPackObj\fP attempts to \fIpack\fP the object pointed at by \fIObj\fP.
  575. Packing only applies to geometry and normals stored in indexed_polygon
  576. format.
  577. See \fIOFFPackProperty\fP below.
  578. .PP
  579. \fIOFFPackProperty\fP packs the property pointed at by \fIProperty\fP.
  580. Packing can only be applied to indexed_polygon properties with format ``fff''.
  581. (This is normally the case for geometry and normals properties.)
  582. Packing works by sharing common data (vertex or normal) values.
  583. Since each vertex and vertex normal of an object is usually shared by
  584. three or more polygons, this can save a great deal of space and rendering
  585. time.
  586. If \fIProperty\fP could not be packed, it is not modified;
  587. otherwise the property data is changed in-place.
  588. .PP
  589. \fIOFFReadGeneric\fP will read the generic data file named \fIFileName\fP
  590. (here \fIFileName\fP contains the full path name) into the
  591. property structure pointed at by \fIProperty\fP.
  592. This routine will allocate the space it needs in order
  593. to read in the data.  A pointer to this allocated data space
  594. will be stored in the \fIPropData\fP field of the specified
  595. \fIproperty\fP as described earlier.
  596. The entire object, including all
  597. allocated memory resources
  598. can later be deallocated by calling \fIOFFDestroyObj\fP.
  599. This routine will not typically be called directly by applications.
  600. \fIOFFReadGeneric\fP will return 0 if the read operation was successful,
  601. -1 otherwise.
  602. .PP
  603. \fIOFFWriteGeneric\fP will write the generic data associated with
  604. \fIProperty\fP into the file \fIFileName\fP (here \fIFileName\fP
  605. contains the full path name of the file to be written).  
  606. If \fIFileType\fP is \fIOFF_ASCII\fP, the file will be written as an ASCII
  607. text generic data file.  If \fIFileType\fP is \fIOFF_BINARY\fP, the file will
  608. be written as a binary generic data file.
  609. This routine will not typically be called directly by applications.
  610. \fIOFFWriteGeneric\fP will return 0 if the write operation was successful,
  611. -1 otherwise.
  612. .PP
  613. \fIOFFReadIndexed\fP will read the indexed data file named \fIFileName\fP
  614. (here \fIFileName\fP contains the full path name) into the
  615. property structure pointed at by \fIProperty\fP.
  616. This routine will allocate the space it needs in order
  617. to read in the data.  A pointer to this allocated data space
  618. will be stored in the \fIPropData\fP field of the specified
  619. \fIproperty\fP as described earlier.
  620. The entire object, including all
  621. allocated memory resources
  622. can later be deallocated by calling \fIOFFDestroyObj\fP.
  623. This routine will not typically be called directly by applications.
  624. \fIOFFReadIndexed\fP will return 0 if the read operation was successful,
  625. -1 otherwise.
  626. .PP
  627. \fIOFFWriteIndexed\fP will write the indexed data associated with
  628. \fIProperty\fP into the file \fIFileName\fP (here \fIFileName\fP
  629. contains the full path name of the file to be written).  
  630. If \fIFileType\fP is \fIOFF_ASCII\fP, the file will be written as an ASCII
  631. text indexed data file.  If \fIFileType\fP is \fIOFF_BINARY\fP, the file will
  632. be written as a binary indexed data file.
  633. This routine will not typically be called directly by applications.
  634. \fIOFFWriteIndexed\fP will return 0 if the write operation was successful,
  635. -1 otherwise.
  636. .PP
  637. \fIOFFReadIndexedPoly\fP will read the indexed_poly data file named
  638. \fIFileName\fP (here \fIFileName\fP contains the full path name) into the
  639. property structure pointed at by \fIProperty\fP.
  640. This routine will allocate the space it needs in order
  641. to read in the data.  A pointer to this allocated data space
  642. will be stored in the \fIPropData\fP field of the specified
  643. \fIproperty\fP as described earlier.
  644. The entire object, including all
  645. allocated memory resources
  646. can later be deallocated by calling \fIOFFDestroyObj\fP.
  647. This routine will not typically be called directly by applications.
  648. \fIOFFReadIndexedPoly\fP will return 0 if the read operation was successful,
  649. -1 otherwise.
  650. .PP
  651. \fIOFFWriteIndexedPoly\fP will write the indexed_poly data associated with
  652. \fIProperty\fP into the file \fIFileName\fP (here \fIFileName\fP contains
  653. the full path name of the file to be written).  
  654. If \fIFileType\fP is \fIOFF_ASCII\fP, the file will be written as an ASCII
  655. text indexed_poly data file.
  656. If \fIFileType\fP is \fIOFF_BINARY\fP, the file will
  657. be written as a binary indexed_poly data file.
  658. This routine will not typically be called directly by applications.
  659. \fIOFFWriteIndexedPoly\fP will return 0 if the write operation was successful,
  660. -1 otherwise.
  661. .PP
  662. \fIOFFCreateObj\fP allocates and initializes an \fIOFFObjDesc\fP structure
  663. A pointer to the newly-created structure is returned.  The null pointer is
  664. returned if the operation was unsuccessful.
  665. .PP
  666. \fIOFFDestroyObj\fP deallocates all memory resources associated with
  667. the object pointed at by \fIObj\fP.  It works by calling
  668. \fIOFFFreeProperty\fP for each property in the property list for
  669. the specified object.
  670. .PP
  671. \fIOFFAddProperty\fP adds a property structure to the property list
  672. associated with the object pointed at by \fIObj\fP, initializes it,
  673. and returns a pointer to it.
  674. The null pointer is returned if the operation was unsuccessful.
  675. .PP
  676. \fIOFFRemoveProperty\fP deletes the named property 
  677. from the object pointed at by \fIObj\fP.  This routine returns
  678. -1 if the named property is not found in the property list for the
  679. specified object.
  680. .PP
  681. \fIOFFFreeProperty\fP frees all the memory resources allocated to the
  682. property structure specified by \fIProperty\fP as well as the property
  683. structure itself.  This routine will not typically be called directly
  684. by applications.
  685. .NH 1
  686. Object Search Path
  687. .PP
  688. It is important to avoid embedding path names in object files.
  689. When an object is transported to another system, chances are slim that the same
  690. directory structure will exist.  The \fIOFFReadObj\fP routine in
  691. libobj.a knows about an environment variable named \fIOBJ_PATH\fP
  692. that is used to overcome this problem.
  693. .PP
  694. When an object is read, an attempt is first made to open it in the
  695. current working directory.  If that attempt fails, the directories
  696. specified in the \fIOBJ_PATH\fP environment variable are tried in turn until
  697. the file is successfully opened or the directory list is exhausted.
  698. \fIOBJ_PATH\fP contains a list of directories, separated by spaces,
  699. that are to be searched for the named objects.
  700. .PP
  701. The name of the directory where a successful open operation occurred
  702. is used for opening associated data files as well.  This means that
  703. all of the data files for a particular object must reside in the same
  704. directory.
  705. .PP
  706. It is hoped that in this way, users will be able to draw on one or
  707. more collections of "standard" objects in addition to their own private
  708. collections of objects.
  709. .bp
  710. .sp 6
  711. .NH 1
  712. Appendix A: Conventions for Polygonal Objects
  713. .PP
  714. This list contains the conventions we have adopted for describing
  715. 3D polygonal objects which are defined in some three-dimensional
  716. model coordinate system.  Items in regular type are string
  717. literal, printed as they would appear in an OFF file, and
  718. item in italics indicate data values that will vary from
  719. object to object.  By convention, the header for an ASCII OFF file
  720. is suffixed with ".aoff" and the header for a binary OFF file
  721. is suffixed with ".off".
  722. There are two choices for how colors may be stored.  If they are
  723. stored as generic data, the suffixes used are ".{b}pcol" for polygon
  724. colors and ".{b}vcol" for vertex colors.  If they are stored as
  725. indexed data, the suffixes used are ".{b}ipcol" and ".{b}ivcol".
  726. .PP
  727. .TS
  728. box;
  729. c|c|c|c|c|c
  730. l|l|l|l|l|l.
  731. Property    Type    Format    Defaults    ASCII filename    Binary Filename
  732. =
  733. name    *****    *****    \fIobjname\fP    *****    *****
  734. author    *****    *****    \fIauthor\fP    *****    *****
  735. description    *****    *****    \fIdescription\fP    *****    *****
  736. copyright    *****    *****    \fIcopyright\fP    *****    *****
  737. type    *****    *****    polyline    *****    *****
  738.     *****    *****    polygon    *****    *****
  739. geometry    indexed_poly    fff    *****    \fIname\fP.geom    \fIname\fP.bgeom
  740. polygon_colors    generic    fff    *****    \fIname\fP.pcol    \fIname\fP.bpcol
  741. vertex_colors    generic    fff    *****    \fIname\fP.vcol    \fIname\fP.bvcol
  742. polygon_colors    indexed    fff    *****    \fIname\fP.ipcol    \fIname\fP.bipcol
  743. vertex_colors    indexed    fff    *****    \fIname\fP.ivcol    \fIname\fP.bivcol
  744. back_faces    default    s    cull    *****    *****    
  745.             display    *****    *****    
  746.             reverse    *****    *****    
  747. vertex_order    default    s    clockwise    *****    *****    
  748.             counter-clockwise    *****    *****    
  749.             counterclockwise    *****    *****    
  750. polygon_normals    generic    fff    *****    \fIname\fP.pnorm    \fIname\fP.bpnorm
  751. vertex_normals    generic    fff    *****    \fIname\fP.vnorm    \fIname\fP.bvnorm
  752. diffuse_coef    default    f    \fIvalue\fP    *****    *****
  753. specular_coef    default    f    \fIvalue\fP    *****    *****
  754. specular_power    default    f    \fIvalue\fP    *****    *****
  755. bounding_box    default    ffffff    \fIvalue\fP    *****    *****
  756. .TE
  757. .bp
  758. .NH 1
  759. Appendix B: OFF Header File For a Cube (cube.aoff)
  760. .PP
  761. .sp 6
  762. .TS
  763. ;
  764. l l.
  765. name    cube
  766. author    Randi J. Rost
  767. description    cube with sides of red, green, blue, cyan, yellow, magenta
  768. copyright    public domain
  769. type    polygon
  770. .TE
  771. .TS
  772. ;
  773. l c c c
  774. l c c c
  775. l l l l.
  776. # Prop.    data type    format    filename or default data
  777. #_______    _________    ______    ________________________
  778. .sp 1
  779. geometry    indexed_poly    fff    cube.geom
  780. vertex_order    default    s    clockwise
  781. polygon_colors    generic    fff    cube.pcol
  782. back_faces    default    s    cull
  783. .TE
  784. .bp
  785. .sp 6
  786. .NH 1
  787. Appendix C: Listing of cube.geom
  788. .PP
  789. .sp 6
  790. .TS
  791. ;
  792. nw(0.5i) nw(0.5i) nw(0.5i) nw(0.5i) nw(0.5i).
  793. 8    6    24
  794. -1.0    -1.0    1.0
  795. -1.0    1.0    1.0
  796. 1.0    1.0    1.0
  797. 1.0    -1.0    1.0
  798. -1.0    -1.0    -1.0
  799. -1.0    1.0    -1.0
  800. 1.0    1.0    -1.0
  801. 1.0    -1.0    -1.0
  802. 4    1    2    3    4
  803. 4    5    6    2    1
  804. 4    3    2    6    7
  805. 4    3    7    8    4
  806. 4    1    4    8    5
  807. 4    8    7    6    5
  808. .TE
  809. .bp
  810. .sp 6
  811. .NH 1
  812. Appendix D: Listing of cube.pcol
  813. .PP
  814. .sp 6
  815. .TS
  816. ;
  817. l s s
  818. nw(0.5i) nw(0.5i) nw(0.5i).
  819. 6        
  820. 1.0        0.0        0.0
  821. 0.0        1.0        0.0
  822. 0.0        0.0        1.0
  823. 0.0        1.0        1.0
  824. 1.0        1.0        0.0
  825. 1.0        0.0        1.0
  826. .TE
  827. .bp
  828. .sp 6
  829. .NH 1
  830. Appendix E: Listing of off.h
  831. .LP
  832. .sp 2
  833. .nf
  834. \f8#define OFF_INDEXED_POLY_MAGIC  0xFEEDFEEDL
  835. #define OFF_GENERIC_MAGIC       0xBEEFBEEFL
  836. #define OFF_INDEXED_MAGIC       0xBADBADBAL
  837.  
  838. #define OFF_BIGSTR              256
  839. #define OFF_SMSTR               40
  840.  
  841. #define    OFF_ASCII               0
  842. #define    OFF_BINARY              1
  843.  
  844.  
  845. /* Types of data for object properties  */
  846.  
  847. #define OFF_UNKNOWN_TYPE_DATA   0
  848. #define OFF_STANDARD_DATA       1
  849. #define OFF_COMMENT_DATA        2
  850. #define OFF_DEFAULT_DATA        3
  851. #define OFF_GENERIC_DATA        4
  852. #define OFF_INDEXED_POLY_DATA   5
  853. #define OFF_INDEXED_DATA        6
  854.  
  855.  
  856. typedef struct _OFFProp
  857.     {
  858.     char        PropName[OFF_SMSTR];     /* Name of property (or attribute)  */
  859.     int         PropType;                /* Type of data for property        */
  860.     char        PropFileName[OFF_BIGSTR];/* Name of file that has prop data  */
  861.     char        DataFormat[OFF_SMSTR];   /* Pointer to property data format  */
  862.     int         PropCount;               /* Number of data items for property*/
  863.     char        *PropData;               /* Pointer to property data         */
  864.     struct _OFFProp *NextProp;           /* Pointer to next property in list */
  865.     } OFFProperty;
  866.  
  867. typedef struct
  868.     { 
  869.     OFFProperty *FirstProp;              /* Pointer to first property in list*/
  870.     } OFFObjDesc;\fP
  871.  
  872. .fi
  873. .bp
  874. .sp 6
  875. .NH 1
  876. Appendix F: Data Structure Format
  877. .PP
  878. The following diagram depicts some of the data structures for the
  879. object \fIcube.aoff\fP after being read by \fIOFFReadObj()\fP (or just prior
  880. to being written by \fIOFFWriteObj()\fP).
  881. .PS
  882. .ps 8
  883. move to 0.0, 14.5
  884. Object:
  885.     [
  886.     boxht = 0.3i; boxwid = 2.0i
  887.     moveht = 0.3i; movewid = 0.0i
  888.        down
  889.     A: box "FirstProp"
  890.        move up 0.3i; move right 2.0i
  891.        down
  892.     B: box
  893.     ]
  894.  
  895. move to 1.0, 13.0
  896. Prop1:
  897.     [
  898.     boxht = 0.3i; boxwid = 1.5i
  899.     moveht = 0.3i; movewid = 0.0i
  900.        down
  901.     A: box "PropName"
  902.        down
  903.     B: box "PropType"
  904.        down
  905.     C: box "PropFileName"
  906.        down
  907.     D: box "DataFormat"
  908.        down
  909.     E: box "PropCount"
  910.        down
  911.     F: box "PropData"
  912.        down
  913.     H: box "NextProp"
  914.        move up 2.1i;
  915.        move right 2.25i;
  916.        boxht = 0.3i; boxwid = 3.0i
  917.        moveht = 0.3i; movewid = 0.0i
  918.        down
  919.     I: box "name"
  920.        down
  921.     J: box "OFF_STANDARD_DATA"
  922.        down
  923.     K: box "\fInull string\fP"
  924.        down
  925.     L: box "\fInull string\fP"
  926.        down
  927.     M: box "0"
  928.        down
  929.     N: box
  930.        down
  931.     O: box
  932.     ]
  933.  
  934. move to 1.0, 10.0
  935. Prop2:
  936.     [
  937.     boxht = 0.3i; boxwid = 1.5i
  938.     moveht = 0.3i; movewid = 0.0i
  939.        down
  940.     A: box "PropName"
  941.        down
  942.     B: box "PropType"
  943.        down
  944.     C: box "PropFileName"
  945.        down
  946.     D: box "DataFormat"
  947.        down
  948.     E: box "PropCount"
  949.        down
  950.     F: box "PropData"
  951.        down
  952.     H: box "NextProp"
  953.        move up 2.1i;
  954.        move right 2.25i;
  955.        boxht = 0.3i; boxwid = 3.0i
  956.        moveht = 0.3i; movewid = 0.0i
  957.        down
  958.     I: box "author"
  959.        down
  960.     J: box "OFF_STANDARD_DATA"
  961.        down
  962.     K: box "\fInull string\fP"
  963.        down
  964.     L: box "\fInull string\fP"
  965.        down
  966.     M: box "0"
  967.        down
  968.     N: box
  969.        down
  970.     O: box
  971.     ]
  972.  
  973. move to 1.0, 7.0
  974. Prop3:
  975.     [
  976.     boxht = 0.3i; boxwid = 1.5i
  977.     moveht = 0.3i; movewid = 0.0i
  978.        down
  979.     A: box "PropName"
  980.        down
  981.     B: box "PropType"
  982.        down
  983.     C: box "PropFileName"
  984.        down
  985.     D: box "DataFormat"
  986.        down
  987.     E: box "PropCount"
  988.        down
  989.     F: box "PropData"
  990.        down
  991.     H: box "NextProp"
  992.        move up 2.1i;
  993.        move right 2.25i;
  994.        boxht = 0.3i; boxwid = 3.0i
  995.        moveht = 0.3i; movewid = 0.0i
  996.        down
  997.     I: box "geometry"
  998.        down
  999.     J: box "OFF_INDEXED_POLY_DATA"
  1000.        down
  1001.     K: box "cube.geom"
  1002.        down
  1003.     L: box "fff"
  1004.        down
  1005.     M: box "6"
  1006.        down
  1007.     N: box
  1008.        down
  1009.     O: box
  1010.     ]
  1011.  
  1012. move to 1.0, 4.0
  1013. Prop4:
  1014.     [
  1015.     boxht = 0.3i; boxwid = 1.5i
  1016.     moveht = 0.3i; movewid = 0.0i
  1017.        down
  1018.     A: box "PropName"
  1019.        down
  1020.     B: box "PropType"
  1021.        down
  1022.     C: box "PropFileName"
  1023.        down
  1024.     D: box "DataFormat"
  1025.        down
  1026.     E: box "PropCount"
  1027.        down
  1028.     F: box "PropData"
  1029.        down
  1030.     H: box "NextProp"
  1031.        move up 2.1i;
  1032.        move right 2.25i;
  1033.        boxht = 0.3i; boxwid = 3.0i
  1034.        moveht = 0.3i; movewid = 0.0i
  1035.        down
  1036.     I: box "polygon_colors"
  1037.        down
  1038.     J: box "OFF_GENERIC_DATA"
  1039.        down
  1040.     K: box "cube.pcol"
  1041.        down
  1042.     L: box "fff"
  1043.        down
  1044.     M: box "6"
  1045.        down
  1046.     N: box
  1047.        down
  1048.     O: box
  1049.     ]
  1050.  
  1051. move to 1.0, 1.0
  1052. Prop5:
  1053.     [
  1054.     boxht = 0.3i; boxwid = 1.5i
  1055.     moveht = 0.3i; movewid = 0.0i
  1056.        down
  1057.     A: box "PropName"
  1058.        down
  1059.     B: box "PropType"
  1060.        down
  1061.     C: box "PropFileName"
  1062.        down
  1063.     D: box "DataFormat"
  1064.        down
  1065.     E: box "PropCount"
  1066.        down
  1067.     F: box "PropData"
  1068.        down
  1069.     H: box "NextProp"
  1070.        move up 2.1i;
  1071.        move right 2.25i;
  1072.        boxht = 0.3i; boxwid = 3.0i
  1073.        moveht = 0.3i; movewid = 0.0i
  1074.        down
  1075.     I: box "back_faces"
  1076.        down
  1077.     J: box "OFF_DEFAULT_DATA"
  1078.        down
  1079.     K: box "\fInull string\fP"
  1080.        down
  1081.     L: box "s"
  1082.        down
  1083.     M: box "0"
  1084.        down
  1085.     N: box
  1086.        down
  1087.     O: box "\fInull pointer\fP"
  1088.     ]
  1089.  
  1090. line from Prop1.O.c to Prop1.O.c.x, (Prop1.s.y + Prop2.n.y) / 2.0
  1091. line to 0.5, (Prop1.s.y + Prop2.n.y) / 2.0
  1092. line to 0.5, Prop2.A.c.y
  1093. arrow to Prop2.A.w
  1094.  
  1095. line from Prop2.O.c to Prop2.O.c.x, (Prop2.s.y + Prop3.n.y) / 2.0
  1096. line to 0.5, (Prop2.s.y + Prop3.n.y) / 2.0
  1097. line dashed to 0.5, Prop3.A.c.y
  1098. arrow to Prop3.A.w
  1099.  
  1100. line from Prop3.O.c to Prop3.O.c.x, (Prop3.s.y + Prop4.n.y) / 2.0
  1101. line to 0.5, (Prop3.s.y + Prop4.n.y) / 2.0
  1102. line dashed to 0.5, Prop4.A.c.y
  1103. arrow to Prop4.A.w
  1104.  
  1105. line from Prop4.O.c to Prop4.O.c.x, (Prop4.s.y + Prop5.n.y) / 2.0
  1106. line to 0.5, (Prop4.s.y + Prop5.n.y) / 2.0
  1107. line to 0.5, Prop5.A.c.y
  1108. arrow to Prop5.A.w
  1109.  
  1110. line from Object.B.c to Object.B.c.x, (Object.s.y + Prop1.n.y) / 2.0
  1111. line to 0.5, (Object.s.y + Prop1.n.y) / 2.0
  1112. line to 0.5, Prop1.A.c.y
  1113. arrow to Prop1.A.w
  1114.  
  1115. boxht = 0.3i; boxwid = 2.0i
  1116. P1: box "cube" at Prop1.N.e.x + 1.5i, Prop1.N.e.y
  1117. arrow from Prop1.N.c to P1.w
  1118.  
  1119. P2: box "Randi J. Rost" at Prop2.N.e.x + 1.5i, Prop2.N.e.y
  1120. arrow from Prop2.N.c to P2.w
  1121.  
  1122. P3: box "  8    6    24 -1.0 -1.0" at Prop3.N.e.x + 1.5i, Prop3.N.e.y
  1123. P4: box " 1.0 -1.0  1.0  1.0  1.0" with .nw at P3.sw
  1124. P5: box " 1.0  1.0  1.0 -1.0  1.0" with .nw at P4.sw
  1125. P6: box "-1.0 -1.0 -1.0 -1.0  1.0" with .nw at P5.sw
  1126. P7: box "-1.0  1.0  1.0 -1.0  1.0" with .nw at P6.sw
  1127. P8: box "-1.0 -1.0 4  4  4 4  4  4" with .nw at P7.sw
  1128. P9: box "1  2  3  4  5  6  2  1" with .nw at P8.sw
  1129. Pa: box "3  2  6  7  3  7  8  4" with .nw at P9.sw
  1130. Pb: box "1  4  8  5  8  7  6  5" with .nw at Pa.sw
  1131. arrow from Prop3.N.c to P3.w
  1132.  
  1133. Pc: box "6    1.0  0.0  0.0  0.0" at Prop4.N.e.x + 1.5i, Prop4.N.e.y
  1134. Pd: box "1.0  0.0  0.0  0.0  1.0" with .nw at Pc.sw
  1135. Pe: box "0.0  1.0  1.0  1.0  1.0" with .nw at Pd.sw
  1136. Pf: box "0.0  1.0  0.0  1.0     " with .nw at Pe.sw
  1137. arrow from Prop4.N.c to Pc.w
  1138.  
  1139. boxht = 0.3i; boxwid = 0.5i
  1140. Pg: box at Prop5.N.e.x + 1.0i, Prop5.N.e.y
  1141. Ph: box "cull" at Pg.e.x + 1.0i, Prop5.N.e.y
  1142. arrow from Pg.c to Ph.w
  1143. arrow from Prop5.N.c to Pg.w
  1144.  
  1145. "Object" at Object.A.n above
  1146. .ps 12
  1147. .PE
  1148. .bp
  1149. .NH 1
  1150. Acknowledgements
  1151. .PP
  1152. OFF is a derivative of an object file format used at Ohio State University.
  1153. Special thanks to Allen Akin of WSE for helpful ideas and suggestions.  
  1154. Thanks also to Jeff Friedberg of Digital's High-Performance Workstation (HPWS)
  1155. group and Shaun Ho of WSE who also contributed to the design.  Danny
  1156. Shapiro of WSE provided suggestions for additional enhancements and
  1157. conventions.
  1158.