From 1422bb82aa32602da0cdf7fed127bd981db9ef32 Mon Sep 17 00:00:00 2001 From: Viktor Lidholt Date: Thu, 18 Jun 2015 15:29:26 -0700 Subject: [PATCH] Optimizes sprite transformations for box to node Enabling/disabling of handling multiple pointers Adds basic touch handling R=abarth@chromium.org Review URL: https://codereview.chromium.org/1179413009. --- examples/game/lib/game_demo_box.dart | 122 ++++++++++++------------- examples/game/lib/game_demo_world.dart | 82 +++++++++++++++++ examples/game/lib/node.dart | 95 +++++++++++++------ examples/game/lib/node_with_size.dart | 4 +- examples/game/lib/sprite.dart | 1 + examples/game/lib/sprite_box.dart | 89 ++++++++++++++++-- 6 files changed, 298 insertions(+), 95 deletions(-) diff --git a/examples/game/lib/game_demo_box.dart b/examples/game/lib/game_demo_box.dart index 58716a1d1..2b9f0c4d3 100644 --- a/examples/game/lib/game_demo_box.dart +++ b/examples/game/lib/game_demo_box.dart @@ -14,66 +14,66 @@ class GameDemoBox extends SpriteBox { int _secondPointer = -1; Point _firstPointerDownPos; - void handleEvent(Event event, BoxHitTestEntry entry) { - if (event is PointerEvent) { - Point pointerPos = new Point(event.x, event.y); - int pointer = event.pointer; - - switch (event.type) { - case 'pointerdown': - if (_firstPointer == -1) { - // Assign the first pointer - _firstPointer = pointer; - _firstPointerDownPos = pointerPos; - } - else if (_secondPointer == -1) { - // Assign second pointer - _secondPointer = pointer; - _gameWorld.controlFire(); - } - else { - // There is a pointer used for steering, let's fire instead - _gameWorld.controlFire(); - } - break; - case 'pointermove': - if (pointer == _firstPointer) { - // Handle turning control - double joystickX = 0.0; - double deltaX = pointerPos.x - _firstPointerDownPos.x; - if (deltaX > _steeringThreshold || deltaX < -_steeringThreshold) { - joystickX = (deltaX - _steeringThreshold)/(_steeringMax - _steeringThreshold); - if (joystickX > 1.0) joystickX = 1.0; - if (joystickX < -1.0) joystickX = -1.0; - } - - double joystickY = 0.0; - double deltaY = pointerPos.y - _firstPointerDownPos.y; - if (deltaY > _steeringThreshold || deltaY < -_steeringThreshold) { - joystickY = (deltaY - _steeringThreshold)/(_steeringMax - _steeringThreshold); - if (joystickY > 1.0) joystickY = 1.0; - if (joystickY < -1.0) joystickY = -1.0; - } - - _gameWorld.controlSteering(joystickX, joystickY); - } - break; - case 'pointerup': - case 'pointercancel': - if (pointer == _firstPointer) { - // Un-assign the first pointer - _firstPointer = -1; - _firstPointerDownPos = null; - _gameWorld.controlSteering(0.0, 0.0); - } - else if (pointer == _secondPointer) { - _secondPointer = -1; - } - break; - default: - break; - } - } - } +// void handleEvent(Event event, BoxHitTestEntry entry) { +// if (event is PointerEvent) { +// Point pointerPos = new Point(event.x, event.y); +// int pointer = event.pointer; +// +// switch (event.type) { +// case 'pointerdown': +// if (_firstPointer == -1) { +// // Assign the first pointer +// _firstPointer = pointer; +// _firstPointerDownPos = pointerPos; +// } +// else if (_secondPointer == -1) { +// // Assign second pointer +// _secondPointer = pointer; +// _gameWorld.controlFire(); +// } +// else { +// // There is a pointer used for steering, let's fire instead +// _gameWorld.controlFire(); +// } +// break; +// case 'pointermove': +// if (pointer == _firstPointer) { +// // Handle turning control +// double joystickX = 0.0; +// double deltaX = pointerPos.x - _firstPointerDownPos.x; +// if (deltaX > _steeringThreshold || deltaX < -_steeringThreshold) { +// joystickX = (deltaX - _steeringThreshold)/(_steeringMax - _steeringThreshold); +// if (joystickX > 1.0) joystickX = 1.0; +// if (joystickX < -1.0) joystickX = -1.0; +// } +// +// double joystickY = 0.0; +// double deltaY = pointerPos.y - _firstPointerDownPos.y; +// if (deltaY > _steeringThreshold || deltaY < -_steeringThreshold) { +// joystickY = (deltaY - _steeringThreshold)/(_steeringMax - _steeringThreshold); +// if (joystickY > 1.0) joystickY = 1.0; +// if (joystickY < -1.0) joystickY = -1.0; +// } +// +// _gameWorld.controlSteering(joystickX, joystickY); +// } +// break; +// case 'pointerup': +// case 'pointercancel': +// if (pointer == _firstPointer) { +// // Un-assign the first pointer +// _firstPointer = -1; +// _firstPointerDownPos = null; +// _gameWorld.controlSteering(0.0, 0.0); +// } +// else if (pointer == _secondPointer) { +// _secondPointer = -1; +// } +// break; +// default: +// break; +// } +// } +// } } diff --git a/examples/game/lib/game_demo_world.dart b/examples/game/lib/game_demo_world.dart index 3b5ae3552..82094d5b2 100644 --- a/examples/game/lib/game_demo_world.dart +++ b/examples/game/lib/game_demo_world.dart @@ -70,6 +70,9 @@ class GameDemoWorld extends NodeWithSize { // Add nebula addNebula(); + + userInteractionEnabled = true; + handleMultiplePointers = true; } // Methods for adding game objects @@ -200,6 +203,73 @@ class GameDemoWorld extends NodeWithSize { void controlFire() { addLaser(); } + + // Handle pointer events + + int _firstPointer = -1; + int _secondPointer = -1; + Point _firstPointerDownPos; + + bool handleEvent(SpriteBoxEvent event) { + Point pointerPos = convertPointToNodeSpace(event.boxPosition); + int pointer = event.pointer; + + switch (event.type) { + case 'pointerdown': + if (_firstPointer == -1) { + // Assign the first pointer + _firstPointer = pointer; + _firstPointerDownPos = pointerPos; + } + else if (_secondPointer == -1) { + // Assign second pointer + _secondPointer = pointer; + controlFire(); + } + else { + // There is a pointer used for steering, let's fire instead + controlFire(); + } + break; + case 'pointermove': + if (pointer == _firstPointer) { + // Handle turning control + double joystickX = 0.0; + double deltaX = pointerPos.x - _firstPointerDownPos.x; + if (deltaX > _steeringThreshold || deltaX < -_steeringThreshold) { + joystickX = (deltaX - _steeringThreshold)/(_steeringMax - _steeringThreshold); + if (joystickX > 1.0) joystickX = 1.0; + if (joystickX < -1.0) joystickX = -1.0; + } + + double joystickY = 0.0; + double deltaY = pointerPos.y - _firstPointerDownPos.y; + if (deltaY > _steeringThreshold || deltaY < -_steeringThreshold) { + joystickY = (deltaY - _steeringThreshold)/(_steeringMax - _steeringThreshold); + if (joystickY > 1.0) joystickY = 1.0; + if (joystickY < -1.0) joystickY = -1.0; + } + + controlSteering(joystickX, joystickY); + } + break; + case 'pointerup': + case 'pointercancel': + if (pointer == _firstPointer) { + // Un-assign the first pointer + _firstPointer = -1; + _firstPointerDownPos = null; + controlSteering(0.0, 0.0); + } + else if (pointer == _secondPointer) { + _secondPointer = -1; + } + break; + default: + break; + } + return true; + } } // Game objects @@ -230,6 +300,18 @@ class Asteroid extends Sprite { _movementVector = new Point(_rand.nextDouble() * _maxAsteroidSpeed * 2 - _maxAsteroidSpeed, _rand.nextDouble() * _maxAsteroidSpeed * 2 - _maxAsteroidSpeed); + + userInteractionEnabled = true; + } + + bool handleEvent(SpriteBoxEvent event) { + if (event.type == "pointerdown") { + colorOverlay = new Color(0x99ff0000); + } + else if (event.type == "pointerup") { + colorOverlay = null; + } + return false; } } diff --git a/examples/game/lib/node.dart b/examples/game/lib/node.dart index 1db9cf204..753ed972e 100644 --- a/examples/game/lib/node.dart +++ b/examples/game/lib/node.dart @@ -13,10 +13,10 @@ class Node { Point _position; double _rotation; - - bool _isMatrixDirty; + Matrix4 _transformMatrix; - Matrix4 _transformMatrixFromWorld; + Matrix4 _transformMatrixNodeToBox; + Matrix4 _transformMatrixBoxToNode; double _scaleX; double _scaleY; @@ -30,6 +30,10 @@ class Node { bool paused = false; + bool _userInteractionEnabled = false; + bool handleMultiplePointers = false; + int _handlingPointer; + List_children; // Constructors @@ -38,7 +42,6 @@ class Node { _rotation = 0.0; _position = Point.origin; _scaleX = _scaleY = 1.0; - _isMatrixDirty = false; _transformMatrix = new Matrix4.identity(); _children = []; _childrenNeedSorting = false; @@ -56,20 +59,23 @@ class Node { double get rotation => _rotation; void set rotation(double rotation) { + assert(rotation != null); _rotation = rotation; - _isMatrixDirty = true; + _invalidateTransformMatrix(); } Point get position => _position; void set position(Point position) { + assert(position != null); _position = position; - _isMatrixDirty = true; + _invalidateTransformMatrix(); } double get zPosition => _zPosition; void set zPosition(double zPosition) { + assert(zPosition != null); _zPosition = zPosition; if (_parent != null) { _parent._childrenNeedSorting = true; @@ -82,8 +88,9 @@ class Node { } void set scale(double scale) { + assert(scale != null); _scaleX = _scaleY = scale; - _isMatrixDirty = true; + _invalidateTransformMatrix(); } List get children => _children; @@ -91,6 +98,7 @@ class Node { // Adding and removing children void addChild(Node child) { + assert(child != null); assert(child._parent == null); _childrenNeedSorting = true; @@ -99,12 +107,15 @@ class Node { child._spriteBox = this._spriteBox; _childrenLastAddedOrder += 1; child._addedOrder = _childrenLastAddedOrder; + if (_spriteBox != null) _spriteBox._eventTargets = null; } void removeChild(Node child) { + assert(child != null); if (_children.remove(child)) { child._parent = null; child._spriteBox = null; + if (_spriteBox != null) _spriteBox._eventTargets = null; } } @@ -120,12 +131,13 @@ class Node { } _children = []; _childrenNeedSorting = false; + if (_spriteBox != null) _spriteBox._eventTargets = null; } // Calculating the transformation matrix Matrix4 get transformMatrix { - if (!_isMatrixDirty) { + if (_transformMatrix != null) { return _transformMatrix; } @@ -148,42 +160,58 @@ class Node { } // Create transformation matrix for scale, position and rotation - _transformMatrix.setValues(cy * _scaleX, sy * _scaleX, 0.0, 0.0, + _transformMatrix = new Matrix4(cy * _scaleX, sy * _scaleX, 0.0, 0.0, -sx * _scaleY, cx * _scaleY, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, - _position.x, _position.y, 0.0, 1.0 - ); + _position.x, _position.y, 0.0, 1.0); return _transformMatrix; } + void _invalidateTransformMatrix() { + _transformMatrix = null; + _invalidateToBoxTransformMatrix(); + } + + void _invalidateToBoxTransformMatrix () { + _transformMatrixNodeToBox = null; + _transformMatrixBoxToNode = null; + + for (Node child in children) { + child._invalidateToBoxTransformMatrix(); + } + } + // Transforms to other nodes Matrix4 _nodeToBoxMatrix() { assert(_spriteBox != null); - - Matrix4 t = transformMatrix; - - // Apply transforms from parents - Node p = this.parent; - while (p != null) { - t = new Matrix4.copy(p.transformMatrix).multiply(t); - p = p.parent; + if (_transformMatrixNodeToBox != null) { + return _transformMatrixNodeToBox; } - // Apply transform from sprite box - t = new Matrix4.copy(_spriteBox.transformMatrix).multiply(t); - - return t; + if (_parent == null) { + // Base case, we are at the top + assert(this == _spriteBox.rootNode); + _transformMatrixNodeToBox = new Matrix4.copy(_spriteBox.transformMatrix).multiply(transformMatrix); + } + else { + _transformMatrixNodeToBox = new Matrix4.copy(_parent._nodeToBoxMatrix()).multiply(transformMatrix); + } + return _transformMatrixNodeToBox; } Matrix4 _boxToNodeMatrix() { assert(_spriteBox != null); - Matrix4 t = _nodeToBoxMatrix(); - t.invert(); + if (_transformMatrixBoxToNode != null) { + return _transformMatrixBoxToNode; + } + + _transformMatrixBoxToNode = new Matrix4.copy(_nodeToBoxMatrix()); + _transformMatrixBoxToNode.invert(); - return t; + return _transformMatrixBoxToNode; } Point convertPointToNodeSpace(Point boxPoint) { @@ -225,6 +253,7 @@ class Node { // Rendering void visit(PictureRecorder canvas) { + assert(canvas != null); if (!visible) return; prePaint(canvas); @@ -241,7 +270,6 @@ class Node { } void paint(PictureRecorder canvas) { - } void visitChildren(PictureRecorder canvas) { @@ -276,4 +304,17 @@ class Node { void spriteBoxPerformedLayout() { } + + // Handling user interaction + + bool get userInteractionEnabled => _userInteractionEnabled; + + void set userInteractionEnabled(bool userInteractionEnabled) { + _userInteractionEnabled = userInteractionEnabled; + if (_spriteBox != null) _spriteBox._eventTargets = null; + } + + bool handleEvent(SpriteBoxEvent event) { + return false; + } } \ No newline at end of file diff --git a/examples/game/lib/node_with_size.dart b/examples/game/lib/node_with_size.dart index 3a6849a95..e8577f390 100644 --- a/examples/game/lib/node_with_size.dart +++ b/examples/game/lib/node_with_size.dart @@ -9,7 +9,9 @@ abstract class NodeWithSize extends Node { pivot = Point.origin; } - NodeWithSize.withSize(Size this.size, [Point this.pivot]); + NodeWithSize.withSize(Size this.size, [Point this.pivot]) { + if (pivot == null) pivot = Point.origin; + } void applyTransformForPivot(PictureRecorder canvas) { if (pivot.x != 0 || pivot.y != 0) { diff --git a/examples/game/lib/sprite.dart b/examples/game/lib/sprite.dart index 84bdf88bf..c730056eb 100644 --- a/examples/game/lib/sprite.dart +++ b/examples/game/lib/sprite.dart @@ -22,6 +22,7 @@ class Sprite extends NodeWithSize { double get opacity => _opacity; void set opacity(double opacity) { + assert(opacity != null); assert(opacity >= 0.0 && opacity <= 1.0); _opacity = opacity; } diff --git a/examples/game/lib/sprite_box.dart b/examples/game/lib/sprite_box.dart index 59311f6d7..1fb0b0806 100644 --- a/examples/game/lib/sprite_box.dart +++ b/examples/game/lib/sprite_box.dart @@ -28,7 +28,8 @@ class SpriteBox extends RenderBox { // Cached transformation matrix Matrix4 _transformMatrix; - bool _transformMatrixIsDirty; + + List _eventTargets; // Setup @@ -47,8 +48,6 @@ class SpriteBox extends RenderBox { _systemWidth = width; _systemHeight = height; - _transformMatrixIsDirty = true; - _scheduleTick(); } @@ -68,20 +67,80 @@ class SpriteBox extends RenderBox { void performLayout() { size = constraints.constrain(Size.infinite); - _transformMatrixIsDirty = true; + _invalidateTransformMatrix(); _callSpriteBoxPerformedLayout(_rootNode); } // Event handling - void handleEvent(Event event, BoxHitTestEntry entry) { + void _addEventTargets(Node node, List eventTargets) { + if (node.userInteractionEnabled) { + eventTargets.add(node); + } + for (Node child in node.children) { + _addEventTargets(child, eventTargets); + } + } + + void handleEvent(Event event, SpriteBoxHitTestEntry entry) { + if (event is PointerEvent) { + + if (event.type == 'pointerdown') { + // Build list of event targets + if (_eventTargets == null) { + _eventTargets = []; + _addEventTargets(_rootNode, _eventTargets); + } + + // Find the once that are hit by the pointer + List nodeTargets = []; + for (int i = _eventTargets.length - 1; i >= 0; i--) { + Node node = _eventTargets[i]; + + // Check if the node is ready to handle a pointer + if (node.handleMultiplePointers || node._handlingPointer == null) { + // Do the hit test + Point posInNodeSpace = node.convertPointToNodeSpace(entry.localPosition); + if (node.hitTest(posInNodeSpace)) { + nodeTargets.add(node); + node._handlingPointer = event.pointer; + } + } + } + + entry.nodeTargets = nodeTargets; + } + + // Pass the event down to nodes that were hit by the pointerdown + List targets = entry.nodeTargets; + for (Node node in targets) { + // Check if this event should be dispatched + if (node.handleMultiplePointers || event.pointer == node._handlingPointer) { + // Dispatch event + bool consumedEvent = node.handleEvent(new SpriteBoxEvent(new Point(event.x, event.y), event.type, event.pointer)); + if (consumedEvent == null || consumedEvent) break; + } + } + + // De-register pointer for nodes that doesn't handle multiple pointers + for (Node node in targets) { + if (event.type == 'pointerup' || event.type == 'pointercancel') { + node._handlingPointer = null; + } + } + } + } + + bool hitTest(HitTestResult result, { Point position }) { + result.add(new SpriteBoxHitTestEntry(this, position)); + return true; } // Rendering Matrix4 get transformMatrix { // Get cached matrix if available - if (!_transformMatrixIsDirty && _transformMatrix != null) { + if (_transformMatrix != null) { return _transformMatrix; } @@ -145,6 +204,11 @@ class SpriteBox extends RenderBox { return _transformMatrix; } + void _invalidateTransformMatrix() { + _transformMatrix = null; + _rootNode._invalidateToBoxTransformMatrix(); + } + void paint(RenderObjectDisplayList canvas) { canvas.save(); @@ -225,3 +289,16 @@ class SpriteBox extends RenderBox { } } } + +class SpriteBoxHitTestEntry extends BoxHitTestEntry { + List nodeTargets; + SpriteBoxHitTestEntry(RenderBox target, Point localPosition) : super(target, localPosition); +} + +class SpriteBoxEvent { + Point boxPosition; + String type; + int pointer; + + SpriteBoxEvent(this.boxPosition, this.type, this.pointer); +} \ No newline at end of file -- GitLab