Files
FC1/Editor/VegetationMap.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

1604 lines
40 KiB
C++

////////////////////////////////////////////////////////////////////////////
//
// Crytek Engine Source File.
// Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
// File name: vegetationmap.cpp
// Version: v1.00
// Created: 31/7/2002 by Timur.
// Compilers: Visual Studio.NET
// Description:
// -------------------------------------------------------------------------
// History:
//
////////////////////////////////////////////////////////////////////////////
#include "StdAfx.h"
#include "VegetationMap.h"
#include "Heightmap.h"
#include "Layer.h"
#include "VegetationBrush.h"
#include <I3DEngine.h>
//////////////////////////////////////////////////////////////////////////
// CVegetationMap implementation.
//////////////////////////////////////////////////////////////////////////
#pragma pack(push,1)
// Structure of vegetation object instance in file.
struct SVegInst
{
Vec3 pos;
float scale;
uchar objectIndex;
uchar brightness;
uchar flags;
};
#pragma pack(pop)
#define MAX_TERRAIN_SIZE 1024
#define MIN_MASK_VALUE 32
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//! Undo object for CBaseObject.
class CUndoVegInstance : public IUndoObject
{
public:
CUndoVegInstance( CVegetationInstance *obj )
{
// Stores the current state of this object.
assert( obj != 0 );
m_obj = obj;
m_obj->AddRef();
ZeroStruct(m_redo);
m_undo.pos = m_obj->pos;
m_undo.scale = m_obj->scale;
m_undo.objectIndex = m_obj->object->GetIndex();
m_undo.brightness = m_obj->brightness;
m_undo.flags = m_obj->flags;
}
~CUndoVegInstance()
{
m_obj->Release();
}
protected:
virtual int GetSize() { return sizeof(*this); }
virtual const char* GetDescription() { return "Vegetation Modify"; };
virtual void Undo( bool bUndo )
{
if (bUndo)
{
m_redo.pos = m_obj->pos;
m_redo.scale = m_obj->scale;
m_redo.objectIndex = m_obj->object->GetIndex();
m_redo.brightness = m_obj->brightness;
m_redo.flags = m_obj->flags;
}
m_obj->scale = m_undo.scale;
m_obj->brightness = m_undo.brightness;
m_obj->flags = m_undo.flags;
GetIEditor()->GetVegetationMap()->MoveInstance( m_obj,m_undo.pos );
}
virtual void Redo()
{
m_obj->scale = m_redo.scale;
m_obj->brightness = m_redo.brightness;
m_obj->flags = m_redo.flags;
GetIEditor()->GetVegetationMap()->MoveInstance( m_obj,m_redo.pos );
}
private:
CVegetationInstance *m_obj;
SVegInst m_undo;
SVegInst m_redo;
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
//! Undo object for CBaseObject.
class CUndoVegInstanceCreate : public IUndoObject
{
public:
CUndoVegInstanceCreate( CVegetationInstance *obj,bool bDeleted )
{
// Stores the current state of this object.
assert( obj != 0 );
m_obj = obj;
m_obj->AddRef();
m_bDeleted = bDeleted;
}
~CUndoVegInstanceCreate()
{
m_obj->Release();
}
protected:
virtual int GetSize() { return sizeof(*this); }
virtual const char* GetDescription() { return "Vegetation Create"; };
virtual void Undo( bool bUndo )
{
CVegetationMap *vegMap = GetIEditor()->GetVegetationMap();
if (m_bDeleted)
{
vegMap->AddObjInstance( m_obj );
vegMap->MoveInstance( m_obj,m_obj->pos );
}
else
vegMap->DeleteObjInstance( m_obj );
}
virtual void Redo()
{
CVegetationMap *vegMap = GetIEditor()->GetVegetationMap();
if (!m_bDeleted)
{
vegMap->AddObjInstance( m_obj );
vegMap->MoveInstance( m_obj,m_obj->pos );
}
else
vegMap->DeleteObjInstance( m_obj );
}
private:
CVegetationInstance *m_obj;
bool m_bDeleted;
};
//////////////////////////////////////////////////////////////////////////
CVegetationMap::CVegetationMap()
{
m_numSectors = 0;
m_sectors = 0;
m_worldToSector = 0;
m_minimalDistance = 0.1f;
m_numInstances = 0;
m_bUpdateOnPaintBrightness = true;
m_I3DEngine = GetIEditor()->Get3DEngine();
// Initialize the random number generator
srand( GetTickCount() );
}
//////////////////////////////////////////////////////////////////////////
CVegetationMap::~CVegetationMap()
{
Clear();
if (m_sectors)
free( m_sectors );
}
void CVegetationMap::Clear()
{
ClearObjects();
}
void CVegetationMap::ClearSectors()
{
// Delete all objects in sectors.
// Iterator over all sectors.
CVegetationInstance *next;
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
SectorInfo *si = &m_sectors[i];
// Iterate on every object in sector.
for (CVegetationInstance *obj = si->first; obj; obj = next)
{
next = obj->next;
obj->Release();
}
si->first = 0;
}
m_numInstances = 0;
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::Allocate( CHeightmap *heightmap )
{
m_I3DEngine = GetIEditor()->Get3DEngine();
m_heightmap = heightmap;
int terrainSize = m_heightmap->GetUnitSize() * max(m_heightmap->GetWidth(),m_heightmap->GetHeight());
int sectorSize;
int numSectors;
if (terrainSize < MAX_TERRAIN_SIZE)
{
sectorSize = 1;
}
else
{
sectorSize = terrainSize / MAX_TERRAIN_SIZE;
}
assert( sectorSize != 0 );
numSectors = terrainSize / sectorSize;
if (sectorSize != m_sectorSize || numSectors != m_numSectors || !m_sectors)
{
ClearSectors();
if (m_sectors)
free( m_sectors );
m_numSectors = numSectors;
m_sectorSize = sectorSize;
// allocate sectors map.
int sz = sizeof(SectorInfo)*m_numSectors*m_numSectors;
m_sectors = (SectorInfo*)malloc( sz );
memset( m_sectors,0,sz );
}
m_mapSize = terrainSize;
m_worldToSector = 1.0f / m_sectorSize;
}
//////////////////////////////////////////////////////////////////////////
int CVegetationMap::GetNumSectors() const
{
return m_numSectors;
}
//////////////////////////////////////////////////////////////////////////
int CVegetationMap::GetSize() const
{
return m_numSectors * m_sectorSize;
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::PlaceObjectsOnTerrain()
{
if (!m_I3DEngine)
return;
// Clear all objects from 3d Engine.
m_I3DEngine->RemoveAllStaticObjects();
// Iterator over all sectors.
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
SectorInfo *si = &m_sectors[i];
// Iterate on every object in sector.
for (CVegetationInstance *obj = si->first; obj; obj = obj->next)
{
if (!obj->object->IsHidden())
{
// Stick vegetation to terrain.
obj->pos.z = m_I3DEngine->GetTerrainElevation(obj->pos.x,obj->pos.y);
m_I3DEngine->AddStaticObject( obj->object->GetId(),obj->pos,obj->scale,obj->brightness );
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::RemoveObjectsFromTerrain()
{
if (m_I3DEngine)
m_I3DEngine->RemoveAllStaticObjects();
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::HideObject( CVegetationObject *object,bool bHide )
{
if (object->IsHidden() == bHide)
return;
object->SetHidden(bHide);
if (object->GetNumInstances() > 0)
{
// Iterate over all sectors.
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
SectorInfo *si = &m_sectors[i];
// Iterate on every object in sector.
for (CVegetationInstance *obj = si->first; obj; obj = obj->next)
{
if (obj->object == object)
{
// Remove/Add to terrain.
if (bHide)
m_I3DEngine->RemoveStaticObject( -1,obj->pos );
else
{
// Stick vegetation to terrain.
obj->pos.z = m_I3DEngine->GetTerrainElevation(obj->pos.x,obj->pos.y);
m_I3DEngine->AddStaticObject( obj->object->GetId(),obj->pos,obj->scale,obj->brightness );
}
}
}
}
}
}
void CVegetationMap::SectorLink( CVegetationInstance *obj,SectorInfo *sector )
{
/*
if (sector->first)
{
// Add to the end of current list.
CVegetationInstance *head = sector->first;
CVegetationInstance *tail = head->prev;
obj->prev = tail;
obj->next = 0;
tail->next = obj;
head->prev = obj; // obj is now last object.
}
else
{
sector->first = obj;
obj->prev = obj;
obj->next = 0;
}
*/
if (sector->first)
{
// Add to the end of current list.
CVegetationInstance *head = sector->first;
obj->prev = 0;
obj->next = head;
head->prev = obj;
sector->first = obj;
}
else
{
sector->first = obj;
obj->prev = 0;
obj->next = 0;
}
}
void CVegetationMap::SectorUnlink( CVegetationInstance *obj,SectorInfo *sector )
{
if (obj == sector->first) // if head of list.
{
sector->first = obj->next;
if (sector->first)
sector->first->prev = 0;
}
else
{
//assert( obj->prev != 0 );
if (obj->prev)
obj->prev->next = obj->next;
if (obj->next)
obj->next->prev = obj->prev;
}
}
void CVegetationMap::AddObjInstance( CVegetationInstance *obj )
{
SectorInfo *si = GetSector(obj->pos);
if (!si)
{
obj->next = obj->prev = 0;
return;
}
obj->AddRef();
CVegetationObject *object = obj->object;
// Add object to end of the list of instances in sector.
// Increase number of instances.
object->SetNumInstances( object->GetNumInstances() + 1 );
m_numInstances++;
SectorLink( obj,si );
}
//////////////////////////////////////////////////////////////////////////
CVegetationInstance* CVegetationMap::CreateObjInstance( CVegetationObject *object,const Vec3 &pos )
{
SectorInfo *si = GetSector(pos);
if (!si)
return 0;
CVegetationInstance *obj = new CVegetationInstance;
obj->m_refCount = 1; // Starts with 1 reference.
//obj->AddRef();
obj->pos = pos;
obj->scale = 1;
obj->object = object;
obj->brightness = 255;
obj->flags = 0;
if (CUndo::IsRecording())
CUndo::Record( new CUndoVegInstanceCreate(obj,false) );
// Add object to end of the list of instances in sector.
// Increase number of instances.
object->SetNumInstances( object->GetNumInstances() + 1 );
m_numInstances++;
SectorLink( obj,si );
return obj;
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::DeleteObjInstance( CVegetationInstance *obj,SectorInfo *sector )
{
if (CUndo::IsRecording())
CUndo::Record( new CUndoVegInstanceCreate(obj,true) );
m_I3DEngine->RemoveStaticObject( -1, obj->pos );
SectorUnlink(obj,sector);
obj->object->SetNumInstances( obj->object->GetNumInstances() - 1 );
m_numInstances--;
assert( m_numInstances >= 0 );
obj->Release();
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::DeleteObjInstance( CVegetationInstance *obj )
{
SectorInfo *sector = GetSector( obj->pos );
if (sector)
DeleteObjInstance( obj,sector );
}
//////////////////////////////////////////////////////////////////////////
CVegetationInstance* CVegetationMap::GetNearestInstance( const Vec3 &pos,float radius )
{
// check all sectors intersected by radius.
float r = radius*m_worldToSector;
float px = pos.x*m_worldToSector;
float py = pos.y*m_worldToSector;
int sx1 = ftoi(px-r); sx1 = max(sx1,0);
int sx2 = ftoi(px+r); sx2 = min(sx2,m_numSectors-1);
int sy1 = ftoi(py-r); sy1 = max(sy1,0);
int sy2 = ftoi(py+r); sy2 = min(sy2,m_numSectors-1);
CVegetationInstance *nearest = 0;
float minDist = FLT_MAX;
for (int y = sy1; y <= sy2; y++)
{
for (int x = sx1; x <= sx2; x++)
{
// For each sector check if any object is nearby.
SectorInfo *si = GetSector(x,y);
if (si->first)
{
for (CVegetationInstance *obj = si->first; obj; obj = obj->next)
{
if (fabs(obj->pos.x-pos.x) < radius && fabs(obj->pos.y-pos.y) < radius)
{
float dist = GetSquaredDistance(pos,obj->pos);
if (dist < minDist)
{
minDist = dist;
nearest = obj;
}
}
}
}
}
}
return nearest;
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::GetObjectInstances( float x1,float y1,float x2,float y2,std::vector<CVegetationInstance*> &instances )
{
instances.reserve(100);
// check all sectors intersected by radius.
int sx1 = ftoi(x1*m_worldToSector); sx1 = max(sx1,0);
int sx2 = ftoi(x2*m_worldToSector); sx2 = min(sx2,m_numSectors-1);
int sy1 = ftoi(y1*m_worldToSector); sy1 = max(sy1,0);
int sy2 = ftoi(y2*m_worldToSector); sy2 = min(sy2,m_numSectors-1);
for (int y = sy1; y <= sy2; y++)
{
for (int x = sx1; x <= sx2; x++)
{
// For each sector check if any object is nearby.
SectorInfo *si = GetSector(x,y);
if (si->first)
{
for (CVegetationInstance *obj = si->first; obj; obj = obj->next)
{
if (obj->pos.x >= x1 && obj->pos.x <= x2 && obj->pos.y >= y1 && obj->pos.y <= y2)
{
instances.push_back(obj);
}
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::GetAllInstances( std::vector<CVegetationInstance*> &instances )
{
int k = 0;
instances.resize( m_numInstances );
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
// Iterate on every object in sector.
for (CVegetationInstance *obj = m_sectors[i].first; obj; obj = obj->next)
{
instances[k++] = obj;
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CVegetationMap::IsPlaceEmpty( const Vec3 &pos,float radius,CVegetationInstance *ignore )
{
// check all sectors intersected by radius.
if (pos.x < 0 || pos.y < 0 || pos.x > m_mapSize || pos.y > m_mapSize)
return false;
// check all sectors intersected by radius.
float r = radius*m_worldToSector;
float px = pos.x*m_worldToSector;
float py = pos.y*m_worldToSector;
int sx1 = ftoi(px-r); sx1 = max(sx1,0);
int sx2 = ftoi(px+r); sx2 = min(sx2,m_numSectors-1);
int sy1 = ftoi(py-r); sy1 = max(sy1,0);
int sy2 = ftoi(py+r); sy2 = min(sy2,m_numSectors-1);
for (int y = sy1; y <= sy2; y++)
{
for (int x = sx1; x <= sx2; x++)
{
// For each sector check if any object is within this radius.
SectorInfo *si = GetSector(x,y);
if (si->first)
{
for (CVegetationInstance *obj = si->first; obj; obj = obj->next)
{
if (obj != ignore && fabs(obj->pos.x-pos.x) < radius && fabs(obj->pos.y-pos.y) < radius)
return false;
}
}
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::MoveInstance( CVegetationInstance* obj,const Vec3 &newPos )
{
if (!IsPlaceEmpty(newPos,m_minimalDistance,obj))
return;
if (obj->pos != newPos)
RecordUndo( obj );
// Then delete object.
m_I3DEngine->RemoveStaticObject( -1, obj->pos );
SectorInfo *from = GetSector(obj->pos);
SectorInfo *to = GetSector(newPos);
if (from != to)
{
// Relink object between sectors.
SectorUnlink( obj,from );
if (to)
{
SectorLink( obj,to );
}
}
obj->pos = newPos;
// Stick vegetation to terrain.
obj->pos.z = m_I3DEngine->GetTerrainElevation(obj->pos.x,obj->pos.y);
m_I3DEngine->AddStaticObject( obj->object->GetId(),newPos,obj->scale,obj->brightness );
}
//////////////////////////////////////////////////////////////////////////
bool CVegetationMap::CanPlace( CVegetationObject *object,const Vec3 &pos,float radius )
{
// check all sectors intersected by radius.
if (pos.x < 0 || pos.y < 0 || pos.x > m_mapSize || pos.y > m_mapSize)
return false;
float r = radius*m_worldToSector;
float px = pos.x*m_worldToSector;
float py = pos.y*m_worldToSector;
int sx1 = ftoi(px-r); sx1 = max(sx1,0);
int sx2 = ftoi(px+r); sx2 = min(sx2,m_numSectors-1);
int sy1 = ftoi(py-r); sy1 = max(sy1,0);
int sy2 = ftoi(py+r); sy2 = min(sy2,m_numSectors-1);
for (int y = sy1; y <= sy2; y++)
{
for (int x = sx1; x <= sx2; x++)
{
// For each sector check if any object is within this radius.
SectorInfo *si = GetSector(x,y);
if (si->first)
{
for (CVegetationInstance *obj = si->first; obj; obj = obj->next)
{
// Only check objects that we need.
if (obj->object == object)
{
if (fabs(obj->pos.x-pos.x) < radius && fabs(obj->pos.y-pos.y) < radius)
return false;
}
else
{
if (fabs(obj->pos.x-pos.x) < m_minimalDistance && fabs(obj->pos.y-pos.y) < m_minimalDistance)
return false;
}
}
}
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////
CVegetationInstance* CVegetationMap::PlaceObjectInstance( const Vec3 &worldPos,CVegetationObject* object )
{
float fScale = object->CalcVariableSize();
// Check if this place is empty.
if (CanPlace( object,worldPos,m_minimalDistance))
{
CVegetationInstance *obj = CreateObjInstance( object,worldPos );
if (obj)
{
obj->scale = fScale;
// Stick vegetation to terrain.
obj->pos.z = m_I3DEngine->GetTerrainElevation(obj->pos.x,obj->pos.y);
m_I3DEngine->AddStaticObject( object->GetId(),obj->pos,obj->scale,obj->brightness );
}
return obj;
}
return 0;
}
//! Remove any object at specified location.
void CVegetationMap::RemoveObjectInstance( const Vec3 &worldPos )
{
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::PaintBrush( CRect &rc,bool bCircle,CVegetationObject* object )
{
assert( object != 0 );
GetIEditor()->SetModifiedFlag();
Vec3 p(0,0,0);
int unitSize = m_heightmap->GetUnitSize();
int mapSize = m_numSectors*m_sectorSize;
bool bProgress = rc.right-rc.left >= mapSize;
CWaitProgress wait("Distributing objects",bProgress);
// Intersect with map rectangle.
// Offset by 1 from each side.
float brushRadius2 = (rc.right-rc.left)*(rc.right-rc.left)/4;
rc &= CRect( 1,1,mapSize-2,mapSize-2 );
float AltMin = object->GetElevationMin();
float AltMax = object->GetElevationMax();
float SlopeMin = object->GetSlopeMin();
float SlopeMax = object->GetSlopeMax();
float density = object->GetDensity();
if (density <= 0)
density = m_minimalDistance;
int area = rc.Width() * rc.Height();
int count = area / (density*density);
// Limit from too much objects.
if (count > area)
count = area;
int j = 0;
float x1 = rc.left;
float y1 = rc.top;
float width2 = (rc.right-rc.left)/2.0f;
float height2 = (rc.bottom-rc.top)/2.0f;
float cx = (rc.right+rc.left)/2.0f;
float cy = (rc.bottom+rc.top)/2.0f;
// Calculate the vegetation for every point in the area marked by the brush
for (int i = 0; i < count; i++)
{
if (bProgress)
{
j++;
if (j > 200)
{
if (!wait.Step( 100*i/count ))
break;
}
}
float x = x1 + (frand()+1)*width2;
float y = y1 + (frand()+1)*height2;
// Skip all coordinates outside the brush circle
if (bCircle) {
if (((x-cx)*(x-cx) + (y-cy)*(y-cy)) > brushRadius2)
continue;
}
// Get the height of the current point
// swap x/y
int hy = ftoi(x/unitSize);
int hx = ftoi(y/unitSize);
float currHeight = m_heightmap->GetXY(hx,hy);
// Check if height valie is withing brush min/max altitude.
if (currHeight < AltMin || currHeight > AltMax)
continue;
// Calculate the slope for this spot
float slope = m_heightmap->GetSlope( hx,hy );
// Check if slope is withing brush min/max slope.
if (slope < SlopeMin || slope > SlopeMax)
continue;
p.x = x;
p.y = y;
float fScale = object->CalcVariableSize();
float placeRadius = fScale*object->GetObjectSize()*0.5f;
// Check if this place is empty.
if (!CanPlace( object,p,placeRadius ))
continue;
p.z = m_I3DEngine->GetTerrainElevation(p.x,p.y);
CVegetationInstance *obj = CreateObjInstance( object,p );
if (obj)
{
obj->scale = fScale;
m_I3DEngine->AddStaticObject( object->GetId(),p,fScale,obj->brightness );
}
}
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::PaintBrightness( float x,float y,float w,float h,uchar brightness,uchar brightness_shadowmap )
{
// Find sector range from world positions.
int startSectorX = ftoi(x*m_worldToSector);
int startSectorY = ftoi(y*m_worldToSector);
int endSectorX = ftoi((x + w)*m_worldToSector);
int endSectorY = ftoi((y + h)*m_worldToSector);
// Clamp start and end sectors to valid range.
if (startSectorX < 0)
startSectorX = 0;
if (startSectorY < 0)
startSectorY = 0;
if (endSectorX >= m_numSectors)
endSectorX = m_numSectors-1;
if (endSectorY >= m_numSectors)
endSectorY = m_numSectors-1;
float x2 = x+w;
float y2 = y+h;
// Iterate all sectors in range.
for (int sy = startSectorY; sy <= endSectorY; sy++)
{
for (int sx = startSectorX; sx <= endSectorX; sx++)
{
// Iterate all objects in sector.
SectorInfo *si = &m_sectors[sy*m_numSectors + sx];
if (!si->first)
continue;
for (CVegetationInstance *obj = si->first; obj; obj = obj->next)
{
if (obj->pos.x >= x && obj->pos.x < x2 && obj->pos.y >= y && obj->pos.y <= y2)
{
bool bNeedUpdate = false;
if (!obj->object->IsPrecalcShadow())
{
if (obj->brightness != brightness_shadowmap)
bNeedUpdate = true;
// If object is not casting precalculated shadow (small grass etc..) affect it by shadow map.
obj->brightness = brightness_shadowmap;
}
else
{
if (obj->brightness != brightness)
bNeedUpdate = true;
obj->brightness = brightness;
}
if (m_bUpdateOnPaintBrightness && bNeedUpdate)
{
RepaintInstance( obj );
}
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::ClearBrush( CRect &rc,bool bCircle,CVegetationObject* object )
{
GetIEditor()->SetModifiedFlag();
Vec3 p(0,0,0);
int unitSize = m_heightmap->GetUnitSize();
int mapSize = m_numSectors*m_sectorSize;
// Intersect with map rectangle.
// Offset by 1 from each side.
float brushRadius2 = (rc.right-rc.left)*(rc.right-rc.left)/4;
float cx = (rc.right+rc.left)/2;
float cy = (rc.bottom+rc.top)/2;
float x1 = rc.left;
float y1 = rc.top;
float x2 = rc.right;
float y2 = rc.bottom;
// check all sectors intersected by radius.
int sx1 = ftoi(x1*m_worldToSector);
int sx2 = ftoi(x2*m_worldToSector);
int sy1 = ftoi(y1*m_worldToSector);
int sy2 = ftoi(y2*m_worldToSector);
sx1 = max(sx1,0);
sx2 = min(sx2,m_numSectors-1);
sy1 = max(sy1,0);
sy2 = min(sy2,m_numSectors-1);
CVegetationInstance *next = 0;
for (int y = sy1; y <= sy2; y++)
{
for (int x = sx1; x <= sx2; x++)
{
// For each sector check if any object is within this radius.
SectorInfo *si = GetSector(x,y);
if (si->first)
{
for (CVegetationInstance *obj = si->first; obj; obj = next)
{
next = obj->next;
if (obj->object != object && object != NULL)
continue;
if (bCircle)
{
// Skip objects outside the brush circle
if (((obj->pos.x-cx)*(obj->pos.x-cx) + (obj->pos.y-cy)*(obj->pos.y-cy)) > brushRadius2)
continue;
}
else
{
// Withing rectangle.
if (obj->pos.x < x1 || obj->pos.x > x2 || obj->pos.y < y1 || obj->pos.y > y2)
continue;
}
// Then delete object.
DeleteObjInstance( obj,si );
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::ClearMask( const CString &maskFile )
{
// Swap x/y
Vec3 p(0,0,0);
CLayer layer;
layer.SetAutoGen(false);
layer.SetSmooth(false);
if (!layer.LoadMask( maskFile ))
{
CString str;
str.Format( "Error loading mask file %s",(const char*)maskFile );
AfxGetMainWnd()->MessageBox( str,"Warning",MB_OK|MB_ICONEXCLAMATION );
return;
}
int layerSize = m_numSectors;
layer.UpdateLayerMask16( 0,layerSize,layerSize,false );
if (!layer.IsValid())
return;
GetIEditor()->SetModifiedFlag();
CWaitProgress wait( "Clearing mask" );
for (int y = 0; y < layerSize; y++)
{
if (!wait.Step( 100*y/layerSize ))
break;
for (int x = 0; x < layerSize; x++)
{
if (layer.GetLayerMaskPoint(x,y) > MIN_MASK_VALUE)
{
// Find sector.
// Swap X/Y.
SectorInfo *si = &m_sectors[y + x*m_numSectors];
// Delete all instances in this sectors.
CVegetationInstance *next = 0;
for (CVegetationInstance *obj = si->first; obj; obj = next)
{
next = obj->next;
DeleteObjInstance( obj,si );
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
CVegetationObject* CVegetationMap::CreateObject( CVegetationObject *prev )
{
int id = -1;
// Generate New id.
// Cannot create more then 256 objects.
for (int i = 0; i < 256; i++)
{
if (m_usedIds.find(i) == m_usedIds.end())
{
id = i;
break;
}
}
if (id < 0)
{
// Free id not found, created more then 256 objects
AfxMessageBox( _T("Vegetation objects limit is reached.\r\nMaximum of 256 vegetation objects are supported."),MB_OK|MB_ICONWARNING );
return 0;
}
// Mark id as used.
m_usedIds.insert(id);
CVegetationObject *obj = new CVegetationObject( id,this );
if (prev)
obj->CopyFrom( *prev );
m_objects.push_back( obj );
return obj;
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::RemoveObject( CVegetationObject *object )
{
// Free id for this object.
m_usedIds.erase( object->GetId() );
if (object->GetNumInstances() > 0)
{
CVegetationInstance *next = 0;
// Delete this object in sectors.
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
SectorInfo *si = &m_sectors[i];
// Iterate on every object in sector.
for (CVegetationInstance *obj = si->first; obj; obj = next)
{
next = obj->next;
if (obj->object == object)
{
DeleteObjInstance( obj,si );
}
}
}
}
Objects::iterator it = std::find(m_objects.begin(),m_objects.end(),object);
if (it != m_objects.end())
m_objects.erase( it );
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::ReplaceObject( CVegetationObject *pOldObject,CVegetationObject *pNewObject )
{
if (pOldObject->GetNumInstances() > 0)
{
pNewObject->SetNumInstances( pNewObject->GetNumInstances() + pOldObject->GetNumInstances() );
CVegetationInstance *next = 0;
// Delete this object in sectors.
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
SectorInfo *si = &m_sectors[i];
// Iterate on every object in sector.
for (CVegetationInstance *obj = si->first; obj; obj = next)
{
next = obj->next;
if (obj->object == pOldObject)
{
obj->object = pNewObject;
}
}
}
}
RemoveObject( pOldObject );
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::ClearObjects()
{
ClearSectors();
m_objects.clear();
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::RepaintInstance( CVegetationInstance *obj )
{
m_I3DEngine->RemoveStaticObject( -1, obj->pos );
m_I3DEngine->AddStaticObject( obj->object->GetId(),obj->pos,obj->scale,obj->brightness );
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::RepositionObject( CVegetationObject *object )
{
// Iterator over all sectors.
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
SectorInfo *si = &m_sectors[i];
// Iterate on every object in sector.
for (CVegetationInstance *obj = si->first; obj; obj = obj->next)
{
if (obj->object == object)
{
RepaintInstance( obj );
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::ScaleObjectInstances( CVegetationObject *object,float fScale )
{
// Iterator over all sectors.
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
SectorInfo *si = &m_sectors[i];
// Iterate on every object in sector.
for (CVegetationInstance *obj = si->first; obj; obj = obj->next)
{
if (obj->object == object)
{
obj->scale *= fScale;
RepaintInstance( obj );
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::Serialize( CXmlArchive &xmlAr )
{
int i;
if (xmlAr.bLoading)
{
CLogFile::WriteLine("Loading Vegetation Map...");
ClearObjects();
CWaitProgress progress( _T("Loading Static Objects") );
XmlNodeRef mainNode = xmlAr.root->findChild( "VegetationMap" );
if (mainNode)
{
XmlNodeRef objects = mainNode->findChild( "Objects" );
if (objects)
{
int numObjects = objects->getChildCount();
for (i = 0; i < numObjects; i++)
{
if (!progress.Step( 100*i/numObjects ))
break;
CVegetationObject *obj = CreateObject();
if (obj)
obj->Serialize( objects->getChild(i),xmlAr.bLoading );
}
}
}
SerializeInstances( xmlAr );
LoadOldStuff( xmlAr );
// Now display all objects on terrain.
PlaceObjectsOnTerrain();
}
else
{
// Storing
CLogFile::WriteLine("Saving Vegetation Map...");
//xmlAr.pNamedData->AddDataBlock( "StatObjectsArray",m_map.GetData(),m_width*m_height );
XmlNodeRef mainNode = xmlAr.root->newChild( "VegetationMap" );
// Save objects.
XmlNodeRef objects = mainNode->newChild( "Objects" );
for (i = 0; i < GetObjectCount(); i++)
{
XmlNodeRef obj = objects->newChild( "Object" );
GetObject(i)->Serialize(obj,xmlAr.bLoading);
}
// Store objects.
SerializeInstances( xmlAr );
}
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::SerializeInstances( CXmlArchive &xmlAr,CRect *saveRect )
{
int i;
if (xmlAr.bLoading)
{
Vec3 posofs(0,0,0);
// Loading.
if (!saveRect)
{
ClearSectors();
}
else
{
posofs.x = saveRect->left;
posofs.y = saveRect->top;
}
int numObjects = m_objects.size();
int arraySize;
void *pData = 0;
if (!xmlAr.pNamedData->GetDataBlock( "VegetationInstancesArray",pData,arraySize ))
return;
SVegInst *array = (SVegInst*)pData;
int numInst = arraySize / sizeof(SVegInst);
for (int i = 0; i < numInst; i++)
{
if (array[i].objectIndex >= numObjects)
continue;
CVegetationObject *object = m_objects[array[i].objectIndex];
CVegetationInstance *obj = CreateObjInstance( object,array[i].pos+posofs );
if (obj)
{
obj->scale = array[i].scale;
obj->brightness = array[i].brightness;
obj->flags = array[i].flags;
}
}
//assert( m_numInstances == numInst );
}
else
{
// Saving.
if (m_numInstances == 0)
return;
int arraySize = sizeof(SVegInst)*m_numInstances;
SVegInst *array = (SVegInst*)malloc( arraySize );
int k = 0;
// Assign indices to objects.
for (i = 0; i < GetObjectCount(); i++)
{
GetObject(i)->SetIndex(i);
}
float x1,y1,x2,y2;
if (saveRect)
{
x1 = saveRect->left;
y1 = saveRect->top;
x2 = saveRect->right;
y2 = saveRect->bottom;
}
// Fill array.
for (i = 0; i < m_numSectors*m_numSectors; i++)
{
SectorInfo *si = &m_sectors[i];
// Iterate on every object in sector.
for (CVegetationInstance *obj = si->first; obj; obj = obj->next)
{
if (saveRect)
{
if (obj->pos.x < x1 || obj->pos.x > x2 || obj->pos.y < y1 || obj->pos.y > y2)
continue;
}
array[k].pos = obj->pos;
array[k].scale = obj->scale;
array[k].brightness = obj->brightness;
array[k].flags = obj->flags;
array[k].objectIndex = obj->object->GetIndex();
k++;
}
}
// Save this array.
xmlAr.pNamedData->AddDataBlock( "VegetationInstancesArray",array,k*sizeof(SVegInst) );
free( array );
}
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::LoadOldStuff( CXmlArchive &xmlAr )
{
XmlNodeRef mainNode = xmlAr.root->findChild( "VegetationMap" );
if (!mainNode)
return;
int i;
// Backward compatability loading of brushes.
XmlNodeRef brushes = mainNode->findChild( "Brushes" );
if (brushes)
{
for (i = 0; i < brushes->getChildCount(); i++)
{
CVegetationBrush br;
br.Serialize( brushes->getChild(i),xmlAr.bLoading );
}
}
void *pData = 0;
int nSize;
if (xmlAr.pNamedData->GetDataBlock( "StatObjectsArray",pData,nSize ))
{
if (nSize != 2048*2048)
return;
int numObjects = m_objects.size();
CVegetationObject* usedObjects[256];
ZeroStruct(usedObjects);
for (i = 0; i < m_objects.size(); i++)
{
usedObjects[m_objects[i]->GetIndex()] = m_objects[i];
}
Vec3 pos;
int i = 0;
uchar *pMap = (uchar*)pData;
for (int y = 0; y < 2048; y++)
{
for (int x = 0; x < 2048; x++,i++)
{
i = x + 2048*y;
if (!pMap[i])
continue;
unsigned int objIndex = pMap[i] - 1;
if (!usedObjects[objIndex])
continue;
//Swap x/y
pos.x = y;
pos.y = x;
pos.z = m_I3DEngine->GetTerrainElevation(pos.x,pos.y);
CVegetationInstance *obj = CreateObjInstance( usedObjects[objIndex],pos );
if (obj)
{
obj->scale = usedObjects[objIndex]->CalcVariableSize();
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
//! Generate shadows from static objects and place them in shadow map bitarray.
void CVegetationMap::GenerateShadowMap( CByteImage &shadowmap,float shadowAmmount,const Vec3 &sunVector )
{
// if(!m_pTerrain)
// return;
int width = shadowmap.GetWidth();
int height = shadowmap.GetHeight();
int i =0;
//@FIXME: Hardcoded.
int sectorSizeInMeters = 64;
int unitSize = m_heightmap->GetUnitSize();
int numSectors = (m_heightmap->GetWidth() * unitSize) / sectorSizeInMeters;
int sectorSize = shadowmap.GetWidth()/numSectors;
int sectorSize2 = sectorSize*2;
assert( sectorSize > 0 );
uint shadowValue = shadowAmmount;
bool bProgress = width >= 2048;
CWaitProgress wait( "Calculating Objects Shadows",bProgress );
unsigned char *sectorImage = (unsigned char*)malloc(sectorSize*sectorSize*3);
unsigned char *sectorImage2 = (unsigned char*)malloc(sectorSize2*sectorSize2*3);
for (int y = 0; y < numSectors; y++)
{
if (bProgress)
{
if (!wait.Step( y*100/numSectors ))
break;
}
for (int x = 0; x < numSectors; x++)
{
m_I3DEngine->MakeSectorLightMap( y*sectorSizeInMeters,x*sectorSizeInMeters,sectorImage2,sectorSize2 );
// Scale sector image down and store into the shadow map.
{
int pos;
uint color;
int x1 = x*sectorSize;
int y1 = y*sectorSize;
for (int j = 0; j < sectorSize; j++)
{
int sx1 = x1+(sectorSize-j-1);
for (int i = 0; i < sectorSize; i++)
{
pos = (i + j*sectorSize2)*2*3;
color = (shadowValue*
((uint)
(255-sectorImage2[pos]) +
(255-sectorImage2[pos+3]) +
(255-sectorImage2[pos+sectorSize2*3]) +
(255-sectorImage2[pos+sectorSize2*3+3])
)) >> 10;
// color = color*shadowValue >> 8;
// swap x/y
//color = (255-sectorImage2[(i+j*sectorSize)*3]);
shadowmap.ValueAt(sx1,y1+i) = color;
}
}
}
}
}
free( sectorImage );
free( sectorImage2 );
}
//////////////////////////////////////////////////////////////////////////
int CVegetationMap::ExportObject( CVegetationObject *object,XmlNodeRef &node,CRect *saveRect )
{
int numSaved = 0;
assert( object != 0 );
object->Serialize( node,false );
if (object->GetNumInstances() > 0)
{
float x1,y1,x2,y2;
if (saveRect)
{
x1 = saveRect->left;
y1 = saveRect->top;
x2 = saveRect->right;
y2 = saveRect->bottom;
}
// Export instances.
XmlNodeRef instancesNode = node->newChild("Instances");
// Iterator over all sectors.
for (int i = 0; i < m_numSectors*m_numSectors; i++)
{
// Iterate on every object in sector.
for (CVegetationInstance *obj = m_sectors[i].first; obj; obj = obj->next)
{
if (obj->object == object)
{
if (saveRect)
{
if (obj->pos.x < x1 || obj->pos.x > x2 || obj->pos.y < y1 || obj->pos.y > y2)
continue;
}
numSaved++;
XmlNodeRef inst = instancesNode->newChild( "Instance" );
inst->setAttr( "Pos",obj->pos );
if (obj->scale != 1)
inst->setAttr( "Scale",obj->scale );
if (obj->brightness != 255)
inst->setAttr( "Brightness",(int)obj->brightness );
if (obj->flags != 0)
inst->setAttr( "Flags",(int)obj->flags );
}
}
}
}
return numSaved;
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::ImportObject( XmlNodeRef &node,const Vec3& offset )
{
CVegetationObject *object = CreateObject();
if (!object)
return;
object->Serialize( node,true );
// Check if object with this GUID. already exists.
for (int i = 0; i < GetObjectCount(); i++)
{
CVegetationObject *pOldObject = GetObject(i);
if (pOldObject == object)
continue;
if (pOldObject->GetGUID() == object->GetGUID())
{
ReplaceObject( pOldObject,object );
/*
// 2 objects have same GUID;
CString msg;
msg.Format( _T("Vegetation Object %s %s already exists in the level!.\r\nOverride existing object?"),
GuidUtil::ToString(pOldObject->GetGUID()),(const char*)pOldObject->GetFileName() );
if (AfxMessageBox( msg,MB_YESNO|MB_ICONWARNING ) == IDYES)
{
ReplaceObject( pOldObject,object );
}
else
{
RemoveObject( object );
object = pOldObject;
}
*/
break;
}
}
Vec3 pos(0,0,0);
float scale = 1;
int brightness = 255;
int flags = 0;
XmlNodeRef instancesNode = node->findChild("Instances");
if (instancesNode)
{
int numChilds = instancesNode->getChildCount();
for (int i = 0; i < numChilds; i++)
{
pos.Set(0,0,0);
scale = 1;
brightness = 255;
flags = 0;
XmlNodeRef inst = instancesNode->getChild(i);
inst->getAttr( "Pos",pos );
inst->getAttr( "Scale",scale );
inst->getAttr( "Brightness",brightness );
inst->getAttr( "Flags",flags );
pos += offset;
CVegetationInstance *obj = GetNearestInstance( pos,0.01f );
if (obj && obj->pos == pos)
{
// Delete pevious object at same position.
DeleteObjInstance( obj );
}
obj = CreateObjInstance( object,pos );
if (obj)
{
obj->scale = scale;
obj->brightness = brightness;
obj->flags = flags;
// Stick vegetation to terrain.
obj->pos.z = m_I3DEngine->GetTerrainElevation(obj->pos.x,obj->pos.y);
}
}
}
RepositionObject(object);
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::DrawToTexture( uint *texture,int texWidth,int texHeight,int srcX,int srcY )
{
assert( texture != 0 );
for (int y = 0; y < texHeight; y++)
{
int trgYOfs = y*texWidth;
for (int x = 0; x < texWidth; x++)
{
int sx = x + srcX;
int sy = y + srcY;
// Swap X/Y
SectorInfo *si = &m_sectors[sy + sx*m_numSectors];
if (si->first)
texture[x+trgYOfs] = 0xFFFFFFFF;
else
texture[x+trgYOfs] = 0;
}
}
}
//////////////////////////////////////////////////////////////////////////
int CVegetationMap::WorldToSector( float worldCoord ) const
{
return ftoi(worldCoord*m_worldToSector);
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::UnloadObjectsGeometry()
{
for (int i = 0; i < GetObjectCount(); i++)
{
GetObject(i)->UnloadObject();
}
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::ExportBlock( const CRect &subRc,CXmlArchive &ar )
{
XmlNodeRef mainNode = ar.root->newChild( "VegetationMap" );
mainNode->setAttr( "X1",subRc.left );
mainNode->setAttr( "Y1",subRc.top );
mainNode->setAttr( "X2",subRc.right );
mainNode->setAttr( "Y2",subRc.bottom );
CRect rect = subRc;
for (int i = 0; i < GetObjectCount(); i++)
{
XmlNodeRef vegObjNode = mainNode->createNode( "VegetationObject" );
CVegetationObject *pObject = GetObject(i);
int numSaved = ExportObject( pObject,vegObjNode,&rect );
if (numSaved > 0)
{
mainNode->addChild( vegObjNode );
}
}
//SerializeInstances( ar,&rect );
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::ImportBlock( CXmlArchive &ar,CPoint placeOffset )
{
XmlNodeRef mainNode = ar.root->findChild( "VegetationMap" );
if (!mainNode)
return;
CRect subRc( 0,0,1,1 );
mainNode->getAttr( "X1",subRc.left );
mainNode->getAttr( "Y1",subRc.top );
mainNode->getAttr( "X2",subRc.right );
mainNode->getAttr( "Y2",subRc.bottom );
subRc.OffsetRect(placeOffset);
// Clear all vegetation instances in this rectangle.
ClearBrush( subRc,false,NULL );
// Serialize vegitation objects.
for (int i = 0; i < mainNode->getChildCount(); i++)
{
XmlNodeRef vegObjNode = mainNode->getChild(i);
ImportObject( vegObjNode,Vec3(placeOffset.x,placeOffset.y,0) );
}
// Clear object in this rectangle.
/*
CRect rect(placeOffset.x,placeOffset.y,placeOffset.x,placeOffset.y);
SerializeInstances( ar,&rect );
*/
// Now display all objects on terrain.
PlaceObjectsOnTerrain();
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::RecordUndo( CVegetationInstance *obj )
{
if (CUndo::IsRecording())
CUndo::Record( new CUndoVegInstance(obj) );
}
//////////////////////////////////////////////////////////////////////////
void CVegetationMap::Validate( CErrorReport &report )
{
for (int i = 0; i < GetObjectCount(); i++)
{
GetObject(i)->Validate( report );
}
}