184d57a7e77a68649e2a7c1d78758db93b8ed577
[sixth-3d.git] / src / main / java / eu / svjatoslav / sixth / e3d / renderer / raster / shapes / composite / base / AbstractCompositeShape.java
1 /*
2  * Sixth 3D engine. Author: Svjatoslav Agejenko. 
3  * This project is released under Creative Commons Zero (CC0) license.
4  */
5 package eu.svjatoslav.sixth.e3d.renderer.raster.shapes.composite.base;
6
7 import eu.svjatoslav.sixth.e3d.geometry.Point3D;
8 import eu.svjatoslav.sixth.e3d.gui.RenderingContext;
9 import eu.svjatoslav.sixth.e3d.gui.UserRelativityTracker;
10 import eu.svjatoslav.sixth.e3d.gui.humaninput.MouseInteractionController;
11 import eu.svjatoslav.sixth.e3d.math.Transform;
12 import eu.svjatoslav.sixth.e3d.math.TransformsPipeline;
13 import eu.svjatoslav.sixth.e3d.renderer.raster.Color;
14 import eu.svjatoslav.sixth.e3d.renderer.raster.RenderAggregator;
15 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.AbstractShape;
16 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.line.Line;
17 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon;
18 import eu.svjatoslav.sixth.e3d.renderer.raster.shapes.basic.texturedpolygon.TexturedPolygon;
19 import eu.svjatoslav.sixth.e3d.renderer.raster.slicer.Slicer;
20
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.stream.Collectors;
24
25 /**
26  * In order to get perspective correct textures, large textured polygons are
27  * sliced into smaller ones.
28  */
29
30 public class AbstractCompositeShape extends AbstractShape {
31     private final List<SubShape> originalSubShapes = new ArrayList<>();
32     private final UserRelativityTracker relativityTracker;
33     double currentSliceFactor = 5;
34     private List<AbstractShape> renderedSubShapes = new ArrayList<>();
35     private boolean slicingOutdated = true;
36     private Transform transform;
37
38     public AbstractCompositeShape() {
39         this(new Transform());
40     }
41
42     public AbstractCompositeShape(final Point3D location) {
43         this(new Transform(location));
44     }
45
46     public AbstractCompositeShape(final Transform transform) {
47         this.transform = transform;
48         relativityTracker = new UserRelativityTracker();
49     }
50
51     public void addShape(final AbstractShape shape) {
52         addShape(shape, null);
53     }
54
55     public void addShape(final AbstractShape shape, final String groupId) {
56         final SubShape subShape = new SubShape(shape);
57         subShape.setGroup(groupId);
58         subShape.setVisible(true);
59         originalSubShapes.add(subShape);
60         slicingOutdated = true;
61     }
62
63     /**
64      * This method should be overridden by anyone wanting to customize shape
65      * before it is rendered.
66      */
67     public void beforeTransformHook(final TransformsPipeline transformPipe,
68                                     final RenderingContext context) {
69     }
70
71     public Point3D getLocation() {
72         return transform.getTranslation();
73     }
74
75     public List<SubShape> getOriginalSubShapes() {
76         return originalSubShapes;
77     }
78
79     public UserRelativityTracker getRelativityTracker() {
80         return relativityTracker;
81     }
82
83     public void hideGroup(final String groupIdentifier) {
84         originalSubShapes.stream().filter(subShape -> subShape.matchesGroup(groupIdentifier)).forEach(subShape -> {
85             subShape.setVisible(false);
86             slicingOutdated = true;
87         });
88     }
89
90     private boolean isReslicingNeeded(double sliceFactor1, double sliceFactor2) {
91
92         if (slicingOutdated)
93             return true;
94
95         if (sliceFactor1 > sliceFactor2) {
96             final double tmp = sliceFactor1;
97             sliceFactor1 = sliceFactor2;
98             sliceFactor2 = tmp;
99         }
100
101         return (sliceFactor2 / sliceFactor1) > 1.5d;
102
103     }
104
105     public void removeGroup(final String groupIdentifier) {
106         final java.util.Iterator<SubShape> iterator = originalSubShapes
107                 .iterator();
108
109         while (iterator.hasNext()) {
110             final SubShape subShape = iterator.next();
111             if (subShape.matchesGroup(groupIdentifier)) {
112                 iterator.remove();
113                 slicingOutdated = true;
114             }
115         }
116     }
117
118     public List<SubShape> getGroup(final String groupIdentifier) {
119         return originalSubShapes.stream().filter(
120                 subShape -> subShape.matchesGroup(groupIdentifier))
121                 .collect(Collectors.toList());
122     }
123
124     private void resliceIfNeeded() {
125
126         final double proposedSliceFactor = relativityTracker
127                 .proposeSliceFactor();
128
129         if (isReslicingNeeded(proposedSliceFactor, currentSliceFactor)) {
130             currentSliceFactor = proposedSliceFactor;
131             slice();
132         }
133     }
134
135     /**
136      * Paint solid elements of this composite shape into given color.
137      */
138     public void setColor(final Color color) {
139         for (final SubShape subShape : getOriginalSubShapes()) {
140             final AbstractShape shape = subShape.getShape();
141
142             if (shape instanceof SolidPolygon)
143                 ((SolidPolygon) shape).setColor(color);
144
145             if (shape instanceof Line)
146                 ((Line) shape).color = color;
147         }
148     }
149
150     public void setGroupForUngrouped(final String groupIdentifier) {
151         originalSubShapes.stream().filter(SubShape::isUngrouped).forEach(subShape -> subShape.setGroup(groupIdentifier));
152     }
153
154     @Override
155     public void setMouseInteractionController(
156             final MouseInteractionController mouseInteractionController) {
157         super.setMouseInteractionController(mouseInteractionController);
158
159         for (final SubShape subShape : originalSubShapes)
160             subShape.getShape().setMouseInteractionController(
161                     mouseInteractionController);
162
163         slicingOutdated = true;
164
165     }
166
167     public void setTransform(final Transform transform) {
168         this.transform = transform;
169     }
170
171     public void showGroup(final String groupIdentifier) {
172         originalSubShapes.stream().filter(subShape -> subShape.matchesGroup(groupIdentifier)).forEach(subShape -> {
173             subShape.setVisible(true);
174             slicingOutdated = true;
175         });
176     }
177
178     private void slice() {
179         slicingOutdated = false;
180
181         final List<AbstractShape> result = new ArrayList<>();
182
183         final Slicer slicer = new Slicer(currentSliceFactor);
184         originalSubShapes.stream().filter(subShape -> subShape.isVisible()).forEach(subShape -> {
185             if (subShape.getShape() instanceof TexturedPolygon)
186                 slicer.slice((TexturedPolygon) subShape.getShape());
187             else
188                 result.add(subShape.getShape());
189         });
190
191         result.addAll(slicer.getResult());
192
193         renderedSubShapes = result;
194     }
195
196     @Override
197     public void transform(final TransformsPipeline transformPipe,
198                           final RenderAggregator aggregator, final RenderingContext context) {
199
200         // add current composite shape transform to the end of the transform
201         // pipeline
202         transformPipe.addTransform(transform);
203
204         relativityTracker.analyze(transformPipe, context);
205
206         beforeTransformHook(transformPipe, context);
207
208         // hack, to get somewhat perspective correct textures
209         resliceIfNeeded();
210
211         // transform rendered subshapes
212         for (final AbstractShape shape : renderedSubShapes)
213             shape.transform(transformPipe, aggregator, context);
214
215         transformPipe.dropTransform();
216     }
217
218 }