forked from teamnwah/openmw-tes3coop
Merge remote-tracking branch 'upstream/master'
commit
0021ccf862
@ -0,0 +1,123 @@
|
||||
Bitstream Vera Fonts Copyright
|
||||
|
||||
The fonts have a generous copyright, allowing derivative works (as
|
||||
long as "Bitstream" or "Vera" are not in the names), and full
|
||||
redistribution (so long as they are not *sold* by themselves). They
|
||||
can be be bundled, redistributed and sold with any software.
|
||||
|
||||
The fonts are distributed under the following copyright:
|
||||
|
||||
Copyright
|
||||
=========
|
||||
|
||||
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream
|
||||
Vera is a trademark of Bitstream, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the fonts accompanying this license ("Fonts") and associated
|
||||
documentation files (the "Font Software"), to reproduce and distribute
|
||||
the Font Software, including without limitation the rights to use,
|
||||
copy, merge, publish, distribute, and/or sell copies of the Font
|
||||
Software, and to permit persons to whom the Font Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright and trademark notices and this permission notice
|
||||
shall be included in all copies of one or more of the Font Software
|
||||
typefaces.
|
||||
|
||||
The Font Software may be modified, altered, or added to, and in
|
||||
particular the designs of glyphs or characters in the Fonts may be
|
||||
modified and additional glyphs or characters may be added to the
|
||||
Fonts, only if the fonts are renamed to names not containing either
|
||||
the words "Bitstream" or the word "Vera".
|
||||
|
||||
This License becomes null and void to the extent applicable to Fonts
|
||||
or Font Software that has been modified and is distributed under the
|
||||
"Bitstream Vera" names.
|
||||
|
||||
The Font Software may be sold as part of a larger software package but
|
||||
no copy of one or more of the Font Software typefaces may be sold by
|
||||
itself.
|
||||
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
|
||||
BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL,
|
||||
OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
|
||||
SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the names of Gnome, the Gnome
|
||||
Foundation, and Bitstream Inc., shall not be used in advertising or
|
||||
otherwise to promote the sale, use or other dealings in this Font
|
||||
Software without prior written authorization from the Gnome Foundation
|
||||
or Bitstream Inc., respectively. For further information, contact:
|
||||
fonts at gnome dot org.
|
||||
|
||||
Copyright FAQ
|
||||
=============
|
||||
|
||||
1. I don't understand the resale restriction... What gives?
|
||||
|
||||
Bitstream is giving away these fonts, but wishes to ensure its
|
||||
competitors can't just drop the fonts as is into a font sale system
|
||||
and sell them as is. It seems fair that if Bitstream can't make money
|
||||
from the Bitstream Vera fonts, their competitors should not be able to
|
||||
do so either. You can sell the fonts as part of any software package,
|
||||
however.
|
||||
|
||||
2. I want to package these fonts separately for distribution and
|
||||
sale as part of a larger software package or system. Can I do so?
|
||||
|
||||
Yes. A RPM or Debian package is a "larger software package" to begin
|
||||
with, and you aren't selling them independently by themselves.
|
||||
See 1. above.
|
||||
|
||||
3. Are derivative works allowed?
|
||||
Yes!
|
||||
|
||||
4. Can I change or add to the font(s)?
|
||||
Yes, but you must change the name(s) of the font(s).
|
||||
|
||||
5. Under what terms are derivative works allowed?
|
||||
|
||||
You must change the name(s) of the fonts. This is to ensure the
|
||||
quality of the fonts, both to protect Bitstream and Gnome. We want to
|
||||
ensure that if an application has opened a font specifically of these
|
||||
names, it gets what it expects (though of course, using fontconfig,
|
||||
substitutions could still could have occurred during font
|
||||
opening). You must include the Bitstream copyright. Additional
|
||||
copyrights can be added, as per copyright law. Happy Font Hacking!
|
||||
|
||||
6. If I have improvements for Bitstream Vera, is it possible they might get
|
||||
adopted in future versions?
|
||||
|
||||
Yes. The contract between the Gnome Foundation and Bitstream has
|
||||
provisions for working with Bitstream to ensure quality additions to
|
||||
the Bitstream Vera font family. Please contact us if you have such
|
||||
additions. Note, that in general, we will want such additions for the
|
||||
entire family, not just a single font, and that you'll have to keep
|
||||
both Gnome and Jim Lyles, Vera's designer, happy! To make sense to add
|
||||
glyphs to the font, they must be stylistically in keeping with Vera's
|
||||
design. Vera cannot become a "ransom note" font. Jim Lyles will be
|
||||
providing a document describing the design elements used in Vera, as a
|
||||
guide and aid for people interested in contributing to Vera.
|
||||
|
||||
7. I want to sell a software package that uses these fonts: Can I do so?
|
||||
|
||||
Sure. Bundle the fonts with your software and sell your software
|
||||
with the fonts. That is the intent of the copyright.
|
||||
|
||||
8. If applications have built the names "Bitstream Vera" into them,
|
||||
can I override this somehow to use fonts of my choosing?
|
||||
|
||||
This depends on exact details of the software. Most open source
|
||||
systems and software (e.g., Gnome, KDE, etc.) are now converting to
|
||||
use fontconfig (see www.fontconfig.org) to handle font configuration,
|
||||
selection and substitution; it has provisions for overriding font
|
||||
names and subsituting alternatives. An example is provided by the
|
||||
supplied local.conf file, which chooses the family Bitstream Vera for
|
||||
"sans", "serif" and "monospace". Other software (e.g., the XFree86
|
||||
core server) has other mechanisms for font substitution.
|
@ -0,0 +1,93 @@
|
||||
Copyright (c) 2010, 2011 Georg Duffner (http://www.georgduffner.at)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
Binary file not shown.
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 50 KiB |
@ -0,0 +1,305 @@
|
||||
#include "localmap.hpp"
|
||||
#include "renderingmanager.hpp"
|
||||
|
||||
#include "../mwworld/environment.hpp"
|
||||
#include "../mwgui/window_manager.hpp"
|
||||
|
||||
#include <OgreOverlayManager.h>
|
||||
#include <OgreMaterialManager.h>
|
||||
|
||||
using namespace MWRender;
|
||||
using namespace Ogre;
|
||||
|
||||
LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWWorld::Environment* env)
|
||||
{
|
||||
mRendering = rend;
|
||||
mEnvironment = env;
|
||||
|
||||
mCellCamera = mRendering->getScene()->createCamera("CellCamera");
|
||||
mCellCamera->setProjectionType(PT_ORTHOGRAPHIC);
|
||||
// look down -y
|
||||
const float sqrt0pt5 = 0.707106781;
|
||||
mCellCamera->setOrientation(Quaternion(sqrt0pt5, -sqrt0pt5, 0, 0));
|
||||
}
|
||||
|
||||
LocalMap::~LocalMap()
|
||||
{
|
||||
deleteBuffers();
|
||||
}
|
||||
|
||||
void LocalMap::deleteBuffers()
|
||||
{
|
||||
mBuffers.clear();
|
||||
}
|
||||
|
||||
void LocalMap::saveTexture(const std::string& texname, const std::string& filename)
|
||||
{
|
||||
TexturePtr tex = TextureManager::getSingleton().getByName(texname);
|
||||
if (tex.isNull()) return;
|
||||
HardwarePixelBufferSharedPtr readbuffer = tex->getBuffer();
|
||||
readbuffer->lock(HardwareBuffer::HBL_NORMAL );
|
||||
const PixelBox &readrefpb = readbuffer->getCurrentLock();
|
||||
uchar *readrefdata = static_cast<uchar*>(readrefpb.data);
|
||||
|
||||
Image img;
|
||||
img = img.loadDynamicImage (readrefdata, tex->getWidth(),
|
||||
tex->getHeight(), tex->getFormat());
|
||||
img.save("./" + filename);
|
||||
|
||||
readbuffer->unlock();
|
||||
}
|
||||
|
||||
std::string LocalMap::coordStr(const int x, const int y)
|
||||
{
|
||||
return StringConverter::toString(x) + "_" + StringConverter::toString(y);
|
||||
}
|
||||
|
||||
void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell)
|
||||
{
|
||||
if (!mInterior)
|
||||
{
|
||||
/*saveTexture("Cell_"+coordStr(mCellX, mCellY)+"_fog",
|
||||
"Cell_"+coordStr(mCellX, mCellY)+"_fog.png");*/
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z);
|
||||
Vector2 max(mBounds.getMaximum().x, mBounds.getMaximum().z);
|
||||
/// \todo why is this workaround needed?
|
||||
min *= 1.3;
|
||||
max *= 1.3;
|
||||
Vector2 length = max-min;
|
||||
|
||||
// divide into segments
|
||||
const int segsX = std::ceil( length.x / sSize );
|
||||
const int segsY = std::ceil( length.y / sSize );
|
||||
|
||||
for (int x=0; x<segsX; ++x)
|
||||
{
|
||||
for (int y=0; y<segsY; ++y)
|
||||
{
|
||||
/*saveTexture(
|
||||
mInteriorName + "_" + coordStr(x,y) + "_fog",
|
||||
mInteriorName + "_" + coordStr(x,y) + "_fog.png");*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell)
|
||||
{
|
||||
mInterior = false;
|
||||
|
||||
std::string name = "Cell_"+coordStr(cell->cell->data.gridX, cell->cell->data.gridY);
|
||||
|
||||
int x = cell->cell->data.gridX;
|
||||
int y = cell->cell->data.gridY;
|
||||
|
||||
render((x+0.5)*sSize, (-y-0.5)*sSize, -10000, 10000, sSize, sSize, name);
|
||||
}
|
||||
|
||||
void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell,
|
||||
AxisAlignedBox bounds)
|
||||
{
|
||||
mInterior = true;
|
||||
mBounds = bounds;
|
||||
|
||||
Vector2 z(bounds.getMaximum().y, bounds.getMinimum().y);
|
||||
Vector2 min(bounds.getMinimum().x, bounds.getMinimum().z);
|
||||
Vector2 max(bounds.getMaximum().x, bounds.getMaximum().z);
|
||||
|
||||
/// \todo why is this workaround needed?
|
||||
min *= 1.3;
|
||||
max *= 1.3;
|
||||
|
||||
Vector2 length = max-min;
|
||||
Vector2 center(bounds.getCenter().x, bounds.getCenter().z);
|
||||
|
||||
// divide into segments
|
||||
const int segsX = std::ceil( length.x / sSize );
|
||||
const int segsY = std::ceil( length.y / sSize );
|
||||
|
||||
mInteriorName = cell->cell->name;
|
||||
|
||||
for (int x=0; x<segsX; ++x)
|
||||
{
|
||||
for (int y=0; y<segsY; ++y)
|
||||
{
|
||||
Vector2 start = min + Vector2(sSize*x,sSize*y);
|
||||
Vector2 newcenter = start + 4096;
|
||||
|
||||
render(newcenter.x, newcenter.y, z.y, z.x, sSize, sSize,
|
||||
cell->cell->name + "_" + coordStr(x,y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocalMap::render(const float x, const float y,
|
||||
const float zlow, const float zhigh,
|
||||
const float xw, const float yw, const std::string& texture)
|
||||
{
|
||||
// disable fog
|
||||
// changing FOG_MODE is not a solution when using shaders, thus we have to push linear start/end
|
||||
const float fStart = mRendering->getScene()->getFogStart();
|
||||
const float fEnd = mRendering->getScene()->getFogEnd();
|
||||
const ColourValue& clr = mRendering->getScene()->getFogColour();
|
||||
mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, 1000000, 10000000);
|
||||
|
||||
// make everything visible
|
||||
mRendering->getScene()->setAmbientLight(ColourValue(1,1,1));
|
||||
|
||||
mCellCamera->setPosition(Vector3(x, zhigh+100000, y));
|
||||
//mCellCamera->setFarClipDistance( (zhigh-zlow) * 1.1 );
|
||||
mCellCamera->setFarClipDistance(0); // infinite
|
||||
|
||||
mCellCamera->setOrthoWindow(xw, yw);
|
||||
|
||||
TexturePtr tex;
|
||||
// try loading from memory
|
||||
tex = TextureManager::getSingleton().getByName(texture);
|
||||
if (tex.isNull())
|
||||
{
|
||||
// try loading from disk
|
||||
//if (boost::filesystem::exists(texture+".jpg"))
|
||||
//{
|
||||
/// \todo
|
||||
//}
|
||||
//else
|
||||
{
|
||||
// render
|
||||
tex = TextureManager::getSingleton().createManual(
|
||||
texture,
|
||||
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
TEX_TYPE_2D,
|
||||
xw*sMapResolution/sSize, yw*sMapResolution/sSize,
|
||||
0,
|
||||
PF_R8G8B8,
|
||||
TU_RENDERTARGET);
|
||||
|
||||
RenderTarget* rtt = tex->getBuffer()->getRenderTarget();
|
||||
rtt->setAutoUpdated(false);
|
||||
Viewport* vp = rtt->addViewport(mCellCamera);
|
||||
vp->setOverlaysEnabled(false);
|
||||
vp->setShadowsEnabled(false);
|
||||
vp->setBackgroundColour(ColourValue(0, 0, 0));
|
||||
//vp->setVisibilityMask( ... );
|
||||
|
||||
rtt->update();
|
||||
|
||||
// create "fog of war" texture
|
||||
TexturePtr tex2 = TextureManager::getSingleton().createManual(
|
||||
texture + "_fog",
|
||||
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
TEX_TYPE_2D,
|
||||
xw*sFogOfWarResolution/sSize, yw*sFogOfWarResolution/sSize,
|
||||
0,
|
||||
PF_A8R8G8B8,
|
||||
TU_DYNAMIC_WRITE_ONLY_DISCARDABLE);
|
||||
|
||||
// create a buffer to use for dynamic operations
|
||||
std::vector<uint32> buffer;
|
||||
buffer.resize(sFogOfWarResolution*sFogOfWarResolution);
|
||||
|
||||
// initialize to (0, 0, 0, 1)
|
||||
for (int p=0; p<sFogOfWarResolution*sFogOfWarResolution; ++p)
|
||||
{
|
||||
buffer[p] = (255 << 24);
|
||||
}
|
||||
|
||||
memcpy(tex2->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4);
|
||||
tex2->getBuffer()->unlock();
|
||||
|
||||
mBuffers[texture] = buffer;
|
||||
|
||||
// save to cache for next time
|
||||
//rtt->writeContentsToFile("./" + texture + ".jpg");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// re-enable fog
|
||||
mRendering->getScene()->setFog(FOG_LINEAR, clr, 0, fStart, fEnd);
|
||||
}
|
||||
|
||||
void LocalMap::updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& direction)
|
||||
{
|
||||
if (sFogOfWarSkip != 0)
|
||||
{
|
||||
static int count=0;
|
||||
if (++count % sFogOfWarSkip != 0)
|
||||
return;
|
||||
}
|
||||
|
||||
// retrieve the x,y grid coordinates the player is in
|
||||
int x,y;
|
||||
Vector2 pos(position.x, position.z);
|
||||
if (!mInterior)
|
||||
{
|
||||
x = std::ceil(pos.x / sSize)-1;
|
||||
y = std::ceil(-pos.y / sSize)-1;
|
||||
mCellX = x;
|
||||
mCellY = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z);
|
||||
min *= 1.3;
|
||||
|
||||
x = std::ceil((pos.x - min.x)/sSize)-1;
|
||||
y = std::ceil((pos.y - min.y)/sSize)-1;
|
||||
|
||||
mEnvironment->mWindowManager->setInteriorMapTexture(x,y);
|
||||
}
|
||||
|
||||
// convert from world coordinates to texture UV coordinates
|
||||
float u,v;
|
||||
std::string texName;
|
||||
if (!mInterior)
|
||||
{
|
||||
u = std::abs((pos.x - (sSize*x))/sSize);
|
||||
v = 1-std::abs((pos.y + (sSize*y))/sSize);
|
||||
texName = "Cell_"+coordStr(x,y);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 min(mBounds.getMinimum().x, mBounds.getMinimum().z);
|
||||
min *= 1.3;
|
||||
|
||||
u = (pos.x - min.x - sSize*x)/sSize;
|
||||
v = (pos.y - min.y - sSize*y)/sSize;
|
||||
|
||||
texName = mInteriorName + "_" + coordStr(x,y);
|
||||
}
|
||||
mEnvironment->mWindowManager->setPlayerPos(u, v);
|
||||
mEnvironment->mWindowManager->setPlayerDir(direction.x, -direction.z);
|
||||
|
||||
// explore radius (squared)
|
||||
const float sqrExploreRadius = 0.01 * sFogOfWarResolution*sFogOfWarResolution;
|
||||
|
||||
// get the appropriate fog of war texture
|
||||
TexturePtr tex = TextureManager::getSingleton().getByName(texName+"_fog");
|
||||
if (!tex.isNull())
|
||||
{
|
||||
// get its buffer
|
||||
if (mBuffers.find(texName) == mBuffers.end()) return;
|
||||
int i=0;
|
||||
for (int texV = 0; texV<sFogOfWarResolution; ++texV)
|
||||
{
|
||||
for (int texU = 0; texU<sFogOfWarResolution; ++texU)
|
||||
{
|
||||
float sqrDist = Math::Sqr(texU - u*sFogOfWarResolution) + Math::Sqr(texV - v*sFogOfWarResolution);
|
||||
uint32 clr = mBuffers[texName][i];
|
||||
uint8 alpha = (clr >> 24);
|
||||
alpha = std::min( alpha, (uint8) (std::max(0.f, std::min(1.f, (sqrDist/sqrExploreRadius)))*255) );
|
||||
mBuffers[texName][i] = (uint32) (alpha << 24);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// copy to the texture
|
||||
memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &mBuffers[texName][0], sFogOfWarResolution*sFogOfWarResolution*4);
|
||||
tex->getBuffer()->unlock();
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
#ifndef _GAME_RENDER_LOCALMAP_H
|
||||
#define _GAME_RENDER_LOCALMAP_H
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
#include <openengine/ogre/renderer.hpp>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Environment;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
///
|
||||
/// \brief Local map rendering
|
||||
///
|
||||
class LocalMap
|
||||
{
|
||||
public:
|
||||
LocalMap(OEngine::Render::OgreRenderer*, MWWorld::Environment* env);
|
||||
~LocalMap();
|
||||
|
||||
/**
|
||||
* Request the local map for an exterior cell.
|
||||
* @remarks It will either be loaded from a disk cache,
|
||||
* or rendered if it is not already cached.
|
||||
* @param exterior cell
|
||||
*/
|
||||
void requestMap (MWWorld::Ptr::CellStore* cell);
|
||||
|
||||
/**
|
||||
* Request the local map for an interior cell.
|
||||
* @remarks It will either be loaded from a disk cache,
|
||||
* or rendered if it is not already cached.
|
||||
* @param interior cell
|
||||
* @param bounding box of the cell
|
||||
*/
|
||||
void requestMap (MWWorld::Ptr::CellStore* cell,
|
||||
Ogre::AxisAlignedBox bounds);
|
||||
|
||||
/**
|
||||
* Set the position & direction of the player.
|
||||
* @remarks This is used to draw a "fog of war" effect
|
||||
* to hide areas on the map the player has not discovered yet.
|
||||
* @param position (OGRE coordinates)
|
||||
* @param view direction (OGRE coordinates)
|
||||
*/
|
||||
void updatePlayer (const Ogre::Vector3& position, const Ogre::Vector3& direction);
|
||||
|
||||
/**
|
||||
* Save the fog of war for the current cell to disk.
|
||||
* @remarks This should be called before loading a
|
||||
* new cell, as well as when the game is quit.
|
||||
* @param current cell
|
||||
*/
|
||||
void saveFogOfWar(MWWorld::Ptr::CellStore* cell);
|
||||
|
||||
private:
|
||||
OEngine::Render::OgreRenderer* mRendering;
|
||||
MWWorld::Environment* mEnvironment;
|
||||
|
||||
// 1024*1024 pixels for a cell
|
||||
static const int sMapResolution = 1024;
|
||||
|
||||
// the dynamic texture is a bottleneck, so don't set this too high
|
||||
static const int sFogOfWarResolution = 32;
|
||||
|
||||
// frames to skip before rendering fog of war
|
||||
static const int sFogOfWarSkip = 2;
|
||||
|
||||
// size of a map segment (for exteriors, 1 cell)
|
||||
static const int sSize = 8192;
|
||||
|
||||
Ogre::Camera* mCellCamera;
|
||||
|
||||
void render(const float x, const float y,
|
||||
const float zlow, const float zhigh,
|
||||
const float xw, const float yw,
|
||||
const std::string& texture);
|
||||
|
||||
void saveTexture(const std::string& texname, const std::string& filename);
|
||||
|
||||
std::string coordStr(const int x, const int y);
|
||||
|
||||
// a buffer for the "fog of war" texture of the current cell.
|
||||
// interior cells could be divided into multiple textures,
|
||||
// so we store in a map.
|
||||
std::map <std::string, std::vector<Ogre::uint32> > mBuffers;
|
||||
|
||||
void deleteBuffers();
|
||||
|
||||
bool mInterior;
|
||||
int mCellX, mCellY;
|
||||
Ogre::AxisAlignedBox mBounds;
|
||||
std::string mInteriorName;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
@ -0,0 +1,254 @@
|
||||
#include "occlusionquery.hpp"
|
||||
|
||||
#include <OgreRenderSystem.h>
|
||||
#include <OgreRoot.h>
|
||||
#include <OgreBillboardSet.h>
|
||||
#include <OgreHardwareOcclusionQuery.h>
|
||||
#include <OgreEntity.h>
|
||||
|
||||
using namespace MWRender;
|
||||
using namespace Ogre;
|
||||
|
||||
OcclusionQuery::OcclusionQuery(OEngine::Render::OgreRenderer* renderer, SceneNode* sunNode) :
|
||||
mSunTotalAreaQuery(0), mSunVisibleAreaQuery(0), mSingleObjectQuery(0), mActiveQuery(0),
|
||||
mDoQuery(0), mSunVisibility(0), mQuerySingleObjectStarted(false), mTestResult(false),
|
||||
mQuerySingleObjectRequested(false), mWasVisible(false), mObjectWasVisible(false), mDoQuery2(false)
|
||||
{
|
||||
mRendering = renderer;
|
||||
mSunNode = sunNode;
|
||||
|
||||
try {
|
||||
RenderSystem* renderSystem = Root::getSingleton().getRenderSystem();
|
||||
|
||||
mSunTotalAreaQuery = renderSystem->createHardwareOcclusionQuery();
|
||||
mSunVisibleAreaQuery = renderSystem->createHardwareOcclusionQuery();
|
||||
mSingleObjectQuery = renderSystem->createHardwareOcclusionQuery();
|
||||
|
||||
mSupported = (mSunTotalAreaQuery != 0) && (mSunVisibleAreaQuery != 0) && (mSingleObjectQuery != 0);
|
||||
}
|
||||
catch (Ogre::Exception e)
|
||||
{
|
||||
mSupported = false;
|
||||
}
|
||||
|
||||
if (!mSupported)
|
||||
{
|
||||
std::cout << "Hardware occlusion queries not supported." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// This means that everything up to RENDER_QUEUE_MAIN can occlude the objects that are tested
|
||||
const int queue = RENDER_QUEUE_MAIN+1;
|
||||
|
||||
MaterialPtr matBase = MaterialManager::getSingleton().getByName("BaseWhiteNoLighting");
|
||||
MaterialPtr matQueryArea = matBase->clone("QueryTotalPixels");
|
||||
matQueryArea->setDepthWriteEnabled(false);
|
||||
matQueryArea->setColourWriteEnabled(false);
|
||||
matQueryArea->setDepthCheckEnabled(false); // Not occluded by objects
|
||||
MaterialPtr matQueryVisible = matBase->clone("QueryVisiblePixels");
|
||||
matQueryVisible->setDepthWriteEnabled(false);
|
||||
matQueryVisible->setColourWriteEnabled(false); // Uncomment this to visualize the occlusion query
|
||||
matQueryVisible->setDepthCheckEnabled(true); // Occluded by objects
|
||||
matQueryVisible->setCullingMode(CULL_NONE);
|
||||
matQueryVisible->setManualCullingMode(MANUAL_CULL_NONE);
|
||||
|
||||
mBBNode = mSunNode->getParentSceneNode()->createChildSceneNode();
|
||||
|
||||
mObjectNode = mRendering->getScene()->getRootSceneNode()->createChildSceneNode();
|
||||
mBBNodeReal = mRendering->getScene()->getRootSceneNode()->createChildSceneNode();
|
||||
|
||||
mBBQueryTotal = mRendering->getScene()->createBillboardSet(1);
|
||||
mBBQueryTotal->setDefaultDimensions(150, 150);
|
||||
mBBQueryTotal->createBillboard(Vector3::ZERO);
|
||||
mBBQueryTotal->setMaterialName("QueryTotalPixels");
|
||||
mBBQueryTotal->setRenderQueueGroup(queue+1);
|
||||
mBBNodeReal->attachObject(mBBQueryTotal);
|
||||
|
||||
mBBQueryVisible = mRendering->getScene()->createBillboardSet(1);
|
||||
mBBQueryVisible->setDefaultDimensions(150, 150);
|
||||
mBBQueryVisible->createBillboard(Vector3::ZERO);
|
||||
mBBQueryVisible->setMaterialName("QueryVisiblePixels");
|
||||
mBBQueryVisible->setRenderQueueGroup(queue+1);
|
||||
mBBNodeReal->attachObject(mBBQueryVisible);
|
||||
|
||||
mBBQuerySingleObject = mRendering->getScene()->createBillboardSet(1);
|
||||
/// \todo ideally this should occupy exactly 1 pixel on the screen
|
||||
mBBQuerySingleObject->setDefaultDimensions(0.003, 0.003);
|
||||
mBBQuerySingleObject->createBillboard(Vector3::ZERO);
|
||||
mBBQuerySingleObject->setMaterialName("QueryVisiblePixels");
|
||||
mBBQuerySingleObject->setRenderQueueGroup(queue);
|
||||
mObjectNode->attachObject(mBBQuerySingleObject);
|
||||
|
||||
mRendering->getScene()->addRenderObjectListener(this);
|
||||
mRendering->getScene()->addRenderQueueListener(this);
|
||||
mDoQuery = true;
|
||||
mDoQuery2 = true;
|
||||
}
|
||||
|
||||
OcclusionQuery::~OcclusionQuery()
|
||||
{
|
||||
RenderSystem* renderSystem = Root::getSingleton().getRenderSystem();
|
||||
if (mSunTotalAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunTotalAreaQuery);
|
||||
if (mSunVisibleAreaQuery) renderSystem->destroyHardwareOcclusionQuery(mSunVisibleAreaQuery);
|
||||
if (mSingleObjectQuery) renderSystem->destroyHardwareOcclusionQuery(mSingleObjectQuery);
|
||||
}
|
||||
|
||||
bool OcclusionQuery::supported()
|
||||
{
|
||||
return mSupported;
|
||||
}
|
||||
|
||||
void OcclusionQuery::notifyRenderSingleObject(Renderable* rend, const Pass* pass, const AutoParamDataSource* source,
|
||||
const LightList* pLightList, bool suppressRenderStateChanges)
|
||||
{
|
||||
// The following code activates and deactivates the occlusion queries
|
||||
// so that the queries only include the rendering of their intended targets
|
||||
|
||||
// Close the last occlusion query
|
||||
// Each occlusion query should only last a single rendering
|
||||
if (mActiveQuery != NULL)
|
||||
{
|
||||
mActiveQuery->endOcclusionQuery();
|
||||
mActiveQuery = NULL;
|
||||
}
|
||||
|
||||
// Open a new occlusion query
|
||||
if (mDoQuery == true)
|
||||
{
|
||||
if (rend == mBBQueryTotal)
|
||||
{
|
||||
mActiveQuery = mSunTotalAreaQuery;
|
||||
mWasVisible = true;
|
||||
}
|
||||
else if (rend == mBBQueryVisible)
|
||||
{
|
||||
mActiveQuery = mSunVisibleAreaQuery;
|
||||
}
|
||||
}
|
||||
if (mDoQuery == true && rend == mBBQuerySingleObject)
|
||||
{
|
||||
mQuerySingleObjectStarted = true;
|
||||
mQuerySingleObjectRequested = false;
|
||||
mActiveQuery = mSingleObjectQuery;
|
||||
mObjectWasVisible = true;
|
||||
}
|
||||
|
||||
if (mActiveQuery != NULL)
|
||||
mActiveQuery->beginOcclusionQuery();
|
||||
}
|
||||
|
||||
void OcclusionQuery::renderQueueEnded(uint8 queueGroupId, const String& invocation, bool& repeatThisInvocation)
|
||||
{
|
||||
if (mActiveQuery != NULL)
|
||||
{
|
||||
mActiveQuery->endOcclusionQuery();
|
||||
mActiveQuery = NULL;
|
||||
}
|
||||
/**
|
||||
* for every beginOcclusionQuery(), we want a respective pullOcclusionQuery() and vice versa
|
||||
* this also means that results can be wrong at other places if we pull, but beginOcclusionQuery() was never called
|
||||
* this can happen for example if the object that is tested is outside of the view frustum
|
||||
* to prevent this, check if the queries have been performed after everything has been rendered and if not, start them manually
|
||||
*/
|
||||
if (queueGroupId == RENDER_QUEUE_SKIES_LATE)
|
||||
{
|
||||
if (mWasVisible == false && mDoQuery)
|
||||
{
|
||||
mSunTotalAreaQuery->beginOcclusionQuery();
|
||||
mSunTotalAreaQuery->endOcclusionQuery();
|
||||
mSunVisibleAreaQuery->beginOcclusionQuery();
|
||||
mSunVisibleAreaQuery->endOcclusionQuery();
|
||||
}
|
||||
if (mObjectWasVisible == false && mDoQuery)
|
||||
{
|
||||
mSingleObjectQuery->beginOcclusionQuery();
|
||||
mSingleObjectQuery->endOcclusionQuery();
|
||||
mQuerySingleObjectStarted = true;
|
||||
mQuerySingleObjectRequested = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OcclusionQuery::update(float duration)
|
||||
{
|
||||
if (!mSupported) return;
|
||||
|
||||
mWasVisible = false;
|
||||
mObjectWasVisible = false;
|
||||
|
||||
// Adjust the position of the sun billboards according to camera viewing distance
|
||||
// we need to do this to make sure that _everything_ can occlude the sun
|
||||
float dist = mRendering->getCamera()->getFarClipDistance();
|
||||
if (dist==0) dist = 10000000;
|
||||
dist -= 1000; // bias
|
||||
dist /= 1000.f;
|
||||
mBBNode->setPosition(mSunNode->getPosition() * dist);
|
||||
mBBNode->setScale(dist, dist, dist);
|
||||
mBBNodeReal->setPosition(mBBNode->_getDerivedPosition());
|
||||
mBBNodeReal->setScale(mBBNode->getScale());
|
||||
|
||||
// Stop occlusion queries until we get their information
|
||||
// (may not happen on the same frame they are requested in)
|
||||
mDoQuery = false;
|
||||
mDoQuery2 = false;
|
||||
|
||||
if (!mSunTotalAreaQuery->isStillOutstanding()
|
||||
&& !mSunVisibleAreaQuery->isStillOutstanding()
|
||||
&& !mSingleObjectQuery->isStillOutstanding())
|
||||
{
|
||||
unsigned int totalPixels;
|
||||
unsigned int visiblePixels;
|
||||
|
||||
mSunTotalAreaQuery->pullOcclusionQuery(&totalPixels);
|
||||
mSunVisibleAreaQuery->pullOcclusionQuery(&visiblePixels);
|
||||
|
||||
if (totalPixels == 0)
|
||||
{
|
||||
// probably outside of the view frustum
|
||||
mSunVisibility = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
mSunVisibility = float(visiblePixels) / float(totalPixels);
|
||||
if (mSunVisibility > 1) mSunVisibility = 1;
|
||||
}
|
||||
|
||||
unsigned int result;
|
||||
|
||||
mSingleObjectQuery->pullOcclusionQuery(&result);
|
||||
|
||||
mTestResult = (result != 0);
|
||||
|
||||
mQuerySingleObjectStarted = false;
|
||||
mQuerySingleObjectRequested = false;
|
||||
|
||||
mDoQuery = true;
|
||||
}
|
||||
}
|
||||
|
||||
void OcclusionQuery::occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object)
|
||||
{
|
||||
assert( !occlusionTestPending()
|
||||
&& "Occlusion test still pending");
|
||||
|
||||
mBBQuerySingleObject->setVisible(true);
|
||||
|
||||
mObjectNode->setPosition(position);
|
||||
// scale proportional to camera distance, in order to always give the billboard the same size in screen-space
|
||||
mObjectNode->setScale( Vector3(1,1,1)*(position - mRendering->getCamera()->getRealPosition()).length() );
|
||||
|
||||
mQuerySingleObjectRequested = true;
|
||||
}
|
||||
|
||||
bool OcclusionQuery::occlusionTestPending()
|
||||
{
|
||||
return (mQuerySingleObjectRequested || mQuerySingleObjectStarted);
|
||||
}
|
||||
|
||||
bool OcclusionQuery::getTestResult()
|
||||
{
|
||||
assert( !occlusionTestPending()
|
||||
&& "Occlusion test still pending");
|
||||
|
||||
return mTestResult;
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
#ifndef _GAME_OCCLUSION_QUERY_H
|
||||
#define _GAME_OCCLUSION_QUERY_H
|
||||
|
||||
#include <OgreRenderObjectListener.h>
|
||||
#include <OgreRenderQueueListener.h>
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class HardwareOcclusionQuery;
|
||||
class Entity;
|
||||
class SceneNode;
|
||||
}
|
||||
|
||||
#include <openengine/ogre/renderer.hpp>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
///
|
||||
/// \brief Implements hardware occlusion queries on the GPU
|
||||
///
|
||||
class OcclusionQuery : public Ogre::RenderObjectListener, public Ogre::RenderQueueListener
|
||||
{
|
||||
public:
|
||||
OcclusionQuery(OEngine::Render::OgreRenderer*, Ogre::SceneNode* sunNode);
|
||||
~OcclusionQuery();
|
||||
|
||||
/**
|
||||
* @return true if occlusion queries are supported on the user's hardware
|
||||
*/
|
||||
bool supported();
|
||||
|
||||
/**
|
||||
* per-frame update
|
||||
*/
|
||||
void update(float duration);
|
||||
|
||||
/**
|
||||
* request occlusion test for a billboard at the given position, omitting an entity
|
||||
* @param position of the billboard in ogre coordinates
|
||||
* @param object to exclude from the occluders
|
||||
*/
|
||||
void occlusionTest(const Ogre::Vector3& position, Ogre::SceneNode* object);
|
||||
|
||||
/**
|
||||
* @return true if a request is still outstanding
|
||||
*/
|
||||
bool occlusionTestPending();
|
||||
|
||||
/**
|
||||
* @return true if the object tested in the last request was occluded
|
||||
*/
|
||||
bool getTestResult();
|
||||
|
||||
float getSunVisibility() const {return mSunVisibility;};
|
||||
|
||||
private:
|
||||
Ogre::HardwareOcclusionQuery* mSunTotalAreaQuery;
|
||||
Ogre::HardwareOcclusionQuery* mSunVisibleAreaQuery;
|
||||
Ogre::HardwareOcclusionQuery* mSingleObjectQuery;
|
||||
Ogre::HardwareOcclusionQuery* mActiveQuery;
|
||||
|
||||
Ogre::BillboardSet* mBBQueryVisible;
|
||||
Ogre::BillboardSet* mBBQueryTotal;
|
||||
Ogre::BillboardSet* mBBQuerySingleObject;
|
||||
|
||||
Ogre::SceneNode* mSunNode;
|
||||
Ogre::SceneNode* mBBNode;
|
||||
Ogre::SceneNode* mBBNodeReal;
|
||||
float mSunVisibility;
|
||||
|
||||
Ogre::SceneNode* mObjectNode;
|
||||
|
||||
bool mWasVisible;
|
||||
bool mObjectWasVisible;
|
||||
|
||||
bool mTestResult;
|
||||
|
||||
bool mSupported;
|
||||
bool mDoQuery;
|
||||
bool mDoQuery2;
|
||||
|
||||
bool mQuerySingleObjectRequested;
|
||||
bool mQuerySingleObjectStarted;
|
||||
|
||||
OEngine::Render::OgreRenderer* mRendering;
|
||||
|
||||
protected:
|
||||
virtual void notifyRenderSingleObject(Ogre::Renderable* rend, const Ogre::Pass* pass, const Ogre::AutoParamDataSource* source,
|
||||
const Ogre::LightList* pLightList, bool suppressRenderStateChanges);
|
||||
|
||||
virtual void renderQueueEnded(Ogre::uint8 queueGroupId, const Ogre::String& invocation, bool& repeatThisInvocation);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,510 @@
|
||||
#include <OgreTerrain.h>
|
||||
#include <OgreTerrainGroup.h>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include "../mwworld/world.hpp"
|
||||
|
||||
#include "terrainmaterial.hpp"
|
||||
#include "terrain.hpp"
|
||||
|
||||
|
||||
using namespace Ogre;
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
TerrainManager::TerrainManager(Ogre::SceneManager* mgr, const MWWorld::Environment& evn) :
|
||||
mEnvironment(evn), mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Z, mLandSize, mWorldSize))
|
||||
{
|
||||
|
||||
TerrainMaterialGeneratorPtr matGen;
|
||||
TerrainMaterialGeneratorB* matGenP = new TerrainMaterialGeneratorB();
|
||||
matGen.bind(matGenP);
|
||||
mTerrainGlobals.setDefaultMaterialGenerator(matGen);
|
||||
|
||||
TerrainMaterialGenerator::Profile* const activeProfile =
|
||||
mTerrainGlobals.getDefaultMaterialGenerator()
|
||||
->getActiveProfile();
|
||||
mActiveProfile = static_cast<TerrainMaterialGeneratorB::SM2Profile*>(activeProfile);
|
||||
|
||||
//The pixel error should be as high as possible without it being noticed
|
||||
//as it governs how fast mesh quality decreases.
|
||||
mTerrainGlobals.setMaxPixelError(8);
|
||||
|
||||
mTerrainGlobals.setLayerBlendMapSize(32);
|
||||
mTerrainGlobals.setDefaultGlobalColourMapSize(65);
|
||||
|
||||
//10 (default) didn't seem to be quite enough
|
||||
mTerrainGlobals.setSkirtSize(128);
|
||||
|
||||
//due to the sudden flick between composite and non composite textures,
|
||||
//this seemed the distance where it wasn't too noticeable
|
||||
mTerrainGlobals.setCompositeMapDistance(mWorldSize*2);
|
||||
|
||||
mActiveProfile->setLightmapEnabled(false);
|
||||
mActiveProfile->setLayerSpecularMappingEnabled(false);
|
||||
mActiveProfile->setLayerNormalMappingEnabled(false);
|
||||
mActiveProfile->setLayerParallaxMappingEnabled(false);
|
||||
mActiveProfile->setReceiveDynamicShadowsEnabled(false);
|
||||
|
||||
//composite maps lead to a drastic reduction in loading time so are
|
||||
//disabled
|
||||
mActiveProfile->setCompositeMapEnabled(false);
|
||||
|
||||
mTerrainGroup.setOrigin(Vector3(mWorldSize/2,
|
||||
0,
|
||||
-mWorldSize/2));
|
||||
|
||||
Terrain::ImportData& importSettings = mTerrainGroup.getDefaultImportSettings();
|
||||
|
||||
importSettings.inputBias = 0;
|
||||
importSettings.terrainSize = mLandSize;
|
||||
importSettings.worldSize = mWorldSize;
|
||||
importSettings.minBatchSize = 9;
|
||||
importSettings.maxBatchSize = mLandSize;
|
||||
|
||||
importSettings.deleteInputData = true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
TerrainManager::~TerrainManager()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::setDiffuse(const ColourValue& diffuse)
|
||||
{
|
||||
mTerrainGlobals.setCompositeMapDiffuse(diffuse);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::setAmbient(const ColourValue& ambient)
|
||||
{
|
||||
mTerrainGlobals.setCompositeMapAmbient(ambient);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::cellAdded(MWWorld::Ptr::CellStore *store)
|
||||
{
|
||||
const int cellX = store->cell->getGridX();
|
||||
const int cellY = store->cell->getGridY();
|
||||
|
||||
ESM::Land* land = mEnvironment.mWorld->getStore().lands.search(cellX, cellY);
|
||||
if ( land != NULL )
|
||||
{
|
||||
land->loadData();
|
||||
}
|
||||
|
||||
//split the cell terrain into four segments
|
||||
const int numTextures = ESM::Land::LAND_TEXTURE_SIZE/2;
|
||||
|
||||
for ( int x = 0; x < 2; x++ )
|
||||
{
|
||||
for ( int y = 0; y < 2; y++ )
|
||||
{
|
||||
Terrain::ImportData terrainData =
|
||||
mTerrainGroup.getDefaultImportSettings();
|
||||
|
||||
const int terrainX = cellX * 2 + x;
|
||||
const int terrainY = cellY * 2 + y;
|
||||
|
||||
//it makes far more sense to reallocate the memory here,
|
||||
//and let Ogre deal with it due to the issues with deleting
|
||||
//it at the wrong time if using threads (Which Terrain does)
|
||||
terrainData.inputFloat = OGRE_ALLOC_T(float,
|
||||
mLandSize*mLandSize,
|
||||
MEMCATEGORY_GEOMETRY);
|
||||
|
||||
if ( land != NULL )
|
||||
{
|
||||
//copy the height data row by row
|
||||
for ( int terrainCopyY = 0; terrainCopyY < mLandSize; terrainCopyY++ )
|
||||
{
|
||||
//the offset of the current segment
|
||||
const size_t yOffset = y * (mLandSize-1) * ESM::Land::LAND_SIZE +
|
||||
//offset of the row
|
||||
terrainCopyY * ESM::Land::LAND_SIZE;
|
||||
const size_t xOffset = x * (mLandSize-1);
|
||||
|
||||
memcpy(&terrainData.inputFloat[terrainCopyY*mLandSize],
|
||||
&land->landData->heights[yOffset + xOffset],
|
||||
mLandSize*sizeof(float));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
memset(terrainData.inputFloat, 0, mLandSize*mLandSize*sizeof(float));
|
||||
}
|
||||
|
||||
std::map<uint16_t, int> indexes;
|
||||
initTerrainTextures(&terrainData, cellX, cellY,
|
||||
x * numTextures, y * numTextures,
|
||||
numTextures, indexes);
|
||||
|
||||
if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL)
|
||||
{
|
||||
mTerrainGroup.defineTerrain(terrainX, terrainY, &terrainData);
|
||||
|
||||
mTerrainGroup.loadTerrain(terrainX, terrainY, true);
|
||||
|
||||
Terrain* terrain = mTerrainGroup.getTerrain(terrainX, terrainY);
|
||||
initTerrainBlendMaps(terrain,
|
||||
cellX, cellY,
|
||||
x * numTextures, y * numTextures,
|
||||
numTextures,
|
||||
indexes);
|
||||
|
||||
if ( land && land->landData->usingColours )
|
||||
{
|
||||
// disable or enable global colour map (depends on available vertex colours)
|
||||
mActiveProfile->setGlobalColourMapEnabled(true);
|
||||
TexturePtr vertex = getVertexColours(land,
|
||||
cellX, cellY,
|
||||
x*(mLandSize-1),
|
||||
y*(mLandSize-1),
|
||||
mLandSize);
|
||||
|
||||
//this is a hack to get around the fact that Ogre seems to
|
||||
//corrupt the global colour map leading to rendering errors
|
||||
MaterialPtr mat = terrain->getMaterial();
|
||||
mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() );
|
||||
//mat = terrain->_getCompositeMapMaterial();
|
||||
//mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
mActiveProfile->setGlobalColourMapEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mTerrainGroup.freeTemporaryResources();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store)
|
||||
{
|
||||
for ( int x = 0; x < 2; x++ )
|
||||
{
|
||||
for ( int y = 0; y < 2; y++ )
|
||||
{
|
||||
mTerrainGroup.unloadTerrain(store->cell->getGridX() * 2 + x,
|
||||
store->cell->getGridY() * 2 + y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size,
|
||||
std::map<uint16_t, int>& indexes)
|
||||
{
|
||||
assert(terrainData != NULL && "Must have valid terrain data");
|
||||
assert(fromX >= 0 && fromY >= 0 &&
|
||||
"Can't get a terrain texture on terrain outside the current cell");
|
||||
assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
fromY+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
"Can't get a terrain texture on terrain outside the current cell");
|
||||
|
||||
//this ensures that the ltex indexes are sorted (or retrived as sorted
|
||||
//which simplifies shading between cells).
|
||||
//
|
||||
//If we don't sort the ltex indexes, the splatting order may differ between
|
||||
//cells which may lead to inconsistent results when shading between cells
|
||||
std::set<uint16_t> ltexIndexes;
|
||||
for ( int y = fromY - 1; y < fromY + size + 1; y++ )
|
||||
{
|
||||
for ( int x = fromX - 1; x < fromX + size + 1; x++ )
|
||||
{
|
||||
ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
//there is one texture that we want to use as a base (i.e. it won't have
|
||||
//a blend map). This holds the ltex index of that base texture so that
|
||||
//we know not to include it in the output map
|
||||
int baseTexture = -1;
|
||||
for ( std::set<uint16_t>::iterator iter = ltexIndexes.begin();
|
||||
iter != ltexIndexes.end();
|
||||
++iter )
|
||||
{
|
||||
const uint16_t ltexIndex = *iter;
|
||||
//this is the base texture, so we can ignore this at present
|
||||
if ( ltexIndex == baseTexture )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::map<uint16_t, int>::const_iterator it = indexes.find(ltexIndex);
|
||||
|
||||
if ( it == indexes.end() )
|
||||
{
|
||||
//NB: All vtex ids are +1 compared to the ltex ids
|
||||
|
||||
assert( (int)mEnvironment.mWorld->getStore().landTexts.getSize() >= (int)ltexIndex - 1 &&
|
||||
"LAND.VTEX must be within the bounds of the LTEX array");
|
||||
|
||||
std::string texture;
|
||||
if ( ltexIndex == 0 )
|
||||
{
|
||||
texture = "_land_default.dds";
|
||||
}
|
||||
else
|
||||
{
|
||||
texture = mEnvironment.mWorld->getStore().landTexts.search(ltexIndex-1)->texture;
|
||||
//TODO this is needed due to MWs messed up texture handling
|
||||
texture = texture.substr(0, texture.rfind(".")) + ".dds";
|
||||
}
|
||||
|
||||
const size_t position = terrainData->layerList.size();
|
||||
terrainData->layerList.push_back(Terrain::LayerInstance());
|
||||
|
||||
terrainData->layerList[position].worldSize = 256;
|
||||
terrainData->layerList[position].textureNames.push_back("textures\\" + texture);
|
||||
|
||||
if ( baseTexture == -1 )
|
||||
{
|
||||
baseTexture = ltexIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
indexes[ltexIndex] = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
void TerrainManager::initTerrainBlendMaps(Terrain* terrain,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size,
|
||||
const std::map<uint16_t, int>& indexes)
|
||||
{
|
||||
assert(terrain != NULL && "Must have valid terrain");
|
||||
assert(fromX >= 0 && fromY >= 0 &&
|
||||
"Can't get a terrain texture on terrain outside the current cell");
|
||||
assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
fromY+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
"Can't get a terrain texture on terrain outside the current cell");
|
||||
|
||||
//size must be a power of 2 as we do divisions with a power of 2 number
|
||||
//that need to result in an integer for correct splatting
|
||||
assert( (size & (size - 1)) == 0 && "Size must be a power of 2");
|
||||
|
||||
const int blendMapSize = terrain->getLayerBlendMapSize();
|
||||
const int splatSize = blendMapSize / size;
|
||||
|
||||
//zero out every map
|
||||
std::map<uint16_t, int>::const_iterator iter;
|
||||
for ( iter = indexes.begin(); iter != indexes.end(); ++iter )
|
||||
{
|
||||
float* pBlend = terrain->getLayerBlendMap(iter->second)
|
||||
->getBlendPointer();
|
||||
memset(pBlend, 0, sizeof(float) * blendMapSize * blendMapSize);
|
||||
}
|
||||
|
||||
//covert the ltex data into a set of blend maps
|
||||
for ( int texY = fromY - 1; texY < fromY + size + 1; texY++ )
|
||||
{
|
||||
for ( int texX = fromX - 1; texX < fromX + size + 1; texX++ )
|
||||
{
|
||||
const uint16_t ltexIndex = getLtexIndexAt(cellX, cellY, texX, texY);
|
||||
|
||||
//check if it is the base texture (which isn't in the map) and
|
||||
//if it is don't bother altering the blend map for it
|
||||
if ( indexes.find(ltexIndex) == indexes.end() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//while texX is the splat index relative to the entire cell,
|
||||
//relX is relative to the current segment we are splatting
|
||||
const int relX = texX - fromX;
|
||||
const int relY = texY - fromY;
|
||||
|
||||
const int layerIndex = indexes.find(ltexIndex)->second;
|
||||
|
||||
float* const pBlend = terrain->getLayerBlendMap(layerIndex)
|
||||
->getBlendPointer();
|
||||
|
||||
for ( int y = -1; y < splatSize + 1; y++ )
|
||||
{
|
||||
for ( int x = -1; x < splatSize + 1; x++ )
|
||||
{
|
||||
|
||||
//Note: Y is reversed
|
||||
const int splatY = blendMapSize - 1 - relY * splatSize - y;
|
||||
const int splatX = relX * splatSize + x;
|
||||
|
||||
if ( splatX >= 0 && splatX < blendMapSize &&
|
||||
splatY >= 0 && splatY < blendMapSize )
|
||||
{
|
||||
const int index = (splatY)*blendMapSize + splatX;
|
||||
|
||||
if ( y >= 0 && y < splatSize &&
|
||||
x >= 0 && x < splatSize )
|
||||
{
|
||||
pBlend[index] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
//this provides a transition shading but also
|
||||
//rounds off the corners slightly
|
||||
pBlend[index] = std::min(1.0f, pBlend[index] + 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ( int i = 1; i < terrain->getLayerCount(); i++ )
|
||||
{
|
||||
TerrainLayerBlendMap* blend = terrain->getLayerBlendMap(i);
|
||||
blend->dirty();
|
||||
blend->update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
int TerrainManager::getLtexIndexAt(int cellX, int cellY,
|
||||
int x, int y)
|
||||
{
|
||||
//check texture index falls within the 9 cell bounds
|
||||
//as this function can't cope with anything above that
|
||||
assert(x >= -ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
y >= -ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
"Trying to get land textures that are out of bounds");
|
||||
|
||||
assert(x < 2*ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
y < 2*ESM::Land::LAND_TEXTURE_SIZE &&
|
||||
"Trying to get land textures that are out of bounds");
|
||||
|
||||
if ( x < 0 )
|
||||
{
|
||||
cellX--;
|
||||
x += ESM::Land::LAND_TEXTURE_SIZE;
|
||||
}
|
||||
else if ( x >= ESM::Land::LAND_TEXTURE_SIZE )
|
||||
{
|
||||
cellX++;
|
||||
x -= ESM::Land::LAND_TEXTURE_SIZE;
|
||||
}
|
||||
|
||||
if ( y < 0 )
|
||||
{
|
||||
cellY--;
|
||||
y += ESM::Land::LAND_TEXTURE_SIZE;
|
||||
}
|
||||
else if ( y >= ESM::Land::LAND_TEXTURE_SIZE )
|
||||
{
|
||||
cellY++;
|
||||
y -= ESM::Land::LAND_TEXTURE_SIZE;
|
||||
}
|
||||
|
||||
|
||||
ESM::Land* land = mEnvironment.mWorld->getStore().lands.search(cellX, cellY);
|
||||
if ( land != NULL )
|
||||
{
|
||||
land->loadData();
|
||||
return land->landData
|
||||
->textures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------
|
||||
|
||||
TexturePtr TerrainManager::getVertexColours(ESM::Land* land,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size)
|
||||
{
|
||||
TextureManager* const texMgr = TextureManager::getSingletonPtr();
|
||||
|
||||
const std::string colourTextureName = "VtexColours_" +
|
||||
boost::lexical_cast<std::string>(cellX) +
|
||||
"_" +
|
||||
boost::lexical_cast<std::string>(cellY) +
|
||||
"_" +
|
||||
boost::lexical_cast<std::string>(fromX) +
|
||||
"_" +
|
||||
boost::lexical_cast<std::string>(fromY);
|
||||
|
||||
TexturePtr tex = texMgr->getByName(colourTextureName);
|
||||
if ( !tex.isNull() )
|
||||
{
|
||||
return tex;
|
||||
}
|
||||
|
||||
tex = texMgr->createManual(colourTextureName,
|
||||
ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
|
||||
TEX_TYPE_2D, size, size, 0, PF_BYTE_BGR);
|
||||
|
||||
HardwarePixelBufferSharedPtr pixelBuffer = tex->getBuffer();
|
||||
|
||||
pixelBuffer->lock(HardwareBuffer::HBL_DISCARD);
|
||||
const PixelBox& pixelBox = pixelBuffer->getCurrentLock();
|
||||
|
||||
uint8* pDest = static_cast<uint8*>(pixelBox.data);
|
||||
|
||||
if ( land != NULL )
|
||||
{
|
||||
const char* const colours = land->landData->colours;
|
||||
for ( int y = 0; y < size; y++ )
|
||||
{
|
||||
for ( int x = 0; x < size; x++ )
|
||||
{
|
||||
const size_t colourOffset = (y+fromY)*3*65 + (x+fromX)*3;
|
||||
|
||||
assert( colourOffset < 65*65*3 &&
|
||||
"Colour offset is out of the expected bounds of record" );
|
||||
|
||||
const unsigned char r = colours[colourOffset + 0];
|
||||
const unsigned char g = colours[colourOffset + 1];
|
||||
const unsigned char b = colours[colourOffset + 2];
|
||||
|
||||
//as is the case elsewhere we need to flip the y
|
||||
const size_t imageOffset = (size - 1 - y)*size*4 + x*4;
|
||||
pDest[imageOffset + 0] = b;
|
||||
pDest[imageOffset + 1] = g;
|
||||
pDest[imageOffset + 2] = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for ( int y = 0; y < size; y++ )
|
||||
{
|
||||
for ( int x = 0; x < size; x++ )
|
||||
{
|
||||
for ( int k = 0; k < 3; k++ )
|
||||
{
|
||||
*pDest++ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pixelBuffer->unlock();
|
||||
|
||||
return tex;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
#ifndef _GAME_RENDER_TERRAIN_H
|
||||
#define _GAME_RENDER_TERRAIN_H
|
||||
|
||||
#include <OgreTerrain.h>
|
||||
#include <OgreTerrainGroup.h>
|
||||
#include "terrainmaterial.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
namespace Ogre{
|
||||
class SceneManager;
|
||||
class TerrainGroup;
|
||||
class TerrainGlobalOptions;
|
||||
class Terrain;
|
||||
}
|
||||
|
||||
namespace MWRender{
|
||||
|
||||
/**
|
||||
* Implements the Morrowind terrain using the Ogre Terrain Component
|
||||
*
|
||||
* Each terrain cell is split into four blocks as this leads to an increase
|
||||
* in performance and means we don't hit splat limits quite as much
|
||||
*/
|
||||
class TerrainManager{
|
||||
public:
|
||||
TerrainManager(Ogre::SceneManager* mgr, const MWWorld::Environment& env);
|
||||
virtual ~TerrainManager();
|
||||
|
||||
void setDiffuse(const Ogre::ColourValue& diffuse);
|
||||
void setAmbient(const Ogre::ColourValue& ambient);
|
||||
|
||||
void cellAdded(MWWorld::Ptr::CellStore* store);
|
||||
void cellRemoved(MWWorld::Ptr::CellStore* store);
|
||||
private:
|
||||
Ogre::TerrainGlobalOptions mTerrainGlobals;
|
||||
Ogre::TerrainGroup mTerrainGroup;
|
||||
|
||||
const MWWorld::Environment& mEnvironment;
|
||||
|
||||
Ogre::TerrainMaterialGeneratorB::SM2Profile* mActiveProfile;
|
||||
|
||||
/**
|
||||
* The length in verticies of a single terrain block.
|
||||
*/
|
||||
static const int mLandSize = (ESM::Land::LAND_SIZE - 1)/2 + 1;
|
||||
|
||||
/**
|
||||
* The length in game units of a single terrain block.
|
||||
*/
|
||||
static const int mWorldSize = ESM::Land::REAL_SIZE/2;
|
||||
|
||||
/**
|
||||
* Setups up the list of textures for part of a cell, using indexes as
|
||||
* an output to create a mapping of MW LtexIndex to the relevant terrain
|
||||
* layer
|
||||
*
|
||||
* @param terrainData the terrain data to setup the textures for
|
||||
* @param cellX the coord of the cell
|
||||
* @param cellY the coord of the cell
|
||||
* @param fromX the ltex index in the current cell to start making the texture from
|
||||
* @param fromY the ltex index in the current cell to start making the texture from
|
||||
* @param size the size (number of splats) to get
|
||||
* @param indexes a mapping of ltex index to the terrain texture layer that
|
||||
* can be used by initTerrainBlendMaps
|
||||
*/
|
||||
void initTerrainTextures(Ogre::Terrain::ImportData* terrainData,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size,
|
||||
std::map<uint16_t, int>& indexes);
|
||||
|
||||
/**
|
||||
* Creates the blend (splatting maps) for the given terrain from the ltex data.
|
||||
*
|
||||
* @param terrain the terrain object for the current cell
|
||||
* @param cellX the coord of the cell
|
||||
* @param cellY the coord of the cell
|
||||
* @param fromX the ltex index in the current cell to start making the texture from
|
||||
* @param fromY the ltex index in the current cell to start making the texture from
|
||||
* @param size the size (number of splats) to get
|
||||
* @param indexes the mapping of ltex to blend map produced by initTerrainTextures
|
||||
*/
|
||||
void initTerrainBlendMaps(Ogre::Terrain* terrain,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size,
|
||||
const std::map<uint16_t, int>& indexes);
|
||||
|
||||
/**
|
||||
* Gets a LTEX index at the given point, assuming the current cell
|
||||
* starts at (0,0). This supports getting values from the surrounding
|
||||
* cells so negative x, y is acceptable
|
||||
*
|
||||
* @param cellX the coord of the cell
|
||||
* @param cellY the coord of the cell
|
||||
* @param x, y the splat position of the ltex index to get relative to the
|
||||
* first splat of the current cell
|
||||
*/
|
||||
int getLtexIndexAt(int cellX, int cellY, int x, int y);
|
||||
|
||||
/**
|
||||
* Due to the fact that Ogre terrain doesn't support vertex colours
|
||||
* we have to generate them manually
|
||||
*
|
||||
* @param cellX the coord of the cell
|
||||
* @param cellY the coord of the cell
|
||||
* @param fromX the *vertex* index in the current cell to start making texture from
|
||||
* @param fromY the *vertex* index in the current cell to start making the texture from
|
||||
* @param size the size (number of vertexes) to get
|
||||
*/
|
||||
Ogre::TexturePtr getVertexColours(ESM::Land* land,
|
||||
int cellX, int cellY,
|
||||
int fromX, int fromY, int size);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // _GAME_RENDER_TERRAIN_H
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,266 @@
|
||||
/*
|
||||
-----------------------------------------------------------------------------
|
||||
This source file is part of OGRE
|
||||
(Object-oriented Graphics Rendering Engine)
|
||||
For the latest info, see http://www.ogre3d.org/
|
||||
|
||||
Copyright (c) 2000-2011 Torus Knot Software Ltd
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef __Ogre_TerrainMaterialGeneratorB_H__
|
||||
#define __Ogre_TerrainMaterialGeneratorB_H__
|
||||
|
||||
#include "OgreTerrainPrerequisites.h"
|
||||
#include "OgreTerrainMaterialGenerator.h"
|
||||
#include "OgreGpuProgramParams.h"
|
||||
|
||||
namespace Ogre
|
||||
{
|
||||
class PSSMShadowCameraSetup;
|
||||
|
||||
/** \addtogroup Optional Components
|
||||
* @{
|
||||
*/
|
||||
/** \addtogroup Terrain
|
||||
* Some details on the terrain component
|
||||
* @{
|
||||
*/
|
||||
|
||||
|
||||
/** A TerrainMaterialGenerator which can cope with normal mapped, specular mapped
|
||||
terrain.
|
||||
@note Requires the Cg plugin to render correctly
|
||||
*/
|
||||
class TerrainMaterialGeneratorB : public TerrainMaterialGenerator
|
||||
{
|
||||
public:
|
||||
TerrainMaterialGeneratorB();
|
||||
~TerrainMaterialGeneratorB();
|
||||
|
||||
/** Shader model 2 profile target.
|
||||
*/
|
||||
class SM2Profile : public TerrainMaterialGenerator::Profile
|
||||
{
|
||||
public:
|
||||
SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc);
|
||||
~SM2Profile();
|
||||
|
||||
bool isVertexCompressionSupported() const {return false;}
|
||||
|
||||
MaterialPtr generate(const Terrain* terrain);
|
||||
MaterialPtr generateForCompositeMap(const Terrain* terrain);
|
||||
uint8 getMaxLayers(const Terrain* terrain) const;
|
||||
void updateParams(const MaterialPtr& mat, const Terrain* terrain);
|
||||
void updateParamsForCompositeMap(const MaterialPtr& mat, const Terrain* terrain);
|
||||
void requestOptions(Terrain* terrain);
|
||||
|
||||
/** Whether to support normal mapping per layer in the shader (default true).
|
||||
*/
|
||||
bool isLayerNormalMappingEnabled() const { return mLayerNormalMappingEnabled; }
|
||||
/** Whether to support normal mapping per layer in the shader (default true).
|
||||
*/
|
||||
void setLayerNormalMappingEnabled(bool enabled);
|
||||
/** Whether to support parallax mapping per layer in the shader (default true).
|
||||
*/
|
||||
bool isLayerParallaxMappingEnabled() const { return mLayerParallaxMappingEnabled; }
|
||||
/** Whether to support parallax mapping per layer in the shader (default true).
|
||||
*/
|
||||
void setLayerParallaxMappingEnabled(bool enabled);
|
||||
/** Whether to support specular mapping per layer in the shader (default true).
|
||||
*/
|
||||
bool isLayerSpecularMappingEnabled() const { return mLayerSpecularMappingEnabled; }
|
||||
/** Whether to support specular mapping per layer in the shader (default true).
|
||||
*/
|
||||
void setLayerSpecularMappingEnabled(bool enabled);
|
||||
/** Whether to support a global colour map over the terrain in the shader,
|
||||
if it's present (default true).
|
||||
*/
|
||||
bool isGlobalColourMapEnabled() const { return mGlobalColourMapEnabled; }
|
||||
/** Whether to support a global colour map over the terrain in the shader,
|
||||
if it's present (default true).
|
||||
*/
|
||||
void setGlobalColourMapEnabled(bool enabled);
|
||||
/** Whether to support a light map over the terrain in the shader,
|
||||
if it's present (default true).
|
||||
*/
|
||||
bool isLightmapEnabled() const { return mLightmapEnabled; }
|
||||
/** Whether to support a light map over the terrain in the shader,
|
||||
if it's present (default true).
|
||||
*/
|
||||
void setLightmapEnabled(bool enabled);
|
||||
/** Whether to use the composite map to provide a lower LOD technique
|
||||
in the distance (default true).
|
||||
*/
|
||||
bool isCompositeMapEnabled() const { return mCompositeMapEnabled; }
|
||||
/** Whether to use the composite map to provide a lower LOD technique
|
||||
in the distance (default true).
|
||||
*/
|
||||
void setCompositeMapEnabled(bool enabled);
|
||||
/** Whether to support dynamic texture shadows received from other
|
||||
objects, on the terrain (default true).
|
||||
*/
|
||||
bool getReceiveDynamicShadowsEnabled() const { return mReceiveDynamicShadows; }
|
||||
/** Whether to support dynamic texture shadows received from other
|
||||
objects, on the terrain (default true).
|
||||
*/
|
||||
void setReceiveDynamicShadowsEnabled(bool enabled);
|
||||
|
||||
/** Whether to use PSSM support dynamic texture shadows, and if so the
|
||||
settings to use (default 0).
|
||||
*/
|
||||
void setReceiveDynamicShadowsPSSM(PSSMShadowCameraSetup* pssmSettings);
|
||||
/** Whether to use PSSM support dynamic texture shadows, and if so the
|
||||
settings to use (default 0).
|
||||
*/
|
||||
PSSMShadowCameraSetup* getReceiveDynamicShadowsPSSM() const { return mPSSM; }
|
||||
/** Whether to use depth shadows (default false).
|
||||
*/
|
||||
void setReceiveDynamicShadowsDepth(bool enabled);
|
||||
/** Whether to use depth shadows (default false).
|
||||
*/
|
||||
bool getReceiveDynamicShadowsDepth() const { return mDepthShadows; }
|
||||
/** Whether to use shadows on low LOD material rendering (when using composite map) (default false).
|
||||
*/
|
||||
void setReceiveDynamicShadowsLowLod(bool enabled);
|
||||
/** Whether to use shadows on low LOD material rendering (when using composite map) (default false).
|
||||
*/
|
||||
bool getReceiveDynamicShadowsLowLod() const { return mLowLodShadows; }
|
||||
|
||||
int getNumberOfLightsSupported() const;
|
||||
|
||||
/// Internal
|
||||
bool _isSM3Available() const { return mSM3Available; }
|
||||
|
||||
protected:
|
||||
|
||||
enum TechniqueType
|
||||
{
|
||||
HIGH_LOD,
|
||||
LOW_LOD,
|
||||
RENDER_COMPOSITE_MAP
|
||||
};
|
||||
void addTechnique(const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt);
|
||||
|
||||
/// Interface definition for helper class to generate shaders
|
||||
class ShaderHelper : public TerrainAlloc
|
||||
{
|
||||
public:
|
||||
ShaderHelper() {}
|
||||
virtual ~ShaderHelper() {}
|
||||
virtual HighLevelGpuProgramPtr generateVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||
virtual HighLevelGpuProgramPtr generateFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||
virtual void updateParams(const SM2Profile* prof, const MaterialPtr& mat, const Terrain* terrain, bool compositeMap);
|
||||
protected:
|
||||
virtual String getVertexProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||
virtual String getFragmentProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||
virtual HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0;
|
||||
virtual HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0;
|
||||
virtual void generateVertexProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
virtual void generateFragmentProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
virtual void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0;
|
||||
virtual void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0;
|
||||
virtual void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0;
|
||||
virtual void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0;
|
||||
virtual void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0;
|
||||
virtual void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0;
|
||||
virtual void defaultVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog);
|
||||
virtual void defaultFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog);
|
||||
virtual void updateVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params);
|
||||
virtual void updateFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params);
|
||||
static String getChannel(uint idx);
|
||||
|
||||
size_t mShadowSamplerStartHi;
|
||||
size_t mShadowSamplerStartLo;
|
||||
|
||||
};
|
||||
|
||||
/// Utility class to help with generating shaders for Cg / HLSL.
|
||||
class ShaderHelperCg : public ShaderHelper
|
||||
{
|
||||
protected:
|
||||
HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||
HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||
void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream);
|
||||
void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream);
|
||||
void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
uint generateVpDynamicShadowsParams(uint texCoordStart, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
void generateVpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
void generateFpDynamicShadowsHelpers(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
void generateFpDynamicShadowsParams(uint* texCoord, uint* sampler, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
void generateFpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream);
|
||||
};
|
||||
|
||||
class ShaderHelperHLSL : public ShaderHelperCg
|
||||
{
|
||||
protected:
|
||||
HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||
HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||
};
|
||||
|
||||
/// Utility class to help with generating shaders for GLSL.
|
||||
class ShaderHelperGLSL : public ShaderHelper
|
||||
{
|
||||
protected:
|
||||
HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||
HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt);
|
||||
void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {}
|
||||
void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {}
|
||||
void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {}
|
||||
void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {}
|
||||
void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {}
|
||||
void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {}
|
||||
};
|
||||
|
||||
ShaderHelper* mShaderGen;
|
||||
bool mLayerNormalMappingEnabled;
|
||||
bool mLayerParallaxMappingEnabled;
|
||||
bool mLayerSpecularMappingEnabled;
|
||||
bool mGlobalColourMapEnabled;
|
||||
bool mLightmapEnabled;
|
||||
bool mCompositeMapEnabled;
|
||||
bool mReceiveDynamicShadows;
|
||||
PSSMShadowCameraSetup* mPSSM;
|
||||
bool mDepthShadows;
|
||||
bool mLowLodShadows;
|
||||
bool mSM3Available;
|
||||
|
||||
bool isShadowingEnabled(TechniqueType tt, const Terrain* terrain) const;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,95 @@
|
||||
#include "water.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
Water::Water (Ogre::Camera *camera, const ESM::Cell* cell) :
|
||||
mCamera (camera), mViewport (camera->getViewport()), mSceneManager (camera->getSceneManager()),
|
||||
mIsUnderwater(false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Ogre::CompositorManager::getSingleton().addCompositor(mViewport, "Water", -1);
|
||||
Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", false);
|
||||
} catch(...) {}
|
||||
|
||||
mTop = cell->water;
|
||||
|
||||
mIsUnderwater = false;
|
||||
|
||||
mWaterPlane = Ogre::Plane(Ogre::Vector3::UNIT_Y, 0);
|
||||
|
||||
Ogre::MeshManager::getSingleton().createPlane("water", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,5, Ogre::Vector3::UNIT_Z);
|
||||
|
||||
mWater = mSceneManager->createEntity("water");
|
||||
|
||||
mWater->setMaterialName("Examples/Water0");
|
||||
|
||||
mWaterNode = mSceneManager->getRootSceneNode()->createChildSceneNode();
|
||||
mWaterNode->setPosition(0, mTop, 0);
|
||||
|
||||
if(!(cell->data.flags & cell->Interior))
|
||||
{
|
||||
mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY));
|
||||
}
|
||||
mWaterNode->attachObject(mWater);
|
||||
}
|
||||
|
||||
|
||||
Water::~Water()
|
||||
{
|
||||
Ogre::MeshManager::getSingleton().remove("water");
|
||||
|
||||
mWaterNode->detachObject(mWater);
|
||||
mSceneManager->destroyEntity(mWater);
|
||||
mSceneManager->destroySceneNode(mWaterNode);
|
||||
|
||||
Ogre::CompositorManager::getSingleton().removeCompositorChain(mViewport);
|
||||
}
|
||||
|
||||
void Water::changeCell(const ESM::Cell* cell)
|
||||
{
|
||||
mTop = cell->water;
|
||||
|
||||
if(!(cell->data.flags & cell->Interior))
|
||||
mWaterNode->setPosition(getSceneNodeCoordinates(cell->data.gridX, cell->data.gridY));
|
||||
else
|
||||
setHeight(mTop);
|
||||
}
|
||||
|
||||
void Water::setHeight(const float height)
|
||||
{
|
||||
mTop = height;
|
||||
mWaterNode->setPosition(0, height, 0);
|
||||
}
|
||||
|
||||
void Water::toggle()
|
||||
{
|
||||
mWater->setVisible(!mWater->getVisible());
|
||||
}
|
||||
|
||||
void Water::checkUnderwater(float y)
|
||||
{
|
||||
if ((mIsUnderwater && y > mTop) || !mWater->isVisible())
|
||||
{
|
||||
try {
|
||||
Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", false);
|
||||
} catch(...) {}
|
||||
mIsUnderwater = false;
|
||||
}
|
||||
|
||||
if (!mIsUnderwater && y < mTop && mWater->isVisible())
|
||||
{
|
||||
try {
|
||||
Ogre::CompositorManager::getSingleton().setCompositorEnabled(mViewport, "Water", true);
|
||||
} catch(...) {}
|
||||
mIsUnderwater = true;
|
||||
}
|
||||
}
|
||||
|
||||
Ogre::Vector3 Water::getSceneNodeCoordinates(int gridX, int gridY)
|
||||
{
|
||||
return Ogre::Vector3(gridX * CELL_SIZE + (CELL_SIZE / 2), mTop, -gridY * CELL_SIZE - (CELL_SIZE / 2));
|
||||
}
|
||||
|
||||
} // namespace
|
@ -0,0 +1,40 @@
|
||||
#ifndef GAME_MWRENDER_WATER_H
|
||||
#define GAME_MWRENDER_WATER_H
|
||||
|
||||
#include <Ogre.h>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
namespace MWRender {
|
||||
|
||||
/// Water rendering
|
||||
class Water : Ogre::RenderTargetListener, Ogre::Camera::Listener
|
||||
{
|
||||
static const int CELL_SIZE = 8192;
|
||||
Ogre::Camera *mCamera;
|
||||
Ogre::SceneManager *mSceneManager;
|
||||
Ogre::Viewport *mViewport;
|
||||
|
||||
Ogre::Plane mWaterPlane;
|
||||
Ogre::SceneNode *mWaterNode;
|
||||
Ogre::Entity *mWater;
|
||||
|
||||
bool mIsUnderwater;
|
||||
int mTop;
|
||||
|
||||
Ogre::Vector3 getSceneNodeCoordinates(int gridX, int gridY);
|
||||
|
||||
public:
|
||||
Water (Ogre::Camera *camera, const ESM::Cell* cell);
|
||||
~Water();
|
||||
|
||||
void toggle();
|
||||
|
||||
void checkUnderwater(float y);
|
||||
void changeCell(const ESM::Cell* cell);
|
||||
void setHeight(const float height);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,407 @@
|
||||
#ifdef OPENMW_USE_FFMPEG
|
||||
|
||||
#include "ffmpeg_decoder.hpp"
|
||||
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
|
||||
static void fail(const std::string &msg)
|
||||
{ throw std::runtime_error("FFmpeg exception: "+msg); }
|
||||
|
||||
|
||||
struct PacketList {
|
||||
AVPacket pkt;
|
||||
PacketList *next;
|
||||
};
|
||||
|
||||
struct FFmpeg_Decoder::MyStream {
|
||||
AVCodecContext *mCodecCtx;
|
||||
int mStreamIdx;
|
||||
|
||||
PacketList *mPackets;
|
||||
|
||||
char *mDecodedData;
|
||||
size_t mDecodedDataSize;
|
||||
|
||||
FFmpeg_Decoder *mParent;
|
||||
|
||||
void clearPackets();
|
||||
void *getAVAudioData(size_t *length);
|
||||
size_t readAVAudioData(void *data, size_t length);
|
||||
};
|
||||
|
||||
|
||||
int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = static_cast<FFmpeg_Decoder*>(user_data)->mDataStream;
|
||||
return stream->read(buf, buf_size);
|
||||
}
|
||||
|
||||
int FFmpeg_Decoder::writePacket(void *user_data, uint8_t *buf, int buf_size)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = static_cast<FFmpeg_Decoder*>(user_data)->mDataStream;
|
||||
return stream->write(buf, buf_size);
|
||||
}
|
||||
|
||||
int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = static_cast<FFmpeg_Decoder*>(user_data)->mDataStream;
|
||||
|
||||
whence &= ~AVSEEK_FORCE;
|
||||
if(whence == AVSEEK_SIZE)
|
||||
return stream->size();
|
||||
if(whence == SEEK_SET)
|
||||
stream->seek(offset);
|
||||
else if(whence == SEEK_CUR)
|
||||
stream->seek(stream->tell()+offset);
|
||||
else if(whence == SEEK_END)
|
||||
stream->seek(stream->size()+offset);
|
||||
else
|
||||
return -1;
|
||||
|
||||
return stream->tell();
|
||||
}
|
||||
|
||||
|
||||
/* Used by getAV*Data to search for more compressed data, and buffer it in the
|
||||
* correct stream. It won't buffer data for streams that the app doesn't have a
|
||||
* handle for. */
|
||||
bool FFmpeg_Decoder::getNextPacket(int streamidx)
|
||||
{
|
||||
PacketList *packet;
|
||||
|
||||
packet = (PacketList*)av_malloc(sizeof(*packet));
|
||||
packet->next = NULL;
|
||||
|
||||
next_packet:
|
||||
while(av_read_frame(mFormatCtx, &packet->pkt) >= 0)
|
||||
{
|
||||
std::vector<MyStream*>::iterator iter = mStreams.begin();
|
||||
|
||||
/* Check each stream the user has a handle for, looking for the one
|
||||
* this packet belongs to */
|
||||
while(iter != mStreams.end())
|
||||
{
|
||||
if((*iter)->mStreamIdx == packet->pkt.stream_index)
|
||||
{
|
||||
PacketList **last;
|
||||
|
||||
last = &(*iter)->mPackets;
|
||||
while(*last != NULL)
|
||||
last = &(*last)->next;
|
||||
|
||||
*last = packet;
|
||||
if((*iter)->mStreamIdx == streamidx)
|
||||
return true;
|
||||
|
||||
packet = (PacketList*)av_malloc(sizeof(*packet));
|
||||
packet->next = NULL;
|
||||
goto next_packet;
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
/* Free the packet and look for another */
|
||||
av_free_packet(&packet->pkt);
|
||||
}
|
||||
av_free(packet);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void FFmpeg_Decoder::MyStream::clearPackets()
|
||||
{
|
||||
while(mPackets)
|
||||
{
|
||||
PacketList *self = mPackets;
|
||||
mPackets = self->next;
|
||||
|
||||
av_free_packet(&self->pkt);
|
||||
av_free(self);
|
||||
}
|
||||
}
|
||||
|
||||
void *FFmpeg_Decoder::MyStream::getAVAudioData(size_t *length)
|
||||
{
|
||||
int size;
|
||||
int len;
|
||||
|
||||
if(length) *length = 0;
|
||||
if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO)
|
||||
return NULL;
|
||||
|
||||
mDecodedDataSize = 0;
|
||||
|
||||
next_packet:
|
||||
if(!mPackets && !mParent->getNextPacket(mStreamIdx))
|
||||
return NULL;
|
||||
|
||||
/* Decode some data, and check for errors */
|
||||
size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
|
||||
while((len=avcodec_decode_audio3(mCodecCtx, (int16_t*)mDecodedData, &size,
|
||||
&mPackets->pkt)) == 0)
|
||||
{
|
||||
PacketList *self;
|
||||
|
||||
if(size > 0)
|
||||
break;
|
||||
|
||||
/* Packet went unread and no data was given? Drop it and try the next,
|
||||
* I guess... */
|
||||
self = mPackets;
|
||||
mPackets = self->next;
|
||||
|
||||
av_free_packet(&self->pkt);
|
||||
av_free(self);
|
||||
|
||||
if(!mPackets)
|
||||
goto next_packet;
|
||||
|
||||
size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
|
||||
}
|
||||
|
||||
if(len < 0)
|
||||
return NULL;
|
||||
|
||||
if(len < mPackets->pkt.size)
|
||||
{
|
||||
/* Move the unread data to the front and clear the end bits */
|
||||
int remaining = mPackets->pkt.size - len;
|
||||
memmove(mPackets->pkt.data, &mPackets->pkt.data[len], remaining);
|
||||
memset(&mPackets->pkt.data[remaining], 0, mPackets->pkt.size - remaining);
|
||||
mPackets->pkt.size -= len;
|
||||
}
|
||||
else
|
||||
{
|
||||
PacketList *self;
|
||||
|
||||
self = mPackets;
|
||||
mPackets = self->next;
|
||||
|
||||
av_free_packet(&self->pkt);
|
||||
av_free(self);
|
||||
}
|
||||
|
||||
if(size == 0)
|
||||
goto next_packet;
|
||||
|
||||
/* Set the output buffer size */
|
||||
mDecodedDataSize = size;
|
||||
if(length) *length = mDecodedDataSize;
|
||||
|
||||
return mDecodedData;
|
||||
}
|
||||
|
||||
size_t FFmpeg_Decoder::MyStream::readAVAudioData(void *data, size_t length)
|
||||
{
|
||||
size_t dec = 0;
|
||||
|
||||
while(dec < length)
|
||||
{
|
||||
/* If there's no decoded data, find some */
|
||||
if(mDecodedDataSize == 0)
|
||||
{
|
||||
if(getAVAudioData(NULL) == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
if(mDecodedDataSize > 0)
|
||||
{
|
||||
/* Get the amount of bytes remaining to be written, and clamp to
|
||||
* the amount of decoded data we have */
|
||||
size_t rem = length-dec;
|
||||
if(rem > mDecodedDataSize)
|
||||
rem = mDecodedDataSize;
|
||||
|
||||
/* Copy the data to the app's buffer and increment */
|
||||
if(data != NULL)
|
||||
{
|
||||
memcpy(data, mDecodedData, rem);
|
||||
data = (char*)data + rem;
|
||||
}
|
||||
dec += rem;
|
||||
|
||||
/* If there's any decoded data left, move it to the front of the
|
||||
* buffer for next time */
|
||||
if(rem < mDecodedDataSize)
|
||||
memmove(mDecodedData, &mDecodedData[rem], mDecodedDataSize - rem);
|
||||
mDecodedDataSize -= rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the number of bytes we were able to get */
|
||||
return dec;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FFmpeg_Decoder::open(const std::string &fname)
|
||||
{
|
||||
close();
|
||||
mDataStream = mResourceMgr.openResource(fname);
|
||||
|
||||
if((mFormatCtx=avformat_alloc_context()) == NULL)
|
||||
fail("Failed to allocate context");
|
||||
|
||||
mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek);
|
||||
if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0)
|
||||
{
|
||||
avformat_free_context(mFormatCtx);
|
||||
mFormatCtx = NULL;
|
||||
fail("Failed to allocate input stream");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(avformat_find_stream_info(mFormatCtx, NULL) < 0)
|
||||
fail("Failed to find stream info in "+fname);
|
||||
|
||||
for(size_t j = 0;j < mFormatCtx->nb_streams;j++)
|
||||
{
|
||||
if(mFormatCtx->streams[j]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
{
|
||||
std::auto_ptr<MyStream> stream(new MyStream);
|
||||
stream->mCodecCtx = mFormatCtx->streams[j]->codec;
|
||||
stream->mStreamIdx = j;
|
||||
stream->mPackets = NULL;
|
||||
|
||||
AVCodec *codec = avcodec_find_decoder(stream->mCodecCtx->codec_id);
|
||||
if(!codec)
|
||||
{
|
||||
std::stringstream ss("No codec found for id ");
|
||||
ss << stream->mCodecCtx->codec_id;
|
||||
fail(ss.str());
|
||||
}
|
||||
if(avcodec_open(stream->mCodecCtx, codec) < 0)
|
||||
fail("Failed to open audio codec " + std::string(codec->long_name));
|
||||
|
||||
stream->mDecodedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE);
|
||||
stream->mDecodedDataSize = 0;
|
||||
|
||||
stream->mParent = this;
|
||||
mStreams.push_back(stream.release());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(mStreams.empty())
|
||||
fail("No audio streams in "+fname);
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
av_close_input_file(mFormatCtx);
|
||||
mFormatCtx = NULL;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void FFmpeg_Decoder::close()
|
||||
{
|
||||
while(!mStreams.empty())
|
||||
{
|
||||
MyStream *stream = mStreams.front();
|
||||
|
||||
stream->clearPackets();
|
||||
avcodec_close(stream->mCodecCtx);
|
||||
av_free(stream->mDecodedData);
|
||||
delete stream;
|
||||
|
||||
mStreams.erase(mStreams.begin());
|
||||
}
|
||||
if(mFormatCtx)
|
||||
av_close_input_file(mFormatCtx);
|
||||
mFormatCtx = NULL;
|
||||
|
||||
mDataStream.setNull();
|
||||
}
|
||||
|
||||
void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
|
||||
{
|
||||
if(mStreams.empty())
|
||||
fail("No audio stream info");
|
||||
|
||||
MyStream *stream = mStreams[0];
|
||||
if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8)
|
||||
*type = SampleType_UInt8;
|
||||
else if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16)
|
||||
*type = SampleType_Int16;
|
||||
else
|
||||
fail(std::string("Unsupported sample format: ")+
|
||||
av_get_sample_fmt_name(stream->mCodecCtx->sample_fmt));
|
||||
|
||||
if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO)
|
||||
*chans = ChannelConfig_Mono;
|
||||
else if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_STEREO)
|
||||
*chans = ChannelConfig_Stereo;
|
||||
else if(stream->mCodecCtx->channel_layout == 0)
|
||||
{
|
||||
/* Unknown channel layout. Try to guess. */
|
||||
if(stream->mCodecCtx->channels == 1)
|
||||
*chans = ChannelConfig_Mono;
|
||||
else if(stream->mCodecCtx->channels == 2)
|
||||
*chans = ChannelConfig_Stereo;
|
||||
else
|
||||
{
|
||||
std::stringstream sstr("Unsupported raw channel count: ");
|
||||
sstr << stream->mCodecCtx->channels;
|
||||
fail(sstr.str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
char str[1024];
|
||||
av_get_channel_layout_string(str, sizeof(str), stream->mCodecCtx->channels,
|
||||
stream->mCodecCtx->channel_layout);
|
||||
fail(std::string("Unsupported channel layout: ")+str);
|
||||
}
|
||||
|
||||
*samplerate = stream->mCodecCtx->sample_rate;
|
||||
}
|
||||
|
||||
size_t FFmpeg_Decoder::read(char *buffer, size_t bytes)
|
||||
{
|
||||
if(mStreams.empty())
|
||||
fail("No audio streams");
|
||||
|
||||
return mStreams.front()->readAVAudioData(buffer, bytes);
|
||||
}
|
||||
|
||||
void FFmpeg_Decoder::readAll(std::vector<char> &output)
|
||||
{
|
||||
if(mStreams.empty())
|
||||
fail("No audio streams");
|
||||
MyStream *stream = mStreams.front();
|
||||
char *inbuf;
|
||||
size_t got;
|
||||
|
||||
while((inbuf=(char*)stream->getAVAudioData(&got)) != NULL && got > 0)
|
||||
output.insert(output.end(), inbuf, inbuf+got);
|
||||
}
|
||||
|
||||
void FFmpeg_Decoder::rewind()
|
||||
{
|
||||
av_seek_frame(mFormatCtx, -1, 0, 0);
|
||||
std::for_each(mStreams.begin(), mStreams.end(), std::mem_fun(&MyStream::clearPackets));
|
||||
}
|
||||
|
||||
FFmpeg_Decoder::FFmpeg_Decoder() : mFormatCtx(NULL)
|
||||
{
|
||||
static bool done_init = false;
|
||||
|
||||
/* We need to make sure ffmpeg is initialized. Optionally silence warning
|
||||
* output from the lib */
|
||||
if(!done_init)
|
||||
{
|
||||
av_register_all();
|
||||
av_log_set_level(AV_LOG_ERROR);
|
||||
done_init = true;
|
||||
}
|
||||
}
|
||||
|
||||
FFmpeg_Decoder::~FFmpeg_Decoder()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,59 @@
|
||||
#ifndef GAME_SOUND_FFMPEG_DECODER_H
|
||||
#define GAME_SOUND_FFMPEG_DECODER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
// FIXME: This can't be right? The headers refuse to build without UINT64_C,
|
||||
// which only gets defined in stdint.h in either C99 mode or with this macro
|
||||
// defined...
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#include <stdint.h>
|
||||
extern "C"
|
||||
{
|
||||
#include <avcodec.h>
|
||||
#include <avformat.h>
|
||||
}
|
||||
|
||||
#include "sound_decoder.hpp"
|
||||
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
class FFmpeg_Decoder : public Sound_Decoder
|
||||
{
|
||||
AVFormatContext *mFormatCtx;
|
||||
|
||||
struct MyStream;
|
||||
std::vector<MyStream*> mStreams;
|
||||
|
||||
bool getNextPacket(int streamidx);
|
||||
|
||||
Ogre::DataStreamPtr mDataStream;
|
||||
static int readPacket(void *user_data, uint8_t *buf, int buf_size);
|
||||
static int writePacket(void *user_data, uint8_t *buf, int buf_size);
|
||||
static int64_t seek(void *user_data, int64_t offset, int whence);
|
||||
|
||||
virtual void open(const std::string &fname);
|
||||
virtual void close();
|
||||
|
||||
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
|
||||
|
||||
virtual size_t read(char *buffer, size_t bytes);
|
||||
virtual void readAll(std::vector<char> &output);
|
||||
virtual void rewind();
|
||||
|
||||
FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs);
|
||||
FFmpeg_Decoder(const FFmpeg_Decoder &rhs);
|
||||
|
||||
FFmpeg_Decoder();
|
||||
public:
|
||||
virtual ~FFmpeg_Decoder();
|
||||
|
||||
friend class SoundManager;
|
||||
};
|
||||
#ifndef DEFAULT_DECODER
|
||||
#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder)
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
@ -0,0 +1,237 @@
|
||||
#ifdef OPENMW_USE_MPG123
|
||||
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include "mpgsnd_decoder.hpp"
|
||||
|
||||
|
||||
static void fail(const std::string &msg)
|
||||
{ throw std::runtime_error("MpgSnd exception: "+msg); }
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
|
||||
//
|
||||
// libSndFile io callbacks
|
||||
//
|
||||
sf_count_t MpgSnd_Decoder::ogresf_get_filelen(void *user_data)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
|
||||
return stream->size();
|
||||
}
|
||||
|
||||
sf_count_t MpgSnd_Decoder::ogresf_seek(sf_count_t offset, int whence, void *user_data)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
|
||||
|
||||
if(whence == SEEK_CUR)
|
||||
stream->seek(stream->tell()+offset);
|
||||
else if(whence == SEEK_SET)
|
||||
stream->seek(offset);
|
||||
else if(whence == SEEK_END)
|
||||
stream->seek(stream->size()+offset);
|
||||
else
|
||||
return -1;
|
||||
|
||||
return stream->tell();
|
||||
}
|
||||
|
||||
sf_count_t MpgSnd_Decoder::ogresf_read(void *ptr, sf_count_t count, void *user_data)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
|
||||
return stream->read(ptr, count);
|
||||
}
|
||||
|
||||
sf_count_t MpgSnd_Decoder::ogresf_write(const void*, sf_count_t, void*)
|
||||
{ return -1; }
|
||||
|
||||
sf_count_t MpgSnd_Decoder::ogresf_tell(void *user_data)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
|
||||
return stream->tell();
|
||||
}
|
||||
|
||||
//
|
||||
// libmpg13 io callbacks
|
||||
//
|
||||
ssize_t MpgSnd_Decoder::ogrempg_read(void *user_data, void *ptr, size_t count)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
|
||||
return stream->read(ptr, count);
|
||||
}
|
||||
|
||||
off_t MpgSnd_Decoder::ogrempg_lseek(void *user_data, off_t offset, int whence)
|
||||
{
|
||||
Ogre::DataStreamPtr stream = static_cast<MpgSnd_Decoder*>(user_data)->mDataStream;
|
||||
|
||||
if(whence == SEEK_CUR)
|
||||
stream->seek(stream->tell()+offset);
|
||||
else if(whence == SEEK_SET)
|
||||
stream->seek(offset);
|
||||
else if(whence == SEEK_END)
|
||||
stream->seek(stream->size()+offset);
|
||||
else
|
||||
return -1;
|
||||
|
||||
return stream->tell();
|
||||
}
|
||||
|
||||
|
||||
void MpgSnd_Decoder::open(const std::string &fname)
|
||||
{
|
||||
close();
|
||||
mDataStream = mResourceMgr.openResource(fname);
|
||||
|
||||
SF_VIRTUAL_IO streamIO = {
|
||||
ogresf_get_filelen, ogresf_seek,
|
||||
ogresf_read, ogresf_write, ogresf_tell
|
||||
};
|
||||
mSndFile = sf_open_virtual(&streamIO, SFM_READ, &mSndInfo, this);
|
||||
if(mSndFile)
|
||||
{
|
||||
if(mSndInfo.channels == 1)
|
||||
mChanConfig = ChannelConfig_Mono;
|
||||
else if(mSndInfo.channels == 2)
|
||||
mChanConfig = ChannelConfig_Stereo;
|
||||
else
|
||||
{
|
||||
sf_close(mSndFile);
|
||||
mSndFile = NULL;
|
||||
fail("Unsupported channel count in "+fname);
|
||||
}
|
||||
mSampleRate = mSndInfo.samplerate;
|
||||
return;
|
||||
}
|
||||
mDataStream->seek(0);
|
||||
|
||||
mMpgFile = mpg123_new(NULL, NULL);
|
||||
if(mMpgFile && mpg123_replace_reader_handle(mMpgFile, ogrempg_read, ogrempg_lseek, NULL) == MPG123_OK &&
|
||||
mpg123_open_handle(mMpgFile, this) == MPG123_OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
int encoding, channels;
|
||||
long rate;
|
||||
if(mpg123_getformat(mMpgFile, &rate, &channels, &encoding) != MPG123_OK)
|
||||
fail("Failed to get audio format");
|
||||
if(encoding != MPG123_ENC_SIGNED_16)
|
||||
fail("Unsupported encoding in "+fname);
|
||||
if(channels != 1 && channels != 2)
|
||||
fail("Unsupported channel count in "+fname);
|
||||
mChanConfig = ((channels==2)?ChannelConfig_Stereo:ChannelConfig_Mono);
|
||||
mSampleRate = rate;
|
||||
return;
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
mpg123_close(mMpgFile);
|
||||
mpg123_delete(mMpgFile);
|
||||
mMpgFile = NULL;
|
||||
throw;
|
||||
}
|
||||
mpg123_close(mMpgFile);
|
||||
}
|
||||
if(mMpgFile)
|
||||
mpg123_delete(mMpgFile);
|
||||
mMpgFile = NULL;
|
||||
|
||||
fail("Unsupported file type: "+fname);
|
||||
}
|
||||
|
||||
void MpgSnd_Decoder::close()
|
||||
{
|
||||
if(mSndFile)
|
||||
sf_close(mSndFile);
|
||||
mSndFile = NULL;
|
||||
|
||||
if(mMpgFile)
|
||||
{
|
||||
mpg123_close(mMpgFile);
|
||||
mpg123_delete(mMpgFile);
|
||||
mMpgFile = NULL;
|
||||
}
|
||||
|
||||
mDataStream.setNull();
|
||||
}
|
||||
|
||||
void MpgSnd_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
|
||||
{
|
||||
if(!mSndFile && !mMpgFile)
|
||||
fail("No open file");
|
||||
|
||||
*samplerate = mSampleRate;
|
||||
*chans = mChanConfig;
|
||||
*type = SampleType_Int16;
|
||||
}
|
||||
|
||||
size_t MpgSnd_Decoder::read(char *buffer, size_t bytes)
|
||||
{
|
||||
size_t got = 0;
|
||||
|
||||
if(mSndFile)
|
||||
{
|
||||
got = sf_read_short(mSndFile, (short*)buffer, bytes/2)*2;
|
||||
}
|
||||
else if(mMpgFile)
|
||||
{
|
||||
int err;
|
||||
err = mpg123_read(mMpgFile, (unsigned char*)buffer, bytes, &got);
|
||||
if(err != MPG123_OK && err != MPG123_DONE)
|
||||
fail("Failed to read from file");
|
||||
}
|
||||
return got;
|
||||
}
|
||||
|
||||
void MpgSnd_Decoder::readAll(std::vector<char> &output)
|
||||
{
|
||||
if(mSndFile && mSndInfo.frames > 0)
|
||||
{
|
||||
size_t pos = output.size();
|
||||
output.resize(pos + mSndInfo.frames*mSndInfo.channels*2);
|
||||
sf_readf_short(mSndFile, (short*)(output.data()+pos), mSndInfo.frames);
|
||||
return;
|
||||
}
|
||||
// Fallback in case we don't know the total already
|
||||
Sound_Decoder::readAll(output);
|
||||
}
|
||||
|
||||
void MpgSnd_Decoder::rewind()
|
||||
{
|
||||
if(!mSndFile && !mMpgFile)
|
||||
fail("No open file");
|
||||
|
||||
if(mSndFile)
|
||||
{
|
||||
if(sf_seek(mSndFile, 0, SEEK_SET) == -1)
|
||||
fail("seek failed");
|
||||
}
|
||||
else if(mMpgFile)
|
||||
{
|
||||
if(mpg123_seek(mMpgFile, 0, SEEK_SET) < 0)
|
||||
fail("seek failed");
|
||||
}
|
||||
}
|
||||
|
||||
MpgSnd_Decoder::MpgSnd_Decoder()
|
||||
: mSndInfo()
|
||||
, mSndFile(NULL)
|
||||
, mMpgFile(NULL)
|
||||
, mDataStream()
|
||||
, mChanConfig(ChannelConfig_Stereo)
|
||||
, mSampleRate(0)
|
||||
{
|
||||
static bool initdone = false;
|
||||
if(!initdone)
|
||||
mpg123_init();
|
||||
initdone = true;
|
||||
}
|
||||
|
||||
MpgSnd_Decoder::~MpgSnd_Decoder()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,57 @@
|
||||
#ifndef GAME_SOUND_MPGSND_DECODER_H
|
||||
#define GAME_SOUND_MPGSND_DECODER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <OgreDataStream.h>
|
||||
|
||||
#include "mpg123.h"
|
||||
#include "sndfile.h"
|
||||
|
||||
#include "sound_decoder.hpp"
|
||||
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
class MpgSnd_Decoder : public Sound_Decoder
|
||||
{
|
||||
SF_INFO mSndInfo;
|
||||
SNDFILE *mSndFile;
|
||||
mpg123_handle *mMpgFile;
|
||||
|
||||
Ogre::DataStreamPtr mDataStream;
|
||||
static sf_count_t ogresf_get_filelen(void *user_data);
|
||||
static sf_count_t ogresf_seek(sf_count_t offset, int whence, void *user_data);
|
||||
static sf_count_t ogresf_read(void *ptr, sf_count_t count, void *user_data);
|
||||
static sf_count_t ogresf_write(const void*, sf_count_t, void*);
|
||||
static sf_count_t ogresf_tell(void *user_data);
|
||||
static ssize_t ogrempg_read(void*, void*, size_t);
|
||||
static off_t ogrempg_lseek(void*, off_t, int);
|
||||
|
||||
ChannelConfig mChanConfig;
|
||||
int mSampleRate;
|
||||
|
||||
virtual void open(const std::string &fname);
|
||||
virtual void close();
|
||||
|
||||
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type);
|
||||
|
||||
virtual size_t read(char *buffer, size_t bytes);
|
||||
virtual void readAll(std::vector<char> &output);
|
||||
virtual void rewind();
|
||||
|
||||
MpgSnd_Decoder& operator=(const MpgSnd_Decoder &rhs);
|
||||
MpgSnd_Decoder(const MpgSnd_Decoder &rhs);
|
||||
|
||||
MpgSnd_Decoder();
|
||||
public:
|
||||
virtual ~MpgSnd_Decoder();
|
||||
|
||||
friend class SoundManager;
|
||||
};
|
||||
#ifndef DEFAULT_DECODER
|
||||
#define DEFAULT_DECODER (::MWSound::MpgSnd_Decoder)
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
@ -0,0 +1,775 @@
|
||||
#include <algorithm>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include "openal_output.hpp"
|
||||
#include "sound_decoder.hpp"
|
||||
#include "sound.hpp"
|
||||
#include "soundmanager.hpp"
|
||||
|
||||
#ifndef ALC_ALL_DEVICES_SPECIFIER
|
||||
#define ALC_ALL_DEVICES_SPECIFIER 0x1013
|
||||
#endif
|
||||
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
|
||||
static void fail(const std::string &msg)
|
||||
{ throw std::runtime_error("OpenAL exception: " + msg); }
|
||||
|
||||
static void throwALCerror(ALCdevice *device)
|
||||
{
|
||||
ALCenum err = alcGetError(device);
|
||||
if(err != ALC_NO_ERROR)
|
||||
{
|
||||
const ALCchar *errstring = alcGetString(device, err);
|
||||
fail(errstring ? errstring : "");
|
||||
}
|
||||
}
|
||||
|
||||
static void throwALerror()
|
||||
{
|
||||
ALenum err = alGetError();
|
||||
if(err != AL_NO_ERROR)
|
||||
{
|
||||
const ALchar *errstring = alGetString(err);
|
||||
fail(errstring ? errstring : "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static ALenum getALFormat(ChannelConfig chans, SampleType type)
|
||||
{
|
||||
static const struct {
|
||||
ALenum format;
|
||||
ChannelConfig chans;
|
||||
SampleType type;
|
||||
} fmtlist[] = {
|
||||
{ AL_FORMAT_MONO16, ChannelConfig_Mono, SampleType_Int16 },
|
||||
{ AL_FORMAT_MONO8, ChannelConfig_Mono, SampleType_UInt8 },
|
||||
{ AL_FORMAT_STEREO16, ChannelConfig_Stereo, SampleType_Int16 },
|
||||
{ AL_FORMAT_STEREO8, ChannelConfig_Stereo, SampleType_UInt8 },
|
||||
};
|
||||
static const size_t fmtlistsize = sizeof(fmtlist)/sizeof(fmtlist[0]);
|
||||
|
||||
for(size_t i = 0;i < fmtlistsize;i++)
|
||||
{
|
||||
if(fmtlist[i].chans == chans && fmtlist[i].type == type)
|
||||
return fmtlist[i].format;
|
||||
}
|
||||
fail(std::string("Unsupported sound format (")+getChannelConfigName(chans)+", "+getSampleTypeName(type)+")");
|
||||
return AL_NONE;
|
||||
}
|
||||
|
||||
//
|
||||
// A streaming OpenAL sound.
|
||||
//
|
||||
class OpenAL_SoundStream : public Sound
|
||||
{
|
||||
static const ALuint sNumBuffers = 6;
|
||||
static const ALfloat sBufferLength;
|
||||
|
||||
OpenAL_Output &mOutput;
|
||||
|
||||
ALuint mSource;
|
||||
ALuint mBuffers[sNumBuffers];
|
||||
|
||||
ALenum mFormat;
|
||||
ALsizei mSampleRate;
|
||||
ALuint mBufferSize;
|
||||
|
||||
DecoderPtr mDecoder;
|
||||
|
||||
volatile bool mIsFinished;
|
||||
|
||||
OpenAL_SoundStream(const OpenAL_SoundStream &rhs);
|
||||
OpenAL_SoundStream& operator=(const OpenAL_SoundStream &rhs);
|
||||
|
||||
public:
|
||||
OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder);
|
||||
virtual ~OpenAL_SoundStream();
|
||||
|
||||
virtual void stop();
|
||||
virtual bool isPlaying();
|
||||
virtual void update();
|
||||
|
||||
void play();
|
||||
bool process();
|
||||
};
|
||||
|
||||
const ALfloat OpenAL_SoundStream::sBufferLength = 0.125f;
|
||||
|
||||
//
|
||||
// A background streaming thread (keeps active streams processed)
|
||||
//
|
||||
struct OpenAL_Output::StreamThread {
|
||||
typedef std::vector<OpenAL_SoundStream*> StreamVec;
|
||||
StreamVec mStreams;
|
||||
boost::mutex mMutex;
|
||||
boost::thread mThread;
|
||||
|
||||
StreamThread()
|
||||
: mThread(boost::ref(*this))
|
||||
{
|
||||
}
|
||||
~StreamThread()
|
||||
{
|
||||
mThread.interrupt();
|
||||
}
|
||||
|
||||
// boost::thread entry point
|
||||
void operator()()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
mMutex.lock();
|
||||
StreamVec::iterator iter = mStreams.begin();
|
||||
while(iter != mStreams.end())
|
||||
{
|
||||
if((*iter)->process() == false)
|
||||
iter = mStreams.erase(iter);
|
||||
else
|
||||
iter++;
|
||||
}
|
||||
mMutex.unlock();
|
||||
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
|
||||
}
|
||||
}
|
||||
|
||||
void add(OpenAL_SoundStream *stream)
|
||||
{
|
||||
mMutex.lock();
|
||||
if(std::find(mStreams.begin(), mStreams.end(), stream) == mStreams.end())
|
||||
mStreams.push_back(stream);
|
||||
mMutex.unlock();
|
||||
}
|
||||
|
||||
void remove(OpenAL_SoundStream *stream)
|
||||
{
|
||||
mMutex.lock();
|
||||
StreamVec::iterator iter = std::find(mStreams.begin(), mStreams.end(), stream);
|
||||
if(iter != mStreams.end())
|
||||
mStreams.erase(iter);
|
||||
mMutex.unlock();
|
||||
}
|
||||
|
||||
void removeAll()
|
||||
{
|
||||
mMutex.lock();
|
||||
mStreams.clear();
|
||||
mMutex.unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
StreamThread(const StreamThread &rhs);
|
||||
StreamThread& operator=(const StreamThread &rhs);
|
||||
};
|
||||
|
||||
|
||||
OpenAL_SoundStream::OpenAL_SoundStream(OpenAL_Output &output, ALuint src, DecoderPtr decoder)
|
||||
: mOutput(output), mSource(src), mDecoder(decoder), mIsFinished(true)
|
||||
{
|
||||
throwALerror();
|
||||
|
||||
alGenBuffers(sNumBuffers, mBuffers);
|
||||
throwALerror();
|
||||
try
|
||||
{
|
||||
int srate;
|
||||
ChannelConfig chans;
|
||||
SampleType type;
|
||||
|
||||
mDecoder->getInfo(&srate, &chans, &type);
|
||||
mFormat = getALFormat(chans, type);
|
||||
mSampleRate = srate;
|
||||
|
||||
mBufferSize = static_cast<ALuint>(sBufferLength*srate);
|
||||
mBufferSize = framesToBytes(mBufferSize, chans, type);
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
alDeleteBuffers(sNumBuffers, mBuffers);
|
||||
alGetError();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
OpenAL_SoundStream::~OpenAL_SoundStream()
|
||||
{
|
||||
mOutput.mStreamThread->remove(this);
|
||||
|
||||
alSourceStop(mSource);
|
||||
alSourcei(mSource, AL_BUFFER, 0);
|
||||
|
||||
mOutput.mFreeSources.push_back(mSource);
|
||||
alDeleteBuffers(sNumBuffers, mBuffers);
|
||||
alGetError();
|
||||
|
||||
mDecoder->close();
|
||||
}
|
||||
|
||||
void OpenAL_SoundStream::play()
|
||||
{
|
||||
std::vector<char> data(mBufferSize);
|
||||
|
||||
alSourceStop(mSource);
|
||||
alSourcei(mSource, AL_BUFFER, 0);
|
||||
throwALerror();
|
||||
|
||||
for(ALuint i = 0;i < sNumBuffers;i++)
|
||||
{
|
||||
size_t got;
|
||||
got = mDecoder->read(data.data(), data.size());
|
||||
alBufferData(mBuffers[i], mFormat, data.data(), got, mSampleRate);
|
||||
}
|
||||
throwALerror();
|
||||
|
||||
alSourceQueueBuffers(mSource, sNumBuffers, mBuffers);
|
||||
alSourcePlay(mSource);
|
||||
throwALerror();
|
||||
|
||||
mIsFinished = false;
|
||||
mOutput.mStreamThread->add(this);
|
||||
}
|
||||
|
||||
void OpenAL_SoundStream::stop()
|
||||
{
|
||||
mOutput.mStreamThread->remove(this);
|
||||
mIsFinished = true;
|
||||
|
||||
alSourceStop(mSource);
|
||||
alSourcei(mSource, AL_BUFFER, 0);
|
||||
throwALerror();
|
||||
|
||||
mDecoder->rewind();
|
||||
}
|
||||
|
||||
bool OpenAL_SoundStream::isPlaying()
|
||||
{
|
||||
ALint state;
|
||||
|
||||
alGetSourcei(mSource, AL_SOURCE_STATE, &state);
|
||||
throwALerror();
|
||||
|
||||
if(state == AL_PLAYING)
|
||||
return true;
|
||||
return !mIsFinished;
|
||||
}
|
||||
|
||||
void OpenAL_SoundStream::update()
|
||||
{
|
||||
alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume);
|
||||
alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]);
|
||||
alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||
throwALerror();
|
||||
}
|
||||
|
||||
bool OpenAL_SoundStream::process()
|
||||
{
|
||||
bool finished = mIsFinished;
|
||||
ALint processed, state;
|
||||
|
||||
alGetSourcei(mSource, AL_SOURCE_STATE, &state);
|
||||
alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);
|
||||
throwALerror();
|
||||
|
||||
if(processed > 0)
|
||||
{
|
||||
std::vector<char> data(mBufferSize);
|
||||
do {
|
||||
ALuint bufid;
|
||||
size_t got;
|
||||
|
||||
alSourceUnqueueBuffers(mSource, 1, &bufid);
|
||||
processed--;
|
||||
|
||||
if(finished)
|
||||
continue;
|
||||
|
||||
got = mDecoder->read(data.data(), data.size());
|
||||
finished = (got < data.size());
|
||||
if(got > 0)
|
||||
{
|
||||
alBufferData(bufid, mFormat, data.data(), got, mSampleRate);
|
||||
alSourceQueueBuffers(mSource, 1, &bufid);
|
||||
}
|
||||
} while(processed > 0);
|
||||
throwALerror();
|
||||
}
|
||||
|
||||
if(state != AL_PLAYING && state != AL_PAUSED)
|
||||
{
|
||||
ALint queued;
|
||||
|
||||
alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued);
|
||||
throwALerror();
|
||||
if(queued > 0)
|
||||
{
|
||||
alSourcePlay(mSource);
|
||||
throwALerror();
|
||||
}
|
||||
}
|
||||
|
||||
mIsFinished = finished;
|
||||
return !finished;
|
||||
}
|
||||
|
||||
//
|
||||
// A regular 2D OpenAL sound
|
||||
//
|
||||
class OpenAL_Sound : public Sound
|
||||
{
|
||||
protected:
|
||||
OpenAL_Output &mOutput;
|
||||
|
||||
ALuint mSource;
|
||||
ALuint mBuffer;
|
||||
|
||||
private:
|
||||
OpenAL_Sound(const OpenAL_Sound &rhs);
|
||||
OpenAL_Sound& operator=(const OpenAL_Sound &rhs);
|
||||
|
||||
public:
|
||||
OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf);
|
||||
virtual ~OpenAL_Sound();
|
||||
|
||||
virtual void stop();
|
||||
virtual bool isPlaying();
|
||||
virtual void update();
|
||||
};
|
||||
|
||||
//
|
||||
// A regular 3D OpenAL sound
|
||||
//
|
||||
class OpenAL_Sound3D : public OpenAL_Sound
|
||||
{
|
||||
OpenAL_Sound3D(const OpenAL_Sound &rhs);
|
||||
OpenAL_Sound3D& operator=(const OpenAL_Sound &rhs);
|
||||
|
||||
public:
|
||||
OpenAL_Sound3D(OpenAL_Output &output, ALuint src, ALuint buf)
|
||||
: OpenAL_Sound(output, src, buf)
|
||||
{ }
|
||||
|
||||
virtual void update();
|
||||
};
|
||||
|
||||
OpenAL_Sound::OpenAL_Sound(OpenAL_Output &output, ALuint src, ALuint buf)
|
||||
: mOutput(output), mSource(src), mBuffer(buf)
|
||||
{
|
||||
}
|
||||
OpenAL_Sound::~OpenAL_Sound()
|
||||
{
|
||||
alSourceStop(mSource);
|
||||
alSourcei(mSource, AL_BUFFER, 0);
|
||||
|
||||
mOutput.mFreeSources.push_back(mSource);
|
||||
mOutput.bufferFinished(mBuffer);
|
||||
}
|
||||
|
||||
void OpenAL_Sound::stop()
|
||||
{
|
||||
alSourceStop(mSource);
|
||||
throwALerror();
|
||||
}
|
||||
|
||||
bool OpenAL_Sound::isPlaying()
|
||||
{
|
||||
ALint state;
|
||||
|
||||
alGetSourcei(mSource, AL_SOURCE_STATE, &state);
|
||||
throwALerror();
|
||||
|
||||
return state==AL_PLAYING;
|
||||
}
|
||||
|
||||
void OpenAL_Sound::update()
|
||||
{
|
||||
alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume);
|
||||
alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]);
|
||||
alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||
throwALerror();
|
||||
}
|
||||
|
||||
void OpenAL_Sound3D::update()
|
||||
{
|
||||
if(mPos.squaredDistance(mOutput.mPos) > mMaxDistance*mMaxDistance)
|
||||
alSourcef(mSource, AL_GAIN, 0.0f);
|
||||
else
|
||||
alSourcef(mSource, AL_GAIN, mVolume*mBaseVolume);
|
||||
alSource3f(mSource, AL_POSITION, mPos[0], mPos[2], -mPos[1]);
|
||||
alSource3f(mSource, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(mSource, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||
throwALerror();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// An OpenAL output device
|
||||
//
|
||||
std::vector<std::string> OpenAL_Output::enumerate()
|
||||
{
|
||||
std::vector<std::string> devlist;
|
||||
const ALCchar *devnames;
|
||||
|
||||
if(alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"))
|
||||
devnames = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
|
||||
else
|
||||
devnames = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
|
||||
while(devnames && *devnames)
|
||||
{
|
||||
devlist.push_back(devnames);
|
||||
devnames += strlen(devnames)+1;
|
||||
}
|
||||
return devlist;
|
||||
}
|
||||
|
||||
void OpenAL_Output::init(const std::string &devname)
|
||||
{
|
||||
deinit();
|
||||
|
||||
mDevice = alcOpenDevice(devname.c_str());
|
||||
if(!mDevice)
|
||||
{
|
||||
if(devname.empty())
|
||||
fail("Failed to open default device");
|
||||
else
|
||||
fail("Failed to open \""+devname+"\"");
|
||||
}
|
||||
if(alcIsExtensionPresent(mDevice, "ALC_ENUMERATE_ALL_EXT"))
|
||||
std::cout << "Opened \""<<alcGetString(mDevice, ALC_ALL_DEVICES_SPECIFIER)<<"\"" << std::endl;
|
||||
else
|
||||
std::cout << "Opened \""<<alcGetString(mDevice, ALC_DEVICE_SPECIFIER)<<"\"" << std::endl;
|
||||
|
||||
mContext = alcCreateContext(mDevice, NULL);
|
||||
if(!mContext || alcMakeContextCurrent(mContext) == ALC_FALSE)
|
||||
{
|
||||
if(mContext)
|
||||
alcDestroyContext(mContext);
|
||||
mContext = 0;
|
||||
fail(std::string("Failed to setup context: ")+alcGetString(mDevice, alcGetError(mDevice)));
|
||||
}
|
||||
|
||||
alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
|
||||
throwALerror();
|
||||
|
||||
ALCint maxmono=0, maxstereo=0;
|
||||
alcGetIntegerv(mDevice, ALC_MONO_SOURCES, 1, &maxmono);
|
||||
alcGetIntegerv(mDevice, ALC_STEREO_SOURCES, 1, &maxstereo);
|
||||
throwALCerror(mDevice);
|
||||
|
||||
try
|
||||
{
|
||||
ALCuint maxtotal = std::min<ALCuint>(maxmono+maxstereo, 256);
|
||||
if (maxtotal == 0) // workaround for broken implementations
|
||||
maxtotal = 256;
|
||||
for(size_t i = 0;i < maxtotal;i++)
|
||||
{
|
||||
ALuint src = 0;
|
||||
alGenSources(1, &src);
|
||||
throwALerror();
|
||||
mFreeSources.push_back(src);
|
||||
}
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
std::cout <<"Error: "<<e.what()<<", trying to continue"<< std::endl;
|
||||
}
|
||||
if(mFreeSources.empty())
|
||||
fail("Could not allocate any sources");
|
||||
}
|
||||
|
||||
void OpenAL_Output::deinit()
|
||||
{
|
||||
mStreamThread->removeAll();
|
||||
|
||||
while(!mFreeSources.empty())
|
||||
{
|
||||
alDeleteSources(1, &mFreeSources.front());
|
||||
mFreeSources.pop_front();
|
||||
}
|
||||
|
||||
mBufferRefs.clear();
|
||||
mUnusedBuffers.clear();
|
||||
while(!mBufferCache.empty())
|
||||
{
|
||||
alDeleteBuffers(1, &mBufferCache.begin()->second);
|
||||
mBufferCache.erase(mBufferCache.begin());
|
||||
}
|
||||
|
||||
alcMakeContextCurrent(0);
|
||||
if(mContext)
|
||||
alcDestroyContext(mContext);
|
||||
mContext = 0;
|
||||
if(mDevice)
|
||||
alcCloseDevice(mDevice);
|
||||
mDevice = 0;
|
||||
}
|
||||
|
||||
|
||||
ALuint OpenAL_Output::getBuffer(const std::string &fname)
|
||||
{
|
||||
ALuint buf = 0;
|
||||
|
||||
NameMap::iterator iditer = mBufferCache.find(fname);
|
||||
if(iditer != mBufferCache.end())
|
||||
{
|
||||
buf = iditer->second;
|
||||
if(mBufferRefs[buf]++ == 0)
|
||||
{
|
||||
IDDq::iterator iter = std::find(mUnusedBuffers.begin(),
|
||||
mUnusedBuffers.end(), buf);
|
||||
if(iter != mUnusedBuffers.end())
|
||||
mUnusedBuffers.erase(iter);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
throwALerror();
|
||||
|
||||
std::vector<char> data;
|
||||
ChannelConfig chans;
|
||||
SampleType type;
|
||||
ALenum format;
|
||||
int srate;
|
||||
|
||||
DecoderPtr decoder = mManager.getDecoder();
|
||||
try
|
||||
{
|
||||
decoder->open(fname);
|
||||
}
|
||||
catch(Ogre::FileNotFoundException &e)
|
||||
{
|
||||
std::string::size_type pos = fname.rfind('.');
|
||||
if(pos == std::string::npos)
|
||||
throw;
|
||||
decoder->open(fname.substr(0, pos)+".mp3");
|
||||
}
|
||||
|
||||
decoder->getInfo(&srate, &chans, &type);
|
||||
format = getALFormat(chans, type);
|
||||
|
||||
decoder->readAll(data);
|
||||
decoder->close();
|
||||
|
||||
alGenBuffers(1, &buf);
|
||||
throwALerror();
|
||||
|
||||
alBufferData(buf, format, data.data(), data.size(), srate);
|
||||
mBufferCache[fname] = buf;
|
||||
mBufferRefs[buf] = 1;
|
||||
|
||||
ALint bufsize = 0;
|
||||
alGetBufferi(buf, AL_SIZE, &bufsize);
|
||||
mBufferCacheMemSize += bufsize;
|
||||
|
||||
// NOTE: Max buffer cache: 15MB
|
||||
while(mBufferCacheMemSize > 15*1024*1024)
|
||||
{
|
||||
if(mUnusedBuffers.empty())
|
||||
{
|
||||
std::cout <<"No more unused buffers to clear!"<< std::endl;
|
||||
break;
|
||||
}
|
||||
|
||||
ALuint oldbuf = mUnusedBuffers.front();
|
||||
mUnusedBuffers.pop_front();
|
||||
|
||||
NameMap::iterator nameiter = mBufferCache.begin();
|
||||
while(nameiter != mBufferCache.end())
|
||||
{
|
||||
if(nameiter->second == oldbuf)
|
||||
mBufferCache.erase(nameiter++);
|
||||
else
|
||||
nameiter++;
|
||||
}
|
||||
|
||||
bufsize = 0;
|
||||
alGetBufferi(oldbuf, AL_SIZE, &bufsize);
|
||||
alDeleteBuffers(1, &oldbuf);
|
||||
mBufferCacheMemSize -= bufsize;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
void OpenAL_Output::bufferFinished(ALuint buf)
|
||||
{
|
||||
if(mBufferRefs.at(buf)-- == 1)
|
||||
{
|
||||
mBufferRefs.erase(mBufferRefs.find(buf));
|
||||
mUnusedBuffers.push_back(buf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SoundPtr OpenAL_Output::playSound(const std::string &fname, float volume, float pitch, int flags)
|
||||
{
|
||||
boost::shared_ptr<OpenAL_Sound> sound;
|
||||
ALuint src=0, buf=0;
|
||||
|
||||
if(mFreeSources.empty())
|
||||
fail("No free sources");
|
||||
src = mFreeSources.front();
|
||||
mFreeSources.pop_front();
|
||||
|
||||
try
|
||||
{
|
||||
buf = getBuffer(fname);
|
||||
sound.reset(new OpenAL_Sound(*this, src, buf));
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
mFreeSources.push_back(src);
|
||||
if(buf && alIsBuffer(buf))
|
||||
bufferFinished(buf);
|
||||
alGetError();
|
||||
throw;
|
||||
}
|
||||
|
||||
alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f);
|
||||
alSourcef(src, AL_MAX_DISTANCE, 1000.0f);
|
||||
alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f);
|
||||
|
||||
alSourcef(src, AL_GAIN, volume);
|
||||
alSourcef(src, AL_PITCH, pitch);
|
||||
|
||||
alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||
alSourcei(src, AL_LOOPING, (flags&Play_Loop) ? AL_TRUE : AL_FALSE);
|
||||
throwALerror();
|
||||
|
||||
alSourcei(src, AL_BUFFER, buf);
|
||||
alSourcePlay(src);
|
||||
throwALerror();
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
SoundPtr OpenAL_Output::playSound3D(const std::string &fname, const Ogre::Vector3 &pos, float volume, float pitch,
|
||||
float min, float max, int flags)
|
||||
{
|
||||
boost::shared_ptr<OpenAL_Sound> sound;
|
||||
ALuint src=0, buf=0;
|
||||
|
||||
if(mFreeSources.empty())
|
||||
fail("No free sources");
|
||||
src = mFreeSources.front();
|
||||
mFreeSources.pop_front();
|
||||
|
||||
try
|
||||
{
|
||||
buf = getBuffer(fname);
|
||||
sound.reset(new OpenAL_Sound3D(*this, src, buf));
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
mFreeSources.push_back(src);
|
||||
if(buf && alIsBuffer(buf))
|
||||
bufferFinished(buf);
|
||||
alGetError();
|
||||
throw;
|
||||
}
|
||||
|
||||
alSource3f(src, AL_POSITION, pos.x, pos.z, -pos.y);
|
||||
alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
alSourcef(src, AL_REFERENCE_DISTANCE, min);
|
||||
alSourcef(src, AL_MAX_DISTANCE, max);
|
||||
alSourcef(src, AL_ROLLOFF_FACTOR, 1.0f);
|
||||
|
||||
alSourcef(src, AL_GAIN, (pos.squaredDistance(mPos) > max*max) ?
|
||||
0.0f : volume);
|
||||
alSourcef(src, AL_PITCH, pitch);
|
||||
|
||||
alSourcei(src, AL_SOURCE_RELATIVE, AL_FALSE);
|
||||
alSourcei(src, AL_LOOPING, (flags&Play_Loop) ? AL_TRUE : AL_FALSE);
|
||||
throwALerror();
|
||||
|
||||
alSourcei(src, AL_BUFFER, buf);
|
||||
alSourcePlay(src);
|
||||
throwALerror();
|
||||
|
||||
return sound;
|
||||
}
|
||||
|
||||
|
||||
SoundPtr OpenAL_Output::streamSound(const std::string &fname, float volume, float pitch)
|
||||
{
|
||||
boost::shared_ptr<OpenAL_SoundStream> sound;
|
||||
ALuint src;
|
||||
|
||||
if(mFreeSources.empty())
|
||||
fail("No free sources");
|
||||
src = mFreeSources.front();
|
||||
mFreeSources.pop_front();
|
||||
|
||||
try
|
||||
{
|
||||
DecoderPtr decoder = mManager.getDecoder();
|
||||
decoder->open(fname);
|
||||
sound.reset(new OpenAL_SoundStream(*this, src, decoder));
|
||||
}
|
||||
catch(std::exception &e)
|
||||
{
|
||||
mFreeSources.push_back(src);
|
||||
throw;
|
||||
}
|
||||
|
||||
alSource3f(src, AL_POSITION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(src, AL_DIRECTION, 0.0f, 0.0f, 0.0f);
|
||||
alSource3f(src, AL_VELOCITY, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
alSourcef(src, AL_REFERENCE_DISTANCE, 1.0f);
|
||||
alSourcef(src, AL_MAX_DISTANCE, 1000.0f);
|
||||
alSourcef(src, AL_ROLLOFF_FACTOR, 0.0f);
|
||||
|
||||
alSourcef(src, AL_GAIN, volume);
|
||||
alSourcef(src, AL_PITCH, pitch);
|
||||
|
||||
alSourcei(src, AL_SOURCE_RELATIVE, AL_TRUE);
|
||||
alSourcei(src, AL_LOOPING, AL_FALSE);
|
||||
throwALerror();
|
||||
|
||||
sound->play();
|
||||
return sound;
|
||||
}
|
||||
|
||||
|
||||
void OpenAL_Output::updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir)
|
||||
{
|
||||
mPos = pos;
|
||||
|
||||
if(mContext)
|
||||
{
|
||||
ALfloat orient[6] = {
|
||||
atdir.x, atdir.z, -atdir.y,
|
||||
updir.x, updir.z, -updir.y
|
||||
};
|
||||
alListener3f(AL_POSITION, mPos.x, mPos.z, -mPos.y);
|
||||
alListenerfv(AL_ORIENTATION, orient);
|
||||
throwALerror();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OpenAL_Output::OpenAL_Output(SoundManager &mgr)
|
||||
: Sound_Output(mgr), mDevice(0), mContext(0), mBufferCacheMemSize(0),
|
||||
mStreamThread(new StreamThread)
|
||||
{
|
||||
}
|
||||
|
||||
OpenAL_Output::~OpenAL_Output()
|
||||
{
|
||||
deinit();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
#ifndef GAME_SOUND_OPENAL_OUTPUT_H
|
||||
#define GAME_SOUND_OPENAL_OUTPUT_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <deque>
|
||||
|
||||
#include "alc.h"
|
||||
#include "al.h"
|
||||
|
||||
#include "sound_output.hpp"
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
class SoundManager;
|
||||
class Sound;
|
||||
|
||||
class OpenAL_Output : public Sound_Output
|
||||
{
|
||||
ALCdevice *mDevice;
|
||||
ALCcontext *mContext;
|
||||
|
||||
typedef std::deque<ALuint> IDDq;
|
||||
IDDq mFreeSources;
|
||||
IDDq mUnusedBuffers;
|
||||
|
||||
typedef std::map<std::string,ALuint> NameMap;
|
||||
NameMap mBufferCache;
|
||||
|
||||
typedef std::map<ALuint,ALuint> IDRefMap;
|
||||
IDRefMap mBufferRefs;
|
||||
|
||||
uint64_t mBufferCacheMemSize;
|
||||
|
||||
ALuint getBuffer(const std::string &fname);
|
||||
void bufferFinished(ALuint buffer);
|
||||
|
||||
virtual std::vector<std::string> enumerate();
|
||||
virtual void init(const std::string &devname="");
|
||||
virtual void deinit();
|
||||
|
||||
virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags);
|
||||
virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos,
|
||||
float volume, float pitch, float min, float max, int flags);
|
||||
virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch);
|
||||
|
||||
virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir);
|
||||
|
||||
OpenAL_Output& operator=(const OpenAL_Output &rhs);
|
||||
OpenAL_Output(const OpenAL_Output &rhs);
|
||||
|
||||
OpenAL_Output(SoundManager &mgr);
|
||||
virtual ~OpenAL_Output();
|
||||
|
||||
class StreamThread;
|
||||
std::auto_ptr<StreamThread> mStreamThread;
|
||||
|
||||
friend class OpenAL_Sound;
|
||||
friend class OpenAL_Sound3D;
|
||||
friend class OpenAL_SoundStream;
|
||||
friend class SoundManager;
|
||||
};
|
||||
#ifndef DEFAULT_OUTPUT
|
||||
#define DEFAULT_OUTPUT (::MWSound::OpenAL_Output)
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
@ -0,0 +1,43 @@
|
||||
#ifndef GAME_SOUND_SOUND_H
|
||||
#define GAME_SOUND_SOUND_H
|
||||
|
||||
#include <OgreVector3.h>
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
class Sound
|
||||
{
|
||||
virtual void update() = 0;
|
||||
|
||||
Sound& operator=(const Sound &rhs);
|
||||
Sound(const Sound &rhs);
|
||||
|
||||
protected:
|
||||
Ogre::Vector3 mPos;
|
||||
float mVolume; /* NOTE: Real volume = mVolume*mBaseVolume */
|
||||
float mBaseVolume;
|
||||
float mMinDistance;
|
||||
float mMaxDistance;
|
||||
int mFlags;
|
||||
|
||||
public:
|
||||
virtual void stop() = 0;
|
||||
virtual bool isPlaying() = 0;
|
||||
void setPosition(const Ogre::Vector3 &pos) { mPos = pos; }
|
||||
void setVolume(float volume) { mVolume = volume; }
|
||||
|
||||
Sound() : mPos(0.0f, 0.0f, 0.0f)
|
||||
, mVolume(1.0f)
|
||||
, mBaseVolume(1.0f)
|
||||
, mMinDistance(20.0f) /* 1 * min_range_scale */
|
||||
, mMaxDistance(12750.0f) /* 255 * max_range_scale */
|
||||
, mFlags(Play_Normal)
|
||||
{ }
|
||||
virtual ~Sound() { }
|
||||
|
||||
friend class OpenAL_Output;
|
||||
friend class SoundManager;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,48 @@
|
||||
#ifndef GAME_SOUND_SOUND_DECODER_H
|
||||
#define GAME_SOUND_SOUND_DECODER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <OgreResourceGroupManager.h>
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
enum SampleType {
|
||||
SampleType_UInt8,
|
||||
SampleType_Int16
|
||||
};
|
||||
const char *getSampleTypeName(SampleType type);
|
||||
|
||||
enum ChannelConfig {
|
||||
ChannelConfig_Mono,
|
||||
ChannelConfig_Stereo
|
||||
};
|
||||
const char *getChannelConfigName(ChannelConfig config);
|
||||
|
||||
size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type);
|
||||
size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type);
|
||||
|
||||
struct Sound_Decoder
|
||||
{
|
||||
Ogre::ResourceGroupManager &mResourceMgr;
|
||||
|
||||
virtual void open(const std::string &fname) = 0;
|
||||
virtual void close() = 0;
|
||||
|
||||
virtual void getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) = 0;
|
||||
|
||||
virtual size_t read(char *buffer, size_t bytes) = 0;
|
||||
virtual void readAll(std::vector<char> &output);
|
||||
virtual void rewind() = 0;
|
||||
|
||||
Sound_Decoder() : mResourceMgr(Ogre::ResourceGroupManager::getSingleton())
|
||||
{ }
|
||||
virtual ~Sound_Decoder() { }
|
||||
|
||||
private:
|
||||
Sound_Decoder(const Sound_Decoder &rhs);
|
||||
Sound_Decoder& operator=(const Sound_Decoder &rhs);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,52 @@
|
||||
#ifndef GAME_SOUND_SOUND_OUTPUT_H
|
||||
#define GAME_SOUND_SOUND_OUTPUT_H
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <OgreVector3.h>
|
||||
|
||||
#include "soundmanager.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
namespace MWSound
|
||||
{
|
||||
class SoundManager;
|
||||
class Sound_Decoder;
|
||||
class Sound;
|
||||
|
||||
class Sound_Output
|
||||
{
|
||||
SoundManager &mManager;
|
||||
|
||||
virtual std::vector<std::string> enumerate() = 0;
|
||||
virtual void init(const std::string &devname="") = 0;
|
||||
virtual void deinit() = 0;
|
||||
|
||||
virtual SoundPtr playSound(const std::string &fname, float volume, float pitch, int flags) = 0;
|
||||
virtual SoundPtr playSound3D(const std::string &fname, const Ogre::Vector3 &pos,
|
||||
float volume, float pitch, float min, float max, int flags) = 0;
|
||||
virtual SoundPtr streamSound(const std::string &fname, float volume, float pitch) = 0;
|
||||
|
||||
virtual void updateListener(const Ogre::Vector3 &pos, const Ogre::Vector3 &atdir, const Ogre::Vector3 &updir) = 0;
|
||||
|
||||
Sound_Output& operator=(const Sound_Output &rhs);
|
||||
Sound_Output(const Sound_Output &rhs);
|
||||
|
||||
protected:
|
||||
Ogre::Vector3 mPos;
|
||||
|
||||
Sound_Output(SoundManager &mgr)
|
||||
: mManager(mgr)
|
||||
, mPos(0.0f, 0.0f, 0.0f)
|
||||
{ }
|
||||
public:
|
||||
virtual ~Sound_Output() { }
|
||||
|
||||
friend class OpenAL_Output;
|
||||
friend class SoundManager;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,39 @@
|
||||
|
||||
#include "ptr.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "containerstore.hpp"
|
||||
|
||||
ESM::CellRef& MWWorld::Ptr::getCellRef() const
|
||||
{
|
||||
assert (mCellRef);
|
||||
|
||||
if (mContainerStore)
|
||||
mContainerStore->flagAsModified();
|
||||
|
||||
return *mCellRef;
|
||||
}
|
||||
|
||||
MWWorld::RefData& MWWorld::Ptr::getRefData() const
|
||||
{
|
||||
assert (mRefData);
|
||||
|
||||
if (mContainerStore)
|
||||
mContainerStore->flagAsModified();
|
||||
|
||||
return *mRefData;
|
||||
}
|
||||
|
||||
void MWWorld::Ptr::setContainerStore (ContainerStore *store)
|
||||
{
|
||||
assert (store);
|
||||
assert (!mCell);
|
||||
|
||||
mContainerStore = store;
|
||||
}
|
||||
|
||||
MWWorld::ContainerStore *MWWorld::Ptr::getContainerStore() const
|
||||
{
|
||||
return mContainerStore;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue