mirror of
https://github.com/worldforge/ember
synced 2026-08-13 16:23:06 -04:00
1500 lines
56 KiB
C++
1500 lines
56 KiB
C++
/*-------------------------------------------------------------------------------------
|
|
Copyright (c) 2006 John Judnich
|
|
|
|
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
|
|
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
|
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
|
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
|
3. This notice may not be removed or altered from any source distribution.
|
|
-------------------------------------------------------------------------------------*/
|
|
|
|
#ifndef __GrassLoader_H__
|
|
#define __GrassLoader_H__
|
|
|
|
#include "PagedGeometry.h"
|
|
#include "PropertyMaps.h"
|
|
#include "RandomTable.h"
|
|
|
|
#include <OgrePrerequisites.h>
|
|
#include <OgreMaterial.h>
|
|
#include <OgrePixelFormat.h>
|
|
#include <OgreStringConverter.h>
|
|
#include <OgreMeshManager.h>
|
|
|
|
|
|
#include <OgreRoot.h>
|
|
#include <OgreTimer.h>
|
|
#include <OgreCamera.h>
|
|
#include <OgreVector.h>
|
|
#include <OgreQuaternion.h>
|
|
#include <OgreEntity.h>
|
|
#include <OgreString.h>
|
|
#include <OgreStringConverter.h>
|
|
#include <OgreMaterialManager.h>
|
|
#include <OgreMaterial.h>
|
|
#include <OgreHardwareBufferManager.h>
|
|
#include <OgreHardwareBuffer.h>
|
|
#include <OgreMeshManager.h>
|
|
#include <OgreMesh.h>
|
|
#include <OgreSubMesh.h>
|
|
#include <OgreLogManager.h>
|
|
#include <OgreTextureManager.h>
|
|
#include <OgreHardwarePixelBuffer.h>
|
|
#include <OgreRenderSystem.h>
|
|
#include <OgreRenderSystemCapabilities.h>
|
|
#include <OgreHighLevelGpuProgram.h>
|
|
#include <OgreHighLevelGpuProgramManager.h>
|
|
#include <OgreTechnique.h>
|
|
|
|
#include <memory>
|
|
|
|
using namespace Ogre;
|
|
namespace Forests {
|
|
|
|
class GrassLayer;
|
|
class GrassLayerBase;
|
|
|
|
/** \brief A PageLoader-derived object you can use with PagedGeometry to produce realistic grass.
|
|
|
|
Using a GrassLoader is simple - simply create an instance, attach it to your PagedGeometry object
|
|
with PagedGeometry::setPageLoader(), and add your grass. Important: For best performance, it is
|
|
recommended that you use GrassPage (included in GrassLoader.h) to display geometry loaded by GrassLoader.
|
|
This page type is designed for best performance with this grass system. BatchPage
|
|
will work, although performance will be reduced slightly, and ImpostorPage will run extremely slow.
|
|
|
|
When creating a GrassLoader you must specify the class which will be used for creating layers. By default a GrassLayer class is provided, which can be used as this:
|
|
\code
|
|
::Forests::GrassLoader<GrassLayer> loader = new ::Forests::GrassLoader<GrassLayer>(grass);
|
|
\endcode
|
|
It's also possible for you to provide your own GrassLayer implementation by overriding the class GrassLayerBase. This will allow you to have much more control over how the grass is placed.
|
|
|
|
|
|
To add grass, just call addLayer(). addLayer() returns a GrassLayer object pointer, which you should
|
|
use to further configure your newly added grass. Properties like size, density, color, animation, etc.
|
|
can be controlled through the GrassLayer class.
|
|
|
|
\note By default, the GrassLoader doesn't know what shape your terrain so all grass will be placed at
|
|
0 height. To inform GrassLoader of the shape of your terrain, you must specify a height function
|
|
that returns the height (y coordinate) of your terrain at the given x and z coordinates. See
|
|
the TreeLoader2D::setHeightFunction() documentation for more information.
|
|
|
|
\warning If you attempt to use Ogre's scene queries to get the terrain height,
|
|
keep in mind that calculating the height of Ogre's built-in terrain this way can
|
|
be VERY slow if not done properly, and may cause stuttering due to long paging delays.
|
|
*/
|
|
template <typename TGrassLayer>
|
|
class GrassLoader: public PageLoader
|
|
{
|
|
public:
|
|
/** \brief Creates a new GrassLoader object.
|
|
\param geom The PagedGeometry object that this GrassLoader will be assigned to.*/
|
|
inline GrassLoader(PagedGeometry *geom);
|
|
~GrassLoader();
|
|
|
|
/** \brief Adds a grass layer to the scene.
|
|
\param material The initial grass texture to use (this can be changed later).
|
|
|
|
Since all grass is potentially infinite, it is not added like normal entities which
|
|
have a specific position. Instead you add a grass "layer" to the scene. A grass layer
|
|
is a "carpet" of a single type of grass that gets applied everywhere in your world.
|
|
If you want multiple types of grass with different appearances, you'll have to add
|
|
a multiple grass layers for each style.
|
|
|
|
Of course, a grass layer is not completely uniform. The GrassLayer class contains
|
|
functions to vary grass size and density levels as desired.
|
|
|
|
\see GrassLayer class for more information. */
|
|
TGrassLayer *addLayer(const Ogre::String &material);
|
|
|
|
/** \brief Removes and deletes a grass layer from the scene
|
|
|
|
This function simply deletes a GrassLayer previously created with addLayer(). */
|
|
void deleteLayer(TGrassLayer *layer);
|
|
|
|
/** \brief Returns a list of added grass layers.
|
|
|
|
This function returns a std::list<GrassLayer*> reference, which contains all grass
|
|
layers which have been added to this GrassLoader. */
|
|
inline std::list<TGrassLayer*> &getLayerList() { return layerList; }
|
|
|
|
/** \brief Sets the global wind direction for this GrassLoader.
|
|
|
|
GrassLayer animation properties are used to configure the most of the animation
|
|
behavior (sway length, speed, etc.), but wind direction is not included in GrassLayer
|
|
since this is really a global property. Using this function, you can set the "global"
|
|
wind direction which affects all animated grass associated with this PageLoader.
|
|
|
|
Default value is Vector3::UNIT_X.
|
|
|
|
\note This only affects grass layers which have breeze animations enabled.
|
|
*/
|
|
inline void setWindDirection(Ogre::Vector3 &dir) { windDir = dir; }
|
|
|
|
inline void setBuildEdgesEnabled(bool value) { autoEdgeBuildEnabled=value; }
|
|
inline bool getBuildEdgesEnabled() { return autoEdgeBuildEnabled; }
|
|
|
|
/** \brief Returns the global wind direction for this GrassLoader.
|
|
|
|
\see setWindDirection() for more information about the wind direction. */
|
|
inline Ogre::Vector3 &getWindDirection() { return windDir; }
|
|
|
|
/** \brief Sets the global density factor for this GrassLoader.
|
|
|
|
This function can be used to up-scale or down-scale the density of all grass
|
|
associated with this GrassLoader. This is typically used to provide the user
|
|
the option to reduce grass density for better performance on slower machines.
|
|
|
|
Final density values are calculated by multiplying the layer density by this
|
|
density factor. For example, a layer with .4 density and a density factor of .5
|
|
will result in a final density of .2 (.5 * .4)
|
|
|
|
By default, the density factor is set to 1.0 so the layer density is not modified.
|
|
*/
|
|
inline void setDensityFactor(float density) { densityFactor = density; }
|
|
|
|
/** \brief Returns the global density factor for this GrassLoader.
|
|
|
|
\see setDensityFactor() for more information about the density factor. */
|
|
inline float getDensityFactor() { return densityFactor; }
|
|
|
|
/** \brief Sets the render queue group the grass will be rendered through
|
|
\param queueID Enumerated value of the queue group to use
|
|
|
|
Like Ogre's MovableObject::setRenderQueueGroup(), this allows you to customize
|
|
the rendering order of your scene. Since grass is transparent, it's likely that
|
|
you may encounter alpha-sorting issues between grass and your particle effects,
|
|
for example. In this case you can use this function to adjust the rendering order
|
|
of the grass to fix the problem.
|
|
|
|
If you don't call this function, the RENDER_QUEUE_6 queue will be used.
|
|
|
|
\note Once grass is loaded and being rendered, this won't have any effect on it.
|
|
Be sure to call this function before the scene begins rendering, otherwise you will
|
|
have to call PagedGeometry::reloadGeometry() to force a reload in order for the changes
|
|
to take effect. */
|
|
inline void setRenderQueueGroup(Ogre::uint8 queueID) { renderQueue = queueID; }
|
|
|
|
/** \brief Sets the height function used to calculate grass Y coordinates
|
|
\param heightFunction A pointer to a height function
|
|
|
|
Unless you want all your grass placed at 0 height, you need to specify a height function
|
|
so GrassLoader will be able to calculate the Y coordinate. The height function given
|
|
to setHeightFunction() should use the following prototype (although you can name the
|
|
function anything you want):
|
|
|
|
\code
|
|
Real getHeightAt(Real x, Real z, void *userData);
|
|
\endcode
|
|
|
|
\note If you're not using the default coordinate system (where x = right, z = back), the
|
|
x/z parameters will actually be representing the appropriate equivalents.
|
|
|
|
The userData parameter allows you to include any additional data you want when your height
|
|
function is called, and is completely optional (although you can't actually omit it from the
|
|
declaration, you can ignore it). Any userData value you choose to supply to setHeightFunction()
|
|
will be passed on to your height function every time it is called.
|
|
|
|
After you've defined a height function, using setHeightFunction is easy:
|
|
|
|
\code
|
|
pageLoader2D->setHeightFunction(&getHeightAt);
|
|
//Or (if you want to pass additional data on to your height function)...
|
|
pageLoader2D->setHeightFunction(&getHeightAt, myUserData);
|
|
\endcode
|
|
|
|
In most cases, you may not even need to use the extra "userData" parameter, but it's there in
|
|
the event that your height function needs extra contextual data.
|
|
*/
|
|
void setHeightFunction(Ogre::Real (*heightFunction)(Ogre::Real x, Ogre::Real z, void *userData), void *userData = NULL) {
|
|
this->heightFunction = heightFunction;
|
|
heightFunctionUserData = userData;
|
|
}
|
|
|
|
bool preparePage(PageInfo &page);
|
|
|
|
/** INTERNAL FUNCTION - DO NOT USE */
|
|
void loadPage(PageInfo &page);
|
|
/** INTERNAL FUNCTION - DO NOT USE */
|
|
void unloadPage(PageInfo &page);
|
|
/** INTERNAL FUNCTION - DO NOT USE */
|
|
void frameUpdate();
|
|
|
|
static float getRangeRandom(float start, float end);
|
|
|
|
|
|
private:
|
|
friend class GrassLayer;
|
|
|
|
//Helper functions
|
|
Ogre::Mesh *generateGrass_QUAD(PageInfo &page, TGrassLayer *layer, float *grassPositions, unsigned int grassCount);
|
|
Ogre::Mesh *generateGrass_CROSSQUADS(PageInfo &page, TGrassLayer *layer, float *grassPositions, unsigned int grassCount);
|
|
Ogre::Mesh *generateGrass_SPRITE(PageInfo &page, TGrassLayer *layer, float *grassPositions, unsigned int grassCount);
|
|
|
|
//List of grass types
|
|
std::list<TGrassLayer*> layerList;
|
|
|
|
//Height data
|
|
Ogre::Real (*heightFunction)(Ogre::Real x, Ogre::Real z, void *userData); //Pointer to height function
|
|
void *heightFunctionUserData;
|
|
|
|
//Misc.
|
|
PagedGeometry *geom;
|
|
Ogre::uint8 renderQueue;
|
|
float densityFactor;
|
|
|
|
// random table
|
|
RandomTable *rTable;
|
|
|
|
//Animation
|
|
Ogre::Timer windTimer;
|
|
Ogre::Vector3 windDir;
|
|
unsigned long lastTime;
|
|
|
|
bool autoEdgeBuildEnabled;
|
|
|
|
static unsigned long GUID;
|
|
static inline Ogre::String getUniqueID()
|
|
{
|
|
return "GrassLDR" + Ogre::StringConverter::toString(++GUID);
|
|
}
|
|
};
|
|
|
|
|
|
/** \brief A technique used to render grass. Passed to GrassLayer::setRenderTechnique(). */
|
|
enum GrassTechnique
|
|
{
|
|
/// Grass constructed of randomly placed and rotated quads
|
|
GRASSTECH_QUAD,
|
|
/// Grass constructed of two quads forming a "X" cross shape
|
|
GRASSTECH_CROSSQUADS,
|
|
/// Grass constructed of camera-facing billboard quads
|
|
GRASSTECH_SPRITE
|
|
};
|
|
|
|
/** \brief A technique used to fade grass into the distance. Passed to GrassLayer::setFadeTechnique(). */
|
|
enum FadeTechnique
|
|
{
|
|
/// Grass that fades into the distance with transparency. Fairly effective in most cases.
|
|
FADETECH_ALPHA,
|
|
/// Grass that fades in by "growing" up out of the ground. Very effective when grass fades in against the sky, or with alpha-rejected grass.
|
|
FADETECH_GROW,
|
|
/// Grass that fades in by slowly becoming opaque while it "grows" up out of the ground. Effective with alpha grass fading in against the sky.
|
|
FADETECH_ALPHAGROW
|
|
};
|
|
|
|
class GrassLayerBase
|
|
{
|
|
public:
|
|
/** \brief Sets the material that is applied to all grass billboards/quads */
|
|
void setMaterialName(const Ogre::String &matName);
|
|
|
|
/** \brief Sets the minimum size that grass quads/billboards will be */
|
|
void setMinimumSize(float width, float height);
|
|
|
|
/** \brief Sets the maximum size that grass quads/billboards will be */
|
|
void setMaximumSize(float width, float height);
|
|
|
|
/** \brief Sets the technique used to render this grass layer
|
|
\param style The GrassTechnique style used to display grass.
|
|
\param blendBase Whether or not grass base blending is enabled.
|
|
|
|
The "style" setting allows you to choose from various construction methods, such as
|
|
sprite-style grass quads, plain 3D quads, etc. See the GrassTechnique documentation
|
|
for more information about this option. GRASSTECH_QUAD is used by default.
|
|
|
|
Setting "blendBase" to true will enable grass base blending, a technique which helps
|
|
reduce the unnatural flat appearance of grass quads near the camera. Since the flatness
|
|
is most obvious where the grass intersects the terrain, this technique attempts to
|
|
smoothly blend the base of near-by grass into the terrain.
|
|
|
|
\note Base blending does not work well with alpha-rejected textures.
|
|
*/
|
|
void setRenderTechnique(GrassTechnique style, bool blendBase = false);
|
|
|
|
/** \brief Sets the technique used when fading out distant grass
|
|
\param style The FadeTechnique style used to fade grass.
|
|
|
|
This "style" setting allows you to choose from various fade techniques. Depending on
|
|
your scene, certain techniques may look better than others. The most compatible method
|
|
is FADETECH_ALPHA (used by default), although better results can usually be achieved
|
|
with other methods. See the FadeTechnique documentation for more information.
|
|
*/
|
|
void setFadeTechnique(FadeTechnique style);
|
|
|
|
/** \brief Enables/disables animation on this layer
|
|
|
|
Always use this function to disable animation, rather than setting SwayLength or SwaySpeed
|
|
to 0. This function will use a different vertex shader which means improved performance
|
|
when animation is disabled.
|
|
*/
|
|
void setAnimationEnabled(bool enabled);
|
|
|
|
/** \brief Enables/disables lighting on this layer
|
|
*/
|
|
void setLightingEnabled(bool enabled);
|
|
|
|
/** \brief Whether vertex colours are enabled on this layer.
|
|
*
|
|
* If you're using a shader to provide dynamic lightning you probably want to disable vertex colours.
|
|
*/
|
|
virtual bool isColoursEnabled() const;
|
|
|
|
/** \brief Whether normals are enabled on this layer.
|
|
*/
|
|
virtual bool isNormalsEnabled() const;
|
|
|
|
/** \brief Whether tangents are enabled on this layer.
|
|
*
|
|
*/
|
|
virtual bool isTangentsEnabled() const;
|
|
|
|
/** \brief Whether shadow casting should be enabled for the mesh generated.
|
|
*/
|
|
virtual bool isCastShadowsEnabled() const;
|
|
|
|
/** \brief Sets how far grass should sway back and forth
|
|
|
|
\note Since this is measured in world units, you may have to adjust this depending on
|
|
the size of your grass as set by setMinimumSize() and setMaximumSize().*/
|
|
void setSwayLength(float mag) { animMag = mag; }
|
|
|
|
/** \brief Sets the sway speed of the grass (measured in "sways-per-second") */
|
|
void setSwaySpeed(float speed) { animSpeed = speed; }
|
|
|
|
/** \brief Sets the smooth distribution (positional phase shift) of the grass swaying animation
|
|
|
|
If you set this to 0, grass animation will look very unnatural, since all the grass sway motions
|
|
will be in perfect synchronization (everything sways to the right, then everything sways to the
|
|
left, etc.) This sets the "positional phase shift", which gives the grass a "wave" like phase
|
|
distribution. The higher this value is, the more "chaotic" the wind will appear. Lower values give
|
|
a smoother breeze appearance, but values too high can look unrealistic.
|
|
*/
|
|
void setSwayDistribution(float freq) { animFreq = freq; }
|
|
|
|
/** \brief Sets the boundaries of the density/color maps
|
|
\param bounds The map boundary
|
|
|
|
By default, the GrassLayer has no knowledge of your terrain/world boundaries, so you must
|
|
use this function to specify a rectangular/square area of your world, otherwise density/color maps
|
|
won't work properly. The boundary given to this function defines the area where density/color
|
|
maps take effect. Normally this is set to your terrain's bounds so the density/color map is aligned
|
|
to your heightmap, but you could apply it anywhere you want.
|
|
|
|
\note The grass system is infinite, so there's no need to worry about using too expansive
|
|
boundaries. This setting simply configures the behavior of density and color maps. */
|
|
virtual void setMapBounds(const Ogre::TRect<Ogre::Real> &bounds)
|
|
{
|
|
mapBounds = bounds;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Calculates the max number of grass instances for this layer.
|
|
* @param page The page to create grass for.
|
|
* @param densityFactor The density factor set on the grass loader
|
|
* @param volume The volume, in world units, to fill
|
|
* @param isAvailable This must be set to true if the grass is currently available.
|
|
* @return The max number of grass instances to create.
|
|
*/
|
|
virtual unsigned int prepareGrass(const PageInfo& page, float densityFactor, float volume, bool& isAvailable) = 0;
|
|
|
|
/** \brief Set the maximum slope a grass of blade can be placed on.
|
|
\param maxSlopeRatio The maximum slope (h/w ratio) a grass blade is allowed to be placed on.
|
|
|
|
This function can be used to set the maximum slope you want your grass to be placed on
|
|
(although it doesn't work for sprite grass). By default grass is allowed on any slope.
|
|
|
|
This version of setMaxSlope() accepts a slope ratio value, where ATan(maxSlopeRatio) =
|
|
maxSlopeAngle. If you wish to provide a maximum slope as an angle, either use the other
|
|
overload of this function, or convert your angle to a slope ratio first with Tan().*/
|
|
void setMaxSlope(const float maxSlopeRatio) { maxSlope = maxSlopeRatio; }
|
|
|
|
void setMaxSlope(Ogre::Radian maxSlopeAngle) {
|
|
if (maxSlopeAngle > Ogre::Degree(89.99f))
|
|
maxSlopeAngle = Ogre::Degree(89.99f);
|
|
if (maxSlopeAngle < Ogre::Degree(0))
|
|
maxSlopeAngle = Ogre::Degree(0);
|
|
|
|
maxSlope = Ogre::Math::Tan(maxSlopeAngle);
|
|
}
|
|
|
|
/** \brief Get the maximum slope a grass blade can be placed on (as set by setMaxSlope()).
|
|
\returns The currently set maximum slope ratio value (not an angle).
|
|
|
|
This returns the currently set maximum slope which is used to determine what ground is too steep
|
|
for grass to be placed on. Note that this returns the slope as a slope ratio, not an angle. If you
|
|
need an angle value, convert with ATan() (maxSlopeAngle = ATan(maxSlopeRatio)).*/
|
|
float getMaxSlope() const { return maxSlope; }
|
|
|
|
protected:
|
|
|
|
//Used by GrassLoader::loadPage() - populates an array with grass.
|
|
//Returns the final number of grasses, which will always be <= grassCount
|
|
virtual unsigned int _populateGrassList(PageInfo page, float *posBuff, unsigned int grassCount) = 0;
|
|
|
|
//Updates the vertex shader used by this layer based on the animate enable status
|
|
void _updateShaders();
|
|
|
|
//Grass material/shape properties
|
|
Ogre::MaterialPtr material;
|
|
float minWidth, maxWidth;
|
|
float minHeight, maxHeight;
|
|
float maxSlope;
|
|
|
|
FadeTechnique fadeTechnique;
|
|
GrassTechnique renderTechnique;
|
|
|
|
//Property maps
|
|
Ogre::TRect<Ogre::Real> mapBounds;
|
|
|
|
//Grass shader properties
|
|
bool animate, blend, lighting, shaderNeedsUpdate;
|
|
float animMag, animSpeed, animFreq;
|
|
|
|
//Current frame of animation for this layer
|
|
float waveCount;
|
|
|
|
PagedGeometry *geom;
|
|
|
|
};
|
|
|
|
|
|
/** \brief A data structure giving you full control over grass properties.
|
|
|
|
Grass is added to the scene through GrassLoader::addLayer(). Through this class you
|
|
can configure your grass layer any way you like - size, density, render technique,
|
|
animation, etc. Simply call the appropriate "set" member function to set the desired property.
|
|
|
|
Remember that you cannot create or delete layers directly. Layers can only be created
|
|
with GrassLoader::addLayer(), and may not be deleted manually (they will be deleted when
|
|
the associated GrassLoader is deleted).
|
|
*/
|
|
class GrassLayer : public GrassLayerBase
|
|
{
|
|
public:
|
|
|
|
/** \brief Sets the maximum density (measured in grass quads/billboards per square unit) of grass */
|
|
void setDensity(float density) { this->density = density; }
|
|
|
|
/** \brief Sets a minimum / maximum height where grass may appear
|
|
\param minHeight Sets the minimum height grass may have. 0 = no minimum
|
|
\param maxHeight Sets the maximum height grass may have. 0 = no maximum
|
|
|
|
By default grass appears at all altitudes. You can use this function to restrict grass to a
|
|
certain height range. For example, if sea level is at 100 units Y, you might restrict this
|
|
layer to display only above 100 units (so your grass doesn't grow under water).
|
|
|
|
It's possible to use density maps (see setDensityMap()) to achieve similar results, but if
|
|
your density map is extremely low resolution, this function may be the only practical option
|
|
to prevent grass from growing under water (when used in combination with your density map).
|
|
|
|
Setting minHeight to 0 means grass has no minimum height - it can grow as low as necessary.
|
|
Similarly, setting maxHeight to 0 means grass has no maximum height - it can grow as high
|
|
as necessary. */
|
|
void setHeightRange(float minHeight, float maxHeight = 0) { minY = minHeight; maxY = maxHeight; }
|
|
|
|
/** \brief Sets the density map used for this grass layer
|
|
\param mapFile The density map image
|
|
\param channel The color channel(s) to from the image to interpret as density
|
|
|
|
A density map is simply a greyscale image, similar to a heightmap, that specifies the grass
|
|
density on your map. Full pixel intensity indicates that grass should be fully dense at that
|
|
point (the maximum density is specified by GrassLayer::setDensity()), while no pixel intensity
|
|
indicates that no grass should appear at that location.
|
|
|
|
The channel parameter allows you to extract the density information from the image's
|
|
red, green, blue, alpha, or color values. For example, you may store density values in the
|
|
alpha channel, in which case you would use CHANNEL_ALPHA. By default, CHANNEL_COLOR is used,
|
|
which means the image color is converted to greyscale internally and used as a density map.
|
|
|
|
Note that GrassLayer by default has no idea of your terrain/world boundaries, so you
|
|
must specify a rectangular/square area of your world that is affected by density/color maps.
|
|
To do this, use the setMapBounds() function. Normally this is set to your terrain's bounds
|
|
so the density/color map is aligned to your heightmap, but you could apply it anywhere you
|
|
want. */
|
|
void setDensityMap(const Ogre::String &mapFile, MapChannel channel = CHANNEL_COLOR);
|
|
|
|
/** \brief Sets the density map used for this grass layer
|
|
|
|
Overloaded to accept a Texture object. See the original setDensityMap() documentation above
|
|
for more detailed information on density maps.
|
|
|
|
\note The texture data you provide is copied into the GrassLayer's own memory space, so you
|
|
can delete the texture after calling this function without risk of crashing. */
|
|
void setDensityMap(Ogre::TexturePtr map, MapChannel channel = CHANNEL_COLOR);
|
|
|
|
/** \brief Sets the filtering mode used for density maps
|
|
|
|
This function can be used to set the filtering mode used for your density map when generating
|
|
grass. By default, bilinear filtering is used (MAPFILTER_BILINEAR). If you disable filtering
|
|
by using MAPFILTER_NONE, the resulting layout of your grass may look square and blocky,
|
|
depending on the resolution of your density map.
|
|
|
|
MAPFILTER_NONE is slightly faster than MAPFILTER_BILINEAR, so use it if you don't notice any
|
|
considerable blockiness.
|
|
*/
|
|
void setDensityMapFilter(MapFilter filter);
|
|
|
|
/** \brief Sets the color map used for this grass layer
|
|
\param mapFile The color map image
|
|
\param channel The color channel(s) to from the image to use
|
|
|
|
A color map is simply a texture that allows you to vary the color and shading of grass
|
|
across your world for a more realistic look. For example, adding a dark spot to the center
|
|
of your color map will make grass near the center of your terrain look darker, as long as
|
|
you have the color map aligned to your terrain (see setMapBounds()).
|
|
|
|
The channel parameter allows you to extract the color information from the image's
|
|
red, green, blue, alpha, or color values. For example, you may store the desired shade of your
|
|
grass in the red channel of an image, in which case you would use CHANNEL_RED (when you choose
|
|
a single channel, it is converted to a greyscale color). By default, CHANNEL_COLOR is used,
|
|
which uses the full color information available in the image.
|
|
|
|
Remember that GrassLayer by default has no idea of your terrain/world boundaries, so you
|
|
must specify a rectangular/square area of your world that is affected by density/color maps.
|
|
To do this, use the setMapBounds() function. Normally this is set to your terrain's bounds
|
|
so the density/color map is aligned to your heightmap, but you could apply it anywhere you
|
|
want. */
|
|
void setColorMap(const Ogre::String &mapFile, MapChannel channel = CHANNEL_COLOR);
|
|
|
|
/** \brief Sets the color map used for this grass layer
|
|
|
|
Overloaded to accept a Texture object. See the original setColorMap() documentation above
|
|
for more detailed information on color maps.
|
|
|
|
\note The texture data you provide is copied into RAM, so you can delete the texture after
|
|
calling this function without risk of crashing. */
|
|
void setColorMap(Ogre::TexturePtr map, MapChannel channel = CHANNEL_COLOR);
|
|
|
|
/** \brief Sets the filtering mode used for color maps
|
|
|
|
This function can be used to set the filtering mode used for your color map when generating
|
|
grass. By default, bilinear filtering is used (MAPFILTER_BILINEAR). If you disable filtering
|
|
by using MAPFILTER_NONE, the resulting grass coloration may appear slightly pixelated,
|
|
depending on the resolution of your color map.
|
|
|
|
MAPFILTER_NONE is slightly faster than MAPFILTER_BILINEAR, so use it if you don't notice any
|
|
considerable pixelation.
|
|
*/
|
|
void setColorMapFilter(MapFilter filter);
|
|
|
|
/** \brief Gets a pointer to the density map being used
|
|
|
|
You can use this function to access the internal density map object used by the GrassLoader.
|
|
Through this object you can directly manipulate the pixels of the density map, among other
|
|
things.
|
|
|
|
Note that although you can edit the density map in real-time through this class, the changes
|
|
won't be uploaded to your video card until you call PagedGeometry::reloadGeometry(). If you
|
|
don't, the grass you see will remain unchanged. */
|
|
DensityMap *getDensityMap() { return densityMap; }
|
|
|
|
/** \brief Gets a pointer to the color map being used
|
|
|
|
You can use this function to access the internal color map object used by the GrassLoader.
|
|
Through this object you can directly manipulate the pixels of the color map, among other
|
|
things.
|
|
|
|
Note that although you can edit the color map in real-time through this class, the changes
|
|
won't be uploaded to your video card until you call PagedGeometry::reloadGeometry(). If you
|
|
don't, the grass you see will remain unchanged. */
|
|
ColorMap *getColorMap() { return colorMap; }
|
|
|
|
virtual unsigned int prepareGrass(const PageInfo& page, float densityFactor, float volume, bool& isAvailable);
|
|
|
|
|
|
/**
|
|
* If there's a colormap registered use that for lookup, else return fullbright.
|
|
* @param x
|
|
* @param z
|
|
* @return
|
|
*/
|
|
inline Ogre::uint32 getColorAt(float x, float z)
|
|
{
|
|
if (colorMap)
|
|
return colorMap->getColorAt(x, z, mapBounds);
|
|
else
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
private:
|
|
friend class GrassLoader<GrassLayer>;
|
|
|
|
/** \brief Do not create a GrassLayer directly - use GrassLoader->addLayer() */
|
|
GrassLayer(PagedGeometry *geom, GrassLoader<GrassLayer> *ldr);
|
|
|
|
/** \brief Do not delete a GrassLayer yourself - the GrassLoader will do this automatically when it's deleted */
|
|
~GrassLayer();
|
|
|
|
//Used by GrassLoader::loadPage() - populates an array with grass.
|
|
//Returns the final number of grasses, which will always be <= grassCount
|
|
virtual unsigned int _populateGrassList(PageInfo page, float *posBuff, unsigned int grassCount);
|
|
|
|
//Used by GrassLoader::loadPage() - populates an array with a uniform distribution of grass
|
|
//Returns the final number of grasses, which will always be <= grassCount
|
|
unsigned int _populateGrassList_Uniform(PageInfo page, float *posBuff, unsigned int grassCount);
|
|
|
|
//Used by GrassLoader::loadPage() - populates an array of grass positions based on the density map
|
|
//Returns the final number of grasses, which will always be <= grassCount
|
|
unsigned int _populateGrassList_UnfilteredDM(PageInfo page, float *posBuff, unsigned int grassCount);
|
|
|
|
//Variation of _populateGrassList(), using bilinear filtering on the density map lookups
|
|
//Returns the final number of grasses, which will always be <= grassCount
|
|
unsigned int _populateGrassList_BilinearDM(PageInfo page, float *posBuff, unsigned int grassCount);
|
|
|
|
|
|
GrassLoader<GrassLayer> *parent;
|
|
|
|
//Grass material/shape properties
|
|
float density;
|
|
float minY, maxY;
|
|
|
|
|
|
DensityMap *densityMap;
|
|
MapFilter densityMapFilter;
|
|
|
|
ColorMap *colorMap;
|
|
MapFilter colorMapFilter;
|
|
|
|
};
|
|
|
|
|
|
/** \brief A custom page type designed specifically for use with GrassLoader.
|
|
|
|
You can use this in your own project if you want, but remember that no optimizations
|
|
are performed. The given entity is simply cloned and attached to a new scene node as
|
|
quickly and simply as possible (this means there's no batching overhead as in BatchPage,
|
|
but it also means potentially poor performance if you don't know what you're doing).
|
|
*/
|
|
class GrassPage: public GeometryPage
|
|
{
|
|
public:
|
|
void init(PagedGeometry *geom, const Ogre::Any &data);
|
|
~GrassPage();
|
|
|
|
void addEntity(Ogre::Entity *ent, const Ogre::Vector3 &position, const Ogre::Quaternion &rotation, const Ogre::Vector3 &scale, const Ogre::ColourValue &color);
|
|
void removeEntities();
|
|
void setFade(bool enabled, Ogre::Real visibleDist, Ogre::Real invisibleDist) {}
|
|
void setVisible(bool visible);
|
|
|
|
private:
|
|
Ogre::SceneManager *sceneMgr;
|
|
Ogre::SceneNode *rootNode;
|
|
|
|
std::list<Ogre::SceneNode*> nodeList;
|
|
|
|
static unsigned long GUID;
|
|
static inline Ogre::String getUniqueID()
|
|
{
|
|
return "GrassPage" + Ogre::StringConverter::toString(++GUID);
|
|
}
|
|
};
|
|
|
|
template <class TGrassLayer>
|
|
GrassLoader<TGrassLayer>::GrassLoader(PagedGeometry *geom)
|
|
{
|
|
GrassLoader<TGrassLayer>::geom = geom;
|
|
|
|
// generate some random numbers
|
|
rTable = new RandomTable();
|
|
|
|
heightFunction = NULL;
|
|
heightFunctionUserData = NULL;
|
|
|
|
windDir = Ogre::Vector3::UNIT_X;
|
|
densityFactor = 1.0f;
|
|
renderQueue = geom->getRenderQueue();
|
|
|
|
windTimer.reset();
|
|
lastTime = 0;
|
|
}
|
|
|
|
template <class TGrassLayer>
|
|
GrassLoader<TGrassLayer>::~GrassLoader()
|
|
{
|
|
typename std::list<TGrassLayer*>::iterator it;
|
|
for (it = layerList.begin(); it != layerList.end(); ++it){
|
|
delete *it;
|
|
}
|
|
layerList.clear();
|
|
|
|
if(rTable)
|
|
{
|
|
delete(rTable);
|
|
rTable=0;
|
|
}
|
|
|
|
}
|
|
|
|
template <class TGrassLayer>
|
|
TGrassLayer *GrassLoader<TGrassLayer>::addLayer(const Ogre::String &material)
|
|
{
|
|
TGrassLayer *layer = new TGrassLayer(geom, this);
|
|
layer->setMaterialName(material);
|
|
layerList.push_back(layer);
|
|
|
|
return layer;
|
|
}
|
|
|
|
template <class TGrassLayer>
|
|
void GrassLoader<TGrassLayer>::deleteLayer(TGrassLayer *layer)
|
|
{
|
|
layerList.remove(layer);
|
|
delete layer;
|
|
}
|
|
|
|
template <class TGrassLayer>
|
|
void GrassLoader<TGrassLayer>::frameUpdate()
|
|
{
|
|
unsigned long currentTime = windTimer.getMilliseconds();
|
|
unsigned long ellapsedTime = currentTime - lastTime;
|
|
lastTime = currentTime;
|
|
|
|
float ellapsed = ellapsedTime / 1000.0f;
|
|
|
|
//Update the vertex shader parameters
|
|
typename std::list<TGrassLayer*>::iterator it;
|
|
for (it = layerList.begin(); it != layerList.end(); ++it){
|
|
TGrassLayer *layer = *it;
|
|
|
|
layer->_updateShaders();
|
|
|
|
Ogre::Technique* tech = layer->material->getBestTechnique();
|
|
if (tech && tech->getNumPasses()) {
|
|
Ogre::Pass* pass = tech->getPass(0);
|
|
if (pass->hasVertexProgram()) {
|
|
Ogre::GpuProgramParametersSharedPtr params = pass->getVertexProgramParameters();
|
|
if (params && layer->animate){
|
|
//Increment animation frame
|
|
layer->waveCount += ellapsed * (layer->animSpeed * Ogre::Math::PI);
|
|
if (layer->waveCount > Ogre::Math::PI*2) layer->waveCount -= Ogre::Math::PI*2;
|
|
|
|
//Set vertex shader parameters
|
|
params->setNamedConstant("time", layer->waveCount);
|
|
params->setNamedConstant("frequency", layer->animFreq);
|
|
|
|
Ogre::Vector3 direction = windDir * layer->animMag;
|
|
params->setNamedConstant("direction", Ogre::Vector4(direction.x, direction.y, direction.z, 0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <class TGrassLayer>
|
|
bool GrassLoader<TGrassLayer>::preparePage(PageInfo &page)
|
|
{
|
|
bool result = true;
|
|
//Calculate how much grass needs to be added
|
|
float volume = page.bounds.width() * page.bounds.height();
|
|
typename std::list<TGrassLayer*>::iterator it;
|
|
for (it = layerList.begin(); it != layerList.end(); ++it){
|
|
TGrassLayer *layer = *it;
|
|
bool isAvailable = false;
|
|
layer->prepareGrass(page, densityFactor, volume, isAvailable);
|
|
result = result && isAvailable;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
template <class TGrassLayer>
|
|
void GrassLoader<TGrassLayer>::loadPage(PageInfo &page)
|
|
{
|
|
//Generate meshes
|
|
typename std::list<TGrassLayer*>::iterator it;
|
|
for (it = layerList.begin(); it != layerList.end(); ++it){
|
|
TGrassLayer *layer = *it;
|
|
layer->material->prepare();
|
|
|
|
// Continue to the next layer if the current page is outside of the layers map boundaries.
|
|
if(layer->mapBounds.right < page.bounds.left || layer->mapBounds.left > page.bounds.right ||
|
|
layer->mapBounds.bottom < page.bounds.top || layer->mapBounds.top > page.bounds.bottom)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
//Calculate how much grass needs to be added
|
|
float volume = page.bounds.width() * page.bounds.height();
|
|
bool isAvailable = false;
|
|
unsigned int grassCount = layer->prepareGrass(page, densityFactor, volume, isAvailable);
|
|
|
|
if (isAvailable && grassCount) {
|
|
//The vertex buffer can't be allocated until the exact number of polygons is known,
|
|
//so the locations of all grasses in this page must be precalculated.
|
|
|
|
//Precompute grass locations into an array of floats. A plain array is used for speed;
|
|
//there's no need to use a dynamic sized array since a maximum size is known.
|
|
float *position = new float[grassCount*4];
|
|
grassCount = layer->_populateGrassList(page, position, grassCount);
|
|
|
|
//Don't build a mesh unless it contains something
|
|
if (grassCount != 0){
|
|
Mesh *mesh = NULL;
|
|
switch (layer->renderTechnique){
|
|
case GRASSTECH_QUAD:
|
|
mesh = generateGrass_QUAD(page, layer, position, grassCount);
|
|
break;
|
|
case GRASSTECH_CROSSQUADS:
|
|
mesh = generateGrass_CROSSQUADS(page, layer, position, grassCount);
|
|
break;
|
|
case GRASSTECH_SPRITE:
|
|
mesh = generateGrass_SPRITE(page, layer, position, grassCount);
|
|
break;
|
|
}
|
|
assert(mesh);
|
|
|
|
//Add the mesh to PagedGeometry
|
|
Entity *entity = geom->getCamera()->getSceneManager()->createEntity(getUniqueID(), mesh->getName());
|
|
entity->setRenderQueueGroup(renderQueue);
|
|
entity->setCastShadows(layer->isCastShadowsEnabled());
|
|
addEntity(entity, page.centerPoint, Quaternion::IDENTITY, Vector3::UNIT_SCALE);
|
|
|
|
//Store the mesh pointer
|
|
page.meshList.push_back(mesh);
|
|
}
|
|
|
|
//Delete the position list
|
|
delete[] position;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
template <class TGrassLayer>
|
|
void GrassLoader<TGrassLayer>::unloadPage(PageInfo &page)
|
|
{
|
|
// we unload the page in the page's destructor
|
|
}
|
|
template <class TGrassLayer>
|
|
Mesh *GrassLoader<TGrassLayer>::generateGrass_QUAD(PageInfo &page, TGrassLayer *layer, float *grassPositions, unsigned int grassCount)
|
|
{
|
|
//Calculate the number of quads to be added
|
|
unsigned int quadCount;
|
|
quadCount = grassCount;
|
|
|
|
// check for overflows of the uint16's
|
|
unsigned int maxUInt16 = std::numeric_limits<uint16>::max();
|
|
if(grassCount > maxUInt16)
|
|
{
|
|
LogManager::getSingleton().logMessage("grass count overflow: you tried to use more than " + StringConverter::toString(maxUInt16) + " (that's the maximum) grass meshes for one page");
|
|
return 0;
|
|
}
|
|
if(quadCount > maxUInt16)
|
|
{
|
|
LogManager::getSingleton().logMessage("quad count overflow: you tried to use more than " + StringConverter::toString(maxUInt16) + " (that's the maximum) grass meshes for one page");
|
|
return 0;
|
|
}
|
|
|
|
//Create manual mesh to store grass quads
|
|
MeshPtr mesh = MeshManager::getSingleton().createManual(getUniqueID(), ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
|
SubMesh *subMesh = mesh->createSubMesh();
|
|
subMesh->useSharedVertices = false;
|
|
|
|
//Setup vertex format information
|
|
subMesh->vertexData = new VertexData;
|
|
subMesh->vertexData->vertexStart = 0;
|
|
subMesh->vertexData->vertexCount = 4 * quadCount;
|
|
|
|
VertexDeclaration* dcl = subMesh->vertexData->vertexDeclaration;
|
|
size_t offset = 0;
|
|
bool colours = layer->isColoursEnabled();
|
|
bool normals = layer->isNormalsEnabled();
|
|
dcl->addElement(0, offset, VET_FLOAT3, VES_POSITION);
|
|
offset += VertexElement::getTypeSize(VET_FLOAT3);
|
|
if (colours) {
|
|
dcl->addElement(0, offset, VET_COLOUR, VES_DIFFUSE);
|
|
offset += VertexElement::getTypeSize(VET_COLOUR);
|
|
}
|
|
dcl->addElement(0, offset, VET_FLOAT2, VES_TEXTURE_COORDINATES);
|
|
offset += VertexElement::getTypeSize(VET_FLOAT2);
|
|
if (normals) {
|
|
dcl->addElement(0, offset, VET_FLOAT4, VES_NORMAL);
|
|
offset += VertexElement::getTypeSize(VET_FLOAT4);
|
|
}
|
|
|
|
//Populate a new vertex buffer with grass
|
|
HardwareVertexBufferSharedPtr vbuf = HardwareBufferManager::getSingleton()
|
|
.createVertexBuffer(offset, subMesh->vertexData->vertexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
|
|
float* pReal = static_cast<float*>(vbuf->lock(HardwareBuffer::HBL_DISCARD));
|
|
|
|
//Calculate size variance
|
|
float rndWidth = layer->maxWidth - layer->minWidth;
|
|
float rndHeight = layer->maxHeight - layer->minHeight;
|
|
Vector3 normal(0.0f, 1.0f, 0.0f); //We'll use a normal pointing straight up, to best simulate grass and sunlight.
|
|
|
|
float minY = Math::POS_INFINITY, maxY = Math::NEG_INFINITY;
|
|
float *posPtr = grassPositions; //Position array "iterator"
|
|
for (uint16 i = 0; i < grassCount; ++i)
|
|
{
|
|
//Get the x and z positions from the position array
|
|
float x = *posPtr++;
|
|
float z = *posPtr++;
|
|
|
|
//Get the color at the grass position
|
|
Ogre::uint32 color(0);
|
|
|
|
if (colours) {
|
|
color = layer->getColorAt(x, z);
|
|
}
|
|
//Calculate size
|
|
float rnd = *posPtr++; //The same rnd value is used for width and height to maintain aspect ratio
|
|
float halfScaleX = (layer->minWidth + rndWidth * rnd) * 0.5f;
|
|
float scaleY = (layer->minHeight + rndHeight * rnd);
|
|
|
|
//Calculate rotation
|
|
float angle = *posPtr++;
|
|
float xTrans = Math::Cos(angle) * halfScaleX;
|
|
float zTrans = Math::Sin(angle) * halfScaleX;
|
|
|
|
//Calculate heights and edge positions
|
|
float x1 = x - xTrans, z1 = z - zTrans;
|
|
float x2 = x + xTrans, z2 = z + zTrans;
|
|
|
|
float y1, y2;
|
|
if (heightFunction){
|
|
y1 = heightFunction(x1, z1, heightFunctionUserData);
|
|
y2 = heightFunction(x2, z2, heightFunctionUserData);
|
|
|
|
if (layer->getMaxSlope() < (Math::Abs(y1 - y2) / (halfScaleX * 2))) {
|
|
//Degenerate the face
|
|
x2 = x1;
|
|
y2 = y1;
|
|
z2 = z1;
|
|
}
|
|
} else {
|
|
y1 = 0;
|
|
y2 = 0;
|
|
}
|
|
|
|
|
|
//Add vertices
|
|
*pReal++ = (x1 - page.centerPoint.x); *pReal++ = (y1 + scaleY); *pReal++ = (z1 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 0; *pReal++ = 0; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
*pReal++ = (x2 - page.centerPoint.x); *pReal++ = (y2 + scaleY); *pReal++ = (z2 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 1; *pReal++ = 0; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
*pReal++ = (x1 - page.centerPoint.x); *pReal++ = (y1); *pReal++ = (z1 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 0; *pReal++ = 1; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
*pReal++ = (x2 - page.centerPoint.x); *pReal++ = (y2); *pReal++ = (z2 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 1; *pReal++ = 1; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
//Update bounds
|
|
if (y1 < minY) minY = y1;
|
|
if (y2 < minY) minY = y2;
|
|
if (y1 + scaleY > maxY) maxY = y1 + scaleY;
|
|
if (y2 + scaleY > maxY) maxY = y2 + scaleY;
|
|
}
|
|
|
|
vbuf->unlock();
|
|
subMesh->vertexData->vertexBufferBinding->setBinding(0, vbuf);
|
|
|
|
//Populate index buffer
|
|
subMesh->indexData->indexStart = 0;
|
|
subMesh->indexData->indexCount = 6 * quadCount;
|
|
subMesh->indexData->indexBuffer = HardwareBufferManager::getSingleton()
|
|
.createIndexBuffer(HardwareIndexBuffer::IT_16BIT, subMesh->indexData->indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
|
|
uint16* pI = static_cast<uint16*>(subMesh->indexData->indexBuffer->lock(HardwareBuffer::HBL_DISCARD));
|
|
for (uint16 i = 0; i < quadCount; ++i)
|
|
{
|
|
uint16 offset = i * 4;
|
|
|
|
*pI++ = 0 + offset;
|
|
*pI++ = 2 + offset;
|
|
*pI++ = 1 + offset;
|
|
|
|
*pI++ = 1 + offset;
|
|
*pI++ = 2 + offset;
|
|
*pI++ = 3 + offset;
|
|
}
|
|
|
|
subMesh->indexData->indexBuffer->unlock();
|
|
//subMesh->setBuildEdgesEnabled(autoEdgeBuildEnabled);
|
|
|
|
//Finish up mesh
|
|
AxisAlignedBox bounds(page.bounds.left - page.centerPoint.x, minY, page.bounds.top - page.centerPoint.z,
|
|
page.bounds.right - page.centerPoint.x, maxY, page.bounds.bottom - page.centerPoint.z);
|
|
mesh->_setBounds(bounds);
|
|
Vector3 temp = bounds.getMaximum() - bounds.getMinimum();
|
|
mesh->_setBoundingSphereRadius(temp.length() * 0.5f);
|
|
|
|
auto logLevel = LogManager::getSingleton().getDefaultLog()->getLogDetail();
|
|
LogManager::getSingleton().setLogDetail(static_cast<LoggingLevel>(0));
|
|
mesh->setAutoBuildEdgeLists(autoEdgeBuildEnabled);
|
|
mesh->load();
|
|
LogManager::getSingleton().setLogDetail(logLevel);
|
|
|
|
//Apply grass material to mesh
|
|
subMesh->setMaterialName(layer->material->getName());
|
|
|
|
if (layer->isTangentsEnabled()) {
|
|
mesh->buildTangentVectors();
|
|
}
|
|
|
|
//Return the mesh
|
|
return mesh.get();
|
|
}
|
|
|
|
template <class TGrassLayer>
|
|
Mesh *GrassLoader<TGrassLayer>::generateGrass_CROSSQUADS(PageInfo &page, TGrassLayer *layer, float *grassPositions, unsigned int grassCount)
|
|
{
|
|
//Calculate the number of quads to be added
|
|
unsigned int quadCount;
|
|
quadCount = grassCount * 2;
|
|
|
|
// check for overflows of the uint16's
|
|
unsigned int maxUInt16 = std::numeric_limits<uint16>::max();
|
|
if(grassCount > maxUInt16)
|
|
{
|
|
LogManager::getSingleton().logMessage("grass count overflow: you tried to use more than " + StringConverter::toString(maxUInt16) + " (that's the maximum) grass meshes for one page");
|
|
return 0;
|
|
}
|
|
if(quadCount > maxUInt16)
|
|
{
|
|
LogManager::getSingleton().logMessage("quad count overflow: you tried to use more than " + StringConverter::toString(maxUInt16) + " (that's the maximum) grass meshes for one page");
|
|
return 0;
|
|
}
|
|
|
|
//Create manual mesh to store grass quads
|
|
MeshPtr mesh = MeshManager::getSingleton().createManual(getUniqueID(), ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
|
SubMesh *subMesh = mesh->createSubMesh();
|
|
subMesh->useSharedVertices = false;
|
|
|
|
//Setup vertex format information
|
|
subMesh->vertexData = new VertexData;
|
|
subMesh->vertexData->vertexStart = 0;
|
|
subMesh->vertexData->vertexCount = 4 * quadCount;
|
|
|
|
VertexDeclaration* dcl = subMesh->vertexData->vertexDeclaration;
|
|
size_t offset = 0;
|
|
bool colours = layer->isColoursEnabled();
|
|
bool normals = layer->isNormalsEnabled();
|
|
dcl->addElement(0, offset, VET_FLOAT3, VES_POSITION);
|
|
offset += VertexElement::getTypeSize(VET_FLOAT3);
|
|
if (colours) {
|
|
dcl->addElement(0, offset, VET_COLOUR, VES_DIFFUSE);
|
|
offset += VertexElement::getTypeSize(VET_COLOUR);
|
|
}
|
|
dcl->addElement(0, offset, VET_FLOAT2, VES_TEXTURE_COORDINATES);
|
|
offset += VertexElement::getTypeSize(VET_FLOAT2);
|
|
if (normals) {
|
|
dcl->addElement(0, offset, VET_FLOAT4, VES_NORMAL);
|
|
offset += VertexElement::getTypeSize(VET_FLOAT4);
|
|
}
|
|
|
|
//Populate a new vertex buffer with grass
|
|
HardwareVertexBufferSharedPtr vbuf = HardwareBufferManager::getSingleton()
|
|
.createVertexBuffer(offset, subMesh->vertexData->vertexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
|
|
float* pReal = static_cast<float*>(vbuf->lock(HardwareBuffer::HBL_DISCARD));
|
|
|
|
//Calculate size variance
|
|
float rndWidth = layer->maxWidth - layer->minWidth;
|
|
float rndHeight = layer->maxHeight - layer->minHeight;
|
|
|
|
float minY = Math::POS_INFINITY, maxY = Math::NEG_INFINITY;
|
|
float *posPtr = grassPositions; //Position array "iterator"
|
|
Vector3 normal(0.0f, 1.0f, 0.0f); //We'll use a normal pointing straight up, to best simulate grass and sunlight.
|
|
for (uint16 i = 0; i < grassCount; ++i)
|
|
{
|
|
//Get the x and z positions from the position array
|
|
float x = *posPtr++;
|
|
float z = *posPtr++;
|
|
|
|
Ogre::uint32 color(0);
|
|
|
|
if (colours) {
|
|
//Get the color at the grass position
|
|
color = layer->getColorAt(x, z);
|
|
}
|
|
|
|
//Calculate size
|
|
float rnd = *posPtr++; //The same rnd value is used for width and height to maintain aspect ratio
|
|
float halfScaleX = (layer->minWidth + rndWidth * rnd) * 0.5f;
|
|
float scaleY = (layer->minHeight + rndHeight * rnd);
|
|
|
|
//Calculate rotation
|
|
float angle = *posPtr++;
|
|
float xTrans = Math::Cos(angle) * halfScaleX;
|
|
float zTrans = Math::Sin(angle) * halfScaleX;
|
|
|
|
//Calculate heights and edge positions
|
|
float x1 = x - xTrans, z1 = z - zTrans;
|
|
float x2 = x + xTrans, z2 = z + zTrans;
|
|
|
|
float y1, y2;
|
|
if (heightFunction){
|
|
y1 = heightFunction(x1, z1, heightFunctionUserData);
|
|
y2 = heightFunction(x2, z2, heightFunctionUserData);
|
|
|
|
if (layer->getMaxSlope() < (Math::Abs(y1 - y2) / (halfScaleX * 2))) {
|
|
//Degenerate the face
|
|
x2 = x1;
|
|
y2 = y1;
|
|
z2 = z1;
|
|
}
|
|
} else {
|
|
y1 = 0;
|
|
y2 = 0;
|
|
}
|
|
|
|
//Add vertices
|
|
*pReal++ = (x1 - page.centerPoint.x); *pReal++ = (y1 + scaleY); *pReal++ = (z1 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 0; *pReal++ = 0; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
*pReal++ = (x2 - page.centerPoint.x); *pReal++ = (y2 + scaleY); *pReal++ = (z2 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 1; *pReal++ = 0; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
*pReal++ = (x1 - page.centerPoint.x); *pReal++ = (y1); *pReal++ = (z1 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 0; *pReal++ = 1; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
*pReal++ = (x2 - page.centerPoint.x); *pReal++ = (y2); *pReal++ = (z2 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 1; *pReal++ = 1; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
//Update bounds
|
|
if (y1 < minY) minY = y1;
|
|
if (y2 < minY) minY = y2;
|
|
if (y1 + scaleY > maxY) maxY = y1 + scaleY;
|
|
if (y2 + scaleY > maxY) maxY = y2 + scaleY;
|
|
|
|
//Calculate heights and edge positions
|
|
float x3 = x + zTrans, z3 = z - xTrans;
|
|
float x4 = x - zTrans, z4 = z + xTrans;
|
|
|
|
float y3, y4;
|
|
if (heightFunction){
|
|
y3 = heightFunction(x3, z3, heightFunctionUserData);
|
|
y4 = heightFunction(x4, z4, heightFunctionUserData);
|
|
if (layer->getMaxSlope() < (Math::Abs(y3 - y4) / (halfScaleX * 2))) {
|
|
//Degenerate the face
|
|
x4 = x3;
|
|
y4 = y3;
|
|
z4 = z3;
|
|
}
|
|
} else {
|
|
y3 = 0;
|
|
y4 = 0;
|
|
}
|
|
|
|
//Add vertices
|
|
*pReal++ = (x3 - page.centerPoint.x); *pReal++ = (y3 + scaleY); *pReal++ = (z3 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 0; *pReal++ = 0; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
*pReal++ = (x4 - page.centerPoint.x); *pReal++ = (y4 + scaleY); *pReal++ = (z4 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 1; *pReal++ = 0; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
*pReal++ = (x3 - page.centerPoint.x); *pReal++ = (y3); *pReal++ = (z3 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 0; *pReal++ = 1; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
*pReal++ = (x4 - page.centerPoint.x); *pReal++ = (y4); *pReal++ = (z4 - page.centerPoint.z); //pos
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = 1; *pReal++ = 1; //uv
|
|
if (normals) {
|
|
*pReal++ = normal.x; *pReal++ = normal.y; *pReal++ = normal.z; *pReal++ = 0.0f;
|
|
}
|
|
|
|
//Update bounds
|
|
if (y3 < minY) minY = y1;
|
|
if (y4 < minY) minY = y2;
|
|
if (y3 + scaleY > maxY) maxY = y3 + scaleY;
|
|
if (y4 + scaleY > maxY) maxY = y4 + scaleY;
|
|
}
|
|
|
|
vbuf->unlock();
|
|
subMesh->vertexData->vertexBufferBinding->setBinding(0, vbuf);
|
|
|
|
//Populate index buffer
|
|
subMesh->indexData->indexStart = 0;
|
|
subMesh->indexData->indexCount = 6 * quadCount;
|
|
subMesh->indexData->indexBuffer = HardwareBufferManager::getSingleton()
|
|
.createIndexBuffer(HardwareIndexBuffer::IT_16BIT, subMesh->indexData->indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
|
|
uint16* pI = static_cast<uint16*>(subMesh->indexData->indexBuffer->lock(HardwareBuffer::HBL_DISCARD));
|
|
for (uint16 i = 0; i < quadCount; ++i)
|
|
{
|
|
uint16 offset = i * 4;
|
|
|
|
*pI++ = 0 + offset;
|
|
*pI++ = 2 + offset;
|
|
*pI++ = 1 + offset;
|
|
|
|
*pI++ = 1 + offset;
|
|
*pI++ = 2 + offset;
|
|
*pI++ = 3 + offset;
|
|
}
|
|
|
|
subMesh->indexData->indexBuffer->unlock();
|
|
//subMesh->setBuildEdgesEnabled(autoEdgeBuildEnabled);
|
|
|
|
|
|
//Finish up mesh
|
|
AxisAlignedBox bounds(page.bounds.left - page.centerPoint.x, minY, page.bounds.top - page.centerPoint.z,
|
|
page.bounds.right - page.centerPoint.x, maxY, page.bounds.bottom - page.centerPoint.z);
|
|
mesh->_setBounds(bounds);
|
|
Vector3 temp = bounds.getMaximum() - bounds.getMinimum();
|
|
mesh->_setBoundingSphereRadius(temp.length() * 0.5f);
|
|
|
|
auto logLevel = LogManager::getSingleton().getDefaultLog()->getLogDetail();
|
|
LogManager::getSingleton().setLogDetail(static_cast<LoggingLevel>(0));
|
|
mesh->setAutoBuildEdgeLists(autoEdgeBuildEnabled);
|
|
mesh->load();
|
|
LogManager::getSingleton().setLogDetail(logLevel);
|
|
|
|
//Apply grass material to mesh
|
|
subMesh->setMaterialName(layer->material->getName());
|
|
|
|
if (layer->isTangentsEnabled()) {
|
|
mesh->buildTangentVectors();
|
|
}
|
|
|
|
//Return the mesh
|
|
return mesh.get();
|
|
}
|
|
|
|
template <class TGrassLayer>
|
|
Mesh *GrassLoader<TGrassLayer>::generateGrass_SPRITE(PageInfo &page, TGrassLayer *layer, float *grassPositions, unsigned int grassCount)
|
|
{
|
|
//Calculate the number of quads to be added
|
|
unsigned int quadCount;
|
|
quadCount = grassCount;
|
|
|
|
// check for overflows of the uint16's
|
|
unsigned int maxUInt16 = std::numeric_limits<uint16>::max();
|
|
if(grassCount > maxUInt16)
|
|
{
|
|
LogManager::getSingleton().logMessage("grass count overflow: you tried to use more than " + StringConverter::toString(maxUInt16) + " (that's the maximum) grass meshes for one page");
|
|
return 0;
|
|
}
|
|
if(quadCount > maxUInt16)
|
|
{
|
|
LogManager::getSingleton().logMessage("quad count overflow: you tried to use more than " + StringConverter::toString(maxUInt16) + " (that's the maximum) grass meshes for one page");
|
|
return 0;
|
|
}
|
|
|
|
//Create manual mesh to store grass quads
|
|
MeshPtr mesh = MeshManager::getSingleton().createManual(getUniqueID(), ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
|
|
SubMesh *subMesh = mesh->createSubMesh();
|
|
subMesh->useSharedVertices = false;
|
|
|
|
//Setup vertex format information
|
|
subMesh->vertexData = new VertexData;
|
|
subMesh->vertexData->vertexStart = 0;
|
|
subMesh->vertexData->vertexCount = 4 * quadCount;
|
|
|
|
VertexDeclaration* dcl = subMesh->vertexData->vertexDeclaration;
|
|
size_t offset = 0;
|
|
bool colours = layer->isColoursEnabled();
|
|
dcl->addElement(0, offset, VET_FLOAT3, VES_POSITION);
|
|
offset += VertexElement::getTypeSize(VET_FLOAT3);
|
|
dcl->addElement(0, offset, VET_FLOAT4, VES_NORMAL);
|
|
offset += VertexElement::getTypeSize(VET_FLOAT4);
|
|
if (colours) {
|
|
dcl->addElement(0, offset, VET_COLOUR, VES_DIFFUSE);
|
|
offset += VertexElement::getTypeSize(VET_COLOUR);
|
|
}
|
|
dcl->addElement(0, offset, VET_FLOAT2, VES_TEXTURE_COORDINATES);
|
|
offset += VertexElement::getTypeSize(VET_FLOAT2);
|
|
|
|
//Populate a new vertex buffer with grass
|
|
HardwareVertexBufferSharedPtr vbuf = HardwareBufferManager::getSingleton()
|
|
.createVertexBuffer(offset, subMesh->vertexData->vertexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
|
|
float* pReal = static_cast<float*>(vbuf->lock(HardwareBuffer::HBL_DISCARD));
|
|
|
|
//Calculate size variance
|
|
float rndWidth = layer->maxWidth - layer->minWidth;
|
|
float rndHeight = layer->maxHeight - layer->minHeight;
|
|
|
|
float minY = Math::POS_INFINITY, maxY = Math::NEG_INFINITY;
|
|
float *posPtr = grassPositions; //Position array "iterator"
|
|
for (uint16 i = 0; i < grassCount; ++i)
|
|
{
|
|
//Get the x and z positions from the position array
|
|
float x = *posPtr++;
|
|
float z = *posPtr++;
|
|
|
|
//Calculate height
|
|
float y;
|
|
if (heightFunction){
|
|
y = heightFunction(x, z, heightFunctionUserData);
|
|
} else {
|
|
y = 0;
|
|
}
|
|
|
|
float x1 = (x - page.centerPoint.x);
|
|
float z1 = (z - page.centerPoint.z);
|
|
|
|
Ogre::uint32 color(0);
|
|
|
|
if (colours) {
|
|
//Get the color at the grass position
|
|
color = layer->getColorAt(x, z);
|
|
}
|
|
|
|
//Calculate size
|
|
float rnd = *posPtr++; //The same rnd value is used for width and height to maintain aspect ratio
|
|
float halfXScale = (layer->minWidth + rndWidth * rnd) * 0.5f;
|
|
float scaleY = (layer->minHeight + rndHeight * rnd);
|
|
|
|
//Randomly mirror grass textures
|
|
float uvLeft, uvRight;
|
|
if (*posPtr++ > 0.5f){
|
|
uvLeft = 0;
|
|
uvRight = 1;
|
|
} else {
|
|
uvLeft = 1;
|
|
uvRight = 0;
|
|
}
|
|
|
|
//Add vertices
|
|
*pReal++ = x1; *pReal++ = y; *pReal++ = z1; //center position
|
|
*pReal++ = -halfXScale; *pReal++ = scaleY; *pReal++ = 0; *pReal++ = 0; //normal (used to store relative corner positions)
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = uvLeft; *pReal++ = 0; //uv
|
|
|
|
*pReal++ = x1; *pReal++ = y; *pReal++ = z1; //center position
|
|
*pReal++ = +halfXScale; *pReal++ = scaleY; *pReal++ = 0; *pReal++ = 0; //normal (used to store relative corner positions)
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = uvRight; *pReal++ = 0; //uv
|
|
|
|
*pReal++ = x1; *pReal++ = y; *pReal++ = z1; //center position
|
|
*pReal++ = -halfXScale; *pReal++ = 0.0f; *pReal++ = 0; *pReal++ = 0; //normal (used to store relative corner positions)
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = uvLeft; *pReal++ = 1; //uv
|
|
|
|
*pReal++ = x1; *pReal++ = y; *pReal++ = z1; //center position
|
|
*pReal++ = +halfXScale; *pReal++ = 0.0f; *pReal++ = 0; *pReal++ = 0; //normal (used to store relative corner positions)
|
|
if (colours) {
|
|
*((uint32*)pReal++) = color; //color
|
|
}
|
|
*pReal++ = uvRight; *pReal++ = 1; //uv
|
|
|
|
//Update bounds
|
|
if (y < minY) minY = y;
|
|
if (y + scaleY > maxY) maxY = y + scaleY;
|
|
}
|
|
|
|
vbuf->unlock();
|
|
subMesh->vertexData->vertexBufferBinding->setBinding(0, vbuf);
|
|
|
|
//Populate index buffer
|
|
subMesh->indexData->indexStart = 0;
|
|
subMesh->indexData->indexCount = 6 * quadCount;
|
|
subMesh->indexData->indexBuffer = HardwareBufferManager::getSingleton()
|
|
.createIndexBuffer(HardwareIndexBuffer::IT_16BIT, subMesh->indexData->indexCount, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
|
|
uint16* pI = static_cast<uint16*>(subMesh->indexData->indexBuffer->lock(HardwareBuffer::HBL_DISCARD));
|
|
for (uint16 i = 0; i < quadCount; ++i)
|
|
{
|
|
uint16 offset = i * 4;
|
|
|
|
*pI++ = 0 + offset;
|
|
*pI++ = 2 + offset;
|
|
*pI++ = 1 + offset;
|
|
|
|
*pI++ = 1 + offset;
|
|
*pI++ = 2 + offset;
|
|
*pI++ = 3 + offset;
|
|
}
|
|
|
|
subMesh->indexData->indexBuffer->unlock();
|
|
//subMesh->setBuildEdgesEnabled(autoEdgeBuildEnabled);
|
|
|
|
|
|
//Finish up mesh
|
|
AxisAlignedBox bounds(page.bounds.left - page.centerPoint.x, minY, page.bounds.top - page.centerPoint.z,
|
|
page.bounds.right - page.centerPoint.x, maxY, page.bounds.bottom - page.centerPoint.z);
|
|
mesh->_setBounds(bounds);
|
|
Vector3 temp = bounds.getMaximum() - bounds.getMinimum();
|
|
mesh->_setBoundingSphereRadius(temp.length() * 0.5f);
|
|
|
|
auto logLevel = LogManager::getSingleton().getDefaultLog()->getLogDetail();
|
|
LogManager::getSingleton().setLogDetail(static_cast<LoggingLevel>(0));
|
|
mesh->setAutoBuildEdgeLists(autoEdgeBuildEnabled);
|
|
mesh->load();
|
|
LogManager::getSingleton().setLogDetail(logLevel);
|
|
|
|
//Apply grass material to mesh
|
|
subMesh->setMaterialName(layer->material->getName());
|
|
|
|
//Return the mesh
|
|
return mesh.get();
|
|
}
|
|
|
|
template <class TGrassLayer>
|
|
unsigned long GrassLoader<TGrassLayer>::GUID = 0;
|
|
|
|
}
|
|
#endif
|