List procedural snippets in viewer

Reads definitions from snippet folders.
This commit is contained in:
piexel committed 2026-10-20 12:25:42 +02:00
1 parent 26825869e9
commit 8d12019246
1 file changed
+68 -15
@@ -18,28 +18,42 @@
package com.volmit.iris.engine.object.procedural;
import com.google.gson.Gson;
import com.volmit.iris.Iris;
import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.engine.object.IrisObject;
import com.volmit.iris.engine.object.annotations.Snippet;
import com.volmit.iris.engine.object.procedural.coral.Coral;
import com.volmit.iris.engine.object.procedural.coral.ResolvedCoral;
import com.volmit.iris.engine.object.procedural.crystal.Crystal;
import com.volmit.iris.engine.object.procedural.crystal.ResolvedCrystal;
import com.volmit.iris.engine.object.procedural.formation.Formation;
import com.volmit.iris.engine.object.procedural.formation.ResolvedFormation;
import com.volmit.iris.engine.object.procedural.fungi.Fungus;
import com.volmit.iris.engine.object.procedural.fungi.ResolvedFungus;
import com.volmit.iris.engine.object.procedural.ruin.ResolvedRuin;
import com.volmit.iris.engine.object.procedural.ruin.Ruin;
import com.volmit.iris.engine.object.procedural.tree.ResolvedTree;
import com.volmit.iris.engine.object.procedural.tree.Tree;
import com.volmit.iris.util.collection.KMap;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* The procedural objects of one pack. A definition several biomes share, such as one snippet, is baked once, and every
* variant can be found again by its key.
* The procedural objects of one pack: those its regions and biomes declare, and those saved on their own as snippets,
* such as {@code snippet/procedural-tree/oak.json}. A definition several places share is baked once, and every variant
* can be found again by its key.
*/
public final class ProceduralObjectCatalog {
private static final Gson DEFINITION_JSON = new Gson();
private static final List<Class<?>> DEFINITION_TYPES = List.of(Tree.class, Ruin.class, Formation.class, Coral.class, Fungus.class, Crystal.class);
private final IrisData data;
private final KMap<String, ResolvedProceduralPlacement> byDefinition = new KMap<>();
@@ -52,20 +66,27 @@ public final class ProceduralObjectCatalog {
/** The placements {@code settings} declare, shared with every other declaration of the same definitions. */
public List<ResolvedProceduralPlacement> resolve(ProceduralObjectSettings settings) {
List<ResolvedProceduralPlacement> all = new ArrayList<>();
addAll(all, settings.trees(), tree -> new ResolvedTree(tree, data));
addAll(all, settings.ruins(), ruin -> new ResolvedRuin(ruin, data));
addAll(all, settings.formations(), formation -> new ResolvedFormation(formation, data));
addAll(all, settings.coral(), coral -> new ResolvedCoral(coral, data));
addAll(all, settings.fungi(), fungus -> new ResolvedFungus(fungus, data));
addAll(all, settings.crystals(), crystal -> new ResolvedCrystal(crystal, data));
Stream.of(settings.trees(), settings.ruins(), settings.formations(), settings.coral(), settings.fungi(), settings.crystals())
.flatMap(List::stream)
.forEach(definition -> all.add(placementOf(definition)));
return List.copyOf(all);
}
private <S> void addAll(List<ResolvedProceduralPlacement> all, List<S> definitions, Function<S, ResolvedProceduralPlacement> resolve) {
for (S definition : definitions) {
String identity = definition.getClass().getName() + DEFINITION_JSON.toJson(definition);
all.add(byDefinition.computeIfAbsent(identity, ignored -> register(resolve.apply(definition))));
}
private ResolvedProceduralPlacement placementOf(Object definition) {
String identity = definition.getClass().getName() + DEFINITION_JSON.toJson(definition);
return byDefinition.computeIfAbsent(identity, ignored -> register(resolvedFor(definition)));
}
private ResolvedProceduralPlacement resolvedFor(Object definition) {
return switch (definition) {
case Tree tree -> new ResolvedTree(tree, data);
case Ruin ruin -> new ResolvedRuin(ruin, data);
case Formation formation -> new ResolvedFormation(formation, data);
case Coral coral -> new ResolvedCoral(coral, data);
case Fungus fungus -> new ResolvedFungus(fungus, data);
case Crystal crystal -> new ResolvedCrystal(crystal, data);
default -> throw new IllegalArgumentException("Not a procedural object: " + definition.getClass().getName());
};
}
private ResolvedProceduralPlacement register(ResolvedProceduralPlacement placement) {
@@ -75,14 +96,46 @@ public final class ProceduralObjectCatalog {
return placement;
}
/** The key of every variant the pack's regions and biomes declare, whether or not its dimensions reach them. */
/** The key of every variant the pack defines, in snippets or in regions and biomes, whether or not its dimensions reach them. */
public List<String> variantKeys() {
Set<String> keys = new LinkedHashSet<>();
for (Class<?> type : DEFINITION_TYPES) {
snippetDefinitions(type).forEach(definition -> keys.addAll(placementOf(definition).variantKeys()));
}
data.getRegionLoader().streamAll().forEach(region -> addVariantKeys(keys, region.getProceduralObjects()));
data.getBiomeLoader().streamAll().forEach(biome -> addVariantKeys(keys, biome.getProceduralObjects()));
return List.copyOf(keys);
}
private List<Object> snippetDefinitions(Class<?> type) {
Path folder = data.getDataFolder().toPath().resolve("snippet").resolve(type.getAnnotation(Snippet.class).value());
if (!Files.isDirectory(folder)) {
return List.of();
}
try (Stream<Path> files = Files.walk(folder)) {
List<Object> definitions = new ArrayList<>();
files.filter(file -> file.toString().endsWith(".json")).sorted().forEach(file -> {
Object definition = readDefinition(file, type);
if (definition != null) {
definitions.add(definition);
}
});
return definitions;
} catch (IOException error) {
Iris.warn("Could not list the procedural objects in " + folder + ": " + error.getMessage());
return List.of();
}
}
private Object readDefinition(Path file, Class<?> type) {
try (Reader reader = Files.newBufferedReader(file)) {
return data.getGson().fromJson(reader, type);
} catch (IOException | RuntimeException error) {
Iris.warn("Could not read the procedural object " + file + ": " + error.getMessage());
return null;
}
}
private void addVariantKeys(Set<String> keys, ProceduralObjectSettings settings) {
if (settings == null) {
return;