2286c8f21165244818f717cd792b4ba7e71249b6
[scilab.git] / scilab / modules / scirenderer / src / org / scilab / forge / scirenderer / ruler / RulerDrawer.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2009-2012 - DIGITEO - Pierre Lando
4  * Copyright (C) 2012 - Scilab Enterprises - Bruno JOFRET
5  * Copyright (C) 2013 - Scilab Enterprises - Calixte DENIZET
6  *
7  * This file must be used under the terms of the CeCILL.
8  * This source file is licensed as described in the file COPYING, which
9  * you should have received as part of this distribution.  The terms
10  * are also available at
11  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
12  */
13
14 package org.scilab.forge.scirenderer.ruler;
15
16 import java.awt.Dimension;
17 import java.awt.geom.Rectangle2D;
18 import java.nio.FloatBuffer;
19 import java.text.DecimalFormat;
20 import java.util.HashMap;
21 import java.util.LinkedList;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.concurrent.ConcurrentHashMap;
25
26 import org.scilab.forge.scirenderer.DrawingTools;
27 import org.scilab.forge.scirenderer.SciRendererException;
28 import org.scilab.forge.scirenderer.buffers.BuffersManager;
29 import org.scilab.forge.scirenderer.buffers.ElementsBuffer;
30 import org.scilab.forge.scirenderer.ruler.graduations.Graduations;
31 import org.scilab.forge.scirenderer.shapes.appearance.Appearance;
32 import org.scilab.forge.scirenderer.shapes.geometry.DefaultGeometry;
33 import org.scilab.forge.scirenderer.shapes.geometry.Geometry;
34 import org.scilab.forge.scirenderer.texture.AnchorPosition;
35 import org.scilab.forge.scirenderer.texture.Texture;
36 import org.scilab.forge.scirenderer.texture.TextureManager;
37 import org.scilab.forge.scirenderer.tranformations.Transformation;
38 import org.scilab.forge.scirenderer.tranformations.Vector3d;
39
40 /**
41  * @author Pierre Lando
42  */
43 public class RulerDrawer {
44
45     /**
46      * Sprite map.
47      */
48     private final Map<Double, Texture> spriteMap = new ConcurrentHashMap<Double, Texture>();
49
50     /**
51      * The current {@link TextureManager}.
52      */
53     private final TextureManager textureManager;
54
55     /**
56      * The used {@link RulerSpriteFactory}.
57      */
58     private RulerSpriteFactory spriteFactory;
59
60     private OneShotRulerDrawer oneShotRulerDrawer;
61
62     /**
63      * Ruler drawer constructor.
64      * @param textureManager the {@link TextureManager} of the canvas where the ruler will be drawn.
65      */
66     public RulerDrawer(TextureManager textureManager) {
67         this.textureManager = textureManager;
68         this.spriteFactory = new DefaultRulerSpriteFactory();
69         this.oneShotRulerDrawer = new OneShotRulerDrawer();
70     }
71
72     /**
73      * Ruler drawing method.
74      * @param drawingTools the {@link DrawingTools} of the canvas where the ruler will be drawn.
75      * @param model the {@link RulerModel} of the drawn ruler.
76      * @return the {@link RulerDrawingResult} give information about how the ruler have been drawn.
77      */
78     public RulerDrawingResult draw(DrawingTools drawingTools, RulerModel model) {
79         return oneShotRulerDrawer.drawWithResults(drawingTools, model);
80     }
81
82     /**
83      * Draw the ruler
84      * @param drawingTools the {@link DrawingTools} of the canvas where the ruler will be drawn.
85      */
86     public void draw(DrawingTools drawingTools) {
87         oneShotRulerDrawer.draw(drawingTools);
88     }
89
90     /**
91      * Get the model
92      * @return the ruler model.
93      */
94     public RulerModel getModel() {
95         return oneShotRulerDrawer.rulerModel;
96     }
97
98     /**
99      * Get the subticks values
100      * @return the values.
101      */
102     public List<Double> getSubTicksValue() {
103         return oneShotRulerDrawer.subTicksValue;
104     }
105
106     /**
107      * Get the ticks values
108      * @return the values.
109      */
110     public List<Double> getTicksValue() {
111         return oneShotRulerDrawer.ticksValue;
112     }
113
114     /**
115      * Compute different parameters on a ruler
116      * @param drawingTools the {@link DrawingTools} of the canvas where the ruler will be drawn.
117      * @param rulerModel the {@link RulerModel} of the drawn ruler.
118      * @param canvasProjection the canvas projection.
119      */
120     public RulerDrawingResult computeRuler(DrawingTools drawingTools, RulerModel model, Transformation canvasProjection) {
121         return oneShotRulerDrawer.computeRuler(drawingTools, model, canvasProjection);
122     }
123
124     /**
125      * Set the current {@link RulerSpriteFactory}.
126      * All existing sprite will be cleared.
127      * This ruler drawer will use the new {@link RulerSpriteFactory}.
128      * @param spriteFactory the new {@link RulerSpriteFactory}.
129      */
130     public void setSpriteFactory(RulerSpriteFactory spriteFactory) {
131         disposeResources();
132         this.spriteFactory = spriteFactory;
133     }
134
135     /**
136      * Dispose all used resources.
137      */
138     public void disposeResources() {
139         textureManager.dispose(spriteMap.values());
140         spriteMap.clear();
141         oneShotRulerDrawer.dispose();
142     }
143
144     /**
145      * This class actually perform all the rendering of one ruler.
146      */
147     private class OneShotRulerDrawer {
148
149         private Transformation canvasProjection;
150         private RulerModel rulerModel;
151         private Vector3d windowSubTicksDelta;
152         private Vector3d windowTicksDelta;
153
154         /**
155          * Sprite Dimension map. Used as cached values in order not to charge DataProvider.
156          */
157         private Map<Double, Dimension> spriteDimensionMap = new HashMap<Double, Dimension>();
158
159         private List<PositionedSprite> spritesList = new LinkedList<PositionedSprite>();
160
161         /**
162          * The maximum distance corresponding to the actually displayed sprites.
163          */
164         private double maximalSpritesDistance;
165
166         /**
167          * Deepest possible {@see Graduations}
168          */
169         private Graduations graduations;
170
171         private List<Double> subTicksValue;
172         private List<Double> ticksValue;
173         private int density;
174
175         public OneShotRulerDrawer() { }
176
177         public synchronized void dispose() {
178             spriteDimensionMap.clear();
179             spritesList.clear();
180             if (subTicksValue != null) {
181                 subTicksValue.clear();
182             }
183             if (ticksValue != null) {
184                 ticksValue.clear();
185             }
186             rulerModel = null;
187         }
188
189         /**
190          * Compute different parameters on a ruler
191          * @param drawingTools the {@link DrawingTools} of the canvas where the ruler will be drawn.
192          * @param rulerModel the {@link RulerModel} of the drawn ruler.
193          * @param canvasProjection the canvas projection.
194          */
195         public synchronized RulerDrawingResult computeRuler(DrawingTools drawingTools, RulerModel rulerModel, Transformation canvasProjection) {
196             // Same code as drawWithResults (without drawing)
197             // Historically, computations were made when drawing and they are made before drawing.
198             // TODO: remove drawWithResults ??
199             this.canvasProjection = canvasProjection;
200             this.rulerModel = rulerModel;
201             subTicksValue = new LinkedList<Double>();
202             ticksValue = new LinkedList<Double>();
203             spritesList = new LinkedList<PositionedSprite>();
204
205             Vector3d windowTicksDirection = canvasProjection.projectDirection(rulerModel.getTicksDirection());
206             windowTicksDirection = windowTicksDirection.setZ(0);
207
208             Vector3d normalizedProjectedTicksDirection = windowTicksDirection.getNormalized();
209             windowSubTicksDelta = normalizedProjectedTicksDirection.times(rulerModel.getSubTicksLength());
210             windowTicksDelta = normalizedProjectedTicksDirection.times(rulerModel.getTicksLength());
211
212             DecimalFormat format;
213             if (rulerModel.isAutoTicks()) {
214                 format = computeAutoGraduation();
215             } else {
216                 format = computeUserGraduation();
217             }
218             computeTicksData();
219
220             double distRatio = computeTicksDistanceRatio(windowTicksDirection.getNorm());
221
222             return new RulerDrawingResult(format, ticksValue, subTicksValue, density, distRatio, normalizedProjectedTicksDirection);
223         }
224
225         /**
226          * Constructor.
227          * @param drawingTools the {@link DrawingTools} of the canvas where the ruler will be drawn.
228          * @param rulerModel the {@link RulerModel} of the drawn ruler.
229          */
230         public synchronized RulerDrawingResult drawWithResults(DrawingTools drawingTools, RulerModel rulerModel) {
231             this.rulerModel = rulerModel;
232             subTicksValue = new LinkedList<Double>();
233             ticksValue = new LinkedList<Double>();
234             spritesList = new LinkedList<PositionedSprite>();
235             canvasProjection = drawingTools.getTransformationManager().getCanvasProjection();
236
237             Vector3d windowTicksDirection = canvasProjection.projectDirection(rulerModel.getTicksDirection());
238             windowTicksDirection = windowTicksDirection.setZ(0);
239
240             Vector3d normalizedProjectedTicksDirection = windowTicksDirection.getNormalized();
241             windowSubTicksDelta = normalizedProjectedTicksDirection.times(rulerModel.getSubTicksLength());
242             windowTicksDelta = normalizedProjectedTicksDirection.times(rulerModel.getTicksLength());
243
244             DecimalFormat format;
245             if (rulerModel.isAutoTicks()) {
246                 format = computeAutoGraduation();
247             } else {
248                 format = computeUserGraduation();
249             }
250             computeTicksData();
251
252             draw(drawingTools);
253
254             double distRatio = computeTicksDistanceRatio(windowTicksDirection.getNorm());
255
256             return new RulerDrawingResult(format, ticksValue, subTicksValue, density, distRatio, normalizedProjectedTicksDirection);
257         }
258
259         /**
260          * Compute the ratio between windows ticks norm and the sprite distance.
261          * @param windowTicksNorm the windows tics norm.
262          * @return the ratio between windows ticks norm and the sprite distance.
263          */
264         private double computeTicksDistanceRatio(double windowTicksNorm) {
265             double distRatio;
266             if (windowTicksNorm == 0) {
267                 distRatio = 1.0;
268             } else if (maximalSpritesDistance == 0) {
269                 distRatio = rulerModel.getSpriteDistance() / windowTicksNorm;
270             } else {
271                 distRatio = maximalSpritesDistance / windowTicksNorm;
272             }
273             return distRatio;
274         }
275
276         /**
277          * Actually perform the ruler drawing.
278          * @param drawingTools {@link DrawingTools} used to perform the ruler drawing.
279          */
280         private synchronized void draw(DrawingTools drawingTools) {
281             BuffersManager bufferManager = drawingTools.getCanvas().getBuffersManager();
282             ElementsBuffer vertices = bufferManager.createElementsBuffer();
283             fillVertices(vertices, rulerModel, ticksValue, subTicksValue, canvasProjection);
284             DefaultGeometry geometry = new DefaultGeometry();
285             geometry.setFillDrawingMode(Geometry.FillDrawingMode.NONE);
286             geometry.setLineDrawingMode(Geometry.LineDrawingMode.SEGMENTS);
287             geometry.setVertices(vertices);
288
289             Appearance appearance = new Appearance();
290             appearance.setLineColor(rulerModel.getColor());
291             appearance.setLineWidth((float) rulerModel.getLineWidth());
292
293             drawingTools.getTransformationManager().useWindowCoordinate();
294             try {
295                 for (PositionedSprite positionedSprite : spritesList) {
296                     drawingTools.draw(positionedSprite.getSprite(), AnchorPosition.CENTER, positionedSprite.getWindowPosition());
297                 }
298                 drawingTools.draw(geometry, appearance);
299             } catch (SciRendererException ignored) {
300             }
301             drawingTools.getTransformationManager().useSceneCoordinate();
302             bufferManager.dispose(vertices);
303         }
304
305         /**
306          * Compute the {@link Graduations} used to the ruler drawing in auto-ticks mode..
307          * @return the used decimal format.
308          */
309         private DecimalFormat computeAutoGraduation() {
310             /* The maximum distance corresponding to the actually displayed sprites. */
311             double maxSpritesDistance = 0.0;
312
313             Graduations currentGraduations = rulerModel.getGraduations();
314             Graduations ticksGraduation = currentGraduations;
315             DecimalFormat format = currentGraduations.getFormat();
316             boolean canGetMore = true;
317             List<PositionedSprite> newSpritesList = new LinkedList<PositionedSprite>();
318             while (currentGraduations != null) {
319                 /* The maximum distance to any of the sprites' farthest sides at a given iteration. */
320                 double currentMaximalSpritesDistance = 0;
321
322                 newSpritesList.clear();
323                 List<Double> ticks = currentGraduations.getNewValues();
324                 for (double value : ticks) {
325                     Texture sprite = computeSprite(value, format);
326                     Vector3d windowPosition = canvasProjection.project(rulerModel.getPosition(value));
327
328                     Dimension textureSize = computeSpriteDimension(value);
329
330                     Vector3d delta = projectCenterToEdge(textureSize, windowTicksDelta);
331                     PositionedSprite newSprite = new PositionedSprite(sprite, textureSize, windowPosition.plus(windowTicksDelta.plus(delta)));
332                     newSpritesList.add(newSprite);
333
334                     Vector3d farDelta = windowTicksDelta.plus(delta.times(2.0));
335                     currentMaximalSpritesDistance = Math.max(currentMaximalSpritesDistance, farDelta.getNorm());
336                 }
337
338                 if (collide(newSpritesList, rulerModel.getMargin()) || collide(spritesList, newSpritesList, rulerModel.getMargin())) {
339                     currentGraduations = currentGraduations.getAlternative();
340                     canGetMore = false;
341                 } else {
342                     maxSpritesDistance = Math.max(maxSpritesDistance, currentMaximalSpritesDistance);
343                     spritesList.addAll(newSpritesList);
344                     ticksGraduation = currentGraduations;
345                     if (canGetMore) {
346                         currentGraduations = currentGraduations.getMore();
347                     } else {
348                         currentGraduations = null;
349                     }
350                 }
351             }
352
353
354             this.graduations = ticksGraduation;
355             this.maximalSpritesDistance = maxSpritesDistance;
356
357             return format;
358         }
359
360         /**
361          * Compute the {@link Graduations} used to the ruler drawing in auto-ticks mode..
362          * @return the used decimal format.
363          */
364         private DecimalFormat computeUserGraduation() {
365             /* The maximum distance corresponding to the actually displayed sprites. */
366             double maxSpritesDistance = 0.0;
367             Graduations currentGraduations = rulerModel.getGraduations();
368
369             List<Double> ticks = currentGraduations.getNewValues();
370             DecimalFormat format = currentGraduations.getFormat();
371             for (double value : ticks) {
372                 Texture sprite = computeSprite(value, format);
373                 if (sprite != null) {
374                     Vector3d windowPosition = canvasProjection.project(rulerModel.getPosition(value));
375
376                     Vector3d delta = projectCenterToEdge(sprite, windowTicksDelta);
377                     spritesList.add(new PositionedSprite(sprite, windowPosition.plus(windowTicksDelta.plus(delta))));
378
379                     Vector3d farDelta = windowTicksDelta.plus(delta.times(2.0));
380                     maxSpritesDistance = Math.max(maxSpritesDistance, farDelta.getNorm());
381                 }
382             }
383
384             this.graduations = currentGraduations;
385             this.maximalSpritesDistance = maxSpritesDistance;
386             return format;
387         }
388
389         /**
390          * Compute the ticks, sub-ticks data and the sub-ticks density.
391          */
392         private void computeTicksData() {
393             if (graduations != null) {
394                 ticksValue = graduations.getAllValues();
395                 final int N = rulerModel.getSubticksNumber();
396
397                 if (N < 0) {
398                     Graduations subGraduation = graduations.getSubGraduations();
399                     while ((subGraduation != null) && (computeTicksDistance(subGraduation) < rulerModel.getMinimalSubTicksDistance())) {
400                         subGraduation = subGraduation.getAlternative();
401                     }
402
403                     if (subGraduation != null) {
404                         subTicksValue = subGraduation.getAllValues();
405                     } else {
406                         subTicksValue = new LinkedList<Double>();
407                     }
408                 } else {
409                     subTicksValue = graduations.getSubGraduations(N);
410                 }
411                 density = getDensity();
412             } else {
413                 subTicksValue = new LinkedList<Double>();
414                 ticksValue = new LinkedList<Double>();
415                 density = 0;
416             }
417         }
418
419         private int getDensity() {
420             int N = ticksValue == null ? 0 : ticksValue.size();
421             int M = subTicksValue == null ? 0 : subTicksValue.size();
422
423             if (M <= N || N == 1) {
424                 return 0;
425             }
426
427             return (M - N) / (N - 1);
428         }
429
430         /**
431          * Compute and return the minimal screen distance between two successive ticks of the given {@link Graduations}.
432          * If the given {@link Graduations} is <code>null</code>, the returned value is {@link Double#MAX_VALUE}.
433          * @param graduations the given {@link Graduations}.
434          * @return the minimal screen distance between two successive ticks of the given {@link Graduations}.
435          */
436         private double computeTicksDistance(Graduations graduations) {
437             double minimalDistance = Double.MAX_VALUE;
438             if (graduations != null) {
439                 Vector3d previousProjection = null;
440                 for (double currentValue : graduations.getAllValues()) {
441                     Vector3d currentProjection = canvasProjection.project(rulerModel.getPosition(currentValue));
442                     if (previousProjection != null) {
443                         minimalDistance = Math.min(minimalDistance, currentProjection.minus(previousProjection).getNorm2());
444                     }
445                     previousProjection = currentProjection;
446                 }
447                 minimalDistance = Math.sqrt(minimalDistance);
448             }
449             return minimalDistance;
450         }
451
452         /**
453          * Return true if at leas two element of {@see spritesList} collide.
454          * @param spritesList the list of sprite to be tested.
455          * @param margin the collision margin.
456          * @return true if at leas two element of {@see spritesList} collide.
457          */
458         private boolean collide(List<PositionedSprite> spritesList, double margin) {
459             for (int i = 0; i < spritesList.size(); i++) {
460                 for (int j = i + 1; j < spritesList.size(); j++) {
461                     if (collide(spritesList.get(i).getWindowBounds(), spritesList.get(j).getWindowBounds(), margin)) {
462                         return true;
463                     }
464                 }
465             }
466             return false;
467         }
468
469         /**
470          * Return true if the at least one element of <code>newSpritesList</code> collides with one element of <code>spritesList</code>.
471          * @param spritesList the list of reference sprites.
472          * @param newSpritesList the list of new sprites.
473          * @param margin the collision margin.
474          * @return true if the at least one element of <code>newSpritesList</code> collides with one element of <code>spritesList</code>.
475          */
476         private boolean collide(List<PositionedSprite> spritesList, List<PositionedSprite> newSpritesList, double margin) {
477             for (PositionedSprite sprite1 : newSpritesList) {
478                 for (PositionedSprite sprite2 : spritesList) {
479                     if (collide(sprite1.getWindowBounds(), sprite2.getWindowBounds(), margin)) {
480                         return true;
481                     }
482                 }
483             }
484             return false;
485         }
486
487         /**
488          * Return true if the givens rectangles collide.
489          * @param rectangle1 first rectangle.
490          * @param rectangle2 second rectangle.
491          * @param margin the margin.
492          * @return true if the givens rectangles collide.
493          */
494         private boolean collide(Rectangle2D rectangle1, Rectangle2D rectangle2, double margin) {
495             return  ((rectangle2.getMinX() - rectangle1.getMaxX()) < margin)
496                     &&  ((rectangle1.getMinX() - rectangle2.getMaxX()) < margin)
497                     &&  ((rectangle2.getMinY() - rectangle1.getMaxY()) < margin)
498                     &&  ((rectangle1.getMinY() - rectangle2.getMaxY()) < margin);
499         }
500
501         /**
502          * Compute and return a translation along the given <code>direction</code>.
503          * This translation is the vector between the <code>sprite</code> center and is projection along the given
504          * {@see direction} to the sprite edges.
505          * @param sprite the given {@link Texture}
506          * @param direction the given direction.
507          * @return the vector between the sprite center and is projection to the sprite edges.
508          */
509         private Vector3d projectCenterToEdge(Texture sprite, Vector3d direction) {
510             Vector3d usedDirection;
511             if ((direction == null) || (direction.isZero())) {
512                 usedDirection = new Vector3d(1, 0, 0);
513             } else {
514                 usedDirection = direction;
515             }
516
517             /* +1 is used to have a space between the tick and its label */
518             Dimension textureSize = sprite.getDataProvider().getTextureSize();
519             double ratioX = textureSize.width / Math.abs(usedDirection.getX()) + 1;
520             double ratioY = textureSize.height / Math.abs(usedDirection.getY()) + 1;
521             double ratio = Math.min(ratioY, ratioX) / 2;
522             return usedDirection.times(ratio);
523         }
524
525         /**
526          * Compute and return a translation along the given <code>direction</code>.
527          * This translation is the vector between the <code>sprite</code> center and is projection along the given
528          * {@see direction} to the sprite edges.
529          * @param textureSize the size of corresponding texture
530          * @param direction the given direction.
531          * @return the vector between the sprite center and is projection to the sprite edges.
532          */
533         private Vector3d projectCenterToEdge(Dimension textureSize, Vector3d direction) {
534             Vector3d usedDirection;
535             if ((direction == null) || (direction.isZero())) {
536                 usedDirection = new Vector3d(1, 0, 0);
537             } else {
538                 usedDirection = direction;
539             }
540
541             /* +1 is used to have a space between the tick and its label */
542             double ratioX = textureSize.width / Math.abs(usedDirection.getX()) + 1;
543             double ratioY = textureSize.height / Math.abs(usedDirection.getY()) + 1;
544             double ratio = Math.min(ratioY, ratioX) / 2;
545             return usedDirection.times(ratio);
546         }
547
548         /**
549          * Fill a vertices buffer with the needed data to draw a ruler.
550          * @param verticesBuffer the {@link ElementsBuffer} to fill.
551          * @param rulerModel the {@link RulerModel} to draw.
552          * @param ticksValue the list of ticks.
553          * @param subTicksValue the list of sub-ticks.
554          * @param canvasProjection the used canvas projection.
555          */
556         private void fillVertices(ElementsBuffer verticesBuffer, RulerModel rulerModel, List<Double> ticksValue, List<Double> subTicksValue, Transformation canvasProjection) {
557             Vector3d a = rulerModel.getFirstPoint();
558             Vector3d b = rulerModel.getSecondPoint();
559
560             if ((a != null) && (b != null)) {
561                 int bufferSize = 2 * ticksValue.size() + 2 * subTicksValue.size();
562                 if (rulerModel.isLineVisible()) {
563                     bufferSize += 2;
564                 }
565                 FloatBuffer data = FloatBuffer.allocate(4 * bufferSize);
566                 data.rewind();
567
568                 for (double value : ticksValue) {
569                     Vector3d p = canvasProjection.project(rulerModel.getPosition(value));
570                     data.put(p.getDataAsFloatArray(4));
571                     data.put(p.plus(windowTicksDelta).getDataAsFloatArray(4));
572                 }
573
574                 for (double value : subTicksValue) {
575                     Vector3d p = canvasProjection.project(rulerModel.getPosition(value));
576                     data.put(p.getDataAsFloatArray(4));
577                     data.put(p.plus(windowSubTicksDelta).getDataAsFloatArray(4));
578                 }
579
580                 if (rulerModel.isLineVisible()) {
581                     data.put(canvasProjection.project(a).getDataAsFloatArray(4));
582                     data.put(canvasProjection.project(b).getDataAsFloatArray(4));
583                 }
584
585                 data.rewind();
586                 verticesBuffer.setData(data, 4);
587             } else {
588                 verticesBuffer.setData(new float[0], 4);
589             }
590         }
591
592         /**
593          * Compute the {@link Texture} for a given value.
594          * The {@link Texture} is made once using the current {@link RulerSpriteFactory}.
595          * @param value the given value.
596          * @param format the format to use.
597          * @return A {@link Texture} for the label at the given value.
598          */
599         private Texture computeSprite(double value, DecimalFormat format) {
600             Texture sprite = spriteMap.get(value);
601             if (sprite == null) {
602                 sprite = spriteFactory.create(value, format, textureManager);
603                 if (sprite != null) {
604                     spriteMap.put(value, sprite);
605                 }
606             }
607             return sprite;
608         }
609
610         private Dimension computeSpriteDimension(double value) {
611             Dimension spriteDimension = spriteDimensionMap.get(value);
612             if (spriteDimension == null) {
613                 Texture sprite = spriteMap.get(value);
614                 if (sprite != null) {
615                     spriteDimension = sprite.getDataProvider().getTextureSize();
616                     spriteDimensionMap.put(value, spriteDimension);
617                 }
618             }
619
620             return spriteDimension;
621         }
622
623         /**
624          * This class is a basic container for a {@link Texture} and an associated window position.
625          *
626          * @author Pierre Lando
627          */
628         private class PositionedSprite {
629
630             private final Texture sprite;
631             private final Vector3d windowPosition;
632             private final Rectangle2D windowsBounds;
633
634             /**
635              * Default constructor.
636              * @param sprite the {@link Texture}.
637              * @param windowPosition the window position.
638              */
639             public PositionedSprite(Texture sprite,  Vector3d windowPosition) {
640                 //long tic = Calendar.getInstance().getTimeInMillis();
641                 this.windowPosition = windowPosition;
642                 this.sprite = sprite;
643
644                 Dimension textureSize = sprite.getDataProvider().getTextureSize();
645                 windowsBounds = new Rectangle2D.Double(
646                     windowPosition.getX(),
647                     windowPosition.getY(),
648                     textureSize.width,
649                     textureSize.height
650                 );
651
652                 //System.err.println("====[new PositionedSprite] time = "+(Calendar.getInstance().getTimeInMillis() - tic));
653             }
654
655             public PositionedSprite(Texture sprite, Dimension textureSize, Vector3d windowPosition) {
656                 //long tic = Calendar.getInstance().getTimeInMillis();
657                 this.windowPosition = windowPosition;
658                 this.sprite = sprite;
659
660                 windowsBounds = new Rectangle2D.Double(
661                     windowPosition.getX(),
662                     windowPosition.getY(),
663                     textureSize.width,
664                     textureSize.height
665                 );
666
667                 //System.err.println("====[new PositionedSprite] time = "+(Calendar.getInstance().getTimeInMillis() - tic));
668             }
669             /**
670              * Return the {@link Texture}
671              * @return the {@link Texture}
672              */
673             public Texture getSprite() {
674                 return sprite;
675             }
676
677             /**
678              * Return the window position of the {@link Texture}.
679              * @return the window position of the {@link Texture}.
680              */
681             public Vector3d getWindowPosition() {
682                 return windowPosition;
683             }
684
685             /**
686              * Return the window bounds of the {@link Texture}
687              * @return the window bounds of the {@link Texture}
688              */
689             public Rectangle2D getWindowBounds() {
690                 return windowsBounds;
691             }
692         }
693     }
694 }