Ticket #2264: TerrainFlattening.3.diff

File TerrainFlattening.3.diff, 21.0 KB (added by wraitii, 10 years ago)
  • source/simulation2/components/ICmpTerrainModifier.h

     
     1/* Copyright (C) 2014 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#ifndef INCLUDED_ICMPTERRAINMODIFIER
     19#define INCLUDED_ICMPTERRAINMODIFIER
     20
     21#include "simulation2/system/Interface.h"
     22
     23/**
     24 * Handles terrain changes around an entity
     25 * Currently only handles flattening on building placement.
     26 */
     27class ICmpTerrainModifier : public IComponent
     28{
     29public:
     30    virtual void ApplyTerrainModification() = 0;
     31   
     32    DECLARE_INTERFACE_TYPE(TerrainModifier)
     33};
     34
     35#endif // INCLUDED_ICMPTERRAINMODIFIER
  • source/simulation2/components/CCmpTerrainModifier.cpp

    Property changes on: source/simulation2/components/ICmpTerrainModifier.h
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1/* Copyright (C) 2013 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "precompiled.h"
     19
     20#include "simulation2/system/Component.h"
     21#include "ICmpTerrainModifier.h"
     22
     23#include "graphics/Terrain.h"
     24#include "ps/CLogger.h"
     25#include "simulation2/MessageTypes.h"
     26#include "simulation2/serialization/SerializeTemplates.h"
     27
     28#include "simulation2/components/ICmpFootprint.h"
     29#include "simulation2/components/ICmpPosition.h"
     30#include "simulation2/components/ICmpTerrain.h"
     31#include "simulation2/helpers/Geometry.h"
     32
     33
     34/**
     35 * Handles terrain changes around an entity
     36 * Currently only handles flattening on building placement.
     37 */
     38class CCmpTerrainModifier : public ICmpTerrainModifier
     39{
     40private:
     41    // used to expand outwards.
     42    struct Point {
     43        Point() : X(0), Y(0), parentX(0), parentY(0), generation(1), originalHeight(0)
     44        { }
     45        Point(int x, int y, u16 height) : X(x), Y(y), parentX(x), parentY(y), generation(1), originalHeight(height)
     46        {}
     47        Point(int x, int y, int px, int py, int Generation, u16 height) : X(x), Y(y), parentX(px), parentY(py), generation(Generation), originalHeight(height)
     48        {}
     49
     50        int X,Y;
     51        int parentX,parentY;
     52        int generation;
     53        u16 originalHeight;
     54    };
     55   
     56public:
     57    static void ClassInit(CComponentManager& componentManager)
     58    {
     59        componentManager.SubscribeToMessageType(MT_Destroy);
     60    }
     61
     62    DEFAULT_COMPONENT_ALLOCATOR(TerrainModifier)
     63
     64    // Dynamic state:
     65
     66    static std::string GetSchema()
     67    {
     68        return
     69            "<a:example/>"
     70            "<a:help>Causes the entity to flatten the terrain when placed on the map, if possible.</a:help>"
     71            "<empty/>";
     72    }
     73
     74    virtual void Init(const CParamNode& UNUSED(paramNode))
     75    {
     76    }
     77
     78    virtual void Deinit()
     79    {
     80    }
     81
     82    template<typename S>
     83    void SerializeCommon(S& UNUSED(serialize))
     84    {
     85    }
     86
     87    virtual void Serialize(ISerializer& serialize)
     88    {
     89        SerializeCommon(serialize);
     90    }
     91
     92    virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
     93    {
     94        Init(paramNode);
     95
     96        SerializeCommon(deserialize);
     97    }
     98
     99    virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
     100    {
     101        switch (msg.GetType())
     102        {
     103        case MT_Destroy:
     104        {
     105            break;
     106        }
     107        }
     108    }
     109   
     110    // Try to modifiy the terrain around us.
     111    virtual void ApplyTerrainModification()
     112    {
     113        FlattenTerrainAroundCenter();
     114    }
     115   
     116    // Flatten the terrain around us.
     117    // this uses a  weird "Expand and come back" algorithm to flatten terrain.
     118    void FlattenTerrainAroundCenter()
     119    {
     120        CmpPtr<ICmpPosition> cmpPosition(GetEntityHandle());
     121        if (!cmpPosition)
     122            return;
     123
     124        CmpPtr<ICmpTerrain> cmpTerrain(GetSystemEntity());
     125        if (!cmpTerrain)
     126            return;
     127
     128        CmpPtr<ICmpFootprint> cmpFootprint(GetEntityHandle());
     129        if (!cmpFootprint)
     130            return;
     131       
     132        //CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity());
     133        //StationaryOnlyObstructionFilter obstructionFilter;
     134
     135        // grab the shape and dimensions of the footprint
     136        entity_pos_t footprintSize0, footprintSize1, footprintHeight;
     137        ICmpFootprint::EShape footprintShape;
     138        cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight);
     139       
     140        // original position
     141        CFixedVector2D position = cmpPosition->GetPosition2D();
     142        // scaled down
     143        CFixedVector2D mapPosition(position.X/4, position.Y/4);
     144       
     145        fixed& originX = mapPosition.X;
     146        fixed& originY = mapPosition.Y;
     147       
     148        int posX = originX.ToInt_RoundToZero();
     149        int posY = originY.ToInt_RoundToZero();
     150       
     151        // get the terrain map, we will modify it directly.
     152        CTerrain* terrain = cmpTerrain->GetCTerrain();
     153        ssize_t width = terrain->GetVerticesPerSide();
     154        u16* heightmap = terrain->GetHeightMap();
     155       
     156        // we'll be hotmodifying the heightmap.
     157       
     158        /* Okay so let's start flattening.
     159         * Algorithm is "expand and retract".
     160         * Basically we'll start from the 4 vertices around the center of the building
     161         * And we'll expand outwards until we're quite sure we'll be able to flatten terrain nicely.
     162         * Then in a second step we'll start from the outermost point and work our way back to the center, actually flattening
     163         * Then we'll smooth the result.
     164         */
     165       
     166        // get the height we'll try to flatten to.
     167        int averageHeight = 0;
     168        int divider = 0;
     169        for (int YY = posY-2; YY <= posY+2; ++YY)
     170            for (int XX = posX-2; XX <= posX+2; ++XX)
     171                if (terrain->IsOnMap(XX*4,YY*4))
     172                {
     173                    averageHeight += heightmap[XX + YY*width];
     174                    ++divider;
     175                }
     176        u16 originalHeight = (u16)(averageHeight/divider);
     177
     178       
     179        // points to consider
     180        std::vector<Point> openList;
     181        // the outermost points from which we'll start flattening
     182        std::stack<Point> lastPoints;
     183        // acts as a global list of points I've studied.
     184        std::map<int, Point> closedList;
     185       
     186        Point basePointA(posX,posY, originalHeight);
     187        Point basePointB(posX,posY+1, originalHeight);
     188        Point basePointC(posX+1,posY, originalHeight);
     189        Point basePointD(posX+1,posY+1, originalHeight);
     190        openList.push_back(basePointA);
     191        openList.push_back(basePointB);
     192        openList.push_back(basePointC);
     193        openList.push_back(basePointD);
     194       
     195        // some macros to make the code more readable below.
     196#define checkHeight(X,Y,changeX,changeY) abs(heightmap[X+changeX + (Y+changeY)*width]-heightmap[X + Y*width]) < 4000
     197#define checkCollision(X,Y,changeX,changeY) (!cmpObstructionManager || !cmpObstructionManager->TestLine(obstructionFilter, entity_pos_t::FromInt(X*4),entity_pos_t::FromInt(Y*4), entity_pos_t::FromInt(X*4+changeX*4),entity_pos_t::FromInt(Y*4+changeY*4), entity_pos_t::FromInt(1)))
     198#define addPoint(id, X,Y,changeX,changeY, generation) id = Point(X+changeX,Y+changeY,X,Y,generation, heightmap[X+changeX + (Y+changeY)*width]);\
     199openList.push_back(id);
     200
     201       
     202        // for each base pixel on the openList, update
     203        while (openList.size() > 0)
     204        {
     205            Point curPoint = openList[0];
     206            // disregard already moved points or points not on the map (includes the edges, so we don't need to clamp)
     207            if (closedList.find(curPoint.X + curPoint.Y*width) != closedList.end())
     208            {
     209                openList.erase(openList.begin());
     210                continue;
     211            }
     212            if (curPoint.X <= 0 || curPoint.X >= width-2
     213                || curPoint.Y <= 0 || curPoint.Y >= width-2)
     214            {
     215                lastPoints.push(curPoint);
     216                closedList[curPoint.X + curPoint.Y*width] = curPoint;
     217                openList.erase(openList.begin());
     218                continue;
     219            }
     220
     221            bool inFoundation = false;
     222            if (footprintShape == ICmpFootprint::SQUARE)
     223            {
     224                    fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
     225                    fixed a = cmpPosition->GetRotation().Y;
     226                    sincos_approx(a, s, c);
     227                    CFixedVector2D u(c, -s); // unit vector along the rotated X axis
     228                    CFixedVector2D v(s, c); // unit vector along the rotated Z axis
     229                    CFixedVector2D halfSize(footprintSize0/8, footprintSize1/8);
     230                    if (Geometry::PointIsInSquare(CFixedVector2D(fixed::FromInt(curPoint.X), fixed::FromInt(curPoint.Y)) - CFixedVector2D(originX,originY), u, v, halfSize))
     231                        inFoundation = true;
     232            }
     233           
     234            CFixedVector2D path(fixed::FromInt(curPoint.X) - originX,fixed::FromInt(curPoint.Y) - originY);
     235           
     236            if (curPoint.generation > 20)
     237            {
     238                // failed, do not save us and add the first not-in foundation point to the last point list
     239                while (curPoint.generation > 2)
     240                    curPoint = closedList.find(curPoint.parentX + curPoint.parentY*width)->second;
     241
     242                lastPoints.push(curPoint);
     243                closedList[curPoint.X + curPoint.Y*width] = curPoint;
     244                openList.erase(openList.begin());
     245                continue;
     246            }
     247            else if(!inFoundation && curPoint.generation == 2 && abs(heightmap[curPoint.X + curPoint.Y*width] - originalHeight) < 100)
     248            {
     249                // it appears this is flat, so do nothing.
     250                openList.erase(openList.begin());
     251                continue;
     252            }
     253            else if(!inFoundation && abs(heightmap[curPoint.X + curPoint.Y*width] - originalHeight) < 1000*(curPoint.generation-1))
     254            {
     255                // here we're not under the foundation and we know we'll be able to flatten from here, so stop.
     256                lastPoints.push(curPoint);
     257                closedList[curPoint.X + curPoint.Y*width] = curPoint;
     258                openList.erase(openList.begin());
     259                continue;
     260            }
     261                       
     262            int newSquares[2][2] = { {0,0}, {0,0}};
     263
     264            if (path.X <= fixed::Zero() && path.Y <= fixed::Zero())
     265            {
     266                // lower left quadrant
     267                newSquares[0][0] = -1;
     268                newSquares[1][1] = -1;
     269            }
     270            else if (path.X <= fixed::Zero() && path.Y >= fixed::Zero())
     271            {
     272                // upper left quadrant
     273                newSquares[0][0] = -1;
     274                newSquares[1][1] = +1;
     275            }
     276            else if (path.X >= fixed::Zero() && path.Y <= fixed::Zero())
     277            {
     278                // lower right quadrant
     279                newSquares[0][0] = +1;
     280                newSquares[1][1] = -1;
     281            }
     282            else if (path.X >= fixed::Zero() && path.Y >= fixed::Zero())
     283            {
     284                // upper right quadrant
     285                newSquares[0][0] = +1;
     286                newSquares[1][1] = +1;
     287            }
     288            int generation = 1;
     289            if(!inFoundation)
     290                generation = curPoint.generation+1;
     291           
     292            // let's check if we actually add the points, or if something prevents us from going further in that direction.
     293            Point newA;
     294            Point newB;
     295           
     296            // let's check that the slope isn't too big in that direction (meaning it's already impassable, so we won't try to flatten there)
     297            // or if we run into a building, then we won't try to flatten under it either).
     298            if (checkHeight(curPoint.X,curPoint.Y,newSquares[0][0],newSquares[0][1]))
     299                //  && checkCollision(curPoint.X,curPoint.Y,newSquares[0][0],newSquares[0][1]))
     300                addPoint(newA,curPoint.X,curPoint.Y,newSquares[0][0],newSquares[0][1], generation);
     301           
     302            if (checkHeight(curPoint.X,curPoint.Y,newSquares[1][0],newSquares[1][1]))
     303                //  && checkCollision(curPoint.X,curPoint.Y,newSquares[1][0],newSquares[1][1]))
     304                addPoint(newB,curPoint.X,curPoint.Y,newSquares[1][0],newSquares[1][1], generation);
     305                       
     306            closedList[curPoint.X + curPoint.Y*width] = curPoint;
     307            openList.erase(openList.begin());
     308        }
     309#undef addPoint
     310#undef checkCollision
     311#undef checkHeight
     312
     313        // Now for each point, we'll move towards the last parent, flattening a bit each time.
     314        // we'll do it linearly for now, since we now of many points we'll go through till the center.
     315
     316        int32_t minX = width, maxX = 0;
     317        int32_t minY = width, maxY = 0;
     318
     319        while (lastPoints.size() > 0)
     320        {
     321            Point curPoint = lastPoints.top();
     322            lastPoints.pop();
     323            int startGeneration = curPoint.generation - 1;
     324            u16 startHeight = curPoint.originalHeight;
     325            u16 myHeight = curPoint.originalHeight;
     326            bool carryOn = true;
     327            while (carryOn)
     328            {
     329                if (curPoint.X == curPoint.parentX && curPoint.Y == curPoint.parentY)
     330                    carryOn = false;
     331               
     332                // target height is original height
     333                fixed coeff = startGeneration == 0 ? fixed::Zero() : fixed::FromInt(curPoint.generation-1) / startGeneration;
     334                fixed height = coeff * (int)startHeight;
     335                height += (fixed::FromInt(1) - coeff) * (int)originalHeight;
     336                if (myHeight-height.ToInt_RoundToNearest() > 1000)
     337                    height = fixed::FromInt(myHeight - 1000);
     338                else if (myHeight-height.ToInt_RoundToNearest() < -1000)
     339                    height = fixed::FromInt(myHeight + 1000);
     340                   
     341                myHeight = heightmap[width*curPoint.Y + curPoint.X] = height.ToInt_RoundToNearest();
     342                minX =  curPoint.X < minX ? curPoint.X : minX;
     343                minY =  curPoint.Y < minY ? curPoint.Y : minY;
     344                maxX =  curPoint.X > maxX ? curPoint.X : maxX;
     345                maxY =  curPoint.Y > maxY ? curPoint.Y : maxY;
     346                curPoint = closedList.find(curPoint.parentX + curPoint.parentY*width)->second;
     347            }
     348        }
     349        int height;
     350       
     351#define addHeightUnlessdiff(X,Y) height = (int)heightmap[X+(Y)*width]; \
     352if (height-averageHeight < 2000 && averageHeight-height > -2000) { \
     353    averageHeight += height; \
     354    ++divider; \
     355}
     356        /// smooth out of the foundations
     357        for (std::map<int, Point>::iterator itr = closedList.begin(); itr != closedList.end(); itr++)
     358        {
     359            Point& pt = itr->second;
     360            if (pt.generation == 1)
     361                continue;
     362            divider = 2;
     363            averageHeight = (int)(heightmap[pt.X + pt.Y*width])*2;
     364            addHeightUnlessdiff(pt.X+1,pt.Y);
     365            addHeightUnlessdiff(pt.X,pt.Y+1);
     366            addHeightUnlessdiff(pt.X,pt.Y-1);
     367            addHeightUnlessdiff(pt.X-1,pt.Y);
     368            heightmap[pt.X + pt.Y*width] = (u16)(averageHeight/divider);
     369        }
     370#undef addHeightUnlessdiff
     371       
     372        if (minX < maxX && minY < maxY)
     373        {
     374            // upload the modified heightmap.
     375            terrain->SetHeightMap(heightmap);
     376            cmpTerrain->MakeDirty(minX,minY,maxX,maxY);
     377        }
     378    }
     379};
     380
     381REGISTER_COMPONENT_TYPE(TerrainModifier)
  • source/simulation2/components/ICmpTerrainModifier.cpp

    Property changes on: source/simulation2/components/CCmpTerrainModifier.cpp
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
     1/* Copyright (C) 2014 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "precompiled.h"
     19
     20#include "ICmpTerrainModifier.h"
     21
     22#include "simulation2/system/InterfaceScripted.h"
     23#include "simulation2/system/SimContext.h"
     24
     25BEGIN_INTERFACE_WRAPPER(TerrainModifier)
     26DEFINE_INTERFACE_METHOD_0("ApplyTerrainModification", void, ICmpTerrainModifier, ApplyTerrainModification)
     27/*DEFINE_INTERFACE_METHOD_2("CheckFoundation", std::string, ICmpObstruction, CheckFoundation_wrapper, std::string, bool)
     28DEFINE_INTERFACE_METHOD_0("CheckDuplicateFoundation", bool, ICmpObstruction, CheckDuplicateFoundation)
     29DEFINE_INTERFACE_METHOD_2("GetEntityCollisions", std::vector<entity_id_t>, ICmpObstruction, GetEntityCollisions, bool, bool)
     30DEFINE_INTERFACE_METHOD_1("SetActive", void, ICmpObstruction, SetActive, bool)
     31DEFINE_INTERFACE_METHOD_3("SetDisableBlockMovementPathfinding", void, ICmpObstruction, SetDisableBlockMovementPathfinding, bool, bool, int32_t)
     32DEFINE_INTERFACE_METHOD_0("GetBlockMovementFlag", bool, ICmpObstruction, GetBlockMovementFlag)
     33DEFINE_INTERFACE_METHOD_1("SetControlGroup", void, ICmpObstruction, SetControlGroup, entity_id_t)
     34DEFINE_INTERFACE_METHOD_0("GetControlGroup", entity_id_t, ICmpObstruction, GetControlGroup)
     35DEFINE_INTERFACE_METHOD_1("SetControlGroup2", void, ICmpObstruction, SetControlGroup2, entity_id_t)
     36DEFINE_INTERFACE_METHOD_0("GetControlGroup2", entity_id_t, ICmpObstruction, GetControlGroup2)
     37*/END_INTERFACE_WRAPPER(TerrainModifier)
  • binaries/data/mods/public/simulation/helpers/Commands.js

    Property changes on: source/simulation2/components/ICmpTerrainModifier.cpp
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
     
    876876    var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
    877877    cmpFoundation.InitialiseConstruction(player, cmd.template);
    878878
     879
     880    // If it modifies the terrain, apply the changes
     881    var CmpTerrainModifier = Engine.QueryInterface(ent, IID_TerrainModifier);
     882    if (CmpTerrainModifier)
     883        CmpTerrainModifier.ApplyTerrainModification();
     884
    879885    // send Metadata info if any
    880886    if (cmd.metadata)
    881887        Engine.PostMessage(ent, MT_AIMetadata, { "id": ent, "metadata" : cmd.metadata, "owner" : player } );
  • binaries/data/mods/public/simulation/templates/template_structure.xml

     
    9797    <BarHeight>0.6</BarHeight>
    9898    <HeightOffset>12.0</HeightOffset>
    9999  </StatusBars>
     100  <TerrainModifier/>
    100101  <TerritoryDecay>
    101102    <HealthDecayRate>5</HealthDecayRate>
    102103  </TerritoryDecay>
  • binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml

     
    6262  <StatusBars>
    6363    <HeightOffset>8.0</HeightOffset>
    6464  </StatusBars>
     65  <TerrainModifier disable=""/>
    6566  <TerritoryDecay disable=""/>
    6667  <Vision>
    6768    <Range>0</Range>
  • binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml

     
    5252      <death>attack/destruction/building_collapse_large.xml</death>
    5353    </SoundGroups>
    5454  </Sound>
     55  <TerrainModifier disable=""/>
    5556  <TerritoryDecay disable=""/>
    5657  <ProductionQueue>
    5758    <BatchTimeModifier>0.8</BatchTimeModifier>
  • source/simulation2/TypeList.h

     
    149149INTERFACE(Terrain)
    150150COMPONENT(Terrain)
    151151
     152INTERFACE(TerrainModifier)
     153COMPONENT(TerrainModifier)
     154
    152155INTERFACE(TerritoryInfluence)
    153156COMPONENT(TerritoryInfluence)
    154157
  • source/simulation2/components/CCmpTemplateManager.cpp

     
    572572    permittedComponentTypes.insert("AIProxy");
    573573    permittedComponentTypes.insert("RallyPoint");
    574574    permittedComponentTypes.insert("RallyPointRenderer");
     575    permittedComponentTypes.insert("TerrainModifier");
    575576
    576577    CParamNode::LoadXMLString(out, "<Entity/>");
    577578    out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);