org.terasology.polyworld.voronoi.GraphFacetProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.terasology.polyworld.voronoi.GraphFacetProvider.java

Source

/*
 * Copyright 2014 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.polyworld.voronoi;

import java.math.RoundingMode;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.entitySystem.Component;
import org.terasology.math.Rect2i;
import org.terasology.math.delaunay.Voronoi;
import org.terasology.math.geom.Rect2f;
import org.terasology.math.geom.Vector2f;
import org.terasology.polyworld.TriangleLookup;
import org.terasology.polyworld.rp.WorldRegionFacet;
import org.terasology.polyworld.rp.RegionType;
import org.terasology.polyworld.rp.WorldRegion;
import org.terasology.rendering.nui.properties.Range;
import org.terasology.utilities.random.FastRandom;
import org.terasology.utilities.random.Random;
import org.terasology.world.generation.Border3D;
import org.terasology.world.generation.ConfigurableFacetProvider;
import org.terasology.world.generation.Facet;
import org.terasology.world.generation.GeneratingRegion;
import org.terasology.world.generation.Produces;
import org.terasology.world.generation.Requires;

import com.google.common.base.Stopwatch;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import com.google.common.math.DoubleMath;

/**
 * TODO Type description
 * @author Martin Steiger
 */
@Produces(GraphFacet.class)
@Requires(@Facet(WorldRegionFacet.class))
public class GraphFacetProvider implements ConfigurableFacetProvider {

    private static final Logger logger = LoggerFactory.getLogger(GraphFacetProvider.class);

    private LoadingCache<WorldRegion, Graph> graphCache = CacheBuilder.newBuilder()
            .build(new CacheLoader<WorldRegion, Graph>() {

                @Override
                public Graph load(WorldRegion wr) throws Exception {
                    Stopwatch sw = Stopwatch.createStarted();

                    Graph graph = createGraph(wr);

                    logger.info("Created graph for {} in {}ms.", wr.getArea(), sw.elapsed(TimeUnit.MILLISECONDS));

                    return graph;
                }
            });

    private final LoadingCache<Graph, TriangleLookup> lookupCache = CacheBuilder.newBuilder()
            .build(new CacheLoader<Graph, TriangleLookup>() {

                @Override
                public TriangleLookup load(Graph graph) throws Exception {
                    return new TriangleLookup(graph);
                }
            });

    private long seed;

    private GraphProviderConfiguration configuration = new GraphProviderConfiguration();

    @Override
    public void setSeed(long seed) {
        this.seed = seed;
    }

    @Override
    public void process(GeneratingRegion region) {
        Border3D border = region.getBorderForFacet(GraphFacet.class);
        GraphFacetImpl facet = new GraphFacetImpl(region.getRegion(), border);
        WorldRegionFacet regionFacet = region.getRegionFacet(WorldRegionFacet.class);

        Collection<WorldRegion> areas = regionFacet.getRegions();

        for (WorldRegion wr : areas) {
            Graph graph = graphCache.getUnchecked(wr);
            TriangleLookup lookup = lookupCache.getUnchecked(graph);
            facet.add(wr, graph, lookup);
        }

        region.setRegionFacet(GraphFacet.class, facet);
    }

    private Graph createGraph(WorldRegion wr) {
        Rect2i area = wr.getArea();
        if (wr.getType() == RegionType.OCEAN) {
            //            int rows = DoubleMath.roundToInt(area.height() / cellSize, RoundingMode.HALF_UP);
            //            int cols = DoubleMath.roundToInt(area.width() / cellSize, RoundingMode.HALF_UP);
            return createGridGraph(area, 1, 1);
        } else {
            int numSites = DoubleMath.roundToInt(area.area() / configuration.graphDensity, RoundingMode.HALF_UP);
            return createVoronoiGraph(area, numSites);
        }
    }

    private static Graph createGridGraph(Rect2i bounds, int rows, int cols) {

        Rect2i doubleBounds = Rect2i.createFromMinAndSize(bounds.minX(), bounds.minY(), bounds.width(),
                bounds.height());
        final Graph graph = new GridGraph(doubleBounds, rows, cols);

        return graph;
    }

    private Graph createVoronoiGraph(Rect2i bounds, int numSites) {

        // use different seeds for different areas
        long areaSeed = seed ^ bounds.hashCode();
        final Random r = new FastRandom(areaSeed);

        List<Vector2f> points = Lists.newArrayListWithCapacity(numSites);
        for (int i = 0; i < numSites; i++) {
            float px = bounds.minX() + r.nextFloat() * bounds.width();
            float py = bounds.minY() + r.nextFloat() * bounds.height();
            points.add(new Vector2f(px, py));
        }

        Rect2f doubleBounds = Rect2f.createFromMinAndSize(bounds.minX(), bounds.minY(), bounds.width(),
                bounds.height());
        final Voronoi v = new Voronoi(points, doubleBounds);
        final Graph graph = new VoronoiGraph(v, 2);
        GraphEditor.improveCorners(graph.getCorners());

        return graph;
    }

    @Override
    public String getConfigurationName() {
        return "Voronoi Graphs";
    }

    @Override
    public Component getConfiguration() {
        return configuration;
    }

    @Override
    public void setConfiguration(Component configuration) {
        this.configuration = (GraphProviderConfiguration) configuration;
    }

    private static class GraphProviderConfiguration implements Component {
        @Range(min = 100, max = 5000f, increment = 100f, precision = 0, description = "Define the density for graph cells")
        private float graphDensity = 500f;
    }
}