Index: remoting/ios/ui/scene_view.mm |
diff --git a/remoting/ios/ui/scene_view.mm b/remoting/ios/ui/scene_view.mm |
deleted file mode 100644 |
index 97a577df775849732402ac4809cc22b272e617d8..0000000000000000000000000000000000000000 |
--- a/remoting/ios/ui/scene_view.mm |
+++ /dev/null |
@@ -1,642 +0,0 @@ |
-// Copyright 2014 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-#if !defined(__has_feature) || !__has_feature(objc_arc) |
-#error "This file requires ARC support." |
-#endif |
- |
-#import "remoting/ios/ui/scene_view.h" |
- |
-#import "remoting/ios/utility.h" |
- |
-namespace { |
- |
-// TODO (aboone) Some of the layout is not yet set in stone, so variables have |
-// been used to position and turn items on and off. Eventually these may be |
-// stabilized and removed. |
- |
-// Scroll speed multiplier for swiping |
-const static int kMouseSensitivity = 2.5; |
- |
-// Input Axis inversion |
-// 1 for standard, -1 for inverted |
-const static int kXAxisInversion = -1; |
-const static int kYAxisInversion = -1; |
- |
-// Experimental value for bounding the maximum zoom ratio |
-const static int kMaxZoomSize = 3; |
-} // namespace |
- |
-@interface SceneView (Private) |
-// Returns the number of pixels displayed per device pixel when the scaling is |
-// such that the entire frame would fit perfectly in content. Note the ratios |
-// are different for width and height, some people have multiple monitors, some |
-// have 16:9 or 4:3 while iPad is always single screen, but different iOS |
-// devices have different resolutions. |
-- (CGPoint)pixelRatio; |
- |
-// Return the FrameSize in perspective of the CLIENT resolution |
-- (webrtc::DesktopSize)frameSizeToScale:(float)scale; |
- |
-// When bounded on the top and right, this point is where the scene must be |
-// positioned given a scene size |
-- (webrtc::DesktopVector)getBoundsForSize:(const webrtc::DesktopSize&)size; |
- |
-// Converts a point in the the CLIENT resolution to a similar point in the HOST |
-// resolution. Additionally, CLIENT resolution is expressed in float values |
-// while HOST operates in integer values. |
-- (BOOL)convertTouchPointToMousePoint:(CGPoint)touchPoint |
- targetPoint:(webrtc::DesktopVector&)desktopPoint; |
- |
-// Converts a point in the the HOST resolution to a similar point in the CLIENT |
-// resolution. Additionally, CLIENT resolution is expressed in float values |
-// while HOST operates in integer values. |
-- (BOOL)convertMousePointToTouchPoint:(const webrtc::DesktopVector&)mousePoint |
- targetPoint:(CGPoint&)touchPoint; |
-@end |
- |
-@implementation SceneView |
- |
-- (id)init { |
- self = [super init]; |
- if (self) { |
- |
- _frameSize = webrtc::DesktopSize(1, 1); |
- _contentSize = webrtc::DesktopSize(1, 1); |
- _mousePosition = webrtc::DesktopVector(0, 0); |
- |
- _position = GLKVector3Make(0, 0, 1); |
- _margin.left = 0; |
- _margin.right = 0; |
- _margin.top = 0; |
- _margin.bottom = 0; |
- _anchored.left = false; |
- _anchored.right = false; |
- _anchored.top = false; |
- _anchored.bottom = false; |
- } |
- return self; |
-} |
- |
-- (const GLKMatrix4&)projectionMatrix { |
- return _projectionMatrix; |
-} |
- |
-- (const GLKMatrix4&)modelViewMatrix { |
- // Start by using the entire scene |
- _modelViewMatrix = GLKMatrix4Identity; |
- |
- // Position scene according to any panning or bounds |
- _modelViewMatrix = GLKMatrix4Translate(_modelViewMatrix, |
- _position.x + _margin.left, |
- _position.y + _margin.bottom, |
- 0.0); |
- |
- // Apply zoom |
- _modelViewMatrix = GLKMatrix4Scale(_modelViewMatrix, |
- _position.z / self.pixelRatio.x, |
- _position.z / self.pixelRatio.y, |
- 1.0); |
- |
- // We are directly above the screen and looking down. |
- static const GLKMatrix4 viewMatrix = GLKMatrix4MakeLookAt( |
- 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // center view |
- |
- _modelViewMatrix = GLKMatrix4Multiply(viewMatrix, _modelViewMatrix); |
- |
- return _modelViewMatrix; |
-} |
- |
-- (const webrtc::DesktopSize&)contentSize { |
- return _contentSize; |
-} |
- |
-- (void)setContentSize:(const CGSize&)size { |
- |
- _contentSize.set(size.width, size.height); |
- |
- _projectionMatrix = GLKMatrix4MakeOrtho( |
- 0.0, _contentSize.width(), 0.0, _contentSize.height(), 1.0, -1.0); |
- |
- TexturedQuad newQuad; |
- newQuad.bl.geometryVertex = CGPointMake(0.0, 0.0); |
- newQuad.br.geometryVertex = CGPointMake(_contentSize.width(), 0.0); |
- newQuad.tl.geometryVertex = CGPointMake(0.0, _contentSize.height()); |
- newQuad.tr.geometryVertex = |
- CGPointMake(_contentSize.width(), _contentSize.height()); |
- |
- newQuad.bl.textureVertex = CGPointMake(0.0, 1.0); |
- newQuad.br.textureVertex = CGPointMake(1.0, 1.0); |
- newQuad.tl.textureVertex = CGPointMake(0.0, 0.0); |
- newQuad.tr.textureVertex = CGPointMake(1.0, 0.0); |
- |
- _glQuad = newQuad; |
-} |
- |
-- (const webrtc::DesktopSize&)frameSize { |
- return _frameSize; |
-} |
- |
-- (void)setFrameSize:(const webrtc::DesktopSize&)size { |
- DCHECK(size.width() > 0 && size.height() > 0); |
- // Don't do anything if the size has not changed. |
- if (_frameSize.equals(size)) |
- return; |
- |
- _frameSize.set(size.width(), size.height()); |
- |
- _position.x = 0; |
- _position.y = 0; |
- |
- float verticalPixelScaleRatio = |
- (static_cast<float>(_contentSize.height() - _margin.top - |
- _margin.bottom) / |
- static_cast<float>(_frameSize.height())) / |
- _position.z; |
- |
- // Anchored at the position (0,0) |
- _anchored.left = YES; |
- _anchored.right = NO; |
- _anchored.top = NO; |
- _anchored.bottom = YES; |
- |
- [self panAndZoom:CGPointMake(0.0, 0.0) scaleBy:verticalPixelScaleRatio]; |
- |
- // Center the mouse on the CLIENT screen |
- webrtc::DesktopVector centerMouseLocation; |
- if ([self convertTouchPointToMousePoint:CGPointMake(_contentSize.width() / 2, |
- _contentSize.height() / 2) |
- targetPoint:centerMouseLocation]) { |
- _mousePosition.set(centerMouseLocation.x(), centerMouseLocation.y()); |
- } |
- |
-#if DEBUG |
- NSLog(@"resized frame:%d:%d scale:%f", |
- _frameSize.width(), |
- _frameSize.height(), |
- _position.z); |
-#endif // DEBUG |
-} |
- |
-- (const webrtc::DesktopVector&)mousePosition { |
- return _mousePosition; |
-} |
- |
-- (void)setPanVelocity:(const CGPoint&)delta { |
- _panVelocity.x = delta.x; |
- _panVelocity.y = delta.y; |
-} |
- |
-- (void)setMarginsFromLeft:(int)left |
- right:(int)right |
- top:(int)top |
- bottom:(int)bottom { |
- _margin.left = left; |
- _margin.right = right; |
- _margin.top = top; |
- _margin.bottom = bottom; |
-} |
- |
-- (void)draw { |
- glEnableVertexAttribArray(GLKVertexAttribPosition); |
- glEnableVertexAttribArray(GLKVertexAttribTexCoord0); |
- glEnableVertexAttribArray(GLKVertexAttribTexCoord1); |
- |
- // Define our scene space |
- glVertexAttribPointer(GLKVertexAttribPosition, |
- 2, |
- GL_FLOAT, |
- GL_FALSE, |
- sizeof(TexturedVertex), |
- &(_glQuad.bl.geometryVertex)); |
- // Define the desktop plane |
- glVertexAttribPointer(GLKVertexAttribTexCoord0, |
- 2, |
- GL_FLOAT, |
- GL_FALSE, |
- sizeof(TexturedVertex), |
- &(_glQuad.bl.textureVertex)); |
- // Define the cursor plane |
- glVertexAttribPointer(GLKVertexAttribTexCoord1, |
- 2, |
- GL_FLOAT, |
- GL_FALSE, |
- sizeof(TexturedVertex), |
- &(_glQuad.bl.textureVertex)); |
- |
- // Draw! |
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |
- |
- [Utility logGLErrorCode:@"SceneView draw"]; |
-} |
- |
-- (CGPoint)pixelRatio { |
- |
- CGPoint r = CGPointMake(static_cast<float>(_contentSize.width()) / |
- static_cast<float>(_frameSize.width()), |
- static_cast<float>(_contentSize.height()) / |
- static_cast<float>(_frameSize.height())); |
- return r; |
-} |
- |
-- (webrtc::DesktopSize)frameSizeToScale:(float)scale { |
- return webrtc::DesktopSize(_frameSize.width() * scale, |
- _frameSize.height() * scale); |
-} |
- |
-- (webrtc::DesktopVector)getBoundsForSize:(const webrtc::DesktopSize&)size { |
- webrtc::DesktopVector r( |
- _contentSize.width() - _margin.left - _margin.right - size.width(), |
- _contentSize.height() - _margin.bottom - _margin.top - size.height()); |
- |
- if (r.x() > 0) { |
- r.set((_contentSize.width() - size.width()) / 2, r.y()); |
- } |
- |
- if (r.y() > 0) { |
- r.set(r.x(), (_contentSize.height() - size.height()) / 2); |
- } |
- |
- return r; |
-} |
- |
-- (BOOL)containsTouchPoint:(CGPoint)point { |
- // Here frame is from the top-left corner, most other calculations are framed |
- // from the bottom left. |
- CGRect frame = |
- CGRectMake(_margin.left, |
- _margin.top, |
- _contentSize.width() - _margin.left - _margin.right, |
- _contentSize.height() - _margin.top - _margin.bottom); |
- return CGRectContainsPoint(frame, point); |
-} |
- |
-- (BOOL)convertTouchPointToMousePoint:(CGPoint)touchPoint |
- targetPoint:(webrtc::DesktopVector&)mousePoint { |
- if (![self containsTouchPoint:touchPoint]) { |
- return NO; |
- } |
- // A touch location occurs in respect to the user's entire view surface. |
- |
- // The GL Context is upside down from the User's perspective so flip it. |
- CGPoint glOrientedTouchPoint = |
- CGPointMake(touchPoint.x, _contentSize.height() - touchPoint.y); |
- |
- // The GL surface generally is not at the same origination point as the touch, |
- // so translate by the scene's position. |
- CGPoint glOrientedPointInRespectToFrame = |
- CGPointMake(glOrientedTouchPoint.x - _position.x, |
- glOrientedTouchPoint.y - _position.y); |
- |
- // The perspective exists in relative to the CLIENT resolution at 1:1, zoom |
- // our perspective so we are relative to the HOST at 1:1 |
- CGPoint glOrientedPointInFrame = |
- CGPointMake(glOrientedPointInRespectToFrame.x / _position.z, |
- glOrientedPointInRespectToFrame.y / _position.z); |
- |
- // Finally, flip the perspective back over to the Users, but this time in |
- // respect to the HOST desktop. Floor to ensure the result is always in |
- // frame. |
- CGPoint deskTopOrientedPointInFrame = |
- CGPointMake(floorf(glOrientedPointInFrame.x), |
- floorf(_frameSize.height() - glOrientedPointInFrame.y)); |
- |
- // Convert from float to integer |
- mousePoint.set(deskTopOrientedPointInFrame.x, deskTopOrientedPointInFrame.y); |
- |
- return CGRectContainsPoint( |
- CGRectMake(0, 0, _frameSize.width(), _frameSize.height()), |
- deskTopOrientedPointInFrame); |
-} |
- |
-- (BOOL)convertMousePointToTouchPoint:(const webrtc::DesktopVector&)mousePoint |
- targetPoint:(CGPoint&)touchPoint { |
- // A mouse point is in respect to the desktop frame. |
- |
- // Flip the perspective back over to the Users, in |
- // respect to the HOST desktop. |
- CGPoint deskTopOrientedPointInFrame = |
- CGPointMake(mousePoint.x(), _frameSize.height() - mousePoint.y()); |
- |
- // The perspective exists in relative to the CLIENT resolution at 1:1, zoom |
- // our perspective so we are relative to the HOST at 1:1 |
- CGPoint glOrientedPointInFrame = |
- CGPointMake(deskTopOrientedPointInFrame.x * _position.z, |
- deskTopOrientedPointInFrame.y * _position.z); |
- |
- // The GL surface generally is not at the same origination point as the touch, |
- // so translate by the scene's position. |
- CGPoint glOrientedPointInRespectToFrame = |
- CGPointMake(glOrientedPointInFrame.x + _position.x, |
- glOrientedPointInFrame.y + _position.y); |
- |
- // Convert from float to integer |
- touchPoint.x = floorf(glOrientedPointInRespectToFrame.x); |
- touchPoint.y = floorf(glOrientedPointInRespectToFrame.y); |
- |
- return [self containsTouchPoint:touchPoint]; |
-} |
- |
-- (void)panAndZoom:(CGPoint)translation scaleBy:(float)ratio { |
- CGPoint ratios = [self pixelRatio]; |
- |
- // New Scaling factor bounded by a min and max |
- float resultScale = _position.z * ratio; |
- float scaleUpperBound = MAX(ratios.x, MAX(ratios.y, kMaxZoomSize)); |
- float scaleLowerBound = MIN(ratios.x, ratios.y); |
- |
- if (resultScale < scaleLowerBound) { |
- resultScale = scaleLowerBound; |
- } else if (resultScale > scaleUpperBound) { |
- resultScale = scaleUpperBound; |
- } |
- |
- DCHECK(isnormal(resultScale) && resultScale > 0); |
- |
- // The GL perspective is upside down in relation to the User's view, so flip |
- // the translation |
- translation.y = -translation.y; |
- |
- // The constants here could be user options later. |
- translation.x = |
- translation.x * kXAxisInversion * (1 / (ratios.x * kMouseSensitivity)); |
- translation.y = |
- translation.y * kYAxisInversion * (1 / (ratios.y * kMouseSensitivity)); |
- |
- CGPoint delta = CGPointMake(0, 0); |
- CGPoint scaleDelta = CGPointMake(0, 0); |
- |
- webrtc::DesktopSize currentSize = [self frameSizeToScale:_position.z]; |
- |
- { |
- // Closure for this variable, so the variable is not available to the rest |
- // of this function |
- webrtc::DesktopVector currentBounds = [self getBoundsForSize:currentSize]; |
- // There are rounding errors in the scope of this function, see the |
- // butterfly effect. In successive calls, the resulting position isn't |
- // always exactly the calculated position. If we know we are Anchored, then |
- // go ahead and reposition it to the values above. |
- if (_anchored.right) { |
- _position.x = currentBounds.x(); |
- } |
- |
- if (_anchored.top) { |
- _position.y = currentBounds.y(); |
- } |
- } |
- |
- if (_position.z != resultScale) { |
- // When scaling the scene, the origination of scaling is the mouse's |
- // location. But when the frame is anchored, adjust the origination to the |
- // anchor point. |
- |
- CGPoint mousePositionInClientResolution; |
- [self convertMousePointToTouchPoint:_mousePosition |
- targetPoint:mousePositionInClientResolution]; |
- |
- // Prefer to zoom based on the left anchor when there is a choice |
- if (_anchored.left) { |
- mousePositionInClientResolution.x = 0; |
- } else if (_anchored.right) { |
- mousePositionInClientResolution.x = _contentSize.width(); |
- } |
- |
- // Prefer to zoom out from the top anchor when there is a choice |
- if (_anchored.top) { |
- mousePositionInClientResolution.y = _contentSize.height(); |
- } else if (_anchored.bottom) { |
- mousePositionInClientResolution.y = 0; |
- } |
- |
- scaleDelta.x -= |
- [SceneView positionDeltaFromScaling:ratio |
- position:_position.x |
- length:currentSize.width() |
- anchor:mousePositionInClientResolution.x]; |
- |
- scaleDelta.y -= |
- [SceneView positionDeltaFromScaling:ratio |
- position:_position.y |
- length:currentSize.height() |
- anchor:mousePositionInClientResolution.y]; |
- } |
- |
- delta.x = [SceneView |
- positionDeltaFromTranslation:translation.x |
- position:_position.x |
- freeSpace:_contentSize.width() - currentSize.width() |
- scaleingPositionDelta:scaleDelta.x |
- isAnchoredLow:_anchored.left |
- isAnchoredHigh:_anchored.right]; |
- |
- delta.y = [SceneView |
- positionDeltaFromTranslation:translation.y |
- position:_position.y |
- freeSpace:_contentSize.height() - currentSize.height() |
- scaleingPositionDelta:scaleDelta.y |
- isAnchoredLow:_anchored.bottom |
- isAnchoredHigh:_anchored.top]; |
- { |
- // Closure for this variable, so the variable is not available to the rest |
- // of this function |
- webrtc::DesktopVector bounds = |
- [self getBoundsForSize:[self frameSizeToScale:resultScale]]; |
- |
- delta.x = [SceneView boundDeltaFromPosition:_position.x |
- delta:delta.x |
- lowerBound:bounds.x() |
- upperBound:0]; |
- |
- delta.y = [SceneView boundDeltaFromPosition:_position.y |
- delta:delta.y |
- lowerBound:bounds.y() |
- upperBound:0]; |
- } |
- |
- BOOL isLeftAndRightAnchored = _anchored.left && _anchored.right; |
- BOOL isTopAndBottomAnchored = _anchored.top && _anchored.bottom; |
- |
- [self updateMousePositionAndAnchorsWithTranslation:translation |
- scale:resultScale]; |
- |
- // If both anchors were lost, then keep the one that is easier to predict |
- if (isLeftAndRightAnchored && !_anchored.left && !_anchored.right) { |
- delta.x = -_position.x; |
- _anchored.left = YES; |
- } |
- |
- // If both anchors were lost, then keep the one that is easier to predict |
- if (isTopAndBottomAnchored && !_anchored.top && !_anchored.bottom) { |
- delta.y = -_position.y; |
- _anchored.bottom = YES; |
- } |
- |
- // FINALLY, update the scene's position |
- _position.x += delta.x; |
- _position.y += delta.y; |
- _position.z = resultScale; |
-} |
- |
-- (void)updateMousePositionAndAnchorsWithTranslation:(CGPoint)translation |
- scale:(float)scale { |
- webrtc::DesktopVector centerMouseLocation; |
- [self convertTouchPointToMousePoint:CGPointMake(_contentSize.width() / 2, |
- _contentSize.height() / 2) |
- targetPoint:centerMouseLocation]; |
- |
- webrtc::DesktopVector currentBounds = |
- [self getBoundsForSize:[self frameSizeToScale:_position.z]]; |
- webrtc::DesktopVector nextBounds = |
- [self getBoundsForSize:[self frameSizeToScale:scale]]; |
- |
- webrtc::DesktopVector predictedMousePosition( |
- _mousePosition.x() - translation.x, _mousePosition.y() + translation.y); |
- |
- _mousePosition.set( |
- [SceneView boundMouseGivenNextPosition:predictedMousePosition.x() |
- maxPosition:_frameSize.width() |
- centerPosition:centerMouseLocation.x() |
- isAnchoredLow:_anchored.left |
- isAnchoredHigh:_anchored.right], |
- [SceneView boundMouseGivenNextPosition:predictedMousePosition.y() |
- maxPosition:_frameSize.height() |
- centerPosition:centerMouseLocation.y() |
- isAnchoredLow:_anchored.top |
- isAnchoredHigh:_anchored.bottom]); |
- |
- _panVelocity.x = [SceneView boundVelocity:_panVelocity.x |
- axisLength:_frameSize.width() |
- mousePosition:_mousePosition.x()]; |
- _panVelocity.y = [SceneView boundVelocity:_panVelocity.y |
- axisLength:_frameSize.height() |
- mousePosition:_mousePosition.y()]; |
- |
- _anchored.left = (nextBounds.x() >= 0) || |
- (_position.x == 0 && |
- predictedMousePosition.x() <= centerMouseLocation.x()); |
- |
- _anchored.right = |
- (nextBounds.x() >= 0) || |
- (_position.x == currentBounds.x() && |
- predictedMousePosition.x() >= centerMouseLocation.x()) || |
- (_mousePosition.x() == _frameSize.width() - 1 && !_anchored.left); |
- |
- _anchored.bottom = (nextBounds.y() >= 0) || |
- (_position.y == 0 && |
- predictedMousePosition.y() >= centerMouseLocation.y()); |
- |
- _anchored.top = |
- (nextBounds.y() >= 0) || |
- (_position.y == currentBounds.y() && |
- predictedMousePosition.y() <= centerMouseLocation.y()) || |
- (_mousePosition.y() == _frameSize.height() - 1 && !_anchored.bottom); |
-} |
- |
-+ (float)positionDeltaFromScaling:(float)ratio |
- position:(float)position |
- length:(float)length |
- anchor:(float)anchor { |
- float newSize = length * ratio; |
- float scaleXBy = fabs(position - anchor) / length; |
- float delta = (newSize - length) * scaleXBy; |
- return delta; |
-} |
- |
-+ (int)positionDeltaFromTranslation:(int)translation |
- position:(int)position |
- freeSpace:(int)freeSpace |
- scaleingPositionDelta:(int)scaleingPositionDelta |
- isAnchoredLow:(BOOL)isAnchoredLow |
- isAnchoredHigh:(BOOL)isAnchoredHigh { |
- if (isAnchoredLow && isAnchoredHigh) { |
- // center the view |
- return (freeSpace / 2) - position; |
- } else if (isAnchoredLow) { |
- return 0; |
- } else if (isAnchoredHigh) { |
- return scaleingPositionDelta; |
- } else { |
- return translation + scaleingPositionDelta; |
- } |
-} |
- |
-+ (int)boundDeltaFromPosition:(float)position |
- delta:(int)delta |
- lowerBound:(int)lowerBound |
- upperBound:(int)upperBound { |
- int result = position + delta; |
- |
- if (lowerBound < upperBound) { // the view is larger than the bounds |
- if (result > upperBound) { |
- result = upperBound; |
- } else if (result < lowerBound) { |
- result = lowerBound; |
- } |
- } else { |
- // the view is smaller than the bounds so we'll always be at the lowerBound |
- result = lowerBound; |
- } |
- return result - position; |
-} |
- |
-+ (int)boundMouseGivenNextPosition:(int)nextPosition |
- maxPosition:(int)maxPosition |
- centerPosition:(int)centerPosition |
- isAnchoredLow:(BOOL)isAnchoredLow |
- isAnchoredHigh:(BOOL)isAnchoredHigh { |
- if (nextPosition < 0) { |
- return 0; |
- } |
- if (nextPosition > maxPosition - 1) { |
- return maxPosition - 1; |
- } |
- |
- if ((isAnchoredLow && nextPosition <= centerPosition) || |
- (isAnchoredHigh && nextPosition >= centerPosition)) { |
- return nextPosition; |
- } |
- |
- return centerPosition; |
-} |
- |
-+ (float)boundVelocity:(float)velocity |
- axisLength:(int)axisLength |
- mousePosition:(int)mousePosition { |
- if (velocity != 0) { |
- if (mousePosition <= 0 || mousePosition >= (axisLength - 1)) { |
- return 0; |
- } |
- } |
- |
- return velocity; |
-} |
- |
-- (BOOL)tickPanVelocity { |
- BOOL inMotion = ((_panVelocity.x != 0.0) || (_panVelocity.y != 0.0)); |
- |
- if (inMotion) { |
- |
- uint32_t divisor = 50 / _position.z; |
- float reducer = .95; |
- |
- if (_panVelocity.x != 0.0 && ABS(_panVelocity.x) < divisor) { |
- _panVelocity = CGPointMake(0.0, _panVelocity.y); |
- } |
- |
- if (_panVelocity.y != 0.0 && ABS(_panVelocity.y) < divisor) { |
- _panVelocity = CGPointMake(_panVelocity.x, 0.0); |
- } |
- |
- [self panAndZoom:CGPointMake(_panVelocity.x / divisor, |
- _panVelocity.y / divisor) |
- scaleBy:1.0]; |
- |
- _panVelocity.x *= reducer; |
- _panVelocity.y *= reducer; |
- } |
- |
- return inMotion; |
-} |
- |
-@end |