diff --git a/docs/usage/parameters/TileTasks.md b/docs/usage/parameters/TileTasks.md index f2cf21340..14b22b155 100644 --- a/docs/usage/parameters/TileTasks.md +++ b/docs/usage/parameters/TileTasks.md @@ -183,14 +183,19 @@ place: default:stone ### `renderSurfaces` -Renders 2-D shapes surfaces with [placeables](Placeables.md) on a given [heightmap](Heightmaps.md). +Renders 2-D shapes surfaces with [placeables](Placeables.md) on a given [heightmap](Heightmaps.md) and/or at an altitude given by model. #### Extra parameters - `models`: [Selection of models](ModelSelection.md) to render (required, models must be convertible to 2d shapes) -- `heightmap`: [Heightmap](Heightmaps.md) to use (required) +- `heightmap`: [Heightmap](Heightmaps.md) to use (optional, default 0) +- `altitudeOffset`: A [value](ModelValues.md) added to altitude (optional, default 0) - `place`: [Placeable](Placeables.md) to place on each voxel of shapes (required) +If both `heightmap` and `altitudeOffset` are specified, the resulting placement altitude will be the addition of both. + +If both `heightmap` and `altitudeOffset` are omitted, due to default values, flat shape will be rendered at altitude 0. + #### Examples Draw "building" shapes with cobble on ground: @@ -289,8 +294,8 @@ Layouts are tried in order, the first one that can fit the requested space is us #### Extra parameters - `models`: [Selection of models](ModelSelection.md) to render (required, models must be convertible to 2d shapes) -- `height` (string): Name of the metadata containing the desired height. (required) -- `altitude` (string): Name of the metadata containing the altitude. (required) +- `height`: [Value](ModelValues.md) of walls height. (required) +- `altitude`: [Value](ModelValues.md) of walls base altitude. (required) - `build`: List of [layouts](Layouts.md) to try. Default axis policies are `ADJUST` on `X` and `Z`, `KEEP` on `Y`. See [Layouts](Layouts.md) for more information about layouts @@ -300,8 +305,9 @@ See [Layouts](Layouts.md) for more information about layouts ```yaml type: renderFacades models: buildings -height: height -altitude: ground-floor-altitude +height: 20 +altitude: + metadata: ground-floor-altitude build: - structure: at: [ 0, -1..0, 0 ] diff --git a/examples/processes/full.yaml b/examples/processes/full.yaml index 57ee5951f..99ce7b451 100644 --- a/examples/processes/full.yaml +++ b/examples/processes/full.yaml @@ -118,20 +118,16 @@ forEachTile: placeAbove: *voxel-air placeBelow: *voxel-building-ground - # Hacky way to render roofs. Should be removed. - # Would have been nice to have a fillBetweenMetadatas renderRoofs: after: flattenGroundBuildings - type: renderBuildings + type: renderSurfaces models: type: buildings - filter: - value: - metadata: height - greaterThan: 0 - roof: *voxel-cobble - wall: *voxel-air - window: *voxel-air + altitudeOffset: + sum: + - metadata: ground-floor-altitude + - metadata: height + place: *voxel-cobble forest: type: sequence @@ -313,8 +309,12 @@ forEachTile: filter: metadata: usage_1 equals: Résidentiel - height: height - altitude: ground-floor-altitude + height: + metadata: height + altitude: + sum: + - metadata: ground-floor-altitude + - 1 # TODO: Layout should probably be defined on a common.yaml file in format. build: - along: x @@ -405,8 +405,12 @@ forEachTile: not: metadata: usage_1 equals: Résidentiel - height: height - altitude: ground-floor-altitude + height: + metadata: height + altitude: + sum: + - metadata: ground-floor-altitude + - 1 # TODO: Layout should probably be defined on a common.yaml file in format. build: - along: x diff --git a/src/main/java/fr/ign/voxatile/core/Voxatile.java b/src/main/java/fr/ign/voxatile/core/Voxatile.java index 9b30b8251..eca756af5 100644 --- a/src/main/java/fr/ign/voxatile/core/Voxatile.java +++ b/src/main/java/fr/ign/voxatile/core/Voxatile.java @@ -41,7 +41,6 @@ import fr.ign.voxatile.core.parameters.tasks.HeightmapStatsTaskParams; import fr.ign.voxatile.core.parameters.tasks.NoOperationTaskParams; import fr.ign.voxatile.core.parameters.tasks.PopulateHeightmapTaskParams; -import fr.ign.voxatile.core.parameters.tasks.RenderBuildingsTaskParams; import fr.ign.voxatile.core.parameters.tasks.RenderFacadesTaskParams; import fr.ign.voxatile.core.parameters.tasks.RenderHeightmapTaskParams; import fr.ign.voxatile.core.parameters.tasks.RenderLines2dTaskParams; @@ -128,7 +127,6 @@ public static void main(String[] args) throws FactoryException, InterruptedExcep parser.registerParams("fetchData", FetchDataTaskParams.class); parser.registerParams("fillBetweenHeightmapAndValue", FillBetweenHeightmapAndValueTaskParams.class); parser.registerParams("populateHeightmap", PopulateHeightmapTaskParams.class); - parser.registerParams("renderBuildings", RenderBuildingsTaskParams.class); parser.registerParams("renderHeightmap", RenderHeightmapTaskParams.class); parser.registerParams("renderSurfaces", RenderSurfacesTaskParams.class); parser.registerParams("renderLines", RenderLinesTaskParams.class); diff --git a/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderBuildingsTaskParams.java b/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderBuildingsTaskParams.java deleted file mode 100644 index 4dd70d6f1..000000000 --- a/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderBuildingsTaskParams.java +++ /dev/null @@ -1,72 +0,0 @@ -package fr.ign.voxatile.core.parameters.tasks; - -import java.beans.ConstructorProperties; - -import com.fasterxml.jackson.annotation.JsonSetter; -import com.fasterxml.jackson.annotation.Nulls; - -import fr.ign.voxatile.core.generation.Generation; -import fr.ign.voxatile.core.parameters.placeables.PlaceableParams; -import fr.ign.voxatile.core.tasks.RenderBuildingsTask; -import fr.ign.voxatile.core.tasks.TileTask; - -/** - * Parameters for creating a {@link RenderBuildingsTask}. - */ -public class RenderBuildingsTaskParams extends ModelTaskParams { - - /** - * {@code Placeable} used to render the roof of the models (required). - */ - @JsonSetter(nulls = Nulls.FAIL) - public PlaceableParams roof; - - /** - * {@code Placeable} used to render walls of the models (required). - */ - @JsonSetter(nulls = Nulls.FAIL) - public PlaceableParams wall; - - /** - * {@code Placeable} used to render windows of the models (required). - */ - @JsonSetter(nulls = Nulls.FAIL) - public PlaceableParams window; - - /** - * Constructor used to ensure that the required fields are present during - * deserialization. - * - * @param roof {@code Placeable} for roofs - * @param wall {@code Placeable} for walls - * @param window {@code Placeable} for windows - */ - @ConstructorProperties({ "roof", "wall", "window" }) - public RenderBuildingsTaskParams( - PlaceableParams roof, - PlaceableParams wall, - PlaceableParams window - ) { - this.roof = roof; - this.wall = wall; - this.window = window; - } - - @Override - public void validate() throws IllegalArgumentException { - super.validate(); - roof.validate(); - wall.validate(); - window.validate(); - } - - @Override - public TileTask create(Generation generation) { - return new RenderBuildingsTask( - models.create(generation), - roof.create(generation.seed()), - wall.create(generation.seed()), - window.create(generation.seed()) - ); - } -} diff --git a/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderFacadesTaskParams.java b/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderFacadesTaskParams.java index 34a3cc9e6..52216bc20 100644 --- a/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderFacadesTaskParams.java +++ b/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderFacadesTaskParams.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.Nulls; import fr.ign.voxatile.core.generation.Generation; +import fr.ign.voxatile.core.parameters.models.values.ModelValueParams; import fr.ign.voxatile.core.parameters.placeables.layouts.LayoutBuilderParams; import fr.ign.voxatile.core.placeables.layouts.UnbuildableLayoutException; import fr.ign.voxatile.core.tasks.RenderFacadesTask; @@ -30,28 +31,28 @@ public class RenderFacadesTaskParams extends ModelTaskParams { public List build; /** - * Name of metadata containing building height (from wall bottom to wall top). + * Building height (from wall bottom to wall top). */ @JsonSetter(nulls = Nulls.FAIL) - public String height; + public ModelValueParams height; /** - * Name of metadata containing building altitude (altitude of wall bottom). + * Building base altitude (altitude of wall bottom). */ @JsonSetter(nulls = Nulls.FAIL) - public String altitude; + public ModelValueParams altitude; /** - * Creates a new {@code RenderFacadeTaskParams} out of mandatory parameters. + * Creates a new {@code RenderFacadesTaskParams} out of mandatory parameters. * @param build builders to try, in order - * @param height name of metadata containing building height - * @param altitude name of metadata containing building altitude + * @param height building height + * @param altitude building base altitude (altitude of walls bottom) */ @ConstructorProperties({ "build", "height", "altitude"}) public RenderFacadesTaskParams( List build, - String height, - String altitude + ModelValueParams height, + ModelValueParams altitude ) { this.build = build; this.height = height; @@ -61,10 +62,8 @@ public RenderFacadesTaskParams( @Override public void validate() { super.validate(); - if (height.isBlank()) - throw new IllegalArgumentException("Height metadata cannot be blank"); - if (altitude.isBlank()) - throw new IllegalArgumentException("Altitude metadata cannot be blank"); + height.validate(); + altitude.validate(); build.forEach(LayoutBuilderParams::validate); } @@ -83,8 +82,8 @@ public TileTask create(Generation generation) { throw new IllegalArgumentException(e); } }).toList(), - height, - altitude + height.create(generation), + altitude.create(generation) ); } } diff --git a/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderSurfacesTaskParams.java b/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderSurfacesTaskParams.java index 74a44eb45..b5523ae69 100644 --- a/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderSurfacesTaskParams.java +++ b/src/main/java/fr/ign/voxatile/core/parameters/tasks/RenderSurfacesTaskParams.java @@ -6,7 +6,10 @@ import com.fasterxml.jackson.annotation.Nulls; import fr.ign.voxatile.core.generation.Generation; +import fr.ign.voxatile.core.parameters.heightmaps.ConstantHeightmapParams; import fr.ign.voxatile.core.parameters.heightmaps.ReadableHeightmapParams; +import fr.ign.voxatile.core.parameters.models.values.AbsentValueParams; +import fr.ign.voxatile.core.parameters.models.values.ModelValueParams; import fr.ign.voxatile.core.parameters.placeables.PlaceableParams; import fr.ign.voxatile.core.tasks.RenderSurfacesTask; import fr.ign.voxatile.core.tasks.TileTask; @@ -16,10 +19,17 @@ */ public class RenderSurfacesTaskParams extends ModelTaskParams { /** - * Heightmap to render on (required). + * Heightmap to render on (optional default 0). */ - @JsonSetter(nulls = Nulls.FAIL) - public ReadableHeightmapParams heightmap; + @JsonSetter(nulls = Nulls.SKIP) + public ReadableHeightmapParams heightmap = new ConstantHeightmapParams(0); + + /** + * Altitude offset to add (optional default 0). + */ + @JsonSetter(nulls = Nulls.SKIP) + public ModelValueParams altitudeOffset = new AbsentValueParams(); + /** * What to place on surface (required). */ @@ -29,12 +39,10 @@ public class RenderSurfacesTaskParams extends ModelTaskParams { /** * Constructor used to ensure that the required fields are present during deserialization. * - * @param heightmap heightmap to render on. * @param place what to place on surface. */ - @ConstructorProperties({ "heightmap", "place"}) - public RenderSurfacesTaskParams(ReadableHeightmapParams heightmap, PlaceableParams place) { - this.heightmap = heightmap; + @ConstructorProperties({ "place" }) + public RenderSurfacesTaskParams(PlaceableParams place) { this.place = place; } @@ -42,6 +50,7 @@ public RenderSurfacesTaskParams(ReadableHeightmapParams heightmap, PlaceablePara public void validate() throws IllegalArgumentException { super.validate(); heightmap.validate(); + altitudeOffset.validate(); place.validate(); } @@ -50,6 +59,7 @@ public TileTask create(Generation generation) { return new RenderSurfacesTask( models.create(generation), heightmap.create(generation.heightmaps()), + altitudeOffset.create(generation), place.create(generation.seed()) ); } diff --git a/src/main/java/fr/ign/voxatile/core/tasks/RenderBuildingsTask.java b/src/main/java/fr/ign/voxatile/core/tasks/RenderBuildingsTask.java deleted file mode 100644 index 004bf6f14..000000000 --- a/src/main/java/fr/ign/voxatile/core/tasks/RenderBuildingsTask.java +++ /dev/null @@ -1,78 +0,0 @@ -package fr.ign.voxatile.core.tasks; - -import fr.ign.voxatile.core.generation.GenerationTile; -import fr.ign.voxatile.core.models.ModelSelection; -import fr.ign.voxatile.core.models.Shape2dConvertibleModel; -import fr.ign.voxatile.core.placeables.Placeable; -import fr.ign.voxatile.core.utils.world2d.Positioned2d; -import fr.ign.voxatile.core.voxelization.shape2d.voxelizer.Shape2dVoxelizer; -import fr.ign.voxatile.core.voxelization.shape2d.voxelizer.SurfaceVoxelizer2d; -import fr.ign.voxatile.core.voxelization.shape2d.voxelizer.ThinLinearVoxelizer2d; - -/** - * A {@link TileTask} rendering a {@link ModelSelection} as buildings. - * - * The generated buildings include a custom height, walls, - * windows and a roof. - */ -public class RenderBuildingsTask extends ModelTask { - private final Placeable roof; - private final Placeable wall; - private final Placeable window; - - private final Shape2dVoxelizer surfaceVoxelizer = new SurfaceVoxelizer2d(); - private final ThinLinearVoxelizer2d borderVoxelizer = new ThinLinearVoxelizer2d(); - - /** - * Creates a new {@code RenderBuildingsTask}. - * - * @param selection building models selection - * @param roof {@code Placeable} for roofs - * @param wall {@code Placeable} for walls - * @param window {@code Placeable} for windows - */ - public RenderBuildingsTask( - ModelSelection selection, - Placeable roof, - Placeable wall, - Placeable window - ) { - super(Shape2dConvertibleModel.class, selection); - this.roof = roof; - this.wall = wall; - this.window = window; - } - - @Override - protected void run(Shape2dConvertibleModel model, GenerationTile tile) { - // Metadata required to render a model as buildings - // Obtain them by executing the 'computeHeightmapStats' task - // - // The metadata is hardcoded because this renderer will be deleted - Integer zMinFoundation = model.getMetadata("minimum-ground-altitude"); - Integer zMaxFoundation = model.getMetadata("ground-floor-altitude"); - if (zMinFoundation == null || zMaxFoundation == null) - return; - - if (!(model.getMetadata("height") instanceof Integer buildingHeight) || buildingHeight <= 0) - return; - - /* - The height of a building is calculated according to the largest side of its wall - in order not to overestimate the height of the building it is necessary - to subtract the height of the foundation from the height of the building - - The height of a building cannot be negative - */ - buildingHeight = Math.max(0, buildingHeight - (zMaxFoundation - zMinFoundation)); - - // Place the facade and enhance it with windows - for (Positioned2d voxel : tile.limits().to2d().filterInside(borderVoxelizer.voxelize(model))) - for (int z = zMaxFoundation + 1; z <= zMaxFoundation + buildingHeight; z++) // zMax + 1 to place walls/windows above the foundation - ((z % 4 == 0) ? window : wall).place(tile.voxels(), voxel.coords().x(), voxel.coords().y(), z); - - // Place the roof of the building - for (Positioned2d voxel : tile.limits().to2d().filterInside(surfaceVoxelizer.voxelize(model))) - roof.place(tile.voxels(), voxel.coords().x(), voxel.coords().y(), zMaxFoundation + buildingHeight); - } -} diff --git a/src/main/java/fr/ign/voxatile/core/tasks/RenderFacadesTask.java b/src/main/java/fr/ign/voxatile/core/tasks/RenderFacadesTask.java index 4eb688003..c835eee99 100644 --- a/src/main/java/fr/ign/voxatile/core/tasks/RenderFacadesTask.java +++ b/src/main/java/fr/ign/voxatile/core/tasks/RenderFacadesTask.java @@ -3,9 +3,11 @@ import java.util.LinkedList; import java.util.List; +import fr.ign.voxatile.core.exceptions.IgnorableException; import fr.ign.voxatile.core.generation.GenerationTile; import fr.ign.voxatile.core.models.ModelSelection; import fr.ign.voxatile.core.models.Shape2dConvertibleModel; +import fr.ign.voxatile.core.models.values.ModelValue; import fr.ign.voxatile.core.placeables.LayoutStructure; import fr.ign.voxatile.core.placeables.Structure; import fr.ign.voxatile.core.placeables.layouts.LayoutBuilder; @@ -22,8 +24,8 @@ public class RenderFacadesTask extends ModelTask { private final List builders; - private final String heightMetadata; - private final String baseAltitudeMetadata; + private final ModelValue heightValue; + private final ModelValue altitudeValue; private final ThickLinearIndexedVoxelizer2d voxelizer; /** @@ -31,19 +33,19 @@ public class RenderFacadesTask extends ModelTask { * * @param selection Selection of models to render * @param builders Layout builder to use to render facades - * @param heightMetadata Name of metadata holding building height - * @param baseAltitudeMetadata Name of metadata holding building base altitude (altitude of walls bottom) + * @param heightValue Building height + * @param altitudeValue Building base altitude (altitude of walls bottom) */ public RenderFacadesTask( ModelSelection selection, List builders, - String heightMetadata, - String baseAltitudeMetadata + ModelValue heightValue, + ModelValue altitudeValue ) { super(Shape2dConvertibleModel.class, selection); this.builders = builders; - this.heightMetadata = heightMetadata; - this.baseAltitudeMetadata = baseAltitudeMetadata; + this.heightValue = heightValue; + this.altitudeValue = altitudeValue; int thickness = 0; for (LayoutBuilder builder : builders) @@ -53,12 +55,12 @@ public RenderFacadesTask( } @Override - protected void run(Shape2dConvertibleModel model, GenerationTile tile) { + protected void run(Shape2dConvertibleModel model, GenerationTile tile) throws IgnorableException { - // Process metadata - Integer height = model.getMetadata(heightMetadata); - Integer baseAltitude = model.getMetadata(baseAltitudeMetadata); - if (height == null || baseAltitude == null || height <= 0) + int altitude = altitudeValue.getAsInt(model).orElseThrow(() -> new IgnorableException("Missing altitude")); + int height = heightValue.getAsInt(model).orElseThrow(() -> new IgnorableException("Missing height")); + + if (height <= 0) return; // Process each lineString (actually linearRing) separately @@ -98,10 +100,9 @@ protected void run(Shape2dConvertibleModel model, GenerationTile tile) { } catch (UnbuildableLayoutException ignored) {} } - if (structure == null) { - System.out.println("Could not build facade structure"); - return; - } + if (structure == null) + throw new IgnorableException("Could not build facade structure"); + structures.add(structure); } @@ -116,7 +117,7 @@ protected void run(Shape2dConvertibleModel model, GenerationTile tile) { y = (int) Math.round(index.distance()); for (int z = 0; z < height; z++) if (structure.limits().contains(x, y, z)) - structure.get(x, y, z).place(tile.voxels(), index.coords().x(), index.coords().y(), z + baseAltitude); + structure.get(x, y, z).place(tile.voxels(), index.coords().x(), index.coords().y(), z + altitude); } } } diff --git a/src/main/java/fr/ign/voxatile/core/tasks/RenderSurfacesTask.java b/src/main/java/fr/ign/voxatile/core/tasks/RenderSurfacesTask.java index 3297b55c1..12b191bf4 100644 --- a/src/main/java/fr/ign/voxatile/core/tasks/RenderSurfacesTask.java +++ b/src/main/java/fr/ign/voxatile/core/tasks/RenderSurfacesTask.java @@ -5,6 +5,7 @@ import fr.ign.voxatile.core.generation.heightmaps.ReadableHeightmapSpec; import fr.ign.voxatile.core.models.ModelSelection; import fr.ign.voxatile.core.models.Shape2dConvertibleModel; +import fr.ign.voxatile.core.models.values.ModelValue; import fr.ign.voxatile.core.placeables.Placeable; import fr.ign.voxatile.core.utils.world2d.Positioned2d; import fr.ign.voxatile.core.utils.world2d.WorldCoords2d; @@ -16,6 +17,7 @@ */ public class RenderSurfacesTask extends ModelTask { private final ReadableHeightmapSpec heightmapSpec; + private final ModelValue altitudeOffset; private final Placeable placeable; private final Shape2dVoxelizer voxelizer = new SurfaceVoxelizer2d(); @@ -25,21 +27,24 @@ public class RenderSurfacesTask extends ModelTask { * * @param selection model selection containing the models to render * @param heightmapSpec heightmap on which features will be placed + * @param altitudeOffset a model value added to heightmap altitude for placing * @param placeable what to place on surface */ - public RenderSurfacesTask(ModelSelection selection, ReadableHeightmapSpec heightmapSpec, Placeable placeable) { + public RenderSurfacesTask(ModelSelection selection, ReadableHeightmapSpec heightmapSpec, ModelValue altitudeOffset, Placeable placeable) { super(Shape2dConvertibleModel.class, selection); this.heightmapSpec = heightmapSpec; + this.altitudeOffset = altitudeOffset; this.placeable = placeable; } @Override protected void run(Shape2dConvertibleModel model, GenerationTile tile) { ReadableHeightmap heightmap = tile.heightmap(heightmapSpec); + int offset = altitudeOffset.getAsInt(model).orElse(0); for (Positioned2d voxel : tile.limits().to2d().filterInside(voxelizer.voxelize(model))) { WorldCoords2d c = voxel.coords(); - placeable.place(tile.voxels(), c.x(), c.y(), heightmap.get(c)); + placeable.place(tile.voxels(), c.x(), c.y(), heightmap.get(c) + offset); } } } diff --git a/src/test/java/fr/ign/voxatile/core/parameters/GenerationParamsTest.java b/src/test/java/fr/ign/voxatile/core/parameters/GenerationParamsTest.java index e6d9341a5..e0b48a485 100644 --- a/src/test/java/fr/ign/voxatile/core/parameters/GenerationParamsTest.java +++ b/src/test/java/fr/ign/voxatile/core/parameters/GenerationParamsTest.java @@ -11,7 +11,7 @@ import fr.ign.voxatile.core.parameters.providers.TestingProviderParams; import fr.ign.voxatile.core.parameters.tasks.FetchDataTaskParams; import fr.ign.voxatile.core.parameters.tasks.NoOperationTaskParams; -import fr.ign.voxatile.core.parameters.tasks.RenderBuildingsTaskParams; +import fr.ign.voxatile.core.parameters.tasks.RenderSurfacesTaskParams; import fr.ign.voxatile.core.utils.world2d.WorldBBox2d; import fr.ign.voxatile.core.world.TestingVoxelWorld; @@ -121,11 +121,7 @@ public void testCreate() throws ParseException { params.forEachTile.tasks.put("source2", new FetchDataTaskParams("models2", new TestingProviderParams("value3"))); TestingVoxelParams placeable = new TestingVoxelParams("voxel"); - RenderBuildingsTaskParams task = new RenderBuildingsTaskParams( - placeable, - placeable, - placeable - ); + RenderSurfacesTaskParams task = new RenderSurfacesTaskParams(placeable); task.models.type = "building"; params.forEachTile.tasks.put("building", task); diff --git a/src/test/java/fr/ign/voxatile/core/parameters/tasks/RenderBuildingsTaskParamsTest.java b/src/test/java/fr/ign/voxatile/core/parameters/tasks/RenderBuildingsTaskParamsTest.java deleted file mode 100644 index 7ed151602..000000000 --- a/src/test/java/fr/ign/voxatile/core/parameters/tasks/RenderBuildingsTaskParamsTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package fr.ign.voxatile.core.parameters.tasks; - -import org.junit.jupiter.api.Test; -import tools.jackson.databind.cfg.MapperBuilder; -import tools.jackson.databind.exc.InvalidNullException; - -import fr.ign.voxatile.core.parameters.ParamsTester; -import fr.ign.voxatile.core.parameters.models.TestingModelSelectionParams; -import fr.ign.voxatile.core.parameters.placeables.voxels.TestingVoxelParams; - -import static org.junit.jupiter.api.Assertions.*; - -public class RenderBuildingsTaskParamsTest { - @Test - public void testValidate() { - RenderBuildingsTaskParams params; - TestingVoxelParams placeable = new TestingVoxelParams("voxel"); - - params = new RenderBuildingsTaskParams(placeable, placeable, placeable); - params.models = TestingModelSelectionParams.INVALID; - assertThrows(IllegalArgumentException.class, params::validate); - - params = new RenderBuildingsTaskParams(placeable, placeable, placeable); - assertDoesNotThrow(params::validate); - } - - @Test - public void testDeserialization() { - MapperBuilder builder = ParamsTester.mapperBuilderWithParams("building", RenderBuildingsTaskParams.class); - - RenderBuildingsTaskParams params = assertDoesNotThrow(() -> ParamsTester.deserialize(RenderBuildingsTaskParams.class, """ - type: building - models: - type: building - roof: voxelA - wall: voxelB - window: voxelC - """, builder)); - assertEquals("building", params.models.type); - assertEquals("voxelA", assertInstanceOf(TestingVoxelParams.class, params.roof).name); - assertEquals("voxelB", assertInstanceOf(TestingVoxelParams.class, params.wall).name); - assertEquals("voxelC", assertInstanceOf(TestingVoxelParams.class, params.window).name); - - assertThrows(InvalidNullException.class, () -> ParamsTester.deserialize(RenderBuildingsTaskParams.class, """ - type: building - models: - type: building - roof: - wall: voxelB - window: voxelC - """, builder)); - - assertThrows(InvalidNullException.class, () -> ParamsTester.deserialize(RenderBuildingsTaskParams.class, """ - type: building - models: - type: building - roof: voxelA - wall: - window: voxelC - """, builder)); - - assertThrows(InvalidNullException.class, () -> ParamsTester.deserialize(RenderBuildingsTaskParams.class, """ - type: building - models: - type: building - roof: voxelA - wall: voxelB - window: - """, builder)); - } -} diff --git a/src/test/java/fr/ign/voxatile/core/parameters/tasks/RenderFacadesTaskParamsTest.java b/src/test/java/fr/ign/voxatile/core/parameters/tasks/RenderFacadesTaskParamsTest.java new file mode 100644 index 000000000..1127b141b --- /dev/null +++ b/src/test/java/fr/ign/voxatile/core/parameters/tasks/RenderFacadesTaskParamsTest.java @@ -0,0 +1,31 @@ +package fr.ign.voxatile.core.parameters.tasks; + +import java.util.Collections; + +import org.junit.jupiter.api.Test; + +import fr.ign.voxatile.core.parameters.models.TestingModelSelectionParams; +import fr.ign.voxatile.core.parameters.models.values.TestingModelValueParams; + +import static org.junit.jupiter.api.Assertions.*; + +public class RenderFacadesTaskParamsTest { + @Test + public void testValidate() { + RenderFacadesTaskParams params; + + params = new RenderFacadesTaskParams(Collections.emptyList(), TestingModelValueParams.VALID, TestingModelValueParams.VALID); + assertDoesNotThrow(params::validate); + + // Test required arguments + params = new RenderFacadesTaskParams(Collections.emptyList(), TestingModelValueParams.INVALID, TestingModelValueParams.VALID); + assertThrows(IllegalArgumentException.class, params::validate); + + params = new RenderFacadesTaskParams(Collections.emptyList(), TestingModelValueParams.VALID, TestingModelValueParams.INVALID); + assertThrows(IllegalArgumentException.class, params::validate); + + params = new RenderFacadesTaskParams(Collections.emptyList(), TestingModelValueParams.VALID, TestingModelValueParams.VALID); + params.models = TestingModelSelectionParams.INVALID; + assertThrows(IllegalArgumentException.class, params::validate); + } +} diff --git a/src/test/java/fr/ign/voxatile/core/parameters/tasks/RenderSurfacesTaskParamsTest.java b/src/test/java/fr/ign/voxatile/core/parameters/tasks/RenderSurfacesTaskParamsTest.java index fe5bbb06f..82d8a230b 100644 --- a/src/test/java/fr/ign/voxatile/core/parameters/tasks/RenderSurfacesTaskParamsTest.java +++ b/src/test/java/fr/ign/voxatile/core/parameters/tasks/RenderSurfacesTaskParamsTest.java @@ -4,6 +4,7 @@ import fr.ign.voxatile.core.parameters.heightmaps.TestingHeightmapParams; import fr.ign.voxatile.core.parameters.models.TestingModelSelectionParams; +import fr.ign.voxatile.core.parameters.models.values.TestingModelValueParams; import fr.ign.voxatile.core.parameters.placeables.TestingPlaceableParams; import static org.junit.jupiter.api.Assertions.*; @@ -14,17 +15,22 @@ public void testValidate() { RenderSurfacesTaskParams params; // Test required arguments - params = new RenderSurfacesTaskParams(TestingHeightmapParams.VALID, TestingPlaceableParams.VALID); + params = new RenderSurfacesTaskParams(TestingPlaceableParams.VALID); params.models = TestingModelSelectionParams.INVALID; assertThrows(IllegalArgumentException.class, params::validate); - params = new RenderSurfacesTaskParams(TestingHeightmapParams.INVALID, TestingPlaceableParams.VALID); + params = new RenderSurfacesTaskParams(TestingPlaceableParams.VALID); + params.heightmap = TestingHeightmapParams.INVALID; assertThrows(IllegalArgumentException.class, params::validate); - params = new RenderSurfacesTaskParams(TestingHeightmapParams.VALID, TestingPlaceableParams.INVALID); + params = new RenderSurfacesTaskParams(TestingPlaceableParams.VALID); + params.altitudeOffset = TestingModelValueParams.INVALID; assertThrows(IllegalArgumentException.class, params::validate); - params = new RenderSurfacesTaskParams(TestingHeightmapParams.VALID, TestingPlaceableParams.VALID); + params = new RenderSurfacesTaskParams(TestingPlaceableParams.INVALID); + assertThrows(IllegalArgumentException.class, params::validate); + + params = new RenderSurfacesTaskParams(TestingPlaceableParams.VALID); assertDoesNotThrow(params::validate); } } diff --git a/src/test/java/fr/ign/voxatile/core/tasks/RenderBuildingsTaskTest.java b/src/test/java/fr/ign/voxatile/core/tasks/RenderBuildingsTaskTest.java deleted file mode 100644 index 8308725ea..000000000 --- a/src/test/java/fr/ign/voxatile/core/tasks/RenderBuildingsTaskTest.java +++ /dev/null @@ -1,127 +0,0 @@ -package fr.ign.voxatile.core.tasks; - -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import fr.ign.voxatile.core.generation.TestingGenerationTile; -import fr.ign.voxatile.core.models.Model; -import fr.ign.voxatile.core.models.ModelSelection; -import fr.ign.voxatile.core.models.TestingRectangleShape2dModel; -import fr.ign.voxatile.core.models.filters.ModelFilterHasMetadata; -import fr.ign.voxatile.core.parameters.placeables.voxels.TestingVoxelParams; -import fr.ign.voxatile.core.placeables.Placeable; -import fr.ign.voxatile.core.utils.random.TestingSeed; -import fr.ign.voxatile.core.utils.world2d.WorldBBox2d; -import fr.ign.voxatile.core.utils.world3d.WorldBBox3d; -import fr.ign.voxatile.core.utils.world3d.WorldCoords3d; - -import static org.junit.jupiter.api.Assertions.*; - -public class RenderBuildingsTaskTest { - private TestingGenerationTile tile; - - @BeforeEach - void setUp() { - WorldBBox3d bbox = new WorldBBox3d(0, 0, 0, 3, 3, 22); - tile = new TestingGenerationTile(bbox); - } - - /** - * Test the rendering of the building with a model with good specified metadata. - */ - @Test - void testBuildingRenderingWithMetadata() { - WorldBBox2d modelBbox = new WorldBBox2d(0, 0, 3, 3); - Model building = new TestingRectangleShape2dModel(modelBbox); - - int expectedHeight = 20; - // Required metadata - building.setMetadata("height", expectedHeight); - building.setMetadata("minimum-ground-altitude", 0); - building.setMetadata("ground-floor-altitude", 0); - tile.models().add("building", List.of(building)); - - assertDoesNotThrow(() -> new RenderBuildingsTask( - new ModelSelection("building", new ModelFilterHasMetadata("height")), - new TestingVoxelParams("roof").create(TestingSeed.UNUSED), - new TestingVoxelParams("wall").create(TestingSeed.UNUSED), - new TestingVoxelParams("window").create(TestingSeed.UNUSED) - ).run(tile)); - - for (WorldCoords3d c : tile.limits()) { - int x = c.x(); - int y = c.y(); - int z = c.z(); - - // The building is always above the terrain. - // In this test, the (flat) ground is at z = 0, - // thus the building starts at z = 1 - if (z == 0) - continue; - - // Roof check - if (z == expectedHeight) - tile.voxels().assertVoxel("roof", c); - // Air check - if (z > expectedHeight) - tile.voxels().assertVoxelNull(c); - // Checks here below the roof of the building - if (z < expectedHeight) { - // Windows check - if (x != 1 && y != 1 && z % 4 == 0) - tile.voxels().assertVoxel("window", c); - // Walls check - if (x != 1 && y != 1 && z % 4 != 0) - tile.voxels().assertVoxel("wall", c); - // Air check between two floors - if (x == 1 && y == 1 && z % 4 != 2) - tile.voxels().assertVoxelNull(c); - } - } - } - - /** - * Test the rendering of the building with a model that - * does not have 'height' metadata. - */ - @Test - void testBuildingRenderingWithoutMetadata() { - WorldBBox2d modelBbox = new WorldBBox2d(0, 0, 3, 3); - Model building = new TestingRectangleShape2dModel(modelBbox); - tile.models().add("building", List.of(building)); - - Placeable placeable = new TestingVoxelParams("voxel").create(TestingSeed.UNUSED); - assertDoesNotThrow(() -> new RenderBuildingsTask( - new ModelSelection("building", new ModelFilterHasMetadata("height")), - placeable, - placeable, - placeable - ).run(tile)); - } - - /** - * Test the rendering of the building with a model - * that contains negative 'height' metadata. - */ - @Test - void testBuildingRenderingWithNegativeHeight() { - WorldBBox2d modelBbox = new WorldBBox2d(0, 0, 3, 3); - Model building = new TestingRectangleShape2dModel(modelBbox); - - building.setMetadata("height", -20); - tile.models().add("building", List.of(building)); - - Placeable placeable = new TestingVoxelParams("voxel").create(TestingSeed.UNUSED); - assertDoesNotThrow(() -> new RenderBuildingsTask( - new ModelSelection("building", new ModelFilterHasMetadata("height")), - placeable, - placeable, - placeable - ).run(tile)); - - for (WorldCoords3d c : tile.limits()) - tile.voxels().assertVoxelNull(c); - } -} diff --git a/src/test/java/fr/ign/voxatile/core/tasks/RenderSurfacesTaskTest.java b/src/test/java/fr/ign/voxatile/core/tasks/RenderSurfacesTaskTest.java index 2ac0e7429..c043469d5 100644 --- a/src/test/java/fr/ign/voxatile/core/tasks/RenderSurfacesTaskTest.java +++ b/src/test/java/fr/ign/voxatile/core/tasks/RenderSurfacesTaskTest.java @@ -10,6 +10,7 @@ import fr.ign.voxatile.core.generation.heightmaps.TestingHeightmap; import fr.ign.voxatile.core.models.ModelSelection; import fr.ign.voxatile.core.models.TestingRectangleShape2dModel; +import fr.ign.voxatile.core.models.values.FixedValue; import fr.ign.voxatile.core.utils.world2d.WorldBBox2d; import fr.ign.voxatile.core.utils.world2d.WorldCoords2d; import fr.ign.voxatile.core.utils.world3d.WorldBBox3d; @@ -37,7 +38,7 @@ public void setUp() { @Test public void testConstructor() { - assertDoesNotThrow(() -> new RenderSurfacesTask(modelSelection, heightmap.spec(), voxel)); + assertDoesNotThrow(() -> new RenderSurfacesTask(modelSelection, heightmap.spec(), new FixedValue(0.0), voxel)); } @Test @@ -47,10 +48,10 @@ public void testRenderFlatSmaller() { WorldBBox2d modelBbox = new WorldBBox2d(0, -1, 3, 4); tile.models().add("testing", List.of(new TestingRectangleShape2dModel(modelBbox))); - new RenderSurfacesTask(modelSelection, heightmap.spec(), voxel).run(tile); + new RenderSurfacesTask(modelSelection, heightmap.spec(), new FixedValue(2.0), voxel).run(tile); for (WorldCoords3d pos : bbox) { - if (pos.z() == 0 && modelBbox.contains(pos.to2d())) + if (pos.z() == 2 && modelBbox.contains(pos.to2d())) tile.voxels().assertVoxelNotNull(pos); else tile.voxels().assertVoxelNull(pos); @@ -68,10 +69,10 @@ public void testRenderHeightmap() { // Add one model covering the whole map tile.models().add("testing", List.of(new TestingRectangleShape2dModel(bbox.to2d()))); - new RenderSurfacesTask(modelSelection, heightmap.spec(), voxel).run(tile); + new RenderSurfacesTask(modelSelection, heightmap.spec(), new FixedValue(3.0), voxel).run(tile); for (WorldCoords3d pos : bbox) { - if (pos.z() == (pos.x() + pos.y()) / 2) + if (pos.z() == (pos.x() + pos.y()) / 2 + 3) tile.voxels().assertVoxelNotNull(pos); else tile.voxels().assertVoxelNull(pos); @@ -90,7 +91,7 @@ public void testRenderVerticalOverflow() { // Add one model covering the whole map with BORDER voxels tile.models().add("testing", List.of(new TestingRectangleShape2dModel(bbox.to2d()))); - new RenderSurfacesTask(modelSelection, heightmap.spec(), voxel).run(tile); + new RenderSurfacesTask(modelSelection, heightmap.spec(), new FixedValue(0.0), voxel).run(tile); for (WorldCoords3d pos : bbox) { if (pos.z() == pos.x() + pos.y() * 2) @@ -107,7 +108,7 @@ public void testRenderHorizontalOverflow() { WorldBBox2d modelBbox = new WorldBBox2d(bbox.minX() - 1, bbox.minY() - 1, bbox.sizeX() + 2, bbox.sizeY() + 2); tile.models().add("testing", List.of(new TestingRectangleShape2dModel(modelBbox))); - new RenderSurfacesTask(modelSelection, heightmap.spec(), voxel).run(tile); + new RenderSurfacesTask(modelSelection, heightmap.spec(), new FixedValue(0.0), voxel).run(tile); for (WorldCoords3d pos : bbox) { if (pos.z() == 0)