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