Chapter 4 Let There Be Light Sample Chapter
Chapter 4 Let There Be Light Sample Chapter
Alex Peterson is a graphics enthusiast with a background in game programming. His work with the Ogre engine is primarily due to programming a universe size game engine, a space skybox creator called Spacescape, and most recently, mobile games. Though his current life is filled with his family and running a business, he makes it a point be active musically and spiritually. He aims to promote his faith in God through his work to serve others, whether it is by fueling their creativity, entertaining them, or educating them. You . can find Alex online at I would like to thank my Father, my family, my wife Lydia, the Ogre development team, Steve Streeting, the Ogre forum moderators, Sean O'Neil, Chris, Ava Barneys, and all the kind people who have helped me be a part of this work. Thank you.
Creating weather controls Creating lights Creating dynamic effects Managing particle systems Managing shadows
Introduction
In this chapter, we'll explore some of the dynamic features of Ogre, such as particle systems, lights, and shadows. All of the dynamic features that we show you in this chapter can be manipulated with code to increase the realism of the effects or the artistic quality. Particle systems, in particular, are designed to change dynamically over time to achieve effects, such as sparks, explosions, or even a waterfall. Similarly, we can manipulate a light dynamically if we want to simulate the ickering of a re.
Getting ready
Add the sounds folder, which contains nature sounds, such as rain and thunder, in your media folder. To follow along with this recipe, open the solution located in the Recipes/Chapter04 folder in the code bundle available on the Packt website.
How to do it...
1. First, create a new Ogre MFC application named WeatherConditions, by following the Creating an MFC Ogre application recipe, in Chapter 1, Delving Deep into Application Design. 2. Next, create a SAPI voice.
m_cpVoice.CoCreateInstance(CLSID_SpVoice);
3. Create a submenu named Weather Control in the main menu. Then, add commands: Rain, Snow, Fog, Sky, and Sun to the submenu. For this recipe, we will only be implementing the rain functionality, but it will be easy to complete the other controls, once you see how the rain is implemented.
4. Next, add an event handler to the Rain submenu item using the Event Handler Wizard.
void CWeatherControlView::OnWeatherControlRain() { Ogre::SceneNode *RainNode = NULL; if (!m_SceneManager->hasParticleSystem("Rain")) { m_Rain = m_SceneManager->createParticleSystem("Rain", "Examples/Rain"); if (m_Rain != NULL) { RainNode = m_SceneManager->
108
Chapter 4 getRootSceneNode()->createChildSceneNode("RainNode"); RainNode->attachObject(m_Rain); m_Rain->setVisible(false); } } if (m_RainControlDlg == NULL) { m_RainControlDlg = new CRainControlDlg(); m_RainControlDlg->Create(IDD_RAIN_CONTROL); } m_RainControlDlg->ShowWindow(SW_SHOW); }
5. In the Rain submenu event handler, we create the rain particle system and the Rain Control dialog-box, and then show it. 6. Create the Rain Control dialog-box using the Dialog Editor.
Using this dialog-box, we can start and stop the rain, control particle dimensions, and enable or disable rain sounds. 6. Now that we've created the dialog-box, we need to add the event handler code for the dialog-box controls. Add a message handler to the Rain Control dialog-box called OnHScroll that handles the WM_HSCROLL message. The WM_HSCROLL message is sent when a click is detected in a horizontal scroll bar. Inside the OnHScroll, we simply check the position of the particle width and height controls, and modify the particle system with the updated values.
void CRainControlDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { CMainFrame *MainFrame = (CMainFrame *)(( CWeatherControlApp*)AfxGetApp())->GetMainWnd();
109
7.
Next, add an event handler named OnClickAction to the Start/Stop button to handle the click event. Inside OnClickAction, we toggle the rain visibility and rain sounds.
View->m_Rain->setVisible(!View->m_Rain->getVisible()); if (View->m_Rain->getVisible()) { m_Action.SetWindowTextA("Stop"); View->SetTimer(ID_RAIN_TIMER, 1, 0); } else { m_Action.SetWindowTextA("Start"); View->KillTimer(ID_RAIN_TIMER); WeatherControlApp->m_cpVoice->Pause(); }
We also start or stop the ID_RAIN_TIMER to toggle rain sounds in our OnTimer() function that gets called every time we receive a timer message. 8. To handle the timer messages, add an ON_WM_TIMER message handler to CWeatherControlView, and name it OnTimer. In the OnTimer member function, we will play rain sounds and render the scene when we receive the ID_RAIN_TIMER timer event.
SoundPath += L"\\..\\..\\media\\sounds\\rain\\rain storm.wav"; CWeatherControlApp* WeatherControlApp = (CWeatherControlApp*) AfxGetApp(); CComPtr<ISpVoice> Voice = WeatherControlApp->m_cpVoice; CComPtr<ISpStream> cpWavStream; switch (nIDEvent) { case ID_RAIN_TIMER: if (m_RainControlDlg != NULL){ if (m_RainControlDlg->m_PlaySound && m_Rain->getVisible()) {
110
Chapter 4 SPBindToFile(SoundPath, SPFM_OPEN_READONLY, &cpWavStream); Voice->Resume(); Voice->SpeakStream(cpWavStream, SPF_ASYNC, NULL); } else { Voice->Pause(); } } Root->renderOneFrame();
The SPBindToFile function binds the audio stream to the specied le, and SpeakStream plays the contents of the stream. In our case, when we receive an ID_RAIN_TIMER event, we play the rain sound.
How it works...
The settings for our Examples/Rain particle system reside in the media/particle/ Examples.particle le. The .particle le is just a text le and the settings are detailed in the Ogre online manual:
particle_system Examples/Rain { material Examples/Droplet particle_width 20 particle_height 100 cull_each true quota 10000 // Make common direction straight down (faster than self oriented) billboard_type oriented_common common_direction 0 -1 0 // Area emitter emitter Box { angle emission_rate time_to_live direction velocity width height depth } // Gravity
111
The rain particle effect uses a box emitter, so all the particles originate from a at 1000x1000 box. Note that we use the Y-axis as the up and down axis in our example, and our material is the Examples/Droplet material found in the media/materials/scripts/Examples. material le.
material Examples/Droplet{ technique { pass { emissive 0.3 0.3 0.3 scene_blend colour_blend depth_write off diffuse vertexcolour texture_unit { texture basic_droplet.png } } } }
When you run the program and turn on the rain effect, you can see the rain particles falling on our robot overlord.
112
Chapter 4
There's more...
You can add additional elements to the Rain Control dialog-box to control other parameters of the rain particle system, such as the speed or the color. By adding dialog-boxes for each weather control, we can create a full-scale weather editor.
Creating lights
In Ogre, when you create a light, you are dening the origin and color for a light, but there is no visible representation of an object casting light in the scene, such as a light bulb, or a window, or TV screen. In this recipe, we'll show you how to create those illuminated objects that represent the light source object in a 3D scene.
Getting ready
First, add Wall.material to media\materials\scripts, and then add the White.jpg, Grey.jpg, Yellow.jpg, and Black.jpg textures to the media\materials\textures folder. To follow along with this recipe, open the solution located in the Recipes/Chapter04 folder in the code bundle available on the Packt website.
How to do it...
1. First, create an MFC Ogre application named Lights. 2. Next, in LightsView::EngineSetup(), add a spotlight can and a light beam, to represent the light from the spotlight, hitting particles in the air.
// spotlight can CCone ConeObject; ConeObject.m_Height = 20.0; ConeObject.m_Radius = 10.0; Ogre::ManualObject *Can = ConeObject.CreateCone(0,"SpotLightLight","Wall/Black"); Ogre::SceneNode *CanNode = SceneManager->getRootSceneNode()-> createChildSceneNode(Ogre::Vector3(100.0, 181.0, 0.0)); //Can->setCastShadows(true); CanNode->attachObject(Can); // spotlight beam ConeObject.m_Height = 200.0; ConeObject.m_Radius = 80.0; Ogre::ManualObject *Beam = ConeObject.CreateCone(0.99,"SpotLightBeam","LightBeam",0.5);
113
We position the light beam cone and the spotlight, just under the can. 3. Next, create a point light and a are billboard sprite as its visual representation.
// point light Ogre::Light* PointLight = SceneManager->createLight("PointLight"); PointLight->setType(Ogre::Light::LT_POINT); PointLight->setDiffuseColour(1.0, 0.0, 0.0); PointLight->setSpecularColour(1.0, 0.0, 0.0); PointLight->setVisible(true); PointLight->setAttenuation(3250.0,1.0,0.0014,0.000007); Ogre::SceneNode *PointLightNode = SceneManager-> getRootSceneNode()->createChildSceneNode("PointLight"); PointLightNode->attachObject(PointLight); PointLightNode->setPosition(Ogre::Vector3(-100.0,150,30.0)); // attach a flare to the point light node Ogre::BillboardSet* FlareSet = SceneManager-> createBillboardSet("FlareSet"); FlareSet->setMaterialName("Examples/FlarePointSprite"); FlareSet->setDefaultDimensions(50.0,50.0); Ogre::Billboard* Flare = FlareSet->createBillboard( Ogre::Vector3(0,0,0),Ogre::ColourValue(1.0,0.0,0.0,0.5)); PointLightNode->attachObject(FlareSet);
114
Chapter 4
4. Finally, add a couple dangerous robots under each light and a ground plane.
// floor mesh Ogre::Plane Floor(Ogre::Vector3::UNIT_Y, 0); Ogre::MeshPtr WallMesh = Ogre::MeshManager::getSingleton().createPlane("Floor", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Floor, 1000,1000,100,100,true,1,5,5, Ogre::Vector3::UNIT_Z); Ogre::Entity *FloorEntity = SceneManager->createEntity("Floor", "Floor"); FloorEntity->setCastShadows(false); Ogre::SceneNode *FloorNode = SceneManager->getRootSceneNode()-> createChildSceneNode("Floor"); FloorNode->attachObject(FloorEntity); Ogre::Entity *RobotEntity = SceneManager->createEntity("Robot", "robot.mesh"); Ogre::SceneNode *RobotNode = SceneManager->getRootSceneNode()-> createChildSceneNode(Ogre::Vector3(100.0,00.0,0.0)); RobotNode->yaw(-Ogre::Radian(Ogre::Math::HALF_PI)); RobotEntity->setCastShadows(true); RobotNode->attachObject(RobotEntity); Ogre::Entity *RobotEntity2 = SceneManager->createEntity("Robot2", "robot.mesh"); Ogre::SceneNode *RobotNode2 = SceneManager->getRootSceneNode()-> createChildSceneNode(Ogre::Vector3(-100.0,00.0,0.0)); RobotNode2->yaw(-Ogre::Radian(Ogre::Math::HALF_PI)); RobotEntity2->setCastShadows(true); RobotNode2->attachObject(RobotEntity2);
How it works...
In Ogre 3D, there are three types of lights: point lights, spot lights, and directional lights. In this recipe, we use a transparent cone to represent the light from the spot light, hitting air particles. To accomplish this, we create a cone mesh using an Ogre::ManualObject with each vertex having an alpha value based on the height of the vertex. This way, the vertices at the bottom of the spot light cone are fully transparent, and vertices at the top of the cone are less transparent.
Cone->colour(Intensity, Intensity, 0.0, ((Ogre::Real) HeightSegmentIndex / (Ogre::Real)m_HeightSegments) * fadeAmount);
115
Let There Be Light The alpha value for each vertex in the cone is also multiplied by some variable fadeAmount. For this recipe, we set the fadeAmount to 0.5, so that the top of the cone is not opaque. For the point light, we use a are sprite tinted red that always faces the camera. This is the most common approach for making light sources visible. We make use of the Ogre::BillboardSet and the Ogre::Billboard classes to create ares that will always face the camera, because Ogre will adjust the billboard positions for every frame for us. The Ogre::BillboardSet class is also very efcient at managing and rendering many billboards at once.
Our robot overlords sure know how to get their mood on!
There's more...
Our recipe does not have a are for the spotlight. Create another are for the spotlight, then make the transparency of each are dependent on the view angle from the camera to the are, so that, when looking directly at the are, it is most visible, but as the camera looks away from the are, it becomes less visible. Ideally, the are for the spotlight should only be visible from under the spotlight. The directional light, not shown in this recipe, is a good choice for representing the sun or moon, which also can be represented with a billboard sprite that always faces the camera.
116
Chapter 4
See also
In this chapter:
Getting ready
To follow along with this recipe, open the solution located in the Recipes/Chapter04 folder in the code bundle available on the Packt website.
How to do it...
1. First, create an MFC Ogre application named DynamicEffects. 2. Next, add an Ogre::Light member variable to the CDynamicEffectsView class, and then initialize it in CDynamicEffectsView::EngineSetup().
Ogre::AxisAlignedBox Box(-1000, -1000, -1000, 1000, 1000, 1000); Ogre::Vector3 Center = Box.getCenter(); Light = SceneManager->createLight(); m_Camera->setPosition(Ogre::Vector3(25.0, 25.0, 25.0)); m_Camera->setDirection((Ogre::Vector3(0.0, 100.0, 0.0) m_Camera->getPosition()).normalisedCopy()); Light->setDirection((Ogre::Vector3(0.0, 100.0, 0.0) Ogre::Vector3(0.0, 0.0, 0.0)).normalisedCopy()); Ogre::Real Intensity = m_Camera->getDirection().dotProduct( Light->getDirection()); Ogre::SceneNode* lightNode = SceneManager->getRootSceneNode()-> createChildSceneNode(Center); Light->setType(Ogre::Light::LT_SPOTLIGHT); Light->setVisible(true);
117
3. After creating the light, we set its type to Ogre::Light::LT_SPOTLIGHT, and attach it to the scene graph. 4. Next, add the Cone.cpp and the Cone.h les from the example project, and then create a new CCone object in CDynamicEffectsView::EngineSetup().
CCone ConeObject; Cone = ConeObject.CreateCone(Intensity); Ogre::SceneNode *ConeNode = SceneManager->getRootSceneNode()-> createChildSceneNode(Ogre::Vector3(0.0, 100.0, 0.0)); ConeNode->attachObject(Cone);
The cone mesh object will represent our spotlight beam in the scene. 5. Now that we have all the graphical elements we need in the scene, it's time to add controls, so that we can move the camera. Add an integer member variable named m_WorkingTimer to CDynamicEffectsView. 6. Next, add a handler for the ON_WM_KEYDOWN message named CDynamicEffectsV iew::OnKeyDown().
m_WorkingTimer = 0; switch case case case (nChar) { VK_LEFT: //left 65: //A 97: //a
m_WorkingTimer = 1; break; case VK_UP: //up case 87: //W case 119: //w m_WorkingTimer = 2; break; case VK_RIGHT: //right
118
Chapter 4 case 68: //D case 100: //d m_WorkingTimer = 3; break; case VK_DOWN: //down case 83: //S case 115://s m_WorkingTimer = 4; break; } if (m_WorkingTimer != 0) SetTimer(m_WorkingTimer, 10, NULL);
In OnKeyDown, we set a different working timer value, depending on which key is down. 7. Next, add a handler for the ON_WM_KEYUP message named CDynamicEffectsView::OnKeyUP().
KillTimer(m_WorkingTimer); CView::OnKeyUp(nChar, nRepCnt, nFlags);
Here' we kill the m_WorkingTimer, so we stop generating WM_TIMER messages. 8. Next, add a handler for the ON_WM_TIMER message named CDynamicEffectsVie w::OnTimer().
CEngine *Engine = ((CDynamicEffectsApp*)AfxGetApp())->m_Engine; if (Engine == NULL) return; Ogre::Root *Root = Engine->GetRoot(); if (Root == NULL) { return; } Ogre::Vector3 CameraMove; switch (nIDEvent) {
119
9. First, we check the timer event ID, and set the CameraMove variable appropriately, to move the camera in the right direction. 10. Next, we calculate the dot product between camera direction and the spotlight direction. The spotlight intensity is proportional to this dot product.
m_Camera->moveRelative(CameraMove); m_Camera->setDirection((Light->getPosition() - m_Camera-> getPosition()).normalisedCopy()); Ogre::Real Intensity = m_Camera->getDirection().dotProduct(Light-> getDirection()); Light->setDiffuseColour(Intensity, Intensity, 0.0); Light->setSpecularColour(Intensity, Intensity, 0.0);
120
Chapter 4
11. Next, we update the vertex colors for the spotlight beam, so that their intensity matches the spotlight intensity.
int numSegBase = 24; int numSegHeight = 24; Ogre::Real radius = 10.0; Ogre::Real height = 20.0; Cone->beginUpdate(0); Ogre::Real deltaAngle = (Ogre::Math::TWO_PI / numSegBase); Ogre::Real deltaHeight = height/(Ogre::Real)numSegHeight; Ogre::Real uTile = 1.0; Ogre::Real vTile = 1.0; Ogre::Vector3 refNormal = Ogre::Vector3(radius, height, 0.f).normalisedCopy(); Ogre::Quaternion q; int offset = 0; for (int i = 0; i <=numSegHeight; i++) { Ogre::Real r0 = radius * (1 - i / (Ogre::Real)numSegHeight); for (int j = 0; j<=numSegBase; j++) { Ogre::Real x0 = r0* cosf(j * deltaAngle); Ogre::Real z0 = r0 * sinf(j * deltaAngle); Cone->position(x0, i * deltaHeight, z0); Cone->colour(Intensity, Intensity, 0.0, 0.0); q.FromAngleAxis(Ogre::Radian(-j*deltaAngle), Ogre::Vector3::NEGATIVE_UNIT_Y); Cone->normal(q*refNormal); Cone->textureCoord(j / (Ogre::Real)numSegBase * uTile, i / (Ogre::Real)numSegHeight * vTile); if (i != numSegHeight&& j != numSegBase) { Cone->index(offset + numSegBase + 2); Cone->index(offset); Cone->index(offset + numSegBase + 1); Cone->index(offset + numSegBase + 2); Cone->index(offset + 1); Cone->index(offset); } offset ++; }
121
How it works...
Each time we render a frame, we calculate the dot product of the spotlight direction vector and the camera view vector, and use that value to set the intensity for the spotlight beam. The dot product value is the cosine of the angle between the two vectors. So, when the vectors are parallel, this value will be 1 or -1, and when the vectors are perpendicular, the dot product will be 0. In this recipe, we set the Intensity variable value based on the dot product, so that if the camera is looking directly at the spotlight beam, the intensity will be magnied, but if the camera is looking away, the intensity will be less. The effect of changing the intensity of the light is meant to be similar to the way our eyes adjust to lights when we look directly at them or an angle. If we were using a point light, which has no direction, we would take the dot product of a camera view vector, and a normalized vector from the light's origin to the camera's origin.
122
Chapter 4
The intensity of the spotlight beam cone is very low when the camera direction vector is perpendicular to the spotlight direction vector.
As the camera has moved, and the angle between the camera view vector and the spotlight direction vector is smaller, the intensity has increased.
123
Let There Be Light Finally, when the camera view vector and the spotlight direction vector are parallel, the intensity is at its maximum value.
There's more...
In this recipe, we used a solid color for our spotlight beam. A better looking spotlight beam should use a gradient texture; one that is bright and opaque at the spotlight source, then progressively fades out, relative to the distance to the source.
Getting ready
To follow along with this recipe, open the solution located in the Recipes/Chapter04 folder in the code bundle available on the Packt website.
How to do it...
1. First, create an MFC Ogre application named ParticleSystem. 2. Next, create a dialog-box named Particle System Editor using the Visual Studio Dialog Editor. Add two menus to the dialog-boxa menu to add various types of emitters and a menu to add affecters. Add event handles to each menu item. Next, add a tree control to manage the structure of the particle system, and controls for creating particle systems.
124
Chapter 4
When the Create button is pressed, we add a new particle system called sun, and set default dimensions, a material name, the particle type, and the maximum number of particles. 4. Next, create a handler for the Try button named OnBnClickedTryParticleSystem().
void CParticleSystemControlDlg::OnBnClickedTryParticleSystem() { CMainFrame *MainFrame = (CMainFrame *)(( CParticleSystemApp*)AfxGetApp())->GetMainWnd(); CEngine *Engine = ((CParticleSystemApp*)AfxGetApp())->m_Engine; Ogre::Root *Root = Engine->GetRoot(); Ogre::SceneManager *SceneManager = Root-> getSceneManager("ParticleSystem"); Ogre::SceneNode *SceneNode = SceneManager->getRootSceneNode()-> createChildSceneNode(); SceneNode->attachObject(m_ParticleSystem); SceneNode->setPosition(0, 10, 0); m_ParticleSystem->setVisible(true); Root->renderOneFrame(); MainFrame->GetActiveView()->SetTimer(1,1,0); }
125
Let There Be Light In OnBnClickedTryParticleSystem(), we attach the particle system to a scene node, and render the scene. We also activate a timer to render the scene and update the particle system at regular intervals. 5. Next, we need to implement the handlers for creating the emitters and affecters. In this recipe, we will show you how to implement the Box Emitter and the Deector plane affecter. The implementation for the remaining emitters and affecters should be very similar. The message handler for Add Box Emitter should look like this:
void CParticleSystemControlDlg::OnEmittersAddBoxEmitter() { CBoxEmitterDlg BoxEmitterDlg; if (IDOK == BoxEmitterDlg.DoModal()) { HTREEITEM EmitterItem = m_ParticleSystemTree.InsertItem(BoxEmitterDlg.m_EmitterName, m_EmittersItem); m_ParticleSystemTree.EnsureVisible(EmitterItem); Ogre::ParticleEmitter *BoxEmitter = m_ParticleSystem-> addEmitter("Box");
When the Add Box Emitter menu item is selected, the BoxEmitterDlg dialog-box is displayed, and a box emitter is added to the particle system. 6. Create a dialog-box named CBoxEmitterDlg with a single text eld for the emitter name. 7. Now, let's implement the Add Deector Plane menu item handler. Create a dialogbox named CDeflectorPlaneAffectorDlg, and add controls for naming the affecter, setting the plane origin, normal, and bounce value.
126
Chapter 4
8. Next, we add code to the Add Deector Plane Affector menu item that opens the CDeectorPlaneAffectorDlg, then uses the dialog-box settings to create the affecter, and add it to the scene.
void CParticleSystemControlDlg:: OnAffectorsAdddeflectorplaneaffector() { CDeflectorPlaneAffectorDlg DeflectorPlaneAffectorDlg; if (IDOK == DeflectorPlaneAffectorDlg.DoModal()) { m_ParticleSystemTree.InsertItem( DeflectorPlaneAffectorDlg.m_AffectorName, m_AffectorsItem); Ogre::ParticleAffector *Plane = m_ParticleSystem-> addAffector("DeflectorPlane"); Plane->setParameter("plane_point", "0 -50 0"); Plane->setParameter("plane_normal", "0 1 0"); Plane->setParameter("bounce", "1"); } }
How it works...
When you create the particle system, and add emitters and affecters, they are added to the tree control. When the Try button is pressed, the particle system activates and a timer is enabled that renders the scene at regular intervals.
void CParticleSystemView::OnTimer(UINT_PTR nIDEvent) { CEngine * Engine = ((CParticleSystemApp*)AfxGetApp())->m_Engine; Ogre::Root *Root = Engine->GetRoot(); Root->renderOneFrame(); CView::OnTimer(nIDEvent); }
127
Let There Be Light We also add a robot mesh to the scene to give a sense of scale, though you may wish to remove it, if the particle system you are testing is obscured by it.
There's more...
In this recipe, we only implemented one emitter and one affecter. You can implement the handlers and dialog-boxes for the remaining emitters and affecters. You can also add functionality to the tree, to enable and disable emitters and affecters by adding and removing them from the particle system, when the check-box next to each tree item is checked.
Managing shadows
When architects design buildings, they must adhere to city building codes and be aware of the impact on the environment caused by the structures they design. A tall building erected near others might cast a shadow over them, thus decreasing the warming rays of the sun, and increasing the cost of heating them. In this recipe, we will assume the role of the architect, and our job is to determine if the building that we want to erect will cast a shadow on an existing, nearby building. To simulate shadows cast on buildings, we will create a scene with a house to represent the affected building, and then use a 3D box model to represent the building we want to construct. We will also move a point light representing the sun, to simulate shadows at different times of day.
Getting ready
To follow along with this recipe, open the solution located in the Recipes/Chapter04 folder in the code bundle available on the Packt website.
How to do it...
1. First, create an MFC Ogre application with a ribbon named Shadows. 2. Next, create the simple scene with a ground plane, a house model, and a box model to represent the new building.
Ogre::Plane Ground(Ogre::Vector3::UNIT_Y, 0); Ogre::MeshPtr GroundMesh = Ogre::MeshManager::getSingleton().createPlane("Ground", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ground, 10000, 10000,20,20,true,1,5,5, Ogre::Vector3::UNIT_Z); Ogre::Entity *GroundEntity = SceneManager->createEntity("Ground", "Ground");
128
Chapter 4 Ogre::SceneNode *GroundNode = SceneManager->getRootSceneNode()-> createChildSceneNode("Ground"); GroundNode->attachObject(GroundEntity); GroundEntity->setCastShadows(false); Ogre::Vector3 InsertionPoint; Ogre::Entity *HouseEntity = SceneManager->createEntity("House", "tudorhouse.mesh"); Ogre::AxisAlignedBox HouseBox = HouseEntity->getBoundingBox(); InsertionPoint = - HouseBox.getCorner( Ogre::AxisAlignedBox::NEAR_LEFT_BOTTOM); Ogre::SceneNode *HouseNode = SceneManager->getRootSceneNode()-> createChildSceneNode(InsertionPoint); HouseNode->attachObject(HouseEntity); HouseEntity->setCastShadows(false); Ogre::Entity *BoxEntity = SceneManager->createEntity("Box", Ogre::SceneManager::PrefabType::PT_CUBE); BoxEntity->setMaterialName("Examples/BeachStones"); Ogre::AxisAlignedBox Box = BoxEntity->getBoundingBox(); Ogre::SceneNode *BoxNode = SceneManager->getRootSceneNode()-> createChildSceneNode("Box"); BoxNode->attachObject(BoxEntity); BoxNode->setScale(Ogre::Vector3(5, 20, 5)); BoxNode->setPosition(Ogre::Vector3(-1000, 0, 1000)); BoxEntity->setCastShadows(true);
129
Let There Be Light 4. Next, add panels and sliders to the ribbon for controlling the height of the building and the time of day.
5. Now, it's time to create the event handlers for ribbon sliders.
void CShadowsView::OnTime() { CMainFrame *MainFrame = (CMainFrame *)(( CShadowsApp*)AfxGetApp())->GetMainWnd(); CMFCRibbonBar* RibbonBar = MainFrame->GetRibbonBar(); CMFCRibbonSlider* Slider = DYNAMIC_DOWNCAST(CMFCRibbonSlider, RibbonBar->FindByID(ID_TIME)); Ogre::Radian Angle = Ogre::Radian(Ogre::Math::TWO_PI * ( double)Slider->GetPos() / 24); CEngine *Engine = ((CShadowsApp*)AfxGetApp())->m_Engine; Ogre::Root *Root = Engine->GetRoot(); Ogre::SceneManager *SceneManager = Root-> getSceneManager("Shadows"); Ogre::Light *Sun = SceneManager->getLight("Sun"); Sun->setPosition(2500 * Ogre::Math::Cos(Angle), 1000, 2500 * Ogre::Math::Sin(Angle)); if (Root != NULL) { Root->renderOneFrame(); } }
6. For the Time slider, we convert the slider position to an angle, and use that value to set the sun position.
void CShadowsView::OnHeight() { CMainFrame *MainFrame = (CMainFrame *)(( CShadowsApp*)AfxGetApp())->GetMainWnd(); CMFCRibbonBar* RibbonBar = MainFrame->GetRibbonBar();
130
Chapter 4 CMFCRibbonSlider* Slider = DYNAMIC_DOWNCAST(CMFCRibbonSlider, RibbonBar->FindByID(ID_HEIGHT)); CEngine *Engine = ((CShadowsApp*)AfxGetApp())->m_Engine; Ogre::Root *Root = Engine->GetRoot(); Ogre::SceneManager *SceneManager = Root-> getSceneManager("Shadows"); Ogre::SceneNode *BoxNode = SceneManager->getSceneNode("Box"); BoxNode->setScale(5, Slider->GetPos(), 5); if (Root != NULL) { Root->renderOneFrame(); } }
The Height slider simply scales the 3D box that represents the new building.
How it works...
When the Time slider is moved, we change the position of the sun point light, and render the scene again. Each time the scene is rendered, Ogre automatically updates the shadows in the scene, based on the light position, the shadow casters, and receivers in the scene. In this recipe, we specically instructed Ogre to only cast shadows for the 3D box that represents our new building, so the shadow is clearly visible. We also set the shadow type to SHADOWTYPE_ STENCIL_ADDITIVE, so the shadows are very crisp and dened.
131
There's more...
To make a complete shadows analyzer, you should add a sun position calculator based on real locations in the world at various times of year. You can also add terrain to the scene, to more accurately represent the building location. Finally, using the Creating and Editing a Scene recipe, from the previous chapter, you can add functionality to add and remove objects in the scene.
See also
Chapter 3, Managing Objects and Scenes:
Creating a terrain from a LandXML le Creating Delaunay triangulation Creating and editing a scene
132
Alternatively, you can buy the book from Amazon, BN.com, Computer Manuals and most internet book retailers.
www.PacktPub.com