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