/* File: MBCBoardViewMouse.mm Contains: Handle mouse coordinate transformations Version: 1.0 Copyright: © 2002 by Apple Computer, Inc., all rights reserved. File Ownership: DRI: Matthias Neeracher x43683 Writers: (MN) Matthias Neeracher Change History (most recent first): $Log: MBCBoardViewMouse.mm,v $ Revision 1.20 2004/09/08 00:35:24 neerache Reduce square sizes to avoid navigation ambiguities Revision 1.19 2004/08/16 07:50:55 neerache Support accessibility Revision 1.18 2003/07/18 22:14:26 neerache Disable pondering during drag to improve interactive performance (RADAR 2736549) Revision 1.17 2003/07/14 23:21:49 neerache Move promotion defaults into MBCBoard Revision 1.16 2003/07/07 08:47:54 neerache Switch to textured main window Revision 1.15 2003/06/18 21:55:17 neerache More (mostly unsuccessful) tweaking of floating windows Revision 1.14 2003/06/05 08:31:26 neerache Added Tuner Revision 1.13 2003/06/05 00:14:37 neerache Reduce excessive threshold Revision 1.12 2003/06/04 23:14:05 neerache Neater manipulation widget; remove obsolete graphics options Revision 1.11 2003/06/04 09:25:47 neerache New and improved board manipulation metaphor Revision 1.10 2003/06/02 05:44:48 neerache Implement direct board manipulation Revision 1.9 2003/05/24 20:28:27 neerache Address race conditions between ploayer and engine Revision 1.8 2003/05/02 01:16:33 neerache Simplify drawing methods Revision 1.7 2003/04/28 22:11:45 neerache Handle black promotion square Revision 1.6 2003/04/25 22:26:23 neerache Simplify mouse model, fix startup bug Revision 1.5 2003/04/25 16:37:00 neerache Clean automake build Revision 1.4 2003/04/24 23:20:35 neeri Support pawn promotions Revision 1.3 2003/04/02 19:01:36 neeri Explore strategies to speed up dragging Revision 1.2 2002/12/04 02:30:50 neeri Experiment (unsuccessfully so far) with ways to speed up piece movement Revision 1.1 2002/08/22 23:47:06 neeri Initial Checkin */ #import "MBCBoardViewMouse.h" #import "MBCBoardViewDraw.h" // For drawBoardPlane #import "MBCInteractivePlayer.h" #import #import using std::min; using std::max; // // We're doing a lot of Projects and UnProjects. // These classes encapsulate them. // class MBCProjector { public: MBCProjector(); NSPoint Project(MBCPosition pos); protected: GLint fViewport[4]; GLdouble fMVMatrix[16]; GLdouble fProjMatrix[16]; }; class MBCUnProjector : private MBCProjector { public: MBCUnProjector(GLdouble winX, GLdouble winY); MBCPosition UnProject(); MBCPosition UnProject(GLfloat knownY); private: GLdouble fWinX; GLdouble fWinY; }; MBCProjector::MBCProjector() { glGetIntegerv(GL_VIEWPORT, fViewport); glGetDoublev(GL_MODELVIEW_MATRIX, fMVMatrix); glGetDoublev(GL_PROJECTION_MATRIX, fProjMatrix); } NSPoint MBCProjector::Project(MBCPosition pos) { GLdouble w[3]; gluProject(pos[0], pos[1], pos[2], fMVMatrix, fProjMatrix, fViewport, w+0, w+1, w+2); NSPoint pt = {w[0], w[1]}; return pt; } MBCUnProjector::MBCUnProjector(GLdouble winX, GLdouble winY) : MBCProjector(), fWinX(winX), fWinY(winY) { } MBCPosition MBCUnProjector::UnProject() { MBCPosition pos; GLfloat z; GLdouble wv[3]; glReadPixels((GLint)fWinX, (GLint)fWinY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z); gluUnProject(fWinX, fWinY, z, fMVMatrix, fProjMatrix, fViewport, wv+0, wv+1, wv+2); pos[0] = wv[0]; pos[1] = wv[1]; pos[2] = wv[2]; return pos; } MBCPosition MBCUnProjector::UnProject(GLfloat knownY) { MBCPosition pos; GLdouble p1[3]; GLdouble p0[3]; gluUnProject(fWinX, fWinY, 1.0f, fMVMatrix, fProjMatrix, fViewport, p1+0, p1+1, p1+2); gluUnProject(fWinX, fWinY, 0.0f, fMVMatrix, fProjMatrix, fViewport, p0+0, p0+1, p0+2); GLdouble yint = (knownY-p1[1])/(p0[1]-p1[1]); pos[0] = p1[0]+(p0[0]-p1[0])*yint; pos[1] = knownY; pos[2] = p1[2]+(p0[2]-p1[2])*yint; return pos; } MBCPosition operator-(const MBCPosition & a, const MBCPosition & b) { MBCPosition res; res[0] = a[0]-b[0]; res[1] = a[1]-b[1]; res[2] = a[2]-b[2]; return res; } @implementation MBCBoardView ( Mouse ) - (NSRect) approximateBoundsOfSquare:(MBCSquare)square { const float kSquare = 4.5f; MBCProjector proj; MBCPosition pos = [self squareToPosition:square]; pos[0] -= kSquare; pos[2] -= kSquare; NSPoint p0 = proj.Project(pos); pos[0] += 2.0f*kSquare; NSPoint p1 = proj.Project(pos); pos[2] += 2.0f*kSquare; NSPoint p2 = proj.Project(pos); pos[0] -= 2.0f*kSquare; NSPoint p3 = proj.Project(pos); NSRect r; if (p1.x > p0.x) { r.origin.x = max(p0.x, p3.x); r.size.width = min(p1.x, p2.x)-r.origin.x; } else { r.origin.x = max(p1.x, p2.x); r.size.width = min(p0.x, p3.x)-r.origin.x; } if (p2.y > p1.y) { r.origin.y = max(p0.y, p1.y); r.size.height = min(p2.y, p3.y)-r.origin.y; } else { r.origin.y = max(p2.y, p3.y); r.size.height = min(p0.y, p1.y)-r.origin.y; } return r; } - (MBCPosition) mouseToPosition:(NSPoint)mouse { MBCUnProjector unproj(mouse.x, mouse.y); return unproj.UnProject(); } - (MBCPosition) eventToPosition:(NSEvent *)event { NSPoint p = [event locationInWindow]; NSPoint l = [self convertPoint:p fromView:nil]; return [self mouseToPosition:l]; } - (void) mouseMoved:(NSEvent *)event { MBCPosition pos = [self eventToPosition:event]; float pxa = fabs(pos[0]); float pza = fabs(pos[2]); NSCursor * cursor = fArrowCursor; if (pxa > kBoardRadius || pza > kBoardRadius) if (pxa < kBoardRadius+kBorderWidth+.1f && pza < kBoardRadius+kBorderWidth+.1f) cursor = fHandCursor; [cursor set]; } - (void) mouseDown:(NSEvent *)event { MBCSquare previouslyPicked = fPickedSquare; NSPoint p = [event locationInWindow]; NSPoint l = [self convertPoint:p fromView:nil]; // // On mousedown, we determine the point on the board surface that // corresponds to the mouse location by the frontmost Z value, but // then pretend that the click happened at board surface level. Weirdly // enough, this seems to give the most natural feeling mouse behavior. // MBCPosition pos = [self mouseToPosition:l]; MBCSquare selectedStart = fSelectedDest = [self positionToSquareOrRegion:&pos]; switch (fSelectedDest) { case kInvalidSquare: return; case kWhitePromoSquare: case kBlackPromoSquare: return; case kBorderRegion: fInBoardManipulation= true; fOrigMouse = l; fCurMouse = l; fRawAzimuth = fAzimuth; fSelectedPiece = 0; [NSCursor hide]; [NSEvent startPeriodicEventsAfterDelay:0.1f withPeriod:0.1f]; break; default: if (!fWantMouse || fInAnimation || pos[1] < 0.1) return; // // Let interactive player decide whether we hit one of their pieces // [fInteractive startSelection:fSelectedDest]; if (!fSelectedPiece) // Apparently not... return; break; } pos[1] = 0.0f; fSelectedStartPos = pos; gettimeofday(&fLastRedraw, NULL); fLastSelectedPos = pos; // // For better interactivity, we stop the engine while a drag is in progress // [[fController engine] interruptEngine]; [self drawNow]; NSDate * whenever = [NSDate distantFuture]; for (bool goOn = true; goOn; ) { event = [NSApp nextEventMatchingMask: NSPeriodicMask|NSLeftMouseUpMask|NSLeftMouseDraggedMask untilDate:whenever inMode:NSEventTrackingRunLoopMode dequeue:YES]; switch ([event type]) { case NSPeriodic: case NSLeftMouseDragged: [self dragAndRedraw:event forceRedraw:NO]; break; case NSLeftMouseUp: { [self dragAndRedraw:event forceRedraw:YES]; [fInteractive endSelection:fSelectedDest animate:NO]; if (fPickedSquare == previouslyPicked) fPickedSquare = kInvalidSquare; // Toggle pick goOn = false; if (fInBoardManipulation) { fInBoardManipulation = false; [NSCursor unhide]; [NSEvent stopPeriodicEvents]; } break; } default: /* Ignore any other kind of event. */ break; } } fSelectedDest = kInvalidSquare; } - (void) mouseUp:(NSEvent *)event { if (!fWantMouse || fInAnimation) return; MBCPiece promo; if (fSelectedDest == kWhitePromoSquare) { promo = [fBoard defaultPromotion:YES]; } else if (fSelectedDest == kBlackPromoSquare) { promo = [fBoard defaultPromotion:NO]; } else if (fPickedSquare != kInvalidSquare) { [fInteractive startSelection:fPickedSquare]; [fInteractive endSelection:fSelectedDest animate:YES]; return; } else return; switch (promo) { case QUEEN: if (fVariant == kVarSuicide) promo = KING; // King promotion is very popular in suicide else promo = KNIGHT; // Second most useful break; case KING: // Suicide only promo = KNIGHT; break; case KNIGHT: promo = ROOK; break; case ROOK: promo = BISHOP; break; case BISHOP: promo = QUEEN; break; } [fBoard setDefaultPromotion:promo for:fSelectedDest == kWhitePromoSquare]; [self setNeedsDisplay:YES]; } - (void) dragAndRedraw:(NSEvent *)event forceRedraw:(BOOL)force { if ([event type] != NSPeriodic) { NSPoint p = [event locationInWindow]; NSPoint l = [self convertPoint:p fromView:nil]; fCurMouse = l; // // On drag, we can use a fairly fast interpolation to determine // the 3D coordinate using the y where we touched the piece // MBCUnProjector unproj(l.x, l.y); fSelectedPos = unproj.UnProject(0.0f); [self snapToSquare:&fSelectedPos]; } struct timeval now; gettimeofday(&now, NULL); MBCPosition delta = fSelectedPos-fLastSelectedPos; GLfloat d2 = delta[0]*delta[0]+delta[2]*delta[2]; NSTimeInterval dt = now.tv_sec - fLastRedraw.tv_sec + 0.000001 * (now.tv_usec - fLastRedraw.tv_usec); const float kTiltSpeed = 0.50f; const float kSpinSpeed = 0.50f; const float kThreshold = 10.0f; const float kAzimuthRound = 5.0f; if (force) { [self setNeedsDisplay:YES]; } else if (fSelectedDest == kBorderRegion) { float dx = fCurMouse.x-fOrigMouse.x; float dy = fCurMouse.y-fOrigMouse.y; if (fabs(dx) > fabs(dy) && fabs(dx) > kThreshold) { fRawAzimuth += dx*dt*kSpinSpeed; fRawAzimuth = fmod(fRawAzimuth+360.0f, 360.0f); float angle = fmod((fAzimuth = fRawAzimuth), 90.0f); if (angle < kAzimuthRound) fAzimuth -= angle; else if (angle > 90.0f-kAzimuthRound) fAzimuth += 90.0f-angle; fNeedPerspective= true; fLastRedraw = now; [self drawNow]; } else if (fabs(dy) > kThreshold) { fElevation -= dy*dt*kTiltSpeed; fElevation = max(kMinElevation, min(kMaxElevation, fElevation)); fNeedPerspective= true; fLastRedraw = now; [self drawNow]; } } else if (d2 > 25.0f || (d2 > 1.0f && dt > 0.02)) { fSelectedDest = [self positionToSquare:&fSelectedPos]; fLastRedraw = now; [self drawNow]; } } @end // Local Variables: // mode:ObjC // End: