David Rose 25 éve
szülő
commit
126f95498d
62 módosított fájl, 7777 hozzáadás és 0 törlés
  1. 59 0
      pandaapp/Package.pp
  2. 4 0
      pandaapp/Sources.pp
  3. 4 0
      pandaapp/src/Sources.pp
  4. 46 0
      pandaapp/src/stitch/Sources.pp
  5. 45 0
      pandaapp/src/stitch/stitchCommandProgram.cxx
  6. 27 0
      pandaapp/src/stitch/stitchCommandProgram.h
  7. 48 0
      pandaapp/src/stitch/stitchImageProgram.cxx
  8. 30 0
      pandaapp/src/stitch/stitchImageProgram.h
  9. 43 0
      pandaapp/src/stitch/stitchViewerProgram.cxx
  10. 26 0
      pandaapp/src/stitch/stitchViewerProgram.h
  11. 38 0
      pandaapp/src/stitchbase/Sources.pp
  12. 16 0
      pandaapp/src/stitchbase/config_stitch.cxx
  13. 13 0
      pandaapp/src/stitchbase/config_stitch.h
  14. 32 0
      pandaapp/src/stitchbase/fixedPoint.h
  15. 750 0
      pandaapp/src/stitchbase/layeredImage.cxx
  16. 120 0
      pandaapp/src/stitchbase/layeredImage.h
  17. 472 0
      pandaapp/src/stitchbase/morphGrid.cxx
  18. 101 0
      pandaapp/src/stitchbase/morphGrid.h
  19. 630 0
      pandaapp/src/stitchbase/stitchCommand.cxx
  20. 141 0
      pandaapp/src/stitchbase/stitchCommand.h
  21. 39 0
      pandaapp/src/stitchbase/stitchCommandReader.cxx
  22. 34 0
      pandaapp/src/stitchbase/stitchCommandReader.h
  23. 182 0
      pandaapp/src/stitchbase/stitchCylindricalLens.cxx
  24. 37 0
      pandaapp/src/stitchbase/stitchCylindricalLens.h
  25. 47 0
      pandaapp/src/stitchbase/stitchFile.cxx
  26. 34 0
      pandaapp/src/stitchbase/stitchFile.h
  27. 314 0
      pandaapp/src/stitchbase/stitchFisheyeLens.cxx
  28. 39 0
      pandaapp/src/stitchbase/stitchFisheyeLens.h
  29. 350 0
      pandaapp/src/stitchbase/stitchImage.cxx
  30. 154 0
      pandaapp/src/stitchbase/stitchImage.h
  31. 91 0
      pandaapp/src/stitchbase/stitchImageCommandOutput.cxx
  32. 39 0
      pandaapp/src/stitchbase/stitchImageCommandOutput.h
  33. 15 0
      pandaapp/src/stitchbase/stitchImageOutputter.cxx
  34. 26 0
      pandaapp/src/stitchbase/stitchImageOutputter.h
  35. 217 0
      pandaapp/src/stitchbase/stitchImageRasterizer.cxx
  36. 49 0
      pandaapp/src/stitchbase/stitchImageRasterizer.h
  37. 69 0
      pandaapp/src/stitchbase/stitchLens.cxx
  38. 74 0
      pandaapp/src/stitchbase/stitchLens.h
  39. 364 0
      pandaapp/src/stitchbase/stitchLexer.lxx
  40. 23 0
      pandaapp/src/stitchbase/stitchLexerDefs.h
  41. 300 0
      pandaapp/src/stitchbase/stitchPSphereLens.cxx
  42. 43 0
      pandaapp/src/stitchbase/stitchPSphereLens.h
  43. 34 0
      pandaapp/src/stitchbase/stitchParser.h
  44. 417 0
      pandaapp/src/stitchbase/stitchParser.yxx
  45. 36 0
      pandaapp/src/stitchbase/stitchParserDefs.h
  46. 79 0
      pandaapp/src/stitchbase/stitchPerspectiveLens.cxx
  47. 31 0
      pandaapp/src/stitchbase/stitchPerspectiveLens.h
  48. 20 0
      pandaapp/src/stitchbase/stitchPoint.cxx
  49. 30 0
      pandaapp/src/stitchbase/stitchPoint.h
  50. 464 0
      pandaapp/src/stitchbase/stitcher.cxx
  51. 62 0
      pandaapp/src/stitchbase/stitcher.h
  52. 54 0
      pandaapp/src/stitchbase/triangle.cxx
  53. 24 0
      pandaapp/src/stitchbase/triangle.h
  54. 571 0
      pandaapp/src/stitchbase/triangleRasterizer.cxx
  55. 68 0
      pandaapp/src/stitchbase/triangleRasterizer.h
  56. 19 0
      pandaapp/src/stitchviewer/Sources.pp
  57. 120 0
      pandaapp/src/stitchviewer/stitchImageConverter.cxx
  58. 28 0
      pandaapp/src/stitchviewer/stitchImageConverter.h
  59. 330 0
      pandaapp/src/stitchviewer/stitchImageVisualizer.cxx
  60. 83 0
      pandaapp/src/stitchviewer/stitchImageVisualizer.h
  61. 84 0
      pandaapp/src/stitchviewer/triangleMesh.cxx
  62. 38 0
      pandaapp/src/stitchviewer/triangleMesh.h

+ 59 - 0
pandaapp/Package.pp

@@ -0,0 +1,59 @@
+//
+// Package.pp
+//
+// This file defines certain configuration variables that are to be
+// written into the various make scripts.  It is processed by ppremake
+// (along with the Sources.pp files in each of the various
+// directories) to generate build scripts appropriate to each
+// environment.
+//
+// This is the package-specific file, which should be at the top of
+// every source hierarchy.  It generally gets the ball rolling, and is
+// responsible for explicitly including all of the relevent Config.pp
+// files.
+
+
+
+// What is the name and version of this source tree?
+#if $[eq $[PACKAGE],]
+  #define PACKAGE pandaapp
+  #define VERSION 0.80
+#endif
+
+
+// Where should we find the PANDATOOL source directory?
+#if $[or $[CTPROJS],$[PANDATOOL]]
+  // If we are presently attached, use the environment variable.
+  #define PANDATOOL_SOURCE $[PANDATOOL]
+  #if $[eq $[PANDATOOL],]
+    #error You seem to be attached to some trees, but not PANDATOOL!
+  #endif
+#else
+  // Otherwise, if we are not attached, we guess that the source is a
+  // sibling directory to this source root.
+  #define PANDATOOL_SOURCE $[standardize $[TOPDIR]/../pandatool]
+#endif
+
+// Where should we install PANDAAPP?
+#if $[or $[CTPROJS],$[PANDAAPP]]
+  #define PANDAAPP_INSTALL $[PANDAAPP]
+  #define PANDAAPP_INSTALL_OTHER $(PANDAAPP)
+  #if $[eq $[PANDAAPP],]
+    #error You seem to be attached to some trees, but not PANDAAPP!
+  #endif
+#else
+  #defer PANDAAPP_INSTALL $[INSTALL_DIR]
+  #defer PANDAAPP_INSTALL_OTHER $[INSTALL_DIR]
+#endif
+
+
+// Define the inter-tree dependencies.
+#define NEEDS_TREES $[NEEDS_TREES] pandatool
+
+
+// Also get the PANDATOOL Package file and everything that includes.
+#if $[not $[isfile $[PANDATOOL_SOURCE]/Package.pp]]
+  #error PANDATOOL source directory not found!  Are you attached properly?
+#endif
+
+#include $[PANDATOOL_SOURCE]/Package.pp

+ 4 - 0
pandaapp/Sources.pp

@@ -0,0 +1,4 @@
+// This is the toplevel directory.  It contains configure.in and other
+// stuff.
+
+#define DIR_TYPE toplevel

+ 4 - 0
pandaapp/src/Sources.pp

@@ -0,0 +1,4 @@
+// This is a group directory: a directory level above a number of
+// source subdirectories.
+
+#define DIR_TYPE group

+ 46 - 0
pandaapp/src/stitch/Sources.pp

@@ -0,0 +1,46 @@
+#begin bin_target
+  #define TARGET stitch-command
+  #define LOCAL_LIBS \
+    stitchbase
+  #define OTHER_LIBS \
+    progbase \
+    linmath:c putil:c express:c panda:m pandaexpress:m pystub dtoolconfig dtool
+
+  #define SOURCES \
+    stitchCommandProgram.cxx stitchCommandProgram.h
+
+  #define INSTALL_HEADERS \
+
+#end bin_target
+
+#begin bin_target
+  #define TARGET stitch-image
+  #define LOCAL_LIBS \
+    stitchbase
+  #define OTHER_LIBS \
+    progbase \
+    pnmimagetypes:c pnmimage:c linmath:c putil:c express:c panda:m \
+    pandaexpress:m pystub dtoolconfig dtool
+
+  #define SOURCES \
+    stitchImageProgram.cxx stitchImageProgram.h
+
+#end bin_target
+
+#begin bin_target
+  #define TARGET stitch-viewer
+  #define LOCAL_LIBS \
+    stitchviewer stitchbase
+  #define OTHER_LIBS \
+    progbase \
+    device:c tform:c graph:c dgraph:c sgraph:c gobj:c sgattrib:c \
+    event:c chancfg:c display:c sgraphutil:c light:c \
+    pnmimagetypes:c pnmimage:c putil:c express:c \
+    panda:m pandaexpress:m \
+    dtoolutil:c dconfig:c dtoolconfig:m dtool:m pystub
+
+  #define SOURCES \
+    stitchViewerProgram.cxx stitchViewerProgram.h
+
+#end bin_target
+

+ 45 - 0
pandaapp/src/stitch/stitchCommandProgram.cxx

@@ -0,0 +1,45 @@
+// Filename: stitchCommandProgram.cxx
+// Created by:  drose (16Mar00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchCommandProgram.h"
+#include "stitchImageCommandOutput.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: StitchCommandProgram::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+StitchCommandProgram::
+StitchCommandProgram() {
+  set_program_description
+    ("This program reads a stitch command file, performs processing on the "
+     "file (such as alignment of images according to points marked within a "
+     "stitch region), and writes the resulting command file to standard "
+     "output.  It does not actually operate on any images.\n"
+
+     "The primary function of this program is to test the syntax of a "
+     "command file, or to preprocess a command file so that a series of "
+     "images (for instance, frames of a movie) may be easily transformed "
+     "by the exact same operation.");
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: StitchCommandProgram::run
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void StitchCommandProgram::
+run() {
+  StitchImageCommandOutput outputter;
+  _command_file.process(outputter);
+}
+
+
+int main(int argc, char *argv[]) {
+  StitchCommandProgram prog;
+  prog.parse_command_line(argc, argv);
+  prog.run();
+  return 0;
+}

+ 27 - 0
pandaapp/src/stitch/stitchCommandProgram.h

@@ -0,0 +1,27 @@
+// Filename: stitchCommandProgram.h
+// Created by:  drose (16Mar00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHCOMMANDPROGRAM_H
+#define STITCHCOMMANDPROGRAM_H
+
+#include <pandatoolbase.h>
+
+#include "stitchCommandReader.h"
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : StitchCommandProgram
+// Description : A program to read a stitch command file, process it
+//               without actually manipulating any images, and write
+//               the processed command file out.
+////////////////////////////////////////////////////////////////////
+class StitchCommandProgram : public StitchCommandReader {
+public:
+  StitchCommandProgram();
+
+  void run();
+};
+
+#endif
+

+ 48 - 0
pandaapp/src/stitch/stitchImageProgram.cxx

@@ -0,0 +1,48 @@
+// Filename: stitchImageProgram.cxx
+// Created by:  drose (16Mar00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchImageProgram.h"
+#include "stitchImageRasterizer.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: StitchImageProgram::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+StitchImageProgram::
+StitchImageProgram() {
+  set_program_description
+    ("This program reads a stitch command file, performs whatever processing "
+     "is indicated by the command file, and generates an output image for "
+     "each image listed in an output_image section.\n"
+
+     "The images are generated internally using a CPU-based rasterization "
+     "algorithm (no graphics hardware is used).");
+
+  add_option
+    ("f", "", 0, 
+     "Apply a very simple filter in an attempt to smooth the results.",
+     &StitchImageProgram::dispatch_none, &_filter_output);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: StitchImageProgram::run
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void StitchImageProgram::
+run() {
+  StitchImageRasterizer outputter;
+  outputter._filter_output = _filter_output;
+  _command_file.process(outputter);
+}
+
+
+int main(int argc, char *argv[]) {
+  StitchImageProgram prog;
+  prog.parse_command_line(argc, argv);
+  prog.run();
+  return 0;
+}

+ 30 - 0
pandaapp/src/stitch/stitchImageProgram.h

@@ -0,0 +1,30 @@
+// Filename: stitchImageProgram.h
+// Created by:  drose (16Mar00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHIMAGEPROGRAM_H
+#define STITCHIMAGEPROGRAM_H
+
+#include <pandatoolbase.h>
+
+#include "stitchCommandReader.h"
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : StitchImageProgram
+// Description : A program to read a stitch command file, perform the
+//               image manipulations in the CPU, and write output
+//               images for each processed image.
+////////////////////////////////////////////////////////////////////
+class StitchImageProgram : public StitchCommandReader {
+public:
+  StitchImageProgram();
+
+  void run();
+
+private:
+  bool _filter_output;
+};
+
+#endif
+

+ 43 - 0
pandaapp/src/stitch/stitchViewerProgram.cxx

@@ -0,0 +1,43 @@
+// Filename: stitchViewerProgram.cxx
+// Created by:  drose (16Mar00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchViewerProgram.h"
+#include "stitchImageConverter.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: StitchViewerProgram::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+StitchViewerProgram::
+StitchViewerProgram() {
+  set_program_description
+    ("This program reads a stitch command file, performs whatever processing "
+     "is indicated by the command file, and draws a 3-d representation of "
+     "all of the input images described in the command file.  The output "
+     "images are ignored.\n"
+     
+     "This program is primarily useful for showing the 3-d relationship "
+     "between images that has been inferred from the stitch command file.");
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: StitchViewerProgram::run
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void StitchViewerProgram::
+run() {
+  StitchImageVisualizer outputter;
+  _command_file.process(outputter);
+}
+
+
+int main(int argc, char *argv[]) {
+  StitchViewerProgram prog;
+  prog.parse_command_line(argc, argv);
+  prog.run();
+  return 0;
+}

+ 26 - 0
pandaapp/src/stitch/stitchViewerProgram.h

@@ -0,0 +1,26 @@
+// Filename: stitchViewerProgram.h
+// Created by:  drose (16Mar00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHVIEWERPROGRAM_H
+#define STITCHVIEWERPROGRAM_H
+
+#include <pandatoolbase.h>
+
+#include "stitchCommandReader.h"
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : StitchViewerProgram
+// Description : A program to read a stitch command file, and draw a
+//               3-d representation of all of the input images.
+////////////////////////////////////////////////////////////////////
+class StitchViewerProgram : public StitchCommandReader {
+public:
+  StitchViewerProgram();
+
+  void run();
+};
+
+#endif
+

+ 38 - 0
pandaapp/src/stitchbase/Sources.pp

@@ -0,0 +1,38 @@
+#define YACC_PREFIX stitchyy
+#define LFLAGS -i
+
+#begin ss_lib_target
+  #define TARGET stitchbase
+  #define LOCAL_LIBS
+  #define OTHER_LIBS \
+    progbase \
+    putil:c express:c mathutil:c linmath:c pnmimage:c pnm:c panda:m
+  #define UNIX_SYS_LIBS \
+    m
+
+  #define SOURCES \
+    config_stitch.cxx config_stitch.h layeredImage.cxx layeredImage.h \
+    morphGrid.cxx morphGrid.h stitchCommand.cxx stitchCommand.h \
+    stitchCommandReader.cxx stitchCommandReader.h \
+    stitchCylindricalLens.cxx stitchCylindricalLens.h stitchFile.cxx \
+    stitchFile.h stitchFisheyeLens.cxx stitchFisheyeLens.h \
+    stitchImage.cxx stitchImage.h stitchImageCommandOutput.cxx \
+    stitchImageCommandOutput.h stitchImageOutputter.cxx \
+    stitchImageOutputter.h stitchImageRasterizer.cxx \
+    stitchImageRasterizer.h stitchLens.cxx stitchLens.h \
+    stitchPSphereLens.cxx stitchPSphereLens.h stitchPerspectiveLens.cxx \
+    stitchPerspectiveLens.h stitchPoint.cxx stitchPoint.h stitcher.cxx \
+    stitcher.h triangle.cxx triangle.h triangleRasterizer.cxx \
+    triangleRasterizer.h \
+    stitchParserDefs.h stitchParser.yxx stitchLexerDefs.h stitchLexer.lxx
+
+  #define INSTALL_HEADERS \
+    config_stitch.h fixedPoint.h layeredImage.h morphGrid.h stitchCommand.h \
+    stitchCommandReader.h stitchCylindricalLens.h stitchFile.h \
+    stitchFisheyeLens.h stitchImage.h stitchImageCommandOutput.h \
+    stitchImageOutputter.h stitchImageRasterizer.h stitchLens.h \
+    stitchLexerDefs.h stitchPSphereLens.h stitchParser.h \
+    stitchParserDefs.h stitchPerspectiveLens.h stitchPoint.h \
+    stitcher.h triangle.h triangleRasterizer.h
+
+#end ss_lib_target

+ 16 - 0
pandaapp/src/stitchbase/config_stitch.cxx

@@ -0,0 +1,16 @@
+// Filename: config_stitch.cxx
+// Created by:  drose (05Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "config_stitch.h"
+
+#include <dconfig.h>
+
+Configure(config_stitch);
+
+ConfigureFn(config_stitch) {
+}
+
+string chan_cfg = config_stitch.GetString("chan-config", "single");
+

+ 13 - 0
pandaapp/src/stitchbase/config_stitch.h

@@ -0,0 +1,13 @@
+// Filename: config_stitch.h
+// Created by:  drose (05Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef CONFIG_STITCH_H
+#define CONFIG_STITCH_H
+
+#include <pandatoolbase.h>
+
+extern string chan_cfg;
+
+#endif

+ 32 - 0
pandaapp/src/stitchbase/fixedPoint.h

@@ -0,0 +1,32 @@
+// Filename: fixedPoint.h
+// Created by:  drose (06Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef FIXEDPOINT_H
+#define FIXEDPOINT_H
+
+// Simple fixed-point arithmetic definitions, for support of
+// TriangleRasterizer.  Totally ripped off from Mesa.
+
+typedef int FixedPoint;
+
+#define FIXED_ONE       0x00000800
+#define FIXED_HALF      0x00000400
+#define FIXED_FRAC_MASK 0x000007FF
+#define FIXED_INT_MASK  (~FIXED_FRAC_MASK)
+#define FIXED_EPSILON   1
+#define FIXED_SCALE     2048.0
+#define FIXED_SHIFT     11
+#define FloatToFixed(X) ((FixedPoint) ((X) * FIXED_SCALE))
+#define IntToFixed(I)   ((I) << FIXED_SHIFT)
+#define FixedToInt(X)   ((X) >> FIXED_SHIFT)
+#define FixedToUns(X)   (((unsigned int)(X)) >> 11)
+#define FixedCeil(X)    (((X) + FIXED_ONE - FIXED_EPSILON) & FIXED_INT_MASK)
+#define FixedFloor(X)   ((X) & FIXED_INT_MASK)
+/* 0.00048828125 = 1/FIXED_SCALE */
+#define FixedToFloat(X) ((X) * 0.00048828125)
+#define PosFloatToFixed(X)      FloatToFixed(X)
+#define SignedFloatToFixed(X)   FloatToFixed(X)
+
+#endif

+ 750 - 0
pandaapp/src/stitchbase/layeredImage.cxx

@@ -0,0 +1,750 @@
+// Filename: layeredImage.cxx
+// Created by:  drose (29Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "layeredImage.h"
+
+#include <pnmImage.h>
+#include <datagram.h>
+
+#include <stdarg.h>
+
+// Constants taken from various header files in Gimp.
+#define TILE_WIDTH 64
+#define TILE_HEIGHT 64
+
+#define RGB_GIMAGE  0
+#define RGBA_GIMAGE 1
+
+LayeredImage::TileManager::
+TileManager(const PNMImage *image, int channel) :
+  _data(image), _channel(channel) 
+{
+  int width = image->get_x_size();
+  int height = image->get_y_size();
+
+  while (width > TILE_WIDTH || height > TILE_WIDTH) {
+    _levels.push_back(Level());
+    Level &l = _levels.back();
+
+    l._width = width;
+    l._height = height;
+    l._ntile_rows = (height + TILE_HEIGHT - 1) / TILE_HEIGHT;
+    l._ntile_cols = (width + TILE_WIDTH - 1) / TILE_WIDTH;
+
+    width /= 2;
+    height /= 2;
+  }
+
+  _levels.push_back(Level());
+  Level &l = _levels.back();
+  
+  l._width = width;
+  l._height = height;
+  l._ntile_rows = (height + TILE_HEIGHT - 1) / TILE_HEIGHT;
+  l._ntile_cols = (width + TILE_WIDTH - 1) / TILE_WIDTH;
+}
+
+int LayeredImage::TileManager::
+get_nlevels() const {
+  return _levels.size();
+}
+
+int LayeredImage::TileManager::
+get_level_width(int level) const {
+  assert(level >= 0 && level < (int)_levels.size());
+  return _levels[level]._width;
+}
+
+int LayeredImage::TileManager::
+get_level_height(int level) const {
+  assert(level >= 0 && level < (int)_levels.size());
+  return _levels[level]._height;
+}
+
+int LayeredImage::TileManager::
+get_ntiles(int level) const {
+  assert(level >= 0 && level < (int)_levels.size());
+  return _levels[level]._ntile_rows * _levels[level]._ntile_cols;
+}
+
+int LayeredImage::TileManager::
+get_tile_left(int level, int tile) const {
+  //  int ntile_rows = _levels[level]._ntile_rows;
+  int ntile_cols = _levels[level]._ntile_cols;
+
+  //  int r = tile / ntile_cols;
+  int c = tile % ntile_cols;
+  return c * TILE_WIDTH;
+}
+
+int LayeredImage::TileManager::
+get_tile_top(int level, int tile) const {
+  //  int ntile_rows = _levels[level]._ntile_rows;
+  int ntile_cols = _levels[level]._ntile_cols;
+
+  int r = tile / ntile_cols;
+  //  int c = tile % ntile_cols;
+  return r * TILE_HEIGHT;
+}
+
+int LayeredImage::TileManager::
+get_tile_width(int level, int tile) const {
+  return min(TILE_WIDTH, _data->get_x_size() - get_tile_left(level, tile));
+}
+
+int LayeredImage::TileManager::
+get_tile_height(int level, int tile) const {
+  return min(TILE_HEIGHT, _data->get_y_size() - get_tile_top(level, tile));
+}
+
+// Trims off the invisible (alpha-0) border around the layer.  Returns
+// true if there is anything left, false if the layer would be empty.
+bool LayeredImage::Layer::
+trim() {
+  assert(_data != NULL);
+  if (_data->has_alpha()) {
+    int xsize = _data->get_x_size();
+    int ysize = _data->get_y_size();
+    
+    int top = xsize - 1;
+    int left = ysize - 1;
+    int bottom = 0;
+    int right = 0;
+    
+    for (int y = 0; y < ysize; y++) {
+      for (int x = 0; x < xsize; x++) {
+	if (_data->get_alpha_val(x, y) != 0) {
+	  top = min(top, y);
+	  left = min(left, x);
+	  bottom = max(bottom, y);
+	  right = max(right, x);
+	}
+      }
+    }
+
+    if (top > bottom || left > right) {
+      // The layer is completely empty.
+      return false;
+    }
+
+    if (top > 0 || left > 0 || bottom < ysize - 1 || right < xsize - 1) {
+      xsize = right - left + 1;
+      ysize = bottom - top + 1;
+      PNMImage *sub = new PNMImage(xsize, ysize, 4);
+      sub->copy_sub_image(*_data, 0, 0, left, top);
+      delete _data;
+      _data = sub;
+      _offset[0] += left;
+      _offset[1] += top;
+    }
+  }
+
+  return true;
+}
+
+LayeredImage::
+LayeredImage(int xsize, int ysize) :
+  _xsize(xsize), _ysize(ysize) {
+}
+
+LayeredImage::
+~LayeredImage() {
+  Layers::const_iterator li;
+  for (li = _layers.begin(); li != _layers.end(); ++li) {
+    delete (*li)._data;
+  }
+}
+
+void LayeredImage::
+add_layer(const string &name, const LVector2d &offset,
+	  PNMImage *data) {
+  _layers.push_back(Layer());
+  Layer &l = _layers.back();
+
+  l._name = name;
+  l._offset = offset;
+  l._data = data;
+
+  if (!l.trim()) {
+    // If trimming the layer reveals that it is empty, delete it.
+    delete l._data;
+    _layers.pop_back();
+  }
+}
+
+bool LayeredImage::
+write_file(const Filename &filename) {
+  ofstream out(filename.c_str());
+
+  // Maybe in the future, if we support more than one kind of file
+  // here, we'll decide based on the filename extension which kind to
+  // write out.
+
+  return write_xcf(out);
+}
+
+bool LayeredImage::
+write_xcf(ostream &out) {
+  _out = &out;
+  _pos = 0;
+
+  // Write out the version tag
+  static const int version_tag_len = 14;
+  int8_t version_tag[version_tag_len];
+  memset(version_tag, 0, version_tag_len);
+  strcpy((char *)version_tag, "gimp xcf file");
+  xcf_write_int8(version_tag, version_tag_len);
+
+  // Write out the width, height, and type.
+  int32_t width = _xsize;
+  int32_t height = _ysize;
+  int32_t base_type = RGB_GIMAGE;
+  xcf_write_int32(&width, 1);
+  xcf_write_int32(&height, 1);
+  xcf_write_int32(&base_type, 1);
+
+  xcf_save_image_props();
+
+  // Save the current file position; we'll return here to place the
+  // layer offset information.
+  int saved_pos = _pos;
+
+  int nlayers = _layers.size();
+  int nchannels = 0;
+
+  // Seek to after the offset lists.
+  xcf_seek_pos(_pos + (nlayers + nchannels + 2) * 4);
+
+  // Write out each layer.  Since the layers were added to the
+  // LayeredImage object from the bottom up (to me, the intuitive
+  // order), and since they are stored in the XCF file from the top
+  // down, we must reverse the order here.
+  Layers::reverse_iterator li;
+  for (li = _layers.rbegin(); li != _layers.rend(); ++li) {
+    int32_t offset = _pos;
+    xcf_save_layer(*li);
+
+    // Go back to write this layer offset.
+    xcf_seek_pos(saved_pos);
+    xcf_write_int32(&offset, 1);
+    saved_pos = _pos;
+
+    xcf_seek_end();
+  }
+
+  // Write out '0' offset to indicate the end of the layer offsets.
+  int32_t offset = 0;
+  xcf_seek_pos(saved_pos);
+  xcf_write_int32(&offset, 1);
+  saved_pos = _pos;
+  xcf_seek_end();
+
+  /*
+    No need to explicitly write out the channels.
+
+  // Write out each channel.
+  static const char *channel_name[3] = { "red", "green", "blue" };
+  for (int i = 0; i < 3; i++) {
+    // save the start offset of where we are writing
+    // out the next channel.
+    int32_t offset = _pos;
+
+    // write out the channel.
+    xcf_save_channel(channel_name[i], _layers.front()._data, i);
+
+    // seek back to where we are to write out the next
+    // channel offset and write it out.
+    xcf_seek_pos(saved_pos);
+    xcf_write_int32(&offset, 1);
+
+    // increment the location we are to write out the
+    // next offset.
+    saved_pos = _pos;
+
+    // seek to the end of the file which is where
+    // we will write out the next channel.
+    xcf_seek_end();
+  }
+  */
+
+  // Write out '0' offset to indicate the end of the channel offsets.
+  offset = 0;
+  xcf_seek_pos(saved_pos);
+  xcf_write_int32(&offset, 1);
+  saved_pos = _pos;
+  xcf_seek_end();
+
+  return !_out->fail();
+}
+
+int LayeredImage::
+xcf_write_int8(const int8_t *data, int num) {
+  _out->write((const char *)data, num);
+  return _pos += num;
+}
+
+int LayeredImage::
+xcf_write_int32(const int32_t *data, int num) {
+  // We need to write a bunch of big-endian int32's.
+  Datagram dg;
+  for (int i = 0; i < num; i++) {
+    dg.add_be_int32(data[i]);
+  }
+  _out->write((const char *)dg.get_data(), dg.get_length());
+  return _pos += dg.get_length();
+}
+
+int LayeredImage::
+xcf_write_string(const string &str) {
+  int32_t size = (int32_t)str.size() + 1;
+  if (str.empty()) {
+    size = 0;
+  }
+  xcf_write_int32(&size, 1);
+  return xcf_write_int8((const int8_t *)str.c_str(), size);
+}
+
+void LayeredImage::
+xcf_save_image_props() {
+  xcf_save_prop(PROP_END);
+}
+
+void LayeredImage::
+xcf_save_layer_props(const LayeredImage::Layer &layer) {
+  if (&layer == &_layers.front()) {
+    xcf_save_prop(PROP_ACTIVE_LAYER);
+  }
+  xcf_save_prop(PROP_OPACITY, 255);
+  xcf_save_prop(PROP_VISIBLE, 1);
+  xcf_save_prop(PROP_LINKED, 0);
+  xcf_save_prop(PROP_PRESERVE_TRANSPARENCY, 0);
+  xcf_save_prop(PROP_APPLY_MASK, 1);
+  xcf_save_prop(PROP_EDIT_MASK, 0);
+  xcf_save_prop(PROP_SHOW_MASK, 0);
+  xcf_save_prop(PROP_MODE, 0);
+
+  xcf_save_prop(PROP_OFFSETS, 
+		(int32_t)layer._offset[0], 
+		(int32_t)layer._offset[1]);
+  xcf_save_prop(PROP_END);
+}
+
+void LayeredImage::
+xcf_save_channel_props() {
+  xcf_save_prop(PROP_OPACITY, 255);
+  xcf_save_prop(PROP_VISIBLE, 1);
+  xcf_save_prop(PROP_SHOW_MASKED, 0);
+  //  xcf_save_prop(PROP_COLOR, channel->col);
+
+  xcf_save_prop(PROP_END);
+}
+
+// This odd function is lifted from Gimp's xcf.c.
+void LayeredImage::
+xcf_save_prop(LayeredImage::PropType prop_type, ...) {
+  int32_t size;
+  va_list args;
+
+  va_start(args, prop_type);
+
+  switch (prop_type) {
+  case PROP_END:
+    size = 0;
+    
+    xcf_write_int32((int32_t*)&prop_type, 1);
+    xcf_write_int32(&size, 1);
+    break;
+  case PROP_COLORMAP:
+    {
+      int32_t ncolors;
+      int8_t *colors;
+
+      ncolors = va_arg(args, int32_t);
+      colors = va_arg(args, int8_t*);
+      size = 4 + ncolors;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32(&ncolors, 1);
+      xcf_write_int8(colors, ncolors * 3);
+    }
+    break;
+  case PROP_ACTIVE_LAYER:
+  case PROP_ACTIVE_CHANNEL:
+  case PROP_SELECTION:
+    size = 0;
+
+    xcf_write_int32((int32_t*)&prop_type, 1);
+    xcf_write_int32(&size, 1);
+    break;
+  case PROP_FLOATING_SELECTION:
+    assert(false);
+    break;
+  case PROP_OPACITY:
+    {
+      int32_t opacity;
+
+      opacity = va_arg(args, int32_t);
+
+      size = 4;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32((int32_t*)&opacity, 1);
+    }
+    break;
+  case PROP_MODE:
+    {
+      int32_t mode;
+
+      mode = va_arg(args, int32_t);
+      size = 4;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32((int32_t*)&mode, 1);
+    }
+    break;
+  case PROP_VISIBLE:
+    {
+      int32_t visible;
+
+      visible = va_arg(args, int32_t);
+      size = 4;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32(&visible, 1);
+    }
+    break;
+  case PROP_LINKED:
+    {
+      int32_t linked;
+
+      linked = va_arg(args, int32_t);
+      size = 4;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32(&linked, 1);
+    }
+    break;
+  case PROP_PRESERVE_TRANSPARENCY:
+    {
+      int32_t preserve_trans;
+
+      preserve_trans = va_arg(args, int32_t);
+      size = 4;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32(&preserve_trans, 1);
+    }
+    break;
+  case PROP_APPLY_MASK:
+    {
+      int32_t apply_mask;
+
+      apply_mask = va_arg(args, int32_t);
+      size = 4;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32(&apply_mask, 1);
+    }
+    break;
+  case PROP_EDIT_MASK:
+    {
+      int32_t edit_mask;
+
+      edit_mask = va_arg(args, int32_t);
+      size = 4;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32(&edit_mask, 1);
+    }
+    break;
+  case PROP_SHOW_MASK:
+    {
+      int32_t show_mask;
+
+      show_mask = va_arg(args, int32_t);
+      size = 4;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32(&show_mask, 1);
+    }
+    break;
+  case PROP_SHOW_MASKED:
+    {
+      int32_t show_masked;
+
+      show_masked = va_arg(args, int32_t);
+      size = 4;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32(&show_masked, 1);
+    }
+    break;
+  case PROP_OFFSETS:
+    {
+      int32_t offsets[2];
+
+      offsets[0] = va_arg(args, int32_t);
+      offsets[1] = va_arg(args, int32_t);
+      size = 8;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int32((int32_t*) offsets, 2);
+    }
+    break;
+  case PROP_COLOR:
+    {
+      int8_t *color;
+
+      color = va_arg(args, int8_t*);
+      size = 3;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int8(color, 3);
+    }
+    break;
+  case PROP_COMPRESSION:
+    {
+      int8_t compression;
+
+      compression =(int8_t) va_arg(args, int32_t);
+      size = 1;
+
+      xcf_write_int32((int32_t*)&prop_type, 1);
+      xcf_write_int32(&size, 1);
+      xcf_write_int8(&compression, 1);
+    }
+    break;
+  case PROP_GUIDES:
+    assert(false);
+    break;
+  }
+
+  va_end(args);
+}
+
+void LayeredImage::
+xcf_save_layer(const LayeredImage::Layer &layer) {
+  // write out the width, height and image type information for the layer
+  int32_t width = layer._data->get_x_size();
+  int32_t height = layer._data->get_y_size();
+  int32_t type = RGBA_GIMAGE;
+  xcf_write_int32((int32_t*)&width, 1);
+  xcf_write_int32((int32_t*)&height, 1);
+  xcf_write_int32((int32_t*)&type, 1);
+
+  // write out the layer's name
+  xcf_write_string(layer._name);
+
+  // write out the layer properties
+  xcf_save_layer_props(layer);
+
+  // save the current position which is where the hierarchy offset
+  //  will be stored.
+  int saved_pos = _pos;
+
+  // write out the layer tile hierarchy
+  xcf_seek_pos(_pos + 8);
+  int32_t offset = _pos;
+
+  xcf_save_hierarchy(layer._data, -1);
+
+  xcf_seek_pos(saved_pos);
+  xcf_write_int32(&offset, 1);
+  saved_pos = _pos;
+
+  // write out the layer mask.  We write out the alpha channel here
+  // instead of as a proper alpha channel, since it's more convenient
+  // in The Gimp to edit the alpha channel in the layer mask.
+
+  if (layer._data->has_alpha()) {
+    xcf_seek_end();
+    offset = _pos;
+    xcf_save_channel("mask", layer._data, 3);
+  } else {
+    offset = 0;
+  }
+
+  xcf_seek_pos(saved_pos);
+  xcf_write_int32(&offset, 1);
+}
+
+void LayeredImage::
+xcf_save_channel(const string &name, const PNMImage *image, int channel) {
+  int32_t saved_pos;
+  int32_t offset;
+
+  // write out the width and height information for the channel
+  int32_t width = image->get_x_size();
+  int32_t height = image->get_y_size();
+  xcf_write_int32(&width, 1);
+  xcf_write_int32(&height, 1);
+
+  // write out the channels name
+  xcf_write_string(name);
+
+  // write out the channel properties
+  xcf_save_channel_props();
+
+  // save the current position which is where the hierarchy offset
+  //  will be stored.
+  saved_pos = _pos;
+
+  /* write out the channel tile hierarchy */
+  xcf_seek_pos(_pos + 4);
+  offset = _pos;
+
+  xcf_save_hierarchy(image, channel);
+
+  xcf_seek_pos(saved_pos);
+  xcf_write_int32(&offset, 1);
+  saved_pos = _pos;
+}
+
+void LayeredImage::
+xcf_save_hierarchy(const PNMImage *image, int channel) {
+  int32_t width = image->get_x_size();
+  int32_t height = image->get_y_size();
+  int32_t bpp = (channel < 0) ? 4 : 1;
+  xcf_write_int32(&width, 1);
+  xcf_write_int32(&height, 1);
+  xcf_write_int32(&bpp, 1);
+
+  int saved_pos = _pos;
+
+  TileManager tm(image, channel);
+  int nlevels = tm.get_nlevels();
+
+  xcf_seek_pos(_pos + (nlevels + 1) * 4);
+
+  for (int i = 0; i < nlevels; i++) {
+    // save the start offset of where we are writing
+    // out the next level.
+    int32_t offset = _pos;
+    
+    // write out the level.
+    xcf_save_level(tm, i);
+
+    // seek back to where we are to write out the next
+    // level offset and write it out.
+    xcf_seek_pos(saved_pos);
+    xcf_write_int32(&offset, 1);
+
+    // increment the location we are to write out the
+    // next offset.
+    saved_pos = _pos;
+
+    // seek to the end of the file which is where
+    // we will write out the next level.
+    xcf_seek_end();  
+  }
+
+  // write out a '0' offset position to indicate the end
+  // of the level offsets.
+  int32_t offset = 0;
+  xcf_seek_pos(saved_pos);
+  xcf_write_int32(&offset, 1);
+}
+
+void LayeredImage::
+xcf_save_level(const LayeredImage::TileManager &tm, int level) {
+  // write out the width and height information for the channel
+  int32_t width = tm.get_level_width(level);
+  int32_t height = tm.get_level_height(level);
+  xcf_write_int32(&width, 1);
+  xcf_write_int32(&height, 1);
+
+  int saved_pos = _pos;
+
+  int ntiles = tm.get_ntiles(level);
+  xcf_seek_pos(_pos + (ntiles + 1) * 4);
+
+  for (int i = 0; i < ntiles; i++) {
+    // save the start offset of where we are writing
+    // out the next tile.
+    int32_t offset = _pos;
+    
+    // write out the tile.
+    xcf_save_tile(tm, level, i);
+
+    // seek back to where we are to write out the next
+    // tile offset and write it out.
+    xcf_seek_pos(saved_pos);
+    xcf_write_int32(&offset, 1);
+
+    // increment the location we are to write out the
+    // next offset.
+    saved_pos = _pos;
+    
+    xcf_seek_end();
+  }
+
+  // write out a '0' offset position to indicate the end
+  // of the level offsets.
+  int32_t offset = 0;
+  xcf_seek_pos(saved_pos);
+  xcf_write_int32(&offset, 1);
+}
+
+void LayeredImage::
+xcf_save_tile(const LayeredImage::TileManager &tm, int level, int tile) {
+  int xoff = tm.get_tile_left(level, tile);
+  int yoff = tm.get_tile_top(level, tile);
+  int xsize = tm.get_tile_width(level, tile);
+  int ysize = tm.get_tile_height(level, tile);
+
+  if (tm._channel < 0) {
+    int size = xsize * ysize * 4;
+    int8_t *array = new int8_t[size];
+    int i = 0;
+    for (int y = yoff; y < yoff + ysize; y++) {
+      for (int x = xoff; x < xoff + xsize; x++) {
+	array[i++] = tm._data->get_red_val(x, y);
+	array[i++] = tm._data->get_green_val(x, y);
+	array[i++] = tm._data->get_blue_val(x, y);
+	array[i++] = 255;
+      }
+    }
+    assert(i == size);
+    xcf_write_int8(array, size);
+    delete[] array;
+
+  } else {
+    int size = xsize * ysize;
+    int8_t *array = new int8_t[size];
+    int i = 0;
+    for (int y = yoff; y < yoff + ysize; y++) {
+      for (int x = xoff; x < xoff + xsize; x++) {
+	array[i++] = tm._data->get_channel_val(x, y, tm._channel);
+      }
+    }
+    assert(i == size);
+    xcf_write_int8(array, size);
+    delete[] array;
+  }
+}
+
+void LayeredImage::
+xcf_seek_pos(int to_pos) {
+  _out->seekp(to_pos);
+  _pos = to_pos;
+}
+
+void LayeredImage::
+xcf_seek_end() {
+  _out->seekp(0, ios::end);
+  _pos = _out->tellp();
+}

+ 120 - 0
pandaapp/src/stitchbase/layeredImage.h

@@ -0,0 +1,120 @@
+// Filename: layeredImage.h
+// Created by:  drose (29Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef LAYEREDIMAGE_H
+#define LAYEREDIMAGE_H
+
+#include <pandatoolbase.h>
+
+#include <luse.h>
+#include <filename.h>
+
+#include <vector>
+
+//#include <stdint.h>
+
+class PNMImage;
+
+class LayeredImage {
+public:
+  typedef char int8_t;
+  typedef long int32_t;
+
+  LayeredImage(int xsize, int ysize);
+  ~LayeredImage();
+
+  void add_layer(const string &name, const LVector2d &offset,
+		 PNMImage *data);
+
+  bool write_file(const Filename &filename);
+
+  bool write_xcf(ostream &out);
+
+private:
+  // XCF property types.  From Gimp's xcf.c.
+  enum PropType {
+    PROP_END = 0,
+    PROP_COLORMAP = 1,
+    PROP_ACTIVE_LAYER = 2,
+    PROP_ACTIVE_CHANNEL = 3,
+    PROP_SELECTION = 4,
+    PROP_FLOATING_SELECTION = 5,
+    PROP_OPACITY = 6,
+    PROP_MODE = 7,
+    PROP_VISIBLE = 8,
+    PROP_LINKED = 9,
+    PROP_PRESERVE_TRANSPARENCY = 10,
+    PROP_APPLY_MASK = 11,
+    PROP_EDIT_MASK = 12,
+    PROP_SHOW_MASK = 13,
+    PROP_SHOW_MASKED = 14,
+    PROP_OFFSETS = 15,
+    PROP_COLOR = 16,
+    PROP_COMPRESSION = 17,
+    PROP_GUIDES = 18
+  };
+
+  class Layer {
+  public:
+    bool trim();
+
+    string _name;
+    LVector2d _offset;
+    PNMImage *_data;
+  };
+
+  class TileManager {
+  public:
+    TileManager(const PNMImage *image, int channel);
+    int get_nlevels() const;
+    int get_level_width(int level) const;
+    int get_level_height(int level) const;
+    int get_ntiles(int level) const;
+    int get_tile_left(int level, int tile) const;
+    int get_tile_top(int level, int tile) const;
+    int get_tile_width(int level, int tile) const;
+    int get_tile_height(int level, int tile) const;
+
+    const PNMImage *_data;
+    int _channel;
+
+  private:
+    class Level {
+    public:
+      int _width;
+      int _height;
+      int _ntile_rows;
+      int _ntile_cols;
+    };
+    typedef vector<Level> Levels;
+    Levels _levels;
+  };
+
+  int xcf_write_int8(const int8_t *data, int num);
+  int xcf_write_int32(const int32_t *data, int num);
+  int xcf_write_string(const string &str);
+  void xcf_save_image_props();
+  void xcf_save_layer_props(const Layer &layer);
+  void xcf_save_channel_props();
+  void xcf_save_prop(PropType prop_type, ...);
+  void xcf_save_layer(const Layer &layer);
+  void xcf_save_channel(const string &name, const PNMImage *image, 
+			int channel);
+  void xcf_save_hierarchy(const PNMImage *image, int channel);
+  void xcf_save_level(const TileManager &tm, int level);
+  void xcf_save_tile(const TileManager &tm, int level, int tile);
+  void xcf_seek_pos(int to_pos);
+  void xcf_seek_end();
+
+  typedef vector<Layer> Layers;
+  Layers _layers;
+  int _xsize;
+  int _ysize;
+
+  ostream *_out;
+  int _pos;
+};
+
+#endif

+ 472 - 0
pandaapp/src/stitchbase/morphGrid.cxx

@@ -0,0 +1,472 @@
+// Filename: morphGrid.cxx
+// Created by:  drose (08Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "morphGrid.h"
+#include "triangle.h"
+
+#include <mathNumbers.h>
+
+#include <math.h>
+#include <assert.h>
+
+MorphGrid::Vertex::
+Vertex(const LPoint2d &p) {
+  for (int i = 0; i < (int)TT_num; i++) {
+    _p[i] = p;
+  }
+  _alpha = 1.0;
+  _over_another = false;
+
+  // -1 on the distance counter is a flag that the value hasn't yet
+  // been computed.
+  _dist_from_interior = -1;
+}
+
+MorphGrid::Triangle::
+Triangle(Vertex *v0, Vertex *v1, Vertex *v2) {
+  _v[0] = v0;
+  _v[1] = v1;
+  _v[2] = v2;
+}
+
+bool MorphGrid::Triangle::
+contains_point(const LPoint2d &p, TableType from) const {
+  if ((p[0] < _min_p[from][0] || p[0] > _max_p[from][0]) ||
+      (p[1] < _min_p[from][1] || p[1] > _max_p[from][1])) {
+    // Doesn't pass the minmax test.
+    return false;
+  }
+
+  return triangle_contains_point(p, _v[0]->_p[from], _v[1]->_p[from],
+				 _v[2]->_p[from]);
+}
+
+LPoint2d MorphGrid::Triangle::
+morph_point(const LPoint2d &p, TableType from, TableType to) const {
+  return (p * _inv[from]) * _mat[to];
+}
+
+double MorphGrid::Triangle::
+get_alpha(const LPoint2d &p, TableType from) const {
+  LPoint2d q = p * _inv[from];
+
+  // Now q is a point in a right triangle, where (0,1) is v0, (0,0) is
+  // v1, and (1,0) is v2.  Interpolate the appropriate alpha value
+  // based on this coordinate system.
+
+  double alpha01 = (_v[0]->_alpha + q[1] * (_v[1]->_alpha - _v[0]->_alpha));
+  return (alpha01 + q[0] * (_v[2]->_alpha - alpha01));
+}
+
+void MorphGrid::Triangle::
+recompute() {
+  for (int i = 0; i < (int)TT_num; i++) {
+    for (int a = 0; a < 2; a++) {
+      _min_p[i][a] = min(min(_v[0]->_p[i][a], _v[1]->_p[i][a]), 
+			 _v[2]->_p[i][a]);
+      _max_p[i][a] = max(max(_v[0]->_p[i][a], _v[1]->_p[i][a]), 
+			 _v[2]->_p[i][a]);
+    }
+  
+    LPoint2d origin = _v[1]->_p[i];
+    LVector2d yaxis = _v[0]->_p[i] - origin;
+    LVector2d xaxis = _v[2]->_p[i] - origin;
+    
+    _mat[i] = LMatrix3d(xaxis[0], xaxis[1], 0.0,
+			yaxis[0], yaxis[1], 0.0,
+			origin[0], origin[1], 1.0);
+    
+    _inv[i] = invert(_mat[i]);
+  }
+}
+
+MorphGrid::TriangleTree::
+TriangleTree(Triangle *a, Triangle *b) {
+  _has_tris = true;
+  _u._tri[0] = a;
+  _u._tri[1] = b;
+}
+
+MorphGrid::TriangleTree::
+TriangleTree(TriangleTree *a, TriangleTree *b) {
+  _has_tris = false;
+  _u._tree[0] = a;
+  _u._tree[1] = b;
+}
+
+MorphGrid::TriangleTree::
+~TriangleTree() {
+  if (!_has_tris) {
+    delete _u._tree[0];
+    delete _u._tree[1];
+  }
+}
+
+void MorphGrid::TriangleTree::
+recompute() {
+  if (_has_tris) {
+    _u._tri[0]->recompute();
+    _u._tri[1]->recompute();
+
+    for (int i = 0; i < (int)TT_num; i++) {
+      for (int a = 0; a < 2; a++) {
+	_min_p[i][a] =
+	  min(_u._tri[0]->_min_p[i][a], _u._tri[1]->_min_p[i][a]);
+	_max_p[i][a] =
+	  max(_u._tri[0]->_max_p[i][a], _u._tri[1]->_max_p[i][a]);
+      }
+    }
+  } else {
+    _u._tree[0]->recompute();
+    _u._tree[1]->recompute();
+
+    for (int i = 0; i < (int)TT_num; i++) {
+      for (int a = 0; a < 2; a++) {
+	_min_p[i][a] = 
+	  min(_u._tree[0]->_min_p[i][a], _u._tree[1]->_min_p[i][a]);
+	_max_p[i][a] =
+	  max(_u._tree[0]->_max_p[i][a], _u._tree[1]->_max_p[i][a]);
+      }
+    }
+  }
+}    
+
+MorphGrid::Triangle *MorphGrid::TriangleTree::
+find_triangle(const LPoint2d &p, TableType from) const {
+  if ((p[0] < _min_p[from][0] || p[0] > _max_p[from][0]) ||
+      (p[1] < _min_p[from][1] || p[1] > _max_p[from][1])) {
+    // Doesn't pass the minmax test.
+    return NULL;
+  }
+
+  if (_has_tris) {
+    if (_u._tri[0]->contains_point(p, from)) {
+      return _u._tri[0];
+    }
+    if (_u._tri[1]->contains_point(p, from)) {
+      return _u._tri[1];
+    }
+    return NULL;
+  } else {
+    Triangle *t = _u._tree[0]->find_triangle(p, from);
+    if (t == NULL) {
+      t = _u._tree[1]->find_triangle(p, from);
+    }
+    return t;
+  }
+}
+
+MorphGrid::
+MorphGrid() {
+  _x_verts = 0;
+  _y_verts = 0;
+  _last_triangle = NULL;
+  _tree = NULL;
+}
+
+MorphGrid::
+~MorphGrid() {
+  if (_tree != NULL) {
+    delete _tree;
+  }
+}
+
+bool MorphGrid::
+is_empty() const {
+  return _x_verts <= 0 || _y_verts <= 0;
+}
+
+void MorphGrid::
+clear() {
+  init(0, 0);
+}
+
+void MorphGrid::
+init(int x_verts, int y_verts) {
+  _x_verts = x_verts;
+  _y_verts = y_verts;
+
+  if (_tree != NULL) {
+    delete _tree;
+    _tree = NULL;
+  }
+  _triangles.clear();
+  _last_triangle = NULL;
+  _table.clear();
+  
+  if (is_empty()) {
+    return;
+  }
+
+  // Create a 2-d table of vertices.
+  _table.reserve(_y_verts);
+  int x, y;
+  for (y = 0; y < _y_verts; y++) {
+    _table.push_back(Row());
+    _table[y].clear();
+    _table[y].reserve(_x_verts);
+    for (x = 0; x < _x_verts; x++) {
+      LPoint2d p((double)x / (double)(_x_verts - 1),
+		 1.0 - (double)y / (double)(_y_verts - 1));
+      _table[y].push_back(Vertex(p));
+    }
+  }
+
+  // Now create a bunch of triangles for these vertices.
+  int num_tris = (_y_verts - 1) * (_x_verts - 1) * 2;
+
+  _triangles.reserve(num_tris);
+  for (y = 0; y + 1 < _y_verts; y++) {
+    for (x = 0; x + 1 < _x_verts; x++) {
+      _triangles.push_back(Triangle(&_table[y][x],
+				    &_table[y + 1][x],
+				    &_table[y + 1][x + 1]));
+      _triangles.push_back(Triangle(&_table[y][x],
+				    &_table[y + 1][x + 1],
+				    &_table[y][x + 1]));
+    }
+  }
+  assert((int)_triangles.size() == num_tris);
+
+  // Now create a 2-d table of TriangleTree nodes, each of which
+  // points to a pair of triangles.  We'll use this to build up the
+  // TriangleTree structure.
+  typedef vector<TriangleTree *> TRow;
+  typedef vector<TRow> TTable;
+
+  TTable tree;
+
+  int x_tree = _x_verts - 1;
+  int y_tree = _y_verts - 1;
+  tree.reserve(y_tree);
+  int i = 0;
+  for (y = 0; y < y_tree; y++) {
+    tree.push_back(TRow());
+    tree[y].clear();
+    tree[y].reserve(x_tree);
+    for (x = 0; x < x_tree; x++) {
+      tree[y].push_back(new TriangleTree(&_triangles[i],
+					 &_triangles[i + 1]));
+      i += 2;
+    }
+  }
+  assert(i == num_tris);
+
+  // Now repeatedly pair up adjacent TriangleTree nodes, each time
+  // making a new level with half the number of nodes, until we end up
+  // with a single node.
+  while (x_tree > 1 || y_tree > 1) {
+    // Collapse horizontal pairs.
+    int tx = 0;
+    for (int y = 0; y < y_tree; y++) {
+      tx = 0;
+      int fx = 0;
+      while (fx + 1 < x_tree) {
+	tree[y][tx++] = new TriangleTree(tree[y][fx], tree[y][fx + 1]);
+	fx += 2;
+      }
+      if (fx < x_tree) {
+	// One more odd element remaining, just copy it up.
+	tree[y][tx++] = tree[y][fx];
+	fx++;
+      }
+      assert(fx == x_tree);
+    }
+    x_tree = tx;
+
+    // Collapse vertical pairs.
+    int ty = 0;
+    for (int x = 0; x < x_tree; x++) {
+      ty = 0;
+      int fy = 0;
+      while (fy + 1 < y_tree) {
+	tree[ty++][x] = new TriangleTree(tree[fy][x], tree[fy + 1][x]);
+	fy += 2;
+      }
+      if (fy < y_tree) {
+	// One more odd element remaining, just copy it up.
+	tree[ty++][x] = tree[fy][x];
+	fy++;
+      }
+      assert(fy == y_tree);
+    }
+    y_tree = ty;
+  }
+
+  assert(x_tree == 1 && y_tree == 1);
+  _tree = tree[0][0];
+}
+
+void MorphGrid::
+recompute() {
+  _tree->recompute();
+}
+
+void MorphGrid::
+fill_alpha() {
+  // The stitcher has already made a distinction between interior
+  // points (that is, points which are over no other image, and must
+  // be 100% opaque) and exterior points (points which lay over
+  // another image, and should be feathered).  We now need to
+  // determine the distance each exterior point is from this
+  // interior/exterior dividing line.
+
+  // To do this, we first find an interior point.
+  bool found_interior = false;
+  int x, y;
+  for (y = 0; y < _y_verts && !found_interior; y++) {
+    for (x = 0; x < _x_verts && !found_interior; x++) {
+      if (!_table[y][x]._over_another) {
+	// Here's one!
+	found_interior = true;
+	count_dist_from_interior(x, y, 0);
+      }
+    }
+  }
+
+  if (!found_interior) {
+    // There are no interior points in this image--it entirely covers
+    // other images.  (Doesn't seem to be much point to it, does
+    // there?)  We'll just feather the edges a little.
+    for (y = 0; y < _y_verts; y++) {
+      _table[y][0]._alpha = 0.0;
+      _table[y][_x_verts - 1]._alpha = 0.0;
+    }
+    for (x = 0; x < _x_verts; x++) {
+      _table[0][x]._alpha = 0.0;
+      _table[_y_verts - 1][x]._alpha = 0.0;
+    }
+    return;
+  }
+
+  // Now go back through and assign the alpha based on the relative
+  // distance of each point from the edge and from the interior.
+  for (y = 0; y < _y_verts; y++) {
+    for (x = 0; x < _x_verts; x++) {
+      if (!_table[y][x]._over_another) {
+	_table[y][x]._alpha = 1.0;
+
+      } else {
+	int dist_from_edge = 
+	  min(min(x, y), 
+	      min(_x_verts - 1 - x, _y_verts - 1 - y));
+
+	assert(_table[y][x]._dist_from_interior >= 0);
+
+	// We subtract one from dist_from_interior to give us a bit of
+	// comfort zone around the interior edge--we're not precisely
+	// sure where the actual edge is.
+	int dist_from_interior = 
+	  max(_table[y][x]._dist_from_interior - 1, 0);
+
+	// Now if dist_from_edge is 0, it must be transparent; if
+	// dist_from_interior is 0, it must be opaque.  Any other
+	// combination should be some value in between.
+	if (dist_from_interior == 0) {
+	  _table[y][x]._alpha = 1.0;
+
+	} else if (dist_from_edge == 0) {
+	  _table[y][x]._alpha = 0.0;
+
+	} else {
+	  double ratio = (double)dist_from_interior /
+	    (double)(dist_from_interior + dist_from_edge);
+	  
+	  _table[y][x]._alpha = (cos(ratio * MathNumbers::pi) + 1.0) / 2.0;
+	}
+      }
+    }
+  }
+}
+  
+LPoint2d MorphGrid::
+morph_point(const LPoint2d &p, TableType from, TableType to) {
+  if (is_empty()) {
+    return p;
+  }
+
+  if (_last_triangle != NULL) {
+    // First, check to see if the point is within the same triangle as
+    // the last point was.  This will save a bit of time if it is.
+    if (_last_triangle->contains_point(p, from)) {
+      return _last_triangle->morph_point(p, from, to);
+    }
+  }
+
+  // Nope, we just blew cache.  We'll have to look for the containing
+  // triangle the hard way.
+  assert(_tree != NULL);
+  _last_triangle = _tree->find_triangle(p, from);
+
+  if (_last_triangle == NULL) {
+    return p;
+  } else {
+    return _last_triangle->morph_point(p, from, to);
+  }
+}
+
+double MorphGrid::
+get_alpha(const LPoint2d &p, TableType from) {
+  if (is_empty()) {
+    return 1.0;
+  }
+
+  if (_last_triangle != NULL) {
+    // First, check to see if the point is within the same triangle as
+    // the last point was.  This will save a bit of time if it is.
+    if (_last_triangle->contains_point(p, from)) {
+      return _last_triangle->get_alpha(p, from);
+    }
+  }
+
+  // Nope, we just blew cache.  We'll have to look for the containing
+  // triangle the hard way.
+  assert(_tree != NULL);
+  _last_triangle = _tree->find_triangle(p, from);
+
+  if (_last_triangle == NULL) {
+    return 1.0;
+  } else {
+    return _last_triangle->get_alpha(p, from);
+  }
+}
+
+
+LPoint2d MorphGrid::
+morph_in(const LPoint2d &p) const {
+  return ((MorphGrid *)this)->morph_point(p, TT_out, TT_in);
+}
+
+LPoint2d MorphGrid::
+morph_out(const LPoint2d &p) const {
+  return ((MorphGrid *)this)->morph_point(p, TT_in, TT_out);
+}
+
+double MorphGrid::
+get_alpha(const LPoint2d &p) const {
+  return ((MorphGrid *)this)->get_alpha(p, TT_in);
+}
+
+void MorphGrid::
+count_dist_from_interior(int x, int y, int dist) {
+  if (x >= 0 && x < _x_verts &&
+      y >= 0 && y < _y_verts) {
+    Vertex &v = _table[y][x];
+    if (!v._over_another) {
+      // Here we are in the interior.
+      dist = 0;
+    }
+
+    if (v._dist_from_interior < 0 || dist < v._dist_from_interior) {
+      // Update this point, and recurse to our neighbors.
+      v._dist_from_interior = dist;
+
+      count_dist_from_interior(x + 1, y, dist + 1);
+      count_dist_from_interior(x - 1, y, dist + 1);
+      count_dist_from_interior(x, y + 1, dist + 1);
+      count_dist_from_interior(x, y - 1, dist + 1);
+    }
+  }
+}

+ 101 - 0
pandaapp/src/stitchbase/morphGrid.h

@@ -0,0 +1,101 @@
+// Filename: morphGrid.h
+// Created by:  drose (08Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef MORPHGRID_H
+#define MORPHGRID_H
+
+#include <luse.h>
+
+class MorphGrid {
+public:
+  MorphGrid();
+  ~MorphGrid();
+
+  enum TableType {
+    TT_in = 0,
+    TT_out = 1,
+    TT_num = 2
+  };
+
+  bool is_empty() const;
+  void clear();
+
+  void init(int x_verts, int y_verts);
+  void recompute();
+  void fill_alpha();
+
+  LPoint2d morph_point(const LPoint2d &p, TableType from, TableType to);
+  double get_alpha(const LPoint2d &p, TableType from);
+
+  LPoint2d morph_in(const LPoint2d &p) const;
+  LPoint2d morph_out(const LPoint2d &p) const;
+  double get_alpha(const LPoint2d &p) const;
+
+private:
+  class Triangle;
+public:
+
+  class Vertex {
+  public:
+    Vertex(const LPoint2d &p);
+
+    LPoint2d _p[TT_num];  // TT_in, TT_out
+
+    // These members are used to feather the edges of the images where
+    // they overlap other images.  Once the Stitcher sets the
+    // _over_another flags appropriately, MorphGrid::fill_alpha() will
+    // assign the alpha values to feather the edges.
+    double _alpha;
+    bool _over_another;
+    int _dist_from_interior;
+  };
+
+  int _x_verts, _y_verts;
+  typedef vector<Vertex> Row;
+  typedef vector<Row> Table;
+  Table _table;
+
+private:
+  void count_dist_from_interior(int x, int y, int dist);
+
+  class Triangle {
+  public:
+    Triangle(Vertex *v0, Vertex *v1, Vertex *v2);
+    bool contains_point(const LPoint2d &p, TableType from) const;
+    LPoint2d morph_point(const LPoint2d &p, TableType from, TableType to) const;
+    double get_alpha(const LPoint2d &p, TableType from) const;
+    void recompute();
+
+    Vertex *_v[3];
+    LPoint2d _min_p[TT_num], _max_p[TT_num];
+    LMatrix3d _mat[TT_num], _inv[TT_num];
+  };
+
+  typedef vector<Triangle> Triangles;
+  Triangles _triangles;
+  Triangle *_last_triangle;
+
+  class TriangleTree {
+  public:
+    TriangleTree(Triangle *a, Triangle *b);
+    TriangleTree(TriangleTree *a, TriangleTree *b);
+    ~TriangleTree();
+    void recompute();
+    Triangle *find_triangle(const LPoint2d &p, TableType from) const;
+
+    bool _has_tris;
+    union {
+      Triangle *_tri[2];
+      TriangleTree *_tree[2];
+    } _u;
+
+    LPoint2d _min_p[TT_num], _max_p[TT_num];
+  };
+
+  TriangleTree *_tree;
+  friend class TriangleTree;
+};
+
+#endif

+ 630 - 0
pandaapp/src/stitchbase/stitchCommand.cxx

@@ -0,0 +1,630 @@
+// Filename: stitchCommand.cxx
+// Created by:  drose (08Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchCommand.h"
+#include "stitchImage.h"
+#include "stitchLens.h"
+#include "stitchPerspectiveLens.h"
+#include "stitchFisheyeLens.h"
+#include "stitchCylindricalLens.h"
+#include "stitchPSphereLens.h"
+#include "stitchImageOutputter.h"
+#include "stitcher.h"
+
+#include <indent.h>
+#include <pnmImage.h>
+
+ostream &
+operator << (ostream &out, StitchCommand::Command c) {
+  switch (c) {
+  case StitchCommand::C_global:
+    return out << "global";
+    break;
+
+  case StitchCommand::C_define:
+    return out << "define";
+    break;
+
+  case StitchCommand::C_lens:
+    return out << "lens";
+    break;
+
+  case StitchCommand::C_input_image:
+    return out << "input_image";
+    break;
+
+  case StitchCommand::C_output_image:
+    return out << "output_image";
+    break;
+
+  case StitchCommand::C_perspective:
+    return out << "perspective";
+    break;
+
+  case StitchCommand::C_fisheye:
+    return out << "fisheye";
+    break;
+
+  case StitchCommand::C_cylindrical:
+    return out << "cylindrical";
+    break;
+
+  case StitchCommand::C_psphere:
+    return out << "psphere";
+    break;
+
+  case StitchCommand::C_focal_length:
+    return out << "focal_length";
+    break;
+
+  case StitchCommand::C_fov:
+    return out << "fov";
+    break;
+
+  case StitchCommand::C_singularity_tolerance:
+    return out << "singularity_tolerance";
+    break;
+
+  case StitchCommand::C_resolution:
+    return out << "resolution";
+    break;
+
+  case StitchCommand::C_filename:
+    return out << "filename";
+    break;
+
+  case StitchCommand::C_point2d:
+  case StitchCommand::C_point3d:
+    return out << "point";
+    break;
+
+  case StitchCommand::C_show_points:
+    return out << "show_points";
+    break;
+
+  case StitchCommand::C_image_size:
+    return out << "image_size";
+    break;
+
+  case StitchCommand::C_film_size:
+    return out << "film_size";
+    break;
+
+  case StitchCommand::C_grid:
+    return out << "grid";
+    break;
+
+  case StitchCommand::C_untextured_color:
+    return out << "untextured_color";
+    break;
+
+  case StitchCommand::C_hpr:
+    return out << "hpr";
+    break;
+
+  case StitchCommand::C_layers:
+    return out << "layers";
+    break;
+
+  case StitchCommand::C_stitch:
+    return out << "stitch";
+    break;
+
+  case StitchCommand::C_using:
+    return out << "using";
+    break;
+
+  case StitchCommand::C_user_command:
+    return out << "user_command";
+    break;
+   
+  default:
+    return out << "(**unknown command**)";
+  }
+}
+
+StitchCommand::
+StitchCommand(StitchCommand *parent, StitchCommand::Command command) :
+  _parent(parent),
+  _command(command)
+{
+  _params = 0;
+  _lens = NULL;
+  if (parent != NULL) {
+    parent->add_nested(this);
+  }
+}
+
+StitchCommand::
+~StitchCommand() {
+  Commands::iterator ci;
+  for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+    delete (*ci);
+  }
+}
+
+void StitchCommand::
+clear() {
+  Commands::iterator ci;
+  for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+    delete (*ci);
+  }
+  _nested.clear();
+  _using.clear();
+  _params = 0;
+  _command = C_global;
+}
+
+void StitchCommand::
+set_name(const string &name) {
+  if (!name.empty()) {
+    _params |= P_name;
+    _name = name;
+  }
+}
+
+void StitchCommand::
+set_length(double number) {
+  _params |= P_length;
+  _number = number;
+}
+
+void StitchCommand::
+set_resolution(double number) {
+  _params |= P_resolution;
+  _number = number;
+}
+
+void StitchCommand::
+set_number(double number) {
+  _params |= P_number;
+  _number = number;
+}
+
+void StitchCommand::
+set_point2d(const LVecBase2d &point) {
+  _params |= P_point2d;
+  _n[0] = point[0];
+  _n[1] = point[1];
+}
+
+void StitchCommand::
+set_point3d(const LVecBase3d &point) {
+  _params |= P_point3d;
+  _n[0] = point[0];
+  _n[1] = point[1];
+  _n[2] = point[2];
+}
+
+void StitchCommand::
+set_length_pair(const LVecBase2d &length_pair) {
+  _params |= P_length_pair;
+  _n[0] = length_pair[0];
+  _n[1] = length_pair[1];
+}
+
+void StitchCommand::
+set_color(const Colord &color) {
+  _params |= P_color;
+  _n[0] = color[0];
+  _n[1] = color[1];
+  _n[2] = color[2];
+  _n[3] = color[3];
+}
+
+void StitchCommand::
+set_str(const string &str) {
+  _params |= P_str;
+  _str = str;
+}
+
+bool StitchCommand::
+add_using(const string &name) {
+  StitchCommand *def = find_definition(name);
+  if (def != NULL) {
+    _params |= P_using;
+    _using.push_back(def);
+    return true;
+  }
+  return false;
+}
+
+void StitchCommand::
+add_nested(StitchCommand *nested) {
+  _params |= P_nested;
+  _nested.push_back(nested);
+}
+
+string StitchCommand::
+get_name() const {
+  return _name;
+}
+
+double StitchCommand::
+get_number() const {
+  return _number;
+}
+
+LVecBase2d StitchCommand::
+get_point2d() const {
+  return LVecBase2d(_n[0], _n[1]);
+}
+
+LVecBase3d StitchCommand::
+get_point3d() const {
+  return LVecBase3d(_n[0], _n[1], _n[2]);
+}
+
+LVector3d StitchCommand::
+get_vector3d() const {
+  return LVector3d(_n[0], _n[1], _n[2]);
+}
+
+Colord StitchCommand::
+get_color() const {
+  return Colord(_n[0], _n[1], _n[2], _n[3]);
+}
+
+string StitchCommand::
+get_str() const {
+  return _str;
+}
+
+void StitchCommand::
+process(StitchImageOutputter &outputter, Stitcher *stitcher,
+	StitchFile &file) {
+  if (_command == C_input_image) {
+    StitchImage *image = create_image();
+
+    if (stitcher != NULL) {
+      stitcher->add_image(image);
+    } else {
+      outputter.add_input_image(image);
+    }
+
+  } else if (_command == C_output_image) {
+    StitchImage *image = create_image();
+    outputter.add_output_image(image);
+
+  } else if (_command == C_stitch) {
+    Stitcher *new_stitcher = new Stitcher;
+    Commands::const_iterator ci;
+    for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+      (*ci)->process(outputter, new_stitcher, file);
+    }
+    new_stitcher->stitch();
+
+    // Now add all of the stitched images to the outputter, in order.
+    Stitcher::Images::const_iterator ii;
+    for (ii = new_stitcher->_placed.begin();
+	 ii != new_stitcher->_placed.end();
+	 ++ii) {
+      outputter.add_input_image(*ii);
+    }
+    outputter.add_stitcher(new_stitcher);
+
+  } else if (_command == C_point3d) {
+    if (stitcher != NULL) {
+      stitcher->add_point(_name, get_vector3d());
+    }
+
+  } else if (_command == C_show_points) {
+    if (stitcher != NULL) {
+      stitcher->show_points(get_number(), get_color());
+    }
+
+  } else if (_params & P_nested) {
+    Commands::const_iterator ci;
+    for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+      (*ci)->process(outputter, stitcher, file);
+    }
+  }
+}
+
+void StitchCommand::
+write(ostream &out, int indent_level) const {
+  if (_command == C_user_command) {
+    assert(_using.size() == 1);
+    indent(out, indent_level) << _using.front()->_name << ";\n";
+
+  } else if (_command == C_global) {
+    Commands::const_iterator ci;
+    for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+      (*ci)->write(out, indent_level );
+    }
+
+  } else {
+    indent(out, indent_level) << _command;
+    if (_params & P_name) {
+      out << " " << _name;
+    }
+    if (_params & P_length) {
+      out << " " << get_number() << "mm";
+    }
+    if (_params & P_resolution) {
+      out << " " << get_number() << "p/mm";
+    }
+    if (_params & P_number) {
+      out << " " << get_number();
+    }
+    if (_params & P_point2d) {
+      out << " (" << get_point2d() << ")";
+    }
+    if (_params & P_point3d) {
+      out << " (" << get_point3d() << ")";
+    }
+    if (_params & P_length_pair) {
+      out << " (" << _n[0] << "mm " << _n[1] << "mm)";
+    }
+    if (_params & P_color) {
+      out << " (" << get_color() << ")";
+    }
+    if (_params & P_str) {
+      out << " \"" << _str << "\"";
+    }
+    if (_params & P_using) {
+      Commands::const_iterator ci;
+      ci = _using.begin();
+      if (ci != _using.end()) {
+	out << " " << (*ci)->_name;
+	++ci;
+	while (ci != _using.end()) {
+	  out << ", " << (*ci)->_name;
+	  ++ci;
+	}
+      }
+    }
+    if (_params & P_nested) {
+      out << " {\n";
+      Commands::const_iterator ci;
+      for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+	(*ci)->write(out, indent_level + 2);
+      }
+      indent(out, indent_level) << "}\n";
+    } else {
+      out << ";\n";
+    }
+  }
+}
+
+
+
+StitchCommand *StitchCommand::
+find_definition(const string &name) {
+  Commands::const_iterator ci;
+  for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+    if (((*ci)->_command == C_define || (*ci)->_command == C_lens) && 
+	(*ci)->_name == name) {
+      return (*ci);
+    }
+  }
+  if (_parent != NULL) {
+    return _parent->find_definition(name);
+  }
+  return NULL;
+}
+
+StitchLens *StitchCommand::
+find_using_lens() {
+  if (!_using.empty()) {
+    Commands::const_iterator ci;
+    for (ci = _using.begin(); ci != _using.end(); ++ci) {
+      StitchLens *lens = (*ci)->find_lens();
+      if (lens != NULL) {
+	return lens;
+      }
+    }
+  }
+  if (_parent != NULL) {
+    return _parent->find_using_lens();
+  }
+  return NULL;
+}
+
+StitchLens *StitchCommand::
+find_lens() {
+  if (_command == C_lens) {
+    return make_lens();
+  }
+  Commands::const_iterator ci;
+  for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+    if ((*ci)->_command == C_lens) {
+      return (*ci)->make_lens();
+    }    
+  }
+  if (_parent != NULL) {
+    return _parent->find_using_lens();
+  }
+  return NULL;
+}
+
+StitchLens *StitchCommand::
+make_lens() {
+  if (_lens != NULL) {
+    return _lens;
+  }
+
+  if (find_command(C_fisheye) != NULL) {
+    _lens = new StitchFisheyeLens();
+  } else if (find_command(C_cylindrical) != NULL) {
+    _lens = new StitchCylindricalLens();
+  } else if (find_command(C_psphere) != NULL) {
+    _lens = new StitchPSphereLens();
+  } else {
+    _lens = new StitchPerspectiveLens();
+  }
+
+  StitchCommand *cmd = find_command(C_focal_length);
+  if (cmd != NULL) {
+    _lens->set_focal_length(cmd->get_number());
+  }
+  cmd = find_command(C_fov);
+  if (cmd != NULL) {
+    _lens->set_hfov(cmd->get_number());
+  }
+
+  if (!_lens->is_defined()) {
+    _lens->set_hfov(60.0);
+  }
+
+  Commands::const_iterator ci;
+  for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+    switch ((*ci)->_command) {
+    case C_singularity_tolerance:
+      _lens->set_singularity_tolerance((*ci)->get_number());
+      break;
+
+    default:
+      break;
+    }
+  }
+
+  return _lens;
+}
+
+
+StitchCommand *StitchCommand::
+find_using_command(Command command) {
+  if (!_using.empty()) {
+    Commands::const_iterator ci;
+    for (ci = _using.begin(); ci != _using.end(); ++ci) {
+      StitchCommand *cmd = (*ci)->find_command(command);
+      if (cmd != NULL) {
+	return cmd;
+      }
+    }
+  }
+
+  if (_parent != NULL) {
+    return _parent->find_using_command(command);
+  }
+
+  return NULL;
+}
+
+StitchCommand *StitchCommand::
+find_command(Command command) {
+  Commands::const_iterator ci;
+  for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+    if ((*ci)->_command == command) {
+      return (*ci);
+    }
+  }
+
+  if (_parent != NULL) {
+    return _parent->find_using_command(command);
+  }
+
+  return NULL;
+}
+
+string StitchCommand::
+find_parameter(Command command, const string &dflt) {
+  StitchCommand *cmd = find_command(command);
+  if (cmd != NULL) {
+    return cmd->_str;
+  } else {
+    return dflt;
+  }
+}
+
+double StitchCommand::
+find_parameter(Command command, double dflt) {
+  StitchCommand *cmd = find_command(command);
+  if (cmd != NULL) {
+    return cmd->get_number();
+  } else {
+    return dflt;
+  }
+}
+
+LVecBase2d StitchCommand::
+find_parameter(Command command, const LVecBase2d &dflt) {
+  StitchCommand *cmd = find_command(command);
+  if (cmd != NULL) {
+    return cmd->get_point2d();
+  } else {
+    return dflt;
+  }
+}
+
+
+StitchImage *StitchCommand::
+create_image() {
+  string filename = find_parameter(C_filename, "");
+  LVecBase2d size_pixels(256, 256);
+  LVecBase2d resolution(72.0 / 25.4, 72.0 / 25.4);
+  StitchLens *lens = find_lens();
+  if (lens == NULL) {
+    nout << "Warning: No lens defined for " << filename << "\n";
+    lens = make_lens();
+  }
+  
+  StitchCommand *cmd;
+  cmd = find_command(C_image_size);
+  if (cmd != NULL) {
+    size_pixels = cmd->get_point2d();
+
+  } else if (!filename.empty()) {
+    // If we don't get an explicit image size, try to determine it
+    // from the image file.
+    PNMImageHeader header;
+    if (header.read_header(filename)) {
+      size_pixels.set(header.get_x_size(), header.get_y_size());
+    }
+  }
+
+  cmd = find_command(C_film_size);
+  if (cmd != NULL) {
+    LVecBase2d size_mm = cmd->get_point2d();
+    resolution.set((size_pixels[0]-1) / size_mm[0],
+		   (size_pixels[1]-1) / size_mm[1]);
+  } else {
+    cmd = find_command(C_resolution);
+    if (cmd != NULL) {
+      resolution.set(cmd->get_number(), cmd->get_number());
+    }
+  }
+
+  StitchImage *image = 
+    new StitchImage(get_name(), filename, lens, size_pixels, resolution);
+  image->setup_grid(50, 50);
+
+  // Also look for points and other stuff.
+  Commands::const_iterator ci;
+  for (ci = _nested.begin(); ci != _nested.end(); ++ci) {
+    switch ((*ci)->_command) {
+    case C_point2d:
+      image->add_point((*ci)->_name, (*ci)->get_point2d());
+      break;
+
+    case C_show_points:
+      image->show_points((*ci)->get_number(), (*ci)->get_color());
+      break;
+
+    case C_untextured_color:
+      image->_untextured_color = (*ci)->get_color();
+      break;
+
+    case C_hpr:
+      image->set_hpr((*ci)->get_point3d());
+      break;
+
+    case C_layers:
+      image->_layered_type = StitchImage::LT_separate;
+      break;
+
+    case C_grid:
+      image->setup_grid((int)(*ci)->_n[0], (int)(*ci)->_n[1]);
+      break;
+
+    default:
+      break;
+    }
+  }
+
+  return image;
+}
+

+ 141 - 0
pandaapp/src/stitchbase/stitchCommand.h

@@ -0,0 +1,141 @@
+// Filename: stitchCommand.h
+// Created by:  drose (08Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHCOMMAND_H
+#define STITCHCOMMAND_H
+
+#include <pandatoolbase.h>
+
+#include <luse.h>
+
+#include <vector>
+#include <map>
+
+class StitchLens;
+class StitchImage;
+class StitchImageOutputter;
+class StitchFile;
+class Stitcher;
+
+class StitchCommand {
+public:
+  enum Command {
+    C_global,
+    C_define,
+    C_lens,
+    C_input_image,
+    C_output_image,
+    C_perspective,
+    C_fisheye,
+    C_cylindrical,
+    C_psphere,
+    C_focal_length,
+    C_fov,
+    C_singularity_tolerance,
+    C_resolution,
+    C_filename,
+    C_point2d,
+    C_point3d,
+    C_show_points,
+    C_image_size,
+    C_film_size,
+    C_grid,
+    C_untextured_color,
+    C_hpr,
+    C_layers,
+    C_stitch,
+    C_using,
+    C_user_command,
+  };
+
+  StitchCommand(StitchCommand *parent = NULL, 
+		Command command = C_global);
+  ~StitchCommand();
+
+  void clear();
+
+  void set_name(const string &name);
+  void set_length(double number);
+  void set_resolution(double number);
+  void set_number(double number);
+  void set_point2d(const LVecBase2d &point);
+  void set_point3d(const LVecBase3d &point);
+  void set_length_pair(const LVecBase2d &point);
+  void set_color(const Colord &color);
+  void set_str(const string &str);
+  bool add_using(const string &name);
+  void add_nested(StitchCommand *nested);
+
+  string get_name() const;
+  double get_number() const;
+  LVecBase2d get_point2d() const;
+  LVecBase3d get_point3d() const;
+  LVector3d get_vector3d() const;
+  Colord get_color() const;
+  string get_str() const;
+
+  StitchCommand *find_definition(const string &name);
+
+  void process(StitchImageOutputter &outputter, Stitcher *stitcher,
+	       StitchFile &file);
+
+  void write(ostream &out, int indent) const;
+
+
+private:
+  StitchLens *find_using_lens();
+  StitchLens *find_lens();
+  StitchLens *make_lens();
+
+  StitchCommand *find_using_command(Command command);
+  StitchCommand *find_command(Command command);
+  string find_parameter(Command command, const string &dflt);
+  double find_parameter(Command command, double dflt);
+  LVecBase2d find_parameter(Command command, const LVecBase2d &dflt);
+
+  StitchImage *create_image();
+
+
+  StitchCommand *_parent;
+  Command _command;
+
+  enum Parameters {
+    P_name        = 0x001,
+    P_length      = 0x002,
+    P_resolution  = 0x004,
+    P_number      = 0x008,
+    P_point2d     = 0x010,
+    P_point3d     = 0x020,
+    P_length_pair = 0x040,
+    P_color       = 0x080,
+    P_str         = 0x100,
+    P_using       = 0x200,
+    P_nested      = 0x400
+  };
+
+  int _params;
+
+  string _name;
+  double _number;
+  double _n[4];
+  string _str;
+
+  // This will only get filled in by make_lens().
+  StitchLens *_lens;
+
+  typedef vector<StitchCommand *> Commands;
+  Commands _using;
+  Commands _nested;  
+};
+
+inline ostream &operator << (ostream &out, const StitchCommand &c) {
+  c.write(out, 0);
+  return out;
+}
+
+ostream &operator << (ostream &out, StitchCommand::Command c);
+
+
+#endif

+ 39 - 0
pandaapp/src/stitchbase/stitchCommandReader.cxx

@@ -0,0 +1,39 @@
+// Filename: stitchCommandReader.cxx
+// Created by:  drose (16Mar00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchCommandReader.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: StitchCommandReader::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+StitchCommandReader::
+StitchCommandReader() {
+  clear_runlines();
+  add_runline("[opts] input.st");
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: StitchCommandReader::handle_args
+//       Access: Protected, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool StitchCommandReader::
+handle_args(ProgramBase::Args &args) {
+  if (args.empty()) {
+    nout << "You must specify the stitch command file to read on the\n"
+	 << "command line.\n";
+    return false;
+  }
+  if (args.size() > 1) {
+    nout << "You must specify only one stitch command file to read on the\n"
+	 << "command line.\n";
+    return false;
+  }
+
+  return _command_file.read(args[0]);
+}

+ 34 - 0
pandaapp/src/stitchbase/stitchCommandReader.h

@@ -0,0 +1,34 @@
+// Filename: stitchCommandReader.h
+// Created by:  drose (16Mar00)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHCOMMANDREADER_H
+#define STITCHCOMMANDREADER_H
+
+#include <pandatoolbase.h>
+
+#include "stitchFile.h"
+
+#include <programBase.h>
+
+////////////////////////////////////////////////////////////////////
+// 	 Class : StitchCommandReader
+// Description : This specialization of ProgramBase is intended for
+//               programs in this directory that read and process a
+//               stitch command file.
+//////////////////////////////////////////////////////////////////////
+class StitchCommandReader : public ProgramBase {
+public:
+  StitchCommandReader();
+
+protected:
+  virtual bool handle_args(Args &args);
+
+protected:
+  StitchFile _command_file;
+};
+
+#endif
+
+

+ 182 - 0
pandaapp/src/stitchbase/stitchCylindricalLens.cxx

@@ -0,0 +1,182 @@
+// Filename: stitchCylindricalLens.cxx
+// Created by:  drose (09Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchCylindricalLens.h"
+#include "stitchCommand.h"
+#include "triangleRasterizer.h"
+
+#include <pandatoolbase.h>
+#include <deg_2_rad.h>
+
+#include <math.h>
+
+// This is the focal-length constant for fisheye lenses.  See
+// stitchFisheyeLens.h.
+static const double k = 60.0;
+
+StitchCylindricalLens::
+StitchCylindricalLens() {
+}
+
+double StitchCylindricalLens::
+get_focal_length(double width_mm) const {
+  if (_flags & F_focal_length) {
+    return _focal_length;
+  }
+  if (_flags & F_fov) {
+    return width_mm * k / _fov;
+  }
+  return 0.0;
+}
+
+double StitchCylindricalLens::
+get_hfov(double width_mm) const {
+  if (_flags & F_fov) {
+    return _fov;
+  }
+  if (_flags & F_focal_length) {
+    return width_mm * k / _focal_length;
+  }
+  return 0.0;
+}
+
+double StitchCylindricalLens::
+get_vfov(double height_mm) const {
+  return 2.0 * rad_2_deg(atan(height_mm / 
+			      (2.0 * get_focal_length(height_mm))));
+}
+
+LVector3d StitchCylindricalLens::
+extrude(const LPoint2d &point_mm, double width_mm) const {
+  LVector2d v2 = point_mm;
+
+  double fl = get_focal_length(width_mm);
+  return LVector3d(sin(deg_2_rad(v2[0] * k / fl)) * fl,
+		   cos(deg_2_rad(v2[0] * k / fl)) * fl,
+		   v2[1]);
+}
+
+
+LPoint2d StitchCylindricalLens::
+project(const LVector3d &vec, double width_mm) const {
+  // A cylindrical lens is a cross between a fisheye and a normal
+  // lens.  It is curved in the horizontal direction, and straight in
+  // the vertical direction.
+
+  LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right);
+  
+  // To compute the x position on the frame, we only need to consider
+  // the angle of the vector about the Z axis.  Project the vector
+  // into the XY plane to do this.
+
+  LVector2d xy(v3[0], v3[1]);
+
+  // The x position is the angle about the Z axis.
+  double x =
+    rad_2_deg(atan2(xy[0], xy[1])) * get_focal_length(width_mm) / k;
+  
+  // The y position is the Z height divided by the perspective
+  // distance.
+  double y = v3[2] / length(xy) * get_focal_length(width_mm);
+
+  return LPoint2d(x, y);
+}
+
+LPoint2d StitchCylindricalLens::
+project_left(const LVector3d &vec, double width_mm) const {
+  // This is just like project(), except that if the vertex extends
+  // below -180 degrees, it remains on the left side of the film
+  // (instead of wrapping around to the right side).
+
+  LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right);
+  LVector2d xy(v3[0], v3[1]);
+  double x =
+    (rad_2_deg(atan2(-xy[0], -xy[1])) - 180.0) *
+    get_focal_length(width_mm) / k;
+  
+  double y = v3[2] / length(xy) * get_focal_length(width_mm);
+  return LPoint2d(x, y);
+}
+
+LPoint2d StitchCylindricalLens::
+project_right(const LVector3d &vec, double width_mm) const {
+  // This is just like project(), except that if the vertex extends
+  // above 180 degrees, it remains on the right side of the film
+  // (instead of wrapping around to the left side).
+
+  LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right);
+  LVector2d xy(v3[0], v3[1]);
+  double x =
+    (rad_2_deg(atan2(-xy[0], -xy[1])) + 180.0) *
+    get_focal_length(width_mm) / k;
+  
+  double y = v3[2] / length(xy) * get_focal_length(width_mm);
+  return LPoint2d(x, y);
+}
+
+void StitchCylindricalLens::
+draw_triangle(TriangleRasterizer &rast, const LMatrix3d &mm_to_pixels,
+	      double width_mm, const RasterizerVertex *v0,
+	      const RasterizerVertex *v1, const RasterizerVertex *v2) {
+  // A cylindrical lens has a seam at 180 and -180 degrees (regardless
+  // of its field of view).  If the triangle crosses that seam, we'll
+  // simply draw it twice: once at each side.
+
+  // Determine which quadrant each of the vertices is in.  The
+  // triangle crosses the seam if no vertices are in quadrants I and
+  // II, and some vertices are in quadrant III and others are in
+  // quadrant IV.
+
+  LVector2d xy0(dot(v0->_space, LVector3d::right()),
+		dot(v0->_space, LVector3d::forward()));
+  LVector2d xy1(dot(v1->_space, LVector3d::right()),
+		dot(v1->_space, LVector3d::forward()));
+  LVector2d xy2(dot(v2->_space, LVector3d::right()),
+		dot(v2->_space, LVector3d::forward()));
+
+  if (xy0[1] >= 0.0 || xy1[1] >= 0.0 || xy2[1] >= 0.0) {
+    // Some vertices are in quadrants I or II.
+    rast.draw_triangle(v0, v1, v2);
+
+  } else if (xy0[0] > 0.0 && xy1[0] > 0.0 && xy2[0] > 0.0) {
+    // All vertices are in quadrant IV.
+    rast.draw_triangle(v0, v1, v2);
+
+  } else if (xy0[0] < 0.0 && xy1[0] < 0.0 && xy2[0] < 0.0) {
+    // All vertices are in quadrant III.
+    rast.draw_triangle(v0, v1, v2);
+
+  } else {
+    // The triangle crosses the seam.  Draw it twice.
+    RasterizerVertex v0a = *v0;
+    RasterizerVertex v1a = *v1;
+    RasterizerVertex v2a = *v2;
+
+    v0a._p = project_left(v0a._space, width_mm) * mm_to_pixels;
+    v1a._p = project_left(v1a._space, width_mm) * mm_to_pixels;
+    v2a._p = project_left(v2a._space, width_mm) * mm_to_pixels;
+    rast.draw_triangle(&v0a, &v1a, &v2a);
+
+    v0a._p = project_right(v0a._space, width_mm) * mm_to_pixels;
+    v1a._p = project_right(v1a._space, width_mm) * mm_to_pixels;
+    v2a._p = project_right(v2a._space, width_mm) * mm_to_pixels;
+    rast.draw_triangle(&v0a, &v1a, &v2a);
+  }
+}
+
+void StitchCylindricalLens::
+make_lens_command(StitchCommand *parent) {
+  StitchCommand *lens_cmd = new StitchCommand(parent, StitchCommand::C_lens);
+  StitchCommand *cmd;
+  cmd = new StitchCommand(lens_cmd, StitchCommand::C_cylindrical);
+  if (_flags & F_focal_length) {
+    cmd = new StitchCommand(lens_cmd, StitchCommand::C_focal_length);
+    cmd->set_length(_focal_length);
+  }
+  if (_flags & F_fov) {
+    cmd = new StitchCommand(lens_cmd, StitchCommand::C_fov);
+    cmd->set_number(_fov);
+  }
+}

+ 37 - 0
pandaapp/src/stitchbase/stitchCylindricalLens.h

@@ -0,0 +1,37 @@
+// Filename: stitchCylindricalLens.h
+// Created by:  drose (09Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHCYLINDRICALLENS_H
+#define STITCHCYLINDRICALLENS_H
+
+#include "stitchLens.h"
+
+class StitchCylindricalLens : public StitchLens {
+public:
+  StitchCylindricalLens();
+
+  virtual double get_focal_length(double width_mm) const;
+  virtual double get_hfov(double width_mm) const;
+  virtual double get_vfov(double height_mm) const;
+
+  virtual LVector3d extrude(const LPoint2d &point_mm, double width_mm) const;
+  virtual LPoint2d project(const LVector3d &vec, double width_mm) const; 
+
+  LPoint2d project_left(const LVector3d &vec, double width_mm) const; 
+  LPoint2d project_right(const LVector3d &vec, double width_mm) const; 
+
+  virtual void draw_triangle(TriangleRasterizer &rast,
+			     const LMatrix3d &mm_to_pixels,
+			     double width_mm,
+			     const RasterizerVertex *v0, 
+			     const RasterizerVertex *v1, 
+			     const RasterizerVertex *v2);
+
+  virtual void make_lens_command(StitchCommand *parent);
+};
+
+#endif
+
+

+ 47 - 0
pandaapp/src/stitchbase/stitchFile.cxx

@@ -0,0 +1,47 @@
+// Filename: stitchFile.cxx
+// Created by:  drose (08Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchFile.h"
+#include "stitchImage.h"
+#include "stitchImageOutputter.h"
+#include "stitchParserDefs.h"
+#include "stitchLexerDefs.h"
+
+StitchFile::
+StitchFile() {
+}
+
+StitchFile::
+~StitchFile() {
+}
+
+bool StitchFile::
+read(const string &filename) {
+  _root.clear();
+
+  ifstream in(filename.c_str());
+  if (!in) {
+    nout << "Unable to read " << filename << "\n";
+    return false;
+  }
+  stitch_init_parser(in, filename, &_root);
+  stitchyyparse();
+  if (stitch_error_count() != 0) {
+    return false;
+  }
+
+  return true;
+}
+
+void StitchFile::
+write(ostream &out) const {
+  _root.write(out, 0);
+}
+
+void StitchFile::
+process(StitchImageOutputter &outputter) {
+  _root.process(outputter, (Stitcher *)NULL, *this);
+  outputter.execute();
+}  

+ 34 - 0
pandaapp/src/stitchbase/stitchFile.h

@@ -0,0 +1,34 @@
+// Filename: stitchFile.h
+// Created by:  drose (08Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHFILE_H
+#define STITCHFILE_H
+
+#include "stitchCommand.h"
+
+class StitchImage;
+class StitchImageOutputter;
+
+class StitchFile {
+public:
+  StitchFile();
+  ~StitchFile();
+
+  bool read(const string &filename);
+  void write(ostream &out) const;
+
+  void process(StitchImageOutputter &outputter);
+
+  StitchCommand _root;
+};
+
+inline ostream &operator << (ostream &out, const StitchFile &f) {
+  f.write(out);
+  return out;
+}
+
+#endif
+
+  

+ 314 - 0
pandaapp/src/stitchbase/stitchFisheyeLens.cxx

@@ -0,0 +1,314 @@
+// Filename: stitchFisheyeLens.cxx
+// Created by:  drose (09Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchFisheyeLens.h"
+#include "stitchImage.h"
+#include "stitchCommand.h"
+#include "triangleRasterizer.h"
+#include "triangle.h"
+
+#include <pandatoolbase.h>
+#include <deg_2_rad.h>
+
+#include <math.h>
+
+// This is the focal-length constant for fisheye lenses.  The focal
+// length of a fisheye lens relates to its fov by the equation:
+
+//   w = Fd/k
+
+// Where w is the width of the negative, F is the focal length, and d
+// is the total field of view in degrees.
+
+// k is chosen here by simple examination of a couple of actual lenses
+// for 35mm film.  Don't know how well this extends to other lenses
+// and other negative sizes.
+
+static const double k = 60.0;
+
+StitchFisheyeLens::
+StitchFisheyeLens() {
+}
+
+double StitchFisheyeLens::
+get_focal_length(double width_mm) const {
+  if (_flags & F_focal_length) {
+    return _focal_length;
+  }
+  if (_flags & F_fov) {
+    return width_mm * k / _fov;
+  }
+  return 0.0;
+}
+
+double StitchFisheyeLens::
+get_hfov(double width_mm) const {
+  if (_flags & F_fov) {
+    return _fov;
+  }
+  if (_flags & F_focal_length) {
+    return width_mm * k / _focal_length;
+  }
+  return 0.0;
+}
+
+LVector3d StitchFisheyeLens::
+extrude(const LPoint2d &point_mm, double width_mm) const {
+  // This operation is essentially a conversion from Cartesian to
+  // polar coordinates.
+
+  // First, get the vector from the center of the film to the point,
+  // and normalize it.
+  LVector2d v2 = point_mm;
+
+  double r = length(v2);
+  if (r == 0.0) {
+    // Special case: directly forward.
+    return LVector3d::forward();
+  }
+
+  v2 /= r;
+      
+  // Now get the point r units around the circle in the YZ plane.
+  double dist = r * k / get_focal_length(width_mm);
+  LVector3d p(0.0, cos(deg_2_rad(dist)), sin(deg_2_rad(dist)));
+  
+  // And rotate this point around the Y axis.
+  LVector3d result = LVector3d::rfu(p[0]*v2[1] + p[2]*v2[0],
+				    p[1],
+				    p[2]*v2[1] - p[0]*v2[0]);
+  return result;
+}
+
+LPoint2d StitchFisheyeLens::
+project(const LVector3d &vec, double width_mm) const {
+  // A fisheye lens projection has the property that the distance from
+  // the center point to any other point on the projection is
+  // proportional to the actual distance on the sphere along the great
+  // circle.  Also, the angle to the point on the projection is equal
+  // to the angle to the point on the sphere.
+    
+  // First, discard the distance by normalizing the vector.
+  LVector3d v2 = normalize(vec * LMatrix4d::convert_mat(CS_default,
+							CS_zup_right));
+    
+  // Now, project the point into the XZ plane and measure its angle
+  // to the Z axis.  This is the same angle it will have to the
+  // vertical axis on the film.
+  LVector2d y(v2[0], v2[2]);
+  y = normalize(y);
+    
+  if (y == LVector2d(0.0, 0.0)) {
+    // Special case.  This point is either directly ahead or directly
+    // behind.
+    return LPoint2d(0.0, 0.0);
+  }
+  
+  // Now bring the vector into the YZ plane by rotating about the Y
+  // axis.
+  LVector2d x(v2[1], v2[0]*y[0]+v2[2]*y[1]);
+  x = normalize(x);
+    
+  // Now the angle of x to the forward vector represents the distance
+  // along the great circle to the point.
+  double r = 90.0 - rad_2_deg(atan2(x[0], x[1]));
+    
+  return y * (r * get_focal_length(width_mm) / k);
+}
+
+void StitchFisheyeLens::
+draw_triangle(TriangleRasterizer &rast, const LMatrix3d &,
+	      double, const RasterizerVertex *v0,
+	      const RasterizerVertex *v1, const RasterizerVertex *v2) {
+  // A fisheye lens has a singularity at 180 degrees--this point maps
+  // to the entire outer rim of the circle.  Near this singularity,
+  // small distances in space map to very large distances on the film,
+  // meaning that our use of triangles to approximate curvature
+  // becomes very bad near the singularity.  Furthermore, triangles
+  // that cross the singularity will be incorrectly drawn across the
+  // entire image on the film.
+
+  // We resolve this by simply not drawing any triangles that come
+  // with a user-specified angle (the _singularity_tolerance) from the
+  // singularity point.
+
+  
+  // Determine which quadrant each of the vertices is in.  The
+  // triangle crosses the singularity if all vertices' y coordinate is
+  // negative, and if the projection of the triangle into the x, z
+  // plane intersects the origin.  It comes within
+  // _singularity_tolerance of the singularity if the projection into
+  // x, z intersects a circle about the origin with radius
+  // _singularity_radius.
+
+  if (dot(v0->_space, LVector3d::forward()) < 0.0 &&
+      dot(v1->_space, LVector3d::forward()) < 0.0 &&
+      dot(v2->_space, LVector3d::forward()) < 0.0) {
+    LPoint2d xz0(dot(v0->_space, LVector3d::right()),
+		 dot(v0->_space, LVector3d::up()));
+    LPoint2d xz1(dot(v1->_space, LVector3d::right()),
+		 dot(v1->_space, LVector3d::up()));
+    LPoint2d xz2(dot(v2->_space, LVector3d::right()),
+		 dot(v2->_space, LVector3d::up()));
+
+    // This projection will reverse the vertex order.
+    if (triangle_contains_circle(LPoint2d(0.0, 0.0), 
+				 _singularity_radius,
+				 xz0, xz2, xz1)) {
+      // The triangle does cross the singularity!  Reject it.
+      /*
+      nout << "Rejecting:\n"
+	   << "   " << v0->_space << "\n"
+	   << "   " << v1->_space << "\n"
+	   << "   " << v2->_space << "\n\n";
+      */
+      _singularity_detected = 1;
+      return;
+    }
+  }
+
+  rast.draw_triangle(v0, v1, v2);
+}
+
+void StitchFisheyeLens::
+pick_up_singularity(TriangleRasterizer &rast, 
+		    const LMatrix3d &mm_to_pixels,
+		    const LMatrix3d &pixels_to_mm,
+		    const LMatrix3d &rotate,
+		    double width_mm, StitchImage *input) {
+  if (_singularity_detected) {
+    nout << "Picking up singularity\n";
+
+    // We will be drawing all the pixels between the circle
+    // representing points 180 degrees from forward, and the circle
+    // represent points (180 - _singularity_tolerance * 2) degrees
+    // from forward.
+
+    double outer_mm = 
+      (180 * get_focal_length(width_mm) / k);
+    double inner_mm = 
+      ((180 - _singularity_tolerance * 2) * get_focal_length(width_mm) / k);
+    
+    int xsize = rast._output->get_x_size();
+    int ysize = rast._output->get_y_size();
+
+    LPoint2d py = LPoint2d(0.0, outer_mm) * mm_to_pixels;
+    int top_y = max((int)floor(py[1]), 0);
+    py = LPoint2d(0.0, -outer_mm) * mm_to_pixels;
+    int bot_y = min((int)ceil(py[1]), ysize - 1);
+
+    py = LPoint2d(0.0, inner_mm) * mm_to_pixels;
+    int inner_top_y = (int)floor(py[1]);
+    py = LPoint2d(0.0, -inner_mm) * mm_to_pixels;
+    int inner_bot_y = (int)ceil(py[1]);
+
+    RasterizerVertex v0;
+    v0._p.set(0.0, 0.0);
+    v0._uv.set(0.0, 0.0);
+    v0._space.set(0.0, 0.0, 0.0);
+    v0._alpha = 1.0;
+    v0._visibility = 0;
+
+    int xi, yi;
+    for (yi = top_y; yi <= bot_y; yi++) {
+      int left_x_1, right_x_1;
+      int left_x_2, right_x_2;
+
+      // Where are the left and right X pixels at this slice?
+      if (yi <= inner_top_y) {
+	// This is the top slice of the ring: between the top of the
+	// outer circle and the top of the inner circle.
+	
+	LPoint2d pmm = LPoint2d(0.0, yi) * pixels_to_mm;
+	pmm[0] = sqrt(outer_mm * outer_mm - pmm[1] * pmm[1]);
+	
+	LPoint2d px = LPoint2d(-pmm[0], pmm[1]) * mm_to_pixels;
+	left_x_1 = max((int)floor(px[0]), 0);
+       	px = LPoint2d(pmm[0], pmm[1]) * mm_to_pixels;
+	right_x_1 = min((int)ceil(px[0]), xsize - 1);
+
+	right_x_2 = right_x_1;
+	left_x_2 = right_x_2 + 1;
+
+      } else if (yi < inner_bot_y) {
+	// This is the inner section: within the inner circle area.
+	// We have both a left and a right section here.
+	
+	LPoint2d pmm = LPoint2d(0.0, yi) * pixels_to_mm;
+	pmm[0] = sqrt(outer_mm * outer_mm - pmm[1] * pmm[1]);
+	
+	LPoint2d px = LPoint2d(-pmm[0], pmm[1]) * mm_to_pixels;
+	left_x_1 = max((int)floor(px[0]), 0);
+       	px = LPoint2d(pmm[0], pmm[1]) * mm_to_pixels;
+	right_x_2 = min((int)ceil(px[0]), xsize - 1);
+
+	pmm[0] = sqrt(inner_mm * inner_mm - pmm[1] * pmm[1]);
+	px = LPoint2d(-pmm[0], pmm[1]) * mm_to_pixels;
+	right_x_1 = max((int)floor(px[0]), 0);
+       	px = LPoint2d(pmm[0], pmm[1]) * mm_to_pixels;
+	left_x_2 = min((int)ceil(px[0]), xsize - 1);
+
+      } else {
+	// This is the bottom slice of the ring: between the bottom of
+	// the inner circle and the bottom of the outer circle.
+	
+	LPoint2d pmm = LPoint2d(0.0, yi) * pixels_to_mm;
+	pmm[0] = sqrt(outer_mm * outer_mm - pmm[1] * pmm[1]);
+	
+	LPoint2d px = LPoint2d(-pmm[0], pmm[1]) * mm_to_pixels;
+	left_x_1 = max((int)floor(px[0]), 0);
+       	px = LPoint2d(pmm[0], pmm[1]) * mm_to_pixels;
+	right_x_1 = min((int)ceil(px[0]), xsize - 1);
+
+	right_x_2 = right_x_1;
+	left_x_2 = right_x_2 + 1;
+
+      }
+
+      // Project xi point 1 to determine the radius.
+      v0._p.set(left_x_1 + 1, yi);
+      v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate;
+      v0._uv = input->project(v0._space);
+      
+      for (xi = left_x_1; xi <= right_x_1; xi++) {
+	double last_u = v0._uv[0];
+	
+	v0._p.set(xi, yi);
+	v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate;
+	v0._uv = input->project(v0._space);
+	rast.draw_pixel(&v0, fabs(v0._uv[0] - last_u));
+      }
+
+      // Project xi point 1 to determine the radius.
+      v0._p.set(left_x_2 + 1, yi);
+      v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate;
+      v0._uv = input->project(v0._space);
+      
+      for (xi = left_x_2; xi <= right_x_2; xi++) {
+	double last_u = v0._uv[0];
+	
+	v0._p.set(xi, yi);
+	v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate;
+	v0._uv = input->project(v0._space);
+	rast.draw_pixel(&v0, fabs(v0._uv[0] - last_u));
+      }
+    }
+  }
+}
+
+void StitchFisheyeLens::
+make_lens_command(StitchCommand *parent) {
+  StitchCommand *lens_cmd = new StitchCommand(parent, StitchCommand::C_lens);
+  StitchCommand *cmd;
+  cmd = new StitchCommand(lens_cmd, StitchCommand::C_fisheye);
+  if (_flags & F_focal_length) {
+    cmd = new StitchCommand(lens_cmd, StitchCommand::C_focal_length);
+    cmd->set_length(_focal_length);
+  }
+  if (_flags & F_fov) {
+    cmd = new StitchCommand(lens_cmd, StitchCommand::C_fov);
+    cmd->set_number(_fov);
+  }
+}

+ 39 - 0
pandaapp/src/stitchbase/stitchFisheyeLens.h

@@ -0,0 +1,39 @@
+// Filename: stitchFisheyeLens.h
+// Created by:  drose (09Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHFISHEYELENS_H
+#define STITCHFISHEYELENS_H
+
+#include "stitchLens.h"
+
+class StitchFisheyeLens : public StitchLens {
+public:
+  StitchFisheyeLens();
+
+  virtual double get_focal_length(double width_mm) const;
+  virtual double get_hfov(double width_mm) const;
+
+  virtual LVector3d extrude(const LPoint2d &point_mm, double width_mm) const;
+  virtual LPoint2d project(const LVector3d &vec, double width_mm) const; 
+
+  virtual void draw_triangle(TriangleRasterizer &rast,
+			     const LMatrix3d &mm_to_pixels,
+			     double width_mm,
+			     const RasterizerVertex *v0, 
+			     const RasterizerVertex *v1, 
+			     const RasterizerVertex *v2);
+
+  virtual void pick_up_singularity(TriangleRasterizer &rast, 
+				   const LMatrix3d &mm_to_pixels,
+				   const LMatrix3d &pixels_to_mm,
+				   const LMatrix3d &rotate,
+				   double width_mm,
+				   StitchImage *input);
+
+  virtual void make_lens_command(StitchCommand *parent);
+};
+
+#endif
+

+ 350 - 0
pandaapp/src/stitchbase/stitchImage.cxx

@@ -0,0 +1,350 @@
+// Filename: stitchImage.cxx
+// Created by:  drose (04Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchImage.h"
+#include "stitchLens.h"
+#include "layeredImage.h"
+
+#include <compose_matrix.h>
+#include <rotate_to.h>
+
+StitchImage::
+StitchImage(const string &name, const string &filename,
+	    StitchLens *lens, const LVecBase2d &size_pixels,
+	    const LVecBase2d &pixels_per_mm) :
+  _lens(lens),
+  _size_pixels(size_pixels),
+  _pixels_per_mm(pixels_per_mm),
+  _rotate(LMatrix3d::ident_mat()),
+  _inv_rotate(LMatrix3d::ident_mat()),
+  _filename(filename),
+  _name(name)
+{
+  _size_mm.set((_size_pixels[0] - 1.0) / _pixels_per_mm[0],
+	       (_size_pixels[1] - 1.0) / _pixels_per_mm[1]);
+
+  // There are several coordinate systems to talk about points on the
+  // image.
+
+  // UV's are used for doing most operations.  They range from (0, 0)
+  // at the lower-left corner to (1, 1) at the upper-right.
+  
+  // Pixels are used when interfacing with the user.  They range from
+  // (0, 0) at the upper-left corner to (_size_pixels[0] - 1,
+  // _size_pixels[1] - 1) at the lower-right.
+
+  // Millimeters are used when interfacing with the lens.  They start
+  // at (0, 0) at the center, and range from -size_mm at the
+  // lower-left, to size_mm at the upper-right.
+
+  LVector2d pixels_per_uv(_size_pixels[0] - 1.0, _size_pixels[1] - 1.0);
+
+  _pixels_to_uv = 
+    LMatrix3d::translate_mat(LVector2d(0.0, -pixels_per_uv[1])) *
+    LMatrix3d::scale_mat(1.0 / pixels_per_uv[0], -1.0 / pixels_per_uv[1]);
+
+  _uv_to_pixels =
+    LMatrix3d::scale_mat(pixels_per_uv[0], -pixels_per_uv[1]) *
+    LMatrix3d::translate_mat(LVector2d(0.0, pixels_per_uv[1]));
+
+  /*
+  nout << "_pixels_to_uv * _uv_to_pixels is\n" 
+       << _pixels_to_uv * _uv_to_pixels << "\n"
+       << "Corners in pixels:\n"
+       << "ll " << LPoint2d(0.0, 0.0) * _uv_to_pixels
+       << " lr " << LPoint2d(1.0, 0.0) * _uv_to_pixels
+       << " ul " << LPoint2d(0.0, 1.0) * _uv_to_pixels
+       << " ur " << LPoint2d(1.0, 1.0) * _uv_to_pixels << "\n"
+       << "center " << LPoint2d(0.5, 0.5) * _uv_to_pixels << "\n\n";
+  */
+
+  LVector2d mm_per_uv = get_size_mm();
+
+  _uv_to_mm = 
+    LMatrix3d::translate_mat(LVector2d(-0.5, -0.5)) *
+    LMatrix3d::scale_mat(mm_per_uv);
+
+  _mm_to_uv =
+    LMatrix3d::scale_mat(1.0 / mm_per_uv[0], 1.0 / mm_per_uv[1]) *
+    LMatrix3d::translate_mat(LVector2d(0.5, 0.5));
+
+  _pixels_to_mm = _pixels_to_uv * _uv_to_mm;
+  _mm_to_pixels = _mm_to_uv * _uv_to_pixels;
+
+  _show_points = false;
+  setup_grid(2, 2);
+
+  _data = NULL;
+  _untextured_color.set(1.0, 1.0, 1.0, 1.0);
+  _index = 0;
+  _hpr_set = false;
+  _layered_type = LT_flat;
+  _layer_index = 0;
+  _layered_image = NULL;
+
+  if (_filename.get_extension() == "xcf") {
+    _layered_type = LT_combined;
+  }
+}
+
+bool StitchImage::
+has_name() const {
+  return !_name.empty();
+}
+
+string StitchImage::
+get_name() const {
+  if (_name.empty()) {
+    return _filename.get_basename_wo_extension();
+  }
+  return _name;
+}
+
+bool StitchImage::
+has_filename() const {
+  return !_filename.empty();
+}
+
+string StitchImage::
+get_filename() const {
+  return _filename;
+}
+
+bool StitchImage::
+read_file() {
+  if (_data != NULL) {
+    delete _data;
+    _data = NULL;
+  }
+  if (!has_filename()) {
+    return false;
+  }
+  _data = new PNMImage;
+  nout << "Reading " << _filename << "\n";
+  bool result = _data->read(_filename);
+  if (!result) {
+    delete _data;
+    _data = NULL;
+  }
+  return result;
+}
+
+void StitchImage::
+clear_file() {
+  if (_data != NULL) {
+    delete _data;
+    _data = NULL;
+  }
+  if (_layered_image != NULL) {
+    delete _layered_image;
+    _layered_image = NULL;
+  }
+}
+
+void StitchImage::
+open_output_file() {
+  clear_file();
+  if (_layered_type == LT_flat) {
+    _data = new PNMImage(_size_pixels[0], _size_pixels[1], 4);
+
+  } else if (_layered_type == LT_combined) {
+    _layered_image = new LayeredImage(_size_pixels[0], _size_pixels[1]);
+  }
+}
+
+void StitchImage::
+open_layer(const string &layer_name) {
+  _layer_name = layer_name;
+
+  if (_layered_type == LT_separate || _layered_type == LT_combined) {
+    _data = new PNMImage(_size_pixels[0], _size_pixels[1], 4);
+  }
+}
+
+bool StitchImage::
+close_layer(bool nonempty) {
+  bool result = true;
+
+  if (_layered_type == LT_separate) {
+    if (_data == NULL) {
+      result = false;
+    } else {
+      if (nonempty) {
+	char buff[1024];
+	_layer_index++;
+	sprintf(buff, _filename.c_str(), _layer_index);
+	nout << "Writing layer " << _layer_name << " as " << buff << "\n";
+	result = _data->write(buff);
+      }
+    }
+    clear_file();
+
+  } else if (_layered_type == LT_combined) {
+    if (_data == NULL) {
+      result = false;
+    } else {
+      if (nonempty) {
+	_layered_image->add_layer(_layer_name, LVector2d(0.0, 0.0),
+				  _data);
+	_data = NULL;
+      }
+    }
+  }
+  return result;
+}
+
+bool StitchImage::
+close_output_file() {
+  bool result = true;
+
+  if (_layered_type == LT_separate) {
+
+  } else if (_layered_type == LT_combined) {
+    if (_layered_image == NULL) {
+      result = false;
+    } else {
+      nout << "Writing " << _filename << "\n";
+      result = _layered_image->write_file(_filename);
+    }
+
+  } else { // _layered_type == LT_flat
+    if (_data == NULL) {
+      result = false;
+    } else {
+      nout << "Writing " << _filename << "\n";
+      result = _data->write(_filename);
+    }
+  }
+
+  clear_file();
+  return result;
+}
+
+void StitchImage::
+clear_transform() {
+  _rotate = LMatrix3d::ident_mat();
+  _inv_rotate = LMatrix3d::ident_mat();
+  _morph.clear();
+}
+
+void StitchImage::
+set_transform(const LMatrix3d &rot) {
+  _rotate = rot;
+  _inv_rotate = invert(rot);
+}
+
+void StitchImage::
+set_hpr(const LVecBase3d &hpr) {
+  compose_matrix(_rotate, LVecBase3d(1.0, 1.0, 1.0), hpr);
+  _inv_rotate = invert(_rotate);
+  _hpr_set = true;
+  _hpr = hpr;
+}
+
+void StitchImage::
+show_points(double radius, const Colord &color) {
+  _show_points = true;
+  _point_radius = radius;
+  _point_color = color;
+}
+
+void StitchImage::
+setup_grid(int x_verts, int y_verts) {
+  _x_verts = x_verts;
+  _y_verts = y_verts;
+}
+
+int StitchImage::
+get_x_verts() const {
+  return _x_verts;
+}
+
+int StitchImage::
+get_y_verts() const {
+  return _y_verts;
+}
+
+LPoint2d StitchImage::
+get_grid_uv(int xv, int yv) {
+  return LPoint2d((double)xv / (double)(_x_verts - 1),
+		  1.0 - (double)yv / (double)(_y_verts - 1));
+		  
+}
+
+LVector3d StitchImage::
+get_grid_vector(int xv, int yv) {
+  return extrude(get_grid_uv(xv, yv));
+}
+
+double StitchImage::
+get_grid_alpha(int xv, int yv) {
+  return get_alpha(get_grid_uv(xv, yv));
+}
+
+const LVecBase2d &StitchImage::
+get_size_pixels() const {
+  return _size_pixels;
+}
+
+LVecBase2d StitchImage::
+get_size_mm() const {
+  return _size_mm;
+}
+
+LVector3d StitchImage::
+extrude(const LPoint2d &point_uv) const {
+  LPoint2d p = _morph.morph_out(point_uv);
+  return _lens->extrude(p * _uv_to_mm, _size_mm[0]) * _rotate;
+}
+
+LPoint2d StitchImage::
+project(const LVector3d &vec) const {
+  LPoint2d m = _lens->project(vec * _inv_rotate, _size_mm[0]);
+  return _morph.morph_in(m * _mm_to_uv);
+}
+
+double StitchImage::
+get_alpha(const LPoint2d &point_uv) const {
+  return _morph.get_alpha(point_uv);
+}
+
+void StitchImage::
+reset_singularity_detected() {
+  _lens->reset_singularity_detected();
+}
+
+void StitchImage::
+draw_triangle(TriangleRasterizer &rast, const RasterizerVertex *v0, 
+	      const RasterizerVertex *v1, const RasterizerVertex *v2) {
+  _lens->draw_triangle(rast, _mm_to_pixels, _size_mm[0], v0, v1, v2);
+}
+
+void StitchImage::
+pick_up_singularity(TriangleRasterizer &rast, StitchImage *input) {
+  _lens->pick_up_singularity(rast, _mm_to_pixels, _pixels_to_mm, 
+			     _rotate, _size_mm[0], input);
+}
+
+void StitchImage::
+add_point(const string &name, const LPoint2d &pixel) {
+  _points[name] = pixel * _pixels_to_uv;
+}
+
+
+void StitchImage::
+output(ostream &out) const {
+  out << "image " << get_name() << ":\n"
+      << get_size_pixels() << " pixels, or " << get_size_mm()
+      << " mm\n";
+
+  LVecBase3d scale, hpr;
+  if (decompose_matrix(_rotate, scale, hpr)) {
+    out << "rotate " << hpr << "\n";
+  } else {
+    out << "Invalid rotation transform:\n";
+    _rotate.write(out);
+  }
+}
+

+ 154 - 0
pandaapp/src/stitchbase/stitchImage.h

@@ -0,0 +1,154 @@
+// Filename: stitchImage.h
+// Created by:  drose (04Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHIMAGE_H
+#define STITCHIMAGE_H
+
+#include <pandatoolbase.h>
+
+#include "stitchPoint.h"
+#include "morphGrid.h"
+
+#include <lmatrix.h>
+#include <luse.h>
+#include <pnmImage.h>
+#include <filename.h>
+
+#include <map>
+
+class StitchLens;
+class TriangleRasterizer;
+class RasterizerVertex;
+class LayeredImage;
+
+class StitchImage {
+public:
+  StitchImage(const string &name, const string &filename,
+	      StitchLens *lens, 
+	      const LVecBase2d &size_pixels,
+	      const LVecBase2d &pixels_per_mm);
+
+  bool has_name() const;
+  string get_name() const;
+  bool has_filename() const;
+  string get_filename() const;
+
+  // This function reads the image file if it is available.
+  bool read_file();
+  void clear_file();
+
+  // These functions handle the writing of the image file.
+  // open_output_file() should be called first.  open_layer() and
+  // close_layer() should be called in pairs; after a call to
+  // open_layer(), the _data member is guaranteed to contain a
+  // PNMImage that may be empty or may contain the contents of
+  // previous layers.  close_layer() should be called as each layer is
+  // filled, and close_output_file() should be called when the image
+  // is completely done.
+  void open_output_file();
+  void open_layer(const string &layer_name);
+  bool close_layer(bool nonempty);
+  bool close_output_file();
+
+  void clear_transform();
+  void set_transform(const LMatrix3d &rot);
+  void set_hpr(const LVecBase3d &hpr);
+
+  void show_points(double radius, const Colord &color);
+  void setup_grid(int x_verts, int y_verts);
+  int get_x_verts() const;
+  int get_y_verts() const;
+
+  LPoint2d get_grid_uv(int xv, int yv);
+  LPoint2d get_grid_pixel(int xv, int yv);
+  LVector3d get_grid_vector(int xv, int yv);
+  double get_grid_alpha(int xv, int yv);
+
+  const LVecBase2d &get_size_pixels() const;
+  LVecBase2d get_size_mm() const;
+
+  LVector3d extrude(const LPoint2d &point_uv) const;
+  LPoint2d project(const LVector3d &vec) const; 
+  double get_alpha(const LPoint2d &point_uv) const;
+
+  void reset_singularity_detected();
+
+  // This function simply passes the indicated triangle on to the
+  // rasterizer.  It exists here in the lens so that the lens may do
+  // something special if the triangle crosses a seam or singularity
+  // in the lens' coordinate space.
+  void draw_triangle(TriangleRasterizer &rast,
+		     const RasterizerVertex *v0, 
+		     const RasterizerVertex *v1, 
+		     const RasterizerVertex *v2);
+
+  // This function is to be called after all triangles have been
+  // drawn; it will draw pixel-by-pixel all the points within
+  // _singularity_radius of any singularity points the lens may have
+  // (these points were not draw by draw_triangle(), above).
+  void pick_up_singularity(TriangleRasterizer &rast,
+			   StitchImage *input);
+
+  void add_point(const string &name, const LPoint2d &pixel);
+
+  void output(ostream &out) const;
+
+  PNMImage *_data;
+  StitchLens *_lens;
+  LVecBase2d _size_pixels, _size_mm;
+  LVecBase2d _pixels_per_mm;
+  LMatrix3d _mm_to_uv, _uv_to_mm;
+  LMatrix3d _pixels_to_mm, _mm_to_pixels;
+  LMatrix3d _pixels_to_uv, _uv_to_pixels;
+  bool _hpr_set;
+  LVecBase3d _hpr;
+
+  enum LayeredType {
+    LT_flat,        // One flat image--no layers.
+    LT_separate,    // A separate image file for each layer.
+    LT_combined,    // A single image file with multiple layers.
+  };
+  LayeredType _layered_type;
+
+  bool _show_points;
+  double _point_radius;
+  Colord _point_color;
+  Colord _untextured_color;
+
+  typedef map<string, LPoint2d> Points;
+  Points _points;
+  LMatrix3d _rotate, _inv_rotate;
+  MorphGrid _morph;
+
+  // This index number is filled in by the Stitcher.  It allows us to
+  // sort the images in order as they are specified in the command
+  // file.
+  int _index;
+  
+private:
+  Filename _filename;
+  string _name;
+
+  int _x_verts, _y_verts;
+  int _layer_index;
+  string _layer_name;
+  LayeredImage *_layered_image;
+};
+
+inline ostream &operator << (ostream &out, const StitchImage &i) {
+  i.output(out);
+  return out;
+}
+
+// An STL function object to sort image pointers by index number.
+class StitchImageByIndex {
+public:
+  bool operator()(const StitchImage *a, const StitchImage *b) const {
+    return a->_index < b->_index;
+  }
+};
+
+#endif
+

+ 91 - 0
pandaapp/src/stitchbase/stitchImageCommandOutput.cxx

@@ -0,0 +1,91 @@
+// Filename: stitchImageCommandOutput.cxx
+// Created by:  drose (29Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchImageCommandOutput.h"
+#include "stitchImage.h"
+#include "stitchLens.h"
+#include "stitcher.h"
+#include "stitchCommand.h"
+
+#include <pnmImage.h>
+#include <compose_matrix.h>
+
+StitchImageCommandOutput::
+StitchImageCommandOutput() {
+}
+
+
+void StitchImageCommandOutput::
+add_input_image(StitchImage *image) {
+  _input_images.push_back(image);
+}
+
+void StitchImageCommandOutput::
+add_output_image(StitchImage *image) {
+  _output_images.push_back(image);
+}
+
+void StitchImageCommandOutput::
+add_stitcher(Stitcher *stitcher) {
+  _stitchers.push_back(stitcher);
+}
+
+void StitchImageCommandOutput::
+execute() {
+  StitchCommand root;
+
+  Stitchers::const_iterator si;
+  for (si = _stitchers.begin(); si != _stitchers.end(); ++si) {
+    Stitcher *stitcher = (*si);
+    Stitcher::LoosePoints::const_iterator pi;
+    for (pi = stitcher->_loose_points.begin();
+	 pi != stitcher->_loose_points.end();
+	 ++pi) {
+      StitchCommand *cmd = new StitchCommand(&root, StitchCommand::C_point3d);
+      cmd->set_name((*pi)->_name);
+      cmd->set_point3d((*pi)->_space);
+    }
+  }
+
+  Images::const_iterator ii;
+  for (ii = _input_images.begin(); ii != _input_images.end(); ++ii) {
+    StitchImage *input = (*ii);
+    StitchCommand *image_cmd = new StitchCommand(&root, StitchCommand::C_input_image);
+    fill_image_cmd(image_cmd, input);
+  }
+
+  for (ii = _output_images.begin(); ii != _output_images.end(); ++ii) {
+    StitchImage *output = (*ii);
+    StitchCommand *image_cmd = new StitchCommand(&root, StitchCommand::C_output_image);
+    fill_image_cmd(image_cmd, output);
+  }
+
+  cout << root << "\n";
+}
+
+void StitchImageCommandOutput::
+fill_image_cmd(StitchCommand *image_cmd, StitchImage *image) {
+  if (image->has_name()) {
+    image_cmd->set_name(image->get_name());
+  }
+
+  StitchCommand *cmd;
+  cmd = new StitchCommand(image_cmd, StitchCommand::C_filename);
+  cmd->set_str(image->get_filename());
+
+  cmd = new StitchCommand(image_cmd, StitchCommand::C_image_size);
+  cmd->set_point2d(image->get_size_pixels());
+
+  cmd = new StitchCommand(image_cmd, StitchCommand::C_film_size);
+  cmd->set_length_pair(image->get_size_mm());
+
+  image->_lens->make_lens_command(image_cmd);
+
+  LVecBase3d scale, hpr;
+  if (decompose_matrix(image->_rotate, scale, hpr)) {
+    cmd = new StitchCommand(image_cmd, StitchCommand::C_hpr);
+    cmd->set_point3d(hpr);
+  }
+}

+ 39 - 0
pandaapp/src/stitchbase/stitchImageCommandOutput.h

@@ -0,0 +1,39 @@
+// Filename: stitchImageCommandOutput.h
+// Created by:  drose (29Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHIMAGECOMMANDOUTPUT_H
+#define STITCHIMAGECOMMANDOUTPUT_H
+
+#include "stitchImageOutputter.h"
+
+#include <luse.h>
+
+class Stitcher;
+class StitchImage;
+class StitchCommand;
+
+class StitchImageCommandOutput : public StitchImageOutputter {
+public:
+  StitchImageCommandOutput();
+
+  virtual void add_input_image(StitchImage *image);
+  virtual void add_output_image(StitchImage *image);
+  virtual void add_stitcher(Stitcher *stitcher);
+
+  virtual void execute();
+  
+protected:
+  void fill_image_cmd(StitchCommand *image_cmd, StitchImage *image);
+
+  typedef vector<StitchImage *> Images;
+  Images _input_images;
+  Images _output_images;
+
+  typedef vector<Stitcher *> Stitchers;
+  Stitchers _stitchers;
+};
+
+#endif
+

+ 15 - 0
pandaapp/src/stitchbase/stitchImageOutputter.cxx

@@ -0,0 +1,15 @@
+// Filename: stitchImageOutputter.cxx
+// Created by:  drose (09Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchImageOutputter.h"
+
+
+StitchImageOutputter::
+StitchImageOutputter() {
+}
+
+StitchImageOutputter::
+~StitchImageOutputter() {
+}

+ 26 - 0
pandaapp/src/stitchbase/stitchImageOutputter.h

@@ -0,0 +1,26 @@
+// Filename: stitchImageOutputter.h
+// Created by:  drose (09Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHIMAGEOUTPUTTER_H
+#define STITCHIMAGEOUTPUTTER_H
+
+class StitchImage;
+class Stitcher;
+
+#include <luse.h>
+
+class StitchImageOutputter {
+public:
+  StitchImageOutputter();
+  virtual ~StitchImageOutputter();
+
+  virtual void add_input_image(StitchImage *image)=0;
+  virtual void add_output_image(StitchImage *image)=0;
+  virtual void add_stitcher(Stitcher *stitcher)=0;
+
+  virtual void execute()=0;
+};
+
+#endif

+ 217 - 0
pandaapp/src/stitchbase/stitchImageRasterizer.cxx

@@ -0,0 +1,217 @@
+// Filename: stitchImageRasterizer.cxx
+// Created by:  drose (06Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchImageRasterizer.h"
+#include "triangleRasterizer.h"
+#include "stitchImage.h"
+#include "stitcher.h"
+
+#include <pnmImage.h>
+
+StitchImageRasterizer::
+StitchImageRasterizer() {
+  _filter_output = true;
+}
+
+
+void StitchImageRasterizer::
+add_input_image(StitchImage *image) {
+  _input_images.push_back(image);
+}
+
+void StitchImageRasterizer::
+add_output_image(StitchImage *image) {
+  _output_images.push_back(image);
+}
+
+void StitchImageRasterizer::
+add_stitcher(Stitcher *stitcher) {
+  _stitchers.push_back(stitcher);
+}
+
+void StitchImageRasterizer::
+execute() {
+  Images::iterator oi;
+  for (oi = _output_images.begin(); oi != _output_images.end(); ++oi) {
+    StitchImage *output = (*oi);
+    if (!output->has_filename()) {
+      nout << "Output image has no filename; cannot generate.\n";
+    } else {
+      nout << "Generating " << output->get_name() << "\n";
+      output->open_output_file();
+
+      Images::const_iterator ii;
+      for (ii = _input_images.begin(); ii != _input_images.end(); ++ii) {
+	StitchImage *input = (*ii);
+	draw_image(output, input);
+      }
+
+      output->open_layer("points");
+      bool shown_points = false;
+      Stitchers::const_iterator si;
+      for (si = _stitchers.begin(); si != _stitchers.end(); ++si) {
+	Stitcher *stitcher = (*si);
+	if (stitcher->_show_points && !stitcher->_loose_points.empty()) {
+	  draw_points(output, stitcher, stitcher->_point_color,
+		      stitcher->_point_radius);
+	  shown_points = true;
+	}
+      }
+      for (ii = _input_images.begin(); ii != _input_images.end(); ++ii) {
+	StitchImage *input = (*ii);
+	if (input->_show_points && !input->_points.empty()) {
+	  draw_points(output, input, input->_point_color,
+		      input->_point_radius);
+	  shown_points = true;
+	}
+      }
+      output->close_layer(shown_points);
+      
+      if (!output->close_output_file()) {
+	nout << "Error in writing.\n";
+      }
+    }
+  }
+}
+  
+void StitchImageRasterizer::
+draw_points(StitchImage *output, StitchImage *input,
+	    const Colord &color, double radius) {
+  StitchImage::Points::const_iterator pi;
+  for (pi = input->_points.begin(); pi != input->_points.end(); ++pi) {
+    LPoint2d to = output->project(input->extrude((*pi).second));
+    draw_spot(output, to * output->_uv_to_pixels, color, radius);
+  }
+}
+  
+void StitchImageRasterizer::
+draw_points(StitchImage *output, Stitcher *input,
+	    const Colord &color, double radius) {
+  Stitcher::LoosePoints::const_iterator pi;
+  for (pi = input->_loose_points.begin(); 
+       pi != input->_loose_points.end(); ++pi) {
+    LPoint2d to = output->project((*pi)->_space);
+    draw_spot(output, to * output->_uv_to_pixels, color, radius);
+  }
+}
+
+  
+void StitchImageRasterizer::
+draw_image(StitchImage *output, StitchImage *input) {
+  nout << "Rasterizing " << input->get_name() << "\n";
+  output->open_layer(input->get_name());
+
+  TriangleRasterizer rast;
+  rast._output = output->_data;
+  rast._input = input;
+  rast._filter_output = _filter_output;
+  rast._untextured_color = input->_untextured_color;
+
+  int x_verts = input->get_x_verts();
+  int y_verts = input->get_y_verts();
+
+  // Build up the table of verts.
+  typedef vector<RasterizerVertex> VRow;
+  typedef vector<VRow> VTable;
+  VTable _table(x_verts, VRow());
+
+  int xi, yi;
+  for (xi = 0; xi < x_verts; xi++) {
+    _table[xi] = VRow(y_verts, RasterizerVertex());
+
+    for (yi = 0; yi < y_verts; yi++) {
+      LVector3d space = input->get_grid_vector(xi, yi);
+      double alpha = input->get_grid_alpha(xi, yi);
+      LPoint2d to = output->project(space);
+      LPoint2d from = input->get_grid_uv(xi, yi);
+
+      _table[xi][yi]._p = to * output->_uv_to_pixels;
+      _table[xi][yi]._uv = from;
+      _table[xi][yi]._space = space * output->_inv_rotate;
+      _table[xi][yi]._alpha = alpha;
+
+      _table[xi][yi]._space = normalize(_table[xi][yi]._space);
+
+      // We assign one bit for each quadrant the vertex may be out of
+      // bounds.  If all three vertices of a triangle are out in the
+      // same quadrant, then the entire triangle is out of bounds.
+      _table[xi][yi]._visibility =
+	((to[0] < 0.0) |
+	 ((to[0] > 1.0) << 1) |
+	 ((to[1] < 0.0) << 2) |
+	 ((to[1] > 1.0) << 3) |
+	 ((from[0] < 0.0) << 4) |
+	 ((from[0] > 1.0) << 5) |
+	 ((from[1] < 0.0) << 6) |
+	 ((from[1] > 1.0) << 7));
+    }
+  }
+
+  // Now draw all of the triangles, top-to-bottom.
+  output->reset_singularity_detected();
+
+  for (yi = 0; yi < y_verts - 1; yi++) {
+    for (xi = 0; xi < x_verts - 1; xi++) {
+      output->draw_triangle(rast,
+			    &_table[xi][yi],
+			    &_table[xi][yi + 1],
+			    &_table[xi + 1][yi + 1]);
+      output->draw_triangle(rast,
+			    &_table[xi][yi],
+			    &_table[xi + 1][yi + 1],
+			    &_table[xi + 1][yi]);
+    }
+  }
+  output->pick_up_singularity(rast, input);
+  output->close_layer(rast._read_input);
+
+  input->clear_file();
+}
+
+
+void StitchImageRasterizer::
+draw_spot(StitchImage *output,
+	  const LPoint2d pixel_center, const Colord &color, double radius) {
+  LPoint2d minp = pixel_center - LPoint2d(radius, radius);
+  LPoint2d maxp = pixel_center + LPoint2d(radius, radius);
+  
+  int min_x = (int)floor(minp[0]);
+  int max_x = (int)ceil(maxp[0]);
+  
+  int min_y = (int)floor(minp[1]);
+  int max_y = (int)ceil(maxp[1]);
+
+  double r2 = radius * radius;
+
+  for (int yi = min_y; yi <= max_y; yi++) {
+    if (yi >= 0 && yi < output->_data->get_y_size()) {
+      for (int xi = min_x; xi <= max_x; xi++) {
+	if (xi >= 0 && xi < output->_data->get_x_size()) {
+	  // Check the coverage of the four points around the pixel, and
+	  // the pixel center.
+	  LPoint2d ul = pixel_center - LPoint2d(xi - 0.5, yi - 0.5);
+	  LPoint2d ll = pixel_center - LPoint2d(xi - 0.5, yi + 0.5);
+	  LPoint2d ur = pixel_center - LPoint2d(xi + 0.5, yi - 0.5);
+	  LPoint2d lr = pixel_center - LPoint2d(xi + 0.5, yi + 0.5);
+	  LPoint2d pc = pixel_center - LPoint2d(xi, yi);
+	  
+	  // Net coverage.
+	  int coverage =
+	    (dot(ul, ul) <= r2) +
+	    (dot(ll, ll) <= r2) +
+	    (dot(ur, ur) <= r2) +
+	    (dot(lr, lr) <= r2) +
+	    (dot(pc, pc) <= r2);
+	  
+	  if (coverage != 0) {
+	    output->_data->blend(xi, yi, color[0], color[1], color[2],
+				 color[3] * (double)coverage / 5.0);
+	  }
+	}
+      }
+    }
+  }
+}
+

+ 49 - 0
pandaapp/src/stitchbase/stitchImageRasterizer.h

@@ -0,0 +1,49 @@
+// Filename: stitchImageRasterizer.h
+// Created by:  drose (06Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHIMAGERASTERIZER_H
+#define STITCHIMAGERASTERIZER_H
+
+#include "stitchImageOutputter.h"
+
+#include <luse.h>
+
+class Stitcher;
+class StitchImage;
+
+class StitchImageRasterizer : public StitchImageOutputter {
+public:
+  StitchImageRasterizer();
+
+  virtual void add_input_image(StitchImage *image);
+  virtual void add_output_image(StitchImage *image);
+  virtual void add_stitcher(Stitcher *stitcher);
+
+  virtual void execute();
+
+  bool _filter_output;
+  
+protected:
+  void draw_points(StitchImage *output, StitchImage *input,
+		   const Colord &color, double radius);
+  void draw_points(StitchImage *output, Stitcher *input,
+		   const Colord &color, double radius);
+  void draw_image(StitchImage *output, StitchImage *input);
+
+  typedef vector<StitchImage *> Images;
+  Images _input_images;
+  Images _output_images;
+
+  typedef vector<Stitcher *> Stitchers;
+  Stitchers _stitchers;
+
+protected:
+  void draw_spot(StitchImage *output,
+		 const LPoint2d pixel_center, const Colord &color,
+		 double radius);
+};
+
+#endif
+

+ 69 - 0
pandaapp/src/stitchbase/stitchLens.cxx

@@ -0,0 +1,69 @@
+// Filename: stitchLens.cxx
+// Created by:  drose (04Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchLens.h"
+#include "triangleRasterizer.h"
+
+#include <pandatoolbase.h>
+#include <deg_2_rad.h>
+
+#include <math.h>
+
+StitchLens::
+StitchLens() {
+  _flags = 0;
+  _singularity_detected = 0;
+  set_singularity_tolerance(5.0);
+}
+
+StitchLens::
+~StitchLens() {
+}
+
+void StitchLens::
+set_focal_length(double focal_length_mm) {
+  _flags |= F_focal_length;
+  _focal_length = focal_length_mm;
+}
+
+void StitchLens::
+set_hfov(double fov_deg) {
+  _flags |= F_fov;
+  _fov = fov_deg;
+}
+
+void StitchLens::
+set_singularity_tolerance(double tol) {
+  _singularity_tolerance = tol;
+  _singularity_radius = sin(deg_2_rad(tol));
+}
+
+void StitchLens::
+reset_singularity_detected() {
+  _singularity_detected = 0;
+}
+
+bool StitchLens::
+is_defined() const {
+  return (_flags != 0);
+}
+
+double StitchLens::
+get_vfov(double height_mm) const {
+  return get_hfov(height_mm);
+}
+
+void StitchLens::
+draw_triangle(TriangleRasterizer &rast, const LMatrix3d &,
+	      double, const RasterizerVertex *v0,
+	      const RasterizerVertex *v1, const RasterizerVertex *v2) {
+  rast.draw_triangle(v0, v1, v2);
+}
+
+void StitchLens::
+pick_up_singularity(TriangleRasterizer &, const LMatrix3d &, 
+		    const LMatrix3d &, const LMatrix3d &, 
+		    double, StitchImage *) {
+}

+ 74 - 0
pandaapp/src/stitchbase/stitchLens.h

@@ -0,0 +1,74 @@
+// Filename: stitchLens.h
+// Created by:  drose (04Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHLENS_H
+#define STITCHLENS_H
+
+#include <luse.h>
+
+class TriangleRasterizer;
+class RasterizerVertex;
+class StitchImage;
+class StitchCommand;
+
+class StitchLens {
+public:
+  StitchLens();
+  virtual ~StitchLens();
+
+  virtual void set_focal_length(double focal_length_mm);
+  virtual void set_hfov(double fov_deg);
+
+  void set_singularity_tolerance(double tol);
+  void reset_singularity_detected();
+
+  bool is_defined() const;
+  virtual double get_focal_length(double width_mm) const=0;
+  virtual double get_hfov(double width_mm) const=0;
+  virtual double get_vfov(double height_mm) const;
+
+  virtual LVector3d extrude(const LPoint2d &point_mm, double width_mm) const=0;
+  virtual LPoint2d project(const LVector3d &vec, double width_mm) const=0; 
+
+  // This function simply passes the indicated triangle on to the
+  // rasterizer.  It exists here in the lens so that the lens may do
+  // something special if the triangle crosses a seam or singularity
+  // in the lens' coordinate space.
+  virtual void draw_triangle(TriangleRasterizer &rast,
+			     const LMatrix3d &mm_to_pixels,
+			     double width_mm,
+			     const RasterizerVertex *v0, 
+			     const RasterizerVertex *v1, 
+			     const RasterizerVertex *v2);
+
+  // This function is to be called after all triangles have been
+  // drawn; it will draw pixel-by-pixel all the points within
+  // _singularity_radius of any singularity points the lens may have
+  // (these points were not draw by draw_triangle(), above).
+  virtual void pick_up_singularity(TriangleRasterizer &rast, 
+				   const LMatrix3d &mm_to_pixels,
+				   const LMatrix3d &pixels_to_mm,
+				   const LMatrix3d &rotate,
+				   double width_mm,
+				   StitchImage *input);
+
+  // This generates a StitchCommand that represents the given lens.
+  virtual void make_lens_command(StitchCommand *parent)=0;
+  
+protected:
+  enum Flags {
+    F_focal_length = 0x01,
+    F_fov          = 0x02,
+  };
+  int _flags;
+  int _singularity_detected;
+  double _focal_length;
+  double _fov;
+  double _singularity_tolerance;
+  double _singularity_radius;
+};
+
+#endif
+

+ 364 - 0
pandaapp/src/stitchbase/stitchLexer.lxx

@@ -0,0 +1,364 @@
+/*
+// Filename: lexer.l
+// Created by:  drose (08Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+*/
+
+%{
+#include "stitchLexerDefs.h"
+#include "stitchParserDefs.h"
+#include "stitchParser.h"
+
+#include <indent.h>
+
+#include <assert.h>
+#include <math.h>
+
+extern "C" int stitchyywrap(void);  // declared below.
+
+static int yyinput(void);        // declared by flex.
+
+
+////////////////////////////////////////////////////////////////////
+// Static variables
+////////////////////////////////////////////////////////////////////
+
+// We'll increment line_number and col_number as we parse the file, so
+// that we can report the position of an error.
+static int line_number = 0;
+static int col_number = 0;
+
+// current_line holds as much of the current line as will fit.  Its
+// only purpose is for printing it out to report an error to the user.
+static const int max_error_width = 1024;
+static char current_line[max_error_width + 1];
+
+static int error_count = 0;
+static int warning_count = 0;
+
+// This is the pointer to the current input stream.
+static istream *inp = NULL;
+
+// This is the name of the stitch file we're parsing.  We keep it so we
+// can print it out for error messages.
+static string stitch_filename;
+
+////////////////////////////////////////////////////////////////////
+// Defining the interface to the lexer.
+////////////////////////////////////////////////////////////////////
+
+void
+stitch_init_lexer(istream &in, const string &filename) {
+  inp = &in;
+  stitch_filename = filename;
+  line_number = 0;
+  col_number = 0;
+  error_count = 0;
+  warning_count = 0;
+}
+
+int
+stitch_error_count() {
+  return error_count;
+}
+
+int
+stitch_warning_count() {
+  return warning_count;
+}
+
+
+////////////////////////////////////////////////////////////////////
+// Internal support functions.
+////////////////////////////////////////////////////////////////////
+
+int
+stitchyywrap(void) {
+  return 1;
+}
+
+void
+stitchyyerror(const string &msg) {
+  nout << "\nError";
+  if (!stitch_filename.empty()) {
+    nout << " in " << stitch_filename;
+  }
+  nout 
+    << " at line " << line_number << ", column " << col_number << ":\n"
+    << current_line << "\n";
+  indent(nout, col_number-1) 
+    << "^\n" << msg << "\n\n" << flush;
+  error_count++;
+}
+
+void
+stitchyyerror(ostringstream &strm) {
+  stitchyyerror(strm.str());
+}
+
+void
+stitchyywarning(const string &msg) {
+  nout 
+    << "\nWarning at line " << line_number << ", column " << col_number << ":\n"
+    << current_line << "\n";
+  indent(nout, col_number-1) 
+    << "^\n" << msg << "\n\n" << flush;
+  warning_count++;
+}
+
+void
+stitchyywarning(ostringstream &strm) {
+  stitchyywarning(strm);
+}
+
+// Now define a function to take input from an istream instead of a
+// stdio FILE pointer.  This is flex-specific.
+static void
+input_chars(char *buffer, int &result, int max_size) {
+  assert(inp != NULL);
+  if (*inp) {
+    inp->read(buffer, max_size);
+    result = inp->gcount();
+
+    if (line_number == 0) {
+      // This is a special case.  If we are reading the very first bit
+      // from the stream, copy it into the current_line array.  This
+      // is because the \n.* rule below, which fills current_line
+      // normally, doesn't catch the first line.
+      strncpy(current_line, yytext, max_error_width);
+      current_line[max_error_width] = '\0';
+      line_number++;
+      col_number = 0;
+
+      // Truncate it at the newline.
+      char *end = strchr(current_line, '\n');
+      if (end != NULL) {
+	*end = '\0';
+      }
+    }
+
+  } else {
+    // End of file or I/O error.
+    result = 0;
+  }
+}
+#undef YY_INPUT
+#define YY_INPUT(buffer, result, max_size) input_chars(buffer, result, max_size)
+
+// read_char reads and returns a single character, incrementing the
+// supplied line and column numbers as appropriate.  A convenience
+// function for the scanning functions below.
+static int
+read_char(int &line, int &col) {
+  int c = yyinput();
+  if (c == '\n') {
+    line++;
+    col = 0;
+  } else {
+    col++;
+  }
+  return c;
+}
+
+// scan_quoted_string reads a string delimited by quotation marks and
+// returns it.
+static string
+scan_quoted_string() {
+  string result;
+
+  // We don't touch the current line number and column number during
+  // scanning, so that if we detect an error while scanning the string
+  // (e.g. an unterminated string), we'll report the error as
+  // occurring at the start of the string, not at the end--somewhat
+  // more convenient for the user.
+
+  // Instead of adjusting the global line_number and col_number
+  // variables, we'll operate on our own local variables for the
+  // interim.
+  int line = line_number;
+  int col = col_number;
+
+  int c;
+  c = read_char(line, col);
+  while (c != '"' && c != EOF) {
+    result += c;
+    c = read_char(line, col);
+  }
+
+  if (c == EOF) {
+    stitchyyerror("This quotation mark is unterminated.");
+  }
+
+  line_number = line;
+  col_number = col;
+
+  return result;
+}
+
+// eat_c_comment scans past all characters up until the first */
+// encountered.
+static void
+eat_c_comment() {
+  // As above, we'll operate on our own local copies of line_number
+  // and col_number within this function.
+
+  int line = line_number;
+  int col = col_number;
+
+  int c, last_c;
+  
+  last_c = '\0';
+  c = read_char(line, col);
+  while (c != EOF && !(last_c == '*' && c == '/')) {
+    if (last_c == '/' && c == '*') {
+      ostringstream errmsg;
+      errmsg << "This comment contains a nested /* symbol at line "
+	     << line << ", column " << col-1 << "--possibly unclosed?";
+      stitchyywarning(errmsg);
+    }
+    last_c = c;
+    c = read_char(line, col);
+  }
+
+  if (c == EOF) {
+    stitchyyerror("This comment marker is unclosed.");
+  }
+
+  line_number = line;
+  col_number = col;
+}
+
+
+
+// accept() is called below as each piece is pulled off and
+// accepted by the lexer; it increments the current column number.
+INLINE void accept() {
+  col_number += yyleng;
+}
+
+%}
+
+NUMERIC         ([+-]?(([0-9]+[.]?)|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
+
+%%
+
+\n.* {
+  // New line.  Save a copy of the line so we can print it out for the
+  // benefit of the user in case we get an error.
+
+  strncpy(current_line, yytext+1, max_error_width);
+  current_line[max_error_width] = '\0';
+  line_number++;
+  col_number=0;
+
+  // Return the whole line to the lexer, except the newline character,
+  // which we eat.
+  yyless(1);
+}
+
+[ \t] { 
+  // Eat whitespace.
+  accept();
+}
+
+"//".* { 
+  // Eat C++-style comments.
+  accept();
+}
+
+"/*" {
+  // Eat C-style comments.
+  accept();
+  eat_c_comment(); 
+}
+
+
+
+{NUMERIC} { 
+  // An integer or floating-point number.
+  accept(); 
+  stitchyylval.number = atof(stitchyytext); 
+  stitchyylval.str = yytext;
+  return NUMBER; 
+}
+
+["] {
+  // Quoted string.
+  accept();
+  stitchyylval.str = scan_quoted_string();
+  return STRING;
+}
+
+[a-zA-Z][a-zA-Z0-9_]* {
+  // Identifier or keyword.
+  accept();
+  string str = yytext;
+  stitchyylval.str = str;
+
+  if (str == "define") {
+    return KW_DEFINE;
+  } else if (str == "lens") {
+    return KW_LENS;
+  } else if (str == "input_image") {
+    return KW_INPUT_IMAGE;
+  } else if (str == "output_image") {
+    return KW_OUTPUT_IMAGE;
+  } else if (str == "perspective") {
+    return KW_PERSPECTIVE;
+  } else if (str == "fisheye") {
+    return KW_FISHEYE;
+  } else if (str == "cylindrical") {
+    return KW_CYLINDRICAL;
+  } else if (str == "psphere") {
+    return KW_PSPHERE;
+  } else if (str == "focal_length") {
+    return KW_FOCAL_LENGTH;
+  } else if (str == "fov") {
+    return KW_FOV;
+  } else if (str == "singularity_tolerance") {
+    return KW_SINGULARITY_TOLERANCE;
+  } else if (str == "resolution") {
+    return KW_RESOLUTION;
+  } else if (str == "filename") {
+    return KW_FILENAME;
+  } else if (str == "point") {
+    return KW_POINT;
+  } else if (str == "show_points") {
+    return KW_SHOW_POINTS;
+  } else if (str == "image_size") {
+    return KW_IMAGE_SIZE;
+  } else if (str == "film_size") {
+    return KW_FILM_SIZE;
+  } else if (str == "grid") {
+    return KW_GRID;
+  } else if (str == "untextured_color") {
+    return KW_UNTEXTURED_COLOR;
+  } else if (str == "hpr") {
+    return KW_HPR;
+  } else if (str == "layers") {
+    return KW_LAYERS;
+  } else if (str == "stitch") {
+    return KW_STITCH;
+  } else if (str == "points") {
+    return KW_POINTS;
+  } else if (str == "using") {
+    return KW_USING;
+  } else if (str == "in") {
+    return KW_IN;
+  } else if (str == "mm") {
+    return KW_MM;
+  } else if (str == "cm") {
+    return KW_CM;
+  } else if (str == "p") {
+    return KW_P;
+  }
+
+  return IDENTIFIER;
+}
+
+. {
+  // Send any other character as itself.
+  accept(); 
+  return stitchyytext[0];
+}

+ 23 - 0
pandaapp/src/stitchbase/stitchLexerDefs.h

@@ -0,0 +1,23 @@
+// Filename: stitchLexerDefs.h
+// Created by:  drose (08Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHLEXERDEFS_H
+#define STITCHLEXERDEFS_H
+
+#include <pandatoolbase.h>
+
+void stitch_init_lexer(istream &in, const string &filename);
+int stitch_error_count();
+int stitch_warning_count();
+
+void stitchyyerror(const string &msg);
+void stitchyyerror(ostringstream &strm);
+
+void stitchyywarning(const string &msg);
+void stitchyywarning(ostringstream &strm);
+
+int stitchyylex();
+
+#endif

+ 300 - 0
pandaapp/src/stitchbase/stitchPSphereLens.cxx

@@ -0,0 +1,300 @@
+// Filename: stitchPSphereLens.cxx
+// Created by:  drose (16Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchPSphereLens.h"
+#include "stitchImage.h"
+#include "stitchCommand.h"
+#include "triangleRasterizer.h"
+#include "triangle.h"
+
+#include <pandatoolbase.h>
+#include <deg_2_rad.h>
+
+#include <math.h>
+
+// This is the focal-length constant for fisheye lenses.  See
+// stitchFisheyeLens.h.
+static const double k = 60.0;
+
+StitchPSphereLens::
+StitchPSphereLens() {
+}
+
+double StitchPSphereLens::
+get_focal_length(double width_mm) const {
+  if (_flags & F_focal_length) {
+    return _focal_length;
+  }
+  if (_flags & F_fov) {
+    return width_mm * k / _fov;
+  }
+  return 0.0;
+}
+
+double StitchPSphereLens::
+get_hfov(double width_mm) const {
+  if (_flags & F_fov) {
+    return _fov;
+  }
+  if (_flags & F_focal_length) {
+    return width_mm * k / _focal_length;
+  }
+  return 0.0;
+}
+
+LVector3d StitchPSphereLens::
+extrude(const LPoint2d &point_mm, double width_mm) const {
+  LVector2d v2 = point_mm;
+
+  double fl = get_focal_length(width_mm);
+  return LVector3d::forward() *
+    LMatrix3d::rotate_mat(v2[1] * k / fl, LVector3d::right()) *
+    LMatrix3d::rotate_mat(-v2[0] * k / fl, LVector3d::up());
+}
+
+
+LPoint2d StitchPSphereLens::
+project(const LVector3d &vec, double width_mm) const {
+  // A PSphere lens is a toroidal lens. It is independently curved in
+  // the horizontal and vertical directions.
+
+  LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right);
+  
+  // To compute the x position on the frame, we only need to consider
+  // the angle of the vector about the Z axis.  Project the vector
+  // into the XY plane to do this.
+
+  LVector2d xy(v3[0], v3[1]);
+
+  // The x position is the angle about the Z axis.
+  double x =
+    rad_2_deg(atan2(xy[0], xy[1])) * get_focal_length(width_mm) / k;
+  
+  // Unroll the Z angle, and the y position is the angle about the X
+  // axis.
+  xy = normalize(xy);
+  LVector2d yz(v3[0]*xy[0] + v3[1]*xy[1], v3[2]);
+  double y =
+    rad_2_deg(atan2(yz[1], yz[0])) * get_focal_length(width_mm) / k;
+
+  return LPoint2d(x, y);
+}
+
+LPoint2d StitchPSphereLens::
+project_left(const LVector3d &vec, double width_mm) const {
+  // This is just like project(), except that if the vertex extends
+  // below -180 degrees, it remains on the left side of the film
+  // (instead of wrapping around to the right side).
+
+  LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right);
+  LVector2d xy(v3[0], v3[1]);
+  double x =
+    (rad_2_deg(atan2(-xy[0], -xy[1])) - 180.0) *
+    get_focal_length(width_mm) / k;
+  
+  xy = normalize(xy);
+  LVector2d yz(v3[0]*xy[0] + v3[1]*xy[1], v3[2]);
+  double y =
+    rad_2_deg(atan2(yz[1], yz[0])) * get_focal_length(width_mm) / k;
+
+  return LPoint2d(x, y);
+}
+
+LPoint2d StitchPSphereLens::
+project_right(const LVector3d &vec, double width_mm) const {
+  // This is just like project(), except that if the vertex extends
+  // above 180 degrees, it remains on the right side of the film
+  // (instead of wrapping around to the left side).
+
+  LVector3d v3 = vec * LMatrix4d::convert_mat(CS_default, CS_zup_right);
+  LVector2d xy(v3[0], v3[1]);
+  double x =
+    (rad_2_deg(atan2(-xy[0], -xy[1])) + 180.0) *
+    get_focal_length(width_mm) / k;
+  
+  xy = normalize(xy);
+  LVector2d yz(v3[0]*xy[0] + v3[1]*xy[1], v3[2]);
+  double y =
+    rad_2_deg(atan2(yz[1], yz[0])) * get_focal_length(width_mm) / k;
+
+  return LPoint2d(x, y);
+}
+
+void StitchPSphereLens::
+draw_triangle(TriangleRasterizer &rast, const LMatrix3d &mm_to_pixels,
+	      double width_mm, const RasterizerVertex *v0,
+	      const RasterizerVertex *v1, const RasterizerVertex *v2) {
+  // A PSphere lens has two singularities, at the north and south
+  // poles, as well as a seam at 180 and -180 degrees.
+
+  // First, we reject any triangles within _singularity_tolerance of
+  // either pole, similar to the fisheye lens.
+
+  LVector2d xy0(dot(v0->_space, LVector3d::right()),
+		dot(v0->_space, LVector3d::forward()));
+  LVector2d xy1(dot(v1->_space, LVector3d::right()),
+		dot(v1->_space, LVector3d::forward()));
+  LVector2d xy2(dot(v2->_space, LVector3d::right()),
+		dot(v2->_space, LVector3d::forward()));
+
+  double z0 = dot(v0->_space, LVector3d::up());
+  double z1 = dot(v0->_space, LVector3d::up());
+  double z2 = dot(v0->_space, LVector3d::up());
+
+  if (z0 < 0.0 && z1 < 0.0 && z2 < 0.0) {
+    // A triangle on the southern hemisphere.  This projection will
+    // reverse the vertex order.
+    if (triangle_contains_circle(LPoint2d(0.0, 0.0), 
+				 _singularity_radius,
+				 xy0, xy2, xy1)) {
+      // The triangle does cross the singularity!  Reject it.
+      _singularity_detected |= 1;
+      return;
+    }
+  } else if (z0 > 0.0 && z1 > 0.0 && z2 > 0.0) {
+    // A triangle on the northern hemisphere.  This projection will
+    // preserve the vertex order.
+    if (triangle_contains_circle(LPoint2d(0.0, 0.0), 
+				 _singularity_radius,
+				 xy0, xy1, xy2)) {
+      // The triangle does cross the singularity!  Reject it.
+      _singularity_detected |= 2;
+      return;
+    }
+  }
+
+  // So the triangle is not at the north or south poles.  But it might
+  // cross the seam at the back.  If it does, we'll simply draw it
+  // twice: once at each side.
+
+  // Determine which quadrant each of the vertices is in.  The
+  // triangle crosses the seam if no vertices are in quadrants I and
+  // II, and some vertices are in quadrant III and others are in
+  // quadrant IV.
+
+  if (xy0[1] >= 0.0 || xy1[1] >= 0.0 || xy2[1] >= 0.0) {
+    // Some vertices are in quadrants I or II.
+    rast.draw_triangle(v0, v1, v2);
+
+  } else if (xy0[0] > 0.0 && xy1[0] > 0.0 && xy2[0] > 0.0) {
+    // All vertices are in quadrant IV.
+    rast.draw_triangle(v0, v1, v2);
+
+  } else if (xy0[0] < 0.0 && xy1[0] < 0.0 && xy2[0] < 0.0) {
+    // All vertices are in quadrant III.
+    rast.draw_triangle(v0, v1, v2);
+
+  } else {
+    // The triangle crosses the seam.  Draw it twice.
+    RasterizerVertex v0a = *v0;
+    RasterizerVertex v1a = *v1;
+    RasterizerVertex v2a = *v2;
+
+    v0a._p = project_left(v0a._space, width_mm) * mm_to_pixels;
+    v1a._p = project_left(v1a._space, width_mm) * mm_to_pixels;
+    v2a._p = project_left(v2a._space, width_mm) * mm_to_pixels;
+    rast.draw_triangle(&v0a, &v1a, &v2a);
+
+    v0a._p = project_right(v0a._space, width_mm) * mm_to_pixels;
+    v1a._p = project_right(v1a._space, width_mm) * mm_to_pixels;
+    v2a._p = project_right(v2a._space, width_mm) * mm_to_pixels;
+    rast.draw_triangle(&v0a, &v1a, &v2a);
+  }
+}
+
+void StitchPSphereLens::
+pick_up_singularity(TriangleRasterizer &rast, 
+		    const LMatrix3d &mm_to_pixels,
+		    const LMatrix3d &pixels_to_mm,
+		    const LMatrix3d &rotate,
+		    double width_mm, StitchImage *input) {
+  if (_singularity_detected & 2) {
+    nout << "Picking up north pole singularity\n";
+    // Determine what the bottom y pixel is of the circle around the
+    // north pole.
+    double d = deg_2_rad(_singularity_tolerance * 2.0);
+    LPoint2d pmm = project(LVector3d(0.0, sin(d), cos(d)), width_mm);
+    LPoint2d p = pmm * mm_to_pixels;
+    
+    int xsize = rast._output->get_x_size();
+    int ysize = rast._output->get_y_size();
+    
+    int bot_y = min((int)ceil(p[1]), ysize - 1);
+    RasterizerVertex v0;
+    v0._p.set(0.0, 0.0);
+    v0._uv.set(0.0, 0.0);
+    v0._space.set(0.0, 0.0, 0.0);
+    v0._alpha = 1.0;
+    v0._visibility = 0;
+    
+    int xi, yi;
+    for (yi = 0; yi <= bot_y; yi++) {
+      // Project xi point 1 to determine the radius.
+      v0._p.set(xi, yi);
+      v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate;
+      v0._uv = input->project(v0._space);
+      
+      for (xi = 0; xi < xsize; xi++) {
+	double last_u = v0._uv[0];
+	
+	v0._p.set(xi, yi);
+	v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate;
+	v0._uv = input->project(v0._space);
+	rast.draw_pixel(&v0, fabs(v0._uv[0] - last_u));
+      }
+    }
+  }
+  if (_singularity_detected & 1) {
+    nout << "Picking up south pole singularity\n";
+    // Determine what the top y pixel is of the circle around the
+    // south pole.
+    double d = deg_2_rad(_singularity_tolerance * 2.0);
+    LPoint2d pmm = project(LVector3d(0.0, sin(d), -cos(d)), width_mm);
+    LPoint2d p = pmm * mm_to_pixels;
+    
+    int xsize = rast._output->get_x_size();
+    int ysize = rast._output->get_y_size();
+    
+    int top_y = max((int)floor(p[1]), 0);
+    RasterizerVertex v0;
+    v0._p.set(0.0, 0.0);
+    v0._uv.set(0.0, 0.0);
+    v0._space.set(0.0, 0.0, 0.0);
+    v0._alpha = 1.0;
+    v0._visibility = 0;
+    
+    int xi, yi;
+    for (yi = top_y; yi < ysize; yi++) {
+      // Project xi point 1 to determine the radius.
+      v0._p.set(xi, yi);
+      v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate;
+      v0._uv = input->project(v0._space);
+      
+      for (xi = 0; xi < xsize; xi++) {
+	double last_u = v0._uv[0];
+	
+	v0._p.set(xi, yi);
+	v0._space = extrude(v0._p * pixels_to_mm, width_mm) * rotate;
+	v0._uv = input->project(v0._space);
+	rast.draw_pixel(&v0, fabs(v0._uv[0] - last_u));
+      }
+    }
+  }
+}
+
+void StitchPSphereLens::
+make_lens_command(StitchCommand *parent) {
+  StitchCommand *lens_cmd = new StitchCommand(parent, StitchCommand::C_lens);
+  StitchCommand *cmd;
+  cmd = new StitchCommand(lens_cmd, StitchCommand::C_psphere);
+  if (_flags & F_focal_length) {
+    cmd = new StitchCommand(lens_cmd, StitchCommand::C_focal_length);
+    cmd->set_length(_focal_length);
+  }
+  if (_flags & F_fov) {
+    cmd = new StitchCommand(lens_cmd, StitchCommand::C_fov);
+    cmd->set_number(_fov);
+  }
+}

+ 43 - 0
pandaapp/src/stitchbase/stitchPSphereLens.h

@@ -0,0 +1,43 @@
+// Filename: stitchPSphereLens.h
+// Created by:  drose (16Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHPSPHERELENS_H
+#define STITCHPSPHERELENS_H
+
+#include "stitchLens.h"
+
+class StitchPSphereLens : public StitchLens {
+public:
+  StitchPSphereLens();
+
+  virtual double get_focal_length(double width_mm) const;
+  virtual double get_hfov(double width_mm) const;
+
+  virtual LVector3d extrude(const LPoint2d &point_mm, double width_mm) const;
+  virtual LPoint2d project(const LVector3d &vec, double width_mm) const; 
+
+  LPoint2d project_left(const LVector3d &vec, double width_mm) const; 
+  LPoint2d project_right(const LVector3d &vec, double width_mm) const; 
+
+  virtual void draw_triangle(TriangleRasterizer &rast,
+			     const LMatrix3d &mm_to_pixels,
+			     double width_mm,
+			     const RasterizerVertex *v0, 
+			     const RasterizerVertex *v1, 
+			     const RasterizerVertex *v2);
+
+  virtual void pick_up_singularity(TriangleRasterizer &rast, 
+				   const LMatrix3d &mm_to_pixels,
+				   const LMatrix3d &pixels_to_mm,
+				   const LMatrix3d &rotate,
+				   double width_mm,
+				   StitchImage *input);
+
+  virtual void make_lens_command(StitchCommand *parent);
+};
+
+#endif
+
+

+ 34 - 0
pandaapp/src/stitchbase/stitchParser.h

@@ -0,0 +1,34 @@
+#define	NUMBER	257
+#define	IDENTIFIER	258
+#define	STRING	259
+#define	KW_DEFINE	260
+#define	KW_LENS	261
+#define	KW_INPUT_IMAGE	262
+#define	KW_OUTPUT_IMAGE	263
+#define	KW_PERSPECTIVE	264
+#define	KW_FISHEYE	265
+#define	KW_CYLINDRICAL	266
+#define	KW_PSPHERE	267
+#define	KW_FOCAL_LENGTH	268
+#define	KW_FOV	269
+#define	KW_SINGULARITY_TOLERANCE	270
+#define	KW_RESOLUTION	271
+#define	KW_FILENAME	272
+#define	KW_POINT	273
+#define	KW_SHOW_POINTS	274
+#define	KW_IMAGE_SIZE	275
+#define	KW_FILM_SIZE	276
+#define	KW_GRID	277
+#define	KW_UNTEXTURED_COLOR	278
+#define	KW_HPR	279
+#define	KW_LAYERS	280
+#define	KW_STITCH	281
+#define	KW_POINTS	282
+#define	KW_USING	283
+#define	KW_IN	284
+#define	KW_MM	285
+#define	KW_CM	286
+#define	KW_P	287
+
+
+extern YYSTYPE stitchyylval;

+ 417 - 0
pandaapp/src/stitchbase/stitchParser.yxx

@@ -0,0 +1,417 @@
+// Filename: stitchParser.y
+// Created by:  drose (08Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+%{
+
+#include "stitchParserDefs.h"
+#include "stitchLexerDefs.h"
+#include "stitchCommand.h"
+
+////////////////////////////////////////////////////////////////////
+// Defining the interface to the parser.
+////////////////////////////////////////////////////////////////////
+
+#define YYERROR_VERBOSE
+
+typedef vector<StitchCommand *> CommandStack;
+static CommandStack cstack;
+static StitchCommand *parent; 
+
+void
+stitch_init_parser(istream &in, const string &filename,
+		   StitchCommand *tos) {
+  stitch_init_lexer(in, filename);
+  parent = tos;
+  cstack.push_back(parent);
+}
+
+%}
+
+%token <number> NUMBER
+%token <str> IDENTIFIER
+%token <str> STRING
+
+%token KW_DEFINE
+%token KW_LENS
+%token KW_INPUT_IMAGE
+%token KW_OUTPUT_IMAGE
+%token KW_PERSPECTIVE
+%token KW_FISHEYE
+%token KW_CYLINDRICAL
+%token KW_PSPHERE
+%token KW_FOCAL_LENGTH
+%token KW_FOV
+%token KW_SINGULARITY_TOLERANCE
+%token KW_RESOLUTION
+%token KW_FILENAME
+%token KW_POINT
+%token KW_SHOW_POINTS
+%token KW_IMAGE_SIZE
+%token KW_FILM_SIZE
+%token KW_GRID
+%token KW_UNTEXTURED_COLOR
+%token KW_HPR
+%token KW_LAYERS
+%token KW_STITCH
+%token KW_POINTS
+%token KW_USING
+%token KW_IN
+%token KW_MM
+%token KW_CM
+%token KW_P
+
+%type <command> command
+%type <command> group_command
+%type <command> simple_command
+%type <number> length
+%type <number> length_units
+%type <number> resolution
+%type <number> resolution_units
+%type <vec> point
+%type <vec> vec2
+%type <vec> vec3
+%type <vec> length_pair
+%type <vec> color
+%type <str> name
+%type <str> optional_name
+
+%%
+
+stitch_file:
+	commands
+
+commands:
+	empty
+	| commands command
+{
+  //  parent->add_nested($2);
+}
+	| commands KW_POINTS '{' points_list '}'
+	;
+
+command:
+	group_command
+{
+  parent = $1;
+  cstack.push_back(parent);
+}
+	nested_commands
+{
+  cstack.pop_back();
+  parent = cstack.back();
+}
+	| simple_command ';'
+{
+  $$ = $1;
+}
+	;
+
+nested_commands:
+	'{' commands '}'
+	| ';'
+	;
+
+group_command:
+	KW_DEFINE name
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_define);
+  $$->set_name($2);
+}
+	| KW_LENS optional_name
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_lens);
+  $$->set_name($2);
+}
+	| KW_INPUT_IMAGE optional_name
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_input_image);
+  $$->set_name($2);
+}
+	| KW_OUTPUT_IMAGE optional_name
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_output_image);
+  $$->set_name($2);
+}
+	| KW_STITCH optional_name
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_stitch);
+  $$->set_name($2);
+}
+	| KW_USING
+{
+  cstack.push_back(new StitchCommand(parent, StitchCommand::C_using));
+}
+	using_list
+{
+  $$ = cstack.back();
+  cstack.pop_back();
+}
+	;
+
+simple_command:
+	KW_PERSPECTIVE
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_perspective);
+}
+	| KW_FISHEYE
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_fisheye);
+}
+	| KW_CYLINDRICAL
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_cylindrical);
+}
+	| KW_PSPHERE
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_psphere);
+}
+	| KW_FOCAL_LENGTH length
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_focal_length);
+  $$->set_length($2);
+}
+	| KW_FOV NUMBER
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_fov);
+  $$->set_number($2);
+}
+	| KW_SINGULARITY_TOLERANCE NUMBER
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_singularity_tolerance);
+  $$->set_number($2);
+}
+	| KW_RESOLUTION resolution
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_resolution);
+  $$->set_resolution($2);
+}
+	| KW_FILENAME STRING
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_filename);
+  $$->set_str($2);
+}
+	| KW_POINT name point
+{
+  if ($<num_components>3 == 2) {
+    $$ = new StitchCommand(parent, StitchCommand::C_point2d);
+    $$->set_point2d((const LPoint2d &)$3);
+  } else {
+    $$ = new StitchCommand(parent, StitchCommand::C_point3d);
+    $$->set_point3d((const LPoint3d &)$3);
+  }
+  $$->set_name($2);
+}
+	| KW_SHOW_POINTS NUMBER color
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_show_points);
+  $$->set_number($2);
+  $$->set_color($3);
+}
+	| KW_IMAGE_SIZE vec2
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_image_size);
+  $$->set_point2d((const LPoint2d &)$2);
+}
+	| KW_FILM_SIZE length_pair
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_film_size);
+  $$->set_length_pair((const LPoint2d &)$2);
+}
+	| KW_GRID vec2
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_grid);
+  $$->set_point2d((const LPoint2d &)$2);
+}
+	| KW_UNTEXTURED_COLOR color
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_untextured_color);
+  $$->set_color((const Colord &)$2);
+}
+	| KW_HPR vec3
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_hpr);
+  $$->set_point3d((const LPoint3d &)$2);
+}
+	| KW_LAYERS
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_layers);
+}
+	| IDENTIFIER
+{
+  $$ = new StitchCommand(parent, StitchCommand::C_user_command);
+  if (!$$->add_using($1)) {
+    yyerror("Undefined identifier " + $1);
+  }
+}
+	;
+
+using_list:
+	IDENTIFIER
+{
+  if (!cstack.back()->add_using($1)) {
+    yyerror("Undefined identifier " + $1);
+  }
+}
+	| using_list ',' IDENTIFIER	
+{
+  if (!cstack.back()->add_using($3)) {
+    yyerror("Undefined identifier " + $3);
+  }
+}
+	;
+
+points_list:
+	empty
+	| points_list name point ';'
+{
+  StitchCommand *cmd;
+  if ($<num_components>3 == 2) {
+    cmd = new StitchCommand(parent, StitchCommand::C_point2d);
+    cmd->set_point2d((const LPoint2d &)$3);
+  } else {
+    cmd = new StitchCommand(parent, StitchCommand::C_point3d);
+    cmd->set_point3d((const LPoint3d &)$3);
+  }
+  cmd->set_name($2);
+  //  parent->add_nested(cmd);
+}
+	;
+  
+
+length:
+	NUMBER length_units
+{
+  $$ = $1 * $2;   // convert to mm
+}
+	;
+
+length_units:
+	KW_IN
+{
+  $$ = 25.4;   // in to mm
+}
+	| KW_CM
+{
+  $$ = 10.0;   // cm to mm
+}
+	| KW_MM
+{
+  $$ = 1.0;
+}
+	;
+
+
+resolution:
+	NUMBER resolution_units
+{
+  $$ = $1 * $2;    // convert to pixels per mm
+}
+	;
+
+resolution_units:
+	KW_P '/' length_units
+{
+  $$ = 1.0 / $3;
+}
+	;
+
+point:
+	'(' NUMBER NUMBER ')'
+{
+  $$.set($2, $3, 0.0, 0.0);
+  $<num_components>$ = 2;
+}
+	| '(' NUMBER ',' NUMBER ')'
+{
+  $$.set($2, $4, 0.0, 0.0);
+  $<num_components>$ = 2;
+}
+	| '(' NUMBER NUMBER NUMBER ')'
+{
+  $$.set($2, $3, $4, 0.0);
+  $<num_components>$ = 3;
+}
+	| '(' NUMBER ',' NUMBER ',' NUMBER ')'
+{
+  $$.set($2, $4, $6, 0.0);
+  $<num_components>$ = 3;
+}
+	;
+
+vec2:
+	'(' NUMBER NUMBER ')'
+{
+  $$.set($2, $3, 0.0, 0.0);
+  $<num_components>$ = 2;
+}
+	| '(' NUMBER ',' NUMBER ')'
+{
+  $$.set($2, $4, 0.0, 0.0);
+  $<num_components>$ = 2;
+}
+	;
+
+vec3:	'(' NUMBER NUMBER NUMBER ')'
+{
+  $$.set($2, $3, $4, 0.0);
+  $<num_components>$ = 3;
+}
+	| '(' NUMBER ',' NUMBER ',' NUMBER ')'
+{
+  $$.set($2, $4, $6, 0.0);
+  $<num_components>$ = 3;
+}
+	;
+
+length_pair:
+	'(' length length ')'
+{
+  $$.set($2, $3, 0.0, 0.0);
+  $<num_components>$ = 2;
+}
+	| '(' length ',' length ')'
+{
+  $$.set($2, $4, 0.0, 0.0);
+  $<num_components>$ = 2;
+}
+	;
+
+color:
+	'(' NUMBER NUMBER NUMBER ')'
+{
+  $$.set($2, $3, $4, 1.0);
+  $<num_components>$ = 3;
+}
+	| '(' NUMBER ',' NUMBER ',' NUMBER ')'
+{
+  $$.set($2, $4, $6, 1.0);
+  $<num_components>$ = 3;
+}
+	| '(' NUMBER NUMBER NUMBER NUMBER ')'
+{
+  $$.set($2, $3, $4, $5);
+  $<num_components>$ = 4;
+}
+	| '(' NUMBER ',' NUMBER ',' NUMBER ',' NUMBER ')'
+{
+  $$.set($2, $4, $6, $8);
+  $<num_components>$ = 4;
+}
+	;
+
+name:
+	IDENTIFIER
+	;
+
+optional_name:
+	IDENTIFIER
+	| empty
+{
+  $$ = "";
+}
+	;
+
+empty:
+	;

+ 36 - 0
pandaapp/src/stitchbase/stitchParserDefs.h

@@ -0,0 +1,36 @@
+// Filename: stitchParserDefs.h
+// Created by:  drose (08Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHPARSERDEFS_H
+#define STITCHPARSERDEFS_H
+
+#include <pandatoolbase.h>
+
+#include <luse.h>
+
+class StitchCommand;
+
+int stitchyyparse();
+
+void stitch_init_parser(istream &in, const string &filename,
+			StitchCommand *tos);
+
+// This structure holds the return value for each token.
+// Traditionally, this is a union, and is declared with the %union
+// declaration in the parser.y file, but unions are pretty worthless
+// in C++ (you can't include an object that has member functions in a
+// union), so we'll use a class instead.  That means we need to
+// declare it externally, here.
+
+class YYSTYPE {
+public:
+  double number;
+  string str;
+  StitchCommand *command;
+  LVecBase4d vec;
+  int num_components;
+};
+
+#endif

+ 79 - 0
pandaapp/src/stitchbase/stitchPerspectiveLens.cxx

@@ -0,0 +1,79 @@
+// Filename: stitchPerspectiveLens.cxx
+// Created by:  drose (09Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchPerspectiveLens.h"
+#include "stitchCommand.h"
+
+#include <pandatoolbase.h>
+#include <deg_2_rad.h>
+
+#include <math.h>
+
+StitchPerspectiveLens::
+StitchPerspectiveLens() {
+}
+
+void StitchPerspectiveLens::
+set_hfov(double fov_deg) {
+  StitchLens::set_hfov(fov_deg);
+  _tan_fov = tan(deg_2_rad(_fov / 2.0)) * 2.0;
+}
+
+double StitchPerspectiveLens::
+get_focal_length(double width_mm) const {
+  if (_flags & F_focal_length) {
+    return _focal_length;
+  }
+  if (_flags & F_fov) {
+    return width_mm / _tan_fov;
+  }
+  return 0.0;
+}
+
+double StitchPerspectiveLens::
+get_hfov(double width_mm) const {
+  if (_flags & F_fov) {
+    return _fov;
+  }
+  if (_flags & F_focal_length) {
+    return 2.0 * rad_2_deg(atan(width_mm / (2.0 * _focal_length)));
+  }
+  return 0.0;
+}
+
+LVector3d StitchPerspectiveLens::
+extrude(const LPoint2d &point_mm, double width_mm) const {
+  return LVector3d::rfu(point_mm[0], get_focal_length(width_mm), point_mm[1]);
+}
+
+LPoint2d StitchPerspectiveLens::
+project(const LVector3d &vec, double width_mm) const {
+  double r = dot(vec, LVector3d::right());
+  double f = dot(vec, LVector3d::forward());
+  double u = dot(vec, LVector3d::up());
+  if (f <= 0.0) {
+    // If the point is in or behind our view plane, project it out to
+    // as near to infinity as we can comfortably manage.
+    return LPoint2d(r / 0.000001, u / 0.000001);
+  } else {
+    return LPoint2d(r / f * get_focal_length(width_mm), 
+		    u / f * get_focal_length(width_mm));
+  }
+}
+
+void StitchPerspectiveLens::
+make_lens_command(StitchCommand *parent) {
+  StitchCommand *lens_cmd = new StitchCommand(parent, StitchCommand::C_lens);
+  StitchCommand *cmd;
+  cmd = new StitchCommand(lens_cmd, StitchCommand::C_perspective);
+  if (_flags & F_focal_length) {
+    cmd = new StitchCommand(lens_cmd, StitchCommand::C_focal_length);
+    cmd->set_length(_focal_length);
+  }
+  if (_flags & F_fov) {
+    cmd = new StitchCommand(lens_cmd, StitchCommand::C_fov);
+    cmd->set_number(_fov);
+  }
+}

+ 31 - 0
pandaapp/src/stitchbase/stitchPerspectiveLens.h

@@ -0,0 +1,31 @@
+// Filename: stitchPerspectiveLens.h
+// Created by:  drose (09Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHPERSPECTIVELENS_H
+#define STITCHPERSPECTIVELENS_H
+
+#include "stitchLens.h"
+
+class StitchPerspectiveLens : public StitchLens {
+public:
+  StitchPerspectiveLens();
+
+  virtual void set_hfov(double fov_deg);
+
+  virtual double get_focal_length(double width_mm) const;
+  virtual double get_hfov(double width_mm) const;
+
+  virtual LVector3d extrude(const LPoint2d &point_mm, double width_mm) const;
+  virtual LPoint2d project(const LVector3d &vec, double width_mm) const; 
+
+  virtual void make_lens_command(StitchCommand *parent);
+  
+private:
+  double _tan_fov;
+};
+
+#endif
+
+

+ 20 - 0
pandaapp/src/stitchbase/stitchPoint.cxx

@@ -0,0 +1,20 @@
+// Filename: stitchPoint.cxx
+// Created by:  drose (04Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchPoint.h"
+
+StitchPoint::
+StitchPoint(const string &name) :
+  _name(name)
+{
+  _space_known = false;
+}
+
+void StitchPoint::
+set_space(const LVector3d &space) {
+  _space_known = true;
+  _space = space;
+}
+

+ 30 - 0
pandaapp/src/stitchbase/stitchPoint.h

@@ -0,0 +1,30 @@
+// Filename: stitchPoint.h
+// Created by:  drose (04Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHPOINT_H
+#define STITCHPOINT_H
+
+#include <luse.h>
+
+#include <string>
+
+class StitchImage;
+
+class StitchPoint {
+public:
+  StitchPoint(const string &name);
+
+  void set_space(const LVector3d &space);
+
+  string _name;
+  bool _space_known;
+  LVector3d _space;
+
+  typedef set<StitchImage *> Images;
+  Images _images;
+};
+
+#endif
+

+ 464 - 0
pandaapp/src/stitchbase/stitcher.cxx

@@ -0,0 +1,464 @@
+// Filename: stitcher.cxx
+// Created by:  drose (09Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitcher.h"
+#include "stitchImage.h"
+#include "stitchPoint.h"
+
+#include <rotate_to.h>
+#include <compose_matrix.h>
+
+
+Stitcher::MatchingPoint::
+MatchingPoint(StitchPoint *p, const LPoint2d &got_uv) :
+  _p(p),
+  _got_uv(got_uv)
+{
+  _need_uv.set(0.0, 0.0);
+  _diff = 0.0;
+}
+
+Stitcher::
+Stitcher() {
+  _show_points = false;
+}
+
+Stitcher::
+~Stitcher() {
+}
+
+void Stitcher::
+add_image(StitchImage *image) {
+  image->_index = _images.size();
+  _images.push_back(image);
+
+  // Record all of the points in the image as well.
+  StitchImage::Points::const_iterator pi;
+  for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) {
+    string name = (*pi).first;
+
+    Points::iterator ppi;
+    ppi = _points.find(name);
+    StitchPoint *sp;
+    if (ppi != _points.end()) {
+      // Previously used point.
+      sp = (*ppi).second;
+    } else {
+      // New point.
+      sp = new StitchPoint(name);
+      _points.insert(Points::value_type(name, sp));
+    }
+    sp->_images.insert(image);
+  }
+}
+
+void Stitcher::
+add_point(const string &name, const LVector3d &vec) {
+  Points::iterator ppi;
+  ppi = _points.find(name);
+  StitchPoint *sp;
+  if (ppi != _points.end()) {
+    // Previously used point.
+    sp = (*ppi).second;
+  } else {
+    // New point.
+    sp = new StitchPoint(name);
+    _points.insert(Points::value_type(name, sp));
+  }
+
+  sp->set_space(normalize(vec));
+  _loose_points.push_back(sp);
+}
+
+
+void Stitcher::
+show_points(double radius, const Colord &color) {
+  _show_points = true;
+  _point_radius = radius;
+  _point_color = color;
+}
+
+
+void Stitcher::
+stitch() {
+  if (_images.empty()) {
+    return;
+  }
+
+  // First place the reference image.  All of its points are fixed
+  // where they are.
+  if (_loose_points.empty()) {
+    StitchImage *image = _images.front();
+    assert(image != NULL);
+    
+    StitchImage::Points::const_iterator pi;
+    for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) {
+      string name = (*pi).first;
+      LPoint2d uv = (*pi).second;
+      
+      Points::iterator ppi;
+      ppi = _points.find(name);
+      assert(ppi != _points.end());
+      StitchPoint *sp = (*ppi).second;
+      
+      LVector3d space = normalize(image->extrude(uv));
+      sp->set_space(space);
+    }
+
+    _placed.push_back(image);
+
+    // Report the reference image.
+    nout << "\n" << *image << "\n";
+    _images.erase(_images.begin());
+  }
+
+  // Now place each of the other images relative to the already-known
+  // points.
+  double net_stitch_score = 0.0;
+  bool done = false;
+
+  while (!_images.empty() && !done) {
+    // Find the image with the greatest number of known points.
+    int max_score = 0;
+    Images::iterator best_image = _images.end();
+
+    Images::iterator ii;
+    for (ii = _images.begin(); ii != _images.end(); ++ii) {
+      int score = score_image(*ii);
+      if (score > max_score) {
+	max_score = score;
+	best_image = ii;
+      }
+    }
+    if (best_image == _images.end()) {
+      // Bad news.  None of the images had a score greater than zero,
+      // so we can't stitch them in--not enough shared points.
+      done = true;
+
+    } else {
+      // Now stitch this image in and remove it from the set.
+      net_stitch_score += stitch_image(*best_image);
+      _placed.push_back(*best_image);
+      _images.erase(best_image);
+    }
+  }
+
+  // Any of the unplaced images with explicit hpr's get placed where
+  // they are.
+  Images::iterator ii = _images.begin();
+  while (ii != _images.end()) {
+    StitchImage *image = (*ii);
+    if (image->_hpr_set) {
+      net_stitch_score += stitch_image(image);
+      _placed.push_back(image);
+      _images.erase(ii);
+    } else {
+      ++ii;
+    }
+  }
+
+  if (!_images.empty()) {
+    nout << "Not enough shared points; " << _images.size()
+	 << " images remain unstitched.\n";
+  }
+
+  nout << "Net score is " << net_stitch_score << "\n";
+
+  // Reorder all of the images by index number order.
+  sort(_placed.begin(), _placed.end(), StitchImageByIndex());
+
+  // And feather the edges between them nicely.  We don't need to
+  // feather the first image.
+  if (_placed.size() > 1) {
+    nout << "Feathering edges\n";
+    Images::iterator ii;
+    ii = _placed.begin();
+    for (++ii; ii != _placed.end(); ++ii) {
+      feather_image(*ii);
+    }
+  }
+}
+
+int Stitcher::
+score_image(StitchImage *image) {
+  // Give the image one point for each StitchPoint it has that has a
+  // known location in space.
+  int score = 0;
+
+  StitchImage::Points::const_iterator pi;
+  for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) {
+    string name = (*pi).first;
+    
+    Points::iterator ppi;
+    ppi = _points.find(name);
+    assert(ppi != _points.end());
+    StitchPoint *sp = (*ppi).second;
+    
+    if (sp->_space_known) {
+      score++;
+    }
+  }
+
+  // We must have at least two points in common to stitch an image.
+  if (score < 2) {
+    score = 0;
+  }
+  return score;
+}
+
+
+double Stitcher::
+stitch_image(StitchImage *image) {
+  // First, collect all the points we have that exist somewhere in
+  // known space.
+  MatchingPoints mp;
+
+  StitchImage::Points::const_iterator pi;
+  for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) {
+    string name = (*pi).first;
+    LPoint2d uv = (*pi).second;
+    
+    Points::iterator ppi;
+    ppi = _points.find(name);
+    assert(ppi != _points.end());
+    StitchPoint *sp = (*ppi).second;
+    
+    if (sp->_space_known) {
+      mp.push_back(MatchingPoint(sp, uv));
+    }
+  }
+
+  // We need at least two points in common, or one point and an
+  // explicit hpr to stitch.
+  if (mp.size() < 2 && !image->_hpr_set) {
+    nout << "cannot stitch " << image->get_name() << "\n\n";
+    return 0.0;
+  }
+
+  double best_score = 0.0;
+  
+  if (mp.empty()) {
+    // If we have no points, we can at least place it where the hpr says to.
+    nout << *image << "placed explicitly.\n\n";
+
+  } else {
+    // If we have at least one point, we can stitch something.
+
+    // Reset the image's total transform, since we'll be changing it.
+    image->clear_transform();
+    
+    // Find the best match.
+    int best_i = -1;
+    int best_j = -1;
+    
+    if (mp.size() < 2) {
+      // If we don't have two points, there's nothing to choose.
+      best_i = 0;
+      best_j = 0;
+    } else {
+      for (int i = 0; i < (int)mp.size(); i++) {
+	for (int j = 0; j < (int)mp.size(); j++) {
+	  if (j != i) {
+	    LMatrix3d rot;
+	    double score = try_match(image, rot, mp, i, j);
+	    if (score < best_score || best_i == -1) {
+	      best_i = i;
+	      best_j = j;
+	      best_score = score;
+	    }
+	  }
+	}
+      }
+    }
+
+    // Now go back and actually use the best match.
+    LMatrix3d rot;
+    try_match(image, rot, mp, best_i, best_j);
+
+    image->set_transform(rot);
+
+    if (mp.size() < 2) {
+      nout << *image << "placed semi-explicitly.\n\n";
+    } else {
+      nout << *image << "score is " << best_score << "\n\n";
+    }
+
+    // Now compute the degree of success.
+    MatchingPoints::iterator mi;
+    for (mi = mp.begin(); mi != mp.end(); ++mi) {
+      (*mi)._need_uv = image->project((*mi)._p->_space);
+      (*mi)._diff = (*mi)._need_uv - (*mi)._got_uv;
+    }
+
+    // Now morph the image out the last few pixels so that all the
+    // points will match up exactly.
+
+    int x_verts = image->get_x_verts(); 
+    int y_verts = image->get_y_verts(); 
+    image->_morph.init(x_verts, y_verts);
+    int x, y;
+    for (y = 0; y < y_verts; y++) {
+      for (x = 0; x < x_verts; x++) {
+	LPoint2d p = image->get_grid_uv(x, y);
+	LVector2d offset(0.0, 0.0);
+	double net = 0.0;
+	
+	MatchingPoints::const_iterator cmi;
+	bool done = false;
+	for (cmi = mp.begin(); cmi != mp.end() && !done; ++cmi) {
+	  LVector2d v = p - (*cmi)._got_uv;
+	  double d = pow(dot(v, v), 0.1);
+	  if (d < 0.0001) {
+	    // This one is dead on; stop here and never mind.
+	    offset = (*cmi)._diff;
+	    net = 1.0;
+	    done = true;
+	  } else {
+	    double scale = 1.0 / d;
+	    offset += (*cmi)._diff * scale;
+	    net += scale;
+	  }
+	}
+	
+	offset /= net;
+	image->_morph._table[y][x]._p[MorphGrid::TT_out] += offset;
+      }
+    }
+    image->_morph.recompute();
+    
+
+  /*
+
+  for (mi = mp.begin(); mi != mp.end(); ++mi) {
+    LVector3d va = normalize(LVector3d(image->extrude((*mi)._got_uv)));
+    LVector3d vb = (*mi)._p->_space;
+    nout << 1000.0 * (1.0 - dot(va, vb)) << " for " << (*mi)._p->_name 
+	 << "\n   at " << va << " vs. " << vb << "\n";
+  }
+  nout << "\n";
+
+  // Report the final results, including the morphs, to the user.
+  for (mi = mp.begin(); mi != mp.end(); ++mi) {
+    (*mi)._need_uv = image->project((*mi)._p->_space);
+    (*mi)._diff = (*mi)._need_uv - (*mi)._got_uv;
+    
+    nout << (*mi)._p->_name
+	 << " "<< (*mi)._need_uv << " vs. " << (*mi)._got_uv
+	 << " diff is " << length((*mi)._diff * image->_uv_to_pixels)
+	 << " pixels\n";
+  }
+  */
+
+    // Finally, mark all of the other points in this image as now known
+    // points in space.
+    for (pi = image->_points.begin(); pi != image->_points.end(); ++pi) {
+      string name = (*pi).first;
+      LPoint2d uv = (*pi).second;
+      
+      Points::iterator ppi;
+      ppi = _points.find(name);
+      assert(ppi != _points.end());
+      StitchPoint *sp = (*ppi).second;
+      
+      if (!sp->_space_known) {
+	LVector3d space = normalize(image->extrude(uv));
+	sp->set_space(space);
+      }
+    }
+  }
+
+  return best_score;
+}
+
+void Stitcher::
+feather_image(StitchImage *image) {
+  // Feather the edges of the image wherever it overlaps with an image
+  // we have laid down previously.  We do this by first determining
+  // which morph points overlap with some other image.
+  int x_verts = image->get_x_verts(); 
+  int y_verts = image->get_y_verts(); 
+  
+  if (image->_morph.is_empty()) {
+    image->_morph.init(x_verts, y_verts);
+    image->_morph.recompute();
+  }
+
+  int x, y;
+  for (y = 0; y < y_verts; y++) {
+    for (x = 0; x < x_verts; x++) {
+      LVector3d space = image->get_grid_vector(x, y);
+      Images::const_iterator ii;
+      for (ii = _placed.begin(); 
+	   ii != _placed.end() && 
+	     !image->_morph._table[y][x]._over_another; 
+	   ++ii) {
+	StitchImage *other = (*ii);
+	if (other->_index < image->_index) {
+	  LPoint2d uv = other->project(space);
+	  if (uv[0] >= 0.0 && uv[0] <= 1.0 &&
+	      uv[1] >= 0.0 && uv[1] <= 1.0) {
+	    // This point is over the other image.
+	    image->_morph._table[y][x]._over_another = true;
+	  }
+	}
+      }
+    }
+  }
+
+  image->_morph.fill_alpha();
+}
+
+
+double Stitcher::
+try_match(StitchImage *image, LMatrix3d &rot,
+	  const Stitcher::MatchingPoints &mp, int zero, int one) {
+
+  // Now rotate this image relative to the other so the first pair of
+  // points exactly coincide.
+  LVector3d v0a = normalize(image->extrude(mp[zero]._got_uv));
+  LVector3d v0b = mp[zero]._p->_space;
+
+  rotate_to(rot, v0a, v0b);
+
+  if (zero == one) {
+    // Here's a special case: only one matching point.  In this case,
+    // we roll by the explicit angle given by the user.
+    if (image->_hpr_set) {
+      rot = rot * LMatrix3d::rotate_mat(image->_hpr[2], v0b);
+    }
+
+  } else {
+    // Now (v0a * rot) == v0b.  Roll about this vector till the
+    // second pair of points comes as close as possible to coinciding.
+    LVector3d v1a = normalize(image->extrude(mp[one]._got_uv));
+    LVector3d v1b = mp[one]._p->_space;
+    
+    v1a = v1a * rot;
+    
+    // We need to determine the appropriate angle to roll.  This is the
+    // angle between the plane that contains v0 and v1a, and the plane
+    // that contains v0 and v1b.
+    
+    LVector3d normal_a = normalize(cross(v0b, v1a));
+    LVector3d normal_b = normalize(cross(v0b, v1b));
+    
+    double cos_theta = dot(normal_a, normal_b);
+    double theta = rad_2_deg(acos(cos_theta));
+    
+    rot = rot * LMatrix3d::rotate_mat(-theta, v0b);
+  }
+
+  // Now compute the score.
+  double score = 0.0;
+
+  MatchingPoints::const_iterator mi;
+  for (mi = mp.begin(); mi != mp.end(); ++mi) {
+    LVector3d va = normalize(LVector3d(image->extrude((*mi)._got_uv) * rot));
+    LVector3d vb = (*mi)._p->_space;
+    score += 1.0 - dot(va, vb);
+  }
+
+  return 1000.0 * score;
+}

+ 62 - 0
pandaapp/src/stitchbase/stitcher.h

@@ -0,0 +1,62 @@
+// Filename: stitcher.h
+// Created by:  drose (09Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHER_H
+#define STITCHER_H
+
+#include <luse.h>
+
+#include <string>
+#include <map>
+
+class StitchPoint;
+class StitchImage;
+
+class Stitcher {
+public:
+  Stitcher();
+  ~Stitcher();
+
+  void add_image(StitchImage *image);
+  void add_point(const string &name, const LVector3d &vec);
+  void show_points(double radius, const Colord &color);
+  void stitch();
+
+  typedef vector<StitchImage *> Images;
+  Images _placed;
+
+  typedef vector<StitchPoint *> LoosePoints;
+  LoosePoints _loose_points;
+  bool _show_points;
+  double _point_radius;
+  Colord _point_color;
+
+private:
+  class MatchingPoint {
+  public:
+    MatchingPoint(StitchPoint *p, const LPoint2d &got_uv);
+
+    StitchPoint *_p;
+    LPoint2d _need_uv; 
+    LPoint2d _got_uv; 
+    LVector2d _diff;
+  };
+  typedef vector<MatchingPoint> MatchingPoints;
+
+  int score_image(StitchImage *image);
+  double stitch_image(StitchImage *image);
+  void feather_image(StitchImage *image);
+
+  double try_match(StitchImage *image, LMatrix3d &rot,
+		   const MatchingPoints &mp, int zero, int one);
+
+  Images _images;
+
+  typedef map<string, StitchPoint *> Points;
+  Points _points;
+};
+
+
+#endif

+ 54 - 0
pandaapp/src/stitchbase/triangle.cxx

@@ -0,0 +1,54 @@
+// Filename: triangle.cxx
+// Created by:  drose (16Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "triangle.h"
+
+inline int
+is_right(const LVector2d &v1, const LVector2d &v2) {
+  return (-v1[0] * v2[1] + v1[1] * v2[0]) < 0.0;
+}
+
+bool
+triangle_contains_point(const LPoint2d &p, const LPoint2d &v0,
+			const LPoint2d &v1, const LPoint2d &v2) {
+  // In the case of a triangle defined with points in counterclockwise
+  // order, a point is interior to the triangle iff the point is not
+  // right of each of the edges.
+
+  if (is_right(p - v0, v1 - v0)) {
+    return false;
+  }
+  if (is_right(p - v1, v2 - v1)) {
+    return false;
+  }
+  if (is_right(p - v2, v0 - v2)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool
+triangle_contains_circle(const LPoint2d &p, double radius,
+			 const LPoint2d &v0,
+			 const LPoint2d &v1, const LPoint2d &v2) {
+  // This is a cheesy hack.  Instead of performing an actual
+  // triangle-circle intersection test, we simply move the point
+  // radius units closer to the centroid of the triangle, and test
+  // that point for intersection.
+
+  LPoint2d centroid = (v0 + v1 + v2) / 3.0;
+
+  LVector2d vec = centroid - p;
+  double d = length(vec);
+  if (d <= radius) {
+    // We were already closer than radius distance from the centroid;
+    // this is an automatic intersection.
+    return true;
+  }
+
+  LPoint2d new_p = p + radius * (vec / d);
+  return triangle_contains_point(new_p, v0, v1, v2);
+}

+ 24 - 0
pandaapp/src/stitchbase/triangle.h

@@ -0,0 +1,24 @@
+// Filename: triangle.h
+// Created by:  drose (16Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TRIANGLE_H
+#define TRIANGLE_H
+
+#include <luse.h>
+
+// A handy triangle utility.  Maybe more later.
+
+// The triangle must be defined with vertices in counter-clockwise
+// order.
+bool
+triangle_contains_point(const LPoint2d &p, const LPoint2d &v0,
+			const LPoint2d &v1, const LPoint2d &v2);
+
+bool
+triangle_contains_circle(const LPoint2d &p, double radius,
+			 const LPoint2d &v0,
+			 const LPoint2d &v1, const LPoint2d &v2);
+
+#endif

+ 571 - 0
pandaapp/src/stitchbase/triangleRasterizer.cxx

@@ -0,0 +1,571 @@
+// Filename: triangleRasterizer.cxx
+// Created by:  drose (06Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "triangleRasterizer.h"
+#include "stitchImage.h"
+
+// Inline function declared up here for the forward reference.
+inline void TriangleRasterizer::
+filter_pixel(RGBColord &rgb, double &alpha,
+	     double s, double t,
+	     double dsdx, double dtdx, double dsdy, double dtdy) {
+  filter_pixel(rgb, alpha, s, t, 
+	       max(max(dsdx, dtdx), max(dsdy, dtdy)) / 2.0);
+}
+
+
+TriangleRasterizer::Edge::
+Edge(const RasterizerVertex *v0, const RasterizerVertex *v1) :
+  _v0(v0), _v1(v1)
+{
+  _dx = v1->_p[0] - v0->_p[0];
+  _dy = v1->_p[1] - v0->_p[1];
+}
+
+TriangleRasterizer::
+TriangleRasterizer() {
+  _output = NULL;
+  _input = NULL;
+  _read_input = false;
+  _texture = NULL;
+  _filter_output = false;
+  _untextured_color.set(1.0, 1.0, 1.0, 1.0);
+}
+
+void TriangleRasterizer::
+draw_triangle(const RasterizerVertex *v0, 
+	      const RasterizerVertex *v1, 
+	      const RasterizerVertex *v2) {
+  if ((v0->_visibility & v1->_visibility & v2->_visibility) != 0) {
+    // All three vertices are out of bounds in the same direction, so
+    // the triangle is completely out of bounds.  Don't bother trying
+    // to draw it.
+    return;
+  }
+
+  assert(_output != NULL);
+  if (!_read_input) {
+    read_input();
+  }
+  
+  double oneOverArea;
+  const RasterizerVertex *vMin, *vMid, *vMax;
+  /* Y(vMin)<=Y(vMid)<=Y(vMax) */
+
+   /* find the order of the 3 vertices along the Y axis */
+  {
+    double y0 = v0->_p[1];
+    double y1 = v1->_p[1];
+    double y2 = v2->_p[1];
+
+    if (y0<=y1) {
+      if (y1<=y2) {
+	vMin = v0;   vMid = v1;   vMax = v2;   /* y0<=y1<=y2 */
+      } else if (y2<=y0) {
+	vMin = v2;   vMid = v0;   vMax = v1;   /* y2<=y0<=y1 */
+      } else {
+	vMin = v0;   vMid = v2;   vMax = v1;   /* y0<=y2<=y1 */
+      }
+    } else {
+      if (y0<=y2) {
+	vMin = v1;   vMid = v0;   vMax = v2;   /* y1<=y0<=y2 */
+      } else if (y2<=y1) {
+	vMin = v2;   vMid = v1;   vMax = v0;   /* y2<=y1<=y0 */
+      } else {
+	vMin = v1;   vMid = v2;   vMax = v0;   /* y1<=y2<=y0 */
+      }
+    }
+  }
+
+  /* vertex/edge relationship */
+  Edge eMaj(vMin, vMax);
+  Edge eTop(vMid, vMax);
+  Edge eBot(vMin, vMid);
+
+  /* compute oneOverArea */
+  {
+    double area = eMaj._dx * eBot._dy - eBot._dx * eMaj._dy;
+
+    // We can't cull very small triangles; we might generate small
+    // triangles through normal operations.
+
+    /*
+    if (area>-0.05 && area<0.05) {
+      return;  // very small; CULLED
+    }
+    */
+    oneOverArea = 1.0 / area;
+  }
+
+  /* Edge setup.  For a triangle strip these could be reused... */
+  {
+    /* fixed point Y coordinates */
+    FixedPoint vMin_fx = FloatToFixed(vMin->_p[0] + 0.5);
+    FixedPoint vMin_fy = FloatToFixed(vMin->_p[1] - 0.5);
+    FixedPoint vMid_fx = FloatToFixed(vMid->_p[0] + 0.5);
+    FixedPoint vMid_fy = FloatToFixed(vMid->_p[1] - 0.5);
+    FixedPoint vMax_fy = FloatToFixed(vMax->_p[1] - 0.5);
+
+    eMaj._fsy = FixedCeil(vMin_fy);
+    eMaj._lines = FixedToInt(vMax_fy + FIXED_ONE - FIXED_EPSILON - eMaj._fsy);
+    if (eMaj._lines > 0) {
+      double dxdy = eMaj._dx / eMaj._dy;
+      eMaj._fdxdy = SignedFloatToFixed(dxdy);
+      eMaj._adjy = (double) (eMaj._fsy - vMin_fy);  /* SCALED! */
+      eMaj._fx0 = vMin_fx;
+      eMaj._fsx = eMaj._fx0 + (FixedPoint) (eMaj._adjy * dxdy);
+    }
+    else {
+      return;  /*CULLED*/
+    }
+
+    eTop._fsy = FixedCeil(vMid_fy);
+    eTop._lines = FixedToInt(vMax_fy + FIXED_ONE - FIXED_EPSILON - eTop._fsy);
+    if (eTop._lines > 0) {
+      double dxdy = eTop._dx / eTop._dy;
+      eTop._fdxdy = SignedFloatToFixed(dxdy);
+      eTop._adjy = (double) (eTop._fsy - vMid_fy); /* SCALED! */
+      eTop._fx0 = vMid_fx;
+      eTop._fsx = eTop._fx0 + (FixedPoint) (eTop._adjy * dxdy);
+    }
+
+    eBot._fsy = FixedCeil(vMin_fy);
+    eBot._lines = FixedToInt(vMid_fy + FIXED_ONE - FIXED_EPSILON - eBot._fsy);
+    if (eBot._lines > 0) {
+      double dxdy = eBot._dx / eBot._dy;
+      eBot._fdxdy = SignedFloatToFixed(dxdy);
+      eBot._adjy = (double) (eBot._fsy - vMin_fy);  /* SCALED! */
+      eBot._fx0 = vMin_fx;
+      eBot._fsx = eBot._fx0 + (FixedPoint) (eBot._adjy * dxdy);
+    }
+  }
+
+  /*
+    * Conceptually, we view a triangle as two subtriangles
+    * separated by a perfectly horizontal line.  The edge that is
+    * intersected by this line is one with maximal absolute dy; we
+    * call it a ``major'' edge.  The other two edges are the
+    * ``top'' edge (for the upper subtriangle) and the ``bottom''
+    * edge (for the lower subtriangle).  If either of these two
+    * edges is horizontal or very close to horizontal, the
+    * corresponding subtriangle might cover zero sample points;
+    * we take care to handle such cases, for performance as well
+    * as correctness.
+    *
+    * By stepping rasterization parameters along the major edge,
+    * we can avoid recomputing them at the discontinuity where
+    * the top and bottom edges meet.  However, this forces us to
+    * be able to scan both left-to-right and right-to-left. 
+    * Also, we must determine whether the major edge is at the
+    * left or right side of the triangle.  We do this by
+    * computing the magnitude of the cross-product of the major
+    * and top edges.  Since this magnitude depends on the sine of
+    * the angle between the two edges, its sign tells us whether
+    * we turn to the left or to the right when travelling along
+    * the major edge to the top edge, and from this we infer
+    * whether the major edge is on the left or the right.
+    *
+    * Serendipitously, this cross-product magnitude is also a
+    * value we need to compute the iteration parameter
+    * derivatives for the triangle, and it can be used to perform
+    * backface culling because its sign tells us whether the
+    * triangle is clockwise or counterclockwise.  In this code we
+    * refer to it as ``area'' because it's also proportional to
+    * the pixel area of the triangle.
+    */
+
+  {
+    int ltor;		/* true if scanning left-to-right */
+
+    // For interpolating the alpha value.
+    double dadx, dady;
+    FixedPoint fdadx;
+
+    // For interpolating texture coordinates.
+    double dsdx, dsdy;
+    FixedPoint fdsdx;
+    double dtdx, dtdy;
+    FixedPoint fdtdx;
+
+    // Set up values for texture coordinates.
+      
+    double twidth, theight;
+    if (_texture != NULL) {
+      twidth = (double) _texture->get_x_size();
+      theight = (double) _texture->get_y_size();
+    } else {
+      twidth = 1.0;
+      theight = 1.0;
+    }
+
+    ltor = (oneOverArea < 0.0);
+      
+    // More alpha setup.
+    {
+      double eMaj_da, eBot_da;
+      eMaj_da = vMax->_alpha - vMin->_alpha;
+      eBot_da = vMid->_alpha - vMin->_alpha;
+      dadx = oneOverArea * (eMaj_da * eBot._dy - eMaj._dy * eBot_da);
+      fdadx = SignedFloatToFixed(dadx);
+      dady = oneOverArea * (eMaj._dx * eBot_da - eMaj_da * eBot._dx);
+    }
+
+    // Texture coordinates.
+    {
+      double eMaj_ds, eBot_ds;
+      eMaj_ds = (vMax->_uv[0] - vMin->_uv[0]) * twidth;
+      eBot_ds = (vMid->_uv[0] - vMin->_uv[0]) * twidth;
+
+      dsdx = oneOverArea * (eMaj_ds * eBot._dy - eMaj._dy * eBot_ds);
+      fdsdx = SignedFloatToFixed(dsdx);
+      dsdy = oneOverArea * (eMaj._dx * eBot_ds - eMaj_ds * eBot._dx);
+    }
+    {
+      double eMaj_dt, eBot_dt;
+      eMaj_dt = (vMax->_uv[1] - vMin->_uv[1]) * theight;
+      eBot_dt = (vMid->_uv[1] - vMin->_uv[1]) * theight;
+
+      dtdx = oneOverArea * (eMaj_dt * eBot._dy - eMaj._dy * eBot_dt);
+      fdtdx = SignedFloatToFixed(dtdx);
+      dtdy = oneOverArea * (eMaj._dx * eBot_dt - eMaj_dt * eBot._dx);
+    }
+
+    /*
+     * We always sample at pixel centers.  However, we avoid
+     * explicit half-pixel offsets in this code by incorporating
+     * the proper offset in each of x and y during the
+     * transformation to window coordinates.
+     *
+     * We also apply the usual rasterization rules to prevent
+     * cracks and overlaps.  A pixel is considered inside a
+     * subtriangle if it meets all of four conditions: it is on or
+     * to the right of the left edge, strictly to the left of the
+     * right edge, on or below the top edge, and strictly above
+     * the bottom edge.  (Some edges may be degenerate.)
+     *
+     * The following discussion assumes left-to-right scanning
+     * (that is, the major edge is on the left); the right-to-left
+     * case is a straightforward variation.
+     *
+     * We start by finding the half-integral y coordinate that is
+     * at or below the top of the triangle.  This gives us the
+     * first scan line that could possibly contain pixels that are
+     * inside the triangle.
+     *
+     * Next we creep down the major edge until we reach that y,
+     * and compute the corresponding x coordinate on the edge. 
+     * Then we find the half-integral x that lies on or just
+     * inside the edge.  This is the first pixel that might lie in
+     * the interior of the triangle.  (We won't know for sure
+     * until we check the other edges.)
+     *
+     * As we rasterize the triangle, we'll step down the major
+     * edge.  For each step in y, we'll move an integer number
+     * of steps in x.  There are two possible x step sizes, which
+     * we'll call the ``inner'' step (guaranteed to land on the
+     * edge or inside it) and the ``outer'' step (guaranteed to
+     * land on the edge or outside it).  The inner and outer steps
+     * differ by one.  During rasterization we maintain an error
+     * term that indicates our distance from the true edge, and
+     * select either the inner step or the outer step, whichever
+     * gets us to the first pixel that falls inside the triangle.
+     *
+     * All parameters (z, red, etc.) as well as the buffer
+     * addresses for color and z have inner and outer step values,
+     * so that we can increment them appropriately.  This method
+     * eliminates the need to adjust parameters by creeping a
+     * sub-pixel amount into the triangle at each scanline.
+     */
+
+    {
+      int subTriangle;
+      FixedPoint fx, fxLeftEdge, fxRightEdge, fdxLeftEdge, fdxRightEdge;
+      FixedPoint fdxOuter;
+      int idxOuter;
+      double dxOuter;
+      FixedPoint fError, fdError;
+      double adjx, adjy;
+      FixedPoint fy;
+      int iy;
+
+      // Alpha.
+      FixedPoint fa, fdaOuter, fdaInner;
+
+      // Texture coordinates.
+      FixedPoint fs, fdsOuter, fdsInner;
+      FixedPoint ft, fdtOuter, fdtInner;
+
+      for (subTriangle=0; subTriangle<=1; subTriangle++) {
+	Edge *eLeft, *eRight;
+	int setupLeft, setupRight;
+	int lines;
+
+	if (subTriangle==0) {
+	  /* bottom half */
+	  if (ltor) {
+	    eLeft = &eMaj;
+	    eRight = &eBot;
+	    lines = eRight->_lines;
+	    setupLeft = 1;
+	    setupRight = 1;
+	  }
+	  else {
+	    eLeft = &eBot;
+	    eRight = &eMaj;
+	    lines = eLeft->_lines;
+	    setupLeft = 1;
+	    setupRight = 1;
+	  }
+	}
+	else {
+	  /* top half */
+	  if (ltor) {
+	    eLeft = &eMaj;
+	    eRight = &eTop;
+	    lines = eRight->_lines;
+	    setupLeft = 0;
+	    setupRight = 1;
+	  }
+	  else {
+	    eLeft = &eTop;
+	    eRight = &eMaj;
+	    lines = eLeft->_lines;
+	    setupLeft = 1;
+	    setupRight = 0;
+	  }
+	  if (lines==0) return;
+	}
+
+	if (setupLeft && eLeft->_lines>0) {
+	  const RasterizerVertex *vLower;
+	  FixedPoint fsx = eLeft->_fsx;
+	  fx = FixedCeil(fsx);
+	  fError = fx - fsx - FIXED_ONE;
+	  fxLeftEdge = fsx - FIXED_EPSILON;
+	  fdxLeftEdge = eLeft->_fdxdy;
+	  fdxOuter = FixedFloor(fdxLeftEdge - FIXED_EPSILON);
+	  fdError = fdxOuter - fdxLeftEdge + FIXED_ONE;
+	  idxOuter = FixedToInt(fdxOuter);
+	  dxOuter = (double) idxOuter;
+
+	  fy = eLeft->_fsy;
+	  iy = FixedToInt(fy);
+
+	  adjx = (double)(fx - eLeft->_fx0);  /* SCALED! */
+	  adjy = eLeft->_adjy;		 /* SCALED! */
+	  
+	  vLower = eLeft->_v0;
+	  
+	  /*
+	   * Now we need the set of parameter (z, color, etc.) values at
+	   * the point (fx, fy).  This gives us properly-sampled parameter
+	   * values that we can step from pixel to pixel.  Furthermore,
+	   * although we might have intermediate results that overflow
+	   * the normal parameter range when we step temporarily outside
+	   * the triangle, we shouldn't overflow or underflow for any
+	   * pixel that's actually inside the triangle.
+	   */
+	  
+	  // Interpolate alpha
+	  fa = (FixedPoint)(vLower->_alpha * FIXED_SCALE + dadx * adjx + dady * adjy)
+	    + FIXED_HALF;
+	  fdaOuter = SignedFloatToFixed(dady + dxOuter * dadx);
+	  // Interpolate texture coordinates
+	  {
+	    double s0, t0;
+	    s0 = vLower->_uv[0] * twidth;
+	    fs = (FixedPoint)(s0 * FIXED_SCALE + dsdx * adjx + dsdy * adjy) + FIXED_HALF;
+	    fdsOuter = SignedFloatToFixed(dsdy + dxOuter * dsdx);
+	    t0 = vLower->_uv[1] * theight;
+	    ft = (FixedPoint)(t0 * FIXED_SCALE + dtdx * adjx + dtdy * adjy) + FIXED_HALF;
+	    fdtOuter = SignedFloatToFixed(dtdy + dxOuter * dtdx);
+	  }
+	  
+	} /*if setupLeft*/
+	
+	
+	if (setupRight && eRight->_lines>0) {
+	  fxRightEdge = eRight->_fsx - FIXED_EPSILON;
+	  fdxRightEdge = eRight->_fdxdy;
+	}
+	
+	if (lines==0) {
+	  continue;
+	}
+
+	/* Rasterize setup */
+	fdaInner = fdaOuter + fdadx;
+	fdsInner = fdsOuter + fdsdx;
+	fdtInner = fdtOuter + fdtdx;
+	
+	while (lines>0) {
+	  if (iy >= 0 && iy < _output->get_y_size()) {
+	    /* initialize the span interpolants to the leftmost value */
+	    /* ff = fixed-pt fragment */
+	    FixedPoint ffa = fa;
+	    FixedPoint ffs = fs,  fft = ft;
+
+	    int left = FixedToInt(fxLeftEdge);
+	    int right = FixedToInt(fxRightEdge);
+
+	    // Alpha
+	    {
+	      //	      FixedPoint ffaend = ffa+(right-left-1)*fdadx;
+	      //	      if (ffaend<0) ffa -= ffaend;
+	      //	      if (ffa<0) ffa = 0;
+	    }
+
+	    // Rasterize left to right at row iy.
+	    if (right > left) {
+	      ffs -= FIXED_HALF; /* off-by-one error? */        
+	      fft -= FIXED_HALF;
+	      ffa -= FIXED_HALF;
+	      for (int ix = left; ix < right; ix++) {
+		if (ix >= 0 && ix < _output->get_x_size()) {
+		  RGBColord rgb;
+		  double alpha;
+		  filter_pixel(rgb, alpha,
+			       FixedToFloat(ffs), FixedToFloat(fft),
+			       dsdx, dtdx, dsdy, dtdy);
+		  alpha *= FixedToFloat(ffa);
+		  _output->blend(ix, iy, rgb, alpha);
+		}
+
+		ffs += fdsdx;
+		fft += fdtdx;
+		ffa += fdadx;
+	      }
+	    }
+	  }
+
+	  /*
+	   * Advance to the next scan line.  Compute the
+	   * new edge coordinates, and adjust the
+	   * pixel-center x coordinate so that it stays
+	   * on or inside the major edge.
+	   */
+	  iy++;
+	  lines--;
+
+	  fxLeftEdge += fdxLeftEdge;
+	  fxRightEdge += fdxRightEdge;
+
+	  fError += fdError;
+	  if (fError >= 0) {
+	    fError -= FIXED_ONE;
+
+	    fa += fdaOuter;
+	    fs += fdsOuter;
+	    ft += fdtOuter;
+	  } else {
+	    fa += fdaInner;
+	    fs += fdsInner;
+	    ft += fdtInner;
+	  }
+	} /*while lines>0*/
+
+      } /* for subTriangle */
+
+    }
+  }
+}
+
+void TriangleRasterizer::
+draw_pixel(const RasterizerVertex *v0, double radius) {
+  if (v0->_visibility != 0) {
+    // The pixel is off the screen.
+    return;
+  }
+  int ix = (int)v0->_p[0];
+  int iy = (int)v0->_p[1];
+
+  if (iy >= 0 && iy < _output->get_y_size() &&
+      ix >= 0 && ix < _output->get_x_size()) {
+    if (!_read_input) {
+      read_input();
+    }
+
+    RGBColord rgb;
+    double alpha;
+    if (_texture == NULL) {
+      filter_pixel(rgb, alpha, v0->_uv[0], v0->_uv[1], radius);
+    } else {
+      filter_pixel(rgb, alpha, 
+		   v0->_uv[0] * (_texture->get_x_size() - 1), 
+		   v0->_uv[1] * (_texture->get_y_size() - 1),
+		   radius * (_texture->get_x_size() - 1));
+    }
+    alpha *= v0->_alpha;
+    _output->blend(ix, iy, rgb, alpha);
+  }
+}
+
+void TriangleRasterizer::
+filter_pixel(RGBColord &rgb, double &alpha,
+	     double s, double t, double radius) {
+  if (_texture == NULL) {
+    rgb.set(_untextured_color[0], 
+	    _untextured_color[1],
+	    _untextured_color[2]);
+    alpha = _untextured_color[3];
+    return;
+  }
+
+  int ri = (int)radius;
+  int si = (int)(s + 0.5);
+  int ti = _texture->get_y_size() - 1 - (int)(t + 0.5);
+  
+  rgb.set(0.0, 0.0, 0.0);
+  alpha = 0.0;
+  
+  if (!_filter_output) {
+    if (si >= 0 && si < _texture->get_x_size() &&
+	ti >= 0 && ti < _texture->get_y_size()) {
+      rgb = _texture->get_xel(si, ti);
+      alpha = 1.0;
+    }
+    return;
+  }
+
+  int num_total = 0;
+  int num_visible = 0;
+  for (int yr = -ri; yr <= ri; yr++) {
+    int tii = ti + yr;
+    for (int xr = -ri; xr <= ri; xr++) {
+      int sii = si + xr;
+      if (sii >= 0 && sii < _texture->get_x_size() &&
+	  tii >= 0 && tii < _texture->get_y_size()) {
+	rgb += _texture->get_xel(sii, tii);
+	num_visible++;
+      }
+      num_total++;
+    }
+  }
+
+  if (num_visible != 0) {
+    rgb /= (double)num_visible;
+    alpha = 1.0;
+  }
+
+  // We would do this to antialias the edge of the image.  However, it
+  // seems to cause problems at seams, so we won't do it.
+  /*
+  if (num_total != 0) {
+    alpha = (double)num_visible / (double)num_total;
+  }
+  */
+}
+
+void TriangleRasterizer::
+read_input() {
+  if (_input != NULL) {
+    if (!_input->read_file()) {
+      nout << "Unable to read image.\n";
+    } else {
+      _texture = _input->_data;
+    }
+  }
+  _read_input = true;
+}

+ 68 - 0
pandaapp/src/stitchbase/triangleRasterizer.h

@@ -0,0 +1,68 @@
+// Filename: triangleRasterizer.h
+// Created by:  drose (06Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TRIANGLERASTERIZER_H
+#define TRIANGLERASTERIZER_H
+
+#include "fixedPoint.h"
+
+#include <luse.h>
+#include <pnmImage.h>
+
+class StitchImage;
+
+class RasterizerVertex {
+public:
+  LPoint2d _p;
+  LPoint2d _uv;
+  LVector3d _space;
+  double _alpha;
+  int _visibility;
+};
+
+class TriangleRasterizer {
+public:
+  TriangleRasterizer();
+
+  void draw_triangle(const RasterizerVertex *v0, 
+		     const RasterizerVertex *v1, 
+		     const RasterizerVertex *v2);
+  void draw_pixel(const RasterizerVertex *v0, double radius);
+
+  PNMImage *_output;
+  StitchImage *_input;
+  bool _read_input;
+  const PNMImage *_texture;
+  bool _filter_output;
+  Colord _untextured_color;
+
+private:
+  class Edge {
+  public:
+    Edge(const RasterizerVertex *v0, const RasterizerVertex *v1);
+
+    const RasterizerVertex *_v0;  //  Y(v0) < Y(v1)
+    const RasterizerVertex *_v1;
+    double _dx;	                  // X(v1) - X(v0)
+    double _dy;	                  // Y(v1) - Y(v0)
+    FixedPoint _fdxdy;	          // dx/dy in fixed-point
+    FixedPoint _fsx;	          // first sample point x coord
+    FixedPoint _fsy;
+    double _adjy;	          // adjust from v[0]->fy to fsy, scaled
+    int _lines;                   // number of lines to be sampled on this edge
+    FixedPoint _fx0;	          // fixed pt X of lower endpoint
+  };
+
+  inline void filter_pixel(RGBColord &rgb, double &alpha,
+			   double s, double t,
+			   double dsdx, double dtdx, double dsdy, double dtdy);
+  void filter_pixel(RGBColord &rgb, double &alpha,
+		    double s, double t, double radius);
+
+  void read_input();
+};
+
+#endif
+

+ 19 - 0
pandaapp/src/stitchviewer/Sources.pp

@@ -0,0 +1,19 @@
+#begin ss_lib_target
+  #define TARGET stitchviewer
+  #define LOCAL_LIBS stitchbase
+  #define OTHER_LIBS \
+    stitchbase \
+    device:c tform:c graph:c dgraph:c sgraph:c gobj:c pnmimage:c \
+    sgattrib:c event:c chancfg:c display:c sgraphutil:c light:c putil:c \
+    express:c panda:m
+
+  #define SOURCES \
+    stitchImageConverter.cxx stitchImageConverter.h \
+    stitchImageVisualizer.cxx stitchImageVisualizer.h triangleMesh.cxx \
+    triangleMesh.h
+
+  #define INSTALL_HEADERS \
+    stitchImageConverter.h stitchImageVisualizer.h triangleMesh.h
+
+#end ss_lib_target
+

+ 120 - 0
pandaapp/src/stitchviewer/stitchImageConverter.cxx

@@ -0,0 +1,120 @@
+// Filename: stitchImageConverter.cxx
+// Created by:  drose (06Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchImageConverter.h"
+#include "stitchImage.h"
+#include "triangleMesh.h"
+
+#include <geomTristrip.h>
+#include <geomNode.h>
+#include <texture.h>
+#include <textureTransition.h>
+#include <chancfg.h>
+#include <camera.h>
+#include <perspectiveProjection.h>
+#include <frustum.h>
+
+StitchImageConverter::
+StitchImageConverter() {
+  _output_image = NULL;
+}
+
+void StitchImageConverter::
+add_output_image(StitchImage *image) {
+  _output_image = image;
+}
+
+void StitchImageConverter::
+override_chan_cfg(ChanCfgOverrides &override) {
+  override.setField(ChanCfgOverrides::Mask,
+		    ((unsigned int)(W_DOUBLE|W_DEPTH|W_MULTISAMPLE)));
+  override.setField(ChanCfgOverrides::Title, "Stitch");
+
+  LVecBase2d size = _output_image->get_size_pixels();
+  
+  override.setField(ChanCfgOverrides::SizeX, (int)size[0]);
+  override.setField(ChanCfgOverrides::SizeY, (int)size[1]);
+}
+
+void StitchImageConverter::
+setup_camera(const RenderRelation &camera_arc) {
+  PT(Camera) cam = DCAST(Camera, camera_arc.get_child());
+
+  Frustumf frust;
+  frust._t = 0.5;
+  frust._b = -0.5;
+  frust._l = -0.5;
+  frust._r = 0.5;
+  frust._fnear = 0.5;
+  frust._ffar = 2.0;
+
+  PerspectiveProjection proj(frust);
+  cam->set_projection(proj);
+}
+
+bool StitchImageConverter::
+is_interactive() const {
+  //return false;
+  return true;
+}
+
+void StitchImageConverter::
+create_image_geometry(Image &im) {
+  assert(_output_image != NULL);
+
+  //  double dist = 1.0 + (double)im._index / (double)_images.size();
+#if 0
+  int x_verts = _output_image->get_x_verts();
+  int y_verts = _output_image->get_y_verts();
+  TriangleMesh mesh(x_verts, y_verts);
+
+  for (int xi = 0; xi < x_verts; xi++) {
+    for (int yi = 0; yi < y_verts; yi++) {
+      LVector2d uvd =
+	im._image->project(_output_image->get_grid_vector(xi, yi));
+      LVector2f uvf(uvd);
+
+      LVector3f p = LVector3f::rfu(2 * (double)xi / (double)(x_verts - 1) - 1,
+				   1.0,
+				   1 - 2 * (double)yi / (double)(y_verts - 1));
+      mesh._coords.push_back(p);
+      mesh._texcoords.push_back(uvf);
+    }
+  }
+#else
+  int x_verts = im._image->get_x_verts();
+  int y_verts = im._image->get_y_verts();
+  TriangleMesh mesh(x_verts, y_verts);
+
+  for (int xi = 0; xi < x_verts; xi++) {
+    for (int yi = 0; yi < y_verts; yi++) {
+      LVector2d uvd = 
+	_output_image->project(im._image->get_grid_vector(xi, yi));
+
+      LVector3f p = LVector3f::rfu(2 * uvd[0] - 1,
+				   1.0,
+				   2 * uvd[1] - 1);
+      LPoint2f uvf((double)xi / (double)(x_verts - 1),
+		   1.0 - (double)yi / (double)(y_verts - 1));
+      mesh._coords.push_back(p);
+      mesh._texcoords.push_back(uvf);
+    }
+  }
+#endif
+
+  PT(GeomTristrip) geom = mesh.build_mesh();
+
+  PT(GeomNode) node = new GeomNode;
+  node->add_geom(geom.p());
+
+  im._arc = new RenderRelation(_render, node);
+
+  if (im._image->_data != NULL) {
+    im._tex = new Texture;
+    im._tex->set_name(im._image->get_filename());
+    im._tex->load(*im._image->_data);
+    im._arc->set_transition(new TextureTransition(im._tex));
+  }
+}

+ 28 - 0
pandaapp/src/stitchviewer/stitchImageConverter.h

@@ -0,0 +1,28 @@
+// Filename: stitchImageConverter.h
+// Created by:  drose (06Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHIMAGECONVERTER_H
+#define STITCHIMAGECONVERTER_H
+
+#include "stitchImageVisualizer.h"
+
+class StitchImage;
+
+class StitchImageConverter : public StitchImageVisualizer {
+public:
+  StitchImageConverter();
+
+  virtual void add_output_image(StitchImage *image);
+
+protected:
+  virtual void override_chan_cfg(ChanCfgOverrides &override);
+  virtual void setup_camera(const RenderRelation &camera_arc);
+  virtual bool is_interactive() const;
+  virtual void create_image_geometry(Image &im);
+
+  StitchImage *_output_image;
+};
+
+#endif

+ 330 - 0
pandaapp/src/stitchviewer/stitchImageVisualizer.cxx

@@ -0,0 +1,330 @@
+// Filename: stitchImageVisualizer.cxx
+// Created by:  drose (05Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "stitchImageVisualizer.h"
+#include "config_stitch.h"
+#include "triangleMesh.h"
+
+#include <luse.h>
+#include <chancfg.h>
+#include <transform2sg.h>
+#include <geomTristrip.h>
+#include <geomNode.h>
+#include <interactiveGraphicsPipe.h>
+#include <noninteractiveGraphicsPipe.h>
+#include <buttonThrower.h>
+#include <dataGraphTraversal.h>
+#include <eventQueue.h>
+#include <texture.h>
+#include <textureTransition.h>
+#include <renderModeTransition.h>
+#include <cullFaceTransition.h>
+#include <cullFaceAttribute.h>
+#include <clockObject.h>
+#include <config_gobj.h>
+#include <allAttributesWrapper.h>
+
+#include <algorithm>
+
+StitchImageVisualizer *StitchImageVisualizer::_static_siv;
+
+// Returns the largest power of 2 less than or equal to value.
+static int
+to_power_2(int value) {
+  int x = 1;
+  while ((x << 1) <= value) {
+    x = (x << 1);
+  }
+  return x;
+}
+
+StitchImageVisualizer::Image::
+Image(StitchImage *image, int index, bool scale) :
+  _image(image),
+  _index(index)
+{
+  _arc = NULL;
+  _viz = true;
+
+  if (!image->read_file()) {
+    nout << "Unable to read image.\n";
+  }
+
+  if (scale && image->_data != NULL) {
+    // We have to make it a power of 2.
+    int old_xsize = image->_data->get_x_size();
+    int old_ysize = image->_data->get_y_size();
+    int new_xsize = old_xsize;
+    int new_ysize = old_ysize;
+    if (max_texture_dimension > 0) {
+      new_xsize = min(new_xsize, max_texture_dimension);
+      new_ysize = min(new_ysize, max_texture_dimension);
+    }
+    new_xsize = to_power_2(new_xsize);
+    new_ysize = to_power_2(new_ysize);
+
+    if (new_xsize != old_xsize || new_ysize != old_ysize) {
+      nout << "Scaling " << image->get_name() << " from " 
+	   << old_xsize << " " << old_ysize << " to "
+	   << new_xsize << " " << new_ysize << "\n";
+      
+      PNMImage *n = new PNMImage(new_xsize, new_ysize);
+      n->quick_filter_from(*image->_data);
+      delete image->_data;
+      image->_data = n;
+    }
+  }
+}
+
+StitchImageVisualizer::Image::
+Image(const Image &copy) :
+  _image(copy._image),
+  _arc(copy._arc),
+  _tex(copy._tex),
+  _viz(copy._viz),
+  _index(copy._index)
+{
+}
+ 
+void StitchImageVisualizer::Image::
+operator = (const Image &copy) {
+  _image = copy._image;
+  _arc = copy._arc;
+  _tex = copy._tex;
+  _viz = copy._viz;
+  _index = copy._index;
+}
+
+StitchImageVisualizer::
+StitchImageVisualizer() :
+  _event_handler(EventQueue::get_global_event_queue())
+{
+  _event_handler.add_hook("q", static_handle_event);
+  _event_handler.add_hook("z", static_handle_event);
+}
+
+
+void StitchImageVisualizer::
+add_input_image(StitchImage *image) {
+  int index = _images.size();
+  char letter = index + 'a';
+  _images.push_back(Image(image, index, true));
+  
+  string event_name(1, letter);
+  _event_handler.add_hook(event_name, static_handle_event);
+}
+
+void StitchImageVisualizer::
+add_output_image(StitchImage *) {
+}
+
+void StitchImageVisualizer::
+add_stitcher(Stitcher *) {
+}
+
+void StitchImageVisualizer::
+execute() {
+  setup();
+
+  if (is_interactive()) {
+    _main_win->set_draw_callback(this);
+    _main_win->set_idle_callback(this);
+    _running = true;
+    while (_running) {
+      _main_win->update();
+    }
+  } else {
+    nout << "Drawing frame\n";
+    draw(true);
+    nout << "Done drawing frame\n";
+  }
+}
+  
+void StitchImageVisualizer::
+setup() {
+  ChanCfgOverrides override;
+
+  override_chan_cfg(override);
+
+  // load display modules
+  GraphicsPipe::resolve_modules();
+  
+  // Create a window
+  TypeHandle want_pipe_type = InteractiveGraphicsPipe::get_class_type();
+  if (!is_interactive()) {
+    want_pipe_type = NoninteractiveGraphicsPipe::get_class_type();
+  }
+
+  _main_pipe = GraphicsPipe::get_factory().make_instance(want_pipe_type);
+
+  if (_main_pipe == (GraphicsPipe*)0L) {
+    nout << "No suitable pipe is available!  Check your Configrc!\n";
+    exit(1);
+  }
+
+  nout << "Opened a '" << _main_pipe->get_type().get_name()
+       << "' graphics pipe." << endl;
+
+  // Create the render node
+  _render = new NamedNode("render");
+
+  // make a node for the cameras to live under
+  _cameras = new NamedNode("cameras");
+  RenderRelation *cam_trans = new RenderRelation(_render, _cameras);
+
+  _main_win = ChanConfig(_main_pipe, chan_cfg, _cameras, _render, override);
+  assert(_main_win != (GraphicsWindow*)0L);
+
+  // Turn on culling.
+  CullFaceAttribute *cfa = new CullFaceAttribute;
+  cfa->set_mode(CullFaceProperty::M_cull_clockwise);
+  _initial_state.set_attribute(CullFaceTransition::get_class_type(), cfa);
+
+  // Create the data graph root.
+  _data_root = new NamedNode( "data" );
+
+  // Create a mouse and put it in the data graph.
+  _mak = new MouseAndKeyboard(_main_win, 0);
+  new RenderRelation(_data_root, _mak);
+
+  // Create a trackball to handle the mouse input.
+  _trackball = new Trackball("trackball");
+
+  new RenderRelation(_mak, _trackball);
+
+  // Connect the trackball output to the camera's transform.
+  PT(Transform2SG) tball2cam = new Transform2SG("tball2cam");
+  tball2cam->set_arc(cam_trans);
+  new RenderRelation(_trackball, tball2cam);
+  
+  // Create an ButtonThrower to throw events from the keyboard.
+  PT(ButtonThrower) et = new ButtonThrower("kb-events");
+  new RenderRelation(_mak, et);
+
+  // Create all the images.
+  Images::iterator ii;
+  for (ii = _images.begin(); ii != _images.end(); ++ii) {
+    create_image_geometry(*ii);
+  }
+}
+
+
+void StitchImageVisualizer::
+override_chan_cfg(ChanCfgOverrides &override) {
+  override.setField(ChanCfgOverrides::Mask,
+		    ((unsigned int)(W_DOUBLE|W_DEPTH|W_MULTISAMPLE)));
+  override.setField(ChanCfgOverrides::Title, "Stitch");
+}
+
+void StitchImageVisualizer::
+setup_camera(const RenderRelation &) {
+}
+
+bool StitchImageVisualizer::
+is_interactive() const {
+  return true;
+}
+
+void StitchImageVisualizer::
+toggle_viz(StitchImageVisualizer::Image &im) {
+  im._viz = !im._viz;
+  if (im._viz) {
+    im._arc->set_transition(new RenderModeTransition(RenderModeProperty::M_filled));
+    im._arc->set_transition(new CullFaceTransition(CullFaceProperty::M_cull_clockwise));
+    if (im._tex != (Texture *)NULL) {
+      im._arc->set_transition(new TextureTransition(im._tex));
+    }
+  } else {
+    im._arc->set_transition(new RenderModeTransition(RenderModeProperty::M_wireframe));
+    im._arc->set_transition(new CullFaceTransition(CullFaceProperty::M_cull_none));
+    im._arc->set_transition(new TextureTransition);
+  }
+}
+
+void StitchImageVisualizer::
+create_image_geometry(StitchImageVisualizer::Image &im) {
+  /*
+  int x_verts = im._image->get_x_verts();
+  int y_verts = im._image->get_y_verts();
+  */
+  int x_verts = 2;
+  int y_verts = 2;
+  TriangleMesh mesh(x_verts, y_verts);
+
+  LVector3f center = LCAST(float, im._image->extrude(LPoint2d(0.5, 0.5)));
+  double scale = 10.0 / length(center);
+
+  for (int xi = 0; xi < x_verts; xi++) {
+    for (int yi = 0; yi < y_verts; yi++) {
+      LPoint2d uv = LPoint2d((double)xi / (double)(x_verts - 1),
+			     1.0 - (double)yi / (double)(y_verts - 1));
+      LVector3d p = im._image->extrude(uv);
+
+      mesh._coords.push_back(LCAST(float, p) * scale);
+      mesh._texcoords.push_back(LCAST(float, uv));
+    }
+  }
+
+  PT(GeomTristrip) geom = mesh.build_mesh();
+
+  PT(GeomNode) node = new GeomNode;
+  node->add_geom(geom.p());
+
+  im._arc = new RenderRelation(_render, node);
+
+  if (im._image->_data != NULL) {
+    im._tex = new Texture;
+    im._tex->set_name(im._image->get_filename());
+    im._tex->load(*im._image->_data);
+    im._arc->set_transition(new TextureTransition(im._tex));
+  }
+}
+
+
+void StitchImageVisualizer::
+draw(bool) {
+  int num_windows = _main_pipe->get_num_windows();
+  for (int w = 0; w < num_windows; w++) {
+    GraphicsWindow *win = _main_pipe->get_window(w);
+    win->get_gsg()->render_frame(_initial_state);
+  }
+  ClockObject::get_global_clock()->tick();
+}
+
+void StitchImageVisualizer::
+idle() {
+  // Initiate the data traversal, to send device data down its
+  // respective pipelines.
+  traverse_data_graph(_data_root);
+  
+  // Throw any events generated recently.
+  _static_siv = this;
+  _event_handler.process_events();
+}
+
+void StitchImageVisualizer::
+static_handle_event(CPT(Event) event) {
+  _static_siv->handle_event(event);
+}
+
+
+void StitchImageVisualizer::
+handle_event(CPT(Event) event) {
+  string name = event->get_name();
+
+  if (name.size() == 1 && isalpha(name[0])) {
+    int index = tolower(name[0]) - 'a';
+    if (index >= 0 && index < (int)_images.size()) {
+      toggle_viz(_images[index]);
+      return;
+    }
+  }
+  if (name == "q") {
+    _running = false;
+
+  } else if (name == "z") {
+    _trackball->reset();
+  }
+}

+ 83 - 0
pandaapp/src/stitchviewer/stitchImageVisualizer.h

@@ -0,0 +1,83 @@
+// Filename: stitchImageVisualizer.h
+// Created by:  drose (05Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef STITCHIMAGEVISUALIZER_H
+#define STITCHIMAGEVISUALIZER_H
+
+#include "stitchImage.h"
+#include "stitchImageOutputter.h"
+
+#include <pointerTo.h>
+#include <graphicsPipe.h>
+#include <graphicsWindow.h>
+#include <namedNode.h>
+#include <renderRelation.h>
+#include <mouse.h>
+#include <trackball.h>
+#include <nodeTransition.h>
+#include <eventHandler.h>
+#include <texture.h>
+
+class PNMImage;
+class ChanCfgOverrides;
+
+class StitchImageVisualizer : public StitchImageOutputter,
+			      public GraphicsWindow::Callback {
+public:
+  StitchImageVisualizer();
+  virtual void add_input_image(StitchImage *image);
+  virtual void add_output_image(StitchImage *image);
+  virtual void add_stitcher(Stitcher *stitcher);
+
+  virtual void execute();
+  
+protected:
+  void setup();
+
+  class Image {
+  public:
+    Image(StitchImage *image, int index, bool scale);
+    Image(const Image &copy);
+    void operator = (const Image &copy);
+
+    StitchImage *_image;
+    RenderRelation *_arc;
+    PT(Texture) _tex;
+    bool _viz;
+    int _index;
+  };
+
+  virtual void override_chan_cfg(ChanCfgOverrides &override);
+  virtual void setup_camera(const RenderRelation &camera_arc);
+  virtual bool is_interactive() const;
+
+  void toggle_viz(Image &im);
+  virtual void create_image_geometry(Image &im);
+
+  static void static_handle_event(CPT(Event) event);
+  void handle_event(CPT(Event) event);
+
+  virtual void draw(bool);
+  virtual void idle();
+
+  typedef vector<Image> Images;
+  Images _images;
+
+  PT(GraphicsPipe) _main_pipe;
+  PT(GraphicsWindow) _main_win;
+  NodeAttributes _initial_state;
+  PT(NamedNode) _render;
+  PT(NamedNode) _cameras;
+  PT(NamedNode) _data_root;
+  PT(MouseAndKeyboard) _mak;
+  PT(Trackball) _trackball;
+  EventHandler _event_handler;
+
+  static StitchImageVisualizer *_static_siv;
+  bool _running;
+};
+
+#endif
+

+ 84 - 0
pandaapp/src/stitchviewer/triangleMesh.cxx

@@ -0,0 +1,84 @@
+// Filename: triangleMesh.cxx
+// Created by:  drose (06Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#include "triangleMesh.h"
+
+#include <geomTristrip.h>
+
+TriangleMesh::
+TriangleMesh(int x_verts, int y_verts) :
+  _coords(0), _norms(0), _colors(0), _texcoords(0),
+  _x_verts(x_verts),
+  _y_verts(y_verts)
+{
+}
+
+int TriangleMesh::
+get_x_verts() const {
+  return _x_verts;
+}
+
+int TriangleMesh::
+get_y_verts() const {
+  return _y_verts;
+}
+
+int TriangleMesh::
+get_num_verts() const {
+  return _x_verts * _y_verts;
+}
+
+GeomTristrip *TriangleMesh::
+build_mesh() const {
+  //  int num_verts = _x_verts * _y_verts;
+  int num_tstrips = (_y_verts-1);
+  int tstrip_length = 2*(_x_verts-1)+2;
+
+  PTA(int) lengths(num_tstrips);
+  PTA(ushort) vindex(num_tstrips * tstrip_length);
+
+  // Set the lengths array.  We are creating num_tstrips T-strips,
+  // each of which has t_strip length vertices.
+  int n;
+  for (n = 0; n < num_tstrips; n++) {
+    lengths[n] = tstrip_length;
+  }
+
+  // Now fill up the index array into the vertices.  This lays out the
+  // order of the vertices in each T-strip.
+  n = 0;
+  int ti, si;
+  for (ti = 1; ti < _y_verts; ti++) {
+    vindex[n++] = ti * _x_verts;
+    for (si = 1; si < _x_verts; si++) {
+      vindex[n++] = (ti - 1) * _x_verts + (si-1);
+      vindex[n++] = ti * _x_verts + si;
+    }
+    vindex[n++] = (ti - 1) * _x_verts + (_x_verts-1);
+  }
+  assert(n==num_tstrips * tstrip_length);
+
+  GeomTristrip *geom = new GeomTristrip;
+  geom->set_num_prims(num_tstrips);
+  geom->set_lengths(lengths);
+
+  assert(!_coords.empty());
+  geom->set_coords(_coords, G_PER_VERTEX, vindex);
+  
+  if (!_norms.empty()) {
+    geom->set_normals(_norms, G_PER_VERTEX, vindex);
+  }
+  
+  if (!_colors.empty()) {
+    geom->set_colors(_colors, G_PER_VERTEX, vindex);
+  }
+  
+  if (!_texcoords.empty()) {
+    geom->set_texcoords(_texcoords, G_PER_VERTEX, vindex);
+  }
+
+  return geom;
+}
+

+ 38 - 0
pandaapp/src/stitchviewer/triangleMesh.h

@@ -0,0 +1,38 @@
+// Filename: triangleMesh.h
+// Created by:  drose (06Nov99)
+// 
+////////////////////////////////////////////////////////////////////
+
+#ifndef TRIANGLEMESH_H
+#define TRIANGLEMESH_H
+
+#include <luse.h>
+#include <pta_Vertexf.h>
+#include <pta_Normalf.h>
+#include <pta_Colorf.h>
+#include <pta_TexCoordf.h>
+
+class GeomTristrip;
+
+class TriangleMesh {
+public:
+  TriangleMesh(int x_verts, int y_verts);
+
+  int get_x_verts() const;
+  int get_y_verts() const;
+  int get_num_verts() const;
+
+  GeomTristrip *build_mesh() const;
+
+  PTA_Vertexf _coords;
+  PTA_Normalf _norms;
+  PTA_Colorf _colors;
+  PTA_TexCoordf _texcoords;
+
+private:
+  int _x_verts, _y_verts;
+};
+
+#endif
+
+