/* * Copyright 2015 Andrew Mac. All rights reserved. * License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause */ #include "common.h" #include "bgfx_utils.h" #include "imgui/imgui.h" #include "camera.h" #include "bounds.h" #include #include #include static float s_texelHalf = 0.0f; static bool s_originBottomLeft = false; static uint32_t s_terrainSize = 256; struct PosTexCoord0Vertex { float m_x; float m_y; float m_z; float m_u; float m_v; static void init() { ms_decl .begin() .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) .end(); } static bgfx::VertexDecl ms_decl; }; bgfx::VertexDecl PosTexCoord0Vertex::ms_decl; struct TerrainData { uint32_t m_mode; bool m_dirty; float m_transform[16]; uint8_t* m_heightMap; PosTexCoord0Vertex* m_vertices; uint32_t m_vertexCount; uint16_t* m_indices; uint32_t m_indexCount; }; struct BrushData { bool m_raise; int32_t m_size; float m_power; }; class Terrain : public entry::AppI { void init(int _argc, char** _argv) BX_OVERRIDE { Args args(_argc, _argv); m_width = 1280; m_height = 720; m_debug = BGFX_DEBUG_TEXT; m_reset = BGFX_RESET_VSYNC; bgfx::init(args.m_type, args.m_pciId); bgfx::reset(m_width, m_height, m_reset); // Enable m_debug text. bgfx::setDebug(m_debug); // Set view 0 clear state. bgfx::setViewClear(0 , BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH , 0x303030ff , 1.0f , 0 ); // Create vertex stream declaration. PosTexCoord0Vertex::init(); // Create program from shaders. m_terrainProgram = loadProgram("vs_terrain", "fs_terrain"); m_terrainHeightTextureProgram = loadProgram("vs_terrain_height_texture", "fs_terrain"); // Imgui. imguiCreate(); m_timeOffset = bx::getHPCounter(); const bgfx::RendererType::Enum renderer = bgfx::getRendererType(); s_texelHalf = bgfx::RendererType::Direct3D9 == renderer ? 0.5f : 0.0f; s_originBottomLeft = bgfx::RendererType::OpenGL == renderer || bgfx::RendererType::OpenGLES == renderer; m_vbh.idx = bgfx::invalidHandle; m_ibh.idx = bgfx::invalidHandle; m_dvbh.idx = bgfx::invalidHandle; m_dibh.idx = bgfx::invalidHandle; m_heightTexture.idx = bgfx::invalidHandle; s_heightTexture = bgfx::createUniform("s_heightTexture", bgfx::UniformType::Int1); m_oldWidth = 0; m_oldHeight = 0; m_oldReset = m_reset; m_scrollArea = 0; m_brush.m_power = 0.5f; m_brush.m_size = 10; m_brush.m_raise = true; uint32_t num = s_terrainSize * s_terrainSize; m_terrain.m_mode = 0; m_terrain.m_dirty = true; m_terrain.m_vertices = (PosTexCoord0Vertex*)BX_ALLOC(entry::getAllocator(), num * sizeof(PosTexCoord0Vertex) ); m_terrain.m_indices = (uint16_t*)BX_ALLOC(entry::getAllocator(), num * sizeof(uint16_t) * 6); m_terrain.m_heightMap = (uint8_t*)BX_ALLOC(entry::getAllocator(), num); bx::mtxSRT(m_terrain.m_transform, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); memset(m_terrain.m_heightMap, 0, sizeof(uint8_t) * s_terrainSize * s_terrainSize); cameraCreate(); const float initialPos[3] = { s_terrainSize/2.0f, 100.0f, 0.0f }; cameraSetPosition(initialPos); cameraSetVerticalAngle(-bx::pi/4.0f); } virtual int shutdown() BX_OVERRIDE { // Cleanup. cameraDestroy(); imguiDestroy(); if (bgfx::isValid(m_ibh) ) { bgfx::destroyIndexBuffer(m_ibh); } if (bgfx::isValid(m_vbh) ) { bgfx::destroyVertexBuffer(m_vbh); } if (bgfx::isValid(m_dibh) ) { bgfx::destroyDynamicIndexBuffer(m_dibh); } if (bgfx::isValid(m_dvbh) ) { bgfx::destroyDynamicVertexBuffer(m_dvbh); } bgfx::destroyUniform(s_heightTexture); if (bgfx::isValid(m_heightTexture) ) { bgfx::destroyTexture(m_heightTexture); } bgfx::destroyProgram(m_terrainProgram); bgfx::destroyProgram(m_terrainHeightTextureProgram); BX_FREE(entry::getAllocator(), m_terrain.m_vertices); BX_FREE(entry::getAllocator(), m_terrain.m_indices); BX_FREE(entry::getAllocator(), m_terrain.m_heightMap); // Shutdown bgfx. bgfx::shutdown(); return 0; } void updateTerrainMesh() { m_terrain.m_vertexCount = 0; for (uint32_t y = 0; y < s_terrainSize; y++) { for (uint32_t x = 0; x < s_terrainSize; x++) { PosTexCoord0Vertex* vert = &m_terrain.m_vertices[m_terrain.m_vertexCount]; vert->m_x = (float)x; vert->m_y = m_terrain.m_heightMap[(y * s_terrainSize) + x]; vert->m_z = (float)y; vert->m_u = (float)x / (float)s_terrainSize; vert->m_v = (float)y / (float)s_terrainSize; m_terrain.m_vertexCount++; } } m_terrain.m_indexCount = 0; for (uint32_t y = 0; y < (s_terrainSize - 1); y++) { uint32_t y_offset = (y * s_terrainSize); for (uint32_t x = 0; x < (s_terrainSize - 1); x++) { m_terrain.m_indices[m_terrain.m_indexCount + 0] = y_offset + x + 1; m_terrain.m_indices[m_terrain.m_indexCount + 1] = y_offset + x + s_terrainSize; m_terrain.m_indices[m_terrain.m_indexCount + 2] = y_offset + x; m_terrain.m_indices[m_terrain.m_indexCount + 3] = y_offset + x + s_terrainSize + 1; m_terrain.m_indices[m_terrain.m_indexCount + 4] = y_offset + x + s_terrainSize; m_terrain.m_indices[m_terrain.m_indexCount + 5] = y_offset + x + 1; m_terrain.m_indexCount += 6; } } } void updateTerrain() { const bgfx::Memory* mem; switch (m_terrain.m_mode) { default: // Vertex Buffer : Destroy and recreate a regular vertex buffer to update terrain. updateTerrainMesh(); if (bgfx::isValid(m_vbh) ) { bgfx::destroyVertexBuffer(m_vbh); } mem = bgfx::makeRef(&m_terrain.m_vertices[0], sizeof(PosTexCoord0Vertex) * m_terrain.m_vertexCount); m_vbh = bgfx::createVertexBuffer(mem, PosTexCoord0Vertex::ms_decl); if (bgfx::isValid(m_ibh) ) { bgfx::destroyIndexBuffer(m_ibh); } mem = bgfx::makeRef(&m_terrain.m_indices[0], sizeof(uint16_t) * m_terrain.m_indexCount); m_ibh = bgfx::createIndexBuffer(mem); break; case 1: // Dynamic Vertex Buffer : Utilize dynamic vertex buffer to update terrain. updateTerrainMesh(); if (!bgfx::isValid(m_dvbh) ) { m_dvbh = bgfx::createDynamicVertexBuffer(m_terrain.m_vertexCount, PosTexCoord0Vertex::ms_decl); } mem = bgfx::makeRef(&m_terrain.m_vertices[0], sizeof(PosTexCoord0Vertex) * m_terrain.m_vertexCount); bgfx::updateDynamicVertexBuffer(m_dvbh, 0, mem); if (!bgfx::isValid(m_dibh) ) { m_dibh = bgfx::createDynamicIndexBuffer(m_terrain.m_indexCount); } mem = bgfx::makeRef(&m_terrain.m_indices[0], sizeof(uint16_t) * m_terrain.m_indexCount); bgfx::updateDynamicIndexBuffer(m_dibh, 0, mem); break; case 2: // Height Texture: Update a height texture that is sampled in the terrain vertex shader. if (!bgfx::isValid(m_vbh) || !bgfx::isValid(m_ibh) ) { updateTerrainMesh(); mem = bgfx::makeRef(&m_terrain.m_vertices[0], sizeof(PosTexCoord0Vertex) * m_terrain.m_vertexCount); m_vbh = bgfx::createVertexBuffer(mem, PosTexCoord0Vertex::ms_decl); mem = bgfx::makeRef(&m_terrain.m_indices[0], sizeof(uint16_t) * m_terrain.m_indexCount); m_ibh = bgfx::createIndexBuffer(mem); } if (!bgfx::isValid(m_heightTexture) ) { m_heightTexture = bgfx::createTexture2D(s_terrainSize, s_terrainSize, 1, bgfx::TextureFormat::R8); } mem = bgfx::makeRef(&m_terrain.m_heightMap[0], sizeof(uint8_t) * s_terrainSize * s_terrainSize); bgfx::updateTexture2D(m_heightTexture, 0, 0, 0, s_terrainSize, s_terrainSize, mem); break; } } void paintTerrainHeight(uint32_t _x, uint32_t _y) { for (int32_t area_y = -m_brush.m_size; area_y < m_brush.m_size; ++area_y) { for (int32_t area_x = -m_brush.m_size; area_x < m_brush.m_size; ++area_x) { int32_t brush_x = _x + area_x; if (brush_x < 0 || brush_x > (int32_t)s_terrainSize) { continue; } int32_t brush_y = _y + area_y; if (brush_y < 0 || brush_y > (int32_t)s_terrainSize) { continue; } uint32_t heightMapPos = (brush_y * s_terrainSize) + brush_x; float height = (float)m_terrain.m_heightMap[heightMapPos]; // Brush attenuation float a2 = (float)(area_x * area_x); float b2 = (float)(area_y * area_y); float brushAttn = m_brush.m_size - bx::fsqrt(a2 + b2); // Raise/Lower and scale by brush power. height += (bx::fclamp(brushAttn * m_brush.m_power, 0.0, m_brush.m_power) * m_brush.m_raise) ? 1.0f : -1.0f ; m_terrain.m_heightMap[heightMapPos] = (uint8_t)bx::fclamp(height, 0.0f, 255.0f); m_terrain.m_dirty = true; } } } void mousePickTerrain() { float ray_clip[4]; ray_clip[0] = ( (2.0f * m_mouseState.m_mx) / m_width - 1.0f) * -1.0f; ray_clip[1] = ( (1.0f - (2.0f * m_mouseState.m_my) / m_height) ) * -1.0f; ray_clip[2] = -1.0f; ray_clip[3] = 1.0f; float invProjMtx[16]; bx::mtxInverse(invProjMtx, m_projMtx); float ray_eye[4]; bx::vec4MulMtx(ray_eye, ray_clip, invProjMtx); ray_eye[2] = -1.0f; ray_eye[3] = 0.0f; float invViewMtx[16]; bx::mtxInverse(invViewMtx, m_viewMtx); float ray_world[4]; bx::vec4MulMtx(ray_world, ray_eye, invViewMtx); float ray_dir[3]; bx::vec3Norm(ray_dir, ray_world); ray_dir[0] *= -1.0; ray_dir[1] *= -1.0; ray_dir[2] *= -1.0; float pos[3]; cameraGetPosition(pos); for (int i = 0; i < 1000; ++i) { bx::vec3Add(pos, pos, ray_dir); if (pos[0] < 0 || pos[0] > s_terrainSize || pos[2] < 0 || pos[2] > s_terrainSize) { continue; } uint32_t heightMapPos = ( (uint32_t)pos[2] * s_terrainSize) + (uint32_t)pos[0]; if ( pos[1] < m_terrain.m_heightMap[heightMapPos] ) { paintTerrainHeight( (uint32_t)pos[0], (uint32_t)pos[2]); return; } } } bool update() BX_OVERRIDE { if (!entry::processEvents(m_width, m_height, m_debug, m_reset, &m_mouseState) ) { int64_t now = bx::getHPCounter(); static int64_t last = now; const int64_t frameTime = now - last; last = now; const double freq = double(bx::getHPFrequency() ); const double toMs = 1000.0/freq; const float deltaTime = float(frameTime/freq); // Use m_debug font to print information about this example. bgfx::dbgTextClear(); bgfx::dbgTextPrintf(0, 1, 0x4f, "bgfx/examples/27-terrain"); bgfx::dbgTextPrintf(0, 2, 0x6f, "Description: Terrain painting example."); bgfx::dbgTextPrintf(0, 3, 0x0f, "Frame: % 7.3f[ms]", double(frameTime)*toMs); imguiBeginFrame(m_mouseState.m_mx , m_mouseState.m_my , (m_mouseState.m_buttons[entry::MouseButton::Left ] ? IMGUI_MBUT_LEFT : 0) | (m_mouseState.m_buttons[entry::MouseButton::Right ] ? IMGUI_MBUT_RIGHT : 0) | (m_mouseState.m_buttons[entry::MouseButton::Middle] ? IMGUI_MBUT_MIDDLE : 0) , m_mouseState.m_mz , m_width , m_height ); imguiBeginScrollArea("Settings", m_width - m_width / 5 - 10, 10, m_width / 5, m_height / 3, &m_scrollArea); imguiSeparatorLine(); if (imguiCheck("Vertex Buffer", (m_terrain.m_mode == 0) ) ) { m_terrain.m_mode = 0; m_terrain.m_dirty = true; } if (imguiCheck("Dynamic Vertex Buffer", (m_terrain.m_mode == 1) ) ) { m_terrain.m_mode = 1; m_terrain.m_dirty = true; } if (imguiCheck("Height Texture", (m_terrain.m_mode == 2) ) ) { m_terrain.m_mode = 2; m_terrain.m_dirty = true; } imguiSeparatorLine(); if (imguiCheck("Raise Terrain", m_brush.m_raise) ) { m_brush.m_raise = !m_brush.m_raise; } imguiSlider("Brush Size", m_brush.m_size, 1, 50); imguiSlider("Brush Power", m_brush.m_power, 0.0f, 1.0f, 0.01f); imguiEndScrollArea(); imguiEndFrame(); if (!imguiMouseOverArea() ) { // Update camera. cameraUpdate(deltaTime, m_mouseState); if (!!m_mouseState.m_buttons[entry::MouseButton::Left]) { mousePickTerrain(); } } // Update terrain. if (m_terrain.m_dirty) { updateTerrain(); m_terrain.m_dirty = false; } // Set view 0 default viewport. bgfx::setViewRect(0, 0, 0, m_width, m_height); cameraGetViewMtx(m_viewMtx); bx::mtxProj(m_projMtx, 60.0f, float(m_width) / float(m_height), 0.1f, 2000.0f, s_originBottomLeft); bgfx::setViewTransform(0, m_viewMtx, m_projMtx); bgfx::setTransform(m_terrain.m_transform); switch (m_terrain.m_mode) { default: bgfx::setVertexBuffer(m_vbh); bgfx::setIndexBuffer(m_ibh); bgfx::submit(0, m_terrainProgram); break; case 1: bgfx::setVertexBuffer(m_dvbh); bgfx::setIndexBuffer(m_dibh); bgfx::submit(0, m_terrainProgram); break; case 2: bgfx::setVertexBuffer(m_vbh); bgfx::setIndexBuffer(m_ibh); bgfx::setTexture(0, s_heightTexture, m_heightTexture); bgfx::submit(0, m_terrainHeightTextureProgram); break; } // Advance to next frame. Rendering thread will be kicked to // process submitted rendering primitives. bgfx::frame(); return true; } return false; } bgfx::VertexBufferHandle m_vbh; bgfx::IndexBufferHandle m_ibh; bgfx::DynamicVertexBufferHandle m_dvbh; bgfx::DynamicIndexBufferHandle m_dibh; bgfx::ProgramHandle m_terrainProgram; bgfx::ProgramHandle m_terrainHeightTextureProgram; bgfx::UniformHandle s_heightTexture; bgfx::TextureHandle m_heightTexture; float m_viewMtx[16]; float m_projMtx[16]; uint32_t m_width; uint32_t m_height; uint32_t m_debug; uint32_t m_reset; uint32_t m_oldWidth; uint32_t m_oldHeight; uint32_t m_oldReset; int32_t m_scrollArea; TerrainData m_terrain; BrushData m_brush; entry::MouseState m_mouseState; int64_t m_timeOffset; }; ENTRY_IMPLEMENT_MAIN(Terrain);