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