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