// // Copyright (c) 2008-2016 the Urho3D project. // Copyright (c) 2014-2016, THUNDERBEAST GAMES LLC All rights reserved // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Water.h" #include Water::Water(Context* context) : Sample(context) { } void Water::Start() { // Execute base class startup Sample::Start(); // Create the scene content CreateScene(); // Create the UI content CreateInstructions(); // Setup the viewport for displaying the scene SetupViewport(); // Hook up to the frame update event SubscribeToEvents(); // Set the mouse mode to use in the sample Sample::InitMouseMode(MM_RELATIVE); } void Water::CreateScene() { ResourceCache* cache = GetSubsystem(); scene_ = new Scene(context_); // Create octree, use default volume (-1000, -1000, -1000) to (1000, 1000, 1000) scene_->CreateComponent(); // Create a Zone component for ambient lighting & fog control Node* zoneNode = scene_->CreateChild("Zone"); Zone* zone = zoneNode->CreateComponent(); zone->SetBoundingBox(BoundingBox(-1000.0f, 1000.0f)); zone->SetAmbientColor(Color(0.15f, 0.15f, 0.15f)); zone->SetFogColor(Color(1.0f, 1.0f, 1.0f)); zone->SetFogStart(500.0f); zone->SetFogEnd(750.0f); // Create a directional light to the world. Enable cascaded shadows on it Node* lightNode = scene_->CreateChild("DirectionalLight"); lightNode->SetDirection(Vector3(0.6f, -1.0f, 0.8f)); Light* light = lightNode->CreateComponent(); light->SetLightType(LIGHT_DIRECTIONAL); light->SetCastShadows(true); light->SetShadowBias(BiasParameters(0.00025f, 0.5f)); light->SetShadowCascade(CascadeParameters(10.0f, 50.0f, 200.0f, 0.0f, 0.8f)); light->SetSpecularIntensity(0.5f); // Apply slightly overbright lighting to match the skybox light->SetColor(Color(1.2f, 1.2f, 1.2f)); // Create skybox. The Skybox component is used like StaticModel, but it will be always located at the camera, giving the // illusion of the box planes being far away. Use just the ordinary Box model and a suitable material, whose shader will // generate the necessary 3D texture coordinates for cube mapping Node* skyNode = scene_->CreateChild("Sky"); skyNode->SetScale(500.0f); // The scale actually does not matter Skybox* skybox = skyNode->CreateComponent(); skybox->SetModel(cache->GetResource("Models/Box.mdl")); skybox->SetMaterial(cache->GetResource("Materials/Skybox.xml")); // Create heightmap terrain Node* terrainNode = scene_->CreateChild("Terrain"); terrainNode->SetPosition(Vector3(0.0f, 0.0f, 0.0f)); Terrain* terrain = terrainNode->CreateComponent(); terrain->SetPatchSize(64); terrain->SetSpacing(Vector3(2.0f, 0.5f, 2.0f)); // Spacing between vertices and vertical resolution of the height map terrain->SetSmoothing(true); terrain->SetHeightMap(cache->GetResource("Textures/HeightMap.png")); terrain->SetMaterial(cache->GetResource("Materials/Terrain.xml")); // The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all // terrain patches and other objects behind it terrain->SetOccluder(true); // Create 1000 boxes in the terrain. Always face outward along the terrain normal unsigned NUM_OBJECTS = 1000; for (unsigned i = 0; i < NUM_OBJECTS; ++i) { Node* objectNode = scene_->CreateChild("Box"); Vector3 position(Random(2000.0f) - 1000.0f, 0.0f, Random(2000.0f) - 1000.0f); position.y_ = terrain->GetHeight(position) + 2.25f; objectNode->SetPosition(position); // Create a rotation quaternion from up vector to terrain normal objectNode->SetRotation(Quaternion(Vector3(0.0f, 1.0f, 0.0f), terrain->GetNormal(position))); objectNode->SetScale(5.0f); StaticModel* object = objectNode->CreateComponent(); object->SetModel(cache->GetResource("Models/Box.mdl")); object->SetMaterial(cache->GetResource("Materials/Stone.xml")); object->SetCastShadows(true); } // Create a water plane object that is as large as the terrain waterNode_ = scene_->CreateChild("Water"); waterNode_->SetScale(Vector3(2048.0f, 1.0f, 2048.0f)); waterNode_->SetPosition(Vector3(0.0f, 5.0f, 0.0f)); StaticModel* water = waterNode_->CreateComponent(); water->SetModel(cache->GetResource("Models/Plane.mdl")); water->SetMaterial(cache->GetResource("Materials/Water.xml")); // Set a different viewmask on the water plane to be able to hide it from the reflection camera water->SetViewMask(0x80000000); // Create the camera. Set far clip to match the fog. Note: now we actually create the camera node outside // the scene, because we want it to be unaffected by scene load / save cameraNode_ = new Node(context_); Camera* camera = cameraNode_->CreateComponent(); camera->SetFarClip(750.0f); // Set an initial position for the camera scene node above the ground cameraNode_->SetPosition(Vector3(0.0f, 7.0f, -20.0f)); } void Water::CreateInstructions() { SimpleCreateInstructions("Use WASD keys and mouse/touch to move"); } void Water::SetupViewport() { Graphics* graphics = GetSubsystem(); Renderer* renderer = GetSubsystem(); ResourceCache* cache = GetSubsystem(); // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen SharedPtr viewport(new Viewport(context_, scene_, cameraNode_->GetComponent())); renderer->SetViewport(0, viewport); // Create a mathematical plane to represent the water in calculations waterPlane_ = Plane(waterNode_->GetWorldRotation() * Vector3(0.0f, 1.0f, 0.0f), waterNode_->GetWorldPosition()); // Create a downward biased plane for reflection view clipping. Biasing is necessary to avoid too aggressive clipping waterClipPlane_ = Plane(waterNode_->GetWorldRotation() * Vector3(0.0f, 1.0f, 0.0f), waterNode_->GetWorldPosition() - Vector3(0.0f, 0.1f, 0.0f)); // Create camera for water reflection // It will have the same farclip and position as the main viewport camera, but uses a reflection plane to modify // its position when rendering reflectionCameraNode_ = cameraNode_->CreateChild(); Camera* reflectionCamera = reflectionCameraNode_->CreateComponent(); reflectionCamera->SetFarClip(750.0); reflectionCamera->SetViewMask(0x7fffffff); // Hide objects with only bit 31 in the viewmask (the water plane) reflectionCamera->SetAutoAspectRatio(false); reflectionCamera->SetUseReflection(true); reflectionCamera->SetReflectionPlane(waterPlane_); reflectionCamera->SetUseClipping(true); // Enable clipping of geometry behind water plane reflectionCamera->SetClipPlane(waterClipPlane_); // The water reflection texture is rectangular. Set reflection camera aspect ratio to match reflectionCamera->SetAspectRatio((float)graphics->GetWidth() / (float)graphics->GetHeight()); // View override flags could be used to optimize reflection rendering. For example disable shadows //reflectionCamera->SetViewOverrideFlags(VO_DISABLE_SHADOWS); // Create a texture and setup viewport for water reflection. Assign the reflection texture to the diffuse // texture unit of the water material int texSize = 1024; SharedPtr renderTexture(new Texture2D(context_)); renderTexture->SetSize(texSize, texSize, Graphics::GetRGBFormat(), TEXTURE_RENDERTARGET); renderTexture->SetFilterMode(FILTER_BILINEAR); RenderSurface* surface = renderTexture->GetRenderSurface(); SharedPtr rttViewport(new Viewport(context_, scene_, reflectionCamera)); surface->SetViewport(0, rttViewport); Material* waterMat = cache->GetResource("Materials/Water.xml"); waterMat->SetTexture(TU_DIFFUSE, renderTexture); } void Water::SubscribeToEvents() { // Subscribe HandleUpdate() function for processing update events SubscribeToEvent(E_UPDATE, ATOMIC_HANDLER(Water, HandleUpdate)); } void Water::MoveCamera(float timeStep) { Input* input = GetSubsystem(); // Movement speed as world units per second const float MOVE_SPEED = 20.0f; // Mouse sensitivity as degrees per pixel const float MOUSE_SENSITIVITY = 0.1f; // Use this frame's mouse motion to adjust camera node yaw and pitch. Clamp the pitch between -90 and 90 degrees IntVector2 mouseMove = input->GetMouseMove(); yaw_ += MOUSE_SENSITIVITY * mouseMove.x_; pitch_ += MOUSE_SENSITIVITY * mouseMove.y_; pitch_ = Clamp(pitch_, -90.0f, 90.0f); // Construct new orientation for the camera scene node from yaw and pitch. Roll is fixed to zero cameraNode_->SetRotation(Quaternion(pitch_, yaw_, 0.0f)); // Read WASD keys and move the camera scene node to the corresponding direction if they are pressed if (input->GetKeyDown(KEY_W)) cameraNode_->Translate(Vector3::FORWARD * MOVE_SPEED * timeStep); if (input->GetKeyDown(KEY_S)) cameraNode_->Translate(Vector3::BACK * MOVE_SPEED * timeStep); if (input->GetKeyDown(KEY_A)) cameraNode_->Translate(Vector3::LEFT * MOVE_SPEED * timeStep); if (input->GetKeyDown(KEY_D)) cameraNode_->Translate(Vector3::RIGHT * MOVE_SPEED * timeStep); // In case resolution has changed, adjust the reflection camera aspect ratio Graphics* graphics = GetSubsystem(); Camera* reflectionCamera = reflectionCameraNode_->GetComponent(); reflectionCamera->SetAspectRatio((float)graphics->GetWidth() / (float)graphics->GetHeight()); } void Water::HandleUpdate(StringHash eventType, VariantMap& eventData) { using namespace Update; // Take the frame time step, which is stored as a float float timeStep = eventData[P_TIMESTEP].GetFloat(); // Move the camera, scale movement with time step MoveCamera(timeStep); }