mirror of
https://github.com/Dejvino/roadtrip
synced 2025-04-02 19:44:20 +00:00
284 lines
13 KiB
Java
284 lines
13 KiB
Java
package roadtrip.view;
|
|
|
|
import com.jme3.asset.AssetManager;
|
|
import com.jme3.bullet.PhysicsSpace;
|
|
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
|
|
import com.jme3.bullet.collision.shapes.ConeCollisionShape;
|
|
import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
|
|
import com.jme3.bullet.control.RigidBodyControl;
|
|
import com.jme3.light.AmbientLight;
|
|
import com.jme3.light.DirectionalLight;
|
|
import com.jme3.material.Material;
|
|
import com.jme3.math.ColorRGBA;
|
|
import com.jme3.math.Vector2f;
|
|
import com.jme3.math.Vector3f;
|
|
import com.jme3.renderer.Camera;
|
|
import com.jme3.scene.Node;
|
|
import com.jme3.scene.Spatial;
|
|
import com.jme3.terrain.geomipmap.*;
|
|
import com.jme3.terrain.geomipmap.grid.FractalTileLoader;
|
|
import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator;
|
|
import com.jme3.terrain.geomipmap.lodcalc.LodCalculator;
|
|
import com.jme3.terrain.noise.ShaderUtils;
|
|
import com.jme3.terrain.noise.basis.FilteredBasis;
|
|
import com.jme3.terrain.noise.filter.IterativeFilter;
|
|
import com.jme3.terrain.noise.filter.OptimizedErode;
|
|
import com.jme3.terrain.noise.filter.PerturbFilter;
|
|
import com.jme3.terrain.noise.filter.SmoothFilter;
|
|
import com.jme3.terrain.noise.fractal.FractalSum;
|
|
import com.jme3.terrain.noise.modulator.NoiseModulator;
|
|
import com.jme3.texture.Texture;
|
|
import java.util.Random;
|
|
import roadtrip.model.MapObjectInstance;
|
|
import roadtrip.model.ProceduralMapQuadBlock;
|
|
import roadtrip.model.TerrainDataProvider;
|
|
import roadtrip.view.model.GameWorldState;
|
|
|
|
/**
|
|
* Created by dejvino on 14.01.2017.
|
|
*/
|
|
public class GameWorldView {
|
|
|
|
public static boolean DEBUG = false;//true;
|
|
|
|
private final GameWorldState state;
|
|
|
|
private final AssetManager assetManager;
|
|
private final Camera camera;
|
|
private final Node rootNode;
|
|
private final PhysicsSpace physicsSpace;
|
|
|
|
public TerrainView terrain = new TerrainView(new TerrainDataProvider());
|
|
|
|
public GameWorldView(GameWorldState gameWorldState, AssetManager assetManager, Camera camera, Node rootNode, PhysicsSpace physicsSpace) {
|
|
this.state = gameWorldState;
|
|
this.assetManager = assetManager;
|
|
this.camera = camera;
|
|
this.rootNode = rootNode;
|
|
this.physicsSpace = physicsSpace;
|
|
}
|
|
|
|
public static GameWorldView create(GameWorldState gameWorldState, AssetManager assetManager, Camera camera, Node rootNode, PhysicsSpace physicsSpace) {
|
|
GameWorldView gameWorldView = new GameWorldView(gameWorldState, assetManager, camera, rootNode, physicsSpace);
|
|
gameWorldView.initialize();
|
|
return gameWorldView;
|
|
}
|
|
|
|
private void initialize()
|
|
{
|
|
// Environment
|
|
DirectionalLight dl = new DirectionalLight();
|
|
dl.setColor(ColorRGBA.LightGray);
|
|
dl.setDirection(new Vector3f(1, -1, 1));
|
|
rootNode.addLight(dl);
|
|
AmbientLight al = new AmbientLight();
|
|
al.setColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
|
|
rootNode.addLight(al);
|
|
|
|
// TERRAIN TEXTURE material
|
|
terrain.mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md");
|
|
if (DEBUG) {
|
|
terrain.mat_terrain.getAdditionalRenderState().setWireframe(true);
|
|
}
|
|
|
|
float heightScale = 400f;
|
|
|
|
// Parameters to material:
|
|
// regionXColorMap: X = 1..4 the texture that should be appliad to state X
|
|
// regionX: a Vector3f containing the following information:
|
|
// regionX.x: the start height of the region
|
|
// regionX.y: the end height of the region
|
|
// regionX.z: the texture scale for the region
|
|
// it might not be the most elegant way for storing these 3 values, but it packs the data nicely :)
|
|
// slopeColorMap: the texture to be used for cliffs, and steep mountain sites
|
|
// slopeTileFactor: the texture scale for slopes
|
|
// terrainSize: the total size of the terrain (used for scaling the texture)
|
|
// GRASS texture
|
|
Texture grass = this.assetManager.loadTexture("Textures/solid-grass.png");
|
|
grass.setWrap(Texture.WrapMode.Repeat);
|
|
Texture dirt = this.assetManager.loadTexture("Textures/solid-road.png");
|
|
dirt.setWrap(Texture.WrapMode.Repeat);
|
|
Texture rock = this.assetManager.loadTexture("Textures/solid-stone.png");
|
|
rock.setWrap(Texture.WrapMode.Repeat);
|
|
|
|
float modif = (heightScale / 100f) / 3f;
|
|
terrain.mat_terrain.setTexture("region1ColorMap", dirt);
|
|
terrain.mat_terrain.setVector3("region1", new Vector3f(0, 80 * modif, terrain.dirtScale));
|
|
|
|
terrain.mat_terrain.setTexture("region2ColorMap", grass);
|
|
terrain.mat_terrain.setVector3("region2", new Vector3f(100 * modif, 160 * modif, terrain.grassScale));
|
|
|
|
terrain.mat_terrain.setTexture("region3ColorMap", rock);
|
|
terrain.mat_terrain.setVector3("region3", new Vector3f(190 * modif, 240 * modif, terrain.rockScale));
|
|
|
|
terrain.mat_terrain.setTexture("region4ColorMap", dirt);
|
|
terrain.mat_terrain.setVector3("region4", new Vector3f(250 * modif, 350 * modif, terrain.dirtScale));
|
|
|
|
terrain.mat_terrain.setTexture("slopeColorMap", rock);
|
|
terrain.mat_terrain.setFloat("slopeTileFactor", 32);
|
|
|
|
terrain.mat_terrain.setFloat("terrainSize", 513);
|
|
|
|
terrain.terrainDataProvider.base = new FractalSum();
|
|
terrain.terrainDataProvider.base.setRoughness(0.6f);
|
|
terrain.terrainDataProvider.base.setFrequency(1.0f);
|
|
terrain.terrainDataProvider.base.setAmplitude(1.0f);
|
|
terrain.terrainDataProvider.base.setLacunarity(4.12f);
|
|
terrain.terrainDataProvider.base.setOctaves(8);
|
|
terrain.terrainDataProvider.base.setScale(0.02125f);
|
|
terrain.terrainDataProvider.base.addModulator(new NoiseModulator() {
|
|
|
|
@Override
|
|
public float value(float... in) {
|
|
return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1);
|
|
}
|
|
});
|
|
|
|
FilteredBasis ground = new FilteredBasis(terrain.terrainDataProvider.base);
|
|
|
|
terrain.terrainDataProvider.perturb = new PerturbFilter();
|
|
terrain.terrainDataProvider.perturb.setMagnitude(0.119f);
|
|
|
|
terrain.terrainDataProvider.therm = new OptimizedErode();
|
|
terrain.terrainDataProvider.therm.setRadius(5);
|
|
terrain.terrainDataProvider.therm.setTalus(0.011f);
|
|
|
|
terrain.terrainDataProvider.smooth = new SmoothFilter();
|
|
terrain.terrainDataProvider.smooth.setRadius(1);
|
|
terrain.terrainDataProvider.smooth.setEffect(0.3f);
|
|
|
|
terrain.terrainDataProvider.iterate = new IterativeFilter();
|
|
terrain.terrainDataProvider.iterate.addPreFilter(terrain.terrainDataProvider.perturb);
|
|
terrain.terrainDataProvider.iterate.addPostFilter(terrain.terrainDataProvider.smooth);
|
|
terrain.terrainDataProvider.iterate.setFilter(terrain.terrainDataProvider.therm);
|
|
terrain.terrainDataProvider.iterate.setIterations(2);
|
|
|
|
ground.addPreFilter(terrain.terrainDataProvider.iterate);
|
|
|
|
int patchSize = 16;
|
|
Vector3f terrainScale = new Vector3f(8f, 1f, 8f);
|
|
//terrain.terrainGrid = new TerrainGrid("terrain", 16 + 1, 512 + 1, new FractalTileLoader(ground, 300f));
|
|
terrain.terrainGrid = new FineTerrainGrid("terrain", patchSize + 1, 128 + 1, terrainScale, new FractalTileLoader(ground, heightScale));
|
|
|
|
terrain.terrainGrid.setMaterial(terrain.mat_terrain);
|
|
//terrain.terrainGrid.setLocalTranslation(0, -200, 0);
|
|
terrain.terrainGrid.setLocalScale(terrainScale);
|
|
this.rootNode.attachChild(terrain.terrainGrid);
|
|
|
|
final TerrainLodControl lodControl = new FineTerrainGridLodControl(terrain.terrainGrid, camera);
|
|
lodControl.setLodCalculator(new DistanceLodCalculator(patchSize + 1, 3.7f)); // patch size, and a multiplier
|
|
terrain.terrainGrid.addControl(lodControl);
|
|
|
|
final Spatial treeModel = assetManager.loadModel("Models/tree.j3o");
|
|
final Spatial houseModel = assetManager.loadModel("Models/house1.j3o");
|
|
|
|
final FineTerrainGrid terrainGrid = terrain.terrainGrid;
|
|
terrainGrid.addListener(new TerrainGridListener() {
|
|
|
|
@Override
|
|
public void gridMoved(Vector3f newCenter) {
|
|
}
|
|
|
|
@Override
|
|
public void tileAttached(Vector3f cell, TerrainQuad quad) {
|
|
lodControl.forceUpdate();
|
|
while(quad.getControl(RigidBodyControl.class)!=null){
|
|
quad.removeControl(RigidBodyControl.class);
|
|
}
|
|
quad.addControl(new RigidBodyControl(new HeightfieldCollisionShape(quad.getHeightMap(), terrainGrid.getLocalScale()), 0));
|
|
physicsSpace.add(quad);
|
|
|
|
removeQuadObjectsNode(quad);
|
|
|
|
String quadObjectsNodeKey = getQuadObjectsNodeKey(quad);
|
|
Node objects = new Node(quadObjectsNodeKey);
|
|
populateQuadObjectsNode(quad, objects);
|
|
System.out.println("Add quad " + quad.getName());
|
|
rootNode.attachChild(objects);
|
|
}
|
|
|
|
protected void populateQuadObjectsNode(TerrainQuad quad, Node objects)
|
|
{
|
|
ProceduralMapQuadBlock mapQuadBlock = state.proceduralMap.getMapQuadBlock(quad);
|
|
|
|
/*/ DEBUG pole in the middle of a quad
|
|
Spatial m = treeModel.clone();
|
|
m.setLocalTranslation(quad.getWorldTranslation().add(new Vector3f(0f, getHeight(quad, quad.getWorldTranslation()), 0f)));
|
|
m.setLocalScale(new Vector3f(1f, 20f, 1f));
|
|
objects.attachChild(m);
|
|
/**/
|
|
|
|
// Add map objects
|
|
Random rand = mapQuadBlock.getBlockRandom();
|
|
for (MapObjectInstance mapObject : mapQuadBlock.getMapObjects()) {
|
|
Vector3f pos = mapObject.getPosition();
|
|
Vector3f scale = Vector3f.UNIT_XYZ;
|
|
Vector3f boxHalf = Vector3f.UNIT_XYZ;
|
|
Spatial modelInstance;
|
|
RigidBodyControl modelPhysics;
|
|
switch (mapObject.getType()) {
|
|
case "tree":
|
|
modelInstance = treeModel.clone();
|
|
float s = 0.2f + rand.nextFloat() * 5f;
|
|
scale = new Vector3f(s, s, s);
|
|
boxHalf = new Vector3f(s * 0.2f, s * 3f, s * 0.2f);
|
|
modelPhysics = new RigidBodyControl(new BoxCollisionShape(boxHalf), 0f);
|
|
break;
|
|
case "house":
|
|
modelInstance = houseModel.clone();
|
|
boxHalf = new Vector3f(2f + rand.nextFloat() * 10f, 2f + rand.nextFloat() * 10f, 2f + rand.nextFloat() * 10f);
|
|
scale = boxHalf;
|
|
modelPhysics = new RigidBodyControl(new BoxCollisionShape(boxHalf), 0f);
|
|
break;
|
|
default:
|
|
throw new RuntimeException("Unhandled object type: " + mapObject.getType());
|
|
}
|
|
modelInstance.setLocalTranslation(pos);
|
|
modelInstance.setLocalScale(scale);
|
|
// TODO: physics from the model and not hard-coded
|
|
//RigidBodyControl control = treeInstance.getControl(RigidBodyControl.class);
|
|
if (modelPhysics != null) {
|
|
modelPhysics.isActive();
|
|
modelInstance.addControl(modelPhysics);
|
|
modelPhysics.setPhysicsLocation(pos);
|
|
physicsSpace.add(modelPhysics);
|
|
}
|
|
objects.attachChild(modelInstance);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void tileDetached(Vector3f cell, TerrainQuad quad) {
|
|
if (quad.getControl(RigidBodyControl.class) != null) {
|
|
physicsSpace.remove(quad);
|
|
quad.removeControl(RigidBodyControl.class);
|
|
}
|
|
removeQuadObjectsNode(quad);
|
|
System.out.println("Del quad " + quad.getName());
|
|
}
|
|
|
|
protected void removeQuadObjectsNode(TerrainQuad quad)
|
|
{
|
|
Spatial quadObjectsNodeOld = rootNode.getChild(getQuadObjectsNodeKey(quad));
|
|
if (quadObjectsNodeOld != null) {
|
|
physicsSpace.removeAll(quadObjectsNodeOld);
|
|
quadObjectsNodeOld.removeFromParent();
|
|
}
|
|
}
|
|
|
|
private String getQuadObjectsNodeKey(TerrainQuad quad)
|
|
{
|
|
return "Objects-" + quad.getName();
|
|
}
|
|
|
|
private float getHeight(TerrainQuad quad, Vector3f pos)
|
|
{
|
|
return quad.getHeight(new Vector2f(pos.x, pos.z));
|
|
}
|
|
});
|
|
/**/
|
|
|
|
}
|
|
|
|
}
|