/*
* Copyright 2015 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.core.world.generator.facetProviders;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import org.terasology.math.Region3i;
import org.terasology.math.TeraMath;
import org.terasology.math.geom.Vector3i;
import org.terasology.utilities.procedural.Noise;
import org.terasology.utilities.procedural.WhiteNoise;
import org.terasology.world.generation.Facet;
import org.terasology.world.generation.FacetProvider;
import org.terasology.world.generation.Requires;
import org.terasology.world.generation.facets.SurfaceHeightFacet;
import org.terasology.world.generation.facets.base.ObjectFacet2D;
import org.terasology.world.generation.facets.base.ObjectFacet3D;
import java.util.List;
import java.util.Map;
/**
* Places objects on the surface based on population densities
* for a environmental variable (e.g. biome).
*
*/
@Requires(@Facet(SurfaceHeightFacet.class))
public abstract class SurfaceObjectProvider<B, T> implements FacetProvider {
private Noise typeNoiseGen;
private final Table<B, T, Float> probsTable = HashBasedTable.create();
@Override
public void setSeed(long seed) {
typeNoiseGen = new WhiteNoise(seed + 1);
}
/**
* Populates a given facet based on filters and population densities
*
* @param facet the facet to populate
* @param surfaceFacet the surface height facet
* @param typeFacet the facet that provides the environment
* @param filters a set of filters
*/
protected void populateFacet(ObjectFacet3D<T> facet, SurfaceHeightFacet surfaceFacet, ObjectFacet2D<? extends B> typeFacet, List<Predicate<Vector3i>> filters) {
Region3i worldRegion = facet.getWorldRegion();
int minY = worldRegion.minY();
int maxY = worldRegion.maxY();
Vector3i pos = new Vector3i();
for (int z = worldRegion.minZ(); z <= worldRegion.maxZ(); z++) {
for (int x = worldRegion.minX(); x <= worldRegion.maxX(); x++) {
int height = TeraMath.floorToInt(surfaceFacet.getWorld(x, z)) + 1;
// if the surface is in range
if (height >= minY && height <= maxY) {
pos.set(x, height, z);
// if all predicates match
if (applyAll(filters, pos)) {
B biome = typeFacet.getWorld(x, z);
Map<T, Float> plantProb = probsTable.row(biome);
T type = getType(x, z, plantProb);
if (type != null) {
facet.setWorld(x, height, z, type);
}
}
}
}
}
}
private boolean applyAll(List<Predicate<Vector3i>> components, Vector3i pos) {
// Similar to guava's implementation of Predicates#all
// According to google, using indices is superior to using an Iterator
// This implementation also avoids duplicating the list
for (Predicate<Vector3i> component : components) {
if (!component.apply(pos)) {
return false;
}
}
return true;
}
/**
* Registers an object type with a certain population density based on an environmental variable
*
* @param biome the environment type (e.g. biome)
* @param tree the object type
* @param probability the population density in [0..1]
* @throws IllegalArgumentException if probability is not in [0..1]
*/
protected void register(B biome, T tree, float probability) {
Preconditions.checkArgument(probability >= 0, "probability must be >= 0");
Preconditions.checkArgument(probability <= 1, "probability must be <= 1");
probsTable.put(biome, tree, probability);
}
/**
* Clears all registered population densities
*/
protected void clearProbabilities() {
probsTable.clear();
}
/**
* @param x the x coordinate
* @param z the z coordinate
* @param objs a map (objType to probability)
* @return a random pick from the map or <code>null</code>
*/
protected T getType(int x, int z, Map<T, Float> objs) {
float random = Math.abs(typeNoiseGen.noise(x, z));
for (T generator : objs.keySet()) {
Float threshold = objs.get(generator);
if (threshold != null) {
if (random < threshold) {
return generator;
} else {
random -= threshold;
}
}
}
return null;
}
}