diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java index 31c4a54cb..cc50f549f 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/TerrainAsset.java @@ -21,12 +21,18 @@ import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.math.Vector2; +import com.badlogic.gdx.utils.Array; import com.mbrlabs.mundus.commons.assets.meta.Meta; +import com.mbrlabs.mundus.commons.assets.meta.MetaTerrainLayer; import com.mbrlabs.mundus.commons.terrain.SplatMap; import com.mbrlabs.mundus.commons.terrain.SplatTexture; import com.mbrlabs.mundus.commons.terrain.Terrain; import com.mbrlabs.mundus.commons.terrain.TerrainLoader; import com.mbrlabs.mundus.commons.terrain.TerrainMaterial; +import com.mbrlabs.mundus.commons.terrain.attributes.TerrainLayerAttribute; +import com.mbrlabs.mundus.commons.terrain.layers.HeightTerrainLayer; +import com.mbrlabs.mundus.commons.terrain.layers.SlopeTerrainLayer; +import com.mbrlabs.mundus.commons.terrain.layers.TerrainLayer; import java.util.Map; @@ -50,6 +56,8 @@ public class TerrainAsset extends Asset { private TextureAsset splatBNormal; private TextureAsset splatGNormal; private TextureAsset splatANormal; + private Array heightLayers; + private Array slopeLayers; private Terrain terrain; @@ -304,6 +312,44 @@ public void resolveDependencies(Map assets) { if (id != null && assets.containsKey(id)) { setSplatANormal((TextureAsset) assets.get(id)); } + + // Height layers + if (meta.getTerrain().getHeightLayers() != null) { + for (MetaTerrainLayer metaLayer : meta.getTerrain().getHeightLayers()) { + if (heightLayers == null) heightLayers = new Array<>(); + + TextureAsset texAsset = (TextureAsset) assets.get(metaLayer.getTextureAssetId()); + + HeightTerrainLayer terrainLayer = new HeightTerrainLayer(texAsset, metaLayer.getMinHeight(), metaLayer.getMaxHeight()); + terrainLayer.setName(metaLayer.getName()); + terrainLayer.active = metaLayer.isActive(); + + if (metaLayer.getNormalTextureAssetId() != null) { + terrainLayer.normalTextureAsset = (TextureAsset) assets.get(metaLayer.getNormalTextureAssetId()); + } + + heightLayers.add(terrainLayer); + } + } + + // Slope layers + if (meta.getTerrain().getSlopeLayers() != null) { + for (MetaTerrainLayer metaLayer : meta.getTerrain().getSlopeLayers()) { + if (slopeLayers == null) slopeLayers = new Array<>(); + + TextureAsset texAsset = (TextureAsset) assets.get(metaLayer.getTextureAssetId()); + + SlopeTerrainLayer terrainLayer = new SlopeTerrainLayer(texAsset, metaLayer.getMinHeight(), metaLayer.getMaxHeight(), metaLayer.getSlopeStrength()); + terrainLayer.setName(metaLayer.getName()); + terrainLayer.active = metaLayer.isActive(); + + if (metaLayer.getNormalTextureAssetId() != null) { + terrainLayer.normalTextureAsset = (TextureAsset) assets.get(metaLayer.getNormalTextureAssetId()); + } + + slopeLayers.add(terrainLayer); + } + } } @Override @@ -367,6 +413,22 @@ public void applyDependencies() { terrainMaterial.setSplatNormalTexture(new SplatTexture(SplatTexture.Channel.A, splatANormal)); } + if (heightLayers == null) { + terrainMaterial.remove(TerrainLayerAttribute.HeightLayer); + } else { + TerrainLayerAttribute attr = new TerrainLayerAttribute(TerrainLayerAttribute.HeightLayer); + attr.terrainLayers.addAll(heightLayers); + terrainMaterial.set(attr); + } + + if (slopeLayers == null) { + terrainMaterial.remove(TerrainLayerAttribute.SlopeLayer); + } else { + TerrainLayerAttribute attr = new TerrainLayerAttribute(TerrainLayerAttribute.SlopeLayer); + attr.terrainLayers.addAll(slopeLayers); + terrainMaterial.set(attr); + } + terrain.update(); } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaLoader.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaLoader.java index 97284c573..65ad03f2c 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaLoader.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaLoader.java @@ -17,6 +17,7 @@ package com.mbrlabs.mundus.commons.assets.meta; import com.badlogic.gdx.files.FileHandle; +import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.JsonReader; import com.badlogic.gdx.utils.JsonValue; import com.mbrlabs.mundus.commons.assets.AssetType; @@ -74,9 +75,51 @@ private void parseTerrain(Meta meta, JsonValue jsonTerrain) { terrain.setSplatBNormal(jsonTerrain.getString(MetaTerrain.JSON_SPLAT_B_NORMAL, null)); terrain.setSplatANormal(jsonTerrain.getString(MetaTerrain.JSON_SPLAT_A_NORMAL, null)); + // Load Terrain Layers + Array hLayers = parseTerrainLayers(jsonTerrain.get(MetaTerrain.JSON_HEIGHT_LAYERS), false); + Array sLayers = parseTerrainLayers(jsonTerrain.get(MetaTerrain.JSON_SLOPE_LAYERS), true); + terrain.setHeightLayers(hLayers); + terrain.setSlopeLayers(sLayers); + meta.setTerrain(terrain); } + /** + * Converts JsonValue Array of terrain layers into MetaTerrainLayer for + * loading the layer in the future. + */ + private Array parseTerrainLayers(JsonValue layerArray, boolean isSlopeLayer) { + if (layerArray == null) return null; + + Array layers = null; + + // Get first layer of array + JsonValue child = layerArray.child; + + while (child != null) { + if (layers == null) layers = new Array<>(); + + MetaTerrainLayer metaTerrainLayer = new MetaTerrainLayer(); + metaTerrainLayer.setName(child.getString(MetaTerrain.JSON_HEIGHT_LAYER_NAME)); + metaTerrainLayer.setActive(child.getBoolean(MetaTerrain.JSON_HEIGHT_LAYER_ACTIVE)); + metaTerrainLayer.setTextureAssetId(child.getString(MetaTerrain.JSON_LAYER_TEXTURE_ASSET)); + metaTerrainLayer.setNormalTextureAssetId(child.getString(MetaTerrain.JSON_LAYER_NORMAL_TEXTURE_ASSET, null)); + metaTerrainLayer.setMaxHeight(child.getFloat(MetaTerrain.JSON_HEIGHT_LAYER_MAX_HEIGHT)); + metaTerrainLayer.setMinHeight(child.getFloat(MetaTerrain.JSON_HEIGHT_LAYER_MIN_HEIGHT)); + + if (isSlopeLayer) { + metaTerrainLayer.setSlopeStrength(child.getFloat(MetaTerrain.JSON_LAYER_SLOPE_STRENGTH)); + } + + layers.add(metaTerrainLayer); + + // Get next array element + child = child.next(); + } + + return layers; + } + private void parseModel(Meta meta, JsonValue jsonModel) { if(jsonModel == null) return; diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrain.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrain.java index 278e52f12..c742b0ce9 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrain.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrain.java @@ -16,6 +16,8 @@ package com.mbrlabs.mundus.commons.assets.meta; +import com.badlogic.gdx.utils.Array; + /** * * @author Marcus Brummer @@ -38,6 +40,15 @@ public class MetaTerrain { public static final String JSON_SPLAT_B_NORMAL = "bNorm"; public static final String JSON_SPLAT_A_NORMAL = "aNorm"; public static final String JSON_UV_SCALE= "uv"; + public static final String JSON_HEIGHT_LAYERS = "hLayers"; + public static final String JSON_SLOPE_LAYERS = "sLayers"; + public static final String JSON_LAYER_TEXTURE_ASSET = "asset"; + public static final String JSON_LAYER_NORMAL_TEXTURE_ASSET = "assetNorm"; + public static final String JSON_HEIGHT_LAYER_ACTIVE = "active"; + public static final String JSON_HEIGHT_LAYER_NAME = "name"; + public static final String JSON_HEIGHT_LAYER_MIN_HEIGHT = "minH"; + public static final String JSON_HEIGHT_LAYER_MAX_HEIGHT = "maxH"; + public static final String JSON_LAYER_SLOPE_STRENGTH = "slopeStr"; private int size; private int splatMapResolution; @@ -54,6 +65,8 @@ public class MetaTerrain { private String splatGNormal; private String splatBNormal; private String splatANormal; + private Array heightLayers; + private Array slopeLayers; public String getSplatmap() { return splatmap; @@ -175,6 +188,22 @@ public void setSplatANormal(String splatANormal) { this.splatANormal = splatANormal; } + public Array getHeightLayers() { + return heightLayers; + } + + public void setHeightLayers(Array heightLayers) { + this.heightLayers = heightLayers; + } + + public Array getSlopeLayers() { + return slopeLayers; + } + + public void setSlopeLayers(Array slopeLayers) { + this.slopeLayers = slopeLayers; + } + @Override public String toString() { return "MetaTerrain{" + diff --git a/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrainLayer.java b/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrainLayer.java new file mode 100644 index 000000000..3eddeb8fc --- /dev/null +++ b/commons/src/main/com/mbrlabs/mundus/commons/assets/meta/MetaTerrainLayer.java @@ -0,0 +1,72 @@ +package com.mbrlabs.mundus.commons.assets.meta; + +/** + * Simple POJO for the loading and saving of Terrain Layers + * @author JamesTKhan + * @version November 09, 2022 + */ +public class MetaTerrainLayer { + private String name; + private String textureAssetId; + private String normalTextureAssetId; + private boolean active; + private float minHeight; + private float maxHeight; + private float slopeStrength; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTextureAssetId() { + return textureAssetId; + } + + public void setTextureAssetId(String textureAssetId) { + this.textureAssetId = textureAssetId; + } + + public String getNormalTextureAssetId() { + return normalTextureAssetId; + } + + public void setNormalTextureAssetId(String normalTextureAssetId) { + this.normalTextureAssetId = normalTextureAssetId; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public float getMinHeight() { + return minHeight; + } + + public void setMinHeight(float minHeight) { + this.minHeight = minHeight; + } + + public float getMaxHeight() { + return maxHeight; + } + + public void setMaxHeight(float maxHeight) { + this.maxHeight = maxHeight; + } + + public float getSlopeStrength() { + return slopeStrength; + } + + public void setSlopeStrength(float slopeStrength) { + this.slopeStrength = slopeStrength; + } +} diff --git a/commons/src/main/com/mbrlabs/mundus/commons/shaders/TerrainUberShader.java b/commons/src/main/com/mbrlabs/mundus/commons/shaders/TerrainUberShader.java index dbbcf1f68..6b093a1af 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/shaders/TerrainUberShader.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/shaders/TerrainUberShader.java @@ -16,7 +16,9 @@ import com.badlogic.gdx.math.Vector3; import com.mbrlabs.mundus.commons.env.MundusEnvironment; import com.mbrlabs.mundus.commons.terrain.SplatTexture; +import com.mbrlabs.mundus.commons.terrain.layers.TerrainLayer; import com.mbrlabs.mundus.commons.terrain.TerrainMaterial; +import com.mbrlabs.mundus.commons.terrain.attributes.TerrainLayerAttribute; import com.mbrlabs.mundus.commons.terrain.attributes.TerrainMaterialAttribute; import com.mbrlabs.mundus.commons.utils.ShaderUtils; import net.mgsx.gltf.scene3d.attributes.FogAttribute; @@ -29,6 +31,7 @@ public class TerrainUberShader extends LightShader { protected static final String VERTEX_SHADER = "com/mbrlabs/mundus/commons/shaders/terrain.uber.vert.glsl"; protected static final String FRAGMENT_SHADER = "com/mbrlabs/mundus/commons/shaders/terrain.uber.frag.glsl"; public static final TextureDescriptor textureDescription = new TextureDescriptor<>(); + public static final int MAX_LAYERS = 4; public static Vector3 terrainClippingPlane = new Vector3(0.0f,0.0f, 0.0f); public static float terrainClippingHeight = 0f; @@ -106,6 +109,7 @@ public void set(BaseShader shader, int inputID, Renderable renderable, Attribute TerrainMaterialAttribute terrainMaterialAttribute = (TerrainMaterialAttribute) combinedAttributes.get(TerrainMaterialAttribute.TerrainMaterial); TerrainMaterial material = terrainMaterialAttribute.terrainMaterial; textureDescription.texture = material.getNormalTexture(channel).getTexture(); + if (textureDescription.texture == null) return; final int unit = shader.context.textureBinder .bind(textureDescription); shader.set(inputID, unit); @@ -156,6 +160,18 @@ public void set(BaseShader shader, int inputID, Renderable renderable, Attribute public final int u_splatBNormal; public final int u_splatANormal; + // Terrain Layers + public final int u_activeHeightLayers = register(new Uniform("u_activeHeightLayers")); + public final int u_activeSlopeLayers = register(new Uniform("u_activeSlopeLayers")); + public int[] u_heightTextureLayers = new int[MAX_LAYERS]; + public int[] u_heightNormalTextureLayers = new int[MAX_LAYERS]; + public int[] u_heightFloatLayers = new int[MAX_LAYERS]; + + public int[] u_slopeTextureLayers = new int[MAX_LAYERS]; + public int[] u_slopeNormalTextureLayers = new int[MAX_LAYERS]; + public int[] u_slopeHeightLayers = new int[MAX_LAYERS]; + public int[] u_slopeStrengthLayers = new int[MAX_LAYERS]; + /** The renderable used to create this shader, invalid after the call to init */ private Renderable renderable; @@ -209,12 +225,41 @@ public TerrainUberShader(Renderable renderable, DefaultShader.Config config) { protected String createPrefixForRenderable(Renderable renderable) { String prefix = ""; + prefix += "#define maxLayers " + MAX_LAYERS + "\n"; + if (renderable.environment.has(ColorAttribute.Fog)) { prefix += "#define fogFlag\n"; } + boolean hasLayerNormals = false; TerrainMaterial terrainMaterial = getTerrainMaterial(renderable); + TerrainLayerAttribute attr = (TerrainLayerAttribute) terrainMaterial.get(TerrainLayerAttribute.HeightLayer); + if (attr != null) { + prefix += "#define " + TerrainLayerAttribute.HeightLayerAlias + "\n"; + + for (TerrainLayer layer : attr.terrainLayers) { + if (layer.normalTextureAsset != null) { + prefix += "#define " + TerrainLayerAttribute.HeightLayerAlias + "NormalFlag\n"; + hasLayerNormals = true; + break; + } + } + } + + attr = (TerrainLayerAttribute) terrainMaterial.get(TerrainLayerAttribute.SlopeLayer); + if (attr != null) { + prefix += "#define " + TerrainLayerAttribute.SlopeLayerAlias + "\n"; + + for (TerrainLayer layer : attr.terrainLayers) { + if (layer.normalTextureAsset != null) { + prefix += "#define " + TerrainLayerAttribute.SlopeLayerAlias + "NormalFlag\n"; + hasLayerNormals = true; + break; + } + } + } + if (terrainMaterial.getSplatmap() != null && terrainMaterial.getSplatmap().getTexture() != null) { prefix += "#define splatFlag\n"; } @@ -258,6 +303,8 @@ protected String createPrefixForRenderable(Renderable renderable) { if (terrainMaterial.hasNormalChannel(SplatTexture.Channel.A)) { prefix += "#define splatANormalFlag\n"; } + } else if (hasLayerNormals) { + prefix += "#define normalTextureFlag\n"; } return prefix; @@ -265,6 +312,18 @@ protected String createPrefixForRenderable(Renderable renderable) { @Override public void init() { + + for (int i = 0; i < MAX_LAYERS; i++) { + u_heightTextureLayers[i] = register(new Uniform("u_heightLayers["+ i +"].texture")); + u_heightNormalTextureLayers[i] = register(new Uniform("u_heightLayers["+ i +"].normalTexture")); + u_heightFloatLayers[i] = register(new Uniform("u_heightLayers["+ i +"].minMaxHeight")); + + u_slopeTextureLayers[i] = register(new Uniform("u_slopeLayers["+ i +"].texture")); + u_slopeNormalTextureLayers[i] = register(new Uniform("u_slopeLayers["+ i +"].normalTexture")); + u_slopeHeightLayers[i] = register(new Uniform("u_slopeLayers["+ i +"].minMaxHeight")); + u_slopeStrengthLayers[i] = register(new Uniform("u_slopeLayers["+ i +"].slopeStrength")); + } + final ShaderProgram program = this.program; this.program = null; this.init(program, renderable); @@ -287,6 +346,13 @@ public void render(Renderable renderable) { setLights(env); setShadows(env); + TerrainMaterial terrainMaterial = getTerrainMaterial(renderable); + TerrainLayerAttribute attr = (TerrainLayerAttribute) terrainMaterial.get(TerrainLayerAttribute.HeightLayer); + setLayers(attr, u_activeHeightLayers); + + attr = (TerrainLayerAttribute) terrainMaterial.get(TerrainLayerAttribute.SlopeLayer); + setLayers(attr, u_activeSlopeLayers); + super.render(renderable); } @@ -316,6 +382,25 @@ public boolean canRender(Renderable instance) { return terrainMaterialMask == terrainMaterial.getMask(); } + /** + * Iterate through and set uniforms for active terrain layers + */ + private void setLayers(TerrainLayerAttribute attr, int u_activeLayerLoc) { + if (attr != null) { + int count = 0; + + for (int i = 0; i < attr.terrainLayers.size; i++) { + TerrainLayer layer = attr.terrainLayers.get(i); + if (!layer.active) continue; + + layer.setUniforms(this, i); + count++; + } + + set(u_activeLayerLoc, count); + } + } + private static TerrainMaterial getTerrainMaterial(Renderable renderable) { return renderable.material.get(TerrainMaterialAttribute.class, TerrainMaterialAttribute.TerrainMaterial).terrainMaterial; } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.uber.frag.glsl b/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.uber.frag.glsl index dbb8b140f..c3cab7373 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.uber.frag.glsl +++ b/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.uber.frag.glsl @@ -32,9 +32,33 @@ const MED vec4 COLOR_BRUSH = vec4(0.4,0.4,0.4, 0.4); // splat textures uniform sampler2D u_baseTexture; - -#ifdef baseNormalFlag uniform sampler2D u_texture_base_normal; + +#if defined(heightLayer) || defined(slopeLayer) +// Needed by both layers +struct Layer +{ + vec2 minMaxHeight; + float slopeStrength; + sampler2D texture; + sampler2D normalTexture; +}; + +varying vec3 v_normal; // Vertex Normal +varying vec3 v_localPos; // Vertex Position +#endif + +#ifdef heightLayer +uniform int u_activeHeightLayers; +uniform Layer u_heightLayers[4]; +#endif + +#ifdef slopeLayer +uniform int u_activeSlopeLayers; +uniform Layer u_slopeLayers[4]; +#endif + +#if defined(heightLayer) || defined(slopeLayer) #endif #ifdef splatFlag @@ -88,6 +112,14 @@ varying mat3 v_TBN; varying vec2 v_texCoord0; varying float v_clipDistance; +float normalizeRange(float value, float minValue, float maxValue) { + float weight = max(minValue, value); + weight = min(maxValue, weight); + weight -= minValue; + weight /= maxValue - minValue; // Normalizes to 0.0-1.0 range + return weight; +} + // Brings the normal from [0, 1] to [-1, 1] vec3 unpackNormal(vec3 normal) { @@ -98,12 +130,38 @@ void main(void) { if ( v_clipDistance < 0.0 ) discard; - vec3 normal; - + // Terrains always have a base texture, so we sample it first gl_FragColor = texture2D(u_baseTexture, v_texCoord0); - #ifdef baseNormalFlag - normal = unpackNormal(texture2D(u_texture_base_normal, v_texCoord0).rgb); + vec3 normal = unpackNormal(texture2D(u_texture_base_normal, v_texCoord0).rgb); + + #ifdef heightLayer + for (int i = 0 ; i < maxLayers; i++) { + if (i >= u_activeHeightLayers){break;} + + float blend = normalizeRange(v_localPos.y, u_heightLayers[i].minMaxHeight.x /*+ noises*/, u_heightLayers[i].minMaxHeight.y /*+ noises*/); + gl_FragColor = mix(gl_FragColor, texture2D(u_heightLayers[i].texture, v_texCoord0), blend); + + // Blend normals always, to remove base normal from appearing over the layer + normal = mix(normal, texture2D(u_heightLayers[i].normalTexture, v_texCoord0).rgb, blend); + } + #endif + + #ifdef slopeLayer + // Perform Slope Layers after Height layers so that they end up on top + for (int i = 0 ; i < maxLayers; i++) { + if (i >= u_activeSlopeLayers) continue; + + // Slope blending + float blendHeight = normalizeRange(v_localPos.y, u_slopeLayers[i].minMaxHeight.x, u_slopeLayers[i].minMaxHeight.y); + + // Take the surface normal, factor in the blend height and strength. + float slopeBlend = (1.0 - abs(v_normal.y)) * blendHeight * u_slopeLayers[i].slopeStrength; + slopeBlend = clamp(slopeBlend, 0.0, 1.0); + + gl_FragColor = mix(gl_FragColor, texture2D(u_slopeLayers[i].texture, v_texCoord0), slopeBlend); + normal = mix(normal, texture2D(u_slopeLayers[i].normalTexture, v_texCoord0).rgb, slopeBlend); + } #endif // Mix splat textures diff --git a/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.uber.vert.glsl b/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.uber.vert.glsl index 35a86cd45..6e6a6d961 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.uber.vert.glsl +++ b/commons/src/main/com/mbrlabs/mundus/commons/shaders/terrain.uber.vert.glsl @@ -28,10 +28,14 @@ uniform vec4 u_cameraPosition; uniform mat3 u_normalMatrix; varying vec2 v_texCoord0; -varying vec3 v_normal; varying vec3 v_worldPos; varying mat3 v_TBN; +#if defined(heightLayer) || defined(slopeLayer) +varying vec3 v_normal; +varying vec3 v_localPos; +#endif + #ifdef PICKER varying vec3 v_pos; #endif @@ -51,7 +55,6 @@ varying vec3 v_shadowMapUv; void main(void) { // position vec4 worldPos = u_worldTrans * vec4(a_position, 1.0); - gl_Position = u_projViewTrans * worldPos; vec4 spos = u_shadowMapProjViewTrans * worldPos; v_shadowMapUv.xy = (spos.xy / spos.w) * 0.5 + 0.5; @@ -64,6 +67,11 @@ void main(void) { vec3 bitangentW = cross(normalW, tangentW) * a_tangent.w; v_TBN = mat3(tangentW, bitangentW, normalW); + #if defined(heightLayer) || defined(slopeLayer) + v_localPos = a_position; + v_normal = normalize(u_normalMatrix * a_normal); + #endif + // clipping plane v_clipDistance = dot(worldPos, u_clipPlane); @@ -80,4 +88,5 @@ void main(void) { v_pos = worldPos.xyz; #endif + gl_Position = u_projViewTrans * worldPos; } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/terrain/Terrain.java b/commons/src/main/com/mbrlabs/mundus/commons/terrain/Terrain.java index 0f5d2b41b..60a31e7ed 100644 --- a/commons/src/main/com/mbrlabs/mundus/commons/terrain/Terrain.java +++ b/commons/src/main/com/mbrlabs/mundus/commons/terrain/Terrain.java @@ -55,6 +55,8 @@ public class Terrain implements Disposable { private static final Matrix4 tmpMatrix = new Matrix4(); public float[] heightData; + public float maxHeight = 0f; // Stores highest Y Vertices height value + public float minHeight = 0f; // Stores lowest Y Vertices height value public int terrainWidth = 1200; public int terrainDepth = 1200; public int vertexResolution; @@ -267,6 +269,12 @@ private MeshPartBuilder.VertexInfo calculateVertexAt(MeshPartBuilder.VertexInfo out.position.set(dx * this.terrainWidth, height, dz * this.terrainDepth); out.uv.set(dx, dz).scl(uvScale); + if (height > maxHeight) { + maxHeight = height; + } else if (height < minHeight) { + minHeight = height; + } + return out; } @@ -401,7 +409,7 @@ public void update() { } } // Get tangents added to terrains vertices array for normal mapping - MeshTangentSpaceGenerator.computeTangentSpace(vertices, buildIndices(), attribs, false, true, normalMapUVs); + MeshTangentSpaceGenerator.computeTangentSpace(vertices, buildIndices(), attribs, true, true, normalMapUVs); mesh.setVertices(vertices); } diff --git a/commons/src/main/com/mbrlabs/mundus/commons/terrain/attributes/TerrainLayerAttribute.java b/commons/src/main/com/mbrlabs/mundus/commons/terrain/attributes/TerrainLayerAttribute.java new file mode 100644 index 000000000..75c3ed676 --- /dev/null +++ b/commons/src/main/com/mbrlabs/mundus/commons/terrain/attributes/TerrainLayerAttribute.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022. See AUTHORS file. + * + * 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 com.mbrlabs.mundus.commons.terrain.attributes; + +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.GdxRuntimeException; +import com.mbrlabs.mundus.commons.MundusAttribute; +import com.mbrlabs.mundus.commons.terrain.layers.TerrainLayer; + +/** + * @author JamesTKhan + * @version October 16, 2022 + */ +public class TerrainLayerAttribute extends MundusAttribute { + public final static String HeightLayerAlias = "heightLayer"; + public final static long HeightLayer = register(HeightLayerAlias); + + public final static String SlopeLayerAlias = "slopeLayer"; + public final static long SlopeLayer = register(SlopeLayerAlias); + + protected static long Mask = HeightLayer | SlopeLayer; + + public final static boolean is (final long mask) { + return (mask & Mask) != 0; + } + + public final Array terrainLayers; + + public TerrainLayerAttribute(final long type) { + super(type); + if (!is(type)) throw new GdxRuntimeException("Invalid type specified"); + terrainLayers = new Array<>(); + } + + public TerrainLayerAttribute(final long type, Array terrainLayers) { + this(type); + this.terrainLayers.addAll(terrainLayers); + } + + public TerrainLayerAttribute(final TerrainLayerAttribute copyFrom) { + this(copyFrom.type, copyFrom.terrainLayers); + } + + + @Override + public MundusAttribute copy () { + return new TerrainLayerAttribute(this); + } + + @Override + public int hashCode () { + int result = super.hashCode(); + result = 991 * result + terrainLayers.hashCode(); + return result; + } + + @Override + public int compareTo (MundusAttribute o) { + if (type != o.type) return type < o.type ? -1 : 1; + TerrainLayerAttribute other = (TerrainLayerAttribute)o; + return terrainLayers.equals(other.terrainLayers) ? 0 : -1; + } +} + diff --git a/commons/src/main/com/mbrlabs/mundus/commons/terrain/layers/HeightTerrainLayer.java b/commons/src/main/com/mbrlabs/mundus/commons/terrain/layers/HeightTerrainLayer.java new file mode 100644 index 000000000..ef3a8a466 --- /dev/null +++ b/commons/src/main/com/mbrlabs/mundus/commons/terrain/layers/HeightTerrainLayer.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022. See AUTHORS file. + * + * 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 com.mbrlabs.mundus.commons.terrain.layers; + +import com.mbrlabs.mundus.commons.assets.TextureAsset; +import com.mbrlabs.mundus.commons.shaders.TerrainUberShader; + +/** + * Terrain Layer that sets textures based on given minimum and maximum heights for fading in. + * @author JamesTKhan + * @version November 06, 2022 + */ +public class HeightTerrainLayer extends TerrainLayer { + // Used by Height and Slope + public float minHeight; + public float maxHeight; + + public HeightTerrainLayer(TextureAsset textureAsset, float minHeight, float maxHeight) { + super(textureAsset); + this.minHeight = minHeight; + this.maxHeight = maxHeight; + } + + @Override + public void setUniforms(TerrainUberShader shader, int uniformIndex) { + shader.set(shader.u_heightTextureLayers[uniformIndex], textureAsset.getTexture()); + shader.set(shader.u_heightFloatLayers[uniformIndex], minHeight, maxHeight); + + if (normalTextureAsset != null) { + shader.set(shader.u_heightNormalTextureLayers[uniformIndex], normalTextureAsset.getTexture()); + } + } +} diff --git a/commons/src/main/com/mbrlabs/mundus/commons/terrain/layers/SlopeTerrainLayer.java b/commons/src/main/com/mbrlabs/mundus/commons/terrain/layers/SlopeTerrainLayer.java new file mode 100644 index 000000000..1fb28d10b --- /dev/null +++ b/commons/src/main/com/mbrlabs/mundus/commons/terrain/layers/SlopeTerrainLayer.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022. See AUTHORS file. + * + * 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 com.mbrlabs.mundus.commons.terrain.layers; + +import com.mbrlabs.mundus.commons.assets.TextureAsset; +import com.mbrlabs.mundus.commons.shaders.TerrainUberShader; + +/** + * Terrain Layer for rendering textures on the terrain based on slope angles of the surface normal. + * @author JamesTKhan + * @version November 06, 2022 + */ +public class SlopeTerrainLayer extends HeightTerrainLayer { + public float strength; + + public SlopeTerrainLayer(TextureAsset textureAsset, float minHeight, float maxHeight, float strength) { + super(textureAsset, minHeight, maxHeight); + this.strength = strength; + } + + @Override + public void setUniforms(TerrainUberShader shader, int uniformIndex) { + shader.set(shader.u_slopeTextureLayers[uniformIndex], textureAsset.getTexture()); + shader.set(shader.u_slopeHeightLayers[uniformIndex], minHeight, maxHeight); + shader.set(shader.u_slopeStrengthLayers[uniformIndex], strength); + + if (normalTextureAsset != null) { + shader.set(shader.u_slopeNormalTextureLayers[uniformIndex], normalTextureAsset.getTexture()); + } + } +} diff --git a/commons/src/main/com/mbrlabs/mundus/commons/terrain/layers/TerrainLayer.java b/commons/src/main/com/mbrlabs/mundus/commons/terrain/layers/TerrainLayer.java new file mode 100644 index 000000000..6af56abcf --- /dev/null +++ b/commons/src/main/com/mbrlabs/mundus/commons/terrain/layers/TerrainLayer.java @@ -0,0 +1,43 @@ +package com.mbrlabs.mundus.commons.terrain.layers; + +import com.badlogic.gdx.graphics.Texture; +import com.mbrlabs.mundus.commons.assets.TextureAsset; +import com.mbrlabs.mundus.commons.shaders.TerrainUberShader; +import com.mbrlabs.mundus.commons.utils.TextureProvider; + +/** + * Abstract Terrain Layer class for different types of texture layers on terrains. + * @author JamesTKhan + * @version November 04, 2022 + */ +public abstract class TerrainLayer implements TextureProvider { + public TextureAsset textureAsset; + public TextureAsset normalTextureAsset; + public boolean active = true; + + private String name; + + public TerrainLayer(TextureAsset textureAsset) { + this.textureAsset = textureAsset; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Set the uniforms needed for this terrain layer on the shader + * @param shader the shader to set uniforms on. + * @param uniformIndex the uniform index of the terrain shaders layer arrays to use. + */ + public abstract void setUniforms(TerrainUberShader shader, int uniformIndex); + + @Override + public Texture getTexture() { + return textureAsset.getTexture(); + } +} diff --git a/editor/src/main/com/mbrlabs/mundus/editor/Editor.kt b/editor/src/main/com/mbrlabs/mundus/editor/Editor.kt index a14ca217d..2db0cf128 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/Editor.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/Editor.kt @@ -33,6 +33,7 @@ import com.mbrlabs.mundus.editor.core.registry.Registry import com.mbrlabs.mundus.editor.events.FilesDroppedEvent import com.mbrlabs.mundus.editor.events.FullScreenEvent import com.mbrlabs.mundus.editor.events.GameObjectModifiedEvent +import com.mbrlabs.mundus.editor.events.InvalidateShadersEvent import com.mbrlabs.mundus.editor.events.ProjectChangedEvent import com.mbrlabs.mundus.editor.events.SceneChangedEvent import com.mbrlabs.mundus.editor.input.FreeCamController @@ -61,7 +62,8 @@ class Editor : Lwjgl3WindowAdapter(), ApplicationListener, ProjectChangedEvent.ProjectChangedListener, SceneChangedEvent.SceneChangedListener, FullScreenEvent.FullScreenEventListener, - GameObjectModifiedEvent.GameObjectModifiedListener { + GameObjectModifiedEvent.GameObjectModifiedListener, + InvalidateShadersEvent.InvalidateShadersListener { private lateinit var axesInstance: ModelInstance private lateinit var compass: Compass @@ -78,6 +80,8 @@ class Editor : Lwjgl3WindowAdapter(), ApplicationListener, private lateinit var shapeRenderer: ShapeRenderer private lateinit var debugRenderer: DebugRenderer + private lateinit var shaderProvider: EditorShaderProvider + override fun create() { Mundus.registerEventListener(this) camController = Mundus.inject() @@ -137,7 +141,8 @@ class Editor : Lwjgl3WindowAdapter(), ApplicationListener, val sg = scene.sceneGraph val config = ShaderUtils.buildPBRShaderConfig(projectManager.current().assetManager.maxNumBones) - projectManager.modelBatch = ModelBatch(EditorShaderProvider(config), SceneRenderableSorter()) + shaderProvider = EditorShaderProvider(config) + projectManager.modelBatch = ModelBatch(shaderProvider, SceneRenderableSorter()) val depthConfig = ShaderUtils.buildPBRShaderDepthConfig(projectManager.current().assetManager.maxNumBones) projectManager.setDepthBatch((ModelBatch(PBRDepthShaderProvider(depthConfig)))) @@ -263,6 +268,10 @@ class Editor : Lwjgl3WindowAdapter(), ApplicationListener, projectManager.current().currScene.modelCacheManager.requestModelCacheRebuild() } + override fun onInvalidateShaders(event: InvalidateShadersEvent) { + shaderProvider.invalidateShaders() + } + override fun dispose() { debugRenderer.dispose() Mundus.dispose() diff --git a/editor/src/main/com/mbrlabs/mundus/editor/assets/EditorAssetManager.kt b/editor/src/main/com/mbrlabs/mundus/editor/assets/EditorAssetManager.kt index 7729bb14d..3b23816f9 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/assets/EditorAssetManager.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/assets/EditorAssetManager.kt @@ -37,8 +37,12 @@ import com.mbrlabs.mundus.commons.assets.TextureAsset import com.mbrlabs.mundus.commons.assets.WaterAsset import com.mbrlabs.mundus.commons.assets.meta.Meta import com.mbrlabs.mundus.commons.assets.meta.MetaTerrain +import com.mbrlabs.mundus.commons.assets.meta.MetaTerrainLayer import com.mbrlabs.mundus.commons.scene3d.GameObject import com.mbrlabs.mundus.commons.scene3d.components.AssetUsage +import com.mbrlabs.mundus.commons.terrain.attributes.TerrainLayerAttribute +import com.mbrlabs.mundus.commons.terrain.layers.HeightTerrainLayer +import com.mbrlabs.mundus.commons.terrain.layers.SlopeTerrainLayer import com.mbrlabs.mundus.commons.utils.FileFormatUtils import com.mbrlabs.mundus.commons.water.attributes.WaterColorAttribute import com.mbrlabs.mundus.commons.water.attributes.WaterFloatAttribute @@ -691,10 +695,48 @@ class EditorAssetManager(assetsRoot: FileHandle) : AssetManager(assetsRoot) { terrain.meta.terrain.splatBase64 = "data:image/png;base64,$encoded" } + val heightAttr = terrain.terrain.terrainTexture.get(TerrainLayerAttribute.HeightLayer) as TerrainLayerAttribute? + val slopeAttr = terrain.terrain.terrainTexture.get(TerrainLayerAttribute.SlopeLayer) as TerrainLayerAttribute? + + terrain.meta.terrain.heightLayers = getMetaTerrainLayers(heightAttr) + terrain.meta.terrain.slopeLayers = getMetaTerrainLayers(slopeAttr) + // save meta file metaSaver.save(terrain.meta) } + /** + * Builds Array of MetaTerrainLayers for persisting Terrain Layers based on the given attribute + */ + private fun getMetaTerrainLayers(attribute: TerrainLayerAttribute?): Array? { + if (attribute == null) return null + + var arr : Array? = null + + for (layer in attribute.terrainLayers) { + if (arr == null) arr = Array() + + layer as HeightTerrainLayer + val metaLayer = MetaTerrainLayer() + metaLayer.isActive = layer.active + metaLayer.textureAssetId = layer.textureAsset.id + metaLayer.name = layer.name + metaLayer.maxHeight = layer.maxHeight + metaLayer.minHeight = layer.minHeight + + if (layer.normalTextureAsset != null) { + metaLayer.normalTextureAssetId = layer.normalTextureAsset.id + } + + if (layer is SlopeTerrainLayer) { + metaLayer.slopeStrength = layer.strength + } + + arr.add(metaLayer) + } + return arr + } + @Throws(IOException::class) fun saveMaterialAsset(mat: MaterialAsset) { // save .mat diff --git a/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt b/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt index dcacb73b6..b9641fabf 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/assets/MetaSaver.kt @@ -16,12 +16,14 @@ package com.mbrlabs.mundus.editor.assets +import com.badlogic.gdx.utils.Array import com.badlogic.gdx.utils.Json import com.badlogic.gdx.utils.JsonWriter import com.mbrlabs.mundus.commons.assets.AssetType import com.mbrlabs.mundus.commons.assets.meta.Meta import com.mbrlabs.mundus.commons.assets.meta.MetaModel import com.mbrlabs.mundus.commons.assets.meta.MetaTerrain +import com.mbrlabs.mundus.commons.assets.meta.MetaTerrainLayer /** * @@ -88,7 +90,46 @@ class MetaSaver { if (terrain.splatGNormal != null) json.writeValue(MetaTerrain.JSON_SPLAT_G_NORMAL, terrain.splatGNormal) if (terrain.splatBNormal != null) json.writeValue(MetaTerrain.JSON_SPLAT_B_NORMAL, terrain.splatBNormal) if (terrain.splatANormal != null) json.writeValue(MetaTerrain.JSON_SPLAT_A_NORMAL, terrain.splatANormal) + + // Convert layers + addTerrainLayer(json, terrain.heightLayers, false) + addTerrainLayer(json, terrain.slopeLayers, true) + json.writeObjectEnd() } + /** + * Writes MetaTerrainLayers to the Meta Json object as arrays + */ + private fun addTerrainLayer(json: Json, layers: Array?, isSlopeLayer: Boolean) { + if (layers == null || layers.isEmpty) return + + if (isSlopeLayer) { + json.writeArrayStart(MetaTerrain.JSON_SLOPE_LAYERS) + } else { + json.writeArrayStart(MetaTerrain.JSON_HEIGHT_LAYERS) + } + + for (i in 0 until layers.size) { + json.writeObjectStart() + val layer = layers.get(i) as MetaTerrainLayer + json.writeValue(MetaTerrain.JSON_HEIGHT_LAYER_NAME, layer.name) + json.writeValue(MetaTerrain.JSON_HEIGHT_LAYER_ACTIVE, layer.isActive) + json.writeValue(MetaTerrain.JSON_LAYER_TEXTURE_ASSET, layer.textureAssetId) + json.writeValue(MetaTerrain.JSON_HEIGHT_LAYER_MAX_HEIGHT, layer.maxHeight) + json.writeValue(MetaTerrain.JSON_HEIGHT_LAYER_MIN_HEIGHT, layer.minHeight) + + if (layer.normalTextureAssetId != null) { + json.writeValue(MetaTerrain.JSON_LAYER_NORMAL_TEXTURE_ASSET, layer.normalTextureAssetId) + } + + if (isSlopeLayer) { + json.writeValue(MetaTerrain.JSON_LAYER_SLOPE_STRENGTH, layer.slopeStrength) + } + + json.writeObjectEnd() + } + json.writeArrayEnd() + } + } \ No newline at end of file diff --git a/editor/src/main/com/mbrlabs/mundus/editor/events/InvalidateShadersEvent.kt b/editor/src/main/com/mbrlabs/mundus/editor/events/InvalidateShadersEvent.kt new file mode 100644 index 000000000..c754cee75 --- /dev/null +++ b/editor/src/main/com/mbrlabs/mundus/editor/events/InvalidateShadersEvent.kt @@ -0,0 +1,14 @@ +package com.mbrlabs.mundus.editor.events + +/** + * Force Mundus shaders to be recompiled by invalidating current shaders. + * + * @author JamesTKhan + * @version November 09, 2022 + */ +class InvalidateShadersEvent { + interface InvalidateShadersListener { + @Subscribe + fun onInvalidateShaders(event: InvalidateShadersEvent) + } +} \ No newline at end of file diff --git a/editor/src/main/com/mbrlabs/mundus/editor/shader/EditorShaderProvider.java b/editor/src/main/com/mbrlabs/mundus/editor/shader/EditorShaderProvider.java index e6a25f68a..43f0cc92d 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/shader/EditorShaderProvider.java +++ b/editor/src/main/com/mbrlabs/mundus/editor/shader/EditorShaderProvider.java @@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.g3d.Renderable; import com.badlogic.gdx.graphics.g3d.Shader; import com.mbrlabs.mundus.commons.shaders.MundusPBRShaderProvider; +import com.mbrlabs.mundus.commons.shaders.WaterUberShader; import net.mgsx.gltf.scene3d.shaders.PBRShaderConfig; /** @@ -17,6 +18,23 @@ public EditorShaderProvider(PBRShaderConfig config) { super(config); } + /** + * Invalidates shaders, currently only invalidates the terrain shader. + * When invalidated this will force all instances of shader to be recompiled. + * Useful in the editor for development purposes when editing shaders. + */ + public void invalidateShaders() { + for (int i = 0; i < shaders.size; i++) { + Shader shader = shaders.get(i); + + if (shader instanceof EditorTerrainUberShader) { + ((EditorTerrainUberShader)shader).invalid = true; + shaders.removeIndex(i); + shader.dispose(); + } + } + } + @Override protected Shader createTerrainShader(Renderable renderable) { Shader shader = new EditorTerrainUberShader(renderable, config); diff --git a/editor/src/main/com/mbrlabs/mundus/editor/shader/EditorTerrainUberShader.java b/editor/src/main/com/mbrlabs/mundus/editor/shader/EditorTerrainUberShader.java index 1b7c99094..b80d45f68 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/shader/EditorTerrainUberShader.java +++ b/editor/src/main/com/mbrlabs/mundus/editor/shader/EditorTerrainUberShader.java @@ -20,6 +20,8 @@ public class EditorTerrainUberShader extends TerrainUberShader { private static Vector3 pickerPosition = new Vector3(); private static float pickerRadius = 0; + public boolean invalid = false; + public EditorTerrainUberShader(Renderable renderable, DefaultShader.Config config) { super(renderable, config); } @@ -30,6 +32,14 @@ protected String createPrefixForRenderable(Renderable renderable) { return prefix + super.createPrefixForRenderable(renderable); } + @Override + public boolean canRender(Renderable instance) { + if (invalid) { + return false; + } + return super.canRender(instance); + } + @Override public void render(Renderable renderable) { // mouse picking diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/GameObjectInspector.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/GameObjectInspector.kt index a2a8e1eaf..0a9e91e20 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/GameObjectInspector.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/GameObjectInspector.kt @@ -29,6 +29,9 @@ import com.mbrlabs.mundus.commons.scene3d.components.LightComponent import com.mbrlabs.mundus.commons.scene3d.components.ModelComponent import com.mbrlabs.mundus.commons.scene3d.components.TerrainComponent import com.mbrlabs.mundus.commons.scene3d.components.WaterComponent +import com.mbrlabs.mundus.editor.Mundus +import com.mbrlabs.mundus.editor.events.ProjectChangedEvent +import com.mbrlabs.mundus.editor.events.SceneChangedEvent import com.mbrlabs.mundus.editor.ui.UI import com.mbrlabs.mundus.editor.ui.modules.inspector.components.ComponentWidget import com.mbrlabs.mundus.editor.ui.modules.inspector.components.CustomPropertiesWidget @@ -43,7 +46,8 @@ import com.mbrlabs.mundus.editor.ui.modules.inspector.components.WaterComponentW * @author Marcus Brummer * @version 13-10-2016 */ -class GameObjectInspector : VisTable() { +class GameObjectInspector : VisTable(), ProjectChangedEvent.ProjectChangedListener, + SceneChangedEvent.SceneChangedListener { private val identifierWidget = IdentifierWidget() private val transformWidget = TransformWidget() @@ -54,6 +58,8 @@ class GameObjectInspector : VisTable() { private var gameObject: GameObject? = null init { + Mundus.registerEventListener(this) + align(Align.top) add(identifierWidget).growX().pad(7f).row() add(transformWidget).growX().pad(7f).row() @@ -70,7 +76,7 @@ class GameObjectInspector : VisTable() { }) } - fun setGameObject(gameObject: GameObject) { + fun setGameObject(gameObject: GameObject?) { this.gameObject = gameObject // build ui @@ -86,18 +92,26 @@ class GameObjectInspector : VisTable() { fun updateGameObject() { if (gameObject != null) { + identifierWidget.isVisible = true + transformWidget.isVisible = true + addComponentBtn.isVisible = true + identifierWidget.setValues(gameObject!!) transformWidget.setValues(gameObject!!) for (cw in componentWidgets) { cw.setValues(gameObject!!) } + } else { + identifierWidget.isVisible = false + transformWidget.isVisible = false + addComponentBtn.isVisible = false } } private fun buildComponentWidgets() { + componentWidgets.clear() if (gameObject != null) { - componentWidgets.clear() for (component in gameObject!!.components) { // model component widget!! if (component.type == Component.Type.MODEL) { @@ -133,4 +147,12 @@ class GameObjectInspector : VisTable() { } } + override fun onProjectChanged(event: ProjectChangedEvent) { + setGameObject(null) + } + + override fun onSceneChanged(event: SceneChangedEvent) { + setGameObject(null) + } + } diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainComponentWidget.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainComponentWidget.kt index 1fcbc830e..934b35f52 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainComponentWidget.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainComponentWidget.kt @@ -16,7 +16,6 @@ package com.mbrlabs.mundus.editor.ui.modules.inspector.components.terrain -import com.kotcrab.vis.ui.widget.VisLabel import com.kotcrab.vis.ui.widget.VisTable import com.kotcrab.vis.ui.widget.tabbedpane.Tab import com.kotcrab.vis.ui.widget.tabbedpane.TabbedPane @@ -43,6 +42,7 @@ class TerrainComponentWidget(terrainComponent: TerrainComponent) : private val paintTab = TerrainPaintTab(this) private val genTab = TerrainGenTab(this) private val settingsTab = TerrainSettingsTab(this) + private val layerTab = TerrainLayerTab(this) init { tabbedPane.addListener(this) @@ -52,11 +52,11 @@ class TerrainComponentWidget(terrainComponent: TerrainComponent) : tabbedPane.add(smoothTab) tabbedPane.add(rampTab) tabbedPane.add(paintTab) + tabbedPane.add(layerTab) tabbedPane.add(genTab) tabbedPane.add(settingsTab) collapsibleContent.add(tabbedPane.table).growX().row() - collapsibleContent.add(VisLabel("Use CTRL+Scroll Wheel to adjust brush size")).center().padBottom(4f).row() collapsibleContent.add(tabContainer).expand().fill().row() tabbedPane.switchTab(0) } diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainFlattenTab.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainFlattenTab.kt index 2e4f14d22..62267ae20 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainFlattenTab.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainFlattenTab.kt @@ -32,6 +32,7 @@ class TerrainFlattenTab(parent: TerrainComponentWidget) : BaseBrushTab(parent, T init { table.align(Align.left) + table.add(VisLabel("Use CTRL+Scroll Wheel to adjust brush size")).center().padBottom(4f).row() table.add(VisLabel("Hold shift to sample a height")).center().row() table.add(terrainBrushGrid).expand().fill().row() diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainLayerTab.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainLayerTab.kt new file mode 100644 index 000000000..f0c471a21 --- /dev/null +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainLayerTab.kt @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2022. See AUTHORS file. + * + * 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 com.mbrlabs.mundus.editor.ui.modules.inspector.components.terrain + +import com.badlogic.gdx.scenes.scene2d.InputEvent +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener +import com.badlogic.gdx.utils.Array +import com.kotcrab.vis.ui.widget.VisLabel +import com.kotcrab.vis.ui.widget.VisTable +import com.kotcrab.vis.ui.widget.VisTextButton +import com.kotcrab.vis.ui.widget.tabbedpane.Tab +import com.mbrlabs.mundus.commons.assets.Asset +import com.mbrlabs.mundus.commons.assets.TextureAsset +import com.mbrlabs.mundus.commons.terrain.Terrain +import com.mbrlabs.mundus.commons.terrain.attributes.TerrainLayerAttribute +import com.mbrlabs.mundus.commons.terrain.layers.HeightTerrainLayer +import com.mbrlabs.mundus.commons.terrain.layers.SlopeTerrainLayer +import com.mbrlabs.mundus.commons.terrain.layers.TerrainLayer +import com.mbrlabs.mundus.editor.Mundus +import com.mbrlabs.mundus.editor.assets.AssetTextureFilter +import com.mbrlabs.mundus.editor.core.project.ProjectManager +import com.mbrlabs.mundus.editor.ui.UI +import com.mbrlabs.mundus.editor.ui.modules.dialogs.assets.AssetPickerDialog +import com.mbrlabs.mundus.editor.ui.widgets.TerrainLayerWidget + +/** + * @author JamesTKhan + * @version November 04, 2022 + */ +class TerrainLayerTab(private val parentWidget: TerrainComponentWidget) : Tab(false, false) { + + private val table = VisTable() + private val layerTable = VisTable() + private val projectManager: ProjectManager = Mundus.inject() + + init { + Mundus.registerEventListener(this) + layerTable.defaults().pad(4f) + + setupUI() + + buildLayerTable() + table.add(layerTable).fillX().expandX() + } + + /** + * Clears and rebuilds the Layer Table that contains all the layer sections and fields + */ + private fun buildLayerTable() { + layerTable.clear() + + val terrain = parentWidget.component.terrainAsset.terrain + + if (terrain.terrainTexture.has(TerrainLayerAttribute.HeightLayer)) { + val attr = terrain.terrainTexture.get(TerrainLayerAttribute.HeightLayer) as TerrainLayerAttribute + layerTable.add(VisLabel("Height Layers")).row() + layerTable.addSeparator() + addLayerSection(attr.terrainLayers!!, terrain) + } + + if (terrain.terrainTexture.has(TerrainLayerAttribute.SlopeLayer)) { + val attr = terrain.terrainTexture.get(TerrainLayerAttribute.SlopeLayer) as TerrainLayerAttribute + layerTable.add(VisLabel("Slope Layers")).row() + layerTable.addSeparator() + addLayerSection(attr.terrainLayers!!, terrain) + } + + layerTable.pack() + } + + private fun addLayerSection(terrainLayers: Array, terrain: Terrain) { + for (i in 0 until terrainLayers.size) { + val layer = terrainLayers[i] + + val widget = TerrainLayerWidget(layer, terrain, i) + layerTable.add(widget).row() + + // Layer removed listener + widget.setListener(object : TerrainLayerWidget.OnLayerRemovedListener { + override fun onLayerRemoved(layer: TerrainLayer) { + removeTerrainLayerFromAttribute(layer) + buildLayerTable() + } + }) + + // Layer swapped listener + widget.setListener(object : TerrainLayerWidget.OnLayerSwapListener { + override fun onLayerSwap(layer: TerrainLayer, swapDirection: Int) { + swapLayers(layer, swapDirection) + buildLayerTable() + } + }) + + // Layer modified listener + widget.setListener(object : TerrainLayerWidget.OnLayerModifiedListener { + override fun onLayerModified() { + addToModifiedAssets() + } + }) + } + } + + private fun setupUI() { + val addHeightLayer = VisTextButton("Add Height Layer") + addHeightLayer.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + super.clicked(event, x, y) + UI.assetSelectionDialog.show(false, + AssetTextureFilter(), + object : AssetPickerDialog.AssetPickerListener { + override fun onSelected(asset: Asset?) { + val terrain = parentWidget.component.terrainAsset.terrain + asset as TextureAsset + addToModifiedAssets() + + var attr = + terrain.terrainTexture.get(TerrainLayerAttribute.HeightLayer) as TerrainLayerAttribute? + if (attr == null) { + attr = TerrainLayerAttribute(TerrainLayerAttribute.HeightLayer) + } + + val sizeFactor = + if (attr.terrainLayers.size > 0) (attr.terrainLayers.size + 1) / 10f else 0f + val suggestedMin = (terrain.maxHeight + terrain.minHeight) * (.2f + sizeFactor) + val suggestedMax = (terrain.maxHeight + terrain.minHeight) * (.5f + sizeFactor) + + attr.terrainLayers.add( + HeightTerrainLayer( + asset, + suggestedMin, + suggestedMax + ) + ) + + terrain.terrainTexture.set(attr) + buildLayerTable() + } + }) + } + }) + + val addSlopeLayer = VisTextButton("Add Slope Layer") + addSlopeLayer.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + super.clicked(event, x, y) + UI.assetSelectionDialog.show(false, AssetTextureFilter(), object : AssetPickerDialog.AssetPickerListener { + override fun onSelected(asset: Asset?) { + val terrain = parentWidget.component.terrainAsset.terrain + asset as TextureAsset + addToModifiedAssets() + + var attr = + terrain.terrainTexture.get(TerrainLayerAttribute.SlopeLayer) as TerrainLayerAttribute? + if (attr == null) { + attr = TerrainLayerAttribute(TerrainLayerAttribute.SlopeLayer) + } + + val sizeFactor = if (attr!!.terrainLayers.size > 0) (attr!!.terrainLayers.size + 1) / 10f else 0f + val suggestedMin = (terrain.maxHeight + terrain.minHeight) * (.1f + sizeFactor) + val suggestedMax = (terrain.maxHeight + terrain.minHeight) * (.5f + sizeFactor) + + attr!!.terrainLayers.add(SlopeTerrainLayer(asset, suggestedMin, suggestedMax, 5f)) + + terrain.terrainTexture.set(attr) + buildLayerTable() + } + }) + } + }) + + val btnTable = VisTable() + btnTable.defaults().pad(4f) + btnTable.add(addHeightLayer) + btnTable.add(addSlopeLayer) + + table.add(btnTable).row() + } + + private fun swapLayers(layer: TerrainLayer, swapDirection: Int) { + val terrain = parentWidget.component.terrainAsset.terrain + + var attr: TerrainLayerAttribute? = null + + if (layer is SlopeTerrainLayer) { + attr = terrain.terrainTexture.get(TerrainLayerAttribute.SlopeLayer) as TerrainLayerAttribute + } else if (layer is HeightTerrainLayer) { + attr = terrain.terrainTexture.get(TerrainLayerAttribute.HeightLayer) as TerrainLayerAttribute + } + + if (attr == null) return + + val index = attr.terrainLayers.indexOf(layer, true) + + // If the request is to swap "upwards" but its already at index 0, do nothing + if (index == 0 && swapDirection < 0) return + + // If the request is to swap "downwards" but its already at the bottom, do nothing + if (index + swapDirection >= attr.terrainLayers.size) return + + attr.terrainLayers.swap(index, index + swapDirection) + } + + /** + * Removes the layer from the Terrains Layer Attribute + * and removes the attribute if the layer array is empty after removal + */ + private fun removeTerrainLayerFromAttribute(layer: TerrainLayer) { + val terrain = parentWidget.component.terrainAsset.terrain + + var attr: TerrainLayerAttribute? = null + var mask: Long = 0 + + if (layer is SlopeTerrainLayer) { + attr = terrain.terrainTexture.get(TerrainLayerAttribute.SlopeLayer) as TerrainLayerAttribute + mask = TerrainLayerAttribute.SlopeLayer + } else if (layer is HeightTerrainLayer) { + attr = terrain.terrainTexture.get(TerrainLayerAttribute.HeightLayer) as TerrainLayerAttribute + mask = TerrainLayerAttribute.HeightLayer + } + + if (attr == null) return + + attr.terrainLayers.removeValue(layer as TerrainLayer?, true) + if (attr.terrainLayers.isEmpty) terrain.terrainTexture.remove(mask) + } + + private fun addToModifiedAssets() { + projectManager.current().assetManager.addModifiedAsset(parentWidget.component.terrainAsset) + } + + override fun getTabTitle(): String { + return "Layers" + } + + override fun getContentTable(): Table { + return table + } +} \ No newline at end of file diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainPaintTab.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainPaintTab.kt index ead19d210..c99d44594 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainPaintTab.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainPaintTab.kt @@ -60,6 +60,7 @@ class TerrainPaintTab(private val parentWidget: TerrainComponentWidget) : BaseBr init { root.align(Align.left) + root.add(VisLabel("Use CTRL+Scroll Wheel to adjust brush size")).center().padBottom(4f).row() // brushes root.add(terrainBrushGrid).expand().fill().padBottom(5f).row() diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainSmoothTab.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainSmoothTab.kt index 1ab7e191b..3ba2e0c6a 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainSmoothTab.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainSmoothTab.kt @@ -2,6 +2,7 @@ package com.mbrlabs.mundus.editor.ui.modules.inspector.components.terrain import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align +import com.kotcrab.vis.ui.widget.VisLabel import com.kotcrab.vis.ui.widget.VisTable import com.mbrlabs.mundus.editor.tools.brushes.TerrainBrush @@ -16,6 +17,7 @@ class TerrainSmoothTab(parent: TerrainComponentWidget) : BaseBrushTab(parent, Te init { table.align(Align.left) + table.add(VisLabel("Use CTRL+Scroll Wheel to adjust brush size")).center().padBottom(4f).row() table.add(terrainBrushGrid).expand().fill().row() } diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainUpDownTab.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainUpDownTab.kt index 8ca66c9ea..9377d9f5d 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainUpDownTab.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/inspector/components/terrain/TerrainUpDownTab.kt @@ -31,6 +31,7 @@ class TerrainUpDownTab(private val parent: TerrainComponentWidget) : BaseBrushTa init { table.align(Align.left) + table.add(VisLabel("Use CTRL+Scroll Wheel to adjust brush size")).center().padBottom(4f).row() table.add(VisLabel("Hold shift to lower")).center().row() table.add(terrainBrushGrid).expandX().fillX().row() } diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/menu/ToolsMenu.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/menu/ToolsMenu.kt index 659595fb7..a8c4d6370 100644 --- a/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/menu/ToolsMenu.kt +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/modules/menu/ToolsMenu.kt @@ -23,6 +23,7 @@ import com.kotcrab.vis.ui.widget.Menu import com.kotcrab.vis.ui.widget.MenuItem import com.mbrlabs.mundus.editor.Mundus import com.mbrlabs.mundus.editor.core.project.ProjectManager +import com.mbrlabs.mundus.editor.events.InvalidateShadersEvent import com.mbrlabs.mundus.editor.ui.UI import com.mbrlabs.mundus.editor.ui.modules.dialogs.tools.AssetCleanUpDialog @@ -34,12 +35,14 @@ class ToolsMenu : Menu("Tools") { private val findUnusedAssets = MenuItem("Asset Clean Up") private val debugRendering = MenuItem("Debug Render Options") + private val invalidateShaders = MenuItem("Invalidate Shaders") val projectManager: ProjectManager = Mundus.inject() init { addItem(findUnusedAssets) addItem(debugRendering) + addItem(invalidateShaders) findUnusedAssets.addListener(object : ClickListener() { override fun clicked(event: InputEvent?, x: Float, y: Float) { @@ -59,6 +62,12 @@ class ToolsMenu : Menu("Tools") { UI.showDialog(UI.debugRenderDialog) } }) + + invalidateShaders.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + Mundus.postEvent(InvalidateShadersEvent()) + } + }) } } diff --git a/editor/src/main/com/mbrlabs/mundus/editor/ui/widgets/TerrainLayerWidget.kt b/editor/src/main/com/mbrlabs/mundus/editor/ui/widgets/TerrainLayerWidget.kt new file mode 100644 index 000000000..5856d1b6c --- /dev/null +++ b/editor/src/main/com/mbrlabs/mundus/editor/ui/widgets/TerrainLayerWidget.kt @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2022. See AUTHORS file. + * + * 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 com.mbrlabs.mundus.editor.ui.widgets + +import com.badlogic.gdx.Gdx +import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.InputEvent +import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener +import com.badlogic.gdx.scenes.scene2d.utils.ClickListener +import com.badlogic.gdx.utils.Align +import com.kotcrab.vis.ui.VisUI +import com.kotcrab.vis.ui.widget.MenuItem +import com.kotcrab.vis.ui.widget.PopupMenu +import com.kotcrab.vis.ui.widget.VisCheckBox +import com.kotcrab.vis.ui.widget.VisTable +import com.kotcrab.vis.ui.widget.VisTextField +import com.mbrlabs.mundus.commons.assets.Asset +import com.mbrlabs.mundus.commons.assets.TextureAsset +import com.mbrlabs.mundus.commons.terrain.Terrain +import com.mbrlabs.mundus.commons.terrain.layers.HeightTerrainLayer +import com.mbrlabs.mundus.commons.terrain.layers.SlopeTerrainLayer +import com.mbrlabs.mundus.commons.terrain.layers.TerrainLayer +import com.mbrlabs.mundus.editor.Mundus +import com.mbrlabs.mundus.editor.assets.AssetTextureFilter +import com.mbrlabs.mundus.editor.events.InvalidateShadersEvent +import com.mbrlabs.mundus.editor.ui.UI +import com.mbrlabs.mundus.editor.ui.modules.dialogs.assets.AssetPickerDialog +import com.mbrlabs.mundus.editor.utils.Fa + +/** + * @author JamesTKhan + * @version November 06, 2022 + */ +class TerrainLayerWidget(var layer: TerrainLayer, var terrain: Terrain, var index: Int) : VisTable() { + + private var innerTable = VisTable() + private val textureGrid = TextureGrid(60, 5) + private val rightClickMenu = TextureRightClickMenu() + + var onLayerRemovedListener: OnLayerRemovedListener? = null + var onLayerSwapListener: OnLayerSwapListener? = null + var onLayerModifiedListener: OnLayerModifiedListener? = null + + /** + * Called when the X button is pressed to remove the layer is pressed + */ + interface OnLayerRemovedListener { + fun onLayerRemoved(layer: TerrainLayer) + } + + /** + * Called when the up/down arrows are pressed + */ + interface OnLayerSwapListener { + fun onLayerSwap(layer: TerrainLayer, swapDirection: Int) + } + + /** + * Called when an attribute value is modified + */ + interface OnLayerModifiedListener { + fun onLayerModified() + } + + init { + innerTable.defaults().left().pad(2f) + textureGrid.background = VisUI.getSkin().getDrawable("menu-bg") + textureGrid.addTexture(layer) + + textureGrid.setListener { _, leftClick -> + if (!leftClick) { + rightClickMenu.show() + } + } + + setupUI() + } + + private fun setupUI() { + val patch = VisUI.getSkin().getDrawable("window") + background = patch + + addHeaderButtons() + + val nameField = VisTextField() + + if (layer.name == null) { + layer.name = createLayerName(layer) + } + + nameField.text = layer.name + nameField.addListener(object : ChangeListener() { + override fun changed(event: ChangeEvent?, actor: Actor?) { + layer.name = nameField.text + onLayerModifiedListener?.onLayerModified() + } + }) + innerTable.add(ToolTipLabel("Name", "Name of the layer for convenience.")) + innerTable.add(nameField).row() + + if (layer is SlopeTerrainLayer) { + + val slopeStrengthSlider = ImprovedSlider(0.0f, 10.0f, 0.1f) + slopeStrengthSlider.value = (layer as SlopeTerrainLayer).strength + slopeStrengthSlider.addListener(object : ChangeListener() { + override fun changed(event: ChangeEvent?, actor: Actor?) { + (layer as SlopeTerrainLayer).strength = slopeStrengthSlider.value + onLayerModifiedListener?.onLayerModified() + } + }) + innerTable.add(ToolTipLabel("Slope Factor", "This value is multiplied to the surface normal.\n" + + "The higher the value, the more visible the slope texture will become.\n" + + "Higher values will result in the slope texture appearing on smaller inclines/angles. ")) + innerTable.add(slopeStrengthSlider).left().row() + + } + + if (layer is HeightTerrainLayer) { + + val heightStep = Math.abs(terrain.minHeight + terrain.maxHeight) / 20f + + val minHeightSlider = ImprovedSlider(terrain.minHeight, terrain.maxHeight, heightStep) + minHeightSlider.value = (layer as HeightTerrainLayer).minHeight + minHeightSlider.addListener(object : ChangeListener() { + override fun changed(event: ChangeEvent?, actor: Actor?) { + (layer as HeightTerrainLayer).minHeight = minHeightSlider.value + onLayerModifiedListener?.onLayerModified() + } + }) + innerTable.add(ToolTipLabel("Height Blend Start", "The height at which this texture starts blending.")) + innerTable.add(minHeightSlider).left().row() + + val maxHeightSlider = ImprovedSlider(terrain.minHeight, terrain.maxHeight, heightStep) + maxHeightSlider.value = (layer as HeightTerrainLayer).maxHeight + maxHeightSlider.addListener(object : ChangeListener() { + override fun changed(event: ChangeEvent?, actor: Actor?) { + (layer as HeightTerrainLayer).maxHeight = maxHeightSlider.value + onLayerModifiedListener?.onLayerModified() + } + }) + innerTable.add(ToolTipLabel("Height Blend End", "The height at which this texture is fully blended.\n" + + "The further apart this value is, the more gradually the texture fades in.")) + innerTable.add(maxHeightSlider).left().row() + + } + + innerTable.add(textureGrid).expandX().fillX().colspan(2) + + add(innerTable).row() + } + + private fun createLayerName(layer: TerrainLayer): String { + return if (layer is SlopeTerrainLayer) { + "Slope Layer " + (index + 1) + } else { + "Height Layer " + (index + 1) + } + } + + private fun addHeaderButtons() { + val headerTable = VisTable() + headerTable.defaults().pad(0f,2f,0f,2f) + + val active = VisCheckBox("Active") + active.isChecked = layer.active + active.addListener(object : ChangeListener() { + override fun changed(event: ChangeEvent?, actor: Actor?) { + layer.active = active.isChecked + onLayerModifiedListener?.onLayerModified() + } + }) + headerTable.add(active) + + val moveUpBtn = FaTextButton(Fa.CARET_UP) + headerTable.add(moveUpBtn) + moveUpBtn.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + onLayerSwapListener?.onLayerSwap(layer, -1) + onLayerModifiedListener?.onLayerModified() + } + }) + + val moveDownBtn = FaTextButton(Fa.CARET_DOWN) + headerTable.add(moveDownBtn) + moveDownBtn.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + onLayerSwapListener?.onLayerSwap(layer, 1) + onLayerModifiedListener?.onLayerModified() + } + }) + + val deleteBtn = FaTextButton(Fa.TIMES) + headerTable.add(deleteBtn).row() + deleteBtn.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + onLayerRemovedListener?.onLayerRemoved(layer) + onLayerModifiedListener?.onLayerModified() + } + }) + + headerTable.align(Align.right) + add(headerTable).fillX().expandX().row() + } + + private inner class TextureRightClickMenu : PopupMenu() { + private val changeTexture = MenuItem("Change texture") + private val addNormalMap = MenuItem("Add Normal Map") + private val removeNormalMap = MenuItem("Remove Normal Map") + + init { + addItem(changeTexture) + addItem(addNormalMap) + + changeTexture.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + UI.assetSelectionDialog.show( + false, + AssetTextureFilter(), + object : AssetPickerDialog.AssetPickerListener { + override fun onSelected(asset: Asset?) { + textureGrid.removeTextures() + layer.textureAsset = asset as TextureAsset + textureGrid.addTexture(layer) + onLayerModifiedListener?.onLayerModified() + } + }) + } + }) + + addNormalMap.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + UI.assetSelectionDialog.show( + false, + AssetTextureFilter(), + object : AssetPickerDialog.AssetPickerListener { + override fun onSelected(asset: Asset?) { + layer.normalTextureAsset = asset as TextureAsset + onLayerModifiedListener?.onLayerModified() + // Force shaders to be recompiled + Mundus.postEvent(InvalidateShadersEvent()) + } + }) + } + }) + + removeNormalMap.addListener(object : ClickListener() { + override fun clicked(event: InputEvent?, x: Float, y: Float) { + layer.normalTextureAsset = null + onLayerModifiedListener?.onLayerModified() + Mundus.postEvent(InvalidateShadersEvent()) + } + }) + } + + fun show() { + updateMenuVisibility() + showMenu(UI, Gdx.input.x.toFloat(), (Gdx.graphics.height - Gdx.input.y).toFloat()) + } + + fun updateMenuVisibility() { + if (layer.normalTextureAsset != null) { + addItem(removeNormalMap) + } else { + removeNormalMap.remove() + } + pack() + } + } + + fun setListener(listener: OnLayerRemovedListener) { + onLayerRemovedListener = listener + } + + fun setListener(listener: OnLayerSwapListener) { + onLayerSwapListener = listener + } + + fun setListener(listener: OnLayerModifiedListener) { + onLayerModifiedListener = listener + } + +} \ No newline at end of file