OLD | NEW |
| (Empty) |
1 part of sprites; | |
2 | |
3 double convertDegrees2Radians(double degrees) => degrees * Math.PI/180.8; | |
4 | |
5 double convertRadians2Degrees(double radians) => radians * 180.0/Math.PI; | |
6 | |
7 /// A base class for all objects that can be added to the sprite node tree and r
endered to screen using [SpriteBox] and | |
8 /// [SpriteWidget]. | |
9 /// | |
10 /// The [Node] class itself doesn't render any content, but provides the basic f
unctions of any type of node, such as | |
11 /// handling transformations and user input. To render the node tree, a root nod
e must be added to a [SpriteBox] or a | |
12 /// [SpriteWidget]. Commonly used sub-classes of [Node] are [Sprite], [NodeWithS
ize], and many more upcoming subclasses. | |
13 /// | |
14 /// Nodes form a hierarchical tree. Each node can have a number of children, and
the transformation (positioning, | |
15 /// rotation, and scaling) of a node also affects its children. | |
16 class Node { | |
17 | |
18 // Member variables | |
19 | |
20 SpriteBox _spriteBox; | |
21 Node _parent; | |
22 | |
23 Point _position = Point.origin; | |
24 double _rotation = 0.0; | |
25 | |
26 Matrix4 _transformMatrix = new Matrix4.identity(); | |
27 Matrix4 _transformMatrixNodeToBox; | |
28 Matrix4 _transformMatrixBoxToNode; | |
29 | |
30 double _scaleX = 1.0; | |
31 double _scaleY = 1.0; | |
32 | |
33 /// The visibility of this node and its children. | |
34 bool visible = true; | |
35 | |
36 double _zPosition = 0.0; | |
37 int _addedOrder; | |
38 int _childrenLastAddedOrder = 0; | |
39 bool _childrenNeedSorting = false; | |
40 | |
41 /// Decides if the node and its children is currently paused. | |
42 /// | |
43 /// A paused node will not receive any input events, update calls, or run any
animations. | |
44 /// | |
45 /// myNodeTree.paused = true; | |
46 bool paused = false; | |
47 | |
48 bool _userInteractionEnabled = false; | |
49 | |
50 /// If set to true the node will receive multiple pointers, otherwise it will
only receive events the first pointer. | |
51 /// | |
52 /// This property is only meaningful if [userInteractionEnabled] is set to tru
e. Default value is false. | |
53 /// | |
54 /// class MyCustomNode extends Node { | |
55 /// handleMultiplePointers = true; | |
56 /// } | |
57 bool handleMultiplePointers = false; | |
58 int _handlingPointer; | |
59 | |
60 List<Node>_children = []; | |
61 | |
62 // Constructors | |
63 | |
64 /// Creates a new [Node] without any transformation. | |
65 /// | |
66 /// var myNode = new Node(); | |
67 Node() { | |
68 } | |
69 | |
70 // Property setters and getters | |
71 | |
72 /// The [SpriteBox] this node is added to, or null if it's not currently added
to a [SpriteBox]. | |
73 /// | |
74 /// For most applications it's not necessary to access the [SpriteBox] directl
y. | |
75 /// | |
76 /// // Get the transformMode of the sprite box | |
77 /// var transformMode = myNode.spriteBox.transformMode; | |
78 SpriteBox get spriteBox => _spriteBox; | |
79 | |
80 /// The parent of this node, or null if it doesn't have a parent. | |
81 /// | |
82 /// // Hide the parent | |
83 /// myNode.parent.visible = false; | |
84 Node get parent => _parent; | |
85 | |
86 /// The rotation of this node in degrees. | |
87 /// | |
88 /// myNode.rotation = 45.0; | |
89 double get rotation => _rotation; | |
90 | |
91 void set rotation(double rotation) { | |
92 assert(rotation != null); | |
93 _rotation = rotation; | |
94 _invalidateTransformMatrix(); | |
95 } | |
96 | |
97 /// The position of this node relative to its parent. | |
98 /// | |
99 /// myNode.position = new Point(42.0, 42.0); | |
100 Point get position => _position; | |
101 | |
102 void set position(Point position) { | |
103 assert(position != null); | |
104 _position = position; | |
105 _invalidateTransformMatrix(); | |
106 } | |
107 | |
108 /// The draw order of this node compared to its parent and its siblings. | |
109 /// | |
110 /// By default nodes are drawn in the order that they have been added to a par
ent. To override this behavior the | |
111 /// [zPosition] property can be used. A higher value of this property will for
ce the node to be drawn in front of | |
112 /// siblings that have a lower value. If a negative value is used the node wil
l be drawn behind its parent. | |
113 /// | |
114 /// nodeInFront.zPosition = 1.0; | |
115 /// nodeBehind.zPosition = -1.0; | |
116 double get zPosition => _zPosition; | |
117 | |
118 void set zPosition(double zPosition) { | |
119 assert(zPosition != null); | |
120 _zPosition = zPosition; | |
121 if (_parent != null) { | |
122 _parent._childrenNeedSorting = true; | |
123 } | |
124 } | |
125 | |
126 /// The scale of this node relative its parent. | |
127 /// | |
128 /// The [scale] property is only valid if [scaleX] and [scaleY] are equal valu
es. | |
129 /// | |
130 /// myNode.scale = 5.0; | |
131 double get scale { | |
132 assert(_scaleX == _scaleY); | |
133 return _scaleX; | |
134 } | |
135 | |
136 void set scale(double scale) { | |
137 assert(scale != null); | |
138 _scaleX = _scaleY = scale; | |
139 _invalidateTransformMatrix(); | |
140 } | |
141 | |
142 /// The horizontal scale of this node relative its parent. | |
143 /// | |
144 /// myNode.scaleX = 5.0; | |
145 double get scaleX => _scaleX; | |
146 | |
147 void set scaleX(double scaleX) { | |
148 assert(scaleX != null); | |
149 _scaleX = scaleX; | |
150 _invalidateTransformMatrix(); | |
151 } | |
152 | |
153 /// The vertical scale of this node relative its parent. | |
154 /// | |
155 /// myNode.scaleY = 5.0; | |
156 double get scaleY => _scaleY; | |
157 | |
158 void set scaleY(double scaleY) { | |
159 assert(scaleY != null); | |
160 _scaleY = scaleY; | |
161 _invalidateTransformMatrix(); | |
162 } | |
163 | |
164 /// A list of the children of this node. | |
165 /// | |
166 /// This list should only be modified by using the [addChild] and [removeChild
] methods. | |
167 /// | |
168 /// // Iterate over a nodes children | |
169 /// for (Node child in myNode.children) { | |
170 /// // Do something with the child | |
171 /// } | |
172 List<Node> get children { | |
173 _sortChildren(); | |
174 return _children; | |
175 } | |
176 | |
177 // Adding and removing children | |
178 | |
179 /// Adds a child to this node. | |
180 /// | |
181 /// The same node cannot be added to multiple nodes. | |
182 /// | |
183 /// addChild(new Sprite(myImage)); | |
184 void addChild(Node child) { | |
185 assert(child != null); | |
186 assert(child._parent == null); | |
187 | |
188 _childrenNeedSorting = true; | |
189 _children.add(child); | |
190 child._parent = this; | |
191 child._spriteBox = this._spriteBox; | |
192 _childrenLastAddedOrder += 1; | |
193 child._addedOrder = _childrenLastAddedOrder; | |
194 if (_spriteBox != null) _spriteBox._eventTargets = null; | |
195 } | |
196 | |
197 /// Removes a child from this node. | |
198 /// | |
199 /// removeChild(myChildNode); | |
200 void removeChild(Node child) { | |
201 assert(child != null); | |
202 if (_children.remove(child)) { | |
203 child._parent = null; | |
204 child._spriteBox = null; | |
205 if (_spriteBox != null) _spriteBox._eventTargets = null; | |
206 } | |
207 } | |
208 | |
209 /// Removes this node from its parent node. | |
210 /// | |
211 /// removeFromParent(); | |
212 void removeFromParent() { | |
213 assert(_parent != null); | |
214 _parent.removeChild(this); | |
215 } | |
216 | |
217 /// Removes all children of this node. | |
218 /// | |
219 /// removeAllChildren(); | |
220 void removeAllChildren() { | |
221 for (Node child in _children) { | |
222 child._parent = null; | |
223 child._spriteBox = null; | |
224 } | |
225 _children = []; | |
226 _childrenNeedSorting = false; | |
227 if (_spriteBox != null) _spriteBox._eventTargets = null; | |
228 } | |
229 | |
230 void _sortChildren() { | |
231 // Sort children primarily by zPosition, secondarily by added order | |
232 if (_childrenNeedSorting) { | |
233 _children.sort((Node a, Node b) { | |
234 if (a._zPosition == b._zPosition) { | |
235 return a._addedOrder - b._addedOrder; | |
236 } | |
237 else if (a._zPosition > b._zPosition) { | |
238 return 1; | |
239 } | |
240 else { | |
241 return -1; | |
242 } | |
243 }); | |
244 _childrenNeedSorting = false; | |
245 } | |
246 } | |
247 | |
248 // Calculating the transformation matrix | |
249 | |
250 /// The transformMatrix describes the transformation from the node's parent. | |
251 /// | |
252 /// You cannot set the transformMatrix directly, instead use the position, rot
ation and scale properties. | |
253 /// | |
254 /// Matrix4 matrix = myNode.transformMatrix; | |
255 Matrix4 get transformMatrix { | |
256 if (_transformMatrix != null) { | |
257 return _transformMatrix; | |
258 } | |
259 | |
260 double cx, sx, cy, sy; | |
261 | |
262 if (_rotation == 0.0) { | |
263 cx = 1.0; | |
264 sx = 0.0; | |
265 cy = 1.0; | |
266 sy = 0.0; | |
267 } | |
268 else { | |
269 double radiansX = convertDegrees2Radians(_rotation); | |
270 double radiansY = convertDegrees2Radians(_rotation); | |
271 | |
272 cx = Math.cos(radiansX); | |
273 sx = Math.sin(radiansX); | |
274 cy = Math.cos(radiansY); | |
275 sy = Math.sin(radiansY); | |
276 } | |
277 | |
278 // Create transformation matrix for scale, position and rotation | |
279 _transformMatrix = new Matrix4(cy * _scaleX, sy * _scaleX, 0.0, 0.0, | |
280 -sx * _scaleY, cx * _scaleY, 0.0, 0.0, | |
281 0.0, 0.0, 1.0, 0.0, | |
282 _position.x, _position.y, 0.0, 1.0); | |
283 | |
284 return _transformMatrix; | |
285 } | |
286 | |
287 void _invalidateTransformMatrix() { | |
288 _transformMatrix = null; | |
289 _invalidateToBoxTransformMatrix(); | |
290 } | |
291 | |
292 void _invalidateToBoxTransformMatrix () { | |
293 _transformMatrixNodeToBox = null; | |
294 _transformMatrixBoxToNode = null; | |
295 | |
296 for (Node child in children) { | |
297 child._invalidateToBoxTransformMatrix(); | |
298 } | |
299 } | |
300 | |
301 // Transforms to other nodes | |
302 | |
303 Matrix4 _nodeToBoxMatrix() { | |
304 assert(_spriteBox != null); | |
305 if (_transformMatrixNodeToBox != null) { | |
306 return _transformMatrixNodeToBox; | |
307 } | |
308 | |
309 if (_parent == null) { | |
310 // Base case, we are at the top | |
311 assert(this == _spriteBox.rootNode); | |
312 _transformMatrixNodeToBox = new Matrix4.copy(_spriteBox.transformMatrix).m
ultiply(transformMatrix); | |
313 } | |
314 else { | |
315 _transformMatrixNodeToBox = new Matrix4.copy(_parent._nodeToBoxMatrix()).m
ultiply(transformMatrix); | |
316 } | |
317 return _transformMatrixNodeToBox; | |
318 } | |
319 | |
320 Matrix4 _boxToNodeMatrix() { | |
321 assert(_spriteBox != null); | |
322 | |
323 if (_transformMatrixBoxToNode != null) { | |
324 return _transformMatrixBoxToNode; | |
325 } | |
326 | |
327 _transformMatrixBoxToNode = new Matrix4.copy(_nodeToBoxMatrix()); | |
328 _transformMatrixBoxToNode.invert(); | |
329 | |
330 return _transformMatrixBoxToNode; | |
331 } | |
332 | |
333 /// Converts a point from the coordinate system of the [SpriteBox] to the loca
l coordinate system of the node. | |
334 /// | |
335 /// This method is particularly useful when handling pointer events and need t
he pointers position in a local | |
336 /// coordinate space. | |
337 /// | |
338 /// Point localPoint = myNode.convertPointToNodeSpace(pointInBoxCoordinate
s); | |
339 Point convertPointToNodeSpace(Point boxPoint) { | |
340 assert(boxPoint != null); | |
341 assert(_spriteBox != null); | |
342 | |
343 Vector4 v =_boxToNodeMatrix().transform(new Vector4(boxPoint.x, boxPoint.y,
0.0, 1.0)); | |
344 return new Point(v[0], v[1]); | |
345 } | |
346 | |
347 /// Converts a point from the local coordinate system of the node to the coord
inate system of the [SpriteBox]. | |
348 /// | |
349 /// Point pointInBoxCoordinates = myNode.convertPointToBoxSpace(localPoint
); | |
350 Point convertPointToBoxSpace(Point nodePoint) { | |
351 assert(nodePoint != null); | |
352 assert(_spriteBox != null); | |
353 | |
354 Vector4 v =_nodeToBoxMatrix().transform(new Vector4(nodePoint.x, nodePoint.y
, 0.0, 1.0)); | |
355 return new Point(v[0], v[1]); | |
356 } | |
357 | |
358 /// Converts a [point] from another [node]s coordinate system into the local c
oordinate system of this node. | |
359 /// | |
360 /// Point pointInNodeASpace = nodeA.convertPointFromNode(pointInNodeBSpace
, nodeB); | |
361 Point convertPointFromNode(Point point, Node node) { | |
362 assert(node != null); | |
363 assert(point != null); | |
364 assert(_spriteBox != null); | |
365 assert(_spriteBox == node._spriteBox); | |
366 | |
367 Point boxPoint = node.convertPointToBoxSpace(point); | |
368 Point localPoint = convertPointToNodeSpace(boxPoint); | |
369 | |
370 return localPoint; | |
371 } | |
372 | |
373 // Hit test | |
374 | |
375 /// Returns true if the [point] is inside the node, the [point] is in the loca
l coordinate system of the node. | |
376 /// | |
377 /// myNode.isPointInside(localPoint); | |
378 /// | |
379 /// [NodeWithSize] provides a basic bounding box check for this method, if you
require a more detailed check this | |
380 /// method can be overridden. | |
381 /// | |
382 /// bool isPointInside (Point nodePoint) { | |
383 /// double minX = -size.width * pivot.x; | |
384 /// double minY = -size.height * pivot.y; | |
385 /// double maxX = minX + size.width; | |
386 /// double maxY = minY + size.height; | |
387 /// return (nodePoint.x >= minX && nodePoint.x < maxX && | |
388 /// nodePoint.y >= minY && nodePoint.y < maxY); | |
389 /// } | |
390 bool isPointInside(Point point) { | |
391 assert(point != null); | |
392 | |
393 return false; | |
394 } | |
395 | |
396 // Rendering | |
397 | |
398 void _visit(RenderCanvas canvas) { | |
399 assert(canvas != null); | |
400 if (!visible) return; | |
401 | |
402 _prePaint(canvas); | |
403 _visitChildren(canvas); | |
404 _postPaint(canvas); | |
405 } | |
406 | |
407 void _prePaint(RenderCanvas canvas) { | |
408 canvas.save(); | |
409 | |
410 // Get the transformation matrix and apply transform | |
411 canvas.concat(transformMatrix.storage); | |
412 } | |
413 | |
414 /// Paints this node to the canvas. | |
415 /// | |
416 /// Subclasses, such as [Sprite], override this method to do the actual painti
ng of the node. To do custom | |
417 /// drawing override this method and make calls to the [canvas] object. All dr
awing is done in the node's local | |
418 /// coordinate system, relative to the node's position. If you want to make th
e drawing relative to the node's | |
419 /// bounding box's origin, override [NodeWithSize] and call the applyTransform
ForPivot method before making calls for | |
420 /// drawing. | |
421 /// | |
422 /// void paint(RenderCanvas canvas) { | |
423 /// canvas.save(); | |
424 /// applyTransformForPivot(canvas); | |
425 /// | |
426 /// // Do painting here | |
427 /// | |
428 /// canvas.restore(); | |
429 /// } | |
430 void paint(RenderCanvas canvas) { | |
431 } | |
432 | |
433 void _visitChildren(RenderCanvas canvas) { | |
434 // Sort children if needed | |
435 _sortChildren(); | |
436 | |
437 int i = 0; | |
438 | |
439 // Visit children behind this node | |
440 while (i < _children.length) { | |
441 Node child = _children[i]; | |
442 if (child.zPosition >= 0.0) break; | |
443 child._visit(canvas); | |
444 i++; | |
445 } | |
446 | |
447 // Paint this node | |
448 paint(canvas); | |
449 | |
450 // Visit children in front of this node | |
451 while (i < _children.length) { | |
452 Node child = _children[i]; | |
453 child._visit(canvas); | |
454 i++; | |
455 } | |
456 } | |
457 | |
458 void _postPaint(RenderCanvas canvas) { | |
459 canvas.restore(); | |
460 } | |
461 | |
462 // Receiving update calls | |
463 | |
464 /// Called before a frame is drawn. | |
465 /// | |
466 /// Override this method to do any updates to the node or node tree before it'
s drawn to screen. | |
467 /// | |
468 /// // Make the node rotate at a fixed speed | |
469 /// void update(double dt) { | |
470 /// rotation = rotation * 10.0 * dt; | |
471 /// } | |
472 void update(double dt) { | |
473 } | |
474 | |
475 /// Called whenever the [SpriteBox] is modified or resized, or if the device i
s rotated. | |
476 /// | |
477 /// Override this method to do any updates that may be necessary to correctly
display the node or node tree with the | |
478 /// new layout of the [SpriteBox]. | |
479 /// | |
480 /// void spriteBoxPerformedLayout() { | |
481 /// // Move some stuff around here | |
482 /// } | |
483 void spriteBoxPerformedLayout() { | |
484 } | |
485 | |
486 // Handling user interaction | |
487 | |
488 /// The node will receive user interactions, such as pointer (touch or mouse)
events. | |
489 /// | |
490 /// class MyCustomNode extends NodeWithSize { | |
491 /// userInteractionEnabled = true; | |
492 /// } | |
493 bool get userInteractionEnabled => _userInteractionEnabled; | |
494 | |
495 void set userInteractionEnabled(bool userInteractionEnabled) { | |
496 _userInteractionEnabled = userInteractionEnabled; | |
497 if (_spriteBox != null) _spriteBox._eventTargets = null; | |
498 } | |
499 | |
500 /// Handles an event, such as a pointer (touch or mouse) event. | |
501 /// | |
502 /// Override this method to handle events. The node will only receive events i
f the [userInteractionEnabled] property | |
503 /// is set to true and the [isPointInside] method returns true for the positio
n of the pointer down event (default | |
504 /// behavior provided by [NodeWithSize]). Unless [handleMultiplePointers] is s
et to true, the node will only receive | |
505 /// events for the first pointer that is down. | |
506 /// | |
507 /// Return true if the node has consumed the event, if an event is consumed it
will not be passed on to nodes behind | |
508 /// the current node. | |
509 /// | |
510 /// // MyTouchySprite gets transparent when we touch it | |
511 /// class MyTouchySprite extends Sprite { | |
512 /// | |
513 /// MyTouchySprite(Image img) : super (img) { | |
514 /// userInteractionEnabled = true; | |
515 /// } | |
516 /// | |
517 /// bool handleEvent(SpriteBoxEvent event) { | |
518 /// if (event.type == 'pointerdown) { | |
519 /// opacity = 0.5; | |
520 /// } | |
521 /// else if (event.type == 'pointerup') { | |
522 /// opacity = 1.0; | |
523 /// } | |
524 /// return true; | |
525 /// } | |
526 /// } | |
527 bool handleEvent(SpriteBoxEvent event) { | |
528 return false; | |
529 } | |
530 } | |
OLD | NEW |