non/mixer/src/Panner.C

554 lines
14 KiB
C

/*******************************************************************************/
/* Copyright (C) 2008 Jonathan Moore Liles */
/* */
/* This program is free software; you can redistribute it and/or modify it */
/* under the terms of the GNU General Public License as published by the */
/* Free Software Foundation; either version 2 of the License, or (at your */
/* option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, but WITHOUT */
/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
/* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */
/* more details. */
/* */
/* You should have received a copy of the GNU General Public License along */
/* with This program; see the file COPYING. If not,write to the Free Software */
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/*******************************************************************************/
#include "Panner.H"
#include <FL/x.H>
#include <FL/Fl.H>
#include <stdio.h>
#include <math.h>
// #include <FL/fl_draw.H>
#include <FL/Fl_Shared_Image.H>
#include <FL/Fl_Tiled_Image.H>
#include "debug.h"
/* 2D Panner widget. Supports various multichannel configurations. */
Panner::Point *Panner::drag;
int Panner::_range_mode = 1;
int Panner::_projection_mode = 0;
Panner::Panner ( int X, int Y, int W, int H, const char *L ) :
Fl_Group( X, Y, W, H, L )
{
_bg_image = 0;
_bg_image_scaled = 0;
_bg_image_projection = 0;
// _projection = POLAR;
_points.push_back( Point( 1, 0 ) );
static float ranges[] = { 1,3,5,10,15 };
{ Fl_Choice *o = _range_choice = new Fl_Choice(X + 40,Y + H - 18,75,18,"Range:");
o->box(FL_UP_FRAME);
o->down_box(FL_DOWN_FRAME);
o->textsize(9);
o->labelsize(9);
o->align(FL_ALIGN_LEFT);
o->add("1 Meter",0,0,&ranges[0]);
o->add("3 Meters",0,0,&ranges[1]);
o->add("5 Meters",0,0,&ranges[2]);
o->add("10 Meters",0,0,&ranges[3]);
o->add("15 Meters",0,0,&ranges[4]);
o->value(_range_mode);
o->callback( cb_mode, this );
}
{ Fl_Choice *o = _projection_choice = new Fl_Choice(X + W - 75,Y + H - 18,75,18,"Projection:");
o->box(FL_UP_FRAME);
o->down_box(FL_DOWN_FRAME);
o->textsize(9);
o->labelsize(9);
o->align(FL_ALIGN_LEFT);
o->add("Spherical");
o->add("Planar");
o->value(_projection_mode);
o->callback( cb_mode, this );
}
end();
}
Panner::~Panner ( )
{
if ( _bg_image )
{
if ( _bg_image_scaled )
delete _bg_image;
else
((Fl_Shared_Image*)_bg_image)->release();
}
}
static int find_numeric_menu_item( const Fl_Menu_Item *menu, int n )
{
for ( unsigned int i = 0; menu[i].text; i++ )
{
if ( atoi( menu[i].text ) == n )
return i;
}
return -1;
}
void
Panner::cb_mode ( Fl_Widget *w, void *v )
{
((Panner*)v)->cb_mode( w );
}
void
Panner::cb_mode ( Fl_Widget *w )
{
if ( w == _range_choice )
_range_mode = _range_choice->value();
else if ( w == _projection_choice )
_projection_mode = _projection_choice->value();
}
void
Panner::range ( float v )
{
int i = find_numeric_menu_item( _range_choice->menu(), v );
_range_choice->value( i );
_range_mode = i;
}
/** set X, Y, W, and H to the bounding box of point /p/ in screen coords */
void
Panner::point_bbox ( const Point *p, int *X, int *Y, int *W, int *H ) const
{
int tx, ty, tw, th;
bbox( tx, ty, tw, th );
float px, py;
float s = 1.0f;
if ( projection() == POLAR )
{
project_polar( p, &px, &py, &s );
}
else
{
project_ortho( p, &px, &py, &s );
}
const float htw = float(tw)*0.5f;
const float hth = float(th)*0.5f;
*W = *H = tw * s;
if ( *W < 8 )
*W = 8;
if ( *H < 8 )
*H = 8;
*X = tx + (htw * px + htw) - *W/2;
*Y = ty + (hth * py + hth) - *H/2;
}
Panner::Point *
Panner::event_point ( void )
{
for ( int i = _points.size(); i--; )
{
int px, py, pw, ph;
Point *p = &_points[ i ];
if ( ! p->visible )
continue;
point_bbox( p, &px, &py, &pw, &ph );
// printf( "%d, %d -- %d %d %d %d\n", Fl::event_x(), Fl::event_y(), px, py, pw, ph );
if ( Fl::event_inside( px, py, pw, ph ) )
return p;
}
return NULL;
}
void
Panner::draw_the_box ( int tx, int ty, int tw, int th )
{
draw_box();
Fl_Image *i = 0;
if ( _bg_image && ( _bg_image->h() != th || projection() != _bg_image_projection ) )
{
if ( _bg_image_scaled )
delete _bg_image;
else
((Fl_Shared_Image*)_bg_image)->release();
_bg_image = 0;
}
if ( ! _bg_image )
{
if ( projection() == POLAR )
{
if ( th <= 92 )
i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-sphere-92x92.png" );
else if ( th <= 502 )
i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-sphere-502x502.png" );
else if ( th <= 802 )
i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-sphere-802x802.png" );
}
else
{
if ( th <= 92 )
i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-plane-92x92.png" );
else if ( th <= 502 )
i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-plane-502x502.png" );
else if ( th <= 802 )
i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-plane-802x802.png" );
}
if ( i && i->h() != th )
{
Fl_Image *scaled = i->copy( th, th );
_bg_image = scaled;
_bg_image_scaled = true;
}
else
{
_bg_image = i;
_bg_image_scaled = false;
}
}
_bg_image_projection = projection();
if ( _bg_image )
_bg_image->draw( tx, ty );
}
/** translate angle /a/ into x/y coords and place the result in /X/ and /Y/ */
void
Panner::project_polar ( const Point *p, float *X, float *Y, float *S ) const
{
float xp = 0.0f;
float yp = 0.0f;
float zp = 8.0f;
float x = 0 - p->y;
float y = 0 - p->x;
float z = 0 - p->z;
x /= range();
y /= range();
z /= range();
*X = ((x-xp) / (z + zp)) * (zp);
*Y = ((y-yp) / (z + zp)) * (zp);
*S = (0.025f / (z + zp)) * (zp);
}
/** translate angle /a/ into x/y coords and place the result in /X/ and /Y/ */
void
Panner::project_ortho ( const Point *p, float *X, float *Y, float *S ) const
{
const float x = ( 0 - p->y ) / range();
const float y = ( 0 - p->x ) / range();
// const float z = p->z;
// float zp = 4.0f;
*X = x;
*Y = y;
*S = 0.025f;
}
void
Panner::set_ortho ( Point *p, float x, float y )
{
y = 0 - y;
y *= 2;
x *= 2;
p->x = (y) * range();
p->y = (0 - x) * range();
}
void
Panner::set_polar ( Point *p, float x, float y )
{
/* FIXME: not quite the inverse of the projection... */
x = 0 - x;
y = 0 - y;
x *= 2;
y *= 2;
float r = powf( x,2 ) + powf(y,2 );
float X = ( 2 * x ) / ( 1 + r );
float Y = ( 2 * y ) / ( 1 + r );
float Z = ( -1 + r ) / ( 1 + r );
float S = p->radius() / range();
X *= S;
Y *= S;
Z *= S;
p->azimuth( -atan2f( X,Y ) * ( 180 / M_PI ) );
if ( p->elevation() > 0.0f )
p->elevation( -atan2f( Z, sqrtf( powf(X,2) + powf( Y, 2 ))) * ( 180 / M_PI ) );
else
p->elevation( atan2f( Z, sqrtf( powf(X,2) + powf( Y, 2 ))) * ( 180 / M_PI ) );
}
void
Panner::set_polar_radius ( Point *p, float x, float y )
{
y = 0 - y;
float r = sqrtf( powf( y, 2 ) + powf( x, 2 ) );
if ( r > 1.0f )
r = 1.0f;
r *= range() * 2;
if ( r > range() )
r = range();
p->radius( r );
}
void
Panner::draw ( void )
{
int tw, th, tx, ty;
bbox( tx, ty, tw, th );
fl_push_clip( x(),y(),w(),h() );
draw_the_box( tx, ty, tw, th );
// draw_box();
draw_label();
/* if ( _bypassed ) */
/* { */
/* draw_box(); */
/* fl_color( 0 ); */
/* fl_font( FL_HELVETICA, 12 ); */
/* fl_draw( "(bypass)", x(), y(), w(), h(), FL_ALIGN_CENTER ); */
/* goto done; */
/* } */
/* tx += b; */
/* ty += b; */
/* tw -= b * 2; */
/* th -= b * 2; */
fl_line_style( FL_SOLID, 1 );
fl_color( FL_WHITE );
for ( unsigned int i = 0; i < _points.size(); i++ )
{
Point *p = &_points[i];
if ( ! p->visible )
continue;
Fl_Color c = fl_color_add_alpha( p->color, 100 );
fl_color(c);
int px, py, pw, ph;
point_bbox( p, &px, &py, &pw, &ph );
{
float po = 5;
fl_push_clip( px - ( po * 12 ),
py - ( po * 12 ),
pw + ( po * 24 ), ph + (po * 24 ));
fl_pie( px + 5, py + 5, pw - 10, ph - 10, 0, 360 );
fl_pie( px, py, pw, ph, 0, 360 );
fl_pop_clip();
if ( projection() == POLAR )
{
fl_color( fl_color_average( fl_rgb_color( 127,127,127 ), p->color, 0.50 ) );
fl_begin_loop();
fl_circle( tx + tw/2, ty + th/2, tw/2.0f * ( ( p->radius() / range() )));
fl_end_loop();
}
}
const char *s = p->label;
fl_color( fl_color_add_alpha( fl_rgb_color( 220,255,255 ), 127 ) );
fl_font( FL_HELVETICA_BOLD_ITALIC, 10 );
fl_draw( s, px + 20, py + 1, 50, ph - 1, FL_ALIGN_LEFT );
if ( tw > 100 )
{
char pat[50];
snprintf( pat, sizeof(pat), "%.1f°:%.1f° %.1fm", p->azimuth(), p->elevation(), p->radius() );
// fl_color( fl_color_add_alpha( fl_rgb_color( 220,255,255 ), 127 ) );
fl_font( FL_COURIER, 9 );
fl_draw( pat, px + 20, py + 15, 50, ph - 1, FL_ALIGN_LEFT | FL_ALIGN_WRAP );
/* fl_font( FL_HELVETICA_ITALIC, 9 ); */
/* snprintf(pat, sizeof(pat), "range: %.1f meters", range() ); */
/* fl_draw( pat, tx, ty, tw, th, FL_ALIGN_LEFT | FL_ALIGN_BOTTOM | FL_ALIGN_INSIDE ); */
/* if ( _projection == POLAR ) */
/* { */
/* fl_draw( "Polar perspective; azimuth, elevation and radius input. Right click controls radius.", tx, ty, tw, th, FL_ALIGN_BOTTOM | FL_ALIGN_RIGHT | FL_ALIGN_INSIDE ); */
/* } */
/* else */
/* { */
/* fl_draw( "Polar orthographic; angle and distance input.", tx, ty, tw, th, FL_ALIGN_BOTTOM | FL_ALIGN_RIGHT | FL_ALIGN_INSIDE ); */
/* } */
}
}
if ( tw > 200 )
draw_children();
fl_line_style( FL_SOLID, 0 );
fl_pop_clip();
}
/* return the current gain setting for the path in/out */
Panner::Point *
Panner::point( int i )
{
return &_points[ i ];
}
int
Panner::handle ( int m )
{
int r = Fl_Group::handle( m );
switch ( m )
{
case FL_ENTER:
case FL_LEAVE:
_projection_choice->value(_projection_mode);
_range_choice->value(_range_mode);
redraw();
return 1;
case FL_PUSH:
{
if ( Fl::event_button1() || Fl::event_button3() )
drag = event_point();
if ( Fl::event_button2() )
{
/* if ( _projection == POLAR ) */
/* _projection = ORTHO; */
/* else */
/* _projection = POLAR; */
}
return 1;
}
case FL_RELEASE:
if ( drag )
{
do_callback();
drag = NULL;
redraw();
return 1;
}
else
return 0;
case FL_MOUSEWHEEL:
{
/* Point *p = event_point(); */
/* if ( p ) */
/* drag = p; */
/* if ( drag ) */
/* { */
/* // drag->elevation( drag->elevation() + Fl::event_dy()); */
/* drag->elevation( 0 - drag->elevation() ); */
/* do_callback(); */
/* redraw(); */
/* return 1; */
/* } */
return 1;
}
case FL_DRAG:
{
if ( ! drag )
return 0;
int tx, ty, tw, th;
bbox( tx, ty, tw, th );
float X = (float(Fl::event_x() - tx) / tw ) - 0.5f;
float Y = (float(Fl::event_y() - ty) / th) - 0.5f;
if ( Fl::event_button1() )
{
if ( POLAR == projection() )
set_polar( drag,X,Y );
else
{
if ( fabsf( X ) < 0.5f &&
fabsf( Y ) < 0.5f )
set_ortho( drag, X,Y );
}
}
else
set_polar_radius( drag,X,Y );
if ( when() & FL_WHEN_CHANGED )
do_callback();
damage(FL_DAMAGE_EXPOSE);
return 1;
}
}
return r;
// return 0;
}