// @topic W010203 FLTK Graphics -- maze navigation
// @brief class Drawing2D extends Fl_Widget
#ifndef _DRAWING2D_H_INCLUDED_
#define _DRAWING2D_H_INCLUDED_
#include <cassert>
#include <iostream>
#include <string>
#include <sstream>
#include "FL/fl_draw.H"
#include "e002_Room.h"
const double TICK_TIMEOUT = 0.04; // 50 ms
class Drawing2D : public Fl_Widget {
// Use 45 degree segment to calibrate segment:
static const int UNCALIBRATED_SEGMENT = 45;
Room* current_room;
Room* visible_door;
int ax, ay, AX, AY;
int bx, by, BX, BY;
int cx, cy, CX, CY;
int dx, dy, DX, DY;
int ex, ey, EX, EY;
int fx, fy, FX, FY;
int gx, gy, GX, GY;
int hx, hy, HX, HY;
int DLTx, DLTy; // delta
int CTRx, CTRy; // center
int compass;
// door support:
int sx, sy, SX, SY;
int tx, ty, TX, TY;
int ux, uy, UX, UY;
int wx, wy, WX, WY;
int ox, oy, OX, OY; // door knob
int ix, iy, IX, IY; // door ID tag
int ADJy;
int compass_segment; // degrees in one segment
int animation_count;
public:
Drawing2D( int x, int y, int w, int h )
: Fl_Widget( x, y, w, h, 0/*const char* label*/ )
{
// This version of constructor does not invoke
// initialize_room() - the user must do it in
// a separate step
}
Drawing2D( int x, int y, int w, int h, Room* room )
: Fl_Widget( x, y, w, h, 0/*const char* label*/ )
{
initialize_room( room );
}
void initialize_room( Room* room )
{
current_room = room;
visible_door = NULL;
animation_count = 0;
// Wall vertices
ax = x(); ay = y();
dx = x() + w(); dy = y() + h();
bx = dx; by = ay;
cx = ax; cy = dy;
ex = ax + w()/4; ey = ay + h()/4;
fx = bx - w()/4; fy = ey;
gx = ex; gy = cy - h()/4;
hx = fx; hy = gy;
// Remember original vertices
AX = ax; AY = ay;
DX = dx; DY = dy;
BX = bx; BY = by;
CX = cx; CY = cy;
EX = ex; EY = ey;
FX = fx; FY = fy;
GX = gx; GY = gy;
HX = hx; HY = hy;
// Rotation increments
DLTx = ( fx - ex ) / 10;
DLTy = ( gy - ey ) / 15;
// Coordinates of the center
CTRx = ( ax + dx ) / 2;
CTRy = ( ay + dy ) / 2;
// Door
ADJy = 0; // adjustment is unknown at the beginning
// 13*DLTy is the door height;
// 4*DLTx is the foor width:
sx = CTRx - 2*DLTx; sy = gy - 13*DLTy; SX = sx; SY = sy;
tx = CTRx + 2*DLTx; ty = sy; TX = tx; TY = ty;
ux = sx; uy = gy + 15; UX = ux; UY = uy;
wx = tx; wy = uy + 15; WX = wx; WY = wy;
// Initialize compass
compass = 0; // points to north
compass_segment = UNCALIBRATED_SEGMENT; // value to calibrate the compass and 3D drawing
// Calibrate 3D drawing
// turn 90 degrees to the right
int segment_count = 0;
while( compass < 90 ) {
turn_head_right();
segment_count++;
}
// complete 360 turn
while( compass > UNCALIBRATED_SEGMENT ) {
turn_head_right();
segment_count++;
}
compass_segment = 360/segment_count;
compass = 0; // points to north
// There is some drift of the door down on every 45-degree turn.
// It seems like the drift size is about one single DLTy:
ADJy = DLTy;
// recalculate door position
sx = CTRx - 2*DLTx; sy = gy - 13*DLTy; SX = sx; SY = sy;
tx = CTRx + 2*DLTx; ty = sy; TX = tx; TY = ty;
ux = sx; uy = gy; UX = ux; UY = uy;
wx = tx; wy = uy; WX = wx; WY = wy;
ox = sx; oy = sy + ( uy - sy ) / 2;
OX = ox; OY = oy; // door knob
ix = ox; iy = sy - 2*DLTy; IX = ix; IY = iy; // door ID tag
visible_door = current_room->get_room( Room::COMPASS_NORTH );
// Reset original vertices
//AX = ax; AY = ay;
//DX = dx; DY = dy;
//BX = bx; BY = by;
//CX = cx; CY = cy;
//EX = ex; EY = ey;
//FX = fx; FY = fy;
//GX = gx; GY = gy;
//HX = hx; HY = hy;
}
private:
static const Fl_Color m_background_color = FL_BLACK;
static const Fl_Color m_foreground_color = FL_WHITE;
// Window Events
int handle( int event ) {
switch( event ) {
case FL_RELEASE:
if ( !visible_door ) {
alarm_beep();
return Fl_Widget::handle(event);
} else {
redraw();
do_callback();
// never do anything after a callback, as the callback
// may delete the widget!
return 1; // non-zero indicates we already handled the event
}
case FL_PUSH:
case FL_DRAG:
case FL_SHORTCUT:
default:
return Fl_Widget::handle(event);
}
}
void draw()
{
fl_push_clip( x(), y(), w(), h() );
fl_color( m_background_color );
static bool back = true;
//back = back ? false : true;
if ( back ) fl_rectf( x(), y(), w(), h(), m_background_color ); // filled rect
//fl_rect( x()+5, y()+5, w()-10, h()-10, m_foreground_color ); // line rect
//fl_line( CTRx, CTRy, CTRx + 5*DLTy, CTRy + 5*DLTy ); // DEBUG -- marks center
fl_color( m_foreground_color );
/*
std::stringstream buffer;
buffer
<< compass_reading()
//<< compass_direction()
//<< compass
;
*/
const char* cstr = compass_reading();
fl_font( FL_TIMES, 18 );
fl_draw( cstr, CTRx, CY );
// diagonal line:
//fl_line( ax, ay, dx, dy );
fl_line( ex, ey, fx, fy ); // EF
fl_line( ex, ey, gx, gy ); // EG
if ( visible_door ) {
fl_line( hx, hy, gx, gy ); // HG
//fl_line( ux, uy, gx, gy ); // UG
//fl_line( ux, uy, wx, wy ); // UW
//fl_line( hx, hy, wx, wy ); // HW
} else {
fl_line( hx, hy, gx, gy ); // HG
}
fl_line( hx, hy, fx, fy ); // HF
fl_line( ex, ey, ax, ay ); // EA
fl_line( bx, by, fx, fy ); // BF
fl_line( hx, hy, dx, dy ); // HD
fl_line( cx, cy, gx, gy ); // CG
// draw door
if ( visible_door ) {
fl_line( sx, sy, tx, ty ); // ST
fl_line( sx, sy, ux, uy ); // SU
//fl_line( ux, uy, wx, wy ); // UW
fl_line( tx, ty, wx, wy ); // TW
fl_line( sx, sy, wx, wy ); // SW
fl_line( tx, ty, ux, uy ); // TU
fl_line( wx, wy, ux, uy ); // WU // threshold
fl_draw( "o", ox, oy ); // door knob
fl_draw( visible_door->get_name(), ix, iy ); // door tag
}
fl_pop_clip();
}
public:
// Operations
void turn_head_right()
{
if ( compass_segment != UNCALIBRATED_SEGMENT ) turn_compass_clockwise();
bool negative_b4 = (fy-ey) < 0;
//if ( fx > CTRx ) {
//ex -= DLTx; if ( ey > ay - 5*DLTy ) ey -= DLTy;
//fx -= DLTx; if ( fy < CTRy - 2*DLTy ) fy += DLTy;
//gx -= DLTx; if ( gy < cy - DLTy ) gy += DLTy;
//hx -= DLTx; if ( hy > CTRy + 5*DLTy ) hy -= DLTy;
//ex -= DLTx; if ( ey > AY - 5*DLTy ) ey -= DLTy;
//fx -= DLTx; if ( fy < CTRy - 5*DLTy ) fy += DLTy;
//gx -= DLTx; if ( gy < CY - 5*DLTy ) gy += DLTy;
//hx -= DLTx; if ( hy > CTRy + 5*DLTy ) hy -= DLTy;
// Front wall flows to the left:
ex -= DLTx; ey -= DLTy;
fx -= DLTx; fy += DLTy;
gx -= DLTx; gy += DLTy;
hx -= DLTx; hy -= DLTy;
// Left wall also flows to the left:
ax -= DLTx;
ay -= 3*DLTy;
cx -= DLTx;
cy += 3*DLTy;
// Front door flows to the left:
sx -= DLTx; sy -= (DLTy*2)/7;
tx -= DLTx; ty += (DLTy*2)/7;
ux -= DLTx; uy += (DLTy*2)/6;
wx -= DLTx; wy -= (DLTy*2)/8;
ox -= DLTx; //oy -= (DLTy*2)/7; // door knob
ix -= DLTx; iy -= (DLTy*2)/6; // door tag
if ( fx <= CTRx ) {
// User looks directly at the corner of the room
// flip door points
sx = symmetrical_x_positive( sx );
tx = symmetrical_x_positive( tx );
ux = symmetrical_x_positive( ux );
wx = symmetrical_x_positive( wx );
//ox = symmetrical_x_positive( ox );
//ix = symmetrical_x_positive( ix );
std::swap( sx, tx );
std::swap( ux, wx );
std::swap( sy, ty );
std::swap( uy, wy );
ox = sx;
ix = sx; iy = sy - 2*DLTy; // door tag
// adjust door against drift
sy -= ADJy;
ty -= ADJy;
uy -= ADJy;
wy -= ADJy;
// flip wall points once facing NE SE SW NW
ex = fx; ey = fy; // E = F
gx = hx; gy = hy; // G = H
//bx = ax + 2 * ( CTRx - ax ); by = ay;
bx = symmetrical_x_positive( ax ); by = ay;
// B = symmetrical(A)
ax = AX; ay = AY; // A = A_orig
//dx = cx + 2 * ( CTRx - cx ); dy = cy;
dx = symmetrical_x_positive( cx ); dy = cy;
// D = symmetrical(C)
cx = CX; cy = CY; // C = C_orig
//EX = ex; EY = ey;
fx = BX; fy = BY; // F = B_orig
//GX = gx; GY = gy;
hx = DX; hy = DY; // H = D_orig
if ( compass_segment == UNCALIBRATED_SEGMENT ) turn_compass_clockwise();
else compass_round();
configure_right_door();
return;
}
//}
bool negative_a4 = (fy-ey) < 0;
if ( negative_a4 != negative_b4 ) {
if ( compass_segment == UNCALIBRATED_SEGMENT ) turn_compass_clockwise();
else compass_round();
// User looks straight at the wall
// on every pass over 90 degree, i.e. N E S W,
// re-calibrate door position
recalibrate_door_position();
}
}
void recalibrate_door_position()
{
sx = SX; sy = SY;
tx = TX; ty = TY;
ux = UX; uy = UY;
wx = WX; wy = WY;
ox = OX; oy = OY; // door knob
ix = IX; iy = IY; // door id tag
//ex = EX; ey = EY;
//fx = FX; fy = FY;
//gx = GX; gy = GY;
//hx = HX; hy = HY;
gy = UY;
hy = UY;
}
// Determines whether the left-hand side door should become visible
void configure_left_door()
{
visible_door = current_room->get_room( compass_direction() - 1 );
}
// Determines whether the right-hand side door should become visible
void configure_right_door()
{
visible_door = current_room->get_room( compass_direction() + 1 );
}
// Turn compass needle clockwise
void turn_compass_clockwise()
{
compass += compass_segment;
compass %= 360; // stay within 360
}
void turn_head_left()
{
if ( compass_segment != UNCALIBRATED_SEGMENT ) turn_compass_counterclockwise();
bool negative_b4 = (ey-fy) < 0;
//if ( ex < CTRx ) {
// Front wall flows to the left:
ex += DLTx; ey += DLTy;
fx += DLTx; fy -= DLTy;
gx += DLTx; gy -= DLTy;
hx += DLTx; hy += DLTy;
// Right wall also flows to the right:
bx += DLTx;
by -= 3*DLTy;
dx += DLTx;
dy += 3*DLTy;
// Front door flows to the right:
tx += DLTx; ty -= (DLTy*2)/7;
sx += DLTx; sy += (DLTy*2)/7;
wx += DLTx; wy += (DLTy*2)/6;
ux += DLTx; uy -= (DLTy*2)/8;
ox += DLTx; //oy -= (DLTy*2)/7; // door knob
ix += DLTx; iy -= (DLTy*2)/6; // door tag
if ( ex >= CTRx ) {
// User looks straight at the corner of the room
// flip door points
sx = symmetrical_x_negative( sx );
tx = symmetrical_x_negative( tx );
ux = symmetrical_x_negative( ux );
wx = symmetrical_x_negative( wx );
//ox = symmetrical_x_negative( ox );
//ix = symmetrical_x_negative( ix );
std::swap( sx, tx );
std::swap( ux, wx );
std::swap( sy, ty );
std::swap( uy, wy );
ox = sx;
ix = sx; iy = sy - 2*DLTy; // door tag
// adjust door against drift
sy -= ADJy;
ty -= ADJy;
uy -= ADJy;
wy -= ADJy;
// flip wall points once facing NE SE SW NW
fx = ex; fy = ey; // F = E
hx = gx; hy = gy; // H = G
ax = symmetrical_x_negative( bx ); ay = by;
// A = symmetrical(B)
bx = BX; by = BY; // B = B_orig
cx = symmetrical_x_negative( dx ); cy = dy;
// C = symmetrical(D)
dx = DX; dy = DY; // D = D_orig
ex = AX; ey = AY; // E = A_orig
gx = CX; gy = CY; // G = C_orig
if ( compass_segment == UNCALIBRATED_SEGMENT ) turn_compass_counterclockwise();
else compass_round();
configure_left_door();
return;
}
//}
bool negative_a4 = (ey-fy) < 0;
if ( negative_a4 != negative_b4 ) {
if ( compass_segment == UNCALIBRATED_SEGMENT ) turn_compass_counterclockwise();
else compass_round();
// User looks straight at the wall
// on every pass over 90 degree, i.e. N E S W,
// re-calibrate door position
recalibrate_door_position();
}
}
// Turn compass needle counterclockwise
void turn_compass_counterclockwise()
{
if ( compass <= 0 ) {
compass = 360 + compass; // no negative degrees
}
compass -= compass_segment;
compass %= 360; // stay within 360
}
char const* compass_reading()
{
static char const*const sides[] = {
"N",
"NE",
"E",
"SE",
"S",
"SW",
"W",
"NW"
};
return sides[ compass_direction() ];
}
int compass_direction()
{
// N
// NW 338 23 NE
// 315 0 45
// 293 \|/ 68
//W 270-+-90 E
// 248 /|\ 113
// 225 180 135
// SW 203 158 SE
// S
if ( compass < 23 ) return Room::COMPASS_NORTH;
if ( compass < 68 ) return Room::COMPASS_NORTHEAST;
if ( compass < 113 ) return Room::COMPASS_EAST;
if ( compass < 158 ) return Room::COMPASS_SOUTHEAST;
if ( compass < 203 ) return Room::COMPASS_SOUTH;
if ( compass < 248 ) return Room::COMPASS_SOUTHWEST;
if ( compass < 293 ) return Room::COMPASS_WEST;
if ( compass < 338 ) return Room::COMPASS_NORTHWEST;
return Room::COMPASS_NORTH;
}
// Occasionally, at well-known points, it's a good
// idea to round compass direction. For example, when looking
// straight at the corner of the room.
void compass_round()
{
// N
// NW 338 23 NE
// 315 0 45
// 293 \|/ 68
//W 270-+-90 E
// 248 /|\ 113
// 225 180 135
// SW 203 158 SE
// S
if ( compass < 23 ) compass = 0; return;
if ( compass < 68 ) compass = 45; return;
if ( compass < 113 ) compass = 90; return;
if ( compass < 158 ) compass = 135; return;
if ( compass < 203 ) compass = 180; return;
if ( compass < 248 ) compass = 225; return;
if ( compass < 293 ) compass = 270; return;
if ( compass < 338 ) compass = 315; return;
compass = 0;
}
// Calculates position of the symmetrical X axis coordinate
// relative to the center point of the view
int symmetrical_x_positive( int xx )
{
// reflection as follows
// xx -> CTRx -> result
return xx + 2 * ( CTRx - xx );
}
// Calculates position of the symmetrical X axis coordinate
// relative to the center point of the view
int symmetrical_x_negative( int xx )
{
// reflection as follows
// result <- CTRx <- xx
return xx - 2 * ( xx - CTRx );
}
void zoom_view()
{
// Front wall zoom:
ex -= DLTx*2; ey -= DLTy*2;
fx += DLTx*2; fy -= DLTy*2;
gx -= DLTx*2; gy += DLTy*2;
hx += DLTx*2; hy += DLTy*2;
// Door zoom:
sx -= DLTx/2; sy -= DLTy;
tx += DLTx/2; ty -= DLTy;
ux -= DLTx/2; uy += DLTy;
wx += DLTx/2; wy += DLTy;
ox = sx; oy = sy + ( uy - sy ) /2; // door knob
ix = sx; iy = sy - 2*DLTy; // door id tag
}
bool animate_door_entry()
{
if ( animation_count ) {
// door entry animation is in progress
--animation_count;
if ( animation_count == 0 ) {
//animation_count = 0;
int tmp_compass = compass;
initialize_room( visible_door );
compass = tmp_compass;
compass_round();
visible_door = current_room->get_room( compass_direction() );
return false; // stop animation
}
zoom_view();
return true; // continue animation
} else if ( visible_door ) {
// entry animation is in progress
// in which direction is user trying to enter the door?
int door_direction = 45 * current_room->get_direction( visible_door );
//turn_degree = turn_degree ? turn_degree : 360;
int turn_degree = door_direction - compass;
if ( turn_degree < -270 ) turn_degree = 360 - turn_degree; // when user looks NW
//std::cout << " door: " << door_direction << '\n';
//std::cout << "turn_degree: " << turn_degree << '\n';
//std::cout << "old compass: " << compass << "\n\n";
//std::cout << "________________________________\n";
//return false;
if ( std::abs( turn_degree ) < 10 ) {
recalibrate_door_position();
//std::cout << "________________________________\n";
animation_count = 20;
return true;
}
//if ( turn_degree < -90 ) turn_head_right();
else if ( turn_degree > 0 ) {
turn_head_right();
} else {
turn_head_left();
}
//alarm_beep();
//turn_degree = door_direction - compass;
//std::cout << " remaining: " << turn_degree << '\n';
//std::cout << "new compass: " << compass << "\n\n";
return true; // keep animation running
}
std::cout << "-------------------------------------\n";
return false;
}
void alarm_beep()
{
std::cout << '\a';
}
};//class Drawing2D
/*
// N
// NW 338 23 NE
// 315 0 45
// 293 \|/ 68
//W 270-+-90 E
// 248 /|\ 113
// 225 180 135
// SW 203 158 SE
// S
A B
\ /
E________________________________F
| S_______T |
| | CTR | |
G___________U_______W____________H
/ \
C D
*/
#endif //_DRAWING2D_H_INCLUDED_