diff --git a/.gitignore b/.gitignore index 779ebd2..84de5ce 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,9 @@ pom.xml.asc .hg/ .clj-kondo/* .lsp/* + +*.log + +.calva/* + +*.so diff --git a/project.clj b/project.clj index 4eca753..60fb6c8 100644 --- a/project.clj +++ b/project.clj @@ -2,13 +2,45 @@ :description "A game about avoiding climate catastrophe" :license {:name "GNU General Public License,version 2.0 or (at your option) any later version" :url "https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html"} - :dependencies [[org.clojure/clojure "1.10.3"]] - :main ^:skip-aot climate-game.core + :dependencies [[jme-clj "0.1.13"] + [com.jme3/jmonkeyengine3 "3.0.0-SNAPSHOT"] + [com.jme3/jME3-testdata "3.0.0-SNAPSHOT"] + [com.jme3/jME3-terrain "3.0.0-SNAPSHOT"] + [com.jme3/jME3-plugins "3.0.0-SNAPSHOT"] + [com.jme3/jME3-niftygui "3.0.0-SNAPSHOT"] + [com.jme3/jME3-plugins "3.0.0-SNAPSHOT"] + [com.jme3/jME3-lwjgl "3.0.0-SNAPSHOT"] + [com.jme3/jME3-lwjgl-natives "3.0.0-SNAPSHOT"] + [com.jme3/jME3-jogg "3.0.0-SNAPSHOT"] + [com.jme3/jME3-jbullet "3.0.0-SNAPSHOT"] + [com.jme3/jME3-desktop "3.0.0-SNAPSHOT"] + [com.jme3/jME3-core "3.0.0-SNAPSHOT"] + [com.jme3/jME3-blender "3.0.0-SNAPSHOT"] + [com.jme3/jME3-effects "3.0.0.20121220-SNAPSHOT"] + [com.jme3/jME3-networking "3.0.0.20121220-SNAPSHOT"] + [com.jme3/j-ogg-oggd "3.0.0-SNAPSHOT"] + [com.jme3/j-ogg-vorbisd "3.0.0-SNAPSHOT"] + [com.jme3/eventbus "3.0.0-SNAPSHOT"] + [com.jme3/jbullet "3.0.0-SNAPSHOT"] + [com.jme3/jinput "3.0.0-SNAPSHOT"] + [com.jme3/lwjgl "3.0.0-SNAPSHOT"] + [com.jme3/nifty "3.0.0-SNAPSHOT"] + [com.jme3/nifty-default-controls "3.0.0-SNAPSHOT"] + [com.jme3/nifty-style-black "3.0.0-SNAPSHOT"] + [com.jme3/nifty-examples "3.0.0-SNAPSHOT"] + [com.jme3/noise "3.0.0-SNAPSHOT"] + [com.jme3/stack-alloc "3.0.0-SNAPSHOT"] + [com.jme3/vecmath "3.0.0-SNAPSHOT"] + [com.jme3/xmlpull-xpp3 "3.0.0-SNAPSHOT"] + [org.clojure/clojure "1.10.3"]] + :java-source-paths ["src/java"] + :main ^:skip-aot cc.journeyman.climate-game.core :plugins [[lein-cloverage "1.2.2"] [lein-codox "0.10.7-cloverage"]] :profiles {:uberjar {:aot :all :jvm-opts ["-Dclojure.compiler.direct-linking=true"]}} + :repositories {"oss-sonatype" + "https://oss.sonatype.org/content/repositories/snapshots/"} + :source-paths ["src/clj"] :target-path "target/%s" - :url "http://example.com/FIXME" - - ) + :url "http://example.com/FIXME") diff --git a/src/cc/journeyman/climate_game/core.clj b/src/cc/journeyman/climate_game/core.clj deleted file mode 100644 index 4bdc0ce..0000000 --- a/src/cc/journeyman/climate_game/core.clj +++ /dev/null @@ -1,7 +0,0 @@ -(ns cc.journeyman.climate-game.core - (:gen-class)) - -(defn -main - "I don't do a whole lot ... yet." - [& args] - (println "Hello, World!")) diff --git a/src/clj/cc/journeyman/climate_game/core.clj b/src/clj/cc/journeyman/climate_game/core.clj new file mode 100644 index 0000000..45ba939 --- /dev/null +++ b/src/clj/cc/journeyman/climate_game/core.clj @@ -0,0 +1,22 @@ +(ns cc.journeyman.climate-game.core + (:require [jme-clj.core :refer [add-to-root box defsimpleapp geo material set* start]]) + (:import [aaronperkins.planeteg PlanetApp] + [com.jme3.math ColorRGBA Vector3f] + [com.jme3.app SimpleApplication] + [com.jme3.scene Geometry] + [com.jme3.material Material] + [com.jme3.light DirectionalLight])) + +;; (defn init [] +;; (let [box (box 1 1 1) +;; geom (geo "Box" box) +;; mat (material "Common/MatDefs/Misc/Unshaded.j3md")] +;; (set* mat :color "Color" ColorRGBA/Blue) +;; (set* geom :material mat) +;; (add-to-root geom))) + +;; (defsimpleapp app :init init) + +(def app (PlanetApp.)) + +(start app) \ No newline at end of file diff --git a/src/java/aaronperkins/planeteg/PlanetApp.java b/src/java/aaronperkins/planeteg/PlanetApp.java new file mode 100644 index 0000000..e38984e --- /dev/null +++ b/src/java/aaronperkins/planeteg/PlanetApp.java @@ -0,0 +1,57 @@ +package aaronperkins.planeteg; + +import com.jme3.app.SimpleApplication; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.material.Material; +import com.jme3.light.DirectionalLight; + +/** + * Stolen in whole cloth from https://gist.github.com/aaronperkins/1775883 + */ +public class PlanetApp extends SimpleApplication { + + Geometry planet; + + public static void main(String[] args){ + PlanetApp app = new PlanetApp(); + app.start(); + } + + @Override + public void simpleInitApp() { + + // Setup camera + this.getCamera().setLocation(new Vector3f(0,0,1000)); + this.getFlyByCamera().setMoveSpeed(200.0f); + + // Add sun + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f)); + rootNode.addLight(sun); + + // Add planet + planet = new Geometry("Planet"); + + PlanetMeshGen planetMeshGen = new PlanetMeshGen(); + planetMeshGen.generateHeightmap(); + planet.setMesh(planetMeshGen.generateMesh()); + + Material mat = new Material(this.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); + mat.setBoolean("UseVertexColor", true); + // Uncommet for wireframe + //mat.getAdditionalRenderState().setWireframe(true); + + planet.setMaterial(mat); + + rootNode.attachChild(planet); + + } + + @Override + public void simpleUpdate(float tpf) { + planet.rotate(0, 0.005f*tpf, 0); + } + +} + diff --git a/src/java/aaronperkins/planeteg/PlanetMeshGen.java b/src/java/aaronperkins/planeteg/PlanetMeshGen.java new file mode 100644 index 0000000..23805e6 --- /dev/null +++ b/src/java/aaronperkins/planeteg/PlanetMeshGen.java @@ -0,0 +1,330 @@ +package aaronperkins.planeteg; + +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import com.jme3.math.FastMath; + +import java.util.List; +import java.util.ArrayList; +import java.util.Random; + +/** + * Stolen in whole cloth from https://gist.github.com/aaronperkins/1775883 + * PlanetMeshGen + * Generates a planet from a random heightmap. + * Orginal source: + * http://ahuynh.posterous.com/article-1-generating-a-planet-in-opengl + * Adapted for jmonkeyengine by: + * ajperkins@gmail.com + */ +public class PlanetMeshGen { + + // Radius of planet + protected float planetRadius; + // Width of heightmap + protected int heightmapWidth; + // Stores heightmap data + protected float heightmapData[]; + + public PlanetMeshGen() { + + } + + public Mesh generateMesh () { + return generateMesh(250); + } + + public Mesh generateMesh (float radius) { + planetRadius = radius; + + Mesh mesh = new Mesh(); + + int gammaSamples = heightmapWidth; + int thetaSamples = (heightmapWidth - 1) * 2; + + List vertexList = new ArrayList(); + List normalList = new ArrayList(); + List indexList = new ArrayList(); + List colorList = new ArrayList(); + + // Horizontal points + float gammaStep = 2 * FastMath.PI / thetaSamples; + + // Vertical points + float thetaStep = FastMath.PI / ( gammaSamples - 1 ); + + // Generate vertices + for( int i = 0; i < thetaSamples; i++ ) { + float gamma = i * gammaStep; + for( int j = 0; j < gammaSamples; j++ ) { + float theta = j * thetaStep; + + Vector3f pt = new Vector3f(); + pt.x = planetRadius * FastMath.sin( theta ) * FastMath.cos( gamma ); + pt.y = planetRadius * FastMath.cos( theta ); + pt.z = planetRadius * FastMath.sin( theta ) * FastMath.sin( gamma ); + + float height = getHeight(i,j); + + vertexList.add( pt.normalize().mult(planetRadius + height) ); + + // Set vertex colors + if( height <= 1f ) { + colorList.add(0.0f); + colorList.add(0.4f); + colorList.add(0.8f); + colorList.add(1.0f); // Ocean + } else if( height <= 1.5f ) { + colorList.add(0.83f); + colorList.add(0.72f); + colorList.add(0.34f); + colorList.add(1.0f); // Sand + } else if( height <= 10f ) { + colorList.add(0.2f); + colorList.add(0.6f); + colorList.add(0.1f); + colorList.add(1.0f); // Grass + } else { + colorList.add(0.5f); + colorList.add(0.5f); + colorList.add(0.5f); + colorList.add(1.0f); // Mountains + } + } + } + + // Generate normals + for( int i = 0; i < thetaSamples; i++ ) { + for( int j = 0; j < gammaSamples; j++ ) { + int i1 = i * gammaSamples + j; + int i2 = ( ( i + 1 ) % thetaSamples ) * gammaSamples + j; + int i3 = ( ( i + 1 ) % thetaSamples ) * gammaSamples + j + 1; + int i4 = i * gammaSamples + j + 1; + + if( j >= gammaSamples-1 ) { + i3 = gammaSamples + j + 1; + i4 = j + 1; + } + + Vector3f v1 = vertexList.get(i1); + Vector3f v2 = vertexList.get(i2); + Vector3f v3 = vertexList.get(i3); + Vector3f v4 = vertexList.get(i4); + + Vector3f normal; + Vector3f t1, t2, t3, t4; + Vector3f n1, n2, n3, n4; + + t1 = v1.subtract( v1 ); + t2 = v2.subtract( v3 ); + t3 = v3.subtract( v4 ); + t4 = v4.subtract( v1 ); + + n1 = t1.cross( t2 ).normalize(); + n2 = t2.cross( t3 ).normalize(); + n3 = t3.cross( t4 ).normalize(); + n4 = t4.cross( t1 ).normalize(); + + normal = n1.add( n2 ).add( n3 ).add( n4 ).normalize(); + normalList.add(normal); + } + } + + // Generate indices + for( int i = 0; i < thetaSamples; i++ ) { + for( int j = 0; j < gammaSamples-1; j++ ) { + Integer i1 = i * gammaSamples + j; + Integer i2 = ( ( i + 1 ) % thetaSamples ) * gammaSamples + j; + Integer i3 = ( ( i + 1 ) % thetaSamples ) * gammaSamples + j + 1; + Integer i4 = i * gammaSamples + j + 1; + + indexList.add( i1 ); + indexList.add( i2 ); + indexList.add( i3 ); + + indexList.add( i1 ); + indexList.add( i3 ); + indexList.add( i4 ); + } + } + + // Set buffers + mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertexList.toArray(new Vector3f[0]))); + mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normalList.toArray(new Vector3f[0]))); + mesh.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(toIntArray(indexList))); + mesh.setBuffer(Type.Color, 4, BufferUtils.createFloatBuffer(toFloatArray(colorList))); + mesh.updateBound(); + + return mesh; + } + + + /** + * Create the heightmap for the planet with default values. + * + */ + public void generateHeightmap( ) { + generateHeightmap( 750, 19580427, 30, 90, 25000, .8f, .3f ); + } + + + /** + * Create the heightmap for the planet. + * + * @param width The width of the heightmap. Larger values mean more complex mesh + * @param seed The random seed for generating the heightmap + * @param numIslands Total number of land masses + * @param islandRadius How big each land mass is + * @param iterations More interation equals more complex features + * @param displacment How high land features get + * @param smoothing Lower numbers mean smoother land features + */ + public void generateHeightmap(int width, int seed, int numIslands, float islandRadius, int iterations, float displacement, float smoothing ) { + heightmapWidth = width; + heightmapData = new float[heightmapWidth * heightmapWidth]; + + Random rGenerator = new Random(seed); + + for( int j = 0; j < numIslands; j++ ) { + + // Find a random spot to grow an island + int sx = rGenerator.nextInt(heightmapWidth); + int sy = rGenerator.nextInt(heightmapWidth); + int x = sx, y = sy; + for( int i = 0; i < iterations; i++ ) { + float d = getData( x, y ); + // Check neighbors + if( getData( x-1, y ) < d ) { + setData( x-1, y, getData( x-1, y ) + displacement ); + } else if( getData( x+1, y ) < d ) { + setData( x+1, y, getData( x+1, y ) + displacement ); + } else if( this.getData( x, y-1 ) < d ) { + setData( x, y-1, getData( x, y-1 ) + displacement ); + } else if( this.getData( x, y+1 ) < d ) { + setData( x, y+1, getData( x, y+1 ) + displacement ); + } else { + setData( x, y, d + displacement ); + } + + switch( rGenerator.nextInt(4) ) { + case 0: + y++; + if( inCircle( sx, sy, x, y, islandRadius ) && + y > 0 && y < heightmapWidth ) { + break; + } else { + y--; + } + case 1: + y--; + if( inCircle( sx, sy, x, y, islandRadius ) && + y > 0 && y < heightmapWidth ) { + break; + } else { + y++; + } + case 2: + x++; + if( inCircle( sx, sy, x, y, islandRadius ) && + x > 0 && x < heightmapWidth ) { + break; + } else { + x--; + } + case 3: + x--; + if( inCircle( sx, sy, x, y, islandRadius ) && + x > 0 && x < heightmapWidth ) { + break; + } else { + x++; + } + } + } + } + + smooth( smoothing ); + } + + protected void smooth( float k ) { + for( int x = 1; x < heightmapWidth; x++ ) { + for( int z = 0; z < heightmapWidth; z++ ) { + heightmapData[ x * heightmapWidth + z ] = + heightmapData[ (x-1) * heightmapWidth + z ] * ( 1 - k ) + + heightmapData[ x * heightmapWidth + z ] * k; + } + } + + for( int x = heightmapWidth-2; x >= 0; x-- ) { + for( int z = 0; z < heightmapWidth; z++ ) { + heightmapData[ x * heightmapWidth + z ] = + heightmapData[ (x+1) * heightmapWidth + z ] * ( 1 - k ) + + heightmapData[ x * heightmapWidth + z ] * k; + } + } + + for( int x = 0; x < heightmapWidth; x++ ) { + for( int z = heightmapWidth-2; z >= 0; z-- ) { + heightmapData[ x * heightmapWidth + z ] = + heightmapData[ x * heightmapWidth + (z+1) ] * ( 1 - k ) + + heightmapData[ x * heightmapWidth + z ] * k; + } + } + + for( int x = 0; x < heightmapWidth; x++ ) { + for( int z = 1; z < heightmapWidth; z++ ) { + heightmapData[ x * heightmapWidth + z ] = + heightmapData[ x * heightmapWidth + (z-1) ] * ( 1 - k ) + + heightmapData[ x * heightmapWidth + z ] * k; + } + } + } + + protected float getHeight( int i, int j ) { + float offset; + if( i >= heightmapWidth ) { + offset = heightmapData[ ( ( 2 * heightmapWidth - 1 ) - i ) * heightmapWidth + j ]; + } else { + offset = heightmapData[ i * heightmapWidth + j ]; + } + + return offset; + } + + protected float getData( int x, int y ) { + int index = x * heightmapWidth + y; + if (index < heightmapData.length && index >= 0) + return heightmapData[ index ]; + else + return 0f; + } + + protected void setData( int x, int y, float val ) { + int index = x * heightmapWidth + y; + if (index < heightmapData.length) + heightmapData[ index ] = val; + } + + protected boolean inCircle ( int sx, int sy, int x, int y, float r ) { + return ( FastMath.pow( x-sx, 2) + FastMath.pow( y-sy, 2 ) ) < FastMath.pow( r, 2 ); + } + + protected int[] toIntArray(List list) { + int[] ret = new int[list.size()]; + int i = 0; + for (Integer e : list) + ret[i++] = e.intValue(); + return ret; + } + + protected float[] toFloatArray(List list) { + float[] ret = new float[list.size()]; + int i = 0; + for (Float e : list) + ret[i++] = e.floatValue(); + return ret; + } + +} // End PlanetMeshGen Class