/*
======================================================================
areavol2.c

Ernie Wright  18 Jan 09 (29 Nov 96)
MSVC 4.0

LightWave Modeler plug-in that calculates the surface area and volume
of the selected polygons in the foreground layer.  Based on Ronald N.
Goldman, "Area of Planar Polygons and Volume of Polyhedra," Graphics
Gems II, James Arvo ed., Academic Press, 1991 (ISBN 0-12-064480-0).

Added a UI that allows the user to select the units in which the area
and volume are displayed.
====================================================================== */

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <lwserver.h>
#include <lwcmdseq.h>
#include <lwpanel.h>

typedef struct st_MyData {
   GlobalFunc *global;
   MeshEditOp *edit;
   double      volume;
   double      area;
   int         selection;
   LWControl  *ctl_area_val;
   LWControl  *ctl_volume_val;
   LWControl  *ctl_area_unit;
   LWControl  *ctl_volume_unit;
} MyData;


typedef struct st_UnitConversion {
   char *name;
   double meters;
} UnitConversion;


#define NUM_AREA_UNITS 12
static UnitConversion area_unit[ NUM_AREA_UNITS ] = {
   "square nanometers",    1.0e-18,
   "square microns",       1.0e-12,
   "square millimeters",   1.0e-6,
   "square centimeters",   1.0e-4,
   "square meters",        1.0,
   "hectares",             1.0e4,
   "square kilometers",    1.0e6,
   "square inches",        6.4516e-4,
   "square feet",          0.09290304,
   "square yards",         0.83612736,
   "acres",                4.0468564224e3,
   "square miles",         2.589988110336e6
};

#define NUM_VOLUME_UNITS 14
static UnitConversion volume_unit[ NUM_VOLUME_UNITS ] = {
   "cubic nanometers",     1.0e-27,
   "cubic microns",        1.0e-18,
   "cubic millimeters",    1.0e-9,
   "cubic centimeters",    1.0e-6,
   "cubic meters",         1.0,
   "cubic kilometers",     1.0e9,
   "milliliters",          1.0e-6,
   "liters",               1.0e-3,
   "cubic inches",         1.6387064e-5,
   "cubic feet",           0.028316846592,
   "cubic yards",          0.764554857984,
   "cubic miles",          4.168181825440579584e9,
   "fluid ounces",         2.95735295625e-5,
   "gallons",              3.785411784e-3
};

static int area_unit_index = 4;
static int volume_unit_index = 4;


static void set_area_str( MyData *data )
{
   LWValue sval = { LWT_STRING };
   char buf[ 64 ];
   double val;

   val = data->area / area_unit[ area_unit_index ].meters;
   sprintf( buf, "%g", val );
   SET_STR( data->ctl_area_val, buf, sizeof( buf ));
}


static void set_volume_str( MyData *data )
{
   LWValue sval = { LWT_STRING };
   char buf[ 64 ];
   double val;

   val = data->volume / volume_unit[ volume_unit_index ].meters;
   sprintf( buf, "%g", val );
   SET_STR( data->ctl_volume_val, buf, sizeof( buf ));
}


static void area_unit_event( LWControl *ctl, MyData *data )
{
   LWValue ival = { LWT_INTEGER };

   GET_INT( ctl, area_unit_index );
   set_area_str( data );
}


static void volume_unit_event( LWControl *ctl, MyData *data )
{
   LWValue ival = { LWT_INTEGER };

   GET_INT( ctl, volume_unit_index );
   set_volume_str( data );
}


static void position_controls( MyData *data )
{
   LWValue ival = { LWT_INTEGER };
   int wa, wv, h1, h2, hbox, lwa, lwv, lwbox, x, y;
   int margin = 10;

   wa = CON_W( data->ctl_area_val );
   wv = CON_W( data->ctl_volume_val );
   h1 = CON_H( data->ctl_area_val );
   h2 = CON_H( data->ctl_area_unit );

   hbox = h1 > h2 ? h1 : h2;
   hbox = ( hbox * 3 ) / 2;

   lwa = CON_LW( data->ctl_area_val );
   lwv = CON_LW( data->ctl_volume_val );
   lwbox = lwa > lwv ? lwa : lwv;

   x = margin + lwbox - lwa;
   y = ( hbox - h1 ) / 2;
   MOVE_CON( data->ctl_area_val, x, y );
   x += wa;
   y = ( hbox - h2 ) / 2;
   MOVE_CON( data->ctl_area_unit, x, y );

   x = margin + lwbox - lwv;
   y = hbox + ( hbox - h1 ) / 2;
   MOVE_CON( data->ctl_volume_val, x, y );
   x += wv;
   y = hbox + ( hbox - h2 ) / 2;
   MOVE_CON( data->ctl_volume_unit, x, y );
}


static void display_results( MyData *data )
{
   LWPanelFuncs *panf;
   LWPanelID panel;
   LWPanControlDesc desc;
   LWValue ival = { LWT_INTEGER };
   char *area_name[ NUM_AREA_UNITS + 1 ],
        *volume_name[ NUM_VOLUME_UNITS + 1 ];
   int i;

   panf = data->global( LWPANELFUNCS_GLOBAL, GFUSE_TRANSIENT );
   if ( !panf ) return;
   panf->globalFun = data->global;

   if( !( panel = PAN_CREATE( panf, "AreaVolume2" )))
      return;

   for ( i = 0; i < NUM_AREA_UNITS; i++ )
      area_name[ i ] = area_unit[ i ].name;
   for ( i = 0; i < NUM_VOLUME_UNITS; i++ )
      volume_name[ i ] = volume_unit[ i ].name;
   area_name[ NUM_AREA_UNITS ] = NULL;
   volume_name[ NUM_VOLUME_UNITS ] = NULL;

   data->ctl_area_val    = STRRO_CTL( panf, panel, "Area", 20 );
   data->ctl_area_unit   = WPOPUP_CTL( panf, panel, " ", area_name, 150 );
   data->ctl_volume_val  = STRRO_CTL( panf, panel, "Volume", 20 );
   data->ctl_volume_unit = WPOPUP_CTL( panf, panel, " ", volume_name, 150 );

   position_controls( data );

   set_area_str( data );
   set_volume_str( data );
   SET_INT( data->ctl_area_unit, area_unit_index );
   SET_INT( data->ctl_volume_unit, volume_unit_index );

   CON_SETEVENT( data->ctl_area_unit, area_unit_event, data );
   CON_SETEVENT( data->ctl_volume_unit, volume_unit_event, data );

   panf->open( panel, PANF_BLOCKING );

   PAN_KILL( panf, panel );
}


/*
======================================================================
Vector operations.
====================================================================== */

static void copyv( LWDVector to, LWDVector from )
{
   to[ 0 ] = from[ 0 ];
   to[ 1 ] = from[ 1 ];
   to[ 2 ] = from[ 2 ];
}


static void add( LWDVector a, LWDVector b, LWDVector c )
{
   c[ 0 ] = a[ 0 ] + b[ 0 ];
   c[ 1 ] = a[ 1 ] + b[ 1 ];
   c[ 2 ] = a[ 2 ] + b[ 2 ];
}


static double dot( LWDVector a, LWDVector b )
{
   return a[ 0 ] * b[ 0 ] + a[ 1 ] * b[ 1 ] + a[ 2 ] * b[ 2 ];
}


static void cross( LWDVector a, LWDVector b, LWDVector c )
{
   c[ 0 ] = a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ];
   c[ 1 ] = a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ];
   c[ 2 ] = a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ];
}


/*
======================================================================
polycb()

Polygon enumeration callback.

INPUTS
   edit        MeshEditOp with access to edit and query functions
   polygon     the current polygon

RESULTS
   Adds the contribution of the current polygon to the calculation of
   area and volume.

The methods presented in Goldman involve sums over all polygons, so
as each polygon is presented to polycb(), the relevant quantities are
simply added to the two global variables for area and volume.

This function ignores curves, detail polygons, and one- and two-point
polygons.  If polygons were explicitly selected by the user, it also
ignores unselected polygons.
====================================================================== */

static EDError polycb( MyData *data, const EDPolygonInfo *polygon )
{
   MeshEditOp *edit = data->edit;
   LWDVector prev_pt, norm, v0, v = { 0 };
   EDPointInfo *pt;
   double a;
   int i, j;


   if ( polygon->type != LWPOLTYPE_FACE ) return EDERR_NONE;
   if ( polygon->numPnts < 3 ) return EDERR_NONE;

   if ( data->selection && !( polygon->flags & EDDF_SELECT ))
      return EDERR_NONE;

   pt = edit->pointInfo( edit->state, polygon->points[ 0 ] );
   copyv( prev_pt, pt->position );

   for ( i = 0; i < polygon->numPnts; i++ ) {
      j = ( i + 1 ) % polygon->numPnts;
      pt = edit->pointInfo( edit->state, polygon->points[ j ] );
      cross( prev_pt, pt->position, v0 );
      add( v, v0, v );
      copyv( prev_pt, pt->position );
   }

   edit->polyNormal( edit->state, polygon->pol, norm );
   data->area += a = 0.5 * fabs( dot( norm, v ));
   data->volume += dot( prev_pt, norm ) * a / 3;

   return EDERR_NONE;
}


/*
======================================================================
AreaVolume()

Activation function.

INPUTS
   version     should be 1
   global      access to global functions
   local       a ModCommand with access to mesh edit functions
   serverData  not used

RESULTS
   Displays the area and volume of the selected polygons.

All of the calculation is done during the edit->polyScan() call.  The
number of selected polygons is obtained first.  If this is zero, the
callback operates on all polygons in the foreground layer, otherwise
it ignores polygons that weren't explicitly selected.
====================================================================== */

XCALL_( int )
AreaVolume( long version, GlobalFunc *global, LWModCommand *local,
   void *serverData)
{
   MyData data;
   MeshEditOp *edit;

   if ( version != LWMODCOMMAND_VERSION ) return AFUNC_BADVERSION;

   edit = local->editBegin( 0, 0, OPSEL_USER );
   data.edit = edit;
   data.global = global;
   data.area = data.volume = 0.0;
   data.selection = edit->polyCount( edit->state, OPLYR_FG, EDCOUNT_SELECT );
   edit->polyScan( edit->state, polycb, &data, OPLYR_FG );
   edit->done( edit->state, EDERR_NONE, 0 );
   display_results( &data );

   return AFUNC_OK;
}


ServerRecord ServerDesc[] = {
   { LWMODCOMMAND_CLASS, "AreaVolume2", AreaVolume },
   { NULL }
};
