OSDN Git Service

12354f9c67c101609c2b74a71b225801defab217
[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         float x, y;\r
63         float width, height;\r
64         float originX, originY;\r
65         float scaleX = 1, scaleY = 1;\r
66         float rotation;\r
67         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; i < actions.size; i++) {\r
86                         Action action = actions.get(i);\r
87                         if (action.act(delta) && i < actions.size) {\r
88                                 actions.removeIndex(i);\r
89                                 action.setActor(null);\r
90                                 i--;\r
91                         }\r
92                 }\r
93         }\r
94 \r
95         /** Sets this actor as the event {@link Event#setTarget(Actor) target} and propagates the event to this actor and ancestor\r
96          * actors as necessary. If this actor is not in the stage, the stage must be set before calling this method.\r
97          * <p>\r
98          * Events are fired in 2 phases.\r
99          * <ol>\r
100          * <li>The first phase (the "capture" phase) notifies listeners on each actor starting at the root and propagating downward to\r
101          * (and including) this actor.</li>\r
102          * <li>The second phase notifies listeners on each actor starting at this actor and, if {@link Event#getBubbles()} is true,\r
103          * propagating upward to the root.</li>\r
104          * </ol>\r
105          * If the event is {@link Event#stop() stopped} at any time, it will not propagate to the next actor.\r
106          * @return true if the event was {@link Event#cancel() cancelled}. */\r
107         public boolean fire (Event event) {\r
108                 if (event.getStage() == null) event.setStage(getStage());\r
109                 event.setTarget(this);\r
110 \r
111                 // Collect ancestors so event propagation is unaffected by hierarchy changes.\r
112                 Array<Group> ancestors = Pools.obtain(Array.class);\r
113                 Group parent = getParent();\r
114                 while (parent != null) {\r
115                         ancestors.add(parent);\r
116                         parent = parent.getParent();\r
117                 }\r
118 \r
119                 try {\r
120                         // Notify all parent capture listeners, starting at the root. Ancestors may stop an event before children receive it.\r
121                         for (int i = ancestors.size - 1; i >= 0; i--) {\r
122                                 Group currentTarget = ancestors.get(i);\r
123                                 currentTarget.notify(event, true);\r
124                                 if (event.isStopped()) return event.isCancelled();\r
125                         }\r
126 \r
127                         // Notify the target capture listeners.\r
128                         notify(event, true);\r
129                         if (event.isStopped()) return event.isCancelled();\r
130 \r
131                         // Notify the target listeners.\r
132                         notify(event, false);\r
133                         if (!event.getBubbles()) return event.isCancelled();\r
134                         if (event.isStopped()) return event.isCancelled();\r
135 \r
136                         // Notify all parent listeners, starting at the target. Children may stop an event before ancestors receive it.\r
137                         for (int i = 0, n = ancestors.size; i < n; i++) {\r
138                                 ancestors.get(i).notify(event, false);\r
139                                 if (event.isStopped()) return event.isCancelled();\r
140                         }\r
141 \r
142                         return event.isCancelled();\r
143                 } finally {\r
144                         ancestors.clear();\r
145                         Pools.free(ancestors);\r
146                 }\r
147         }\r
148 \r
149         /** Notifies this actor's listeners of the event. The event is not propagated to any parents. Before notifying the listeners,\r
150          * this actor is set as the {@link Event#getListenerActor() listener actor}. The event {@link Event#setTarget(Actor) target}\r
151          * 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
152          * @param capture If true, the capture listeners will be notified instead of the regular listeners.\r
153          * @return true of the event was {@link Event#cancel() cancelled}. */\r
154         public boolean notify (Event event, boolean capture) {\r
155                 if (event.getTarget() == null) throw new IllegalArgumentException("The event target cannot be null.");\r
156 \r
157                 DelayedRemovalArray<EventListener> listeners = capture ? captureListeners : this.listeners;\r
158                 if (listeners.size == 0) return event.isCancelled();\r
159 \r
160                 event.setListenerActor(this);\r
161                 event.setCapture(capture);\r
162                 if (event.getStage() == null) event.setStage(stage);\r
163 \r
164                 listeners.begin();\r
165                 for (int i = 0, n = listeners.size; i < n; i++) {\r
166                         EventListener listener = listeners.get(i);\r
167                         if (listener.handle(event)) {\r
168                                 event.handle();\r
169                                 if (event instanceof InputEvent) {\r
170                                         InputEvent inputEvent = (InputEvent)event;\r
171                                         if (inputEvent.getType() == Type.touchDown) {\r
172                                                 event.getStage().addTouchFocus(listener, this, inputEvent.getTarget(), inputEvent.getPointer(),\r
173                                                         inputEvent.getButton());\r
174                                         }\r
175                                 }\r
176                         }\r
177                 }\r
178                 listeners.end();\r
179 \r
180                 return event.isCancelled();\r
181         }\r
182 \r
183         /** Returns the deepest actor that contains the specified point and is {@link #getTouchable() touchable} and\r
184          * {@link #isVisible() visible}, or null if no actor was hit. The point is specified in the actor's local coordinate system (0,0\r
185          * is the bottom left of the actor and width,height is the upper right).\r
186          * <p>\r
187          * This method is used to delegate touchDown, mouse, and enter/exit events. If this method returns null, those events will not\r
188          * occur on this Actor.\r
189          * <p>\r
190          * The default implementation returns this actor if the point is within this actor's bounds.\r
191          * \r
192          * @param touchable If true, the hit detection will respect the {@link #setTouchable(Touchable) touchability}.\r
193          * @see Touchable */\r
194         public Actor hit (float x, float y, boolean touchable) {\r
195                 if (touchable && this.touchable != Touchable.enabled) return null;\r
196                 return x >= 0 && x < width && y >= 0 && y < height ? this : null;\r
197         }\r
198 \r
199         /** Removes this actor from its parent, if it has a parent.\r
200          * @see Group#removeActor(Actor) */\r
201         public boolean remove () {\r
202                 if (parent != null) return parent.removeActor(this);\r
203                 return false;\r
204         }\r
205 \r
206         /** Add a listener to receive events that {@link #hit(float, float, boolean) hit} this actor. See {@link #fire(Event)}.\r
207          * \r
208          * @see InputListener\r
209          * @see ClickListener */\r
210         public boolean addListener (EventListener listener) {\r
211                 if (!listeners.contains(listener, true)) {\r
212                         listeners.add(listener);\r
213                         return true;\r
214                 }\r
215                 return false;\r
216         }\r
217 \r
218         public boolean removeListener (EventListener listener) {\r
219                 return listeners.removeValue(listener, true);\r
220         }\r
221 \r
222         public Array<EventListener> getListeners () {\r
223                 return listeners;\r
224         }\r
225 \r
226         /** Adds a listener that is only notified during the capture phase.\r
227          * @see #fire(Event) */\r
228         public boolean addCaptureListener (EventListener listener) {\r
229                 if (!captureListeners.contains(listener, true)) captureListeners.add(listener);\r
230                 return true;\r
231         }\r
232 \r
233         public boolean removeCaptureListener (EventListener listener) {\r
234                 return captureListeners.removeValue(listener, true);\r
235         }\r
236 \r
237         public Array<EventListener> getCaptureListeners () {\r
238                 return captureListeners;\r
239         }\r
240 \r
241         public void addAction (Action action) {\r
242                 action.setActor(this);\r
243                 actions.add(action);\r
244         }\r
245 \r
246         public void removeAction (Action action) {\r
247                 if (actions.removeValue(action, true)) action.setActor(null);\r
248         }\r
249 \r
250         public Array<Action> getActions () {\r
251                 return actions;\r
252         }\r
253 \r
254         /** Removes all actions on this actor. */\r
255         public void clearActions () {\r
256                 for (int i = actions.size - 1; i >= 0; i--)\r
257                         actions.get(i).setActor(null);\r
258                 actions.clear();\r
259         }\r
260 \r
261         /** Removes all listeners on this actor. */\r
262         public void clearListeners () {\r
263                 listeners.clear();\r
264                 captureListeners.clear();\r
265         }\r
266 \r
267         /** Removes all actions and listeners on this actor. */\r
268         public void clear () {\r
269                 clearActions();\r
270                 clearListeners();\r
271         }\r
272 \r
273         /** Returns the stage that this actor is currently in, or null if not in a stage. */\r
274         public Stage getStage () {\r
275                 return stage;\r
276         }\r
277 \r
278         /** Called by the framework when this actor or any parent is added to a group that is in the stage.\r
279          * @param stage May be null if the actor or any parent is no longer in a stage. */\r
280         protected void setStage (Stage stage) {\r
281                 this.stage = stage;\r
282         }\r
283 \r
284         /** Returns true if this actor is the same as or is the descendant of the specified actor. */\r
285         public boolean isDescendantOf (Actor actor) {\r
286                 if (actor == null) throw new IllegalArgumentException("actor cannot be null.");\r
287                 Actor parent = this;\r
288                 while (true) {\r
289                         if (parent == null) return false;\r
290                         if (parent == actor) return true;\r
291                         parent = parent.parent;\r
292                 }\r
293         }\r
294 \r
295         /** Returns true if this actor is the same as or is the ascendant of the specified actor. */\r
296         public boolean isAscendantOf (Actor actor) {\r
297                 if (actor == null) throw new IllegalArgumentException("actor cannot be null.");\r
298                 while (true) {\r
299                         if (actor == null) return false;\r
300                         if (actor == this) return true;\r
301                         actor = actor.parent;\r
302                 }\r
303         }\r
304 \r
305         /** Returns true if the actor's parent is not null. */\r
306         public boolean hasParent () {\r
307                 return parent != null;\r
308         }\r
309 \r
310         /** Returns the parent actor, or null if not in a stage. */\r
311         public Group getParent () {\r
312                 return parent;\r
313         }\r
314 \r
315         /** Called by the framework when an actor is added to or removed from a group.\r
316          * @param parent May be null if the actor has been removed from the parent. */\r
317         protected void setParent (Group parent) {\r
318                 this.parent = parent;\r
319         }\r
320 \r
321         public Touchable getTouchable () {\r
322                 return touchable;\r
323         }\r
324 \r
325         /** Determines how touch events are distributed to this actor. Default is {@link Touchable#enabled}. */\r
326         public void setTouchable (Touchable touchable) {\r
327                 this.touchable = touchable;\r
328         }\r
329 \r
330         public boolean isVisible () {\r
331                 return visible;\r
332         }\r
333 \r
334         /** If false, the actor will not be drawn and will not receive touch events. Default is true. */\r
335         public void setVisible (boolean visible) {\r
336                 this.visible = visible;\r
337         }\r
338 \r
339         public float getX () {\r
340                 return x;\r
341         }\r
342 \r
343         public void setX (float x) {\r
344                 this.x = x;\r
345         }\r
346 \r
347         public float getY () {\r
348                 return y;\r
349         }\r
350 \r
351         public void setY (float y) {\r
352                 this.y = y;\r
353         }\r
354 \r
355         /** Sets the x and y. */\r
356         public void setPosition (float x, float y) {\r
357                 this.x = x;\r
358                 this.y = y;\r
359         }\r
360 \r
361         public void translate (float x, float y) {\r
362                 this.x += x;\r
363                 this.y += y;\r
364         }\r
365 \r
366         public float getWidth () {\r
367                 return width;\r
368         }\r
369 \r
370         public void setWidth (float width) {\r
371                 this.width = width;\r
372         }\r
373 \r
374         public float getHeight () {\r
375                 return height;\r
376         }\r
377 \r
378         public void setHeight (float height) {\r
379                 this.height = height;\r
380         }\r
381 \r
382         /** Returns y plus height. */\r
383         public float getTop () {\r
384                 return y + height;\r
385         }\r
386 \r
387         /** Returns x plus width. */\r
388         public float getRight () {\r
389                 return x + width;\r
390         }\r
391 \r
392         /** Sets the width and height. */\r
393         public void setSize (float width, float height) {\r
394                 this.width = width;\r
395                 this.height = height;\r
396         }\r
397 \r
398         /** Adds the specified size to the current size. */\r
399         public void size (float size) {\r
400                 width += size;\r
401                 height += size;\r
402         }\r
403 \r
404         /** Adds the specified size to the current size. */\r
405         public void size (float width, float height) {\r
406                 this.width += width;\r
407                 this.height += height;\r
408         }\r
409 \r
410         /** Set bounds the x, y, width, and height. */\r
411         public void setBounds (float x, float y, float width, float height) {\r
412                 this.x = x;\r
413                 this.y = y;\r
414                 this.width = width;\r
415                 this.height = height;\r
416         }\r
417 \r
418         public float getOriginX () {\r
419                 return originX;\r
420         }\r
421 \r
422         public void setOriginX (float originX) {\r
423                 this.originX = originX;\r
424         }\r
425 \r
426         public float getOriginY () {\r
427                 return originY;\r
428         }\r
429 \r
430         public void setOriginY (float originY) {\r
431                 this.originY = originY;\r
432         }\r
433 \r
434         /** Sets the originx and originy. */\r
435         public void setOrigin (float originX, float originY) {\r
436                 this.originX = originX;\r
437                 this.originY = originY;\r
438         }\r
439 \r
440         public float getScaleX () {\r
441                 return scaleX;\r
442         }\r
443 \r
444         public void setScaleX (float scaleX) {\r
445                 this.scaleX = scaleX;\r
446         }\r
447 \r
448         public float getScaleY () {\r
449                 return scaleY;\r
450         }\r
451 \r
452         public void setScaleY (float scaleY) {\r
453                 this.scaleY = scaleY;\r
454         }\r
455 \r
456         /** Sets the scalex and scaley. */\r
457         public void setScale (float scale) {\r
458                 this.scaleX = scale;\r
459                 this.scaleY = scale;\r
460         }\r
461 \r
462         /** Sets the scalex and scaley. */\r
463         public void setScale (float scaleX, float scaleY) {\r
464                 this.scaleX = scaleX;\r
465                 this.scaleY = scaleY;\r
466         }\r
467 \r
468         /** Adds the specified scale to the current scale. */\r
469         public void scale (float scale) {\r
470                 scaleX += scale;\r
471                 scaleY += scale;\r
472         }\r
473 \r
474         /** Adds the specified scale to the current scale. */\r
475         public void scale (float scaleX, float scaleY) {\r
476                 this.scaleX += scaleX;\r
477                 this.scaleY += scaleY;\r
478         }\r
479 \r
480         public float getRotation () {\r
481                 return rotation;\r
482         }\r
483 \r
484         public void setRotation (float degrees) {\r
485                 this.rotation = degrees;\r
486         }\r
487 \r
488         /** Adds the specified rotation to the current rotation. */\r
489         public void rotate (float amountInDegrees) {\r
490                 rotation += amountInDegrees;\r
491         }\r
492 \r
493         public void setColor (Color color) {\r
494                 this.color.set(color);\r
495         }\r
496 \r
497         public void setColor (float r, float g, float b, float a) {\r
498                 color.set(r, g, b, a);\r
499         }\r
500 \r
501         /** Returns the color the actor will be tinted when drawn. The returned instance can be modified to change the color. */\r
502         public Color getColor () {\r
503                 return color;\r
504         }\r
505 \r
506         public String getName () {\r
507                 return name;\r
508         }\r
509 \r
510         /** Sets a name for easier identification of the actor in application code.\r
511          * @see Group#findActor(String) */\r
512         public void setName (String name) {\r
513                 this.name = name;\r
514         }\r
515 \r
516         /** Changes the z-order for this actor so it is in front of all siblings. */\r
517         public void toFront () {\r
518                 setZIndex(Integer.MAX_VALUE);\r
519         }\r
520 \r
521         /** Changes the z-order for this actor so it is in back of all siblings. */\r
522         public void toBack () {\r
523                 setZIndex(0);\r
524         }\r
525 \r
526         /** Sets the z-index of this actor. The z-index is the index into the parent's {@link Group#getChildren() children}, where a\r
527          * 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
528          * Setting a z-index less than zero is invalid. */\r
529         public void setZIndex (int index) {\r
530                 if (index < 0) throw new IllegalArgumentException("ZIndex cannot be < 0.");\r
531                 Group parent = this.parent;\r
532                 if (parent == null) return;\r
533                 Array<Actor> children = parent.getChildren();\r
534                 if (children.size == 1) return;\r
535                 if (!children.removeValue(this, true)) return;\r
536                 if (index >= children.size)\r
537                         children.add(this);\r
538                 else\r
539                         children.insert(index, this);\r
540         }\r
541 \r
542         /** Returns the z-index of this actor.\r
543          * @see #setZIndex(int) */\r
544         public int getZIndex () {\r
545                 Group parent = this.parent;\r
546                 if (parent == null) return -1;\r
547                 return parent.getChildren().indexOf(this, true);\r
548         }\r
549 \r
550         /** Calls {@link #clipBegin(float, float, float, float)} to clip this actor's bounds. */\r
551         public boolean clipBegin () {\r
552                 return clipBegin(x, y, width, height);\r
553         }\r
554 \r
555         /** Clips the specified screen aligned rectangle, specified relative to the transform matrix of the stage's SpriteBatch. The\r
556          * transform matrix and the stage's camera must not have rotational components. Calling this method must be followed by a call\r
557          * to {@link #clipEnd()} if true is returned.\r
558          * @return false if the clipping area is zero and no drawing should occur.\r
559          * @see ScissorStack */\r
560         public boolean clipBegin (float x, float y, float width, float height) {\r
561                 Rectangle tableBounds = Rectangle.tmp;\r
562                 tableBounds.x = x;\r
563                 tableBounds.y = y;\r
564                 tableBounds.width = width;\r
565                 tableBounds.height = height;\r
566                 Stage stage = this.stage;\r
567                 Rectangle scissorBounds = Pools.obtain(Rectangle.class);\r
568                 ScissorStack.calculateScissors(stage.getCamera(), stage.getSpriteBatch().getTransformMatrix(), tableBounds, scissorBounds);\r
569                 if (ScissorStack.pushScissors(scissorBounds)) return true;\r
570                 Pools.free(scissorBounds);\r
571                 return false;\r
572         }\r
573 \r
574         /** Ends clipping begun by {@link #clipBegin(float, float, float, float)}. */\r
575         public void clipEnd () {\r
576                 Pools.free(ScissorStack.popScissors());\r
577         }\r
578 \r
579         /** Transforms the specified point in screen coordinates to the actor's local coordinate system. */\r
580         public Vector2 screenToLocalCoordinates (Vector2 screenCoords) {\r
581                 Stage stage = this.stage;\r
582                 if (stage == null) return screenCoords;\r
583                 return stageToLocalCoordinates(stage.screenToStageCoordinates(screenCoords));\r
584         }\r
585 \r
586         /** Transforms the specified point in the stage's coordinates to the actor's local coordinate system. */\r
587         public Vector2 stageToLocalCoordinates (Vector2 stageCoords) {\r
588                 if (parent == null) return stageCoords;\r
589                 parent.stageToLocalCoordinates(stageCoords);\r
590                 parentToLocalCoordinates(stageCoords);\r
591                 return stageCoords;\r
592         }\r
593 \r
594         /** Transforms the specified point in the actor's coordinates to be in the stage's coordinates.\r
595          * @see Stage#toScreenCoordinates(Vector2, com.badlogic.gdx.math.Matrix4) */\r
596         public Vector2 localToStageCoordinates (Vector2 localCoords) {\r
597                 return localToAscendantCoordinates(null, localCoords);\r
598         }\r
599 \r
600         /** Transforms the specified point in the actor's coordinates to be in the parent's coordinates. */\r
601         public Vector2 localToParentCoordinates (Vector2 localCoords) {\r
602                 final float rotation = -this.rotation;\r
603                 final float scaleX = this.scaleX;\r
604                 final float scaleY = this.scaleY;\r
605                 final float x = this.x;\r
606                 final float y = this.y;\r
607                 if (rotation == 0) {\r
608                         if (scaleX == 1 && scaleY == 1) {\r
609                                 localCoords.x += x;\r
610                                 localCoords.y += y;\r
611                         } else {\r
612                                 final float originX = this.originX;\r
613                                 final float originY = this.originY;\r
614                                 localCoords.x = (localCoords.x - originX) * scaleX + originX + x;\r
615                                 localCoords.y = (localCoords.y - originY) * scaleY + originY + y;\r
616                         }\r
617                 } else {\r
618                         final float cos = (float)Math.cos(rotation * MathUtils.degreesToRadians);\r
619                         final float sin = (float)Math.sin(rotation * MathUtils.degreesToRadians);\r
620                         final float originX = this.originX;\r
621                         final float originY = this.originY;\r
622                         final float tox = localCoords.x - originX;\r
623                         final float toy = localCoords.y - originY;\r
624                         localCoords.x = (tox * cos + toy * sin) * scaleX + originX + x;\r
625                         localCoords.y = (tox * -sin + toy * cos) * scaleY + originY + y;\r
626                 }\r
627                 return localCoords;\r
628         }\r
629 \r
630         /** Converts coordinates for this actor to those of a parent actor. The ascendant does not need to be a direct parent. */\r
631         public Vector2 localToAscendantCoordinates (Actor ascendant, Vector2 localCoords) {\r
632                 Actor actor = this;\r
633                 while (actor.parent != null) {\r
634                         actor.localToParentCoordinates(localCoords);\r
635                         actor = actor.parent;\r
636                         if (actor == ascendant) break;\r
637                 }\r
638                 return localCoords;\r
639         }\r
640 \r
641         /** Converts the coordinates given in the parent's coordinate system to this actor's coordinate system. */\r
642         public Vector2 parentToLocalCoordinates (Vector2 parentCoords) {\r
643                 final float rotation = this.rotation;\r
644                 final float scaleX = this.scaleX;\r
645                 final float scaleY = this.scaleY;\r
646                 final float childX = x;\r
647                 final float childY = y;\r
648                 if (rotation == 0) {\r
649                         if (scaleX == 1 && scaleY == 1) {\r
650                                 parentCoords.x -= childX;\r
651                                 parentCoords.y -= childY;\r
652                         } else {\r
653                                 final float originX = this.originX;\r
654                                 final float originY = this.originY;\r
655                                 parentCoords.x = (parentCoords.x - childX - originX) / scaleX + originX;\r
656                                 parentCoords.y = (parentCoords.y - childY - originY) / scaleY + originY;\r
657                         }\r
658                 } else {\r
659                         final float cos = (float)Math.cos(rotation * MathUtils.degreesToRadians);\r
660                         final float sin = (float)Math.sin(rotation * MathUtils.degreesToRadians);\r
661                         final float originX = this.originX;\r
662                         final float originY = this.originY;\r
663                         final float tox = parentCoords.x - childX - originX;\r
664                         final float toy = parentCoords.y - childY - originY;\r
665                         parentCoords.x = (tox * cos + toy * sin) / scaleX + originX;\r
666                         parentCoords.y = (tox * -sin + toy * cos) / scaleY + originY;\r
667                 }\r
668                 return parentCoords;\r
669         }\r
670 \r
671         public String toString () {\r
672                 String name = this.name;\r
673                 if (name == null) {\r
674                         name = getClass().getName();\r
675                         int dotIndex = name.lastIndexOf('.');\r
676                         if (dotIndex != -1) name = name.substring(dotIndex + 1);\r
677                 }\r
678                 return name;\r
679         }\r
680 }\r