2 * Aukio 3D engine. Author: Svjatoslav Agejenko.
3 * This project is released under Creative Commons Zero (CC0) license.
5 package eu.svjatoslav.aukio.e3d.renderer.raster.shapes.composite;
7 import eu.svjatoslav.aukio.e3d.geometry.Point3D;
8 import eu.svjatoslav.aukio.e3d.renderer.raster.gi.LightmappedTriangle;
9 import eu.svjatoslav.aukio.e3d.renderer.raster.shapes.AbstractCoordinateShape;
10 import eu.svjatoslav.aukio.e3d.renderer.raster.shapes.AbstractShape;
11 import eu.svjatoslav.aukio.e3d.renderer.raster.shapes.basic.solidpolygon.SolidPolygon;
12 import eu.svjatoslav.aukio.e3d.renderer.raster.shapes.composite.base.AbstractCompositeShape;
14 import java.util.ArrayList;
15 import java.util.List;
18 * Composite shape whose solid polygons can render as lightmapped
19 * triangles for the global illumination system.
21 * <p><b>Lightmapping:</b> when enabled, every {@link SolidPolygon}
22 * (including those inside nested composites, which are flattened) is
23 * wrapped as a {@link LightmappedTriangle} with its own lightmap texture,
24 * and the GI system paints light across its surface instead of leaving
25 * it a single flat color. Rebuild is automatic via the normal
26 * cache-invalidation path.</p>
28 * <p><b>Ordering:</b> none required — the engine's z-buffer resolves
29 * visibility per pixel, so this class needs no spatial ordering
30 * structure of its own. Nested composites are flattened assuming
31 * identity transforms (vertices are used in the composite's local
32 * space); animate transforms above this composite, not inside it.</p>
34 * @see LightmappedTriangle the per-fragment lightmap carrier
35 * @see AbstractCompositeShape the base composite (fan-triangulates
36 * N-vertex polygons when building the render list)
38 public class LightmappedCompositeShape extends AbstractCompositeShape {
41 * When true, the composite's polygons render as
42 * {@link LightmappedTriangle}s: each polygon gets its own lightmap
43 * texture and the GI system paints light across its surface.
45 private boolean lightmappingEnabled;
47 /** World units per lightmap texel (smaller = finer GI detail). */
48 private double lightmapUnitsPerTexel = 12.0;
51 * Replaces solid polygons with lightmapped wrappers when lightmapping
52 * is enabled; otherwise returns the list unchanged.
54 * @param renderList the render list built from the shape registry
55 * @return lightmapped wrappers + non-polygon passthrough shapes
58 protected List<AbstractShape> postprocessRenderList(final List<AbstractShape> renderList) {
59 if (!lightmappingEnabled)
62 final List<AbstractShape> result = new ArrayList<>(renderList.size());
63 for (final AbstractShape shape : renderList) {
64 if (shape instanceof SolidPolygon) {
65 wrapLightmapped((SolidPolygon) shape, result);
66 } else if (shape instanceof AbstractCompositeShape) {
67 // Flatten nested composites. Assumes identity transforms
68 // on the nested composite chain.
69 for (final SolidPolygon polygon
70 : ((AbstractCompositeShape) shape).extractSolidPolygons())
71 wrapLightmapped(polygon, result);
80 * Enables or disables lightmapping. When enabled, the composite's
81 * polygons render as {@link LightmappedTriangle}s with per-polygon
82 * lightmap textures filled by the global illumination system.
83 * Rebuilds the render list on the next frame.
85 * @param enabled true to render polygons lightmapped
87 public void setLightmappingEnabled(final boolean enabled) {
88 if (lightmappingEnabled != enabled) {
89 lightmappingEnabled = enabled;
90 setCacheNeedsRebuild(true);
95 * Returns whether lightmapping is enabled.
97 * @return true when polygons render as lightmapped triangles
99 public boolean isLightmappingEnabled() {
100 return lightmappingEnabled;
104 * Sets the lightmap resolution. Default 12 world units per texel:
105 * a 100-unit wall cell gets an 8x8 lightmap. Halving the units
106 * quadruples the GI tracing work; finer texels are the ONLY way to
107 * smoother shadow edges (there is no upsampling). Takes effect on the
108 * next render list rebuild.
110 * @param unitsPerTexel world units per texel
112 public void setLightmapUnitsPerTexel(final double unitsPerTexel) {
113 lightmapUnitsPerTexel = unitsPerTexel;
114 setCacheNeedsRebuild(true);
118 * Fan-triangulates a polygon (triangles pass through as-is) and wraps
119 * each triangle into a lightmapped textured triangle with the same
120 * geometry, color and culling. The initial texture is dim; the GI
121 * system converges it to full lighting within seconds.
123 * @param polygon the polygon to wrap (any vertex count >= 3)
124 * @param out the list receiving the lightmapped triangles
126 private void wrapLightmapped(final SolidPolygon polygon, final List<AbstractShape> out) {
127 final int vertexCount = polygon.getVertexCount();
128 if (vertexCount == 3) {
129 out.add(wrapTriangle(polygon));
132 // Fan: anchor vertex 0, then consecutive pairs
133 final Point3D anchor = polygon.vertices.get(0).coordinate;
134 for (int i = 1; i + 1 < vertexCount; i++) {
135 final SolidPolygon triangle = new SolidPolygon(
137 polygon.vertices.get(i).coordinate,
138 polygon.vertices.get(i + 1).coordinate,
140 triangle.setShadingEnabled(polygon.isShadingEnabled());
141 triangle.setBackfaceCulling(polygon.isBackfaceCullingEnabled());
142 triangle.setMouseInteractionController(polygon.mouseInteractionController);
143 out.add(wrapTriangle(triangle));
148 * Wraps one triangle into a lightmapped textured triangle with the
149 * same geometry, color and culling.
151 private AbstractCoordinateShape wrapTriangle(final SolidPolygon polygon) {
152 final Point3D a = polygon.vertices.get(0).coordinate;
153 final Point3D b = polygon.vertices.get(1).coordinate;
154 final Point3D c = polygon.vertices.get(2).coordinate;
156 // Normal: right-handed cross(b-a, c-a).
157 final double e1x = b.x - a.x, e1y = b.y - a.y, e1z = b.z - a.z;
158 final double e2x = c.x - a.x, e2y = c.y - a.y, e2z = c.z - a.z;
159 double nx = e1y * e2z - e1z * e2y;
160 double ny = e1z * e2x - e1x * e2z;
161 double nz = e1x * e2y - e1y * e2x;
162 final double len = Math.sqrt(nx * nx + ny * ny + nz * nz);
169 final LightmappedTriangle triangle = new LightmappedTriangle(
170 a, b, c, polygon.getColor(), lightmapUnitsPerTexel,
172 triangle.setBackfaceCulling(polygon.isBackfaceCullingEnabled());
173 triangle.setMouseInteractionController(polygon.mouseInteractionController);