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