OSDN Git Service

Merge pull request #304 from xoppa/misc
[mikumikustudio/libgdx-mikumikustudio.git] / gdx / src / com / badlogic / gdx / scenes / scene2d / Actor.java
1 /*******************************************************************************\r
2  * Copyright 2011 See AUTHORS file.\r
3  * \r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  * \r
8  *   http://www.apache.org/licenses/LICENSE-2.0\r
9  * \r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  ******************************************************************************/\r
16 \r
17 package com.badlogic.gdx.scenes.scene2d;\r
18 \r
19 import com.badlogic.gdx.graphics.Color;\r
20 import com.badlogic.gdx.graphics.g2d.SpriteBatch;\r
21 import com.badlogic.gdx.math.MathUtils;\r
22 import com.badlogic.gdx.math.Rectangle;\r
23 import com.badlogic.gdx.math.Vector2;\r
24 import com.badlogic.gdx.scenes.scene2d.InputEvent.Type;\r
25 import com.badlogic.gdx.scenes.scene2d.actions.Actions;\r
26 import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener;\r
27 import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;\r
28 import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;\r
29 import com.badlogic.gdx.utils.Array;\r
30 import com.badlogic.gdx.utils.DelayedRemovalArray;\r
31 import com.badlogic.gdx.utils.GdxRuntimeException;\r
32 import com.badlogic.gdx.utils.Pools;\r
33 \r
34 /** 2D scene graph node. An actor has a position, rectangular size, origin, scale, rotation, Z index, and color. The position\r
35  * corresponds to the unrotated, unscaled bottom left corner of the actor. The position is relative to the actor's parent. The\r
36  * origin is relative to the position and is used for scale and rotation.\r
37  * <p>\r
38  * An actor has a list of in-progress {@link Action actions} that are applied to the actor (over time). These are generally used to\r
39  * change the presentation of the actor (moving it, resizing it, etc). See {@link #act(float)} and {@link Action}.\r
40  * <p>\r
41  * An actor has two kinds of listeners associated with it: "capture" and regular. The listeners are notified of events the actor\r
42  * or its children receive. The capture listeners are designed to allow a parent or container actor to hide events from child\r
43  * actors. The regular listeners are designed to allow an actor to respond to events that have been delivered. See {@link #fire}\r
44  * for more details.\r
45  * <p>\r
46  * An {@link InputListener} can receive all the basic input events, and more complex listeners (like {@link ClickListener} and\r
47  * {@link ActorGestureListener}) can listen for and combine primitive events and recognize complex interactions like multi-click\r
48  * or pinch.\r
49  * \r
50  * @author mzechner\r
51  * @author Nathan Sweet */\r
52 public class Actor {\r
53         private Stage stage;\r
54         private Group parent;\r
55         private final DelayedRemovalArray<EventListener> listeners = new DelayedRemovalArray(0);\r
56         private final DelayedRemovalArray<EventListener> captureListeners = new DelayedRemovalArray(0);\r
57         private final Array<Action> actions = new Array(0);\r
58 \r
59         private String name;\r
60         private Touchable touchable = Touchable.enabled;\r
61         private boolean visible = true;\r
62         private float x, y;\r
63         private float width, height;\r
64         private float originX, originY;\r
65         private float scaleX = 1, scaleY = 1;\r
66         private float rotation;\r
67         private final Color color = new Color(1, 1, 1, 1);\r
68 \r
69         /** Draws the actor. The SpriteBatch is configured to draw in the parent's coordinate system.\r
70          * {@link SpriteBatch#draw(com.badlogic.gdx.graphics.g2d.TextureRegion, float, float, float, float, float, float, float, float, float)\r
71          * This draw method} is convenient to draw a rotated and scaled TextureRegion. {@link SpriteBatch#begin()} has already been\r
72          * called on the SpriteBatch. If {@link SpriteBatch#end()} is called to draw without the SpriteBatch then\r
73          * {@link SpriteBatch#begin()} must be called before the method returns.\r
74          * <p>\r
75          * The default implementation does nothing.\r
76          * @param parentAlpha Should be multiplied with the actor's alpha, allowing a parent's alpha to affect all children. */\r
77         public void draw (SpriteBatch batch, float parentAlpha) {\r
78         }\r
79 \r
80         /** Updates the actor based on time. Typically this is called each frame by {@link Stage#act(float)}.\r
81          * <p>\r
82          * The default implementation calls {@link Action#act(float)} on each action and removes actions that are complete.\r
83          * @param delta Time in seconds since the last frame. */\r
84         public void act (float delta) {\r
85                 for (int i = 0, n = actions.size; i < n; i++) {\r
86                         Action action = actions.get(i);\r
87                         if (action.act(delta)) {\r
88                                 actions.removeIndex(i);\r
89                                 action.setActor(null);\r
90                                 i--;\r
91                                 n--;\r
92                         }\r
93                 }\r
94         }\r
95 \r
96         /** Sets this actor as the event {@link Event#setTarget(Actor) target} and propagates the event to this actor and ancestor\r
97          * actors as necessary. If this actor is not in the stage, the stage must be set before calling this method.\r
98          * <p>\r
99          * Events are fired in 2 phases.\r
100          * <ol>\r
101          * <li>The first phase (the "capture" phase) notifies listeners on each actor starting at the root and propagating downward to\r
102          * (and including) this actor.</li>\r
103          * <li>The second phase notifies listeners on each actor starting at this actor and, if {@link Event#getBubbles()} is true,\r
104          * propagating upward to the root.</li>\r
105          * </ol>\r
106          * If the event is {@link Event#stop() stopped} at any time, it will not propagate to the next actor.\r
107          * @return true if the event was {@link Event#cancel() cancelled}. */\r
108         public boolean fire (Event event) {\r
109                 if (event.getStage() == null) event.setStage(getStage());\r
110                 event.setTarget(this);\r
111 \r
112                 // Collect ancestors so event propagation is unaffected by hierarchy changes.\r
113                 Array<Group> ancestors = Pools.obtain(Array.class);\r
114                 Group parent = getParent();\r
115                 while (parent != null) {\r
116                         ancestors.add(parent);\r
117                         parent = parent.getParent();\r
118                 }\r
119 \r
120                 try {\r
121                         // Notify all parent capture listeners, starting at the root. Ancestors may stop an event before children receive it.\r
122                         for (int i = ancestors.size - 1; i >= 0; i--) {\r
123                                 Group currentTarget = ancestors.get(i);\r
124                                 currentTarget.notify(event, true);\r
125                                 if (event.isStopped()) return event.isCancelled();\r
126                         }\r
127 \r
128                         // Notify the target capture listeners.\r
129                         notify(event, true);\r
130                         if (event.isStopped()) return event.isCancelled();\r
131 \r
132                         // Notify the target listeners.\r
133                         notify(event, false);\r
134                         if (!event.getBubbles()) return event.isCancelled();\r
135                         if (event.isStopped()) return event.isCancelled();\r
136 \r
137                         // Notify all parent listeners, starting at the target. Children may stop an event before ancestors receive it.\r
138                         for (int i = 0, n = ancestors.size; i < n; i++) {\r
139                                 ancestors.get(i).notify(event, false);\r
140                                 if (event.isStopped()) return event.isCancelled();\r
141                         }\r
142 \r
143                         return event.isCancelled();\r
144                 } finally {\r
145                         ancestors.clear();\r
146                         Pools.free(ancestors);\r
147                 }\r
148         }\r
149 \r
150         /** Notifies this actor's listeners of the event. The event is not propagated to any parents. Before notifying the listeners,\r
151          * this actor is set as the {@link Event#getListenerActor() listener actor}. The event {@link Event#setTarget(Actor) target}\r
152          * must be set before calling this method. If this actor is not in the stage, the stage must be set before calling this method.\r
153          * @param capture If true, the capture listeners will be notified instead of the regular listeners.\r
154          * @return true of the event was {@link Event#cancel() cancelled}. */\r
155         public boolean notify (Event event, boolean capture) {\r
156                 if (event.getTarget() == null) throw new IllegalArgumentException("The event target cannot be null.");\r
157 \r
158                 DelayedRemovalArray<EventListener> listeners = capture ? captureListeners : this.listeners;\r
159                 if (listeners.size == 0) return event.isCancelled();\r
160 \r
161                 event.setListenerActor(this);\r
162                 event.setCapture(capture);\r
163                 if (event.getStage() == null) event.setStage(stage);\r
164 \r
165                 listeners.begin();\r
166                 for (int i = 0, n = listeners.size; i < n; i++) {\r
167                         EventListener listener = listeners.get(i);\r
168                         if (listener.handle(event)) {\r
169                                 event.handle();\r
170                                 if (event instanceof InputEvent) {\r
171                                         InputEvent inputEvent = (InputEvent)event;\r
172                                         if (inputEvent.getType() == Type.touchDown) {\r
173                                                 event.getStage().addTouchFocus(listener, this, inputEvent.getTarget(), inputEvent.getPointer(),\r
174                                                         inputEvent.getButton());\r
175                                         }\r
176                                 }\r
177                         }\r
178                 }\r
179                 listeners.end();\r
180 \r
181                 return event.isCancelled();\r
182         }\r
183 \r
184         /** Returns the deepest actor that contains the specified point and is {@link #getTouchable() touchable} and\r
185          * {@link #isVisible() visible}, or null if no actor was hit. The point is specified in the actor's local coordinate system (0,0\r
186          * is the bottom left of the actor and width,height is the upper right).\r
187          * <p>\r
188          * This method is used to delegate touchDown, mouse, and enter/exit events. If this method returns null, those events will not\r
189          * occur on this Actor.\r
190          * <p>\r
191          * The default implementation returns this actor if the point is within this actor's bounds.\r
192          * \r
193          * @param touchable If true, the hit detection will respect the {@link #setTouchable(Touchable) touchability}.\r
194          * @see Touchable */\r
195         public Actor hit (float x, float y, boolean touchable) {\r
196                 if (touchable && this.touchable != Touchable.enabled) return null;\r
197                 return x >= 0 && x < width && y >= 0 && y < height ? this : null;\r
198         }\r
199 \r
200         /** Removes this actor from its parent, if it has a parent.\r
201          * @see Group#removeActor(Actor) */\r
202         public boolean remove () {\r
203                 if (parent != null) return parent.removeActor(this);\r
204                 return false;\r
205         }\r
206 \r
207         /** Add a listener to receive events that {@link #hit(float, float, boolean) hit} this actor. See {@link #fire(Event)}.\r
208          * \r
209          * @see InputListener\r
210          * @see ClickListener */\r
211         public boolean addListener (EventListener listener) {\r
212                 if (!listeners.contains(listener, true)) {\r
213                         listeners.add(listener);\r
214                         return true;\r
215                 }\r
216                 return false;\r
217         }\r
218 \r
219         public boolean removeListener (EventListener listener) {\r
220                 return listeners.removeValue(listener, true);\r
221         }\r
222 \r
223         public Array<EventListener> getListeners () {\r
224                 return listeners;\r
225         }\r
226 \r
227         /** Adds a listener that is only notified during the capture phase.\r
228          * @see #fire(Event) */\r
229         public boolean addCaptureListener (EventListener listener) {\r
230                 if (!captureListeners.contains(listener, true)) captureListeners.add(listener);\r
231                 return true;\r
232         }\r
233 \r
234         public boolean removeCaptureListener (EventListener listener) {\r
235                 return captureListeners.removeValue(listener, true);\r
236         }\r
237 \r
238         public Array<EventListener> getCaptureListeners () {\r
239                 return captureListeners;\r
240         }\r
241 \r
242         public void addAction (Action action) {\r
243                 action.setActor(this);\r
244                 actions.add(action);\r
245         }\r
246 \r
247         public void removeAction (Action action) {\r
248                 if (actions.removeValue(action, true)) action.setActor(null);\r
249         }\r
250 \r
251         public Array<Action> getActions () {\r
252                 return actions;\r
253         }\r
254 \r
255         /** Removes all actions on this actor. */\r
256         public void clearActions () {\r
257                 for (int i = actions.size - 1; i >= 0; i--)\r
258                         actions.get(i).setActor(null);\r
259                 actions.clear();\r
260         }\r
261 \r
262         /** Removes all listeners on this actor. */\r
263         public void clearListeners () {\r
264                 listeners.clear();\r
265                 captureListeners.clear();\r
266         }\r
267 \r
268         /** Removes all actions and listeners on this actor. */\r
269         public void clear () {\r
270                 clearActions();\r
271                 clearListeners();\r
272         }\r
273 \r
274         /** Returns the stage that this actor is currently in, or null if not in a stage. */\r
275         public Stage getStage () {\r
276                 return stage;\r
277         }\r
278 \r
279         /** Called by the framework when this actor or any parent is added to a group that is in the stage.\r
280          * @param stage May be null if the actor or any parent is no longer in a stage. */\r
281         protected void setStage (Stage stage) {\r
282                 this.stage = stage;\r
283         }\r
284 \r
285         /** Returns true if this actor is the same as or is the descendant of the specified actor. */\r
286         public boolean isDescendantOf (Actor actor) {\r
287                 if (actor == null) throw new IllegalArgumentException("actor cannot be null.");\r
288                 Actor parent = this;\r
289                 while (true) {\r
290                         if (parent == null) return false;\r
291                         if (parent == actor) return true;\r
292                         parent = parent.getParent();\r
293                 }\r
294         }\r
295 \r
296         /** Returns true if this actor is the same as or is the ascendant of the specified actor. */\r
297         public boolean isAscendantOf (Actor actor) {\r
298                 if (actor == null) throw new IllegalArgumentException("actor cannot be null.");\r
299                 while (true) {\r
300                         if (actor == null) return false;\r
301                         if (actor == this) return true;\r
302                         actor = actor.getParent();\r
303                 }\r
304         }\r
305 \r
306         /** Returns true if the actor's parent is not null. */\r
307         public boolean hasParent () {\r
308                 return parent != null;\r
309         }\r
310 \r
311         /** Returns the parent actor, or null if not in a stage. */\r
312         public Group getParent () {\r
313                 return parent;\r
314         }\r
315 \r
316         /** Called by the framework when an actor is added to or removed from a group.\r
317          * @param parent May be null if the actor has been removed from the parent. */\r
318         protected void setParent (Group parent) {\r
319                 this.parent = parent;\r
320         }\r
321 \r
322         public Touchable getTouchable () {\r
323                 return touchable;\r
324         }\r
325 \r
326         /** Determines how touch events are distributed to this actor. Default is {@link Touchable#enabled}. */\r
327         public void setTouchable (Touchable touchable) {\r
328                 this.touchable = touchable;\r
329         }\r
330 \r
331         public boolean isVisible () {\r
332                 return visible;\r
333         }\r
334 \r
335         /** If false, the actor will not be drawn and will not receive touch events. Default is true. */\r
336         public void setVisible (boolean visible) {\r
337                 this.visible = visible;\r
338         }\r
339 \r
340         public float getX () {\r
341                 return x;\r
342         }\r
343 \r
344         public void setX (float x) {\r
345                 this.x = x;\r
346         }\r
347 \r
348         public float getY () {\r
349                 return y;\r
350         }\r
351 \r
352         public void setY (float y) {\r
353                 this.y = y;\r
354         }\r
355 \r
356         /** Sets the x and y. */\r
357         public void setPosition (float x, float y) {\r
358                 setX(x);\r
359                 setY(y);\r
360         }\r
361 \r
362         public void translate (float x, float y) {\r
363                 setX(this.x + x);\r
364                 setY(this.y + y);\r
365         }\r
366 \r
367         public float getWidth () {\r
368                 return width;\r
369         }\r
370 \r
371         public void setWidth (float width) {\r
372                 this.width = width;\r
373         }\r
374 \r
375         public float getHeight () {\r
376                 return height;\r
377         }\r
378 \r
379         public void setHeight (float height) {\r
380                 this.height = height;\r
381         }\r
382 \r
383         /** Returns y plus height. */\r
384         public float getTop () {\r
385                 return getY() + getHeight();\r
386         }\r
387 \r
388         /** Returns x plus width. */\r
389         public float getRight () {\r
390                 return getX() + getWidth();\r
391         }\r
392 \r
393         /** Sets the width and height. */\r
394         public void setSize (float width, float height) {\r
395                 setWidth(width);\r
396                 setHeight(height);\r
397         }\r
398 \r
399         /** Adds the specified size to the current size. */\r
400         public void size (float size) {\r
401                 setWidth(width + size);\r
402                 setHeight(height + size);\r
403         }\r
404 \r
405         /** Adds the specified size to the current size. */\r
406         public void size (float width, float height) {\r
407                 setWidth(this.width + width);\r
408                 setHeight(this.height + height);\r
409         }\r
410 \r
411         /** Set bounds the x, y, width, and height. */\r
412         public void setBounds (float x, float y, float width, float height) {\r
413                 setX(x);\r
414                 setY(y);\r
415                 setWidth(width);\r
416                 setHeight(height);\r
417         }\r
418 \r
419         public float getOriginX () {\r
420                 return originX;\r
421         }\r
422 \r
423         public void setOriginX (float originX) {\r
424                 this.originX = originX;\r
425         }\r
426 \r
427         public float getOriginY () {\r
428                 return originY;\r
429         }\r
430 \r
431         public void setOriginY (float originY) {\r
432                 this.originY = originY;\r
433         }\r
434 \r
435         /** Sets the originx and originy. */\r
436         public void setOrigin (float originX, float originY) {\r
437                 setOriginX(originX);\r
438                 setOriginY(originY);\r
439         }\r
440 \r
441         public float getScaleX () {\r
442                 return scaleX;\r
443         }\r
444 \r
445         public void setScaleX (float scaleX) {\r
446                 this.scaleX = scaleX;\r
447         }\r
448 \r
449         public float getScaleY () {\r
450                 return scaleY;\r
451         }\r
452 \r
453         public void setScaleY (float scaleY) {\r
454                 this.scaleY = scaleY;\r
455         }\r
456 \r
457         /** Sets the scalex and scaley. */\r
458         public void setScale (float scale) {\r
459                 setScaleX(scale);\r
460                 setScaleY(scale);\r
461         }\r
462 \r
463         /** Sets the scalex and scaley. */\r
464         public void setScale (float scaleX, float scaleY) {\r
465                 setScaleX(scaleX);\r
466                 setScaleY(scaleY);\r
467         }\r
468 \r
469         /** Adds the specified scale to the current scale. */\r
470         public void scale (float scale) {\r
471                 setScaleX(scaleX + scale);\r
472                 setScaleY(scaleY + scale);\r
473         }\r
474 \r
475         /** Adds the specified scale to the current scale. */\r
476         public void scale (float scaleX, float scaleY) {\r
477                 setScaleX(this.scaleX + scaleX);\r
478                 setScaleY(this.scaleY + scaleY);\r
479         }\r
480 \r
481         public float getRotation () {\r
482                 return rotation;\r
483         }\r
484 \r
485         public void setRotation (float degrees) {\r
486                 this.rotation = degrees;\r
487         }\r
488 \r
489         /** Adds the specified rotation to the current rotation. */\r
490         public void rotate (float amountInDegrees) {\r
491                 setRotation(rotation + amountInDegrees);\r
492         }\r
493 \r
494         public void setColor (Color color) {\r
495                 this.color.set(color);\r
496         }\r
497 \r
498         public void setColor (float r, float g, float b, float a) {\r
499                 color.set(r, g, b, a);\r
500         }\r
501 \r
502         /** Returns the color the actor will be tinted when drawn. The returned instance can be modified to change the color. */\r
503         public Color getColor () {\r
504                 return color;\r
505         }\r
506 \r
507         public String getName () {\r
508                 return name;\r
509         }\r
510 \r
511         /** Sets a name for easier identification of the actor in application code.\r
512          * @see Group#findActor(String) */\r
513         public void setName (String name) {\r
514                 this.name = name;\r
515         }\r
516 \r
517         /** Changes the z-order for this actor so it is in front of all siblings. */\r
518         public void toFront () {\r
519                 setZIndex(Integer.MAX_VALUE);\r
520         }\r
521 \r
522         /** Changes the z-order for this actor so it is in back of all siblings. */\r
523         public void toBack () {\r
524                 setZIndex(0);\r
525         }\r
526 \r
527         /** Sets the z-index of this actor. The z-index is the index into the parent's {@link Group#getChildren() children}, where a\r
528          * lower index is below a higher index. Setting a z-index higher than the number of children will move the child to the front.\r
529          * Setting a z-index less than zero is invalid. */\r
530         public void setZIndex (int index) {\r
531                 if (index < 0) throw new IllegalArgumentException("ZIndex cannot be < 0.");\r
532                 Group parent = getParent();\r
533                 if (parent == null) return;\r
534                 Array<Actor> children = parent.getChildren();\r
535                 if (children.size == 1) return;\r
536                 if (!children.removeValue(this, true)) return;\r
537                 if (index >= children.size)\r
538                         children.add(this);\r
539                 else\r
540                         children.insert(index, this);\r
541         }\r
542 \r
543         /** Returns the z-index of this actor.\r
544          * @see #setZIndex(int) */\r
545         public int getZIndex () {\r
546                 Group parent = getParent();\r
547                 if (parent == null) return -1;\r
548                 return parent.getChildren().indexOf(this, true);\r
549         }\r
550 \r
551         /** Calls {@link #clipBegin(float, float, float, float)} to clip this actor's bounds. */\r
552         public boolean clipBegin () {\r
553                 return clipBegin(getX(), getY(), getWidth(), getHeight());\r
554         }\r
555 \r
556         /** Clips the specified screen aligned rectangle, specified relative to the transform matrix of the stage's SpriteBatch. The\r
557          * transform matrix and the stage's camera must not have rotational components. Calling this method must be followed by a call\r
558          * to {@link #clipEnd()} if true is returned.\r
559          * @return false if the clipping area is zero and no drawing should occur.\r
560          * @see ScissorStack */\r
561         public boolean clipBegin (float x, float y, float width, float height) {\r
562                 Rectangle tableBounds = Rectangle.tmp;\r
563                 tableBounds.x = x;\r
564                 tableBounds.y = y;\r
565                 tableBounds.width = width;\r
566                 tableBounds.height = height;\r
567                 Stage stage = getStage();\r
568                 Rectangle scissorBounds = Pools.obtain(Rectangle.class);\r
569                 ScissorStack.calculateScissors(stage.getCamera(), stage.getSpriteBatch().getTransformMatrix(), tableBounds, scissorBounds);\r
570                 if (ScissorStack.pushScissors(scissorBounds)) return true;\r
571                 Pools.free(scissorBounds);\r
572                 return false;\r
573         }\r
574 \r
575         /** Ends clipping begun by {@link #clipBegin(float, float, float, float)}. */\r
576         public void clipEnd () {\r
577                 Pools.free(ScissorStack.popScissors());\r
578         }\r
579 \r
580         /** Transforms the specified point in screen coordinates to the actor's local coordinate system. */\r
581         public Vector2 screenToLocalCoordinates (Vector2 screenCoords) {\r
582                 Stage stage = getStage();\r
583                 if (stage == null) return screenCoords;\r
584                 return stageToLocalCoordinates(stage.screenToStageCoordinates(screenCoords));\r
585         }\r
586 \r
587         /** Transforms the specified point in the stage's coordinates to the actor's local coordinate system. */\r
588         public Vector2 stageToLocalCoordinates (Vector2 stageCoords) {\r
589                 if (parent == null) return stageCoords;\r
590                 parent.stageToLocalCoordinates(stageCoords);\r
591                 parentToLocalCoordinates(stageCoords);\r
592                 return stageCoords;\r
593         }\r
594 \r
595         /** Transforms the specified point in the actor's coordinates to be in the stage's coordinates. Note this method will ONLY work\r
596          * for screen aligned, unrotated, unscaled actors! */\r
597         public Vector2 localToStageCoordinates (Vector2 localCoords) {\r
598                 Actor actor = this;\r
599                 while (actor != null) {\r
600                         if (actor.getRotation() != 0 || actor.getScaleX() != 1 || actor.getScaleY() != 1)\r
601                                 throw new GdxRuntimeException("Only unrotated and unscaled actors may use this method.");\r
602                         localCoords.x += actor.getX();\r
603                         localCoords.y += actor.getY();\r
604                         actor = actor.getParent();\r
605                 }\r
606                 return localCoords;\r
607         }\r
608 \r
609         /** Transforms the specified point in the actor's coordinates to be in the parent's coordinates. Note this method will ONLY work\r
610          * for screen aligned, unrotated, unscaled actors! */\r
611         public Vector2 localToParentCoordinates (Vector2 localCoords) {\r
612                 if (getRotation() != 0 || getScaleX() != 1 || getScaleY() != 1)\r
613                         throw new GdxRuntimeException("Only unrotated and unscaled actors may use this method.");\r
614                 localCoords.x += getX();\r
615                 localCoords.y += getY();\r
616                 return localCoords;\r
617         }\r
618 \r
619         /** Converts coordinates for this actor to those of a parent actor. The ascendant does not need to be a direct parent. */\r
620         public Vector2 localToAscendantCoordinates (Actor ascendant, Vector2 localCoords) {\r
621                 Actor actor = this;\r
622                 while (actor.getParent() != null) {\r
623                         actor.localToParentCoordinates(localCoords);\r
624                         actor = actor.getParent();\r
625                         if (actor == ascendant) break;\r
626                 }\r
627                 return localCoords;\r
628         }\r
629 \r
630         /** Converts the coordinates given in the parent's coordinate system to this actor's coordinate system. */\r
631         public Vector2 parentToLocalCoordinates (Vector2 parentCoords) {\r
632                 final float rotation = getRotation();\r
633                 final float scaleX = getScaleX();\r
634                 final float scaleY = getScaleY();\r
635                 final float childX = getX();\r
636                 final float childY = getY();\r
637 \r
638                 if (rotation == 0) {\r
639                         if (scaleX == 1 && scaleY == 1) {\r
640                                 parentCoords.x -= childX;\r
641                                 parentCoords.y -= childY;\r
642                         } else {\r
643                                 final float originX = getOriginX();\r
644                                 final float originY = getOriginY();\r
645                                 if (originX == 0 && originY == 0) {\r
646                                         parentCoords.x = (parentCoords.x - childX) / scaleX;\r
647                                         parentCoords.y = (parentCoords.y - childY) / scaleY;\r
648                                 } else {\r
649                                         parentCoords.x = (parentCoords.x - childX - originX) / scaleX + originX;\r
650                                         parentCoords.y = (parentCoords.y - childY - originY) / scaleY + originY;\r
651                                 }\r
652                         }\r
653                 } else {\r
654                         final float cos = (float)Math.cos(rotation * MathUtils.degreesToRadians);\r
655                         final float sin = (float)Math.sin(rotation * MathUtils.degreesToRadians);\r
656 \r
657                         final float originX = getOriginX();\r
658                         final float originY = getOriginY();\r
659 \r
660                         if (scaleX == 1 && scaleY == 1) {\r
661                                 if (originX == 0 && originY == 0) {\r
662                                         float tox = parentCoords.x - childX;\r
663                                         float toy = parentCoords.y - childY;\r
664 \r
665                                         parentCoords.x = tox * cos + toy * sin;\r
666                                         parentCoords.y = tox * -sin + toy * cos;\r
667                                 } else {\r
668                                         final float worldOriginX = childX + originX;\r
669                                         final float worldOriginY = childY + originY;\r
670                                         final float fx = -originX;\r
671                                         final float fy = -originY;\r
672 \r
673                                         final float x1 = cos * fx - sin * fy + worldOriginX;\r
674                                         final float y1 = sin * fx + cos * fy + worldOriginY;\r
675 \r
676                                         final float tox = parentCoords.x - x1;\r
677                                         final float toy = parentCoords.y - y1;\r
678 \r
679                                         parentCoords.x = tox * cos + toy * sin;\r
680                                         parentCoords.y = tox * -sin + toy * cos;\r
681                                 }\r
682                         } else {\r
683                                 if (originX == 0 && originY == 0) {\r
684                                         final float tox = parentCoords.x - childX;\r
685                                         final float toy = parentCoords.y - childY;\r
686 \r
687                                         parentCoords.x = (tox * cos + toy * sin) / scaleX;\r
688                                         parentCoords.y = (tox * -sin + toy * cos) / scaleY;\r
689                                 } else {\r
690                                         final float worldOriginX = childX + originX;\r
691                                         final float worldOriginY = childY + originY;\r
692                                         final float fx = -originX * scaleX;\r
693                                         final float fy = -originY * scaleY;\r
694 \r
695                                         final float x1 = cos * fx - sin * fy + worldOriginX;\r
696                                         final float y1 = sin * fx + cos * fy + worldOriginY;\r
697 \r
698                                         final float tox = parentCoords.x - x1;\r
699                                         final float toy = parentCoords.y - y1;\r
700 \r
701                                         parentCoords.x = (tox * cos + toy * sin) / scaleX;\r
702                                         parentCoords.y = (tox * -sin + toy * cos) / scaleY;\r
703                                 }\r
704                         }\r
705                 }\r
706                 return parentCoords;\r
707         }\r
708 \r
709         public String toString () {\r
710                 String name = this.name;\r
711                 if (name == null) {\r
712                         name = getClass().getName();\r
713                         int dotIndex = name.lastIndexOf('.');\r
714                         if (dotIndex != -1) name = name.substring(dotIndex + 1);\r
715                 }\r
716                 return name;\r
717         }\r
718 }\r