org.terasology.cities.WorldFacade.java Source code

Java tutorial

Introduction

Here is the source code for org.terasology.cities.WorldFacade.java

Source

/*
 * 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.cities;

import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.terasology.math.Vector2i;
import javax.vecmath.Vector2d;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.cities.generator.DefaultTownWallGenerator;
import org.terasology.cities.generator.LotGeneratorRandom;
import org.terasology.cities.generator.RoadGeneratorSimple;
import org.terasology.cities.generator.RoadModifierRandom;
import org.terasology.cities.generator.RoadShapeGenerator;
import org.terasology.cities.generator.SimpleChurchGenerator;
import org.terasology.cities.generator.SimpleFenceGenerator;
import org.terasology.cities.generator.SimpleHousingGenerator;
import org.terasology.cities.generator.SiteConnector;
import org.terasology.cities.generator.SiteFinderRandom;
import org.terasology.cities.generator.TownWallShapeGenerator;
import org.terasology.cities.model.City;
import org.terasology.cities.model.Junction;
import org.terasology.cities.model.Lake;
import org.terasology.cities.model.MedievalTown;
import org.terasology.cities.model.NamedArea;
import org.terasology.cities.model.Road;
import org.terasology.cities.model.SimpleFence;
import org.terasology.cities.model.SimpleLot;
import org.terasology.cities.model.Site;
import org.terasology.cities.model.bldg.SimpleBuilding;
import org.terasology.cities.model.bldg.SimpleChurch;
import org.terasology.cities.model.bldg.TownWall;
import org.terasology.cities.common.CachingFunction;
import org.terasology.commonworld.Orientation;
import org.terasology.commonworld.Sector;
import org.terasology.commonworld.UnorderedPair;
import org.terasology.commonworld.contour.Contour;
import org.terasology.commonworld.contour.ContourTracer;
import org.terasology.commonworld.heightmap.HeightMap;
import org.terasology.commonworld.heightmap.HeightMaps;
import org.terasology.entitySystem.entity.EntityManager;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.namegenerator.town.DebugTownTheme;
import org.terasology.namegenerator.town.TownAffinityVector;
import org.terasology.namegenerator.town.TownNameProvider;
import org.terasology.namegenerator.waters.DebugWaterTheme;
import org.terasology.namegenerator.waters.WaterNameProvider;
import org.terasology.registry.CoreRegistry;
import org.terasology.world.WorldComponent;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Objects;
import com.google.common.base.Stopwatch;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Sets;

/**
 * Provides many different getters to rasterize a world
 */
public class WorldFacade {

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

    private LoadingCache<Sector, Set<City>> decoratedCities;

    private Function<Site, Set<Site>> connectedCities;

    private Function<Sector, Set<UnorderedPair<Site>>> sectorConnections;

    private Function<Vector2i, Junction> junctions;

    private Function<Sector, Set<Road>> roadMap;

    private Function<Sector, Shape> roadShapeFunc;

    private Function<Sector, Set<Lake>> lakeMap;

    /**
     * @param seed the seed value
     * @param heightMap the height map to use
     */
    public WorldFacade(final String seed, final HeightMap heightMap) {

        final CityTerrainComponent terrainConfig = WorldFacade.getWorldEntity()
                .getComponent(CityTerrainComponent.class);
        final CitySpawnComponent spawnConfig = WorldFacade.getWorldEntity().getComponent(CitySpawnComponent.class);

        junctions = new Function<Vector2i, Junction>() {

            @Override
            public Junction apply(Vector2i input) {
                return new Junction(input);
            }

        };
        junctions = CachingFunction.wrap(junctions);

        lakeMap = CachingFunction.wrap(new Function<Sector, Set<Lake>>() {

            @Override
            public Set<Lake> apply(Sector sector) {

                Integer salt = 2354234;
                int ngseed = Objects.hashCode(salt, seed, sector);
                WaterNameProvider ng = new WaterNameProvider(ngseed, new DebugWaterTheme());

                int minSize = 16;

                int scale = 8;
                int size = Sector.SIZE / scale;
                HeightMap orgHm = HeightMaps.scalingArea(heightMap, scale);
                Vector2i coords = sector.getCoords();

                Rectangle sectorRect = new Rectangle(coords.x * size, coords.y * size, size, size);
                ContourTracer ct = new ContourTracer(orgHm, sectorRect, terrainConfig.getSeaLevel());

                Set<Lake> lakes = Sets.newHashSet();

                for (Contour c : ct.getOuterContours()) {
                    Contour scaledContour = c.scale(scale);
                    Polygon polyLake = scaledContour.getPolygon();

                    if (polyLake.getBounds().width > minSize && polyLake.getBounds().height > minSize) {
                        Lake lake = new Lake(scaledContour, ng.generateName());

                        for (Contour isl : ct.getInnerContours()) {
                            Rectangle bboxIsland = isl.getPolygon().getBounds();

                            if (polyLake.getBounds().contains(bboxIsland)) {
                                if (allInside(polyLake, isl.getPoints())) {
                                    lake.addIsland(isl);
                                }
                            }
                        }

                        lakes.add(lake);
                    }
                }

                return lakes;
            }

            private boolean allInside(Polygon polygon, Collection<Point> points) {
                for (Point pt : points) {
                    if (!polygon.contains(pt)) {
                        return false;
                    }
                }
                return true;
            }
        });

        int minCitiesPerSector = spawnConfig.getMinCitiesPerSector();
        int maxCitiesPerSector = spawnConfig.getMaxCitiesPerSector();
        int minSize = spawnConfig.getMinCityRadius();
        int maxSize = spawnConfig.getMaxCityRadius();

        AreaInfo globalAreaInfo = new AreaInfo(terrainConfig, heightMap);

        Function<? super Sector, AreaInfo> sectorInfos = Functions.constant(globalAreaInfo);
        SiteFinderRandom cpr = new SiteFinderRandom(seed, sectorInfos, minCitiesPerSector, maxCitiesPerSector,
                minSize, maxSize);
        final Function<Sector, Set<Site>> siteMap = CachingFunction.wrap(cpr);

        double maxDist = spawnConfig.getMaxConnectedCitiesDistance();
        connectedCities = new SiteConnector(siteMap, maxDist);
        connectedCities = CachingFunction.wrap(connectedCities);

        sectorConnections = new SectorConnector(siteMap, connectedCities);
        sectorConnections = CachingFunction.wrap(sectorConnections);

        Function<UnorderedPair<Site>, Road> rg = new Function<UnorderedPair<Site>, Road>() {
            private RoadGeneratorSimple rgs = new RoadGeneratorSimple(junctions);
            private RoadModifierRandom rmr = new RoadModifierRandom(0.5);

            @Override
            public Road apply(UnorderedPair<Site> input) {
                Road road = rgs.apply(input);
                rmr.apply(road);
                return road;
            }

        };

        final Function<UnorderedPair<Site>, Road> cachedRoadgen = CachingFunction.wrap(rg);

        roadMap = new Function<Sector, Set<Road>>() {

            @Override
            public Set<Road> apply(Sector sector) {
                Set<Road> allRoads = Sets.newHashSet();

                Set<UnorderedPair<Site>> localConns = sectorConnections.apply(sector);
                Set<UnorderedPair<Site>> allConns = Sets.newHashSet(localConns);
                Set<Lake> allBlockedAreas = Sets.newHashSet(lakeMap.apply(sector));

                // add all neighbors, because their roads might be passing through
                for (Orientation dir : Orientation.values()) {
                    Sector neighbor = sector.getNeighbor(dir);

                    allConns.addAll(sectorConnections.apply(neighbor));
                    allBlockedAreas.addAll(lakeMap.apply(sector));
                }

                for (UnorderedPair<Site> conn : allConns) {
                    Road road = cachedRoadgen.apply(conn);

                    if (!isBlocked(road, lakeMap.apply(sector))) {
                        allRoads.add(road);
                    }
                }

                return allRoads;
            }

            public boolean isBlocked(Road road, Set<? extends NamedArea> blockedAreas) {
                for (Vector2i pt : road.getPoints()) {
                    Vector2d v = new Vector2d(pt.x, pt.y);
                    for (NamedArea area : blockedAreas) {
                        if (area.contains(v)) {
                            return true;
                        }
                    }
                }

                return false;
            }
        };

        roadMap = CachingFunction.wrap(roadMap);

        roadShapeFunc = new RoadShapeGenerator(roadMap);
        roadShapeFunc = CachingFunction.wrap(roadShapeFunc);

        final DefaultTownWallGenerator twg = new DefaultTownWallGenerator(seed, heightMap);
        final LotGeneratorRandom housingLotGenerator = new LotGeneratorRandom(seed);
        final LotGeneratorRandom churchLotGenerator = new LotGeneratorRandom(seed, 25d, 40d, 1, 100);
        final SimpleHousingGenerator blgGenerator = new SimpleHousingGenerator(seed, heightMap);
        final SimpleFenceGenerator sfg = new SimpleFenceGenerator(seed);
        final SimpleChurchGenerator sacg = new SimpleChurchGenerator(seed, heightMap);

        decoratedCities = CachingFunction.wrap(new Function<Sector, Set<City>>() {

            @Override
            public Set<City> apply(Sector input) {

                int sectorSeed = Objects.hashCode(seed, input);
                TownNameProvider nameGen = new TownNameProvider(sectorSeed, new DebugTownTheme());
                Stopwatch pAll = null;
                Stopwatch pSites = null;
                Stopwatch pRoads = null;

                if (logger.isInfoEnabled()) {
                    pAll = Stopwatch.createStarted();
                }

                if (logger.isInfoEnabled()) {
                    pSites = Stopwatch.createStarted();
                }

                Set<Site> sites = siteMap.apply(input);

                if (logger.isInfoEnabled()) {
                    logger.info("Generated settlement sites for {} in {}ms.", input,
                            pSites.elapsed(TimeUnit.MILLISECONDS));
                }

                if (logger.isInfoEnabled()) {
                    pRoads = Stopwatch.createStarted();
                }

                Shape roadShape = roadShapeFunc.apply(input);

                if (logger.isInfoEnabled()) {
                    logger.info("Generated roads for {} in {}ms.", input, pRoads.elapsed(TimeUnit.MILLISECONDS));
                }

                Set<City> cities = Sets.newHashSet();

                for (Site site : sites) {

                    Stopwatch pSite = null;
                    if (logger.isInfoEnabled()) {
                        pSite = Stopwatch.createStarted();
                    }

                    int minX = site.getPos().x - site.getRadius();
                    int minZ = site.getPos().y - site.getRadius();

                    Rectangle cityArea = new Rectangle(minX, minZ, site.getRadius() * 2, site.getRadius() * 2);
                    HeightMap cityAreaHeightMap = HeightMaps.caching(heightMap, cityArea, 4);

                    AreaInfo si = new AreaInfo(terrainConfig, cityAreaHeightMap);
                    si.addBlockedArea(roadShape);

                    String name = nameGen.generateName(TownAffinityVector.create().prefix(0.2).postfix(0.2));
                    MedievalTown town = new MedievalTown(name, site.getPos(), site.getRadius());

                    // add a town wall if radius is larger than 1/4
                    int minRadForTownWall = (spawnConfig.getMinCityRadius() * 3 + spawnConfig.getMaxCityRadius())
                            / 4;

                    if (town.getRadius() > minRadForTownWall) {
                        TownWall tw = twg.generate(town, si);
                        town.setTownWall(tw);

                        TownWallShapeGenerator twsg = new TownWallShapeGenerator();
                        Shape townWallShape = twsg.computeShape(tw);
                        si.addBlockedArea(townWallShape);
                    }

                    Set<SimpleLot> churchLots = churchLotGenerator.generate(town, si);
                    if (!churchLots.isEmpty()) {
                        SimpleLot lot = churchLots.iterator().next();
                        SimpleChurch church = sacg.generate(lot);
                        lot.addBuilding(church);
                        town.add(lot);
                    }

                    Set<SimpleLot> housingLots = housingLotGenerator.generate(town, si);

                    for (SimpleLot lot : housingLots) {
                        town.add(lot);

                        for (SimpleBuilding bldg : blgGenerator.apply(lot)) {
                            lot.addBuilding(bldg);
                            SimpleFence fence = sfg.createFence(town, lot.getShape());
                            lot.setFence(fence);
                        }
                    }

                    if (logger.isInfoEnabled()) {
                        logger.info("Generated city '{}' in {} in {}ms.", town, input,
                                pSite.elapsed(TimeUnit.MILLISECONDS));
                    }

                    cities.add(town);
                }

                if (logger.isInfoEnabled()) {
                    logger.info("Generated {} .. in {}ms.", input, pAll.elapsed(TimeUnit.MILLISECONDS));
                }

                return cities;
            }
        });

        // this required by WorldEventReceiver
        CoreRegistry.put(WorldFacade.class, this);
    }

    /**
     * Clears the caches
     */
    public void expungeCache() {
        decoratedCities.invalidateAll();
    }

    /**
     * @param sector the sector
     * @return a shape that describes the area of all roads
     */
    public Set<Road> getRoads(Sector sector) {
        return roadMap.apply(sector);
    }

    /**
     * @param sector the sector
     * @return all cities in that sector
     */
    public Set<City> getCities(Sector sector) {
        return decoratedCities.apply(sector);
    }

    /**
     * @param sector the sector
     * @return a set of all lakes in that sector
     */
    public Set<Lake> getLakes(Sector sector) {
        return lakeMap.apply(sector);
    }

    public static EntityRef getWorldEntity() {
        EntityManager entityManager = CoreRegistry.get(EntityManager.class);

        for (EntityRef entity : entityManager.getEntitiesWith(WorldComponent.class)) {
            return entity;
        }
        return EntityRef.NULL;
    }
}