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