diff --git a/CMakeLists.txt b/CMakeLists.txt index b5e956aa0..f7387b51d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,8 @@ project(ignition-rendering5 VERSION 5.0.0) # Find ignition-cmake #============================================================================ # If you get an error at this line, you need to install ignition-cmake -find_package(ignition-cmake2 REQUIRED) +find_package(ignition-cmake2 2.3 REQUIRED) +set(IGN_CMAKE_VER ${ignition-cmake2_VERSION_MAJOR}) #============================================================================ # Set up the project diff --git a/examples/heightmap/CMakeLists.txt b/examples/heightmap/CMakeLists.txt new file mode 100644 index 000000000..490f612cc --- /dev/null +++ b/examples/heightmap/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR) +project(ignition-rendering-heightmap) +find_package(ignition-rendering5 REQUIRED) + +include_directories(SYSTEM + ${PROJECT_BINARY_DIR} +) + +find_package(GLUT REQUIRED) +include_directories(SYSTEM ${GLUT_INCLUDE_DIRS}) +link_directories(${GLUT_LIBRARY_DIRS}) + +find_package(OpenGL REQUIRED) +include_directories(SYSTEM ${OpenGL_INCLUDE_DIRS}) +link_directories(${OpenGL_LIBRARY_DIRS}) + +if (NOT APPLE) + find_package(GLEW REQUIRED) + include_directories(SYSTEM ${GLEW_INCLUDE_DIRS}) + link_directories(${GLEW_LIBRARY_DIRS}) +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations") + +configure_file (example_config.hh.in ${PROJECT_BINARY_DIR}/example_config.hh) + +add_executable(heightmap Main.cc GlutWindow.cc) + +target_link_libraries(heightmap + ${GLUT_LIBRARIES} + ${OPENGL_LIBRARIES} + ${GLEW_LIBRARIES} + ${IGNITION-RENDERING_LIBRARIES} +) + +add_custom_command(TARGET heightmap POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/media + $/media) diff --git a/examples/heightmap/GlutWindow.cc b/examples/heightmap/GlutWindow.cc new file mode 100644 index 000000000..7c869502e --- /dev/null +++ b/examples/heightmap/GlutWindow.cc @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2020 Open Source Robotics Foundation + * + * 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. + * + */ + +#if __APPLE__ + #include + #include + #include +#else + #include + #include + #include +#endif + +#if not defined(__APPLE__) && not defined(_WIN32) + #include +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include "GlutWindow.hh" + +#define KEY_ESC 27 +#define KEY_TAB 9 + +////////////////////////////////////////////////// +unsigned int imgw = 0; +unsigned int imgh = 0; + +std::vector g_cameras; +ir::CameraPtr g_camera; +ir::CameraPtr g_currCamera; +unsigned int g_cameraIndex = 0; +ir::ImagePtr g_image; + +bool g_initContext = false; + +#if __APPLE__ + CGLContextObj g_context; + CGLContextObj g_glutContext; +#elif _WIN32 +#else + GLXContext g_context; + Display *g_display; + GLXDrawable g_drawable; + GLXContext g_glutContext; + Display *g_glutDisplay; + GLXDrawable g_glutDrawable; +#endif + +// view control variables +ir::RayQueryPtr g_rayQuery; +ir::OrbitViewController g_viewControl; +ir::RayQueryResult g_target; +struct mouseButton +{ + int button = 0; + int state = GLUT_UP; + int x = 0; + int y = 0; + int motionX = 0; + int motionY = 0; + int dragX = 0; + int dragY = 0; + int scroll = 0; + bool buttonDirty = false; + bool motionDirty = false; +}; +struct mouseButton g_mouse; +std::mutex g_mouseMutex; + +////////////////////////////////////////////////// +void mouseCB(int _button, int _state, int _x, int _y) +{ + // ignore unknown mouse button numbers + if (_button >= 5) + return; + + std::lock_guard lock(g_mouseMutex); + g_mouse.button = _button; + g_mouse.state = _state; + g_mouse.x = _x; + g_mouse.y = _y; + g_mouse.motionX = _x; + g_mouse.motionY = _y; + g_mouse.buttonDirty = true; +} + +////////////////////////////////////////////////// +void motionCB(int _x, int _y) +{ + std::lock_guard lock(g_mouseMutex); + int deltaX = _x - g_mouse.motionX; + int deltaY = _y - g_mouse.motionY; + g_mouse.motionX = _x; + g_mouse.motionY = _y; + + if (g_mouse.motionDirty) + { + g_mouse.dragX += deltaX; + g_mouse.dragY += deltaY; + } + else + { + g_mouse.dragX = deltaX; + g_mouse.dragY = deltaY; + } + g_mouse.motionDirty = true; +} + +////////////////////////////////////////////////// +void handleMouse() +{ + std::lock_guard lock(g_mouseMutex); + // only ogre supports ray query for now so use + // ogre camera located at camera index = 0. + ir::CameraPtr rayCamera = g_cameras[0]; + if (!g_rayQuery) + { + g_rayQuery = rayCamera->Scene()->CreateRayQuery(); + if (!g_rayQuery) + { + ignerr << "Failed to create Ray Query" << std::endl; + return; + } + } + if (g_mouse.buttonDirty) + { + g_mouse.buttonDirty = false; + double nx = + 2.0 * g_mouse.x / static_cast(rayCamera->ImageWidth()) - 1.0; + double ny = 1.0 - + 2.0 * g_mouse.y / static_cast(rayCamera->ImageHeight()); + g_rayQuery->SetFromCamera(rayCamera, ignition::math::Vector2d(nx, ny)); + g_target = g_rayQuery->ClosestPoint(); + if (!g_target) + { + // set point to be 10m away if no intersection found + g_target.point = g_rayQuery->Origin() + g_rayQuery->Direction() * 10; + return; + } + + // mouse wheel scroll zoom + if ((g_mouse.button == 3 || g_mouse.button == 4) && + g_mouse.state == GLUT_UP) + { + double scroll = (g_mouse.button == 3) ? -1.0 : 1.0; + double distance = rayCamera->WorldPosition().Distance( + g_target.point); + int factor = 1; + double amount = -(scroll * factor) * (distance / 5.0); + for (ir::CameraPtr camera : g_cameras) + { + g_viewControl.SetCamera(camera); + g_viewControl.SetTarget(g_target.point); + g_viewControl.Zoom(amount); + } + } + } + + if (g_mouse.motionDirty) + { + g_mouse.motionDirty = false; + auto drag = ignition::math::Vector2d(g_mouse.dragX, g_mouse.dragY); + + // left mouse button pan + if (g_mouse.button == GLUT_LEFT_BUTTON && g_mouse.state == GLUT_DOWN) + { + for (ir::CameraPtr camera : g_cameras) + { + g_viewControl.SetCamera(camera); + g_viewControl.SetTarget(g_target.point); + g_viewControl.Pan(drag); + } + } + else if (g_mouse.button == GLUT_MIDDLE_BUTTON && g_mouse.state == GLUT_DOWN) + { + for (ir::CameraPtr camera : g_cameras) + { + g_viewControl.SetCamera(camera); + g_viewControl.SetTarget(g_target.point); + g_viewControl.Orbit(drag); + } + } + // right mouse button zoom + else if (g_mouse.button == GLUT_RIGHT_BUTTON && g_mouse.state == GLUT_DOWN) + { + double hfov = rayCamera->HFOV().Radian(); + double vfov = 2.0f * atan(tan(hfov / 2.0f) / + rayCamera->AspectRatio()); + double distance = rayCamera->WorldPosition().Distance( + g_target.point); + double amount = ((-g_mouse.dragY / + static_cast(rayCamera->ImageHeight())) + * distance * tan(vfov/2.0) * 6.0); + for (ir::CameraPtr camera : g_cameras) + { + g_viewControl.SetCamera(camera); + g_viewControl.SetTarget(g_target.point); + g_viewControl.Zoom(amount); + } + } + } +} + + +////////////////////////////////////////////////// +void displayCB() +{ +#if __APPLE__ + CGLSetCurrentContext(g_context); +#elif _WIN32 +#else + if (g_display) + { + glXMakeCurrent(g_display, g_drawable, g_context); + } +#endif + + g_cameras[g_cameraIndex]->Capture(*g_image); + handleMouse(); + +#if __APPLE__ + CGLSetCurrentContext(g_glutContext); +#elif _WIN32 +#else + glXMakeCurrent(g_glutDisplay, g_glutDrawable, g_glutContext); +#endif + + unsigned char *data = g_image->Data(); + + glClearColor(0.5, 0.5, 0.5, 1); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glPixelZoom(1, -1); + glRasterPos2f(-1, 1); + glDrawPixels(imgw, imgh, GL_RGB, GL_UNSIGNED_BYTE, data); + + glutSwapBuffers(); +} + +////////////////////////////////////////////////// +void idleCB() +{ + glutPostRedisplay(); +} + +////////////////////////////////////////////////// +void keyboardCB(unsigned char _key, int, int) +{ + if (_key == KEY_ESC || _key == 'q' || _key == 'Q') + { + exit(0); + } + else if (_key == KEY_TAB) + { + g_cameraIndex = (g_cameraIndex + 1) % g_cameras.size(); + } +} + +////////////////////////////////////////////////// +void initCamera(ir::CameraPtr _camera) +{ + g_camera = _camera; + imgw = g_camera->ImageWidth(); + imgh = g_camera->ImageHeight(); + ir::Image image = g_camera->CreateImage(); + g_image = std::make_shared(image); + g_camera->Capture(*g_image); +} + +////////////////////////////////////////////////// +void initContext() +{ + glutInitDisplayMode(GLUT_DOUBLE); + glutInitWindowPosition(0, 0); + glutInitWindowSize(imgw, imgh); + glutCreateWindow("Heightmap"); + glutDisplayFunc(displayCB); + glutIdleFunc(idleCB); + glutKeyboardFunc(keyboardCB); + + glutMouseFunc(mouseCB); + glutMotionFunc(motionCB); +} + +////////////////////////////////////////////////// +void printUsage() +{ + std::cout << "===============================" << std::endl; + std::cout << " TAB - Switch render engines " << std::endl; + std::cout << " ESC - Exit " << std::endl; + std::cout << "===============================" << std::endl; +} + +////////////////////////////////////////////////// +void run(std::vector _cameras) +{ + if (_cameras.empty()) + { + ignerr << "No cameras found. Scene will not be rendered" << std::endl; + return; + } + +#if __APPLE__ + g_context = CGLGetCurrentContext(); +#elif _WIN32 +#else + g_context = glXGetCurrentContext(); + g_display = glXGetCurrentDisplay(); + g_drawable = glXGetCurrentDrawable(); +#endif + + g_cameras = _cameras; + initCamera(_cameras[0]); + initContext(); + printUsage(); + +#if __APPLE__ + g_glutContext = CGLGetCurrentContext(); +#elif _WIN32 +#else + g_glutDisplay = glXGetCurrentDisplay(); + g_glutDrawable = glXGetCurrentDrawable(); + g_glutContext = glXGetCurrentContext(); +#endif + + glutMainLoop(); +} + + diff --git a/examples/heightmap/GlutWindow.hh b/examples/heightmap/GlutWindow.hh new file mode 100644 index 000000000..2d8349ac3 --- /dev/null +++ b/examples/heightmap/GlutWindow.hh @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 Open Source Robotics Foundation + * + * 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. + * + */ +#ifndef IGNITION_RENDERING_EXAMPLES_HEIGHTMAP_GLUTWINDOW_HH_ +#define IGNITION_RENDERING_EXAMPLES_HEIGHTMAP_GLUTWINDOW_HH_ + +#include +#include "ignition/rendering/RenderTypes.hh" + +namespace ir = ignition::rendering; + +/// \brief Run the demo and render the scene from the cameras +/// \param[in] _cameras Cameras in the scene +void run(std::vector _cameras); + +#endif diff --git a/examples/heightmap/Main.cc b/examples/heightmap/Main.cc new file mode 100644 index 000000000..679daf9c2 --- /dev/null +++ b/examples/heightmap/Main.cc @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2020 Open Source Robotics Foundation + * + * 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. + * + */ + +#if defined(__APPLE__) + #include + #include +#elif not defined(_WIN32) + #include + #include + #include +#endif + +#include +#include + +#include +#include +#include +#include + +#include "example_config.hh" +#include "GlutWindow.hh" + +using namespace ignition; +using namespace rendering; + +const std::string RESOURCE_PATH = + common::joinPaths(std::string(PROJECT_BINARY_PATH), "media"); + +////////////////////////////////////////////////// +void buildScene(ScenePtr _scene) +{ + // initialize _scene + _scene->SetAmbientLight(0.3, 0.3, 0.3); + _scene->SetBackgroundColor(0.3, 0.3, 0.3); + VisualPtr root = _scene->RootVisual(); + + // create directional light + DirectionalLightPtr light0 = _scene->CreateDirectionalLight(); + light0->SetDirection(0.5, 0.5, -1); + light0->SetDiffuseColor(0.8, 0.8, 0.8); + light0->SetSpecularColor(0.5, 0.5, 0.5); + root->AddChild(light0); + +//! [create a heightmap] + auto data = std::make_shared(); + data->Load(common::joinPaths(RESOURCE_PATH, "heightmap_bowl.png")); + + HeightmapDescriptor desc; + desc.SetName("example_bowl"); + desc.SetData(data); + desc.SetSize({17, 17, 10}); + desc.SetSampling(2u); + desc.SetUseTerrainPaging(false); + + HeightmapTexture textureA; + textureA.SetSize(1.0); + textureA.SetDiffuse("../media/dirt_diffusespecular.png"); + textureA.SetNormal("../media/flat_normal.png"); + desc.AddTexture(textureA); + + HeightmapBlend blendA; + blendA.SetMinHeight(2.0); + blendA.SetFadeDistance(5.0); + desc.AddBlend(blendA); + + HeightmapTexture textureB; + textureB.SetSize(1.0); + textureB.SetDiffuse("../media/grass_diffusespecular.png"); + textureB.SetNormal("../media/flat_normal.png"); + desc.AddTexture(textureB); + + HeightmapBlend blendB; + blendB.SetMinHeight(4.0); + blendB.SetFadeDistance(5.0); + desc.AddBlend(blendB); + + HeightmapTexture textureC; + textureC.SetSize(1.0); + textureC.SetDiffuse("../media/fungus_diffusespecular.png"); + textureC.SetNormal("../media/flat_normal.png"); + desc.AddTexture(textureC); + + auto heightmapGeom = _scene->CreateHeightmap(desc); + + auto vis = _scene->CreateVisual(); + vis->AddGeometry(heightmapGeom); + root->AddChild(vis); +//! [create a heightmap] + +//! [create another heightmap] + auto data2 = std::make_shared(); + data2->Load(common::joinPaths(RESOURCE_PATH, "city_terrain.jpg")); + + HeightmapDescriptor desc2; + desc2.SetName("example_city"); + desc2.SetData(data2); + desc2.SetSize({26, 26, 20}); + desc2.SetSampling(2u); + desc2.SetUseTerrainPaging(true); + + HeightmapTexture textureA2; + textureA2.SetSize(1.0); + textureA2.SetDiffuse("../media/fungus_diffusespecular.png"); + textureA2.SetNormal("../media/flat_normal.png"); + desc2.AddTexture(textureA2); + + HeightmapBlend blendA2; + blendA2.SetMinHeight(2.0); + blendA2.SetFadeDistance(5.0); + desc2.AddBlend(blendA2); + + HeightmapTexture textureB2; + textureB2.SetSize(1.0); + textureB2.SetDiffuse("../media/grass_diffusespecular.png"); + textureB2.SetNormal("../media/flat_normal.png"); + desc2.AddTexture(textureB2); + + HeightmapBlend blendB2; + blendB2.SetMinHeight(8.0); + blendB2.SetFadeDistance(5.0); + desc2.AddBlend(blendB2); + + HeightmapTexture textureC2; + textureC2.SetSize(1.0); + textureC2.SetDiffuse("../media/dirt_diffusespecular.png"); + textureC2.SetNormal("../media/flat_normal.png"); + desc2.AddTexture(textureC2); + desc2.SetPosition({30, 0, 0}); + auto heightmapGeom2 = _scene->CreateHeightmap(desc2); + + auto vis2 = _scene->CreateVisual(); + vis2->AddGeometry(heightmapGeom2); + root->AddChild(vis2); +//! [create another heightmap] + + // create gray material + MaterialPtr gray = _scene->CreateMaterial(); + gray->SetAmbient(0.7, 0.7, 0.7); + gray->SetDiffuse(0.7, 0.7, 0.7); + gray->SetSpecular(0.7, 0.7, 0.7); + +//! [create grid visual] + VisualPtr grid = _scene->CreateVisual(); + GridPtr gridGeom = _scene->CreateGrid(); + gridGeom->SetCellCount(20); + gridGeom->SetCellLength(1); + gridGeom->SetVerticalCellCount(0); + grid->AddGeometry(gridGeom); + grid->SetLocalPosition(3, 0, 0.0); + grid->SetMaterial(gray); + root->AddChild(grid); +//! [create grid visual] + +//! [create camera] + CameraPtr camera = _scene->CreateCamera("camera"); + camera->SetLocalPosition(1.441, 25.787, 17.801); + camera->SetLocalRotation(0.0, 0.588, -1.125); + camera->SetImageWidth(800); + camera->SetImageHeight(600); + camera->SetAntiAliasing(2); + camera->SetAspectRatio(1.333); + camera->SetHFOV(IGN_PI / 2); + root->AddChild(camera); +//! [create camera] +} + +////////////////////////////////////////////////// +CameraPtr createCamera(const std::string &_engineName) +{ + // create and populate scene + RenderEngine *engine = rendering::engine(_engineName); + if (!engine) + { + ignwarn << "Engine '" << _engineName + << "' is not supported" << std::endl; + return CameraPtr(); + } + ScenePtr scene = engine->CreateScene("scene"); + buildScene(scene); + + // return camera sensor + SensorPtr sensor = scene->SensorByName("camera"); + return std::dynamic_pointer_cast(sensor); +} + +////////////////////////////////////////////////// +int main(int _argc, char** _argv) +{ + glutInit(&_argc, _argv); + + common::Console::SetVerbosity(4); + std::vector engineNames; + std::vector cameras; + + engineNames.push_back("ogre"); + + for (auto engineName : engineNames) + { + try + { + CameraPtr camera = createCamera(engineName); + if (camera) + { + cameras.push_back(camera); + } + } + catch (...) + { + // std::cout << ex.what() << std::endl; + std::cerr << "Error starting up: " << engineName << std::endl; + } + } + run(cameras); + return 0; +} diff --git a/examples/heightmap/example_config.hh.in b/examples/heightmap/example_config.hh.in new file mode 100644 index 000000000..6e44e6df7 --- /dev/null +++ b/examples/heightmap/example_config.hh.in @@ -0,0 +1 @@ +#define PROJECT_BINARY_PATH "${PROJECT_BINARY_DIR}" diff --git a/examples/heightmap/media/city_terrain.jpg b/examples/heightmap/media/city_terrain.jpg new file mode 100644 index 000000000..00e9820ac Binary files /dev/null and b/examples/heightmap/media/city_terrain.jpg differ diff --git a/examples/heightmap/media/dirt_diffusespecular.png b/examples/heightmap/media/dirt_diffusespecular.png new file mode 100644 index 000000000..2b241fe3e Binary files /dev/null and b/examples/heightmap/media/dirt_diffusespecular.png differ diff --git a/examples/heightmap/media/flat_normal.png b/examples/heightmap/media/flat_normal.png new file mode 100644 index 000000000..c6590fb93 Binary files /dev/null and b/examples/heightmap/media/flat_normal.png differ diff --git a/examples/heightmap/media/fungus_diffusespecular.png b/examples/heightmap/media/fungus_diffusespecular.png new file mode 100644 index 000000000..0c5230a08 Binary files /dev/null and b/examples/heightmap/media/fungus_diffusespecular.png differ diff --git a/examples/heightmap/media/grass_diffusespecular.png b/examples/heightmap/media/grass_diffusespecular.png new file mode 100644 index 000000000..0730e8b78 Binary files /dev/null and b/examples/heightmap/media/grass_diffusespecular.png differ diff --git a/examples/heightmap/media/heightmap_bowl.png b/examples/heightmap/media/heightmap_bowl.png new file mode 100644 index 000000000..52df5fcd0 Binary files /dev/null and b/examples/heightmap/media/heightmap_bowl.png differ diff --git a/include/ignition/rendering/Heightmap.hh b/include/ignition/rendering/Heightmap.hh new file mode 100644 index 000000000..dbd818b68 --- /dev/null +++ b/include/ignition/rendering/Heightmap.hh @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 Open Source Robotics Foundation + * + * 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. + * + */ +#ifndef IGNITION_RENDERING_HEIGHTMAP_HH_ +#define IGNITION_RENDERING_HEIGHTMAP_HH_ + +#include "ignition/rendering/config.hh" +#include "ignition/rendering/Geometry.hh" +#include "ignition/rendering/HeightmapDescriptor.hh" + +namespace ignition +{ + namespace rendering + { + inline namespace IGNITION_RENDERING_VERSION_NAMESPACE { + // + /// \class Heightmap Heightmap.hh ignition/rendering/Heightmap + /// \brief A terrain defined by a heightfield. + class Heightmap : + public virtual Geometry + { + /// \brief Get the immutable heightmap descriptor. + /// \return Descriptor with heightmap information. + public: virtual const HeightmapDescriptor &Descriptor() = 0; + }; + } + } +} +#endif diff --git a/include/ignition/rendering/HeightmapDescriptor.hh b/include/ignition/rendering/HeightmapDescriptor.hh new file mode 100644 index 000000000..50f08760c --- /dev/null +++ b/include/ignition/rendering/HeightmapDescriptor.hh @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2020 Open Source Robotics Foundation + * + * 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. + * + */ +#ifndef IGNITION_RENDERING_HEIGHTMAPDESCRIPTOR_HH_ +#define IGNITION_RENDERING_HEIGHTMAPDESCRIPTOR_HH_ + +#include +#include +#include +#include + +#include "ignition/rendering/config.hh" +#include "ignition/rendering/Export.hh" + +namespace ignition +{ +namespace rendering +{ +inline namespace IGNITION_RENDERING_VERSION_NAMESPACE { + class HeightmapDescriptorPrivate; + class HeightmapTexturePrivate; + class HeightmapBlendPrivate; + + /// \brief Texture to be used on heightmaps. + class IGNITION_RENDERING_VISIBLE HeightmapTexture + { + /// \brief Constructor + public: HeightmapTexture(); + + /// \brief Copy constructor + /// \param[in] _texture HeightmapTexture to copy. + public: HeightmapTexture(const HeightmapTexture &_texture); + + /// \brief Move constructor + /// \param[in] _texture HeightmapTexture to move. + public: HeightmapTexture(HeightmapTexture &&_texture) noexcept; + + /// \brief Destructor + public: virtual ~HeightmapTexture(); + + /// \brief Move assignment operator. + /// \param[in] _texture Heightmap texture to move. + /// \return Reference to this. + public: HeightmapTexture &operator=(HeightmapTexture &&_texture); + + /// \brief Copy Assignment operator. + /// \param[in] _texture The heightmap texture to set values from. + /// \return *this + public: HeightmapTexture &operator=(const HeightmapTexture &_texture); + + /// \brief Get the heightmap texture's size. + /// \return The size of the heightmap texture in meters. + public: double Size() const; + + /// \brief Set the size of the texture in meters. + /// \param[in] _size The size of the texture in meters. + public: void SetSize(double _size); + + /// \brief Get the heightmap texture's diffuse map. + /// \return The diffuse map of the heightmap texture. + public: std::string Diffuse() const; + + /// \brief Set the filename of the diffuse map. + /// \param[in] _diffuse The diffuse map of the heightmap texture. + public: void SetDiffuse(const std::string &_diffuse); + + /// \brief Get the heightmap texture's normal map. + /// \return The normal map of the heightmap texture. + public: std::string Normal() const; + + /// \brief Set the filename of the normal map. + /// \param[in] _normal The normal map of the heightmap texture. + public: void SetNormal(const std::string &_normal); + + /// \brief Private data pointer. + IGN_COMMON_WARN_IGNORE__DLL_INTERFACE_MISSING + private: std::unique_ptr dataPtr; + IGN_COMMON_WARN_RESUME__DLL_INTERFACE_MISSING + }; + + /// \brief Blend information to be used between textures on heightmaps. + class IGNITION_RENDERING_VISIBLE HeightmapBlend + { + /// \brief Constructor + public: HeightmapBlend(); + + /// \brief Copy constructor + /// \param[in] _blend HeightmapBlend to copy. + public: HeightmapBlend(const HeightmapBlend &_blend); + + /// \brief Move constructor + /// \param[in] _blend HeightmapBlend to move. + public: HeightmapBlend(HeightmapBlend &&_blend) noexcept; + + /// \brief Destructor + public: virtual ~HeightmapBlend(); + + /// \brief Move assignment operator. + /// \param[in] _blend Heightmap blend to move. + /// \return Reference to this. + public: HeightmapBlend &operator=(HeightmapBlend &&_blend); + + /// \brief Copy Assignment operator. + /// \param[in] _blend The heightmap blend to set values from. + /// \return *this + public: HeightmapBlend &operator=(const HeightmapBlend &_blend); + + /// \brief Get the heightmap blend's minimum height. + /// \return The minimum height of the blend layer. + public: double MinHeight() const; + + /// \brief Set the minimum height of the blend in meters. + /// \param[in] _uri The minimum height of the blend in meters. + public: void SetMinHeight(double _minHeight); + + /// \brief Get the heightmap blend's fade distance. + /// \return The fade distance of the heightmap blend in meters. + public: double FadeDistance() const; + + /// \brief Set the distance over which the blend occurs. + /// \param[in] _uri The distance in meters. + public: void SetFadeDistance(double _fadeDistance); + + /// \brief Private data pointer. + IGN_COMMON_WARN_IGNORE__DLL_INTERFACE_MISSING + private: std::unique_ptr dataPtr; + IGN_COMMON_WARN_RESUME__DLL_INTERFACE_MISSING + }; + + /// \class HeightmapDescriptor HeightmapDescriptor.hh + /// ignition/rendering/HeightmapDescriptor.hh + /// \brief Describes how a Heightmap should be loaded + class IGNITION_RENDERING_VISIBLE HeightmapDescriptor + { + /// \brief Constructor + public: HeightmapDescriptor(); + + /// \brief Copy constructor + /// \param[in] _heightmap HeightmapDescriptor to copy. + public: HeightmapDescriptor(const HeightmapDescriptor &_desc); + + /// \brief Move constructor + /// \param[in] _desc HeightmapDescriptor to move. + public: HeightmapDescriptor(HeightmapDescriptor &&_desc) noexcept; + + /// \brief Destructor + public: virtual ~HeightmapDescriptor(); + + /// \brief Move assignment operator. + /// \param[in] _desc HeightmapDescriptor to move. + /// \return Reference to this. + public: HeightmapDescriptor &operator=(HeightmapDescriptor &&_desc); + + /// \brief Copy Assignment operator. + /// \param[in] _desc The heightmap to set values from. + /// \return *this + public: HeightmapDescriptor &operator=(const HeightmapDescriptor &_desc); + + /// \brief Get the heightmap's name used for caching. + /// This is different from its unique `Heightmap::Name()`. + /// \return Heightmap's given name. Defaults to `Heightmap::Name()`. + public: const std::string &Name() const; + + /// \brief Set the heightmap's name. + /// \param[in] _name Heightmap's name. + public: void SetName(const std::string &_name); + + /// \brief Get the heightfield data. + /// \return Heightmap data. + public: std::shared_ptr Data() const; + + /// \brief Set the heightfield data. + /// \param[in] _data New data. + public: void SetData(const std::shared_ptr &_data); + + /// \brief Get the heightmap's scaling factor. + /// \return The heightmap's size. + public: ignition::math::Vector3d Size() const; + + /// \brief Set the heightmap's scaling factor. Defaults to 1x1x1. + /// \return The heightmap's size factor. + public: void SetSize(const ignition::math::Vector3d &_size); + + /// \brief Get the heightmap's position offset. + /// \return The heightmap's position offset. + public: ignition::math::Vector3d Position() const; + + /// \brief Set the heightmap's position offset. + /// \return The heightmap's position offset. + public: void SetPosition(const ignition::math::Vector3d &_position); + + /// \brief Get whether the heightmap uses terrain paging. + /// \return True if the heightmap uses terrain paging. + public: bool UseTerrainPaging() const; + + /// \brief Set whether the heightmap uses terrain paging. Defaults to false. + /// \param[in] _use True to use. + public: void SetUseTerrainPaging(bool _use); + + /// \brief Get the heightmap's sampling per datum. + /// \return The heightmap's sampling. + public: unsigned int Sampling() const; + + /// \brief Set the heightmap's sampling. Defaults to 1. + /// \param[in] _sampling The heightmap's sampling per datum. + public: void SetSampling(unsigned int _sampling); + + /// \brief Get the number of heightmap textures. + /// \return Number of heightmap textures contained in this Heightmap object. + public: uint64_t TextureCount() const; + + /// \brief Get a heightmap texture based on an index. + /// \param[in] _index Index of the heightmap texture. The index should be in + /// the range [0..TextureCount()). + /// \return Pointer to the heightmap texture. Nullptr if the index does not + /// exist. + /// \sa uint64_t TextureCount() const + public: const HeightmapTexture *TextureByIndex(uint64_t _index) const; + + /// \brief Add a heightmap texture. + /// \param[in] _texture Texture to add. + public: void AddTexture(const HeightmapTexture &_texture); + + /// \brief Get the number of heightmap blends. + /// \return Number of heightmap blends contained in this Heightmap object. + public: uint64_t BlendCount() const; + + /// \brief Get a heightmap blend based on an index. + /// \param[in] _index Index of the heightmap blend. The index should be in + /// the range [0..BlendCount()). + /// \return Pointer to the heightmap blend. Nullptr if the index does not + /// exist. + /// \sa uint64_t BlendCount() const + public: const HeightmapBlend *BlendByIndex(uint64_t _index) const; + + /// \brief Add a heightmap blend. + /// \param[in] _blend Blend to add. + public: void AddBlend(const HeightmapBlend &_blend); + + /// \internal + /// \brief Private data + IGN_COMMON_WARN_IGNORE__DLL_INTERFACE_MISSING + private: std::unique_ptr dataPtr; + IGN_COMMON_WARN_RESUME__DLL_INTERFACE_MISSING + }; +} +} +} +#endif diff --git a/include/ignition/rendering/RenderTypes.hh b/include/ignition/rendering/RenderTypes.hh index dbe540ded..ae6135364 100644 --- a/include/ignition/rendering/RenderTypes.hh +++ b/include/ignition/rendering/RenderTypes.hh @@ -56,13 +56,15 @@ namespace ignition class GizmoVisual; class GpuRays; class Grid; - class JointVisual; + class Heightmap; class Image; class Light; class LightVisual; + class JointVisual; class LidarVisual; - class Material; + class Light; class Marker; + class Material; class Mesh; class Node; class Object; @@ -134,6 +136,10 @@ namespace ignition /// \brief Shared pointer to JointVisual typedef shared_ptr JointVisualPtr; + /// \def HeightmapPtr + /// \brief Shared pointer to Heightmap + typedef shared_ptr HeightmapPtr; + /// \def ImagePtr /// \brief Shared pointer to Image typedef shared_ptr ImagePtr; @@ -280,6 +286,10 @@ namespace ignition /// \brief Shared pointer to const JointVisual typedef shared_ptr ConstJointVisualPtr; + /// \def const HeightmapPtr + /// \brief Shared pointer to const Heightmap + typedef shared_ptr ConstHeightmapPtr; + /// \def const ImagePtr /// \brief Shared pointer to const Image typedef shared_ptr ConstImagePtr; diff --git a/include/ignition/rendering/Scene.hh b/include/ignition/rendering/Scene.hh index 2f3b47733..3350757ce 100644 --- a/include/ignition/rendering/Scene.hh +++ b/include/ignition/rendering/Scene.hh @@ -28,6 +28,7 @@ #include #include "ignition/rendering/config.hh" +#include "ignition/rendering/HeightmapDescriptor.hh" #include "ignition/rendering/MeshDescriptor.hh" #include "ignition/rendering/RenderTypes.hh" #include "ignition/rendering/Storage.hh" @@ -933,6 +934,13 @@ namespace ignition public: virtual LidarVisualPtr CreateLidarVisual( unsigned int _id, const std::string &_name) = 0; + /// \brief Create new heightmap geomerty. The rendering::Heightmap will be + /// created from the given HeightmapDescriptor. + /// \param[in] _desc Data about the heightmap + /// \return The created heightmap + public: virtual HeightmapPtr CreateHeightmap( + const HeightmapDescriptor &_desc) = 0; + /// \brief Create new text geometry. /// \return The created text public: virtual TextPtr CreateText() = 0; diff --git a/include/ignition/rendering/base/BaseHeightmap.hh b/include/ignition/rendering/base/BaseHeightmap.hh new file mode 100644 index 000000000..cafcf2c5a --- /dev/null +++ b/include/ignition/rendering/base/BaseHeightmap.hh @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020 Open Source Robotics Foundation + * + * 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. + * + */ +#ifndef IGNITION_RENDERING_BASE_BASEHEIGHTMAP_HH_ +#define IGNITION_RENDERING_BASE_BASEHEIGHTMAP_HH_ + +#include "ignition/rendering/Heightmap.hh" + +namespace ignition +{ + namespace rendering + { + inline namespace IGNITION_RENDERING_VERSION_NAMESPACE { + // + ////////////////////////////////////////////////// + template + class BaseHeightmap : + public virtual Heightmap, + public virtual T + { + /// \brief Constructor + /// \param[in] _desc Descriptor containing heightmap information. + protected: explicit BaseHeightmap(const HeightmapDescriptor &_desc); + + // Documentation inherited + public: virtual void PreRender() override; + + // Documentation inherited + public: virtual void Destroy() override; + + // Documentation inherited + public: virtual const HeightmapDescriptor &Descriptor() override; + + /// \brief Descriptor containing heightmap information + public: HeightmapDescriptor descriptor; + }; + + ////////////////////////////////////////////////// + template + BaseHeightmap::BaseHeightmap(const HeightmapDescriptor &_desc) + : descriptor{_desc} + { + } + + ////////////////////////////////////////////////// + template + void BaseHeightmap::PreRender() + { + T::PreRender(); + } + + ////////////////////////////////////////////////// + template + void BaseHeightmap::Destroy() + { + T::Destroy(); + } + + ////////////////////////////////////////////////// + template + const HeightmapDescriptor &BaseHeightmap::Descriptor() + { + return this->descriptor; + } + } + } +} +#endif diff --git a/include/ignition/rendering/base/BaseScene.hh b/include/ignition/rendering/base/BaseScene.hh index b48ee2e99..9674afebb 100644 --- a/include/ignition/rendering/base/BaseScene.hh +++ b/include/ignition/rendering/base/BaseScene.hh @@ -428,6 +428,10 @@ namespace ignition public: virtual LidarVisualPtr CreateLidarVisual(unsigned int _id, const std::string &_name) override; + // Documentation inherited. + public: virtual HeightmapPtr CreateHeightmap( + const HeightmapDescriptor &_desc) override; + // Documentation inherited. public: virtual WireBoxPtr CreateWireBox() override; @@ -582,6 +586,15 @@ namespace ignition protected: virtual LidarVisualPtr CreateLidarVisualImpl(unsigned int _id, const std::string &_name) = 0; + /// \brief Implementation for creating a heightmap geometry + /// \param[in] _id Unique object id. + /// \param[in] _name Unique object name. + /// \param[in] _desc Heightmap descriptor. + /// \return Pointer to a heightmap geometry. + protected: virtual HeightmapPtr CreateHeightmapImpl(unsigned int _id, + const std::string &_name, + const HeightmapDescriptor &_desc) = 0; + /// \brief Implementation for creating a wire box geometry /// \param[in] _id unique object id. /// \param[in] _name unique object name. diff --git a/ogre/include/ignition/rendering/ogre/OgreCamera.hh b/ogre/include/ignition/rendering/ogre/OgreCamera.hh index 57ad8b718..862ffe957 100644 --- a/ogre/include/ignition/rendering/ogre/OgreCamera.hh +++ b/ogre/include/ignition/rendering/ogre/OgreCamera.hh @@ -99,6 +99,9 @@ namespace ignition // Documentation inherited. public: virtual void SetVisibilityMask(uint32_t _mask) override; + /// \brief Get underlying Ogre camera + public: Ogre::Camera *Camera() const; + // Documentation inherited. // public: virtual uint32_t VisibilityMask() const override; diff --git a/ogre/include/ignition/rendering/ogre/OgreHeightmap.hh b/ogre/include/ignition/rendering/ogre/OgreHeightmap.hh new file mode 100644 index 000000000..fa1f999ab --- /dev/null +++ b/ogre/include/ignition/rendering/ogre/OgreHeightmap.hh @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2020 Open Source Robotics Foundation + * + * 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. + * +*/ + +#ifndef IGNITION_RENDERING_OGRE_OGREHEIGHTMAP_HH_ +#define IGNITION_RENDERING_OGRE_OGREHEIGHTMAP_HH_ + +#include +#include +#include + +#include "ignition/rendering/base/BaseHeightmap.hh" +#include "ignition/rendering/ogre/OgreGeometry.hh" +#include "ignition/rendering/ogre/OgreIncludes.hh" + +namespace ignition +{ + namespace rendering + { + inline namespace IGNITION_RENDERING_VERSION_NAMESPACE { + // + // Forward declaration + class OgreHeightmapPrivate; + + /// \brief Ogre implementation of a heightmap geometry. + class IGNITION_RENDERING_OGRE_VISIBLE OgreHeightmap +// Ignoring warning: "non dll-interface class +// 'ignition::rendering::v5::Heightmap' used as base for dll-interface class" +// because `Heightmap` and `BaseHeightmap` are header-only +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable:4275) +#endif + : public BaseHeightmap +#ifdef _MSC_VER + #pragma warning(pop) +#endif + { + /// \brief Constructor + protected: explicit OgreHeightmap(const HeightmapDescriptor &_desc); + + /// \brief Destructor + public: virtual ~OgreHeightmap(); + + // Documentation inherited. + public: virtual void Init() override; + + // Documentation inherited. + public: virtual void PreRender() override; + + /// \brief Returns NULL, heightmaps don't have movable objects. + /// \return Null pointer. + public: virtual Ogre::MovableObject *OgreObject() const override; + + /// \brief Returns NULL, heightmap materials don't inherit from + /// MaterialPtr. + /// \return Null pointer. + public: virtual MaterialPtr Material() const override; + + /// \brief Has no effect for heightmaps. The material is set through a + /// HeightmapDescriptor. + /// \param[in] _material Not used. + /// \param[in] _unique Not used. + public: virtual void SetMaterial(MaterialPtr _material, bool _unique) + override; + + /// \brief Configure the terrain default values. + /// \todo Move to private? + private: void ConfigureTerrainDefaults(); + + /// \brief Checks if the terrain was previously loaded by comparing its + /// hash against the one stored in the terrain directory + /// \param[in] _terrainDirPath Path to the directory containing the + /// terrain files and hash. + /// \return True if the terrain requires to regenerate the terrain files. + private: bool PrepareTerrain(const std::string &_terrainDirPath); + + /// \brief Update the hash of a terrain file. The hash will be written in + /// a file called gzterrain.SHA1 . This method will be used when the + /// paging is enabled and the terrain is loaded for the first time or if + /// the heightmap's image has been modified. + /// \param[in] _hash New hash value + /// \param[in] _terrainDir Directory where the terrain hash and the + /// terrain pages are stored. Ex: $TMP/gazebo-paging/heigthmap_bowl + private: void UpdateTerrainHash(const std::string &_hash, + const std::string &_terrainDir); + + /// \brief Split a terrain into subterrains + /// \param[in] _heightmap Source vector of floats with the heights. + /// \param[in] _n Number of subterrains. + /// \param[out] _v Destination vector with the subterrains. + private: void SplitHeights(const std::vector &_heightmap, + int _n, std::vector> &_v); + + /// \brief Define a section of the terrain. + /// \param[in] _x X coordinate of the terrain. + /// \param[in] _y Y coordinate of the terrain. + private: void DefineTerrain(int _x, int _y); + + /// \brief Create terrain material generator. There are two types: + /// custom material generator that support user material scripts, + /// and a default material generator that uses our own glsl shader + /// and supports PSSM shadows. + private: void CreateMaterial(); + + /// \brief Initialize all the blend material maps. + /// \param[in] _terrain The terrain to initialize the blend maps. + private: bool InitBlendMaps(Ogre::Terrain *_terrain); + + /// \brief Internal function used to setup shadows for the terrain. + /// \param[in] _enabled True to enable shadows. + private: void SetupShadows(bool _enabled); + + /// \brief Heightmap should only be created by scene. + private: friend class OgreScene; + + /// \brief Private data class + private: std::unique_ptr dataPtr; + }; + } + } +} +#endif diff --git a/ogre/include/ignition/rendering/ogre/OgreRenderTypes.hh b/ogre/include/ignition/rendering/ogre/OgreRenderTypes.hh index 0863bcffc..c2562136b 100644 --- a/ogre/include/ignition/rendering/ogre/OgreRenderTypes.hh +++ b/ogre/include/ignition/rendering/ogre/OgreRenderTypes.hh @@ -35,12 +35,14 @@ namespace ignition class OgreGizmoVisual; class OgreGpuRays; class OgreGrid; + class OgreHeightmap; class OgreJointVisual; class OgreLight; class OgreLightVisual; class OgreLidarVisual; - class OgreMaterial; + class OgreLight; class OgreMarker; + class OgreMaterial; class OgreMesh; class OgreMeshFactory; class OgreNode; @@ -77,42 +79,44 @@ namespace ignition typedef shared_ptr OgreDepthCameraPtr; typedef shared_ptr OgreDirectionalLightPtr; typedef shared_ptr OgreGeometryPtr; + typedef shared_ptr OgreGeometryStorePtr; typedef shared_ptr OgreGizmoVisualPtr; typedef shared_ptr OgreGpuRaysPtr; typedef shared_ptr OgreGridPtr; + typedef shared_ptr OgreHeightmapPtr; typedef shared_ptr OgreJointVisualPtr; typedef shared_ptr OgreLightPtr; typedef shared_ptr OgreLightVisualPtr; typedef shared_ptr OgreLidarVisualPtr; - typedef shared_ptr OgreMaterialPtr; + typedef shared_ptr OgreLightPtr; + typedef shared_ptr OgreLightStorePtr; typedef shared_ptr OgreMarkerPtr; + typedef shared_ptr OgreMaterialPtr; + typedef shared_ptr OgreMaterialMapPtr; typedef shared_ptr OgreMeshPtr; typedef shared_ptr OgreMeshFactoryPtr; typedef shared_ptr OgreNodePtr; + typedef shared_ptr OgreNodeStorePtr; typedef shared_ptr OgreObjectPtr; typedef shared_ptr OgreParticleEmitterPtr; typedef shared_ptr OgrePointLightPtr; typedef shared_ptr OgreRayQueryPtr; typedef shared_ptr OgreRenderEnginePtr; typedef shared_ptr OgreRenderTargetPtr; + typedef shared_ptr OgreRenderTargetMaterialPtr; typedef shared_ptr OgreRenderTexturePtr; typedef shared_ptr OgreRenderWindowPtr; typedef shared_ptr OgreScenePtr; + typedef shared_ptr OgreSceneStorePtr; typedef shared_ptr OgreSensorPtr; + typedef shared_ptr OgreSensorStorePtr; typedef shared_ptr OgreSpotLightPtr; typedef shared_ptr OgreSubMeshPtr; + typedef shared_ptr OgreSubMeshStorePtr; typedef shared_ptr OgreTextPtr; + typedef shared_ptr OgreThermalCameraPtr; typedef shared_ptr OgreVisualPtr; - typedef shared_ptr OgreSceneStorePtr; - typedef shared_ptr OgreNodeStorePtr; - typedef shared_ptr OgreLightStorePtr; - typedef shared_ptr OgreSensorStorePtr; typedef shared_ptr OgreVisualStorePtr; - typedef shared_ptr OgreGeometryStorePtr; - typedef shared_ptr OgreSubMeshStorePtr; - typedef shared_ptr OgreMaterialMapPtr; - typedef shared_ptr OgreRenderTargetMaterialPtr; - typedef shared_ptr OgreThermalCameraPtr; typedef shared_ptr OgreWireBoxPtr; } } diff --git a/ogre/include/ignition/rendering/ogre/OgreScene.hh b/ogre/include/ignition/rendering/ogre/OgreScene.hh index a2a27adb6..21a4b30b5 100644 --- a/ogre/include/ignition/rendering/ogre/OgreScene.hh +++ b/ogre/include/ignition/rendering/ogre/OgreScene.hh @@ -138,6 +138,10 @@ namespace ignition protected: virtual MeshPtr CreateMeshImpl(unsigned int _id, const std::string &_name, const MeshDescriptor &_desc); + // Documentation inherited + protected: virtual HeightmapPtr CreateHeightmapImpl(unsigned int _id, + const std::string &_name, const HeightmapDescriptor &_desc); + // Documentation inherited protected: virtual GridPtr CreateGridImpl(unsigned int _id, const std::string &_name); diff --git a/ogre/src/OgreCamera.cc b/ogre/src/OgreCamera.cc index 11dc82a4d..c625526b8 100644 --- a/ogre/src/OgreCamera.cc +++ b/ogre/src/OgreCamera.cc @@ -38,6 +38,12 @@ OgreCamera::~OgreCamera() this->Destroy(); } +////////////////////////////////////////////////// +Ogre::Camera *OgreCamera::Camera() const +{ + return this->ogreCamera; +} + ////////////////////////////////////////////////// void OgreCamera::Destroy() { diff --git a/ogre/src/OgreHeightmap.cc b/ogre/src/OgreHeightmap.cc new file mode 100644 index 000000000..d43f4da3f --- /dev/null +++ b/ogre/src/OgreHeightmap.cc @@ -0,0 +1,2834 @@ +/* + * Copyright (C) 2020 Open Source Robotics Foundation + * + * 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. + * +*/ + +#include + +#include +#include + +#include "ignition/rendering/ogre/OgreCamera.hh" +#include "ignition/rendering/ogre/OgreConversions.hh" +#include "ignition/rendering/ogre/OgreHeightmap.hh" +#include "ignition/rendering/ogre/OgreLight.hh" +#include "ignition/rendering/ogre/OgreMaterial.hh" +#include "ignition/rendering/ogre/OgreRenderEngine.hh" +#include "ignition/rendering/ogre/OgreRTShaderSystem.hh" +#include "ignition/rendering/ogre/OgreScene.hh" + +#if OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR >= 11 +// Since OGRE 1.11, the once public +// Ogre::TerrainMaterialGeneratorA::SM2Profile::ShaderHelper +// class and its descendant are now private classes of OGRE, see +// * https://github.com/OGRECave/ogre/blob/master/Docs/1.11-Notes.md#other +// * https://github.com/OGRECave/ogre/pull/722 +// +// As these classes are heavily used in the Heightmap class implementation +// (by accessing a protected Ogre class) we need to disable the definition +// of the custom terrain generator, and just use the Ogre default one. +using Ogre::TechniqueType; +#endif + +#if OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR < 11 +/// \internal +/// \brief Custom terrain material generator for GLSL terrains. +/// A custom material generator that lets Ignition use GLSL shaders +/// (as opposed to the default Cg shaders provided by Ogre) for rendering +/// terrain. +class IgnTerrainMatGen : public Ogre::TerrainMaterialGeneratorA +{ + /// \brief Constructor + public: IgnTerrainMatGen(); + + /// \brief Destructor + public: virtual ~IgnTerrainMatGen(); + + /// \brief Shader model 2 profile target. + public: class SM2Profile : + public Ogre::TerrainMaterialGeneratorA::SM2Profile + { + /// \brief Constructor + public: SM2Profile(Ogre::TerrainMaterialGenerator *_parent, + const Ogre::String &_name, const Ogre::String &_desc); + + /// \brief Destructor + public: virtual ~SM2Profile(); + + public: Ogre::MaterialPtr generate(const Ogre::Terrain *_terrain); + + public: Ogre::MaterialPtr generateForCompositeMap( + const Ogre::Terrain *_terrain); + + public: void UpdateParams(const Ogre::MaterialPtr &_mat, + const Ogre::Terrain *_terrain); + + public: void UpdateParamsForCompositeMap(const Ogre::MaterialPtr &_mat, + const Ogre::Terrain *_terrain); + + protected: virtual void addTechnique(const Ogre::MaterialPtr &_mat, + const Ogre::Terrain *_terrain, TechniqueType _tt); + + /// \brief Utility class to help with generating shaders for GLSL. + /// The class contains a collection of functions that are used to + /// dynamically generate a complete vertex or fragment shader program + /// in a string format. + protected: class ShaderHelperGLSL : + public Ogre::TerrainMaterialGeneratorA::SM2Profile::ShaderHelperGLSL + { + public: ShaderHelperGLSL() + { + auto capabilities = + Ogre::Root::getSingleton().getRenderSystem()->getCapabilities(); + Ogre::DriverVersion glVersion; + glVersion.build = 0; + glVersion.major = 3; + glVersion.minor = 0; + glVersion.release = 0; + if (capabilities->isDriverOlderThanVersion(glVersion)) + { + this->glslVersion = "120"; + this->vpInStr = "attribute"; + this->vpOutStr = "varying"; + this->fpInStr = "varying"; + this->textureStr = "texture2D"; + } + }; + + public: virtual Ogre::HighLevelGpuProgramPtr generateVertexProgram( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt); + + public: virtual Ogre::HighLevelGpuProgramPtr generateFragmentProgram( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt); + + public: virtual void updateParams(const SM2Profile *_prof, + const Ogre::MaterialPtr &_mat, + const Ogre::Terrain *_terrain, bool _compositeMap); + + protected: virtual void generateVpHeader(const SM2Profile *_prof, + const Ogre::Terrain *_terrain, TechniqueType _tt, + Ogre::StringStream &_outStream); + + protected: virtual void generateVpFooter(const SM2Profile *_prof, + const Ogre::Terrain *_terrain, TechniqueType _tt, + Ogre::StringStream &_outStream); + + protected: virtual void generateVertexProgramSource( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt, + Ogre::StringStream &_outStream); + + protected: virtual void defaultVpParams(const SM2Profile *_prof, + const Ogre::Terrain *_terrain, TechniqueType _tt, + const Ogre::HighLevelGpuProgramPtr &_prog); + + protected: virtual unsigned int generateVpDynamicShadowsParams( + unsigned int _texCoordStart, const SM2Profile *_prof, + const Ogre::Terrain *_terrain, TechniqueType _tt, + Ogre::StringStream &_outStream); + + protected: virtual void generateVpDynamicShadows( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt, + Ogre::StringStream &_outStream); + + protected: virtual void generateFpHeader(const SM2Profile *_prof, + const Ogre::Terrain *_terrain, + TechniqueType tt, + Ogre::StringStream &_outStream); + + protected: virtual void generateFpLayer(const SM2Profile *_prof, + const Ogre::Terrain *_terrain, TechniqueType tt, + Ogre::uint _layer, + Ogre::StringStream &_outStream); + + protected: virtual void generateFpFooter(const SM2Profile *_prof, + const Ogre::Terrain *_terrain, + TechniqueType tt, + Ogre::StringStream &_outStream); + + protected: virtual void generateFpDynamicShadowsParams( + Ogre::uint *_texCoord, Ogre::uint *_sampler, + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt, + Ogre::StringStream &_outStream); + + protected: virtual void generateFpDynamicShadowsHelpers( + const SM2Profile *_prof, + const Ogre::Terrain *_terrain, + TechniqueType tt, + Ogre::StringStream &_outStream); + + protected: void generateFpDynamicShadows(const SM2Profile *_prof, + const Ogre::Terrain *_terrain, TechniqueType _tt, + Ogre::StringStream &_outStream); + + protected: virtual void generateFragmentProgramSource( + const SM2Profile *_prof, + const Ogre::Terrain *_terrain, + TechniqueType _tt, + Ogre::StringStream &_outStream); + + protected: virtual void updateVpParams(const SM2Profile *_prof, + const Ogre::Terrain *_terrain, TechniqueType _tt, + const Ogre::GpuProgramParametersSharedPtr &_params); + + private: Ogre::String GetChannel(Ogre::uint _idx); + + /// \brief Capabilities + private: std::string glslVersion = "130"; + private: std::string vpInStr = "in"; + private: std::string vpOutStr = "out"; + private: std::string fpInStr = "in"; + private: std::string fpOutStr = "out"; + private: std::string textureStr = "texture"; + }; + + // Needed to allow access from ShaderHelperGLSL to protected members + // of SM2Profile. + friend ShaderHelperGLSL; + }; +}; + +// #if OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR < 11 +#endif + +/// \internal +/// \brief Custom terrain material generator. +/// A custom material generator that lets user specify their own material +/// script for rendering the heightmap. +class TerrainMaterial : public Ogre::TerrainMaterialGenerator +{ + /// \brief Constructor + /// \param[in] _materialName Name of material + public: explicit TerrainMaterial(const std::string &_materialName); + + /// \brief Set terrain material + /// \param[in] _materialName Name of material + public: void setMaterialByName(const std::string &_materialname); + + /// \brief Set the grid size of the terrain, i.e. Number of terrain slots. + /// This will be used to determined how the texture will be mapped to the + /// terrain + /// \param[in] _size Grid size of the terrain + public: void setGridSize(const unsigned int _size); + + /// \brief Subclassed to provide profile-specific material generation + class Profile : public Ogre::TerrainMaterialGenerator::Profile + { + /// \brief Constructor + /// \param[in] _parent Ogre terrain material generator object + /// \param[in] _name Name of the profile + /// \param[on] _desc Profile description + public: Profile(Ogre::TerrainMaterialGenerator *_parent, + const Ogre::String &_name, const Ogre::String &_desc); + + /// \brief Destructor. + public: ~Profile(); + + // Documentation Inherited + public: bool isVertexCompressionSupported() const override; + + // Documentation Inherited + public: Ogre::MaterialPtr generate(const Ogre::Terrain *_terrain) override; + + // Documentation Inherited + public: Ogre::MaterialPtr generateForCompositeMap( + const Ogre::Terrain *_terrain) override; + + // Documentation Inherited + public: void setLightmapEnabled(bool _enabled) override; + + // Documentation Inherited + public: Ogre::uint8 getMaxLayers(const Ogre::Terrain *_terrain) const + override; + + // Documentation Inherited + public: void updateParams(const Ogre::MaterialPtr& mat, + const Ogre::Terrain *_terrain) override; + + // Documentation Inherited + public: void updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, + const Ogre::Terrain *_terrain) override; + + // Documentation Inherited + public: void requestOptions(Ogre::Terrain *_terrain) override; + }; + + /// \brief Name of material + protected: std::string materialName; + + /// \brief Size of grid + protected: unsigned int gridSize = 1u; +}; + +/// \internal +/// \brief Pretends to provide procedural page content to avoid page loading +class DummyPageProvider : public Ogre::PageProvider +{ + /// \brief Give a provider the opportunity to prepare page content + /// procedurally. The parameters are not used. + public: bool prepareProceduralPage(Ogre::Page *, Ogre::PagedWorldSection *) + { + return true; + } + + /// \brief Give a provider the opportunity to load page content + /// procedurally. The parameters are not used. + public: bool loadProceduralPage(Ogre::Page *, Ogre::PagedWorldSection *) + { + return true; + } + + /// \brief Give a provider the opportunity to unload page content + /// procedurally. The parameters are not used. + public: bool unloadProceduralPage(Ogre::Page *, Ogre::PagedWorldSection *) + { + return true; + } + + /// \brief Give a provider the opportunity to unprepare page content + /// procedurally. The parameters are not used. + public: bool unprepareProceduralPage(Ogre::Page *, Ogre::PagedWorldSection *) + { + return true; + } +}; + +////////////////////////////////////////////////// +class ignition::rendering::OgreHeightmapPrivate +{ + /// \brief Global options - in some Ogre versions, this is enforced as a + /// singleton. + public: static Ogre::TerrainGlobalOptions *terrainGlobals; + + /// \brief The raw height values. + public: std::vector heights; + + /// \brief Size of the heightmap data. + public: unsigned int dataSize{0u}; + + /// \brief True if the terrain need to be split into subterrains + public: bool splitTerrain{false}; + + /// \brief Number of pieces in which a terrain is subdivided. Used + /// for paging and also when heighmap is too large for LOD to work. + public: unsigned int numTerrainSubdivisions{16u}; + + /// \brief Max pixel error allowed for rendering the heightmap. This + /// affects the transitions between LOD levels. + public: double maxPixelError{0.0}; + + /// \brief Group of terrains. + public: Ogre::TerrainGroup *terrainGroup{nullptr}; + + /// \brief Skirt length on LOD tiles + public: double skirtLength{1.0}; + + /// \brief Terrain casts shadows + /// \todo(chapulina) Use Material? Expose through descriptor? + public: bool castShadows{false}; + + /// \brief True if the terrain's hash does not match the image's hash + public: bool terrainHashChanged{true}; + + /// \brief Hash file name that should be present for every terrain file + /// loaded using paging. + public: const std::string kHashFilename{"ignterrain.SHA1"}; + + /// \brief Collection of terrains. Every terrain might be paged. + public: std::vector> subTerrains; + + /// \brief When the terrain paging is enabled, the terrain information + /// for every piece of terrain is stored in disk. This is the path of + /// the top level directory where these files are located. + public: std::string pagingDir; + + /// \brief Name of the top level directory where all the paging info is + /// stored + public: const std::string pagingDirname{"ogre-paging"}; + + /// \brief Central registration point for extension classes, + /// such as the PageStrategy, PageContentFactory. + public: Ogre::PageManager *pageManager{nullptr}; + + /// \brief A page provider is needed to use the paging system. + public: DummyPageProvider dummyPageProvider; + + /// \brief Collection of world content + public: Ogre::PagedWorld *world{nullptr}; + + /// \brief Type of paging applied + public: Ogre::TerrainPaging *terrainPaging{nullptr}; + + /// \brief The terrain pages are loaded if the distance from the camera is + /// within the loadRadius. See Ogre::TerrainPaging::createWorldSection(). + /// LoadRadiusFactor is a multiplier applied to the terrain size to create + /// a load radius that depends on the terrain size. + public: const double loadRadiusFactor{1.0}; + + /// \brief The terrain pages are held in memory but not loaded if they + /// are not ready when the camera is within holdRadius distance. See + /// Ogre::TerrainPaging::createWorldSection(). HoldRadiusFactor is a + /// multiplier applied to the terrain size to create a hold radius that + /// depends on the terrain size. + public: const double holdRadiusFactor{1.15}; + + /// \brief True if the terrain was loaded from the cache. + public: bool loadedFromCache{false}; + + /// \brief True if the terrain was saved to the cache. + public: bool savedToCache{false}; + + /// \brief Used to iterate over all the terrains + public: int terrainIdx{0}; + + /// \brief Name of custom material to use for the terrain. If empty, + /// default material with glsl shader will be used. + /// \todo(chapulina) Will we ever use this? + public: std::string materialName; + +#if OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR < 11 + /// \brief Pointer to the terrain material generator. + public: IgnTerrainMatGen *ignMatGen{nullptr}; +#endif +}; + +Ogre::TerrainGlobalOptions + *ignition::rendering::OgreHeightmapPrivate::terrainGlobals = nullptr; + +using namespace ignition; +using namespace rendering; + +////////////////////////////////////////////////// +OgreHeightmap::OgreHeightmap(const HeightmapDescriptor &_desc) + : BaseHeightmap(_desc), dataPtr(std::make_unique()) +{ + std::string home; + ignition::common::env(IGN_HOMEDIR, home); + + this->dataPtr->pagingDir = + common::joinPaths(home, ".ignition", "rendering", + this->dataPtr->pagingDirname); +} + +////////////////////////////////////////////////// +OgreHeightmap::~OgreHeightmap() +{ +} + +////////////////////////////////////////////////// +void OgreHeightmap::Init() +{ + OgreObject::Init(); + + if (this->descriptor.Data() == nullptr) + { + ignerr << "Failed to initialize: null heightmap data." << std::endl; + return; + } + + if (this->descriptor.Name().empty()) + this->descriptor.SetName(this->Name()); + + // Add paths + for (auto i = 0u; i < this->descriptor.TextureCount(); ++i) + { + auto texture = this->descriptor.TextureByIndex(i); + OgreRenderEngine::Instance()->AddResourcePath(texture->Diffuse()); + OgreRenderEngine::Instance()->AddResourcePath(texture->Normal()); + } + + // The terraingGroup is composed by a number of terrains (1 by default) + int nTerrains = 1; + + // There is an issue with OGRE terrain LOD if heights are not relative to 0. + // So we move the heightmap so that its min elevation = 0 before feeding to + // ogre. It is later translated back by the setOrigin call. + double minElevation = 0.0; + + // TODO(chapulina) add a virtual HeightmapData::MinElevation function to + // avoid the ifdef check. i.e. heightmapSizeZ = MaxElevation - MinElevation + double heightmapSizeZ = this->descriptor.Data()->MaxElevation(); + + // \todo These parameters shouldn't be hardcoded, and instead parametrized so + // that they can be made consistent across different libraries (like + // ign-physics) + bool flipY = false; + // sampling size along image width and height + unsigned int vertSize = (this->descriptor.Data()->Width() * + this->descriptor.Sampling()) - this->descriptor.Sampling() + 1; + math::Vector3d scale; + scale.X(this->descriptor.Size().X() / vertSize); + scale.Y(this->descriptor.Size().Y() / vertSize); + + if (math::equal(heightmapSizeZ, 0.0)) + scale.Z(1.0); + else + scale.Z(fabs(this->descriptor.Size().Z()) / heightmapSizeZ); + + // Construct the heightmap lookup table + std::vector lookup; + this->descriptor.Data()->FillHeightMap(this->descriptor.Sampling(), + vertSize, this->descriptor.Size(), scale, flipY, lookup); + + for (unsigned int y = 0; y < vertSize; ++y) + { + for (unsigned int x = 0; x < vertSize; ++x) + { + int index = (vertSize - y - 1) * vertSize + x; + this->dataPtr->heights.push_back(lookup[index] - minElevation); + } + } + + this->dataPtr->dataSize = vertSize; + + if (this->dataPtr->heights.empty()) + { + ignerr << "Failed to load terrain. Heightmap data is empty" << std::endl; + return; + } + + if (!math::isPowerOfTwo(this->dataPtr->dataSize - 1)) + { + ignerr << "Heightmap final sampling must satisfy 2^n+1." + << std::endl << "size = (width * sampling) = sampling + 1" + << std::endl << "[" << this->dataPtr->dataSize << "] = ([" + << this->descriptor.Data()->Width() << "] * [" + << this->descriptor.Sampling() << "]) = [" + << this->descriptor.Sampling() << "] + 1: " + << std::endl; + return; + } + + // Get the full path of the image heightmap + // \todo(anyone) Name is generated at runtime and depends on the number of + // objects, so it's impossible to make sure it's the same across runs + auto terrainDirPath = common::joinPaths(this->dataPtr->pagingDir, + this->descriptor.Name()); + + // Add the top level terrain paging directory to the OGRE + // ResourceGroupManager + if (!Ogre::ResourceGroupManager::getSingleton().resourceLocationExists( + this->dataPtr->pagingDir, "General")) + { + Ogre::ResourceGroupManager::getSingleton().addResourceLocation( + this->dataPtr->pagingDir, "FileSystem", "General", true); + Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup( + "General"); + } + + // If the paging is enabled we modify the number of subterrains + std::string prefix; + if (this->descriptor.UseTerrainPaging()) + { + this->dataPtr->splitTerrain = true; + nTerrains = this->dataPtr->numTerrainSubdivisions; + prefix = common::joinPaths(terrainDirPath, "ignition_terrain_cache"); + } + else + { + // Note: ran into problems with LOD height glitches if heightmap size is + // larger than 4096 so split it into chunks + // Note: dataSize should be 2^n + 1 + if (this->dataPtr->maxPixelError > 0 && this->dataPtr->dataSize > 4096u) + { + this->dataPtr->splitTerrain = true; + if (this->dataPtr->dataSize == 4097u) + this->dataPtr->numTerrainSubdivisions = 4u; + else + this->dataPtr->numTerrainSubdivisions = 16u; + nTerrains = this->dataPtr->numTerrainSubdivisions; + + ignmsg << "Large heightmap used with LOD. It will be subdivided into " << + this->dataPtr->numTerrainSubdivisions << " terrains." << std::endl; + } + prefix = common::joinPaths(terrainDirPath, "ignition_terrain"); + } + + double sqrtN = sqrt(nTerrains); + + // Create terrain group, which holds all the individual terrain instances. + // Param 1: Pointer to the scene manager + // Param 2: Alignment plane + // Param 3: Number of vertices along one edge of the terrain (2^n+1). + // Terrains must be square, with each side a power of 2 in size + // Param 4: World size of each terrain instance, in meters. + + auto ogreScene = std::dynamic_pointer_cast(this->Scene()); + + this->dataPtr->terrainGroup = new Ogre::TerrainGroup( + ogreScene->OgreSceneManager(), Ogre::Terrain::ALIGN_X_Y, + 1 + ((this->dataPtr->dataSize - 1) / static_cast(sqrtN)), + this->descriptor.Size().X() / (sqrtN)); + + this->dataPtr->terrainGroup->setFilenameConvention( + Ogre::String(prefix), Ogre::String("dat")); + + math::Vector3d pos( + this->descriptor.Position().X() - 0.5 * this->descriptor.Size().X() + + 0.5 * this->descriptor.Size().X() / sqrtN, + this->descriptor.Position().Y() - 0.5 * this->descriptor.Size().X() + + 0.5 * this->descriptor.Size().X() / sqrtN, + this->descriptor.Position().Z() + minElevation); + + this->dataPtr->terrainGroup->setOrigin(OgreConversions::Convert(pos)); + + this->ConfigureTerrainDefaults(); + + // Configure import settings + auto &defaultimp = this->dataPtr->terrainGroup->getDefaultImportSettings(); + + defaultimp.terrainSize = this->dataPtr->dataSize; + defaultimp.worldSize = this->descriptor.Size().X(); + + defaultimp.inputScale = 1.0; + + defaultimp.minBatchSize = 17; + defaultimp.maxBatchSize = 65; + + // textures. The default material generator takes two materials per layer. + // 1. diffuse_specular - diffuse texture with a specular map in the + // alpha channel + // 2. normal_height - normal map with a height map in the alpha channel + { + // number of texture layers + defaultimp.layerList.resize(this->descriptor.TextureCount()); + + // The worldSize decides how big each splat of textures will be. + // A smaller value will increase the resolution + for (unsigned int i = 0; i < this->descriptor.TextureCount(); ++i) + { + auto texture = this->descriptor.TextureByIndex(i); + + defaultimp.layerList[i].worldSize = texture->Size(); + defaultimp.layerList[i].textureNames.push_back(texture->Diffuse()); + defaultimp.layerList[i].textureNames.push_back(texture->Normal()); + } + } + + this->dataPtr->terrainHashChanged = this->PrepareTerrain(terrainDirPath); + + if (this->descriptor.UseTerrainPaging()) + { + if (this->dataPtr->terrainHashChanged) + { + // Split the terrain. Every subterrain will be saved on disk and paged + this->SplitHeights(this->dataPtr->heights, nTerrains, + this->dataPtr->subTerrains); + } + + this->dataPtr->pageManager = OGRE_NEW Ogre::PageManager(); + this->dataPtr->pageManager->setPageProvider( + &this->dataPtr->dummyPageProvider); + + // Add cameras + for (unsigned int i = 0; i < ogreScene->NodeCount(); ++i) + { + auto cam = std::dynamic_pointer_cast( + ogreScene->NodeByIndex(i)); + if (nullptr != cam) + { + this->dataPtr->pageManager->addCamera(cam->Camera()); + } + } + + this->dataPtr->terrainPaging = + OGRE_NEW Ogre::TerrainPaging(this->dataPtr->pageManager); + this->dataPtr->world = this->dataPtr->pageManager->createWorld(); + this->dataPtr->terrainPaging->createWorldSection( + this->dataPtr->world, this->dataPtr->terrainGroup, + this->dataPtr->loadRadiusFactor * this->descriptor.Size().X(), + this->dataPtr->holdRadiusFactor * this->descriptor.Size().X(), + 0, 0, static_cast(sqrtN) - 1, + static_cast(sqrtN) - 1); + } + + ignmsg << "Loading heightmap: " << this->descriptor.Name() << std::endl; + auto time = std::chrono::steady_clock::now(); + + for (int y = 0; y <= sqrtN - 1; ++y) + for (int x = 0; x <= sqrtN - 1; ++x) + this->DefineTerrain(x, y); + + // use ignition shaders + this->CreateMaterial(); + + // Sync load since we want everything in place when we start + this->dataPtr->terrainGroup->loadAllTerrains(true); + + ignmsg << "Heightmap loaded. Process took " + << std::chrono::duration_cast( + std::chrono::steady_clock::now() - time).count() + << " ms." << std::endl; + + // Calculate blend maps + if (!this->dataPtr->loadedFromCache) + { + auto ti = this->dataPtr->terrainGroup->getTerrainIterator(); + while (ti.hasMoreElements()) + { + this->InitBlendMaps(ti.getNext()->instance); + } + } + + this->dataPtr->terrainGroup->freeTemporaryResources(); +} + +////////////////////////////////////////////////// +void OgreHeightmap::PreRender() +{ + if (nullptr == this->dataPtr->terrainGroup) + { + return; + } + + // Make sure the heightmap finishes loading by processing responses until all + // derived data and terrains are loaded + if (this->dataPtr->terrainGroup->isDerivedDataUpdateInProgress()) + { + Ogre::Root::getSingleton().getWorkQueue()->processResponses(); + return; + } + + auto ti = this->dataPtr->terrainGroup->getTerrainIterator(); + while (ti.hasMoreElements()) + { + auto *t = ti.getNext()->instance; + if (!t->isLoaded()) + { + Ogre::Root::getSingleton().getWorkQueue()->processResponses(); + return; + } + } + + // save the terrain once its loaded + if (this->dataPtr->loadedFromCache || this->dataPtr->savedToCache) + { + return; + } + + // saving an ogre terrain data file can take quite some time for large + // terrains. + ignmsg << "Saving heightmap cache data to " + << common::joinPaths(this->dataPtr->pagingDir, this->descriptor.Name()) + << std::endl; + auto time = std::chrono::steady_clock::now(); + + bool saved{false}; + try + { + this->dataPtr->terrainGroup->saveAllTerrains(true); + saved = true; + } + catch(Ogre::Exception &_e) + { + ignerr << "Failed to save heightmap: " << _e.what() << std::endl; + } + + if (saved) + { + ignmsg << "Heightmap cache data saved. Process took " + << std::chrono::duration_cast( + std::chrono::steady_clock::now() - time).count() + << " ms." << std::endl; + } + + this->dataPtr->savedToCache = true; +} + +/////////////////////////////////////////////////// +void OgreHeightmap::ConfigureTerrainDefaults() +{ + if (this->dataPtr->terrainGlobals != nullptr) + return; + + // Configure global + this->dataPtr->terrainGlobals = new Ogre::TerrainGlobalOptions(); + + // Vertex compression breaks anything, e.g. Gpu laser, that tries to build + // a depth map. + this->dataPtr->terrainGlobals->setUseVertexCompressionWhenAvailable(false); + + // MaxPixelError: Decides how precise our terrain is going to be. + // A lower number will mean a more accurate terrain, at the cost of + // performance (because of more vertices) + this->dataPtr->terrainGlobals->setMaxPixelError(this->dataPtr->maxPixelError); + + // CompositeMapDistance: decides how far the Ogre terrain will render + // the lightmapped terrain. + this->dataPtr->terrainGlobals->setCompositeMapDistance(2000); + + // Vertex compression breaks anything, e.g. Gpu laser, that tries to build + // a depth map. + this->dataPtr->terrainGlobals->setUseVertexCompressionWhenAvailable(false); + + this->dataPtr->terrainGlobals->setSkirtSize(this->dataPtr->skirtLength); + + this->dataPtr->terrainGlobals->setCastsDynamicShadows( + this->dataPtr->castShadows); + + auto ogreScene = std::dynamic_pointer_cast(this->Scene()); + this->dataPtr->terrainGlobals->setCompositeMapAmbient( + ogreScene->OgreSceneManager()->getAmbientLight()); + + // Get the first directional light + OgreDirectionalLightPtr directionalLight; + for (unsigned int i = 0; i < this->Scene()->LightCount(); ++i) + { + auto light = std::dynamic_pointer_cast( + this->Scene()->LightByIndex(i)); + if (nullptr != light) + { + directionalLight = light; + break; + } + } + + // Important to set these so that the terrain knows what to use for + // derived (non-realtime) data + if (directionalLight) + { + this->dataPtr->terrainGlobals->setLightMapDirection( + OgreConversions::Convert(directionalLight->Direction())); + + auto const &lightDiffuse = directionalLight->DiffuseColor(); + this->dataPtr->terrainGlobals->setCompositeMapDiffuse( + OgreConversions::Convert(lightDiffuse)); + } + else + { + this->dataPtr->terrainGlobals->setLightMapDirection( + Ogre::Vector3(0, 0, -1)); + this->dataPtr->terrainGlobals->setCompositeMapDiffuse( + Ogre::ColourValue(0.6f, 0.6f, 0.6f, 1.0f)); + } +} + +////////////////////////////////////////////////// +bool OgreHeightmap::PrepareTerrain( + const std::string &_terrainDirPath) +{ + // Compute the original heightmap's image. + auto heightmapHash = common::sha1>(this->dataPtr->heights); + + // Check if the terrain hash exists + auto terrainHashFullPath = common::joinPaths(_terrainDirPath, + this->dataPtr->kHashFilename); + + bool updateHash = true; + if (common::exists(terrainHashFullPath)) + { + try + { + // Read the terrain hash + std::ifstream in(terrainHashFullPath.c_str()); + std::stringstream buffer; + buffer << in.rdbuf(); + std::string terrainHash(buffer.str()); + updateHash = terrainHash != heightmapHash; + } + catch(std::ifstream::failure &_e) + { + ignerr << "Terrain paging error: Unable to read terrain hash [" + << _e.what() << "]" << std::endl; + } + } + + // Update the terrain hash and split the terrain into small pieces + if (updateHash) + { + this->UpdateTerrainHash(heightmapHash, _terrainDirPath); + } + + return updateHash; +} + +////////////////////////////////////////////////// +void OgreHeightmap::UpdateTerrainHash(const std::string &_hash, + const std::string &_terrainDir) +{ + // Create the subdirectories if they do not exist + common::createDirectories(_terrainDir); + + auto terrainHashFullPath = common::joinPaths(_terrainDir, + this->dataPtr->kHashFilename); + + // Update the terrain hash + std::ofstream terrainHashFile; + terrainHashFile.open(terrainHashFullPath.c_str()); + + // Throw an error if we couldn't open the file for writing. + if (terrainHashFile.is_open()) + { + terrainHashFile << _hash; + terrainHashFile.close(); + } + else + { + ignerr << "Unable to open file for creating a terrain hash: [" + + terrainHashFullPath + "]" << std::endl; + } +} + +////////////////////////////////////////////////// +void OgreHeightmap::SplitHeights(const std::vector &_heightmap, + int _n, std::vector> &_v) +{ + // We support splitting the terrain in 4 or 16 pieces + if (_n != 4 && _n != 16) + { + ignerr << "Invalid number of terrain divisions [" << _n + << "]. It should be 4 or 16." << std::endl; + return; + } + + int count = 0; + int width = static_cast(sqrt(_heightmap.size())); + int newWidth = static_cast(1 + (width - 1) / sqrt(_n)); + + // Memory allocation + _v.resize(_n); + + for (int tileR = 0; tileR < sqrt(_n); ++tileR) + { + int tileIndex = static_cast(tileR * sqrt(_n)); + for (int row = 0; row < newWidth - 1; ++row) + { + for (int tileC = 0; tileC < sqrt(_n); ++tileC) + { + for (int col = 0; col < newWidth - 1; ++col) + { + _v[tileIndex].push_back(_heightmap[count]); + ++count; + } + // Copy last value into the last column + _v[tileIndex].push_back(_v[tileIndex].back()); + + tileIndex = static_cast(tileR * sqrt(_n) + + (tileIndex + 1) % static_cast(sqrt(_n))); + } + ++count; + } + // Copy the last row + for (int i = 0; i < sqrt(_n); ++i) + { + tileIndex = static_cast(tileR * sqrt(_n) + i); + std::vector lastRow(_v[tileIndex].end() - newWidth, + _v[tileIndex].end()); + _v[tileIndex].insert(_v[tileIndex].end(), + lastRow.begin(), lastRow.end()); + } + } +} + +///////////////////////////////////////////////// +void OgreHeightmap::DefineTerrain(int _x, int _y) +{ + Ogre::String filename = this->dataPtr->terrainGroup->generateFilename(_x, _y); + + bool resourceExists = + Ogre::ResourceGroupManager::getSingleton().resourceExists( + this->dataPtr->terrainGroup->getResourceGroup(), filename); + + if (resourceExists && !this->dataPtr->terrainHashChanged) + { + ignmsg << "Loading heightmap cache data: " << filename << std::endl; + + this->dataPtr->terrainGroup->defineTerrain(_x, _y); + this->dataPtr->loadedFromCache = true; + } + else + { + if (this->dataPtr->splitTerrain) + { + // generate the subterrains if needed + if (this->dataPtr->subTerrains.empty()) + { + this->SplitHeights(this->dataPtr->heights, + this->dataPtr->numTerrainSubdivisions, + this->dataPtr->subTerrains); + } + + this->dataPtr->terrainGroup->defineTerrain(_x, _y, + &this->dataPtr->subTerrains[this->dataPtr->terrainIdx][0]); + ++this->dataPtr->terrainIdx; + } + else + { + this->dataPtr->terrainGroup->defineTerrain(_x, _y, + &this->dataPtr->heights[0]); + } + } +} + +///////////////////////////////////////////////// +void OgreHeightmap::CreateMaterial() +{ + if (!this->dataPtr->materialName.empty()) + { + // init custom material generator + Ogre::TerrainMaterialGeneratorPtr terrainMaterialGenerator; + TerrainMaterial *terrainMaterial = OGRE_NEW TerrainMaterial( + this->dataPtr->materialName); + if (this->dataPtr->splitTerrain) + terrainMaterial->setGridSize(this->dataPtr->numTerrainSubdivisions); + terrainMaterialGenerator.bind(terrainMaterial); + this->dataPtr->terrainGlobals->setDefaultMaterialGenerator( + terrainMaterialGenerator); + } + else + { +#if OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR < 11 + // use default material + // RTSS PSSM shadows compatible terrain material + if (!this->dataPtr->ignMatGen) + this->dataPtr->ignMatGen = new IgnTerrainMatGen(); + + auto ptr = Ogre::TerrainMaterialGeneratorPtr(); + ptr.bind(this->dataPtr->ignMatGen); + + this->dataPtr->terrainGlobals->setDefaultMaterialGenerator(ptr); +#else + // init custom material generator + Ogre::TerrainMaterialGeneratorPtr terrainMaterialGenerator; + auto terrainMaterial = OGRE_NEW TerrainMaterial("Default/White"); + if (this->dataPtr->splitTerrain) + terrainMaterial->setGridSize(this->dataPtr->numTerrainSubdivisions); + terrainMaterialGenerator.bind(terrainMaterial); + this->dataPtr->terrainGlobals->setDefaultMaterialGenerator( + terrainMaterialGenerator); +#endif + + this->SetupShadows(true); + } +} + +///////////////////////////////////////////////// +void OgreHeightmap::SetupShadows(bool _enableShadows) +{ + auto matGen = this->dataPtr->terrainGlobals->getDefaultMaterialGenerator(); + + // Assume we get a shader model 2 material profile + Ogre::TerrainMaterialGeneratorA::SM2Profile *matProfile; +#if OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR < 11 + matProfile = static_cast( + matGen->getActiveProfile()); +#else + matProfile = static_cast( + matGen->getActiveProfile()); +#endif + + if (nullptr == matProfile) + { + // using custom material script so ignore setting shadows + return; + } + + matProfile->setLayerParallaxMappingEnabled(false); + + if (_enableShadows) + { + // Make sure PSSM is already setup + matProfile->setReceiveDynamicShadowsEnabled(true); + matProfile->setReceiveDynamicShadowsPSSM( + OgreRTShaderSystem::Instance()->PSSMShadowCameraSetup()); + matProfile->setReceiveDynamicShadowsDepth(true); + matProfile->setReceiveDynamicShadowsLowLod(false); + } + else + { + matProfile->setReceiveDynamicShadowsPSSM(nullptr); + } +} + +///////////////////////////////////////////////// +bool OgreHeightmap::InitBlendMaps(Ogre::Terrain *_terrain) +{ + if (nullptr == _terrain) + { + ignerr << "Invalid terrain\n"; + return false; + } + + // no blending to be done if there's only one texture or no textures at all. + if (this->descriptor.BlendCount() <= 1u || + this->descriptor.TextureCount() <= 1u) + return false; + + // Bounds check for following loop + if (_terrain->getLayerCount() < this->descriptor.BlendCount() + 1) + { + ignerr << "Invalid terrain, too few layers [" + << unsigned(_terrain->getLayerCount()) + << "] for the number of blends [" + << this->descriptor.BlendCount() << "] to initialize blend map" + << std::endl; + return false; + } + + // Create the blend maps + std::vector blendMaps; + std::vector pBlend; + unsigned int i{0u}; + + for (i = 0; i < this->descriptor.BlendCount(); ++i) + { + blendMaps.push_back(_terrain->getLayerBlendMap(i+1)); + pBlend.push_back(blendMaps[i]->getBlendPointer()); + } + + // Set the blend values based on the height of the terrain + Ogre::Real val, height; + for (Ogre::uint16 y = 0; y < _terrain->getLayerBlendMapSize(); ++y) + { + for (Ogre::uint16 x = 0; x < _terrain->getLayerBlendMapSize(); ++x) + { + Ogre::Real tx, ty; + + blendMaps[0]->convertImageToTerrainSpace(x, y, &tx, &ty); + height = _terrain->getHeightAtTerrainPosition(tx, ty); + + for (i = 0; i < this->descriptor.BlendCount(); ++i) + { + auto blend = this->descriptor.BlendByIndex(i); + val = (height - blend->MinHeight()) / blend->FadeDistance(); + val = Ogre::Math::Clamp(val, (Ogre::Real)0, (Ogre::Real)1); + *pBlend[i]++ = val; + } + } + } + + // Make sure the blend maps are properly updated + for (auto map : blendMaps) + { + map->dirty(); + map->update(); + } + + return true; +} + +////////////////////////////////////////////////// +Ogre::MovableObject *OgreHeightmap::OgreObject() const +{ + return nullptr; +} + +////////////////////////////////////////////////// +void OgreHeightmap::SetMaterial(MaterialPtr, bool) +{ + // no-op +} + +////////////////////////////////////////////////// +MaterialPtr OgreHeightmap::Material() const +{ + return nullptr; +} + +///////////////////////////////////////////////// +///////////////////////////////////////////////// +// IgnTerrainMatGen +///////////////////////////////////////////////// +///////////////////////////////////////////////// + +#if OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR < 11 + +///////////////////////////////////////////////// +IgnTerrainMatGen::IgnTerrainMatGen() +: TerrainMaterialGeneratorA() +{ + /// \TODO(anyone) - This will have to be changed if TerrainMaterialGeneratorA + /// ever supports more profiles than only CG + + // Add custom SM2Profile SPAM + this->mProfiles.clear(); + + this->mProfiles.push_back(OGRE_NEW SM2Profile(this, "SM2", + "Profile for rendering on Shader Model 2 capable cards " + "(RTSS depth shadows compatible)")); + + /// \TODO(anyone) - check hardware capabilities & use fallbacks if required + /// (more profiles needed) + this->setActiveProfile(this->mProfiles[0]); +} + +///////////////////////////////////////////////// +IgnTerrainMatGen::~IgnTerrainMatGen() +{ +} + +///////////////////////////////////////////////// +IgnTerrainMatGen::SM2Profile::SM2Profile( + Ogre::TerrainMaterialGenerator *_parent, const Ogre::String &_name, + const Ogre::String &_desc) +: TerrainMaterialGeneratorA::SM2Profile(_parent, _name, _desc) +{ + this->mShaderGen = nullptr; +} + +///////////////////////////////////////////////// +IgnTerrainMatGen::SM2Profile::~SM2Profile() +{ + // Because the base SM2Profile has no virtual destructor: + delete this->mShaderGen; + this->mShaderGen = nullptr; +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::addTechnique( + const Ogre::MaterialPtr &_mat, const Ogre::Terrain *_terrain, + TechniqueType _tt) +{ + // Initiate specialized mShaderGen + // Ogre::GpuProgramManager &gmgr = Ogre::GpuProgramManager::getSingleton(); + + Ogre::HighLevelGpuProgramManager &hmgr = + Ogre::HighLevelGpuProgramManager::getSingleton(); + + if (!this->mShaderGen) + { + // By default we use the GLSL shaders. + if (hmgr.isLanguageSupported("glsl")) + { + this->mShaderGen = OGRE_NEW + IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL(); + } + else + { + ignerr << "No supported shader languages" << std::endl; + return; + } + + // check SM3 features + this->mSM3Available = + Ogre::GpuProgramManager::getSingleton().isSyntaxSupported("ps_3_0"); +#if OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR >= 8 + this->mSM4Available = + Ogre::GpuProgramManager::getSingleton().isSyntaxSupported("ps_4_0"); +#endif + } + + // Unfortunately this doesn't work + // Default implementation + // TerrainMaterialGeneratorA::SM2Profile::addTechnique(mat, terrain, tt); + + // So we have to replicate the entire method: + Ogre::Technique *tech = _mat->createTechnique(); + + // Only supporting one pass + Ogre::Pass *pass = tech->createPass(); + + // Doesn't delegate to the proper method otherwise + Ogre::HighLevelGpuProgramPtr vprog = + ((IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL*)this->mShaderGen) + ->generateVertexProgram(this, _terrain, _tt); + + // DEBUG: std::cout << "VertShader[" << vprog->getName() << "]:\n" + // << vprog->getSource() << "\n\n"; + + Ogre::HighLevelGpuProgramPtr fprog = + ((IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL*)this->mShaderGen) + ->generateFragmentProgram(this, _terrain, _tt); + + // DEBUG: std::cout << "FragShader[" << fprog->getName() << "]:\n" + // << fprog->getSource() << "\n\n"; + + pass->setVertexProgram(vprog->getName()); + pass->setFragmentProgram(fprog->getName()); + + if (_tt == HIGH_LOD || _tt == RENDER_COMPOSITE_MAP) + { + // global normal map + Ogre::TextureUnitState* tu = pass->createTextureUnitState(); + tu->setTextureName(_terrain->getTerrainNormalMap()->getName()); + tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + + // global colour map + if (_terrain->getGlobalColourMapEnabled() && + this->isGlobalColourMapEnabled()) + { + tu = pass->createTextureUnitState( + _terrain->getGlobalColourMap()->getName()); + tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + } + + // light map + if (this->isLightmapEnabled()) + { + tu = pass->createTextureUnitState(_terrain->getLightmap()->getName()); + tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + } + + // blend maps + unsigned int maxLayers = this->getMaxLayers(_terrain); + + unsigned int numBlendTextures = std::min( + _terrain->getBlendTextureCount(maxLayers), + _terrain->getBlendTextureCount()); + + unsigned int numLayers = std::min( + maxLayers, static_cast(_terrain->getLayerCount())); + + for (unsigned int i = 0; i < numBlendTextures; ++i) + { + tu = pass->createTextureUnitState(_terrain->getBlendTextureName(i)); + tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + } + + // layer textures + for (unsigned int i = 0; i < numLayers; ++i) + { + // diffuse / specular + pass->createTextureUnitState(_terrain->getLayerTextureName(i, 0)); + + // normal / height + pass->createTextureUnitState(_terrain->getLayerTextureName(i, 1)); + } + } + else + { + // LOW_LOD textures + // composite map + Ogre::TextureUnitState *tu = pass->createTextureUnitState(); + tu->setTextureName(_terrain->getCompositeMap()->getName()); + tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + } + + // Add shadow textures (always at the end) + if (this->isShadowingEnabled(_tt, _terrain)) + { + unsigned int numTextures = 1; + + if (this->getReceiveDynamicShadowsPSSM()) + { + numTextures = this->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (unsigned int i = 0; i < numTextures; ++i) + { + Ogre::TextureUnitState *tu = pass->createTextureUnitState(); + tu->setContentType(Ogre::TextureUnitState::CONTENT_SHADOW); + tu->setTextureAddressingMode(Ogre::TextureUnitState::TAM_BORDER); + tu->setTextureBorderColour(Ogre::ColourValue::White); + } + } +} + +///////////////////////////////////////////////// +// generate() and generateForCompositeMap() are identical to +// TerrainMaterialGeneratorA implementation, the only reason for repeating +// them is that, unfortunately, addTechnique() is not declared virtual. +Ogre::MaterialPtr IgnTerrainMatGen::SM2Profile::generate( + const Ogre::Terrain *_terrain) +{ + // re-use old material if exists + Ogre::MaterialPtr mat = _terrain->_getMaterial(); + + if (mat.isNull()) + { + Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); + + // it's important that the names are deterministic for a given terrain, so + // use the terrain pointer as an ID + const Ogre::String &matName = _terrain->getMaterialName(); + mat = matMgr.getByName(matName); + + if (mat.isNull()) + { + mat = matMgr.create(matName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + } + } + + // clear everything + mat->removeAllTechniques(); + + // Automatically disable normal & parallax mapping if card cannot handle it + // We do this rather than having a specific technique for it since it's + // simpler. + Ogre::GpuProgramManager &gmgr = Ogre::GpuProgramManager::getSingleton(); + + if (!gmgr.isSyntaxSupported("ps_4_0") && + !gmgr.isSyntaxSupported("ps_3_0") && + !gmgr.isSyntaxSupported("ps_2_x") && + !gmgr.isSyntaxSupported("fp40") && + !gmgr.isSyntaxSupported("arbfp1")) + { + this->setLayerNormalMappingEnabled(false); + this->setLayerParallaxMappingEnabled(false); + } + + this->addTechnique(mat, _terrain, HIGH_LOD); + + // LOD + if (this->mCompositeMapEnabled) + { + this->addTechnique(mat, _terrain, LOW_LOD); + Ogre::Material::LodValueList lodValues; + lodValues.push_back( + Ogre::TerrainGlobalOptions::getSingleton().getCompositeMapDistance()); + + mat->setLodLevels(lodValues); + Ogre::Technique *lowLodTechnique = mat->getTechnique(1); + lowLodTechnique->setLodIndex(1); + } + + this->UpdateParams(mat, _terrain); + + return mat; +} + +///////////////////////////////////////////////// +Ogre::MaterialPtr IgnTerrainMatGen::SM2Profile::generateForCompositeMap( + const Ogre::Terrain *_terrain) +{ + // re-use old material if exists + Ogre::MaterialPtr mat = _terrain->_getCompositeMapMaterial(); + + if (mat.isNull()) + { + Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton(); + + // it's important that the names are deterministic for a given terrain, so + // use the terrain pointer as an ID + const Ogre::String &matName = _terrain->getMaterialName() + "/comp"; + + mat = matMgr.getByName(matName); + + if (mat.isNull()) + { + mat = matMgr.create(matName, + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + } + } + + // clear everything + mat->removeAllTechniques(); + + this->addTechnique(mat, _terrain, RENDER_COMPOSITE_MAP); + + this->UpdateParamsForCompositeMap(mat, _terrain); + + return mat; +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::UpdateParams(const Ogre::MaterialPtr &_mat, + const Ogre::Terrain *_terrain) +{ + static_cast( + this->mShaderGen)->updateParams(this, _mat, _terrain, false); +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::UpdateParamsForCompositeMap( + const Ogre::MaterialPtr &_mat, const Ogre::Terrain *_terrain) +{ + // Only tested for Ogre 1.11 & 1.12 + static_cast( + this->mShaderGen)->updateParams(this, _mat, _terrain, true); +} + +///////////////////////////////////////////////// +///////////////////////////////////////////////// +// GLSL Shader helper +///////////////////////////////////////////////// +///////////////////////////////////////////////// + +///////////////////////////////////////////////// +Ogre::HighLevelGpuProgramPtr +IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateVertexProgram( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt) +{ + Ogre::HighLevelGpuProgramPtr ret = + this->createVertexProgram(_prof, _terrain, _tt); + + Ogre::StringStream sourceStr; + this->generateVertexProgramSource(_prof, _terrain, _tt, sourceStr); + + ret->setSource(sourceStr.str()); + ret->load(); + this->defaultVpParams(_prof, _terrain, _tt, ret); + + return ret; +} + +///////////////////////////////////////////////// +Ogre::HighLevelGpuProgramPtr +IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFragmentProgram( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, TechniqueType _tt) +{ + Ogre::HighLevelGpuProgramPtr ret = this->createFragmentProgram(_prof, + _terrain, _tt); + + Ogre::StringStream sourceStr; + + this->generateFragmentProgramSource(_prof, _terrain, _tt, sourceStr); + + ret->setSource(sourceStr.str()); + + ret->load(); + + this->defaultFpParams(_prof, _terrain, _tt, ret); + + Ogre::GpuProgramParametersSharedPtr params = ret->getDefaultParameters(); + params->setIgnoreMissingParams(false); + + Ogre::uint maxLayers = _prof->getMaxLayers(_terrain); + Ogre::uint numBlendTextures = std::min( + _terrain->getBlendTextureCount(maxLayers), + _terrain->getBlendTextureCount()); + + Ogre::uint numLayers = std::min(maxLayers, + static_cast(_terrain->getLayerCount())); + + int samplerCounter = 0; + + if (_tt == LOW_LOD) + params->setNamedConstant("compositeMap", samplerCounter++); + else + { + params->setNamedConstant("globalNormal", samplerCounter++); + + if (_terrain->getGlobalColourMapEnabled() && + _prof->isGlobalColourMapEnabled()) + { + params->setNamedConstant("globalColourMap", samplerCounter++); + } + + if (_prof->isLightmapEnabled()) + params->setNamedConstant("lightMap", samplerCounter++); + + for (Ogre::uint i = 0; i < numBlendTextures; ++i) + { + params->setNamedConstant("blendTex" + + std::to_string(i), samplerCounter++); + } + + for (Ogre::uint i = 0; i < numLayers; ++i) + { + params->setNamedConstant("difftex" + + std::to_string(i), samplerCounter++); + params->setNamedConstant("normtex" + + std::to_string(i), samplerCounter++); + } + } + + if (_prof->isShadowingEnabled(_tt, _terrain)) + { + Ogre::uint numTextures = 1; + if (_prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + + for (Ogre::uint i = 0; i < numTextures; ++i) + { + params->setNamedConstant("shadowMap" + + std::to_string(i), samplerCounter++); + } + } + + return ret; +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::updateParams( + const SM2Profile *_prof, const Ogre::MaterialPtr &_mat, + const Ogre::Terrain *_terrain, bool _compositeMap) +{ + Ogre::Pass *p = _mat->getTechnique(0)->getPass(0); + + if (_compositeMap) + { + this->updateVpParams(_prof, _terrain, RENDER_COMPOSITE_MAP, + p->getVertexProgramParameters()); + this->updateFpParams(_prof, _terrain, RENDER_COMPOSITE_MAP, + p->getFragmentProgramParameters()); + } + else + { + // high lod + this->updateVpParams(_prof, _terrain, HIGH_LOD, + p->getVertexProgramParameters()); + this->updateFpParams(_prof, _terrain, HIGH_LOD, + p->getFragmentProgramParameters()); + + if (_prof->isCompositeMapEnabled()) + { + // low lod + p = _mat->getTechnique(1)->getPass(0); + this->updateVpParams(_prof, _terrain, LOW_LOD, + p->getVertexProgramParameters()); + this->updateFpParams(_prof, _terrain, LOW_LOD, + p->getFragmentProgramParameters()); + } + } +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL:: +generateVertexProgramSource(const SM2Profile *_prof, + const Ogre::Terrain* _terrain, TechniqueType _tt, + Ogre::StringStream &_outStream) +{ + this->generateVpHeader(_prof, _terrain, _tt, _outStream); + + if (_tt != LOW_LOD) + { + unsigned int maxLayers = _prof->getMaxLayers(_terrain); + unsigned int numLayers = std::min(maxLayers, + static_cast(_terrain->getLayerCount())); + + for (unsigned int i = 0; i < numLayers; ++i) + this->generateVpLayer(_prof, _terrain, _tt, i, _outStream); + } + + this->generateVpFooter(_prof, _terrain, _tt, _outStream); +} + +///////////////////////////////////////////////// +// This method is identical to +// TerrainMaterialGeneratorA::SM2Profile::ShaderHelperGLSL::generateVpHeader() +// but is needed because generateVpDynamicShadowsParams() is not declared +// virtual. +void IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateVpHeader( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt, Ogre::StringStream &_outStream) +{ + bool compression = false; + + _outStream << "#version " << this->glslVersion << "\n\n"; + +#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8 + compression = _terrain->_getUseVertexCompression() && + _tt != RENDER_COMPOSITE_MAP; + + if (compression) + { + // The parameter "in vec4 vertex;" is automatically bound by OGRE. + // The parameter "in vec4 uv0'" is automatically bound by OGRE. + _outStream << this->vpInStr << " vec4 vertex;\n" + << this->vpInStr << " vec4 uv0;\n"; + } + else +#endif + { + // The parameter "in vec4 vertex;" is automatically bound by OGRE. + // The parameter "in vec4 uv0'" is automatically bound by OGRE. + _outStream << this->vpInStr << " vec4 vertex;\n" + << this->vpInStr << " vec4 uv0;\n"; + } + + if (_tt != RENDER_COMPOSITE_MAP) + // The parameter "in vec4 uv1'" is automatically bound by OGRE. + _outStream << this->vpInStr << " vec4 uv1;\n"; + + _outStream << + "uniform mat4 worldMatrix;\n" + "uniform mat4 viewProjMatrix;\n" + "uniform vec2 lodMorph;\n"; + + if (compression) + { + _outStream << + "uniform mat4 posIndexToObjectSpace;\n" + "uniform float baseUVScale;\n"; + } + + + // uv multipliers + unsigned int maxLayers = _prof->getMaxLayers(_terrain); + unsigned int numLayers = std::min(maxLayers, + static_cast(_terrain->getLayerCount())); + + unsigned int numUVMultipliers = (numLayers / 4); + + if (numLayers % 4) + ++numUVMultipliers; + + for (unsigned int i = 0; i < numUVMultipliers; ++i) + _outStream << "uniform vec4 uvMul" << i << ";\n"; + + _outStream << + this->vpOutStr << " vec4 position;\n"; + + unsigned int texCoordSet = 1; + _outStream << this->vpOutStr << " vec4 uvMisc;\n"; + + // layer UV's premultiplied, packed as xy/zw + unsigned int numUVSets = numLayers / 2; + + if (numLayers % 2) + ++numUVSets; + + if (_tt != LOW_LOD) + { + for (unsigned int i = 0; i < numUVSets; ++i) + { + _outStream << this->vpOutStr << " vec4 layerUV" << i << ";\n"; + } + } + + if (_prof->getParent()->getDebugLevel() && _tt != RENDER_COMPOSITE_MAP) + { + _outStream << this->vpOutStr << " vec2 lodInfo;\n"; + } + + bool fog = _terrain->getSceneManager()->getFogMode() != Ogre::FOG_NONE && + _tt != RENDER_COMPOSITE_MAP; + + if (fog) + { + _outStream << + "uniform vec4 fogParams;\n" + << this->vpOutStr << " float fogVal;\n"; + } + + if (_prof->isShadowingEnabled(_tt, _terrain)) + { + texCoordSet = this->generateVpDynamicShadowsParams(texCoordSet, _prof, + _terrain, _tt, _outStream); + } + + // check we haven't exceeded texture coordinates + if (texCoordSet > 8) + { + OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS, + "Requested options require too many texture coordinate sets! " + "Try reducing the number of layers.", + __FUNCTION__); + } + + _outStream << "void main()\n" + << "{\n"; + + if (compression) + { + _outStream + << " vec4 pos = posIndexToObjectSpace * " + << "vec4(vertex.x, vertex.y, uv0.x, 1.0);\n" + + << " vec2 uv = vec2(vertex.x * baseUVScale, 1.0 - " + << "(vertex.y * baseUVScale));\n"; + } + else + { + _outStream + << " vec4 pos = vertex;\n" + << " vec2 uv = vec2(uv0.x, uv0.y);\n"; + } + + _outStream << " vec4 worldPos = worldMatrix * pos;\n"; + _outStream << " position = pos;\n"; + + if (_tt != RENDER_COMPOSITE_MAP) + { + // determine whether to apply the LOD morph to this vertex + // we store the deltas against all vertices so we only want to apply + // the morph to the ones which would disappear. The target LOD which is + // being morphed to is stored in lodMorph.y, and the LOD at which + // the vertex should be morphed is stored in uv.w. If we subtract + // the former from the latter, and arrange to only morph if the + // result is negative (it will only be -1 in fact, since after that + // the vertex will never be indexed), we will achieve our aim. + // sign(vertexLOD - targetLOD) == -1 is to morph + _outStream << + " float toMorph = -min(0.0, sign(uv1.y - lodMorph.y));\n"; + + // this will either be 1 (morph) or 0 (don't morph) + if (_prof->getParent()->getDebugLevel()) + { + // x == LOD level (-1 since value is target level, we want to + // display actual) + _outStream << "lodInfo.x = (lodMorph.y - 1.0) / " + << _terrain->getNumLodLevels() << ";\n"; + + // y == LOD morph + _outStream << "lodInfo.y = toMorph * lodMorph.x;\n"; + } + + // morph + switch (_terrain->getAlignment()) + { + case Ogre::Terrain::ALIGN_X_Y: + _outStream << " worldPos.z += uv1.x * toMorph * lodMorph.x;\n"; + break; + case Ogre::Terrain::ALIGN_X_Z: + _outStream << " worldPos.y += uv1.x * toMorph * lodMorph.x;\n"; + break; + case Ogre::Terrain::ALIGN_Y_Z: + _outStream << " worldPos.x += uv1.x * toMorph * lodMorph.x;\n"; + break; + default: + ignerr << "Invalid alignment\n"; + }; + } + + // generate UVs + if (_tt != LOW_LOD) + { + for (unsigned int i = 0; i < numUVSets; ++i) + { + unsigned int layer = i * 2; + unsigned int uvMulIdx = layer / 4; + + _outStream << " layerUV" << i << ".xy = " << " uv.xy * uvMul" + << uvMulIdx << "." << this->GetChannel(layer) << ";\n"; + _outStream << " layerUV" << i << ".zw = " << " uv.xy * uvMul" + << uvMulIdx << "." << this->GetChannel(layer+1) << ";\n"; + } + } +} + +///////////////////////////////////////////////// +// This method is identical to +// TerrainMaterialGeneratorA::SM2Profile::ShaderHelperGLSL::generateVpFooter() +// but is needed because generateVpDynamicShadows() is not declared virtual. +void IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateVpFooter( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt, Ogre::StringStream &_outStream) +{ + _outStream << " gl_Position = viewProjMatrix * worldPos;\n" + << " uvMisc.xy = uv.xy;\n"; + + bool fog = _terrain->getSceneManager()->getFogMode() != Ogre::FOG_NONE && + _tt != RENDER_COMPOSITE_MAP; + if (fog) + { + if (_terrain->getSceneManager()->getFogMode() == Ogre::FOG_LINEAR) + { + _outStream << + " fogVal = clamp((oPos.z - fogParams.y) * fogParams.w, 0.0, 1.0);\n"; + } + else + { + _outStream << + " fogVal = 1 - clamp(1 / (exp(oPos.z * fogParams.x)), 0.0, 1.0);\n"; + } + } + + if (_prof->isShadowingEnabled(_tt, _terrain)) + this->generateVpDynamicShadows(_prof, _terrain, _tt, _outStream); + + _outStream << "}\n"; +} + +///////////////////////////////////////////////// +void +IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateVpDynamicShadows( + const SM2Profile *_prof, const Ogre::Terrain * /*_terrain*/, + TechniqueType /*_tt*/, Ogre::StringStream &_outStream) +{ + unsigned int numTextures = 1; + + if (_prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + + // Calculate the position of vertex in light space + for (unsigned int i = 0; i < numTextures; ++i) + { + _outStream << " lightSpacePos" << i << " = texViewProjMatrix" + << i << " * worldPos;\n"; + + // Don't linearize depth range: RTSS PSSM implementation uses + // view-space depth + // if (prof->getReceiveDynamicShadowsDepth()) + // { + // // make linear + // outStream << "lightSpacePos" << i << ".z = (lightSpacePos" << i + // << ".z - depthRange" << i << ".x) * depthRange" << i + // << ".w;\n"; + // } + } + + if (_prof->getReceiveDynamicShadowsPSSM()) + { + _outStream << " // pass cam depth\n uvMisc.z = gl_Position.z;\n"; + } +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::defaultVpParams( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt, const Ogre::HighLevelGpuProgramPtr &_prog) +{ + Ogre::GpuProgramParametersSharedPtr params = _prog->getDefaultParameters(); + params->setIgnoreMissingParams(true); + + params->setNamedAutoConstant("worldMatrix", + Ogre::GpuProgramParameters::ACT_WORLD_MATRIX); + + params->setNamedAutoConstant("viewProjMatrix", + Ogre::GpuProgramParameters::ACT_VIEWPROJ_MATRIX); + + params->setNamedAutoConstant("lodMorph", + Ogre::GpuProgramParameters::ACT_CUSTOM, + Ogre::Terrain::LOD_MORPH_CUSTOM_PARAM); + + params->setNamedAutoConstant("fogParams", + Ogre::GpuProgramParameters::ACT_FOG_PARAMS); + + if (_prof->isShadowingEnabled(_tt, _terrain)) + { + unsigned int numTextures = 1; + if (_prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (unsigned int i = 0; i < numTextures; ++i) + { + params->setNamedAutoConstant("texViewProjMatrix" + + Ogre::StringConverter::toString(i), + Ogre::GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, i); + + // Don't add depth range params + // if (prof->getReceiveDynamicShadowsDepth()) + // { + // params->setNamedAutoConstant("depthRange" + + // Ogre::StringConverter::toString(i), + // Ogre::GpuProgramParameters::ACT_SHADOW_SCENE_DEPTH_RANGE, i); + // } + } + } + +#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8 + if (_terrain->_getUseVertexCompression() && _tt != RENDER_COMPOSITE_MAP) + { + Ogre::Matrix4 posIndexToObjectSpace; + _terrain->getPointTransform(&posIndexToObjectSpace); + params->setNamedConstant("posIndexToObjectSpace", posIndexToObjectSpace); + } +#endif +} + +///////////////////////////////////////////////// +unsigned int IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL:: +generateVpDynamicShadowsParams(unsigned int _texCoord, const SM2Profile *_prof, + const Ogre::Terrain * /*_terrain*/, TechniqueType /*_tt*/, + Ogre::StringStream &_outStream) +{ + // out semantics & params + unsigned int numTextures = 1; + + if (_prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + + for (unsigned int i = 0; i < numTextures; ++i) + { + _outStream << this->vpOutStr << " vec4 lightSpacePos" << i + << ";\n" << "uniform mat4 texViewProjMatrix" << i << ";\n"; + + // Don't add depth range params + // if (prof->getReceiveDynamicShadowsDepth()) + // { + // _outStream << ", uniform float4 depthRange" << i + // << " // x = min, y = max, z = range, w = 1/range\n"; + // } + } + + return _texCoord; +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpHeader( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt, Ogre::StringStream &_outStream) +{ + _outStream << "#version " << this->glslVersion << "\n\n"; + + _outStream << + "vec4 expand(vec4 v)\n" + "{\n" + " return v * 2 - 1;\n" + "}\n\n"; + + _outStream << + "vec4 lit(float NdotL, float NdotH, float m)\n" + "{\n" + " float specular = (NdotL > 0) ? pow(max(0.0, NdotH), m) : 0.0;\n" + " return vec4(1.0, max(0.0, NdotL), specular, 1.0);\n" + "}\n"; + + if (_prof->isShadowingEnabled(_tt, _terrain)) + this->generateFpDynamicShadowsHelpers(_prof, _terrain, _tt, _outStream); + + _outStream << + this->fpInStr << " vec4 position;\n"; + + Ogre::uint texCoordSet = 1; + _outStream << this->fpInStr << " vec4 uvMisc;\n"; + + // UV's premultiplied, packed as xy/zw + Ogre::uint maxLayers = _prof->getMaxLayers(_terrain); + Ogre::uint numBlendTextures = std::min( + _terrain->getBlendTextureCount(maxLayers), + _terrain->getBlendTextureCount()); + Ogre::uint numLayers = std::min(maxLayers, + static_cast(_terrain->getLayerCount())); + + Ogre::uint numUVSets = numLayers / 2; + + if (numLayers % 2) + ++numUVSets; + + if (_tt != LOW_LOD) + { + for (Ogre::uint i = 0; i < numUVSets; ++i) + { + _outStream << + this->fpInStr << " vec4 layerUV" << i << ";\n"; + } + } + + if (_prof->getParent()->getDebugLevel() && _tt != RENDER_COMPOSITE_MAP) + { + _outStream << this->fpInStr << " vec2 lodInfo;\n"; + } + + bool fog = _terrain->getSceneManager()->getFogMode() != Ogre::FOG_NONE && + _tt != RENDER_COMPOSITE_MAP; + + if (fog) + { + _outStream << + "uniform vec3 fogColour;\n" + << this->fpInStr << " float fogVal;\n"; + } + + Ogre::uint currentSamplerIdx = 0; + + _outStream << + // Only 1 light supported in this version + // deferred shading profile / generator later, ok? :) + "uniform vec3 ambient;\n" + "uniform vec4 lightPosObjSpace;\n" + "uniform vec3 lightDiffuseColour;\n" + "uniform vec3 lightSpecularColour;\n" + "uniform vec3 eyePosObjSpace;\n" + // pack scale, bias and specular + "uniform vec4 scaleBiasSpecular;\n"; + + if (_tt == LOW_LOD) + { + // single composite map covers all the others below + _outStream << "uniform sampler2D compositeMap;\n"; + } + else + { + _outStream << "uniform sampler2D globalNormal;\n"; + + if (_terrain->getGlobalColourMapEnabled() && + _prof->isGlobalColourMapEnabled()) + { + _outStream << "uniform sampler2D globalColourMap;\n"; + } + + if (_prof->isLightmapEnabled()) + { + _outStream << "uniform sampler2D lightMap;\n"; + } + + // Blend textures - sampler definitions + for (Ogre::uint i = 0; i < numBlendTextures; ++i) + { + _outStream << "uniform sampler2D blendTex" << i << ";\n"; + } + + // Layer textures - sampler definitions & UV multipliers + for (Ogre::uint i = 0; i < numLayers; ++i) + { + _outStream << "uniform sampler2D difftex" << i << ";\n"; + _outStream << "uniform sampler2D normtex" << i << ";\n"; + } + } + + if (_prof->isShadowingEnabled(_tt, _terrain)) + { + this->generateFpDynamicShadowsParams(&texCoordSet, ¤tSamplerIdx, + _prof, _terrain, _tt, _outStream); + } + + // check we haven't exceeded samplers + if (currentSamplerIdx > 16) + { + OGRE_EXCEPT(Ogre::Exception::ERR_INVALIDPARAMS, + "Requested options require too many texture samplers! " + "Try reducing the number of layers.", __FUNCTION__); + } + + std::string outputColTypeStr = "vec4"; + if (this->glslVersion != "120") + { + _outStream << "out vec4 outputCol;\n"; + outputColTypeStr = ""; + } + + _outStream << + "void main()\n" + "{\n" + " float shadow = 1.0;\n" + " vec2 uv = uvMisc.xy;\n" + " " << outputColTypeStr << " outputCol = vec4(0.0, 0.0, 0.0, 1.0);\n"; + + if (_tt != LOW_LOD) + { + // global normal + _outStream << " vec3 normal = expand(" + << this->textureStr << "(globalNormal, uv)).xyz;\n"; + } + + _outStream << + " vec3 lightDir =\n" + " lightPosObjSpace.xyz - (position.xyz * lightPosObjSpace.w);\n" + " vec3 eyeDir = eyePosObjSpace - position.xyz;\n" + + // set up accumulation areas + " vec3 diffuse = vec3(0.0, 0.0, 0.0);\n" + " float specular = 0.0;\n"; + + if (_tt == LOW_LOD) + { + // we just do a single calculation from composite map + _outStream << " vec4 composite = " << this->textureStr + << "(compositeMap, uv);\n diffuse = composite.xyz;\n"; + // TODO(anyone) - specular; we'll need normals for this! + } + else + { + // set up the blend values + for (Ogre::uint i = 0; i < numBlendTextures; ++i) + { + _outStream << " vec4 blendTexVal" << i << " = " + << this->textureStr << "(blendTex" << i + << ", uv);\n"; + } + + if (_prof->isLayerNormalMappingEnabled()) + { + // derive the tangent space basis + // we do this in the pixel shader because we don't have per-vertex normals + // because of the LOD, we use a normal map + // tangent is always +x or -z in object space depending on alignment + switch (_terrain->getAlignment()) + { + case Ogre::Terrain::ALIGN_X_Y: + case Ogre::Terrain::ALIGN_X_Z: + _outStream << " vec3 tangent = vec3(1.0, 0.0, 0.0);\n"; + break; + case Ogre::Terrain::ALIGN_Y_Z: + _outStream << " vec3 tangent = vec3(0.0, 0.0, -1.0);\n"; + break; + default: + ignerr << "Invalid terrain alignment\n"; + break; + }; + + _outStream << " vec3 binormal = normalize(cross(tangent, normal));\n"; + // note, now we need to re-cross to derive tangent again because it + // wasn't orthonormal + _outStream << " tangent = normalize(cross(normal, binormal));\n"; + // derive final matrix + /*_outStream << " mat3 TBN = mat3(tangent.x, tangent.y, tangent.z," + "binormal.x, binormal.y, binormal.z," + "normal.x, normal.y, normal.z);\n"; + */ + + // set up lighting result placeholders for interpolation + _outStream << " vec4 litRes, litResLayer;\n"; + _outStream << " vec3 TSlightDir, TSeyeDir, TShalfAngle, TSnormal;\n"; + if (_prof->isLayerParallaxMappingEnabled()) + _outStream << " float displacement;\n"; + // move + _outStream << " TSlightDir = normalize(vec3(dot(tangent, lightDir)," + "dot(binormal, lightDir)," + "dot(normal, lightDir)));\n"; + _outStream << " TSeyeDir = normalize(vec3(dot(tangent, eyeDir)," + "dot(binormal, eyeDir)," + "dot(normal, eyeDir)));\n"; + } + else + { + // simple per-pixel lighting with no normal mapping + _outStream << " lightDir = normalize(lightDir);\n"; + _outStream << " eyeDir = normalize(eyeDir);\n"; + _outStream << " vec3 halfAngle = normalize(lightDir + eyeDir);\n"; + + _outStream << " vec4 litRes = lit(dot(lightDir, normal), " + "dot(halfAngle, normal), scaleBiasSpecular.z);\n"; + } + } +} + +///////////////////////////////////////////////// +void +IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpDynamicShadowsParams( + Ogre::uint *_texCoord, Ogre::uint *_sampler, const SM2Profile *_prof, + const Ogre::Terrain * /*_terrain*/, TechniqueType _tt, + Ogre::StringStream &_outStream) +{ + if (_tt == HIGH_LOD) + this->mShadowSamplerStartHi = *_sampler; + else if (_tt == LOW_LOD) + this->mShadowSamplerStartLo = *_sampler; + + // in semantics & params + Ogre::uint numTextures = 1; + if (_prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = _prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + _outStream << "uniform vec4 pssmSplitPoints;\n"; + } + + for (Ogre::uint i = 0; i < numTextures; ++i) + { + _outStream << this->fpInStr << + " vec4 lightSpacePos" << i << ";\n" << + "uniform sampler2D shadowMap" << i << ";\n"; + + *_sampler = *_sampler + 1; + *_texCoord = *_texCoord + 1; + + if (_prof->getReceiveDynamicShadowsDepth()) + { + _outStream << + "uniform float inverseShadowmapSize" << i << ";\n"; + } + } +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpLayer( + const SM2Profile *_prof, const Ogre::Terrain * /*_terrain*/, + TechniqueType _tt, Ogre::uint _layer, + Ogre::StringStream &_outStream) +{ + Ogre::uint uvIdx = _layer / 2; + Ogre::String uvChannels = (_layer % 2) ? ".zw" : ".xy"; + Ogre::uint blendIdx = (_layer-1) / 4; + Ogre::String blendChannel = this->GetChannel(_layer-1); + Ogre::String blendWeightStr = Ogre::String("blendTexVal") + + Ogre::StringConverter::toString(blendIdx) + "." + blendChannel; + + // generate early-out conditional + // Disable - causing some issues even when trying to force the use of texldd + // if (layer && prof->_isSM3Available()) + // _outStream << " if (" << blendWeightStr << " > 0.0003)\n {\n"; + + // generate UV + _outStream << " vec2 uv" << _layer << " = layerUV" << uvIdx + << uvChannels << ";\n"; + + // calculate lighting here if normal mapping + if (_prof->isLayerNormalMappingEnabled()) + { + if (_prof->isLayerParallaxMappingEnabled() && _tt != RENDER_COMPOSITE_MAP) + { + // modify UV - note we have to sample an extra time + _outStream << " displacement = " << this->textureStr + << "(normtex" << _layer << ", uv" << _layer << ").w\n" + " * scaleBiasSpecular.x + scaleBiasSpecular.y;\n"; + _outStream << " uv" << _layer << " += TSeyeDir.xy * displacement;\n"; + } + + // access TS normal map + _outStream << " TSnormal = expand(" << this->textureStr + << "(normtex" << _layer << ", uv" << _layer << ")).xyz;\n"; + _outStream << " TShalfAngle = normalize(TSlightDir + TSeyeDir);\n"; + + _outStream << " litResLayer = lit(dot(TSlightDir, TSnormal), " + "dot(TShalfAngle, TSnormal), scaleBiasSpecular.z);\n"; + + if (!_layer) + _outStream << " litRes = litResLayer;\n"; + else + _outStream << " litRes = mix(litRes, litResLayer, " + << blendWeightStr << ");\n"; + } + + // sample diffuse texture + _outStream << " vec4 diffuseSpecTex" << _layer << " = " + << this->textureStr << "(difftex" << _layer << ", uv" + << _layer << ");\n"; + + // apply to common + if (!_layer) + { + _outStream << " diffuse = diffuseSpecTex0.xyz;\n"; + if (_prof->isLayerSpecularMappingEnabled()) + _outStream << " specular = diffuseSpecTex0.w;\n"; + } + else + { + _outStream << " diffuse = mix(diffuse, diffuseSpecTex" << _layer + << ".xyz, " << blendWeightStr << ");\n"; + + if (_prof->isLayerSpecularMappingEnabled()) + { + _outStream << " specular = mix(specular, diffuseSpecTex" << _layer + << ".w, " << blendWeightStr << ");\n"; + } + } + + // End early-out + // Disable - causing some issues even when trying to force the use of texldd + // if (layer && prof->_isSM3Available()) + // _outStream << " } // early-out blend value\n"; +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpFooter( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt, Ogre::StringStream &_outStream) +{ + if (_tt == LOW_LOD) + { + if (_prof->isShadowingEnabled(_tt, _terrain)) + { + this->generateFpDynamicShadows(_prof, _terrain, _tt, _outStream); + _outStream << " outputCol.xyz = diffuse * rtshadow;\n"; + } + else + { + _outStream << " outputCol.xyz = diffuse;\n"; + } + } + else + { + if (_terrain->getGlobalColourMapEnabled() && + _prof->isGlobalColourMapEnabled()) + { + // sample colour map and apply to diffuse + _outStream << " diffuse *= " << this->textureStr + << "(globalColourMap, uv).xyz;\n"; + } + + if (_prof->isLightmapEnabled()) + { + // sample lightmap + _outStream << " shadow = " << this->textureStr + << "(lightMap, uv).x;\n"; + } + + if (_prof->isShadowingEnabled(_tt, _terrain)) + { + this->generateFpDynamicShadows(_prof, _terrain, _tt, _outStream); + } + + // diffuse lighting + _outStream << " outputCol.xyz += ambient * diffuse + litRes.y * " + "lightDiffuseColour * diffuse * shadow;\n"; + + // specular default + if (!_prof->isLayerSpecularMappingEnabled()) + _outStream << " specular = 1.0;\n"; + + if (_tt == RENDER_COMPOSITE_MAP) + { + // Lighting embedded in alpha + _outStream << " outputCol.w = shadow;\n"; + } + else + { + // Apply specular + _outStream << " outputCol.xyz += litRes.z * lightSpecularColour * " + "specular * shadow;\n"; + + if (_prof->getParent()->getDebugLevel()) + { + _outStream << " outputCol.xy += lodInfo.xy;\n"; + } + } + } + + bool fog = _terrain->getSceneManager()->getFogMode() != Ogre::FOG_NONE && + _tt != RENDER_COMPOSITE_MAP; + if (fog) + { + _outStream << " outputCol.xyz = mix(outputCol.xyz, fogColour, fogVal);\n"; + } + + if (this->glslVersion == "120") + _outStream << " gl_FragColor = outputCol;\n"; + + // Final return + _outStream << "\n}\n"; +} + +///////////////////////////////////////////////// +void +IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpDynamicShadowsHelpers( + const SM2Profile *_prof, const Ogre::Terrain * /*_terrain*/, + TechniqueType /*_tt*/, Ogre::StringStream &_outStream) +{ + // TODO(anyone) make filtering configurable + _outStream << + "// Simple PCF\n" + "// Number of samples in one dimension (square for total samples)\n" + "#define NUM_SHADOW_SAMPLES_1D 2.0\n" + "#define SHADOW_FILTER_SCALE 1.0\n" + + "#define SHADOW_SAMPLES NUM_SHADOW_SAMPLES_1D*NUM_SHADOW_SAMPLES_1D\n" + + "vec4 offsetSample(vec4 uv, vec2 offset, float invMapSize)\n" + "{\n" + " return vec4(uv.xy + offset * invMapSize * uv.w, uv.z, uv.w);\n" + "}\n"; + + if (_prof->getReceiveDynamicShadowsDepth()) + { + _outStream << + "float calcDepthShadow(sampler2D shadowMap, vec4 uv, " + "float invShadowMapSize)\n" + "{\n" + " // 4-sample PCF\n" + " float shadow = 0.0;\n" + " float offset = (NUM_SHADOW_SAMPLES_1D/2.0 - 0.5) *SHADOW_FILTER_SCALE;" + "\n" + " for (float y = -offset; y <= offset; y += SHADOW_FILTER_SCALE)\n" + " for (float x = -offset; x <= offset; x += SHADOW_FILTER_SCALE)\n" + " {\n" + " vec4 newUV = offsetSample(uv, vec2(x, y), invShadowMapSize);\n" + " // manually project and assign derivatives\n" + " // to avoid gradient issues inside loops\n" + " newUV = newUV / newUV.w;\n"; + // The following line used to be: + // " float depth = tex2d(shadowMap, newUV.xy).x;\n" + if (this->glslVersion == "120") + _outStream << + " float depth = texture2D(shadowMap, newUV.xy).x;\n"; + else + { + _outStream << + " float depth = textureGrad(shadowMap, newUV.xy, " + " vec2(1.0, 1.0), vec2(1.0, 1.0)).x;\n"; + } + _outStream << + // " if (depth >= 1.0 || depth >= uv.z)\n" + " if (depth >= 1.0 || depth >= newUV.z)\n" + " shadow += 1.0;\n" + " }\n" + " shadow /= (SHADOW_SAMPLES); \n" + " return shadow;\n" + "}\n"; + } + else + { + _outStream << + "float calcSimpleShadow(sampler2D shadowMap, vec4 shadowMapPos)\n" + "{\n" + " return " << this->textureStr + << "Proj(shadowMap, shadowMapPos).x;\n" + "}\n"; + } + + if (_prof->getReceiveDynamicShadowsPSSM()) + { + Ogre::uint numTextures = + _prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + + if (_prof->getReceiveDynamicShadowsDepth()) + { + _outStream << "float calcPSSMDepthShadow("; + } + else + { + _outStream << "float calcPSSMSimpleShadow("; + } + + _outStream << "\n "; + + for (Ogre::uint i = 0; i < numTextures; ++i) + _outStream << "sampler2D shadowMap" << i << ", "; + + _outStream << "\n "; + + for (Ogre::uint i = 0; i < numTextures; ++i) + _outStream << "vec4 lsPos" << i << ", "; + + if (_prof->getReceiveDynamicShadowsDepth()) + { + _outStream << "\n "; + for (Ogre::uint i = 0; i < numTextures; ++i) + _outStream << "float invShadowmapSize" << i << ", "; + } + + _outStream << "\n" + " vec4 pssmSplitPoints, float camDepth)\n" + "{\n" + " float shadow = 1.0;\n" + " // calculate shadow\n"; + + for (Ogre::uint i = 0; i < numTextures; ++i) + { + if (!i) + { + _outStream << " if (camDepth <= pssmSplitPoints." + << this->GetChannel(i) << ")\n"; + } + else if (i < numTextures-1) + { + _outStream << " else if (camDepth <= pssmSplitPoints." + << this->GetChannel(i) << ")\n"; + } + else + _outStream << " else\n"; + + _outStream << " {\n"; + + if (_prof->getReceiveDynamicShadowsDepth()) + { + _outStream << " shadow = calcDepthShadow(shadowMap" << i + << ", lsPos" << i << ", invShadowmapSize" << i << ");\n"; + } + else + { + _outStream << " shadow = calcSimpleShadow(shadowMap" << i + << ", lsPos" << i << ");\n"; + } + _outStream << " }\n"; + } + + _outStream << " return shadow;\n" + "}\n\n\n"; + } +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFpDynamicShadows( + const SM2Profile *_prof, const Ogre::Terrain * /*_terrain*/, + TechniqueType /*_tt*/, Ogre::StringStream &_outStream) +{ + if (_prof->getReceiveDynamicShadowsPSSM()) + { + Ogre::uint numTextures = + _prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + + _outStream << " float camDepth = uvMisc.z;\n"; + + if (_prof->getReceiveDynamicShadowsDepth()) + { + _outStream << " float rtshadow = calcPSSMDepthShadow("; + } + else + { + _outStream << " float rtshadow = calcPSSMSimpleShadow("; + } + + for (Ogre::uint i = 0; i < numTextures; ++i) + _outStream << "shadowMap" << i << ", "; + + _outStream << "\n "; + + for (Ogre::uint i = 0; i < numTextures; ++i) + _outStream << "lightSpacePos" << i << ", "; + + if (_prof->getReceiveDynamicShadowsDepth()) + { + _outStream << "\n "; + + for (Ogre::uint i = 0; i < numTextures; ++i) + _outStream << "inverseShadowmapSize" << i << ", "; + } + _outStream << "\n" << + " pssmSplitPoints, camDepth);\n"; + } + else + { + if (_prof->getReceiveDynamicShadowsDepth()) + { + _outStream << + " float rtshadow = calcDepthShadow(shadowMap0, lightSpacePos0, " + "inverseShadowmapSize0);"; + } + else + { + _outStream << + " float rtshadow = calcSimpleShadow(shadowMap0, lightSpacePos0);"; + } + } + + _outStream << " shadow = rtshadow;//min(shadow, rtshadow);\n"; +} + +///////////////////////////////////////////////// +void +IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::generateFragmentProgramSource( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, + TechniqueType _tt, Ogre::StringStream &_outStream) +{ + this->generateFpHeader(_prof, _terrain, _tt, _outStream); + + if (_tt != LOW_LOD) + { + Ogre::uint maxLayers = _prof->getMaxLayers(_terrain); + Ogre::uint numLayers = std::min(maxLayers, + static_cast(_terrain->getLayerCount())); + + for (Ogre::uint i = 0; i < numLayers; ++i) + this->generateFpLayer(_prof, _terrain, _tt, i, _outStream); + } + + this->generateFpFooter(_prof, _terrain, _tt, _outStream); +} + +///////////////////////////////////////////////// +void IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::updateVpParams( + const SM2Profile *_prof, const Ogre::Terrain *_terrain, +#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8 + TechniqueType _tt, +#else + TechniqueType /*_tt*/, +#endif + const Ogre::GpuProgramParametersSharedPtr &_params) +{ + _params->setIgnoreMissingParams(true); + Ogre::uint maxLayers = _prof->getMaxLayers(_terrain); + Ogre::uint numLayers = std::min(maxLayers, + static_cast(_terrain->getLayerCount())); + + Ogre::uint numUVMul = numLayers / 4; + + if (numLayers % 4) + ++numUVMul; + + for (Ogre::uint i = 0; i < numUVMul; ++i) + { + Ogre::Vector4 uvMul( + _terrain->getLayerUVMultiplier(i * 4), + _terrain->getLayerUVMultiplier(i * 4 + 1), + _terrain->getLayerUVMultiplier(i * 4 + 2), + _terrain->getLayerUVMultiplier(i * 4 + 3)); + _params->setNamedConstant("uvMul" + + Ogre::StringConverter::toString(i), uvMul); + } + +#if OGRE_VERSION_MAJOR >= 1 && OGRE_VERSION_MINOR >= 8 + if (_terrain->_getUseVertexCompression() && _tt != RENDER_COMPOSITE_MAP) + { + Ogre::Real baseUVScale = 1.0f / (_terrain->getSize() - 1); + _params->setNamedConstant("baseUVScale", baseUVScale); + } +#endif +} + +///////////////////////////////////////////////// +Ogre::String IgnTerrainMatGen::SM2Profile::ShaderHelperGLSL::GetChannel( +Ogre::uint _idx) +{ + Ogre::uint rem = _idx % 4; + switch (rem) + { + case 0: + default: + return "x"; + case 1: + return "y"; + case 2: + return "z"; + case 3: + return "w"; + }; +} + +// #if OGRE_VERSION_MAJOR == 1 && OGRE_VERSION_MINOR < 11 +#endif + +///////////////////////////////////////////////// +///////////////////////////////////////////////// +// TerrainMaterial +///////////////////////////////////////////////// +///////////////////////////////////////////////// + +////////////////////////////////////////////////// +TerrainMaterial::TerrainMaterial(const std::string &_materialName) + : materialName(_materialName) +{ + this->mProfiles.push_back(OGRE_NEW Profile(this, "OgreMaterial", + "Profile for rendering Ogre standard material")); + this->setActiveProfile("OgreMaterial"); +} + +////////////////////////////////////////////////// +void TerrainMaterial::setMaterialByName(const std::string &_materialname) +{ + this->materialName = _materialname; +} + +////////////////////////////////////////////////// +void TerrainMaterial::setGridSize(const unsigned int _size) +{ + if (_size == 0) + { + ignerr << "Unable to set a grid size of zero" << std::endl; + return; + } + + this->gridSize = _size; +} + +////////////////////////////////////////////////// +TerrainMaterial::Profile::Profile(Ogre::TerrainMaterialGenerator *_parent, + const Ogre::String &_name, const Ogre::String &_desc) + : Ogre::TerrainMaterialGenerator::Profile(_parent, _name, _desc) +{ +} + +////////////////////////////////////////////////// +TerrainMaterial::Profile::~Profile() +{ +} + +////////////////////////////////////////////////// +bool TerrainMaterial::Profile::isVertexCompressionSupported() const +{ + return false; +} + +////////////////////////////////////////////////// +Ogre::MaterialPtr TerrainMaterial::Profile::generate( + const Ogre::Terrain *_terrain) +{ + const Ogre::String& matName = _terrain->getMaterialName(); + + Ogre::MaterialPtr mat = + Ogre::MaterialManager::getSingleton().getByName(matName); + if (!mat.isNull()) + Ogre::MaterialManager::getSingleton().remove(matName); + + TerrainMaterial *parent = + dynamic_cast(getParent()); + + // Set Ogre material + mat = Ogre::MaterialManager::getSingleton().getByName(parent->materialName); + + // clone the material + mat = mat->clone(matName); + if (!mat->isLoaded()) + mat->load(); + + // size of grid in one direction + unsigned int gridWidth = + static_cast(std::sqrt(parent->gridSize)); + // factor to be applied to uv transformation: scale and translation + double factor = 1.0 / gridWidth; + // static counter to keep track which terrain slot we are currently in + static int gridCount = 0; + + for (unsigned int i = 0; i < mat->getNumTechniques(); ++i) + { + Ogre::Technique *tech = mat->getTechnique(i); + for (unsigned int j = 0; j < tech->getNumPasses(); ++j) + { + Ogre::Pass *pass = tech->getPass(j); + + // check if there is a fragment shader + if (!pass->hasFragmentProgram()) + continue; + + Ogre::GpuProgramParametersSharedPtr params = + pass->getFragmentProgramParameters(); + if (params.isNull()) + continue; + + // set up shadow split points in a way that is consistent with the + // default ogre terrain material generator + Ogre::PSSMShadowCameraSetup* pssm = + OgreRTShaderSystem::Instance()->PSSMShadowCameraSetup(); + unsigned int numTextures = + static_cast(pssm->getSplitCount()); + Ogre::Vector4 splitPoints; + const Ogre::PSSMShadowCameraSetup::SplitPointList& splitPointList = + pssm->getSplitPoints(); + // populate from split point 1 not 0, and include shadowFarDistance + for (unsigned int t = 0u; t < numTextures; ++t) + splitPoints[t] = splitPointList[t+1]; + params->setNamedConstant("pssmSplitPoints", splitPoints); + + // set up uv transform + double xTrans = static_cast(gridCount / gridWidth) * factor; + double yTrans = (gridWidth - 1 - (gridCount % gridWidth)) * factor; + // explicitly set all matrix elements to avoid uninitialized values + Ogre::Matrix4 uvTransform(factor, 0.0, 0.0, xTrans, + 0.0, factor, 0.0, yTrans, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0); + params->setNamedConstant("uvTransform", uvTransform); + } + } + gridCount++; + + // Get default pass + Ogre::Pass *p = mat->getTechnique(0)->getPass(0); + + // Add terrain's global normalmap to renderpass so the + // fragment program can find it. + Ogre::TextureUnitState *tu = p->createTextureUnitState(matName+"/nm"); + + Ogre::TexturePtr nmtx = _terrain->getTerrainNormalMap(); + tu->_setTexturePtr(nmtx); + + return mat; +} + +////////////////////////////////////////////////// +Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap( + const Ogre::Terrain *_terrain) +{ + return _terrain->_getCompositeMapMaterial(); +} + +////////////////////////////////////////////////// +void TerrainMaterial::Profile::setLightmapEnabled( + bool /*_enabled*/) +{ +} + +////////////////////////////////////////////////// +Ogre::uint8 TerrainMaterial::Profile::getMaxLayers( + const Ogre::Terrain * /*_terrain*/) const +{ + return 0; +} + +////////////////////////////////////////////////// +void TerrainMaterial::Profile::updateParams(const Ogre::MaterialPtr &/*_mat*/, + const Ogre::Terrain * /*_terrain*/) +{ +} + +////////////////////////////////////////////////// +void TerrainMaterial::Profile::updateParamsForCompositeMap( + const Ogre::MaterialPtr &/*_mat*/, const Ogre::Terrain * /*_terrain*/) +{ +} + +////////////////////////////////////////////////// +void TerrainMaterial::Profile::requestOptions(Ogre::Terrain *_terrain) +{ + _terrain->_setMorphRequired(true); + // enable global normal map + _terrain->_setNormalMapRequired(true); + _terrain->_setLightMapRequired(false); + _terrain->_setCompositeMapRequired(false); +} diff --git a/ogre/src/OgreScene.cc b/ogre/src/OgreScene.cc index 2a1f497d3..588ae92ef 100644 --- a/ogre/src/OgreScene.cc +++ b/ogre/src/OgreScene.cc @@ -20,26 +20,27 @@ #include "ignition/rendering/ogre/OgreArrowVisual.hh" #include "ignition/rendering/ogre/OgreAxisVisual.hh" #include "ignition/rendering/ogre/OgreCamera.hh" -#include "ignition/rendering/ogre/OgreDepthCamera.hh" #include "ignition/rendering/ogre/OgreConversions.hh" +#include "ignition/rendering/ogre/OgreDepthCamera.hh" #include "ignition/rendering/ogre/OgreGeometry.hh" #include "ignition/rendering/ogre/OgreGizmoVisual.hh" #include "ignition/rendering/ogre/OgreGpuRays.hh" #include "ignition/rendering/ogre/OgreGrid.hh" +#include "ignition/rendering/ogre/OgreHeightmap.hh" #include "ignition/rendering/ogre/OgreIncludes.hh" -#include "ignition/rendering/ogre/OgreText.hh" -#include "ignition/rendering/ogre/OgreMaterial.hh" -#include "ignition/rendering/ogre/OgreMarker.hh" #include "ignition/rendering/ogre/OgreLidarVisual.hh" #include "ignition/rendering/ogre/OgreLightVisual.hh" +#include "ignition/rendering/ogre/OgreMarker.hh" +#include "ignition/rendering/ogre/OgreMaterial.hh" #include "ignition/rendering/ogre/OgreMeshFactory.hh" #include "ignition/rendering/ogre/OgreParticleEmitter.hh" +#include "ignition/rendering/ogre/OgreRTShaderSystem.hh" #include "ignition/rendering/ogre/OgreRayQuery.hh" #include "ignition/rendering/ogre/OgreRenderEngine.hh" #include "ignition/rendering/ogre/OgreRenderTarget.hh" -#include "ignition/rendering/ogre/OgreRTShaderSystem.hh" #include "ignition/rendering/ogre/OgreScene.hh" #include "ignition/rendering/ogre/OgreStorage.hh" +#include "ignition/rendering/ogre/OgreText.hh" #include "ignition/rendering/ogre/OgreThermalCamera.hh" #include "ignition/rendering/ogre/OgreVisual.hh" #include "ignition/rendering/ogre/OgreWireBox.hh" @@ -513,6 +514,16 @@ MeshPtr OgreScene::CreateMeshImpl(unsigned int _id, const std::string &_name, return (result) ? mesh : nullptr; } +////////////////////////////////////////////////// +HeightmapPtr OgreScene::CreateHeightmapImpl(unsigned int _id, + const std::string &_name, const HeightmapDescriptor &_desc) +{ + OgreHeightmapPtr heightmap; + heightmap.reset(new OgreHeightmap(_desc)); + bool result = this->InitObject(heightmap, _id, _name); + return (result) ? heightmap : nullptr; +} + ////////////////////////////////////////////////// GridPtr OgreScene::CreateGridImpl(unsigned int _id, const std::string &_name) { diff --git a/ogre/src/OgreVisual.cc b/ogre/src/OgreVisual.cc index 13421b120..fd8f02c01 100644 --- a/ogre/src/OgreVisual.cc +++ b/ogre/src/OgreVisual.cc @@ -74,20 +74,18 @@ bool OgreVisual::AttachGeometry(GeometryPtr _geometry) return false; } + // Some geometries, like heightmaps, may not have an OgreObject Ogre::MovableObject *ogreObj = derived->OgreObject(); - if (!ogreObj) + if (ogreObj) { - ignerr << "Cannot attach a null geometry object" << std::endl; - return false; + // set user data for mouse queries + ogreObj->getUserObjectBindings().setUserAny( + Ogre::Any(this->Id())); + ogreObj->setVisibilityFlags(this->visibilityFlags); + this->ogreNode->attachObject(ogreObj); } - // set user data for mouse queries - ogreObj->getUserObjectBindings().setUserAny( - Ogre::Any(this->Id())); - ogreObj->setVisibilityFlags(this->visibilityFlags); - derived->SetParent(this->SharedThis()); - this->ogreNode->attachObject(ogreObj); return true; } @@ -108,7 +106,8 @@ bool OgreVisual::DetachGeometry(GeometryPtr _geometry) return false; } - this->ogreNode->detachObject(derived->OgreObject()); + if (nullptr != derived->OgreObject()) + this->ogreNode->detachObject(derived->OgreObject()); derived->SetParent(nullptr); return true; } diff --git a/ogre2/include/ignition/rendering/ogre2/Ogre2Scene.hh b/ogre2/include/ignition/rendering/ogre2/Ogre2Scene.hh index fa89cee9b..64ecc0386 100644 --- a/ogre2/include/ignition/rendering/ogre2/Ogre2Scene.hh +++ b/ogre2/include/ignition/rendering/ogre2/Ogre2Scene.hh @@ -188,6 +188,11 @@ namespace ignition const std::string &_name, const MeshDescriptor &_desc) override; + // Documentation inherited + protected: virtual HeightmapPtr CreateHeightmapImpl(unsigned int _id, + const std::string &_name, const HeightmapDescriptor &_desc) + override; + // Documentation inherited protected: virtual GridPtr CreateGridImpl(unsigned int _id, const std::string &_name) override; diff --git a/ogre2/src/Ogre2Scene.cc b/ogre2/src/Ogre2Scene.cc index d5ec3b13f..43816ec02 100644 --- a/ogre2/src/Ogre2Scene.cc +++ b/ogre2/src/Ogre2Scene.cc @@ -383,6 +383,16 @@ MeshPtr Ogre2Scene::CreateMeshImpl(unsigned int _id, return (result) ? mesh : nullptr; } +////////////////////////////////////////////////// +HeightmapPtr Ogre2Scene::CreateHeightmapImpl(unsigned int, + const std::string &, const HeightmapDescriptor &) +{ + ignerr << "Ogre 2 doesn't support heightmaps yet, see " << + "https://github.com/ignitionrobotics/ign-rendering/issues/187" + << std::endl; + return nullptr; +} + ////////////////////////////////////////////////// GridPtr Ogre2Scene::CreateGridImpl(unsigned int _id, const std::string &_name) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index be1ea5d97..dcfd6f2fc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -9,6 +9,8 @@ set_property( # "gtest_sources" variable. ign_get_libsources_and_unittests(sources gtest_sources) +add_subdirectory(base) + if (MSVC) # Warning #4251 is the "dll-interface" warning that tells you when types used # by a class are not being exported. These generated source files have private @@ -18,8 +20,6 @@ if (MSVC) set_source_files_properties(${sources} ${gtest_sources} COMPILE_FLAGS "/wd4251") endif() -add_subdirectory(base) - # Create the library target. ign_create_core_library(SOURCES ${sources} CXX_STANDARD 17) diff --git a/src/HeightmapDescriptor.cc b/src/HeightmapDescriptor.cc new file mode 100644 index 000000000..f10ca52cd --- /dev/null +++ b/src/HeightmapDescriptor.cc @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2020 Open Source Robotics Foundation + * + * 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. + * + */ + + +#include "ignition/rendering/HeightmapDescriptor.hh" + +using namespace ignition; +using namespace rendering; + +////////////////////////////////////////////////// +class ignition::rendering::HeightmapTexturePrivate +{ + /// \brief Texture size. + public: double size{1.0}; + + /// \brief Path to diffuse texture file. + public: std::string diffuse; + + /// \brief Path to normal map file. + public: std::string normal; +}; + +////////////////////////////////////////////////// +class ignition::rendering::HeightmapBlendPrivate +{ + /// \brief Minimum height to blend from. + public: double minHeight{0.0}; + + /// \brief Distance to blend. + public: double fadeDistance{0.0}; +}; + +////////////////////////////////////////////////// +class ignition::rendering::HeightmapDescriptorPrivate +{ + /// \brief Name used for caching + public: std::string name; + + /// \brief Contains heightfield data. + public: std::shared_ptr data{nullptr}; + + /// \brief Heightmap XYZ size in meters. + public: math::Vector3d size{1.0, 1.0, 1.0}; + + /// \brief Heightmap XYZ origin in meters. + public: math::Vector3d position{0.0, 0.0, 0.0}; + + /// \brief Flag that enables/disables the terrain paging + public: bool useTerrainPaging{false}; + + /// \brief Number of samples per heightmap datum. + public: unsigned int sampling{1u}; + + /// \brief Textures in this heightmap, in height order. + public: std::vector textures; + + /// \brief Blends in this heightmap, in height order. There should be one + /// less than textures. + public: std::vector blends; +}; + +////////////////////////////////////////////////// +HeightmapTexture::HeightmapTexture() : + dataPtr(std::make_unique()) +{ +} + +///////////////////////////////////////////////// +HeightmapTexture::~HeightmapTexture() +{ +} + +////////////////////////////////////////////////// +HeightmapTexture::HeightmapTexture(const HeightmapTexture &_texture) + : dataPtr(new HeightmapTexturePrivate(*_texture.dataPtr)) +{ +} + +////////////////////////////////////////////////// +HeightmapTexture::HeightmapTexture(HeightmapTexture &&_texture) noexcept + : dataPtr(std::exchange(_texture.dataPtr, nullptr)) +{ +} + +///////////////////////////////////////////////// +HeightmapTexture &HeightmapTexture::operator=( + const HeightmapTexture &_texture) +{ + return *this = HeightmapTexture(_texture); +} + +///////////////////////////////////////////////// +HeightmapTexture &HeightmapTexture::operator=(HeightmapTexture &&_texture) +{ + std::swap(this->dataPtr, _texture.dataPtr); + return *this; +} + +////////////////////////////////////////////////// +double HeightmapTexture::Size() const +{ + return this->dataPtr->size; +} + +////////////////////////////////////////////////// +void HeightmapTexture::SetSize(double _size) +{ + this->dataPtr->size = _size; +} + +////////////////////////////////////////////////// +std::string HeightmapTexture::Diffuse() const +{ + return this->dataPtr->diffuse; +} + +////////////////////////////////////////////////// +void HeightmapTexture::SetDiffuse(const std::string &_diffuse) +{ + this->dataPtr->diffuse = _diffuse; +} + +////////////////////////////////////////////////// +std::string HeightmapTexture::Normal() const +{ + return this->dataPtr->normal; +} + +////////////////////////////////////////////////// +void HeightmapTexture::SetNormal(const std::string &_normal) +{ + this->dataPtr->normal = _normal; +} + +////////////////////////////////////////////////// +HeightmapBlend::HeightmapBlend() : + dataPtr(std::make_unique()) +{ +} + + +///////////////////////////////////////////////// +HeightmapBlend::~HeightmapBlend() +{ +} + +////////////////////////////////////////////////// +HeightmapBlend::HeightmapBlend(const HeightmapBlend &_blend) + : dataPtr(new HeightmapBlendPrivate(*_blend.dataPtr)) +{ +} + +////////////////////////////////////////////////// +HeightmapBlend::HeightmapBlend(HeightmapBlend &&_blend) noexcept + : dataPtr(std::exchange(_blend.dataPtr, nullptr)) +{ +} + +///////////////////////////////////////////////// +HeightmapBlend &HeightmapBlend::operator=( + const HeightmapBlend &_blend) +{ + return *this = HeightmapBlend(_blend); +} + +///////////////////////////////////////////////// +HeightmapBlend &HeightmapBlend::operator=(HeightmapBlend &&_blend) +{ + std::swap(this->dataPtr, _blend.dataPtr); + return *this; +} +////////////////////////////////////////////////// +double HeightmapBlend::MinHeight() const +{ + return this->dataPtr->minHeight; +} + +////////////////////////////////////////////////// +void HeightmapBlend::SetMinHeight(double _minHeight) +{ + this->dataPtr->minHeight = _minHeight; +} + +////////////////////////////////////////////////// +double HeightmapBlend::FadeDistance() const +{ + return this->dataPtr->fadeDistance; +} + +////////////////////////////////////////////////// +void HeightmapBlend::SetFadeDistance(double _fadeDistance) +{ + this->dataPtr->fadeDistance = _fadeDistance; +} + +////////////////////////////////////////////////// +HeightmapDescriptor::HeightmapDescriptor() : + dataPtr(std::make_unique()) +{ +} + +///////////////////////////////////////////////// +HeightmapDescriptor::~HeightmapDescriptor() = default; + +////////////////////////////////////////////////// +HeightmapDescriptor::HeightmapDescriptor(const HeightmapDescriptor &_heightmap) + : dataPtr(new HeightmapDescriptorPrivate(*_heightmap.dataPtr)) +{ +} + +////////////////////////////////////////////////// +HeightmapDescriptor::HeightmapDescriptor(HeightmapDescriptor &&_heightmap) + noexcept + : dataPtr(std::exchange(_heightmap.dataPtr, nullptr)) +{ +} + +///////////////////////////////////////////////// +HeightmapDescriptor &HeightmapDescriptor::operator=( + const HeightmapDescriptor &_heightmap) +{ + return *this = HeightmapDescriptor(_heightmap); +} + +///////////////////////////////////////////////// +HeightmapDescriptor &HeightmapDescriptor::operator=( + HeightmapDescriptor &&_heightmap) +{ + std::swap(this->dataPtr, _heightmap.dataPtr); + return *this; +} + +////////////////////////////////////////////////// +const std::string &HeightmapDescriptor::Name() const +{ + return this->dataPtr->name; +} + +////////////////////////////////////////////////// +void HeightmapDescriptor::SetName(const std::string &_name) +{ + this->dataPtr->name = _name; +} + +////////////////////////////////////////////////// +std::shared_ptr HeightmapDescriptor::Data() const +{ + return this->dataPtr->data; +} + +////////////////////////////////////////////////// +void HeightmapDescriptor::SetData( + const std::shared_ptr &_data) +{ + this->dataPtr->data = _data; +} + +////////////////////////////////////////////////// +math::Vector3d HeightmapDescriptor::Size() const +{ + return this->dataPtr->size; +} + +////////////////////////////////////////////////// +void HeightmapDescriptor::SetSize(const math::Vector3d &_size) +{ + this->dataPtr->size = _size; +} + +////////////////////////////////////////////////// +math::Vector3d HeightmapDescriptor::Position() const +{ + return this->dataPtr->position; +} + +////////////////////////////////////////////////// +void HeightmapDescriptor::SetPosition(const math::Vector3d &_position) +{ + this->dataPtr->position = _position; +} + +////////////////////////////////////////////////// +bool HeightmapDescriptor::UseTerrainPaging() const +{ + return this->dataPtr->useTerrainPaging; +} + +////////////////////////////////////////////////// +void HeightmapDescriptor::SetUseTerrainPaging(bool _useTerrainPaging) +{ + this->dataPtr->useTerrainPaging = _useTerrainPaging; +} + +////////////////////////////////////////////////// +unsigned int HeightmapDescriptor::Sampling() const +{ + return this->dataPtr->sampling; +} + +////////////////////////////////////////////////// +void HeightmapDescriptor::SetSampling(unsigned int _sampling) +{ + this->dataPtr->sampling = _sampling; +} + +///////////////////////////////////////////////// +uint64_t HeightmapDescriptor::TextureCount() const +{ + return this->dataPtr->textures.size(); +} + +///////////////////////////////////////////////// +const HeightmapTexture *HeightmapDescriptor::TextureByIndex(uint64_t _index) + const +{ + if (_index < this->dataPtr->textures.size()) + return &this->dataPtr->textures[_index]; + return nullptr; +} + +///////////////////////////////////////////////// +void HeightmapDescriptor::AddTexture(const HeightmapTexture &_texture) +{ + this->dataPtr->textures.push_back(_texture); +} + +///////////////////////////////////////////////// +uint64_t HeightmapDescriptor::BlendCount() const +{ + return this->dataPtr->blends.size(); +} + +///////////////////////////////////////////////// +const HeightmapBlend *HeightmapDescriptor::BlendByIndex(uint64_t _index) const +{ + if (_index < this->dataPtr->blends.size()) + return &this->dataPtr->blends[_index]; + return nullptr; +} + +///////////////////////////////////////////////// +void HeightmapDescriptor::AddBlend(const HeightmapBlend &_blend) +{ + this->dataPtr->blends.push_back(_blend); +} diff --git a/src/Heightmap_TEST.cc b/src/Heightmap_TEST.cc new file mode 100644 index 000000000..b7f17387a --- /dev/null +++ b/src/Heightmap_TEST.cc @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2020 Open Source Robotics Foundation + * + * 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. + * +*/ + +#include + +#include +#include +#include + +#include "test_config.h" // NOLINT(build/include) +#include "ignition/rendering/RenderEngine.hh" +#include "ignition/rendering/RenderingIface.hh" +#include "ignition/rendering/Heightmap.hh" +#include "ignition/rendering/Scene.hh" + +using namespace ignition; +using namespace rendering; + +class HeightmapTest : public testing::Test, + public testing::WithParamInterface +{ + // Documentation inherited + protected: void SetUp() override + { + common::Console::SetVerbosity(4); + } + + /// \brief Path to test media files. + public: const std::string TEST_MEDIA_PATH{ + common::joinPaths(std::string(PROJECT_SOURCE_PATH), + "test", "media")}; +}; + +///////////////////////////////////////////////// +// ogre1 not supported on Windows +TEST_P(HeightmapTest, IGN_UTILS_TEST_DISABLED_ON_WIN32(Heightmap)) +{ + std::string renderEngine{this->GetParam()}; + if (renderEngine != "ogre") + { + igndbg << "Heightmap not supported yet in rendering engine: " + << renderEngine << std::endl; + return; + } + + auto engine = rendering::engine(renderEngine); + if (!engine) + { + igndbg << "Engine '" << renderEngine + << "' is not supported" << std::endl; + return; + } + + auto scene = engine->CreateScene("scene"); + ASSERT_NE(nullptr, scene); + + // Heightmap data + auto heightImage = common::joinPaths(TEST_MEDIA_PATH, "heightmap_bowl.png"); + math::Vector3d size{17, 17, 10}; + math::Vector3d position{1, 2, 3}; + auto textureImage = common::joinPaths(TEST_MEDIA_PATH, "materials", + "textures", "texture.png"); + auto normalImage = common::joinPaths(TEST_MEDIA_PATH, "materials", + "textures", "flat_normal.png"); + + auto data = std::make_shared(); + data->Load(heightImage); + + EXPECT_EQ(heightImage, data->Filename()); + + HeightmapDescriptor desc; + desc.SetData(data); + desc.SetSize(size); + desc.SetPosition(position); + desc.SetUseTerrainPaging(true); + desc.SetSampling(4u); + + HeightmapTexture textureA; + textureA.SetSize(0.5); + textureA.SetDiffuse(textureImage); + textureA.SetNormal(normalImage); + desc.AddTexture(textureA); + + HeightmapBlend blendA; + blendA.SetMinHeight(2.0); + blendA.SetFadeDistance(5.0); + desc.AddBlend(blendA); + + HeightmapTexture textureB; + textureB.SetSize(0.5); + textureB.SetDiffuse(textureImage); + textureB.SetNormal(normalImage); + desc.AddTexture(textureB); + + HeightmapBlend blendB; + blendB.SetMinHeight(4.0); + blendB.SetFadeDistance(5.0); + desc.AddBlend(blendB); + + HeightmapTexture textureC; + textureC.SetSize(0.5); + textureC.SetDiffuse(textureImage); + textureC.SetNormal(normalImage); + desc.AddTexture(textureC); + + auto heightmap = scene->CreateHeightmap(desc); + ASSERT_NE(nullptr, heightmap); + + EXPECT_NE(nullptr, heightmap->Descriptor().Data()); + EXPECT_EQ(size, heightmap->Descriptor().Size()); + EXPECT_EQ(position, heightmap->Descriptor().Position()); + EXPECT_TRUE(heightmap->Descriptor().UseTerrainPaging()); + EXPECT_EQ(4u, heightmap->Descriptor().Sampling()); + + EXPECT_EQ(3u, heightmap->Descriptor().TextureCount()); + for (auto i = 0u; i < heightmap->Descriptor().TextureCount(); ++i) + { + auto texture = heightmap->Descriptor().TextureByIndex(i); + ASSERT_NE(nullptr, texture); + EXPECT_EQ(textureImage, texture->Diffuse()); + EXPECT_EQ(normalImage, texture->Normal()); + EXPECT_DOUBLE_EQ(0.5, texture->Size()); + } + + ASSERT_EQ(2u, heightmap->Descriptor().BlendCount()); + + auto blend = heightmap->Descriptor().BlendByIndex(0); + ASSERT_NE(nullptr, blend); + EXPECT_DOUBLE_EQ(2.0, blend->MinHeight()); + EXPECT_DOUBLE_EQ(5.0, blend->FadeDistance()); + + blend = heightmap->Descriptor().BlendByIndex(1); + ASSERT_NE(nullptr, blend); + EXPECT_DOUBLE_EQ(4.0, blend->MinHeight()); + EXPECT_DOUBLE_EQ(5.0, blend->FadeDistance()); + + // Add to a visual + auto vis = scene->CreateVisual(); + EXPECT_EQ(0u, vis->GeometryCount()); + + vis->AddGeometry(heightmap); + EXPECT_EQ(1u, vis->GeometryCount()); + EXPECT_TRUE(vis->HasGeometry(heightmap)); + EXPECT_EQ(heightmap, vis->GeometryByIndex(0)); + + scene->RootVisual()->AddChild(vis); + + // Clean up + engine->DestroyScene(scene); + rendering::unloadEngine(engine->Name()); +} + +////////////////////////////////////////////////// +TEST_P(HeightmapTest, MoveConstructor) +{ + HeightmapDescriptor descriptor; + descriptor.SetSize({0.1, 0.2, 0.3}); + descriptor.SetPosition({0.5, 0.6, 0.7}); + descriptor.SetUseTerrainPaging(true); + descriptor.SetSampling(123u); + + HeightmapDescriptor descriptor2(std::move(descriptor)); + EXPECT_EQ(ignition::math::Vector3d(0.1, 0.2, 0.3), descriptor2.Size()); + EXPECT_EQ(ignition::math::Vector3d(0.5, 0.6, 0.7), descriptor2.Position()); + EXPECT_TRUE(descriptor2.UseTerrainPaging()); + EXPECT_EQ(123u, descriptor2.Sampling()); + + HeightmapTexture texture; + texture.SetSize(123.456); + texture.SetDiffuse("diffuse"); + texture.SetNormal("normal"); + + HeightmapTexture texture2(std::move(texture)); + EXPECT_DOUBLE_EQ(123.456, texture2.Size()); + EXPECT_EQ("diffuse", texture2.Diffuse()); + EXPECT_EQ("normal", texture2.Normal()); + + HeightmapBlend blend; + blend.SetMinHeight(123.456); + blend.SetFadeDistance(456.123); + + HeightmapBlend blend2(std::move(blend)); + EXPECT_DOUBLE_EQ(123.456, blend2.MinHeight()); + EXPECT_DOUBLE_EQ(456.123, blend2.FadeDistance()); +} + +///////////////////////////////////////////////// +TEST_P(HeightmapTest, CopyConstructor) +{ + HeightmapDescriptor descriptor; + descriptor.SetSize({0.1, 0.2, 0.3}); + descriptor.SetPosition({0.5, 0.6, 0.7}); + descriptor.SetUseTerrainPaging(true); + descriptor.SetSampling(123u); + + HeightmapDescriptor descriptor2(descriptor); + EXPECT_EQ(ignition::math::Vector3d(0.1, 0.2, 0.3), descriptor2.Size()); + EXPECT_EQ(ignition::math::Vector3d(0.5, 0.6, 0.7), descriptor2.Position()); + EXPECT_TRUE(descriptor2.UseTerrainPaging()); + EXPECT_EQ(123u, descriptor2.Sampling()); + + HeightmapTexture texture; + texture.SetSize(123.456); + texture.SetDiffuse("diffuse"); + texture.SetNormal("normal"); + + HeightmapTexture texture2(texture); + EXPECT_DOUBLE_EQ(123.456, texture2.Size()); + EXPECT_EQ("diffuse", texture2.Diffuse()); + EXPECT_EQ("normal", texture2.Normal()); + + HeightmapBlend blend; + blend.SetMinHeight(123.456); + blend.SetFadeDistance(456.123); + + HeightmapBlend blend2(blend); + EXPECT_DOUBLE_EQ(123.456, blend2.MinHeight()); + EXPECT_DOUBLE_EQ(456.123, blend2.FadeDistance()); +} + +///////////////////////////////////////////////// +TEST_P(HeightmapTest, CopyAssignmentOperator) +{ + HeightmapDescriptor descriptor; + descriptor.SetSize({0.1, 0.2, 0.3}); + descriptor.SetPosition({0.5, 0.6, 0.7}); + descriptor.SetUseTerrainPaging(true); + descriptor.SetSampling(123u); + + HeightmapDescriptor descriptor2; + descriptor2 = descriptor; + EXPECT_EQ(ignition::math::Vector3d(0.1, 0.2, 0.3), descriptor2.Size()); + EXPECT_EQ(ignition::math::Vector3d(0.5, 0.6, 0.7), descriptor2.Position()); + EXPECT_TRUE(descriptor2.UseTerrainPaging()); + EXPECT_EQ(123u, descriptor2.Sampling()); + + HeightmapTexture texture; + texture.SetSize(123.456); + texture.SetDiffuse("diffuse"); + texture.SetNormal("normal"); + + HeightmapTexture texture2; + texture2 = texture; + EXPECT_DOUBLE_EQ(123.456, texture2.Size()); + EXPECT_EQ("diffuse", texture2.Diffuse()); + EXPECT_EQ("normal", texture2.Normal()); + + HeightmapBlend blend; + blend.SetMinHeight(123.456); + blend.SetFadeDistance(456.123); + + HeightmapBlend blend2; + blend2 = blend; + EXPECT_DOUBLE_EQ(123.456, blend2.MinHeight()); + EXPECT_DOUBLE_EQ(456.123, blend2.FadeDistance()); +} + +///////////////////////////////////////////////// +TEST_P(HeightmapTest, MoveAssignmentOperator) +{ + HeightmapDescriptor descriptor; + descriptor.SetSize({0.1, 0.2, 0.3}); + descriptor.SetPosition({0.5, 0.6, 0.7}); + descriptor.SetUseTerrainPaging(true); + descriptor.SetSampling(123u); + + HeightmapDescriptor descriptor2; + descriptor2 = std::move(descriptor); + EXPECT_EQ(ignition::math::Vector3d(0.1, 0.2, 0.3), descriptor2.Size()); + EXPECT_EQ(ignition::math::Vector3d(0.5, 0.6, 0.7), descriptor2.Position()); + EXPECT_TRUE(descriptor2.UseTerrainPaging()); + EXPECT_EQ(123u, descriptor2.Sampling()); + + HeightmapTexture texture; + texture.SetSize(123.456); + texture.SetDiffuse("diffuse"); + texture.SetNormal("normal"); + + HeightmapTexture texture2; + texture2 = std::move(texture); + EXPECT_DOUBLE_EQ(123.456, texture2.Size()); + EXPECT_EQ("diffuse", texture2.Diffuse()); + EXPECT_EQ("normal", texture2.Normal()); + + HeightmapBlend blend; + blend.SetMinHeight(123.456); + blend.SetFadeDistance(456.123); + + HeightmapBlend blend2; + blend2 = std::move(blend); + EXPECT_DOUBLE_EQ(123.456, blend2.MinHeight()); + EXPECT_DOUBLE_EQ(456.123, blend2.FadeDistance()); +} + +///////////////////////////////////////////////// +TEST_P(HeightmapTest, CopyAssignmentAfterMove) +{ + HeightmapDescriptor descriptor1; + descriptor1.SetSampling(123u); + + HeightmapDescriptor descriptor2; + descriptor2.SetSampling(456u); + + // This is similar to what std::swap does except it uses std::move for each + // assignment + HeightmapDescriptor tmp = std::move(descriptor1); + descriptor1 = descriptor2; + descriptor2 = tmp; + + EXPECT_EQ(456u, descriptor1.Sampling()); + EXPECT_EQ(123u, descriptor2.Sampling()); + + HeightmapTexture texture1; + texture1.SetSize(123.456); + + HeightmapTexture texture2; + texture2.SetSize(456.123); + + HeightmapTexture tmpTexture = std::move(texture1); + texture1 = texture2; + texture2 = tmpTexture; + + EXPECT_DOUBLE_EQ(456.123, texture1.Size()); + EXPECT_DOUBLE_EQ(123.456, texture2.Size()); + + HeightmapBlend blend1; + blend1.SetMinHeight(123.456); + + HeightmapBlend blend2; + blend2.SetMinHeight(456.123); + + HeightmapBlend tmpBlend = std::move(blend1); + blend1 = blend2; + blend2 = tmpBlend; + + EXPECT_DOUBLE_EQ(456.123, blend1.MinHeight()); + EXPECT_DOUBLE_EQ(123.456, blend2.MinHeight()); +} + +// TODO(anyone) Running test with Ogre1. Update once Ogre2 is supported. +// https://github.com/ignitionrobotics/ign-rendering/issues/187 +INSTANTIATE_TEST_CASE_P(Heightmap, HeightmapTest, + ::testing::ValuesIn({"ogre"}), + ignition::rendering::PrintToStringParam()); + +///////////////////////////////////////////////// +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/base/BaseScene.cc b/src/base/BaseScene.cc index 15700a3fe..43e481d51 100644 --- a/src/base/BaseScene.cc +++ b/src/base/BaseScene.cc @@ -1042,6 +1042,14 @@ MeshPtr BaseScene::CreateMesh(const MeshDescriptor &_desc) return this->CreateMeshImpl(objId, objName, _desc); } +////////////////////////////////////////////////// +HeightmapPtr BaseScene::CreateHeightmap(const HeightmapDescriptor &_desc) +{ + unsigned int objId = this->CreateObjectId(); + std::string objName = this->CreateObjectName(objId, "Heightmap"); + return this->CreateHeightmapImpl(objId, objName, _desc); +} + ////////////////////////////////////////////////// GridPtr BaseScene::CreateGrid() { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b40c9d640..d48c661df 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,7 +9,11 @@ configure_file (test_config.h.in ${PROJECT_BINARY_DIR}/test_config.h) # Build gtest add_library(gtest STATIC gtest/src/gtest-all.cc) add_library(gtest_main STATIC gtest/src/gtest_main.cc) -target_link_libraries(gtest_main gtest) +target_link_libraries( + gtest_main + gtest + ignition-cmake${IGN_CMAKE_VER}::utilities +) set(GTEST_LIBRARY "${PROJECT_BINARY_DIR}/test/libgtest.a") set(GTEST_MAIN_LIBRARY "${PROJECT_BINARY_DIR}/test/libgtest_main.a") diff --git a/test/media/heightmap_bowl.png b/test/media/heightmap_bowl.png new file mode 100644 index 000000000..52df5fcd0 Binary files /dev/null and b/test/media/heightmap_bowl.png differ diff --git a/test/media/materials/textures/flat_normal.png b/test/media/materials/textures/flat_normal.png new file mode 100644 index 000000000..c6590fb93 Binary files /dev/null and b/test/media/materials/textures/flat_normal.png differ diff --git a/tutorials.md.in b/tutorials.md.in index 87bd3a259..5829f6c6a 100644 --- a/tutorials.md.in +++ b/tutorials.md.in @@ -24,6 +24,7 @@ Ignition @IGN_DESIGNATION_CAP@ library and how to use the library effectively. 10. \subpage particles "Particles" 11. \subpage render_order "Render Order" 12. \subpage transform_fbx_to_dae "Transform FBX to Collada in Blender" +13. \subpage heightmap "Heightmap" ## License diff --git a/tutorials/21_heightmap.md b/tutorials/21_heightmap.md new file mode 100644 index 000000000..181f5f489 --- /dev/null +++ b/tutorials/21_heightmap.md @@ -0,0 +1,56 @@ +\page heightmap Heightmap + +This example shows how to add a heigntmap to the scene. + +It loads 2 different heightmaps with different parameters. + +## Compile and run the example + +Clone the source code, create a build directory and use `cmake` and `make` to compile the code: + +```{.sh} +git clone https://github.com/ignitionrobotics/ign-rendering +cd ign-rendering/examples/heightmap +mkdir build +cd build +cmake .. +make +``` +Execute the example: + +```{.sh} +./heightmap +``` + +You'll see: + +```{.sh} +[Msg] Loading plugin [ignition-rendering5-ogre] +[Msg] Loading heightmap: scene::Heightmap(65528) +[Msg] Heightmap loaded. Process took 217 ms. +=============================== + TAB - Switch render engines + ESC - Exit +=============================== +``` +@image html img/heightmaps.png + +## Code + +A heightmap is a terrain defined by a 2D grid of height values. This demo +loads the heights from a grayscale image, where the color black represents +the lowest point, and white represents the highest. + +The heightmap's information is stored in the `HeightmapDescriptor` class. +In addition to the height data, the heightmap descriptor also exposes +functionality such as: + +* Its size in meters in XYZ space. +* The position of its origin in the world. +* The textures to use according to the height. +* How to blend these textures. + +Here's the snippet of code from `examples/heightmap/Main.cc` that adds a heightmap +to the scene: + +\snippet examples/heightmap/Main.cc create a heightmap diff --git a/tutorials/img/heightmaps.png b/tutorials/img/heightmaps.png new file mode 100644 index 000000000..14e173a53 Binary files /dev/null and b/tutorials/img/heightmaps.png differ