diff options
Diffstat (limited to 'src/main/kotlin/Terrain.kt')
| -rw-r--r-- | src/main/kotlin/Terrain.kt | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/src/main/kotlin/Terrain.kt b/src/main/kotlin/Terrain.kt new file mode 100644 index 0000000..4f854ab --- /dev/null +++ b/src/main/kotlin/Terrain.kt @@ -0,0 +1,230 @@ +import info.laht.threekt.THREE +import info.laht.threekt.cameras.PerspectiveCamera +import info.laht.threekt.external.controls.OrbitControls +import info.laht.threekt.external.libs.datgui.GUIParams +import info.laht.threekt.external.libs.datgui.NumberController +import info.laht.threekt.external.libs.datgui.dat +import info.laht.threekt.geometries.BoxGeometry +import info.laht.threekt.geometries.PlaneGeometry +import info.laht.threekt.lights.AmbientLight +import info.laht.threekt.lights.PointLight +import info.laht.threekt.materials.MeshBasicMaterial +import info.laht.threekt.materials.MeshPhongMaterial +import info.laht.threekt.math.ColorConstants +import info.laht.threekt.objects.Mesh +import info.laht.threekt.renderers.WebGLRenderer +import info.laht.threekt.renderers.WebGLRendererParams +import info.laht.threekt.scenes.Scene +import kotlin.browser.document +import kotlin.browser.window + +class Terrain { + + private val renderer: WebGLRenderer + private val scene: Scene = Scene() + private val camera: PerspectiveCamera + private val controls: OrbitControls + private lateinit var simplexNoise: SimplexNoise + private lateinit var terrainMesh: Mesh + private lateinit var waterMesh: Mesh + + private val size: Int = 256 + + val zoomFactor = 80 + val scalingFactor = 11 + val autoRotate = false + + val waterHeight = 5 + val snowHeightThreshold = 15 + + val showWireframe = false + + init { + + scene.add(AmbientLight(0xeeeeee)) + + PointLight(0xffffff) + .apply { + castShadow = true + position.set(0, 90, 200) + }.also(scene::add) + + camera = PerspectiveCamera(75, window.innerWidth.toDouble() / window.innerHeight, 0.1, 1000) + camera.position.setZ(45) + camera.position.setY(-73) + + renderer = WebGLRenderer(WebGLRendererParams(antialias = true)) + .apply { + setClearColor(ColorConstants.black, 1) + setSize(window.innerWidth, window.innerHeight) + } + + controls = OrbitControls(camera, renderer.domElement) + + initGui() + + seedNoise() + + generateTerrain() + + generateWater() + + window.addEventListener("resize", { + camera.aspect = window.innerWidth.toDouble() / window.innerHeight + camera.updateProjectionMatrix() + + renderer.setSize(window.innerWidth, window.innerHeight) + }, false) + + document.getElementById("container") + ?.apply { + appendChild(renderer.domElement) + } + + } + + private fun seedNoise() { + simplexNoise = js("new SimplexNoise()") as SimplexNoise + } + + fun reseedNoise() { + seedNoise() + generateTerrain() + } + + private fun generateTerrain() { + + if (this::terrainMesh.isInitialized) { + scene.remove(terrainMesh) + } + + val terrainGeom = PlaneGeometry(100,100, size-1, size-1) + + for (x in 0 until size) { + for (y in 0 until size) { + + if (x == 0 || x == size-1 || y == 0 || y == size-1) { + terrainGeom.vertices[x + (y*size)].setZ(0) + } else { + + var noise = simplexNoise.noise2D(x / zoomFactor.toDouble(), y / zoomFactor.toDouble()) + noise += (0.01 * simplexNoise.noise2D(x.toDouble(), y.toDouble())) + + noise += 1 + noise *= scalingFactor + terrainGeom.vertices[x + (y * size)].setZ(noise) + } + } + } + + applyHeightMapColour(terrainGeom) + + terrainGeom.computeFaceNormals() + terrainGeom.computeVertexNormals() + terrainGeom.colorsNeedUpdate = true + + val terrainMaterial = MeshPhongMaterial() + .apply { + if (showWireframe) { + wireframe = true + wireframeLinewidth = 1.0 + } + vertexColors = THREE.FaceColors + } + + terrainMesh = Mesh(terrainGeom, terrainMaterial) + .apply { receiveShadows = true } + .also(scene::add) + } + + private fun applyHeightMapColour(planeGeometry: PlaneGeometry) { + planeGeometry.faces.forEach { face -> + val vertexes = listOf(planeGeometry.vertices[face.a].z, planeGeometry.vertices[face.b].z, planeGeometry.vertices[face.c].z) + val min = vertexes.min()!! + + if (min < snowHeightThreshold) { + face.color?.set(ColorConstants.forestgreen) + } else if (min >= snowHeightThreshold) { + face.color?.set(ColorConstants.floralwhite) + } + } + } + + private fun generateWater() { + + if (this::waterMesh.isInitialized) { + scene.remove(waterMesh) + } + + val waterGeom = BoxGeometry(99, 99, waterHeight) + val waterMaterial = MeshBasicMaterial() + .apply { + color.set(ColorConstants.aqua) + transparent = true + opacity = 0.7 + } + + waterMesh = Mesh(waterGeom, waterMaterial) + .also { + scene.add(it) + it.translateZ(waterHeight/2) + } + } + + @Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE") + fun initGui() { + dat.GUI( + GUIParams( + closed = false + ) + ).also { + + (it.add(this, "zoomFactor") as NumberController).apply { + min(10) + .max(200) + .step(5) + .onChange { generateTerrain() } + } + + (it.add(this, "scalingFactor") as NumberController).apply { + min(0) + .max(50) + .step(1) + .onChange { generateTerrain() } + } + + (it.add(this, "waterHeight") as NumberController).apply { + min(0) + .max(30) + .step(1) + .onChange { generateWater() } + } + + (it.add(this, "snowHeightThreshold") as NumberController).apply { + min(0) + .max(30) + .step(1) + .onChange { generateTerrain() } + } + + it.add(this, "reseedNoise") + it.add(this, "showWireframe").onChange { generateTerrain() } + it.add(this, "autoRotate") + } + } + + fun animate() { + + window.requestAnimationFrame { + if (autoRotate) { + terrainMesh.rotation.z += 0.005 + waterMesh.rotation.z += 0.005 +// println("x:${camera.position.x} y: ${camera.position.y} z:${camera.position.z}") + } + animate() + } + + renderer.render(scene, camera) + } + +} |