// @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_