OSDN Git Service

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