Bug 12592: Scilab hung with plot(-0)
[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
213             DecimalFormat format;
214             if (rulerModel.isAutoTicks()) {
215                 format = computeAutoGraduation();
216             } else {
217                 format = computeUserGraduation();
218             }
219             computeTicksData();
220
221             double distRatio = computeTicksDistanceRatio(windowTicksDirection.getNorm());
222
223             return new RulerDrawingResult(format, ticksValue, subTicksValue, density, distRatio, normalizedProjectedTicksDirection);
224         }
225
226         /**
227          * Constructor.
228          * @param drawingTools the {@link DrawingTools} of the canvas where the ruler will be drawn.
229          * @param rulerModel the {@link RulerModel} of the drawn ruler.
230          */
231         public synchronized RulerDrawingResult drawWithResults(DrawingTools drawingTools, RulerModel rulerModel) {
232             this.rulerModel = rulerModel;
233             subTicksValue = new LinkedList<Double>();
234             ticksValue = new LinkedList<Double>();
235             spritesList = new LinkedList<PositionedSprite>();
236             canvasProjection = drawingTools.getTransformationManager().getCanvasProjection();
237
238             Vector3d windowTicksDirection = canvasProjection.projectDirection(rulerModel.getTicksDirection());
239             windowTicksDirection = windowTicksDirection.setZ(0);
240
241             Vector3d normalizedProjectedTicksDirection = windowTicksDirection.getNormalized();
242             windowSubTicksDelta = normalizedProjectedTicksDirection.times(rulerModel.getSubTicksLength());
243             windowTicksDelta = normalizedProjectedTicksDirection.times(rulerModel.getTicksLength());
244
245             DecimalFormat format;
246             if (rulerModel.isAutoTicks()) {
247                 format = computeAutoGraduation();
248             } else {
249                 format = computeUserGraduation();
250             }
251             computeTicksData();
252
253             draw(drawingTools);
254
255             double distRatio = computeTicksDistanceRatio(windowTicksDirection.getNorm());
256
257             return new RulerDrawingResult(format, ticksValue, subTicksValue, density, distRatio, normalizedProjectedTicksDirection);
258         }
259
260         /**
261          * Compute the ratio between windows ticks norm and the sprite distance.
262          * @param windowTicksNorm the windows tics norm.
263          * @return the ratio between windows ticks norm and the sprite distance.
264          */
265         private double computeTicksDistanceRatio(double windowTicksNorm) {
266             double distRatio;
267             if (windowTicksNorm == 0) {
268                 distRatio = 1.0;
269             } else if (maximalSpritesDistance == 0) {
270                 distRatio = rulerModel.getSpriteDistance() / windowTicksNorm;
271             } else {
272                 distRatio = maximalSpritesDistance / windowTicksNorm;
273             }
274             return distRatio;
275         }
276
277         /**
278          * Actually perform the ruler drawing.
279          * @param drawingTools {@link DrawingTools} used to perform the ruler drawing.
280          */
281         private synchronized void draw(DrawingTools drawingTools) {
282             BuffersManager bufferManager = drawingTools.getCanvas().getBuffersManager();
283             ElementsBuffer vertices = bufferManager.createElementsBuffer();
284             fillVertices(vertices, rulerModel, ticksValue, subTicksValue, canvasProjection);
285             DefaultGeometry geometry = new DefaultGeometry();
286             geometry.setFillDrawingMode(Geometry.FillDrawingMode.NONE);
287             geometry.setLineDrawingMode(Geometry.LineDrawingMode.SEGMENTS);
288             geometry.setVertices(vertices);
289
290             Appearance appearance = new Appearance();
291             appearance.setLineColor(rulerModel.getColor());
292             appearance.setLineWidth((float) rulerModel.getLineWidth());
293
294             drawingTools.getTransformationManager().useWindowCoordinate();
295             try {
296                 for (PositionedSprite positionedSprite : spritesList) {
297                     drawingTools.draw(positionedSprite.getSprite(), AnchorPosition.CENTER, positionedSprite.getWindowPosition());
298                 }
299                 drawingTools.draw(geometry, appearance);
300             } catch (SciRendererException ignored) {
301             }
302             drawingTools.getTransformationManager().useSceneCoordinate();
303             bufferManager.dispose(vertices);
304         }
305
306         /**
307          * Compute the {@link Graduations} used to the ruler drawing in auto-ticks mode..
308          * @return the used decimal format.
309          */
310         private DecimalFormat computeAutoGraduation() {
311             /* The maximum distance corresponding to the actually displayed sprites. */
312             double maxSpritesDistance = 0.0;
313
314             Graduations currentGraduations = rulerModel.getGraduations();
315             Graduations ticksGraduation = currentGraduations;
316             DecimalFormat format = currentGraduations.getFormat();
317             boolean canGetMore = true;
318             List<PositionedSprite> newSpritesList = new LinkedList<PositionedSprite>();
319             while (currentGraduations != null) {
320                 /* The maximum distance to any of the sprites' farthest sides at a given iteration. */
321                 double currentMaximalSpritesDistance = 0;
322
323                 newSpritesList.clear();
324                 List<Double> ticks = currentGraduations.getNewValues();
325                 for (double value : ticks) {
326                     Texture sprite = computeSprite(value, format);
327                     Vector3d windowPosition = canvasProjection.project(rulerModel.getPosition(value));
328
329                     Dimension textureSize = computeSpriteDimension(value);
330
331                     Vector3d delta = projectCenterToEdge(textureSize, windowTicksDelta);
332                     PositionedSprite newSprite = new PositionedSprite(sprite, textureSize, windowPosition.plus(windowTicksDelta.plus(delta)));
333                     newSpritesList.add(newSprite);
334
335                     Vector3d farDelta = windowTicksDelta.plus(delta.times(2.0));
336                     currentMaximalSpritesDistance = Math.max(currentMaximalSpritesDistance, farDelta.getNorm());
337                 }
338
339                 if (collide(newSpritesList, rulerModel.getMargin()) || collide(spritesList, newSpritesList, rulerModel.getMargin())) {
340                     currentGraduations = currentGraduations.getAlternative();
341                     canGetMore = false;
342                 } else {
343                     maxSpritesDistance = Math.max(maxSpritesDistance, currentMaximalSpritesDistance);
344                     spritesList.addAll(newSpritesList);
345                     ticksGraduation = currentGraduations;
346                     if (canGetMore) {
347                         currentGraduations = currentGraduations.getMore();
348                     } else {
349                         currentGraduations = null;
350                     }
351                 }
352             }
353
354
355             this.graduations = ticksGraduation;
356             this.maximalSpritesDistance = maxSpritesDistance;
357
358             return format;
359         }
360
361         /**
362          * Compute the {@link Graduations} used to the ruler drawing in auto-ticks mode..
363          * @return the used decimal format.
364          */
365         private DecimalFormat computeUserGraduation() {
366             /* The maximum distance corresponding to the actually displayed sprites. */
367             double maxSpritesDistance = 0.0;
368             Graduations currentGraduations = rulerModel.getGraduations();
369
370             List<Double> ticks = currentGraduations.getNewValues();
371             DecimalFormat format = currentGraduations.getFormat();
372             for (double value : ticks) {
373                 Texture sprite = computeSprite(value, format);
374                 if (sprite != null) {
375                     Vector3d windowPosition = canvasProjection.project(rulerModel.getPosition(value));
376
377                     Vector3d delta = projectCenterToEdge(sprite, windowTicksDelta);
378                     spritesList.add(new PositionedSprite(sprite, windowPosition.plus(windowTicksDelta.plus(delta))));
379
380                     Vector3d farDelta = windowTicksDelta.plus(delta.times(2.0));
381                     maxSpritesDistance = Math.max(maxSpritesDistance, farDelta.getNorm());
382                 }
383             }
384
385             this.graduations = currentGraduations;
386             this.maximalSpritesDistance = maxSpritesDistance;
387             return format;
388         }
389
390         /**
391          * Compute the ticks, sub-ticks data and the sub-ticks density.
392          */
393         private void computeTicksData() {
394             if (graduations != null) {
395                 ticksValue = graduations.getAllValues();
396                 final int N = rulerModel.getSubticksNumber();
397
398                 if (N < 0) {
399                     Graduations subGraduation = graduations.getSubGraduations();
400                     while ((subGraduation != null) && (computeTicksDistance(subGraduation) < rulerModel.getMinimalSubTicksDistance())) {
401                         subGraduation = subGraduation.getAlternative();
402                     }
403
404                     if (subGraduation != null) {
405                         subTicksValue = subGraduation.getAllValues();
406                     } else {
407                         subTicksValue = new LinkedList<Double>();
408                     }
409                 } else {
410                     subTicksValue = graduations.getSubGraduations(N);
411                 }
412                 density = getDensity();
413             } else {
414                 subTicksValue = new LinkedList<Double>();
415                 ticksValue = new LinkedList<Double>();
416                 density = 0;
417             }
418         }
419
420         private int getDensity() {
421             int N = ticksValue == null ? 0 : ticksValue.size();
422             int M = subTicksValue == null ? 0 : subTicksValue.size();
423
424             if (M <= N || N == 1) {
425                 return 0;
426             }
427
428             return (M - N) / (N - 1);
429         }
430
431         /**
432          * Compute and return the minimal screen distance between two successive ticks of the given {@link Graduations}.
433          * If the given {@link Graduations} is <code>null</code>, the returned value is {@link Double#MAX_VALUE}.
434          * @param graduations the given {@link Graduations}.
435          * @return the minimal screen distance between two successive ticks of the given {@link Graduations}.
436          */
437         private double computeTicksDistance(Graduations graduations) {
438             double minimalDistance = Double.MAX_VALUE;
439             if (graduations != null) {
440                 Vector3d previousProjection = null;
441                 for (double currentValue : graduations.getAllValues()) {
442                     Vector3d currentProjection = canvasProjection.project(rulerModel.getPosition(currentValue));
443                     if (previousProjection != null) {
444                         minimalDistance = Math.min(minimalDistance, currentProjection.minus(previousProjection).getNorm2());
445                     }
446                     previousProjection = currentProjection;
447                 }
448                 minimalDistance = Math.sqrt(minimalDistance);
449             }
450             return minimalDistance;
451         }
452
453         /**
454          * Return true if at leas two element of {@see spritesList} collide.
455          * @param spritesList the list of sprite to be tested.
456          * @param margin the collision margin.
457          * @return true if at leas two element of {@see spritesList} collide.
458          */
459         private boolean collide(List<PositionedSprite> spritesList, double margin) {
460             for (int i = 0; i < spritesList.size(); i++) {
461                 Rectangle2D bounds = spritesList.get(i).getWindowBounds();
462                 for (int j = i + 1; j < spritesList.size(); j++) {
463                     if (collide(bounds, spritesList.get(j).getWindowBounds(), margin)) {
464                         return true;
465                     }
466                 }
467             }
468             return false;
469         }
470
471         /**
472          * Return true if the at least one element of <code>newSpritesList</code> collides with one element of <code>spritesList</code>.
473          * @param spritesList the list of reference sprites.
474          * @param newSpritesList the list of new sprites.
475          * @param margin the collision margin.
476          * @return true if the at least one element of <code>newSpritesList</code> collides with one element of <code>spritesList</code>.
477          */
478         private boolean collide(List<PositionedSprite> spritesList, List<PositionedSprite> newSpritesList, double margin) {
479             for (PositionedSprite sprite1 : newSpritesList) {
480                 Rectangle2D bounds = sprite1.getWindowBounds();
481                 for (PositionedSprite sprite2 : spritesList) {
482                     if (collide(bounds, sprite2.getWindowBounds(), margin)) {
483                         return true;
484                     }
485                 }
486             }
487             return false;
488         }
489
490         /**
491          * Return true if the givens rectangles collide.
492          * @param rectangle1 first rectangle.
493          * @param rectangle2 second rectangle.
494          * @param margin the margin.
495          * @return true if the givens rectangles collide.
496          */
497         private boolean collide(Rectangle2D rectangle1, Rectangle2D rectangle2, double margin) {
498             return  ((rectangle2.getMinX() - rectangle1.getMaxX()) < margin)
499                     &&  ((rectangle1.getMinX() - rectangle2.getMaxX()) < margin)
500                     &&  ((rectangle2.getMinY() - rectangle1.getMaxY()) < margin)
501                     &&  ((rectangle1.getMinY() - rectangle2.getMaxY()) < margin);
502         }
503
504         /**
505          * Compute and return a translation along the given <code>direction</code>.
506          * This translation is the vector between the <code>sprite</code> center and is projection along the given
507          * {@see direction} to the sprite edges.
508          * @param sprite the given {@link Texture}
509          * @param direction the given direction.
510          * @return the vector between the sprite center and is projection to the sprite edges.
511          */
512         private Vector3d projectCenterToEdge(Texture sprite, Vector3d direction) {
513             Vector3d usedDirection;
514             if ((direction == null) || (direction.isZero())) {
515                 usedDirection = new Vector3d(1, 0, 0);
516             } else {
517                 usedDirection = direction;
518             }
519
520             /* +1 is used to have a space between the tick and its label */
521             Dimension textureSize = sprite.getDataProvider().getTextureSize();
522             double ratioX = textureSize.width / Math.abs(usedDirection.getX()) + 1;
523             double ratioY = textureSize.height / Math.abs(usedDirection.getY()) + 1;
524             double ratio = Math.min(ratioY, ratioX) / 2;
525             return usedDirection.times(ratio);
526         }
527
528         /**
529          * Compute and return a translation along the given <code>direction</code>.
530          * This translation is the vector between the <code>sprite</code> center and is projection along the given
531          * {@see direction} to the sprite edges.
532          * @param textureSize the size of corresponding texture
533          * @param direction the given direction.
534          * @return the vector between the sprite center and is projection to the sprite edges.
535          */
536         private Vector3d projectCenterToEdge(Dimension textureSize, Vector3d direction) {
537             Vector3d usedDirection;
538             if ((direction == null) || (direction.isZero())) {
539                 usedDirection = new Vector3d(1, 0, 0);
540             } else {
541                 usedDirection = direction;
542             }
543
544             /* +1 is used to have a space between the tick and its label */
545             double ratioX = textureSize.width / Math.abs(usedDirection.getX()) + 1;
546             double ratioY = textureSize.height / Math.abs(usedDirection.getY()) + 1;
547             double ratio = Math.min(ratioY, ratioX) / 2;
548             return usedDirection.times(ratio);
549         }
550
551         /**
552          * Fill a vertices buffer with the needed data to draw a ruler.
553          * @param verticesBuffer the {@link ElementsBuffer} to fill.
554          * @param rulerModel the {@link RulerModel} to draw.
555          * @param ticksValue the list of ticks.
556          * @param subTicksValue the list of sub-ticks.
557          * @param canvasProjection the used canvas projection.
558          */
559         private void fillVertices(ElementsBuffer verticesBuffer, RulerModel rulerModel, List<Double> ticksValue, List<Double> subTicksValue, Transformation canvasProjection) {
560             Vector3d a = rulerModel.getFirstPoint();
561             Vector3d b = rulerModel.getSecondPoint();
562
563             if ((a != null) && (b != null)) {
564                 int bufferSize = 2 * ticksValue.size() + 2 * subTicksValue.size();
565                 if (rulerModel.isLineVisible()) {
566                     bufferSize += 2;
567                 }
568                 FloatBuffer data = FloatBuffer.allocate(4 * bufferSize);
569                 data.rewind();
570
571                 for (double value : ticksValue) {
572                     Vector3d p = canvasProjection.project(rulerModel.getPosition(value));
573                     data.put(p.getDataAsFloatArray(4));
574                     data.put(p.plus(windowTicksDelta).getDataAsFloatArray(4));
575                 }
576
577                 for (double value : subTicksValue) {
578                     Vector3d p = canvasProjection.project(rulerModel.getPosition(value));
579                     data.put(p.getDataAsFloatArray(4));
580                     data.put(p.plus(windowSubTicksDelta).getDataAsFloatArray(4));
581                 }
582
583                 if (rulerModel.isLineVisible()) {
584                     data.put(canvasProjection.project(a).getDataAsFloatArray(4));
585                     data.put(canvasProjection.project(b).getDataAsFloatArray(4));
586                 }
587
588                 data.rewind();
589                 verticesBuffer.setData(data, 4);
590             } else {
591                 verticesBuffer.setData(new float[0], 4);
592             }
593         }
594
595         /**
596          * Compute the {@link Texture} for a given value.
597          * The {@link Texture} is made once using the current {@link RulerSpriteFactory}.
598          * @param value the given value.
599          * @param format the format to use.
600          * @return A {@link Texture} for the label at the given value.
601          */
602         private Texture computeSprite(double value, DecimalFormat format) {
603             Texture sprite = spriteMap.get(value);
604             if (sprite == null) {
605                 sprite = spriteFactory.create(value, format, textureManager);
606                 if (sprite != null) {
607                     spriteMap.put(value, sprite);
608                 }
609             }
610             return sprite;
611         }
612
613         private Dimension computeSpriteDimension(double value) {
614             Dimension spriteDimension = spriteDimensionMap.get(value);
615             if (spriteDimension == null) {
616                 Texture sprite = spriteMap.get(value);
617                 if (sprite != null) {
618                     spriteDimension = sprite.getDataProvider().getTextureSize();
619                     spriteDimensionMap.put(value, spriteDimension);
620                 }
621             }
622
623             return spriteDimension;
624         }
625
626         /**
627          * This class is a basic container for a {@link Texture} and an associated window position.
628          *
629          * @author Pierre Lando
630          */
631         private class PositionedSprite {
632
633             private final Texture sprite;
634             private final Vector3d windowPosition;
635             private final Rectangle2D windowsBounds;
636
637             /**
638              * Default constructor.
639              * @param sprite the {@link Texture}.
640              * @param windowPosition the window position.
641              */
642             public PositionedSprite(Texture sprite,  Vector3d windowPosition) {
643                 //long tic = Calendar.getInstance().getTimeInMillis();
644                 this.windowPosition = windowPosition;
645                 this.sprite = sprite;
646
647                 Dimension textureSize = sprite.getDataProvider().getTextureSize();
648                 windowsBounds = new Rectangle2D.Double(
649                     windowPosition.getX(),
650                     windowPosition.getY(),
651                     textureSize.width,
652                     textureSize.height
653                 );
654
655                 //System.err.println("====[new PositionedSprite] time = "+(Calendar.getInstance().getTimeInMillis() - tic));
656             }
657
658             public PositionedSprite(Texture sprite, Dimension textureSize, Vector3d windowPosition) {
659                 //long tic = Calendar.getInstance().getTimeInMillis();
660                 this.windowPosition = windowPosition;
661                 this.sprite = sprite;
662
663                 windowsBounds = new Rectangle2D.Double(
664                     windowPosition.getX(),
665                     windowPosition.getY(),
666                     textureSize.width,
667                     textureSize.height
668                 );
669
670                 //System.err.println("====[new PositionedSprite] time = "+(Calendar.getInstance().getTimeInMillis() - tic));
671             }
672             /**
673              * Return the {@link Texture}
674              * @return the {@link Texture}
675              */
676             public Texture getSprite() {
677                 return sprite;
678             }
679
680             /**
681              * Return the window position of the {@link Texture}.
682              * @return the window position of the {@link Texture}.
683              */
684             public Vector3d getWindowPosition() {
685                 return windowPosition;
686             }
687
688             /**
689              * Return the window bounds of the {@link Texture}
690              * @return the window bounds of the {@link Texture}
691              */
692             public Rectangle2D getWindowBounds() {
693                 return windowsBounds;
694             }
695         }
696     }
697 }