Bug 13022 fixed: Vectorial export did not clip large segments
[scilab.git] / scilab / modules / scirenderer / src / org / scilab / forge / scirenderer / implementation / g2d / motor / Motor3D.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2012-2013 - Scilab Enterprises - Calixte Denizet
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2.1-en.txt
10  */
11
12 package org.scilab.forge.scirenderer.implementation.g2d.motor;
13
14 import java.awt.Color;
15 import java.awt.Dimension;
16 import java.awt.Graphics2D;
17 import java.awt.RenderingHints;
18 import java.awt.image.BufferedImage;
19 import java.nio.FloatBuffer;
20 import java.nio.IntBuffer;
21 import java.util.Arrays;
22 import java.util.List;
23
24 import org.scilab.forge.scirenderer.DrawingTools;
25 import org.scilab.forge.scirenderer.buffers.ElementsBuffer;
26 import org.scilab.forge.scirenderer.clipping.ClippingPlane;
27 import org.scilab.forge.scirenderer.implementation.g2d.G2DCanvas;
28 import org.scilab.forge.scirenderer.implementation.g2d.buffers.G2DElementsBuffer;
29 import org.scilab.forge.scirenderer.implementation.g2d.texture.G2DTextureDrawingTools;
30 import org.scilab.forge.scirenderer.implementation.g2d.texture.G2DTextureManager;
31 import org.scilab.forge.scirenderer.shapes.appearance.Appearance;
32 import org.scilab.forge.scirenderer.shapes.geometry.Geometry;
33 import org.scilab.forge.scirenderer.shapes.geometry.Geometry.FaceCullingMode;
34 import org.scilab.forge.scirenderer.texture.AnchorPosition;
35 import org.scilab.forge.scirenderer.texture.Texture;
36 import org.scilab.forge.scirenderer.tranformations.Transformation;
37 import org.scilab.forge.scirenderer.tranformations.Vector3d;
38 import org.scilab.forge.scirenderer.tranformations.Vector3f;
39
40 import org.scilab.forge.scirenderer.lightning.Light;
41 import org.scilab.forge.scirenderer.lightning.LightManager;
42 import org.scilab.forge.scirenderer.implementation.g2d.lighting.G2DLight;
43 import org.scilab.forge.scirenderer.implementation.g2d.lighting.G2DLightManager;
44 import org.scilab.forge.scirenderer.shapes.appearance.Material;
45
46 /**
47  * @author Calixte DENIZET
48  */
49 public class Motor3D {
50
51     private Transformation transf;
52     private Transformation singleTransf;
53     private FaceCullingMode mode = FaceCullingMode.BOTH;
54     private Graphics2D g2d;
55     private Dimension dim;
56     private G2DTextureDrawingTools textureDrawingTools;
57     private G2DCanvas canvas;
58
59     /**
60      * Default constructor
61      * @param g2d a Graphics2D object where to draw
62      * @param dim the graphic dimensions
63      */
64     public Motor3D(G2DCanvas canvas, Graphics2D g2d, Dimension dim) {
65         this.canvas = canvas;
66         this.g2d = g2d;
67         this.dim = dim;
68         this.textureDrawingTools = new G2DTextureDrawingTools(g2d);
69         AbstractDrawable3DObject.resetDefaultPrecedence();
70     }
71
72     public void setGraphics(Graphics2D g2d) {
73         this.g2d = g2d;
74         this.textureDrawingTools.setGraphics(g2d);
75     }
76
77     public void setAntialiased(boolean aa) {
78         if (aa) {
79             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
80         } else {
81             g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
82         }
83     }
84
85     public boolean is2DView() {
86         return canvas.getMainDrawer().is2DView();
87     }
88
89     public void setClippingPlanes(List<ClippingPlane> clippingPlanes) {
90         Scene.setClippingPlanes(clippingPlanes);
91     }
92
93     /**
94      * Set the face culling mode
95      * @param mode the mode to set
96      */
97     public void setFaceCullingMode(FaceCullingMode mode) {
98         this.mode = mode;
99     }
100
101     /**
102      * Set the current transformation
103      * @param transf the transformation to set
104      */
105     public void setTransformation(Transformation transf, Transformation single) {
106         this.transf = transf;
107         this.singleTransf = single;
108     }
109
110     public Transformation getCurrentTransformation() {
111         return transf;
112     }
113
114     public Transformation getCurrentSingleTransformation() {
115         return singleTransf;
116     }
117
118     /**
119      * Reset this motor
120      * @param color the filling color
121      */
122     public void reset(Color color) {
123         transf = null;
124         mode = FaceCullingMode.BOTH;
125         g2d.setColor(color);
126         g2d.fillRect(0, 0, (int) dim.getWidth(), (int) dim.getHeight());
127         Scene.clear();
128     }
129
130     /**
131      * Clear the depth buffer
132      */
133     public void clearDepth() {
134         Scene.clearDepth();
135     }
136
137     /**
138      * Draw the scene in the Graphics2D
139      */
140     public void draw() {
141         Scene.drawRoot(g2d);
142         Scene.clearAll();
143         G2DTextureManager.clear();
144     }
145
146     public void drawTexture(DrawingTools drawingTools, BufferedImage image, Texture texture) {
147         try {
148             SpritedRectangle o = new SpritedRectangle(new Vector3d(0, 0, 0), transf, image, texture.getMagnificationFilter());
149             add(o);
150         } catch (InvalidPolygonException e) { }
151     }
152
153     /**
154      * Add the geometry to the scene
155      * @param drawingTools the DrawingTools
156      * @param geometry the geometry to draw
157      * @param appearance the appearance to use
158      */
159     public void draw(DrawingTools drawingTools, Geometry geometry, Appearance appearance) {
160         setFaceCullingMode(geometry.getFaceCullingMode());
161         FloatBuffer vertexBuffer = geometry.getVertices().getData();
162
163         IntBuffer indicesBuffer = null;;
164         if (geometry.getIndices() != null) {
165             indicesBuffer = geometry.getIndices().getData();
166         }
167
168         IntBuffer wireIndicesBuffer = null;
169         if (geometry.getWireIndices() != null) {
170             wireIndicesBuffer = geometry.getWireIndices().getData();
171         }
172
173         FloatBuffer colorBuffer = null;
174         if (geometry.getColors() != null) {
175             colorBuffer = geometry.getColors().getData();
176         }
177
178         FloatBuffer normalBuffer = null;
179         if (geometry.getNormals() != null) {
180             normalBuffer = geometry.getNormals().getData();
181         }
182
183         Texture texture = appearance.getTexture();
184         FloatBuffer textureCoordinatesBuffer = null;
185         BufferedImage image = null;
186         if (texture != null && geometry.getTextureCoordinates() != null) {
187             textureCoordinatesBuffer = geometry.getTextureCoordinates().getData();
188             image = ((G2DTextureManager.G2DTexture) texture).getImage();
189         }
190
191         G2DLightManager lm = (G2DLightManager)drawingTools.getLightManager();
192         lm.setMaterial(appearance.getMaterial());
193
194         if (geometry.getFillDrawingMode() != Geometry.FillDrawingMode.NONE) {
195             addTriangles(vertexBuffer, normalBuffer, colorBuffer, appearance.getFillColor(), indicesBuffer, textureCoordinatesBuffer, image, texture, geometry.getFillDrawingMode(), lm);
196         }
197
198         if (geometry.getLineDrawingMode() != Geometry.LineDrawingMode.NONE) {
199             if (appearance.getLineColor() == null) {
200                 addSegments(vertexBuffer, colorBuffer, null, wireIndicesBuffer, geometry.getLineDrawingMode(), appearance);
201             } else {
202                 addSegments(vertexBuffer, null, appearance.getLineColor(), wireIndicesBuffer, geometry.getLineDrawingMode(), appearance);
203             }
204         }
205     }
206
207     public void draw(DrawingTools drawingTools, Texture texture, AnchorPosition anchor, ElementsBuffer positions, double rotationAngle) {
208         FloatBuffer positionsBuffer = positions.getData();
209         float[] buffer;
210
211         positionsBuffer.rewind();
212         if (positionsBuffer.hasArray()) {
213             buffer = positionsBuffer.array();
214         } else {
215             buffer = new float[positionsBuffer.limit()];
216             positionsBuffer.get(buffer);
217         }
218         Vector3d[] verticesArray = getMultiVectors(buffer, transf, false);
219
220         for (Vector3d v : verticesArray) {
221             try {
222                 SpritedRectangle o = new SpritedRectangle(v, texture, anchor, textureDrawingTools, rotationAngle);
223                 add(o);
224             } catch (InvalidPolygonException e) { }
225         }
226     }
227
228     public void draw(DrawingTools drawingTools, Texture texture, AnchorPosition anchor, Vector3d position, double rotationAngle) {
229         try {
230             add(new SpritedRectangle(transf.project(position), texture, anchor, textureDrawingTools, rotationAngle));
231         } catch (InvalidPolygonException e) { }
232     }
233
234     /**
235      * Add a Triangle to the scene
236      * @param tri the triangle to add
237      */
238     private void add(Triangle tri) {
239         Vector3d normal = tri.getNormal();
240         if (normal != null) {
241             //normal = transf.projectDirection(normal);
242             if ((mode == FaceCullingMode.CW && normal.getZ() > 0) || (mode == FaceCullingMode.CCW && normal.getZ() < 0) || mode == FaceCullingMode.BOTH) {
243                 Scene.addToRoot(is2DView(), tri);
244             }
245         } else {
246             Scene.addToRoot(is2DView(), tri);
247         }
248     }
249
250     /**
251      * Add a segment to the scene
252      * @param s the segment to add
253      */
254     private void add(Segment s) {
255         Scene.addToRoot(is2DView(), s);
256     }
257
258     private void add(SpritedRectangle sprite) {
259         Scene.addToRoot(is2DView(), sprite);
260     }
261
262     private void add(PolyLine p) {
263         Scene.addToRoot(is2DView(), p);
264     }
265
266     /**
267      * Get arrays from Buffer
268      * @param vertices a buffer containing vertices
269      * @param colors a buffer containing the colors
270      * @param defaultColor the color to use when colors is null
271      * @param indices a buffer containg the index of the vertices to retrieve
272      * @return an array of length 2 containing the vertices array and the colors array
273      */
274     private Object[] getArrays(FloatBuffer vertices, FloatBuffer colors, Color defaultColor, FloatBuffer textureCoords, IntBuffer indices) {
275         float[] buffer;
276         Vector3d[] verticesArray;
277         Vector3d[] textureCoordsArray = null;
278         Color[] colorsArray;
279
280         vertices.rewind();
281         if (vertices.hasArray()) {
282             buffer = vertices.array();
283         } else {
284             buffer = new float[vertices.limit()];
285             vertices.get(buffer);
286         }
287         verticesArray = getMultiVectors(buffer, transf, false);
288
289         if (colors != null) {
290             colors.rewind();
291             if (colors.hasArray()) {
292                 buffer = colors.array();
293             } else {
294                 buffer = new float[colors.limit()];
295                 colors.get(buffer);
296             }
297             colorsArray = getMultiColors(buffer);
298         } else {
299             colorsArray = new Color[vertices.limit() / G2DElementsBuffer.ELEMENT_SIZE];
300             Arrays.fill(colorsArray, defaultColor);
301         }
302
303         if (textureCoords != null) {
304             textureCoords.rewind();
305             if (textureCoords.hasArray()) {
306                 buffer = textureCoords.array();
307             } else {
308                 buffer = new float[textureCoords.limit()];
309                 textureCoords.get(buffer);
310             }
311             textureCoordsArray = getMultiVectors(buffer);
312         }
313
314         if (indices != null) {
315             indices.rewind();
316             int[] ind;
317             if (indices.hasArray()) {
318                 ind = indices.array();
319             } else {
320                 ind = new int[indices.limit()];
321                 indices.get(ind);
322             }
323             Vector3d[] va = new Vector3d[ind.length];
324             Color[] ca = new Color[ind.length];
325             Vector3d[] ta = null;
326             if (textureCoords != null) {
327                 ta = new Vector3d[ind.length];
328             }
329
330             for (int i = 0; i < ind.length; i++) {
331                 va[i] = verticesArray[ind[i]];
332                 ca[i] = colorsArray[ind[i]];
333                 if (ta != null) {
334                     ta[i] = textureCoordsArray[ind[i]];
335                 }
336             }
337             verticesArray = va;
338             colorsArray = ca;
339             textureCoordsArray = ta;
340         }
341
342         return new Object[] {verticesArray, colorsArray, textureCoordsArray};
343     }
344
345     /**
346      * Convert the buffer vertices into vertices array and put the segments in the scene
347      * @param vertices a buffer containing vertices
348      * @param colors a buffer containing the colors
349      * @param defaultColor the color to use when colors is null
350      * @param indices a buffer containg the index of the vertices to retrieve
351      * @param drawingMode the drawing mode
352      * @param stroke the Stroke to use to draw a segment
353      */
354     private void addSegments(FloatBuffer vertices, FloatBuffer colors, Color defaultColor, IntBuffer indices, Geometry.LineDrawingMode drawingMode, Appearance appearance) {
355         Object[] arrays = getArrays(vertices, colors, defaultColor, null, indices);
356         Vector3d[] verticesArray = (Vector3d[]) arrays[0];
357         Color[] colorsArray = (Color[]) arrays[1];
358         Vector3d[] v;
359         Color[] c;
360         double cumLength = 0;
361
362         if (verticesArray.length <= 1) {
363             return;
364         }
365
366         switch (drawingMode) {
367             case SEGMENTS_STRIP :
368                 if (is2DView()) {
369                     List<PolyLine> list = PolyLine.getPolyLines(verticesArray, colorsArray, G2DStroke.getStroke(appearance, 0), false);
370                     for (PolyLine p : list) {
371                         add(p);
372                     }
373                 } else {
374                     for (int i = 0; i < verticesArray.length - 1; i++) {
375                         v = new Vector3d[] {verticesArray[i], verticesArray[i + 1]};
376                         c = new Color[] {colorsArray[i], colorsArray[i + 1]};
377                         try {
378                             add(new Segment(v, c, G2DStroke.getStroke(appearance, cumLength), false));
379                             cumLength += Segment.getLength(v);
380                         } catch (InvalidPolygonException e) {
381                             cumLength = 0;
382                         }
383                     }
384                 }
385                 break;
386             case SEGMENTS_LOOP :
387                 if (is2DView()) {
388                     List<PolyLine> list = PolyLine.getPolyLines(verticesArray, colorsArray, G2DStroke.getStroke(appearance, 0), true);
389                     for (PolyLine p : list) {
390                         add(p);
391                     }
392                 } else {
393                     for (int i = 0; i < verticesArray.length - 1; i++) {
394                         v = new Vector3d[] {verticesArray[i], verticesArray[i + 1]};
395                         c = new Color[] {colorsArray[i], colorsArray[i + 1]};
396                         try {
397                             add(new Segment(v, c, G2DStroke.getStroke(appearance, cumLength), false));
398                             cumLength += Segment.getLength(v);
399                         } catch (InvalidPolygonException e) {
400                             cumLength = 0;
401                         }
402                     }
403                     int n = verticesArray.length - 1;
404                     v = new Vector3d[] {verticesArray[n], verticesArray[0]};
405                     c = new Color[] {colorsArray[n], colorsArray[0]};
406                     try {
407                         add(new Segment(v, c, G2DStroke.getStroke(appearance, cumLength), false));
408                     } catch (InvalidPolygonException e) { }
409                 }
410                 break;
411             case SEGMENTS :
412             default :
413                 for (int i = 0; i < verticesArray.length - 1; i += 2) {
414                     v = new Vector3d[] {verticesArray[i], verticesArray[i + 1]};
415                     c = new Color[] {colorsArray[i], colorsArray[i + 1]};
416                     try {
417                         add(new Segment(v, c, G2DStroke.getStroke(appearance, 0), is2DView()));
418                     } catch (InvalidPolygonException e) { }
419                 }
420                 break;
421         }
422     }
423
424     /**
425      * Convert the buffer vertices into vertices array and put the triangles in the scene
426      * @param vertices a buffer containing vertices
427      * @param normals a buffer containing the normals (not used)
428      * @param colors a buffer containing the colors
429      * @param defaultColor the color to use when colors is null
430      * @param indices a buffer containg the index of the vertices to retrieve
431      * @param drawingMode the drawing mode
432      */
433     private void addTriangles(FloatBuffer vertices, FloatBuffer normals, FloatBuffer colors, Color defaultColor, IntBuffer indices, FloatBuffer textureCoords, final BufferedImage image, Texture texture, Geometry.FillDrawingMode drawingMode, G2DLightManager lightManager) {
434         Object[] arrays = getArrays(vertices, colors, defaultColor, textureCoords, indices);
435         Vector3d[] verticesArray = (Vector3d[]) arrays[0];
436         Color[] colorsArray = (Color[]) arrays[1];
437         Vector3d[] textureCoordsArray = (Vector3d[]) arrays[2];
438         Vector3d[] v;
439         Color[] c;
440         Texture.Filter filter = Texture.Filter.NEAREST;
441
442         if (texture != null) {
443             filter = texture.getMagnificationFilter();
444         }
445
446         colorsArray = applyLighting(vertices, normals, indices, colorsArray, lightManager);
447
448         switch (drawingMode) {
449             case TRIANGLE_FAN :
450                 for (int i = 1; i < verticesArray.length - 1; i++) {
451                     v = new Vector3d[] {verticesArray[0], verticesArray[i], verticesArray[i + 1]};
452                     try {
453                         if (image == null) {
454                             c = new Color[] {colorsArray[0], colorsArray[i], colorsArray[i + 1]};
455                             add(new Triangle(v, c, null));
456                         } else {
457                             add(new Triangle(v, new Vector3d[] {textureCoordsArray[0], textureCoordsArray[i], textureCoordsArray[i + 1]}, image, filter));
458                         }
459                     } catch (InvalidPolygonException e) { }
460                 }
461                 int n = verticesArray.length - 1;
462                 v = new Vector3d[] {verticesArray[0], verticesArray[n], verticesArray[1]};
463                 try {
464                     if (image == null) {
465                         c = new Color[] {colorsArray[0], colorsArray[n], colorsArray[1]};
466                         add(new Triangle(v, c, null));
467                     } else {
468                         add(new Triangle(v, new Vector3d[] {textureCoordsArray[0], textureCoordsArray[n], textureCoordsArray[1]}, image, filter));
469                     }
470                 } catch (InvalidPolygonException e) { }
471                 break;
472             case TRIANGLE_STRIP :
473                 for (int i = 0; i < verticesArray.length - 2; i++) {
474                     v = new Vector3d[] {verticesArray[i], verticesArray[i + 1], verticesArray[i + 2]};
475                     try {
476                         if (image == null) {
477                             c = new Color[] {colorsArray[i], colorsArray[i + 1], colorsArray[i + 2]};
478                             add(new Triangle(v, c, null));
479                         } else {
480                             add(new Triangle(v, new Vector3d[] {textureCoordsArray[i], textureCoordsArray[i + 1], textureCoordsArray[i + 2]}, image, filter));
481                         }
482                     } catch (InvalidPolygonException e) { }
483                 }
484                 break;
485             case TRIANGLES :
486             default :
487                 for (int i = 0; i < verticesArray.length - 2; i += 3) {
488                     v = new Vector3d[] {verticesArray[i], verticesArray[i + 1], verticesArray[i + 2]};
489                     try {
490                         if (image == null) {
491                             c = new Color[] {colorsArray[i], colorsArray[i + 1], colorsArray[i + 2]};
492                             add(new Triangle(v, c, null));
493                         } else {
494                             add(new Triangle(v, new Vector3d[] {textureCoordsArray[i], textureCoordsArray[i + 1], textureCoordsArray[i + 2]}, image, filter));
495                         }
496                     } catch (InvalidPolygonException e) { }
497                 }
498                 break;
499         }
500     }
501
502     /**
503      * Convert an array of float into an array of Vector3d objects
504      * @param vertices an array of float containing (vertices.length / G2DElementsBuffer.ELEMENT_SIZE) vectors coordinates
505      * @param t the transformation to use for the projection
506      * @param dir if true t.projectDirection() is used rather than t.project()
507      * @return an array of Vector3d containg the vertices
508      */
509     private static final Vector3d[] getMultiVectors(final float[] vertices, final Transformation t, final boolean dir) {
510         Vector3d[] v = new Vector3d[vertices.length / G2DElementsBuffer.ELEMENT_SIZE];
511         if (dir) {
512             int j = 0;
513             for (int i = 0; i < v.length; i++) {
514                 v[i] = t.projectDirection(new Vector3d(vertices[j], vertices[j + 1], vertices[j + 2]));
515                 j += G2DElementsBuffer.ELEMENT_SIZE;
516             }
517         } else {
518             int j = 0;
519             for (int i = 0; i < v.length; i++) {
520                 v[i] = t.project(new Vector3d(vertices[j], vertices[j + 1], vertices[j + 2]));
521                 j += G2DElementsBuffer.ELEMENT_SIZE;
522             }
523         }
524
525         return v;
526     }
527
528     /**
529      * Convert an array of float into an array of Vector3d objects
530      * @param vertices an array of float containing (vertices.length / G2DElementsBuffer.ELEMENT_SIZE) vectors coordinates
531      * @return an array of Vector3d containg the vertices
532      */
533     private static final Vector3d[] getMultiVectors(final float[] vertices) {
534         Vector3d[] v = new Vector3d[vertices.length / G2DElementsBuffer.ELEMENT_SIZE];
535         int j = 0;
536         for (int i = 0; i < v.length; i++) {
537             v[i] = new Vector3d(vertices[j], vertices[j + 1], vertices[j + 2]);
538             j += G2DElementsBuffer.ELEMENT_SIZE;
539         }
540
541         return v;
542     }
543
544     /**
545      * Convert a float array into a Color array
546      * @param colors a float array
547      * @return an array of Color
548      */
549     private static final Color[] getMultiColors(final float[] colors) {
550         Color[] c = new Color[colors.length / G2DElementsBuffer.ELEMENT_SIZE];
551         int j = 0;
552         Color prev = Color.BLACK;
553         for (int i = 0; i < c.length; i++) {
554             c[i] = new Color(colors[j], colors[j + 1], colors[j + 2], colors[j + 3]);
555             if (prev.equals(c[i])) {
556                 c[i] = prev;
557             }
558             prev = c[i];
559             j += G2DElementsBuffer.ELEMENT_SIZE;
560         }
561
562         return c;
563     }
564
565     /**
566      * Perform per-vertex lighting
567      */
568     private Color[] applyLighting(FloatBuffer vertices, FloatBuffer normals, IntBuffer index, Color[] colors, G2DLightManager lightManager) {
569
570         if (!lightManager.isLightningEnable() || vertices == null || normals == null
571                 || index == null || colors == null) {
572             return colors;
573         }
574
575         Material mat = lightManager.getMaterial();
576         if (mat == null) {
577             return colors;
578         }
579
580         float[] vertexTransf = lightManager.getVertexTransform();
581         float[] normalTransf = lightManager.getNormalTransform();
582         //for transformed vertices camera is at origin.
583         Vector3f camera = new Vector3f(0.f, 0.f , 0.f);
584         Vector3f[] vertexArray = LightHelper.getIndexedVector3f(vertices, index, G2DElementsBuffer.ELEMENT_SIZE, vertexTransf);
585         Vector3f[] normalArray = LightHelper.getIndexedVector3f(normals, index, G2DElementsBuffer.ELEMENT_SIZE, normalTransf);
586
587         for (int i = 0; i < normalArray.length; ++i) {
588             normalArray[i] = normalArray[i].getNormalized();
589         }
590
591
592         Color[] outColors = new Color[colors.length];
593         boolean first = true;
594         for (int i = 0; i < lightManager.getLightNumber(); ++i) {
595             G2DLight l = (G2DLight)lightManager.getLight(i);
596
597             if (l == null || !l.isEnable()) {
598                 continue;
599             }
600
601             outColors = LightHelper.applyLight(l, mat, camera, vertexArray, normalArray, colors, outColors, vertexTransf, !first);
602             first = false;
603         }
604         return outColors;
605     }
606 }