mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
Merge pull request #324 from DavidWyand-GG/SideBySideRendering
Side by side rendering
This commit is contained in:
commit
2805ec81c8
|
|
@ -219,6 +219,7 @@ GameConnection::GameConnection()
|
|||
// first person
|
||||
mFirstPerson = true;
|
||||
mUpdateFirstPerson = false;
|
||||
clearDisplayDevice();
|
||||
}
|
||||
|
||||
GameConnection::~GameConnection()
|
||||
|
|
@ -1729,6 +1730,13 @@ DefineEngineMethod( GameConnection, setControlObject, bool, (GameBase* ctrlObj),
|
|||
return true;
|
||||
}
|
||||
|
||||
DefineEngineMethod( GameConnection, clearDisplayDevice, void, (),,
|
||||
"@brief Clear any display device.\n\n"
|
||||
"A display device may define a number of properties that are used during rendering.\n\n")
|
||||
{
|
||||
object->clearDisplayDevice();
|
||||
}
|
||||
|
||||
DefineEngineMethod( GameConnection, getControlObject, GameBase*, (),,
|
||||
"@brief On the server, returns the object that the client is controlling."
|
||||
"By default the control object is an instance of the Player class, but can also be an instance "
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ enum GameConnectionConstants
|
|||
DataBlockQueueCount = 16
|
||||
};
|
||||
|
||||
class IDisplayDevice;
|
||||
class SFXProfile;
|
||||
class MatrixF;
|
||||
class MatrixF;
|
||||
|
|
@ -86,6 +87,8 @@ private:
|
|||
F32 mCameraFov; ///< Current camera fov (in degrees).
|
||||
F32 mCameraPos; ///< Current camera pos (0-1).
|
||||
F32 mCameraSpeed; ///< Camera in/out speed.
|
||||
|
||||
IDisplayDevice* mDisplayDevice; ///< Optional client display device that imposes rendering properties.
|
||||
/// @}
|
||||
|
||||
public:
|
||||
|
|
@ -263,6 +266,10 @@ public:
|
|||
|
||||
void setFirstPerson(bool firstPerson);
|
||||
|
||||
bool hasDisplayDevice() const { return mDisplayDevice != NULL; }
|
||||
const IDisplayDevice* getDisplayDevice() const { return mDisplayDevice; }
|
||||
void setDisplayDevice(IDisplayDevice* display) { mDisplayDevice = display; }
|
||||
void clearDisplayDevice() { mDisplayDevice = NULL; }
|
||||
/// @}
|
||||
|
||||
void detectLag();
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
#include "math/mEase.h"
|
||||
#include "core/module.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
#include "platform/output/IDisplayDevice.h"
|
||||
|
||||
static void RegisterGameFunctions();
|
||||
static void Process3D();
|
||||
|
|
@ -82,6 +82,8 @@ static S32 gEaseBack = Ease::Back;
|
|||
static S32 gEaseBounce = Ease::Bounce;
|
||||
|
||||
|
||||
extern bool gEditingMission;
|
||||
|
||||
extern void ShowInit();
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
|
@ -354,9 +356,44 @@ bool GameProcessCameraQuery(CameraQuery *query)
|
|||
sVisDistanceScale = mClampF( sVisDistanceScale, 0.01f, 1.0f );
|
||||
query->farPlane = gClientSceneGraph->getVisibleDistance() * sVisDistanceScale;
|
||||
|
||||
F32 cameraFov;
|
||||
if(!connection->getControlCameraFov(&cameraFov))
|
||||
// Provide some default values
|
||||
query->projectionOffset = Point2F::Zero;
|
||||
query->eyeOffset = Point3F::Zero;
|
||||
|
||||
F32 cameraFov = 0.0f;
|
||||
bool fovSet = false;
|
||||
|
||||
// Try to use the connection's display deivce, if any, but only if the editor
|
||||
// is not open
|
||||
if(!gEditingMission && connection->hasDisplayDevice())
|
||||
{
|
||||
const IDisplayDevice* display = connection->getDisplayDevice();
|
||||
|
||||
// The connection's display device may want to set the FOV
|
||||
if(display->providesYFOV())
|
||||
{
|
||||
cameraFov = mRadToDeg(display->getYFOV());
|
||||
fovSet = true;
|
||||
}
|
||||
|
||||
// The connection's display device may want to set the projection offset
|
||||
if(display->providesProjectionOffset())
|
||||
{
|
||||
query->projectionOffset = display->getProjectionOffset();
|
||||
}
|
||||
|
||||
// The connection's display device may want to set the eye offset
|
||||
if(display->providesEyeOffset())
|
||||
{
|
||||
query->eyeOffset = display->getEyeOffset();
|
||||
}
|
||||
}
|
||||
|
||||
// Use the connection's FOV settings if requried
|
||||
if(!fovSet && !connection->getControlCameraFov(&cameraFov))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
query->fov = mDegToRad(cameraFov);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -278,7 +278,11 @@ bool LightFlareData::_testVisibility(const SceneRenderState *state, LightFlareSt
|
|||
// the last result.
|
||||
const Point3F &lightPos = flareState->lightMat.getPosition();
|
||||
const RectI &viewport = GFX->getViewport();
|
||||
bool onScreen = MathUtils::mProjectWorldToScreen( lightPos, outLightPosSS, viewport, GFX->getWorldMatrix(), state->getSceneManager()->getNonClipProjection() );
|
||||
MatrixF projMatrix;
|
||||
state->getFrustum().getProjectionMatrix(&projMatrix);
|
||||
if( state->isReflectPass() )
|
||||
projMatrix = state->getSceneManager()->getNonClipProjection();
|
||||
bool onScreen = MathUtils::mProjectWorldToScreen( lightPos, outLightPosSS, viewport, GFX->getWorldMatrix(), projMatrix );
|
||||
|
||||
// It is onscreen, so raycast as a simple occlusion test.
|
||||
const LightInfo *lightInfo = flareState->lightInfo;
|
||||
|
|
@ -452,13 +456,17 @@ void LightFlareData::prepRender( SceneRenderState *state, LightFlareState *flare
|
|||
Point3F oneOverViewportExtent( 1.0f / (F32)viewport.extent.x, 1.0f / (F32)viewport.extent.y, 0.0f );
|
||||
|
||||
// Really convert it to screen space.
|
||||
lightPosSS.x -= viewport.point.x;
|
||||
lightPosSS.y -= viewport.point.y;
|
||||
lightPosSS *= oneOverViewportExtent;
|
||||
lightPosSS = ( lightPosSS * 2.0f ) - Point3F::One;
|
||||
lightPosSS.y = -lightPosSS.y;
|
||||
lightPosSS.z = 0.0f;
|
||||
|
||||
Point3F flareVec( -lightPosSS );
|
||||
// Take any projection offset into account so that the point where the flare's
|
||||
// elements converge is at the 'eye' point rather than the center of the viewport.
|
||||
const Point2F& projOffset = state->getFrustum().getProjectionOffset();
|
||||
Point3F flareVec( -lightPosSS + Point3F(projOffset.x, projOffset.y, 0.0f) );
|
||||
const F32 flareLength = flareVec.len();
|
||||
if ( flareLength > 0.0f )
|
||||
flareVec *= 1.0f / flareLength;
|
||||
|
|
|
|||
|
|
@ -159,6 +159,9 @@ GFXDevice::GFXDevice()
|
|||
|
||||
// misc
|
||||
mAllowRender = true;
|
||||
mCurrentRenderStyle = RS_Standard;
|
||||
mCurrentProjectionOffset = Point2F::Zero;
|
||||
mStereoEyeOffset = Point3F::Zero;
|
||||
mCanCurrentlyRender = false;
|
||||
mInitialized = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -231,6 +231,13 @@ private:
|
|||
//--------------------------------------------------------------------------
|
||||
// Core GFX interface
|
||||
//--------------------------------------------------------------------------
|
||||
public:
|
||||
enum GFXDeviceRenderStyles
|
||||
{
|
||||
RS_Standard = 0,
|
||||
RS_StereoSideBySide = (1<<0),
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
/// Adapter for this device.
|
||||
|
|
@ -254,6 +261,15 @@ protected:
|
|||
/// Set if we're in a mode where we want rendering to occur.
|
||||
bool mAllowRender;
|
||||
|
||||
/// The style of rendering that is to be performed, based on GFXDeviceRenderStyles
|
||||
U32 mCurrentRenderStyle;
|
||||
|
||||
/// The current projection offset. May be used during side-by-side rendering, for example.
|
||||
Point2F mCurrentProjectionOffset;
|
||||
|
||||
/// Eye offset used when using a stereo rendering style
|
||||
Point3F mStereoEyeOffset;
|
||||
|
||||
/// This will allow querying to see if a device is initialized and ready to
|
||||
/// have operations performed on it.
|
||||
bool mInitialized;
|
||||
|
|
@ -285,6 +301,24 @@ public:
|
|||
|
||||
inline bool allowRender() const { return mAllowRender; }
|
||||
|
||||
/// Retrieve the current rendering style based on GFXDeviceRenderStyles
|
||||
U32 getCurrentRenderStyle() const { return mCurrentRenderStyle; }
|
||||
|
||||
/// Set the current rendering style, based on GFXDeviceRenderStyles
|
||||
void setCurrentRenderStyle(U32 style) { mCurrentRenderStyle = style; }
|
||||
|
||||
/// Set the current projection offset used during stereo rendering
|
||||
const Point2F& getCurrentProjectionOffset() { return mCurrentProjectionOffset; }
|
||||
|
||||
/// Get the current projection offset used during stereo rendering
|
||||
void setCurrentProjectionOffset(const Point2F& offset) { mCurrentProjectionOffset = offset; }
|
||||
|
||||
/// Get the current eye offset used during stereo rendering
|
||||
const Point3F& getStereoEyeOffset() { return mStereoEyeOffset; }
|
||||
|
||||
/// Set the current eye offset used during stereo rendering
|
||||
void setStereoEyeOffset(const Point3F& offset) { mStereoEyeOffset = offset; }
|
||||
|
||||
GFXCardProfiler* getCardProfiler() const { return mCardProfiler; }
|
||||
|
||||
/// Returns active graphics adapter type.
|
||||
|
|
|
|||
|
|
@ -53,6 +53,13 @@ ConsoleDocClass( GuiTSCtrl,
|
|||
U32 GuiTSCtrl::smFrameCount = 0;
|
||||
Vector<GuiTSCtrl*> GuiTSCtrl::smAwakeTSCtrls;
|
||||
|
||||
ImplementEnumType( GuiTSRenderStyles,
|
||||
"Style of rendering for a GuiTSCtrl.\n\n"
|
||||
"@ingroup Gui3D" )
|
||||
{ GuiTSCtrl::RenderStyleStandard, "standard" },
|
||||
{ GuiTSCtrl::RenderStyleStereoSideBySide, "stereo side by side" },
|
||||
EndImplementEnumType;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
|
@ -131,6 +138,8 @@ GuiTSCtrl::GuiTSCtrl()
|
|||
mForceFOV = 0;
|
||||
mReflectPriority = 1.0f;
|
||||
|
||||
mRenderStyle = RenderStyleStandard;
|
||||
|
||||
mSaveModelview.identity();
|
||||
mSaveProjection.identity();
|
||||
mSaveViewport.set( 0, 0, 10, 10 );
|
||||
|
|
@ -142,6 +151,9 @@ GuiTSCtrl::GuiTSCtrl()
|
|||
mLastCameraQuery.farPlane = 10.0f;
|
||||
mLastCameraQuery.nearPlane = 0.01f;
|
||||
|
||||
mLastCameraQuery.projectionOffset = Point2F::Zero;
|
||||
mLastCameraQuery.eyeOffset = Point3F::Zero;
|
||||
|
||||
mLastCameraQuery.ortho = false;
|
||||
}
|
||||
|
||||
|
|
@ -165,6 +177,9 @@ void GuiTSCtrl::initPersistFields()
|
|||
"The reflect update priorities of all visible GuiTSCtrls are added together and each control is assigned "
|
||||
"a share of the per-frame reflection update time according to its percentage of the total priority value." );
|
||||
|
||||
addField("renderStyle", TYPEID< RenderStyles >(), Offset(mRenderStyle, GuiTSCtrl),
|
||||
"Indicates how this control should render its contents." );
|
||||
|
||||
endGroup( "Rendering" );
|
||||
|
||||
Parent::initPersistFields();
|
||||
|
|
@ -256,7 +271,9 @@ F32 GuiTSCtrl::calculateViewDistance(F32 radius)
|
|||
F32 fov = mLastCameraQuery.fov;
|
||||
F32 wwidth;
|
||||
F32 wheight;
|
||||
F32 aspectRatio = F32(getWidth()) / F32(getHeight());
|
||||
F32 renderWidth = (mRenderStyle == RenderStyleStereoSideBySide) ? F32(getWidth())*0.5f : F32(getWidth());
|
||||
F32 renderHeight = F32(getHeight());
|
||||
F32 aspectRatio = renderWidth / renderHeight;
|
||||
|
||||
// Use the FOV to calculate the viewport height scale
|
||||
// then generate the width scale from the aspect ratio.
|
||||
|
|
@ -321,10 +338,27 @@ void GuiTSCtrl::onRender(Point2I offset, const RectI &updateRect)
|
|||
mLastCameraQuery.cameraMatrix.mul(rotMat);
|
||||
}
|
||||
|
||||
// Set up the appropriate render style
|
||||
U32 prevRenderStyle = GFX->getCurrentRenderStyle();
|
||||
Point2F prevProjectionOffset = GFX->getCurrentProjectionOffset();
|
||||
Point3F prevEyeOffset = GFX->getStereoEyeOffset();
|
||||
if(mRenderStyle == RenderStyleStereoSideBySide)
|
||||
{
|
||||
GFX->setCurrentRenderStyle(GFXDevice::RS_StereoSideBySide);
|
||||
GFX->setCurrentProjectionOffset(mLastCameraQuery.projectionOffset);
|
||||
GFX->setStereoEyeOffset(mLastCameraQuery.eyeOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
GFX->setCurrentRenderStyle(GFXDevice::RS_Standard);
|
||||
}
|
||||
|
||||
// set up the camera and viewport stuff:
|
||||
F32 wwidth;
|
||||
F32 wheight;
|
||||
F32 aspectRatio = F32(getWidth()) / F32(getHeight());
|
||||
F32 renderWidth = (mRenderStyle == RenderStyleStereoSideBySide) ? F32(getWidth())*0.5f : F32(getWidth());
|
||||
F32 renderHeight = F32(getHeight());
|
||||
F32 aspectRatio = renderWidth / renderHeight;
|
||||
|
||||
// Use the FOV to calculate the viewport height scale
|
||||
// then generate the width scale from the aspect ratio.
|
||||
|
|
@ -339,16 +373,28 @@ void GuiTSCtrl::onRender(Point2I offset, const RectI &updateRect)
|
|||
wwidth = aspectRatio * wheight;
|
||||
}
|
||||
|
||||
F32 hscale = wwidth * 2.0f / F32(getWidth());
|
||||
F32 vscale = wheight * 2.0f / F32(getHeight());
|
||||
|
||||
F32 left = (updateRect.point.x - offset.x) * hscale - wwidth;
|
||||
F32 right = (updateRect.point.x + updateRect.extent.x - offset.x) * hscale - wwidth;
|
||||
F32 top = wheight - vscale * (updateRect.point.y - offset.y);
|
||||
F32 bottom = wheight - vscale * (updateRect.point.y + updateRect.extent.y - offset.y);
|
||||
F32 hscale = wwidth * 2.0f / renderWidth;
|
||||
F32 vscale = wheight * 2.0f / renderHeight;
|
||||
|
||||
Frustum frustum;
|
||||
frustum.set( mLastCameraQuery.ortho, left, right, top, bottom, mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane );
|
||||
if(mRenderStyle == RenderStyleStereoSideBySide)
|
||||
{
|
||||
F32 left = 0.0f * hscale - wwidth;
|
||||
F32 right = renderWidth * hscale - wwidth;
|
||||
F32 top = wheight - vscale * 0.0f;
|
||||
F32 bottom = wheight - vscale * renderHeight;
|
||||
|
||||
frustum.set( mLastCameraQuery.ortho, left, right, top, bottom, mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane );
|
||||
}
|
||||
else
|
||||
{
|
||||
F32 left = (updateRect.point.x - offset.x) * hscale - wwidth;
|
||||
F32 right = (updateRect.point.x + updateRect.extent.x - offset.x) * hscale - wwidth;
|
||||
F32 top = wheight - vscale * (updateRect.point.y - offset.y);
|
||||
F32 bottom = wheight - vscale * (updateRect.point.y + updateRect.extent.y - offset.y);
|
||||
|
||||
frustum.set( mLastCameraQuery.ortho, left, right, top, bottom, mLastCameraQuery.nearPlane, mLastCameraQuery.farPlane );
|
||||
}
|
||||
|
||||
// Manipulate the frustum for tiled screenshots
|
||||
const bool screenShotMode = gScreenShot && gScreenShot->isPending();
|
||||
|
|
@ -412,6 +458,11 @@ void GuiTSCtrl::onRender(Point2I offset, const RectI &updateRect)
|
|||
// we begin rendering the child controls.
|
||||
saver.restore();
|
||||
|
||||
// Restore the render style and any stereo parameters
|
||||
GFX->setCurrentRenderStyle(prevRenderStyle);
|
||||
GFX->setCurrentProjectionOffset(prevProjectionOffset);
|
||||
GFX->setStereoEyeOffset(prevEyeOffset);
|
||||
|
||||
// Allow subclasses to render 2D elements.
|
||||
GFX->setClipRect(updateRect);
|
||||
renderGui( offset, updateRect );
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ struct CameraQuery
|
|||
F32 nearPlane;
|
||||
F32 farPlane;
|
||||
F32 fov;
|
||||
Point2F projectionOffset;
|
||||
Point3F eyeOffset;
|
||||
bool ortho;
|
||||
MatrixF cameraMatrix;
|
||||
};
|
||||
|
|
@ -45,12 +47,17 @@ class GuiTSCtrl : public GuiContainer
|
|||
{
|
||||
typedef GuiContainer Parent;
|
||||
|
||||
public:
|
||||
enum RenderStyles {
|
||||
RenderStyleStandard = 0,
|
||||
RenderStyleStereoSideBySide = (1<<0),
|
||||
};
|
||||
|
||||
protected:
|
||||
static U32 smFrameCount;
|
||||
F32 mCameraZRot;
|
||||
F32 mForceFOV;
|
||||
|
||||
protected:
|
||||
|
||||
/// A list of GuiTSCtrl which are awake and
|
||||
/// most likely rendering.
|
||||
static Vector<GuiTSCtrl*> smAwakeTSCtrls;
|
||||
|
|
@ -59,8 +66,11 @@ protected:
|
|||
/// update timeslice for this viewport to get.
|
||||
F32 mReflectPriority;
|
||||
|
||||
F32 mOrthoWidth;
|
||||
F32 mOrthoHeight;
|
||||
/// The current render type
|
||||
U32 mRenderStyle;
|
||||
|
||||
F32 mOrthoWidth;
|
||||
F32 mOrthoHeight;
|
||||
|
||||
MatrixF mSaveModelview;
|
||||
MatrixF mSaveProjection;
|
||||
|
|
@ -150,4 +160,8 @@ public:
|
|||
DECLARE_DESCRIPTION( "Abstract base class for controls that render a 3D viewport." );
|
||||
};
|
||||
|
||||
typedef GuiTSCtrl::RenderStyles GuiTSRenderStyles;
|
||||
|
||||
DefineEnumType( GuiTSRenderStyles );
|
||||
|
||||
#endif // _GUITSCONTROL_H_
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@
|
|||
#include "math/util/matrixSet.h"
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
|
||||
const RenderInstType AdvancedLightBinManager::RIT_LightInfo( "LightInfo" );
|
||||
const String AdvancedLightBinManager::smBufferName( "lightinfo" );
|
||||
|
||||
|
|
@ -49,7 +48,6 @@ ShadowFilterMode AdvancedLightBinManager::smShadowFilterMode = ShadowFilterMode_
|
|||
bool AdvancedLightBinManager::smPSSMDebugRender = false;
|
||||
bool AdvancedLightBinManager::smUseSSAOMask = false;
|
||||
|
||||
|
||||
ImplementEnumType( ShadowFilterMode,
|
||||
"The shadow filtering modes for Advanced Lighting shadows.\n"
|
||||
"@ingroup AdvancedLighting" )
|
||||
|
|
@ -221,7 +219,7 @@ void AdvancedLightBinManager::addLight( LightInfo *light )
|
|||
curBin.push_back( lEntry );
|
||||
}
|
||||
|
||||
void AdvancedLightBinManager::clear()
|
||||
void AdvancedLightBinManager::clearAllLights()
|
||||
{
|
||||
Con::setIntVariable("lightMetrics::activeLights", mLightBin.size());
|
||||
Con::setIntVariable("lightMetrics::culledLights", mNumLightsCulled);
|
||||
|
|
@ -454,6 +452,33 @@ void AdvancedLightBinManager::_setupPerFrameParameters( const SceneRenderState *
|
|||
const Point3F *wsFrustumPoints = frustum.getPoints();
|
||||
const Point3F& cameraPos = frustum.getPosition();
|
||||
|
||||
// Perform a camera offset. We need to manually perform this offset on the sun (or vector) light's
|
||||
// polygon, which is at the far plane.
|
||||
const Point2F& projOffset = frustum.getProjectionOffset();
|
||||
Point3F cameraOffsetPos = cameraPos;
|
||||
if(!projOffset.isZero())
|
||||
{
|
||||
// First we need to calculate the offset at the near plane. The projOffset
|
||||
// given above can be thought of a percent as it ranges from 0..1 (or 0..-1).
|
||||
F32 nearOffset = frustum.getNearRight() * projOffset.x;
|
||||
|
||||
// Now given the near plane distance from the camera we can solve the right
|
||||
// triangle and calcuate the SIN theta for the offset at the near plane.
|
||||
// SIN theta = x/y
|
||||
F32 sinTheta = nearOffset / frustum.getNearDist();
|
||||
|
||||
// Finally, we can calcuate the offset at the far plane, which is where our sun (or vector)
|
||||
// light's polygon is drawn.
|
||||
F32 farOffset = frustum.getFarDist() * sinTheta;
|
||||
|
||||
// We can now apply this far plane offset to the far plane itself, which then compensates
|
||||
// for the project offset.
|
||||
MatrixF camTrans = frustum.getTransform();
|
||||
VectorF offset = camTrans.getRightVector();
|
||||
offset *= farOffset;
|
||||
cameraOffsetPos += offset;
|
||||
}
|
||||
|
||||
// Now build the quad for drawing full-screen vector light
|
||||
// passes.... this is a volatile VB and updates every frame.
|
||||
FarFrustumQuadVert verts[4];
|
||||
|
|
@ -461,18 +486,22 @@ void AdvancedLightBinManager::_setupPerFrameParameters( const SceneRenderState *
|
|||
verts[0].point.set( wsFrustumPoints[Frustum::FarBottomLeft] - cameraPos );
|
||||
invCam.mulP( wsFrustumPoints[Frustum::FarBottomLeft], &verts[0].normal );
|
||||
verts[0].texCoord.set( -1.0, -1.0 );
|
||||
verts[0].tangent.set(wsFrustumPoints[Frustum::FarBottomLeft] - cameraOffsetPos);
|
||||
|
||||
verts[1].point.set( wsFrustumPoints[Frustum::FarTopLeft] - cameraPos );
|
||||
invCam.mulP( wsFrustumPoints[Frustum::FarTopLeft], &verts[1].normal );
|
||||
verts[1].texCoord.set( -1.0, 1.0 );
|
||||
verts[1].tangent.set(wsFrustumPoints[Frustum::FarTopLeft] - cameraOffsetPos);
|
||||
|
||||
verts[2].point.set( wsFrustumPoints[Frustum::FarTopRight] - cameraPos );
|
||||
invCam.mulP( wsFrustumPoints[Frustum::FarTopRight], &verts[2].normal );
|
||||
verts[2].texCoord.set( 1.0, 1.0 );
|
||||
verts[2].tangent.set(wsFrustumPoints[Frustum::FarTopRight] - cameraOffsetPos);
|
||||
|
||||
verts[3].point.set( wsFrustumPoints[Frustum::FarBottomRight] - cameraPos );
|
||||
invCam.mulP( wsFrustumPoints[Frustum::FarBottomRight], &verts[3].normal );
|
||||
verts[3].texCoord.set( 1.0, -1.0 );
|
||||
verts[3].tangent.set(wsFrustumPoints[Frustum::FarBottomRight] - cameraOffsetPos);
|
||||
}
|
||||
mFarFrustumQuadVerts.set( GFX, 4 );
|
||||
dMemcpy( mFarFrustumQuadVerts.lock(), verts, sizeof( verts ) );
|
||||
|
|
|
|||
|
|
@ -107,12 +107,15 @@ public:
|
|||
|
||||
// RenderBinManager
|
||||
virtual void render(SceneRenderState *);
|
||||
virtual void clear();
|
||||
virtual void clear() {}
|
||||
virtual void sort() {}
|
||||
|
||||
// Add a light to the bins
|
||||
void addLight( LightInfo *light );
|
||||
|
||||
// Clear all lights from the bins
|
||||
void clearAllLights();
|
||||
|
||||
virtual bool setTargetSize(const Point2I &newTargetSize);
|
||||
|
||||
// ConsoleObject interface
|
||||
|
|
@ -220,7 +223,7 @@ protected:
|
|||
|
||||
AdvancedLightBufferConditioner *mConditioner;
|
||||
|
||||
typedef GFXVertexPNT FarFrustumQuadVert;
|
||||
typedef GFXVertexPNTT FarFrustumQuadVert;
|
||||
GFXVertexBufferHandle<FarFrustumQuadVert> mFarFrustumQuadVerts;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -438,7 +438,7 @@ void AdvancedLightManager::unregisterAllLights()
|
|||
Parent::unregisterAllLights();
|
||||
|
||||
if ( mLightBinManager )
|
||||
mLightBinManager->clear();
|
||||
mLightBinManager->clearAllLights();
|
||||
}
|
||||
|
||||
bool AdvancedLightManager::setTextureStage( const SceneData &sgData,
|
||||
|
|
|
|||
|
|
@ -76,6 +76,9 @@ Frustum::Frustum( bool isOrtho,
|
|||
mNumTiles = 1;
|
||||
mCurrTile.set(0,0);
|
||||
mTileOverlap.set(0.0f, 0.0f);
|
||||
|
||||
mProjectionOffset.zero();
|
||||
mProjectionOffsetMatrix.identity();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
@ -433,12 +436,27 @@ void Frustum::mulL( const MatrixF& mat )
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void Frustum::setProjectionOffset(const Point2F& offsetMat)
|
||||
{
|
||||
mProjectionOffset = offsetMat;
|
||||
mProjectionOffsetMatrix.identity();
|
||||
mProjectionOffsetMatrix.setPosition(Point3F(mProjectionOffset.x, mProjectionOffset.y, 0.0f));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void Frustum::getProjectionMatrix( MatrixF *proj, bool gfxRotate ) const
|
||||
{
|
||||
if (mIsOrtho)
|
||||
{
|
||||
MathUtils::makeOrthoProjection(proj, mNearLeft, mNearRight, mNearTop, mNearBottom, mNearDist, mFarDist, gfxRotate);
|
||||
proj->mulL(mProjectionOffsetMatrix);
|
||||
}
|
||||
else
|
||||
{
|
||||
MathUtils::makeProjection(proj, mNearLeft, mNearRight, mNearTop, mNearBottom, mNearDist, mFarDist, gfxRotate);
|
||||
proj->mulL(mProjectionOffsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -246,6 +246,12 @@ class Frustum : public PolyhedronImpl< FrustumData >
|
|||
|
||||
/// @}
|
||||
|
||||
/// Offset used for projection matrix calculations
|
||||
Point2F mProjectionOffset;
|
||||
|
||||
/// The calculated projection offset matrix
|
||||
MatrixF mProjectionOffsetMatrix;
|
||||
|
||||
public:
|
||||
|
||||
/// @name Constructors
|
||||
|
|
@ -403,9 +409,23 @@ class Frustum : public PolyhedronImpl< FrustumData >
|
|||
/// points typically used for early rejection.
|
||||
const Box3F& getBounds() const { _update(); return mBounds; }
|
||||
|
||||
/// Get the offset used when calculating the projection matrix
|
||||
const Point2F& getProjectionOffset() const { return mProjectionOffset; }
|
||||
|
||||
/// Get the offset matrix used when calculating the projection matrix
|
||||
const MatrixF& getProjectionOffsetMatrix() const { return mProjectionOffsetMatrix; }
|
||||
|
||||
/// Set the offset used when calculating the projection matrix
|
||||
void setProjectionOffset(const Point2F& offsetMat);
|
||||
|
||||
/// Clear any offset used when calculating the projection matrix
|
||||
void clearProjectionOffset() { mProjectionOffset.zero(); mProjectionOffsetMatrix.identity(); }
|
||||
|
||||
/// Generates a projection matrix from the frustum.
|
||||
void getProjectionMatrix( MatrixF *proj, bool gfxRotate=true ) const;
|
||||
|
||||
/// Will update the frustum if it is dirty
|
||||
void update() { _update(); }
|
||||
/// @}
|
||||
|
||||
/// @name Culling
|
||||
|
|
|
|||
44
Engine/source/platform/output/IDisplayDevice.h
Normal file
44
Engine/source/platform/output/IDisplayDevice.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// 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 _IDISPLAYDEVICE_H_
|
||||
#define _IDISPLAYDEVICE_H_
|
||||
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
// Defines a custom display device that requires particular rendering settings
|
||||
// in order for a scene to display correctly.
|
||||
|
||||
class IDisplayDevice
|
||||
{
|
||||
public:
|
||||
virtual bool providesYFOV() const = 0;
|
||||
virtual F32 getYFOV() const = 0;
|
||||
|
||||
virtual bool providesEyeOffset() const = 0;
|
||||
virtual const Point3F& getEyeOffset() const = 0;
|
||||
|
||||
virtual bool providesProjectionOffset() const = 0;
|
||||
virtual const Point2F& getProjectionOffset() const = 0;
|
||||
};
|
||||
|
||||
#endif // _IDISPLAYDEVICE_H_
|
||||
|
|
@ -435,26 +435,53 @@ void PostEffect::_updateScreenGeometry( const Frustum &frustum,
|
|||
const Point3F *frustumPoints = frustum.getPoints();
|
||||
const Point3F& cameraPos = frustum.getPosition();
|
||||
|
||||
// Perform a camera offset. We need to manually perform this offset on the postFx's
|
||||
// polygon, which is at the far plane.
|
||||
const Point2F& projOffset = frustum.getProjectionOffset();
|
||||
Point3F cameraOffsetPos = cameraPos;
|
||||
if(!projOffset.isZero())
|
||||
{
|
||||
// First we need to calculate the offset at the near plane. The projOffset
|
||||
// given above can be thought of a percent as it ranges from 0..1 (or 0..-1).
|
||||
F32 nearOffset = frustum.getNearRight() * projOffset.x;
|
||||
|
||||
// Now given the near plane distance from the camera we can solve the right
|
||||
// triangle and calcuate the SIN theta for the offset at the near plane.
|
||||
// SIN theta = x/y
|
||||
F32 sinTheta = nearOffset / frustum.getNearDist();
|
||||
|
||||
// Finally, we can calcuate the offset at the far plane, which is where our sun (or vector)
|
||||
// light's polygon is drawn.
|
||||
F32 farOffset = frustum.getFarDist() * sinTheta;
|
||||
|
||||
// We can now apply this far plane offset to the far plane itself, which then compensates
|
||||
// for the project offset.
|
||||
MatrixF camTrans = frustum.getTransform();
|
||||
VectorF offset = camTrans.getRightVector();
|
||||
offset *= farOffset;
|
||||
cameraOffsetPos += offset;
|
||||
}
|
||||
|
||||
PFXVertex *vert = outVB->lock();
|
||||
|
||||
vert->point.set( -1.0, -1.0, 0.0 );
|
||||
vert->texCoord.set( 0.0f, 1.0f );
|
||||
vert->wsEyeRay = frustumPoints[Frustum::FarBottomLeft] - cameraPos;
|
||||
vert->wsEyeRay = frustumPoints[Frustum::FarBottomLeft] - cameraOffsetPos;
|
||||
vert++;
|
||||
|
||||
vert->point.set( -1.0, 1.0, 0.0 );
|
||||
vert->texCoord.set( 0.0f, 0.0f );
|
||||
vert->wsEyeRay = frustumPoints[Frustum::FarTopLeft] - cameraPos;
|
||||
vert->wsEyeRay = frustumPoints[Frustum::FarTopLeft] - cameraOffsetPos;
|
||||
vert++;
|
||||
|
||||
vert->point.set( 1.0, 1.0, 0.0 );
|
||||
vert->texCoord.set( 1.0f, 0.0f );
|
||||
vert->wsEyeRay = frustumPoints[Frustum::FarTopRight] - cameraPos;
|
||||
vert->wsEyeRay = frustumPoints[Frustum::FarTopRight] - cameraOffsetPos;
|
||||
vert++;
|
||||
|
||||
vert->point.set( 1.0, -1.0, 0.0 );
|
||||
vert->texCoord.set( 1.0f, 1.0f );
|
||||
vert->wsEyeRay = frustumPoints[Frustum::FarBottomRight] - cameraPos;
|
||||
vert->wsEyeRay = frustumPoints[Frustum::FarBottomRight] - cameraOffsetPos;
|
||||
vert++;
|
||||
|
||||
outVB->unlock();
|
||||
|
|
@ -710,6 +737,8 @@ void PostEffect::_setupConstants( const SceneRenderState *state )
|
|||
MathUtils::mProjectWorldToScreen( lightPos, &sunPos, GFX->getViewport(), tmp, proj );
|
||||
|
||||
// And normalize it to the 0 to 1 range.
|
||||
sunPos.x -= (F32)GFX->getViewport().point.x;
|
||||
sunPos.y -= (F32)GFX->getViewport().point.y;
|
||||
sunPos.x /= (F32)GFX->getViewport().extent.x;
|
||||
sunPos.y /= (F32)GFX->getViewport().extent.y;
|
||||
|
||||
|
|
|
|||
|
|
@ -233,7 +233,70 @@ void SceneManager::renderScene( SceneRenderState* renderState, U32 objectMask, S
|
|||
|
||||
// Render the scene.
|
||||
|
||||
renderSceneNoLights( renderState, objectMask, baseObject, baseZone );
|
||||
if(GFX->getCurrentRenderStyle() == GFXDevice::RS_StereoSideBySide)
|
||||
{
|
||||
// Store previous values
|
||||
RectI originalVP = GFX->getViewport();
|
||||
MatrixF originalWorld = GFX->getWorldMatrix();
|
||||
|
||||
Point2F projOffset = GFX->getCurrentProjectionOffset();
|
||||
Point3F eyeOffset = GFX->getStereoEyeOffset();
|
||||
|
||||
// Render left half of display
|
||||
RectI leftVP = originalVP;
|
||||
leftVP.extent.x *= 0.5;
|
||||
GFX->setViewport(leftVP);
|
||||
|
||||
MatrixF leftWorldTrans(true);
|
||||
leftWorldTrans.setPosition(Point3F(eyeOffset.x, eyeOffset.y, eyeOffset.z));
|
||||
MatrixF leftWorld(originalWorld);
|
||||
leftWorld.mulL(leftWorldTrans);
|
||||
GFX->setWorldMatrix(leftWorld);
|
||||
|
||||
Frustum gfxFrustum = GFX->getFrustum();
|
||||
gfxFrustum.setProjectionOffset(Point2F(projOffset.x, projOffset.y));
|
||||
GFX->setFrustum(gfxFrustum);
|
||||
|
||||
SceneCameraState cameraStateLeft = SceneCameraState::fromGFX();
|
||||
SceneRenderState renderStateLeft( this, renderState->getScenePassType(), cameraStateLeft );
|
||||
renderStateLeft.setSceneRenderStyle(SRS_SideBySide);
|
||||
renderStateLeft.setSceneRenderField(0);
|
||||
|
||||
renderSceneNoLights( &renderStateLeft, objectMask, baseObject, baseZone );
|
||||
|
||||
// Render right half of display
|
||||
RectI rightVP = originalVP;
|
||||
rightVP.extent.x *= 0.5;
|
||||
rightVP.point.x += rightVP.extent.x;
|
||||
GFX->setViewport(rightVP);
|
||||
|
||||
MatrixF rightWorldTrans(true);
|
||||
rightWorldTrans.setPosition(Point3F(-eyeOffset.x, eyeOffset.y, eyeOffset.z));
|
||||
MatrixF rightWorld(originalWorld);
|
||||
rightWorld.mulL(rightWorldTrans);
|
||||
GFX->setWorldMatrix(rightWorld);
|
||||
|
||||
gfxFrustum = GFX->getFrustum();
|
||||
gfxFrustum.setProjectionOffset(Point2F(-projOffset.x, projOffset.y));
|
||||
GFX->setFrustum(gfxFrustum);
|
||||
|
||||
SceneCameraState cameraStateRight = SceneCameraState::fromGFX();
|
||||
SceneRenderState renderStateRight( this, renderState->getScenePassType(), cameraStateRight );
|
||||
renderStateRight.setSceneRenderStyle(SRS_SideBySide);
|
||||
renderStateRight.setSceneRenderField(1);
|
||||
|
||||
renderSceneNoLights( &renderStateRight, objectMask, baseObject, baseZone );
|
||||
|
||||
// Restore previous values
|
||||
GFX->setWorldMatrix(originalWorld);
|
||||
gfxFrustum.clearProjectionOffset();
|
||||
GFX->setFrustum(gfxFrustum);
|
||||
GFX->setViewport(originalVP);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderSceneNoLights( renderState, objectMask, baseObject, baseZone );
|
||||
}
|
||||
|
||||
// Trigger the post-render signal.
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,18 @@ enum ScenePassType
|
|||
};
|
||||
|
||||
|
||||
/// The type of scene render style
|
||||
/// @see SceneRenderState
|
||||
enum SceneRenderStyle
|
||||
{
|
||||
/// The regular style of rendering
|
||||
SRS_Standard,
|
||||
|
||||
/// Side-by-side style rendering
|
||||
SRS_SideBySide,
|
||||
};
|
||||
|
||||
|
||||
/// An object that manages the SceneObjects belonging to a scene.
|
||||
class SceneManager
|
||||
{
|
||||
|
|
|
|||
|
|
@ -44,7 +44,9 @@ SceneRenderState::SceneRenderState( SceneManager* sceneManager,
|
|||
mUsePostEffects( usePostEffects ),
|
||||
mDisableAdvancedLightingBins( false ),
|
||||
mRenderArea( view.getFrustum().getBounds() ),
|
||||
mAmbientLightColor( sceneManager->getAmbientLightColor() )
|
||||
mAmbientLightColor( sceneManager->getAmbientLightColor() ),
|
||||
mSceneRenderStyle( SRS_Standard ),
|
||||
mRenderField( 0 )
|
||||
{
|
||||
// Setup the default parameters for the screen metrics methods.
|
||||
mDiffuseCameraTransform = view.getViewWorldMatrix();
|
||||
|
|
|
|||
|
|
@ -69,6 +69,12 @@ class SceneRenderState
|
|||
/// The type of scene render pass we're doing.
|
||||
ScenePassType mScenePassType;
|
||||
|
||||
/// The render style being performed
|
||||
SceneRenderStyle mSceneRenderStyle;
|
||||
|
||||
/// When doing stereo rendering, the current field that is being rendered
|
||||
S32 mRenderField;
|
||||
|
||||
/// The render pass which we are setting up with this scene state.
|
||||
RenderPassManager* mRenderPass;
|
||||
|
||||
|
|
@ -219,6 +225,23 @@ class SceneRenderState
|
|||
|
||||
/// @}
|
||||
|
||||
/// @name Render Style
|
||||
/// @{
|
||||
|
||||
/// Get the rendering style used for the scene
|
||||
SceneRenderStyle getSceneRenderStyle() const { return mSceneRenderStyle; }
|
||||
|
||||
/// Set the rendering style used for the scene
|
||||
void setSceneRenderStyle(SceneRenderStyle style) { mSceneRenderStyle = style; }
|
||||
|
||||
/// Get the stereo field being rendered
|
||||
S32 getSceneRenderField() const { return mRenderField; }
|
||||
|
||||
/// Set the stereo field being rendered
|
||||
void setSceneRenderField(S32 field) { mRenderField = field; }
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Transforms, projections, and viewports.
|
||||
/// @{
|
||||
|
||||
|
|
|
|||
|
|
@ -58,8 +58,9 @@ singleton PostEffect( LightRayPostFX )
|
|||
isEnabled = false;
|
||||
allowReflectPass = false;
|
||||
|
||||
renderTime = "PFXAfterDiffuse";
|
||||
renderPriority = 0.1;
|
||||
renderTime = "PFXBeforeBin";
|
||||
renderBin = "EditorBin";
|
||||
renderPriority = 10;
|
||||
|
||||
shader = LightRayOccludeShader;
|
||||
stateBlock = LightRayStateBlock;
|
||||
|
|
|
|||
|
|
@ -89,6 +89,14 @@ struct VertexIn_PNT
|
|||
float2 uv0 : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct VertexIn_PNTT
|
||||
{
|
||||
float4 pos : POSITION;
|
||||
float3 normal : NORMAL;
|
||||
float3 tangent : TANGENT;
|
||||
float2 uv0 : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct VertexIn_PNCT
|
||||
{
|
||||
float4 pos : POSITION;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
#include "farFrustumQuad.hlsl"
|
||||
|
||||
|
||||
FarFrustumQuadConnectV main( VertexIn_PNT IN,
|
||||
FarFrustumQuadConnectV main( VertexIn_PNTT IN,
|
||||
uniform float4 rtParams0 )
|
||||
{
|
||||
FarFrustumQuadConnectV OUT;
|
||||
|
|
@ -36,7 +36,7 @@ FarFrustumQuadConnectV main( VertexIn_PNT IN,
|
|||
|
||||
// Interpolators will generate eye rays the
|
||||
// from far-frustum corners.
|
||||
OUT.wsEyeRay = IN.pos.xyz;
|
||||
OUT.wsEyeRay = IN.tangent.xyz;
|
||||
OUT.vsEyeRay = IN.normal.xyz;
|
||||
|
||||
return OUT;
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ float4 main( FarFrustumQuadConnectP IN,
|
|||
|
||||
// Use eye ray to get ws pos
|
||||
float4 worldPos = float4(eyePosWorld + IN.wsEyeRay * depth, 1.0f);
|
||||
|
||||
|
||||
// Get the light attenuation.
|
||||
float dotNL = dot(-lightDirection, normal);
|
||||
|
||||
|
|
|
|||
|
|
@ -58,8 +58,9 @@ singleton PostEffect( LightRayPostFX )
|
|||
isEnabled = false;
|
||||
allowReflectPass = false;
|
||||
|
||||
renderTime = "PFXAfterDiffuse";
|
||||
renderPriority = 0.1;
|
||||
renderTime = "PFXBeforeBin";
|
||||
renderBin = "EditorBin";
|
||||
renderPriority = 10;
|
||||
|
||||
shader = LightRayOccludeShader;
|
||||
stateBlock = LightRayStateBlock;
|
||||
|
|
|
|||
|
|
@ -89,6 +89,14 @@ struct VertexIn_PNT
|
|||
float2 uv0 : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct VertexIn_PNTT
|
||||
{
|
||||
float4 pos : POSITION;
|
||||
float3 normal : NORMAL;
|
||||
float3 tangent : TANGENT;
|
||||
float2 uv0 : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct VertexIn_PNCT
|
||||
{
|
||||
float4 pos : POSITION;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
#include "farFrustumQuad.hlsl"
|
||||
|
||||
|
||||
FarFrustumQuadConnectV main( VertexIn_PNT IN,
|
||||
FarFrustumQuadConnectV main( VertexIn_PNTT IN,
|
||||
uniform float4 rtParams0 )
|
||||
{
|
||||
FarFrustumQuadConnectV OUT;
|
||||
|
|
@ -36,7 +36,7 @@ FarFrustumQuadConnectV main( VertexIn_PNT IN,
|
|||
|
||||
// Interpolators will generate eye rays the
|
||||
// from far-frustum corners.
|
||||
OUT.wsEyeRay = IN.pos.xyz;
|
||||
OUT.wsEyeRay = IN.tangent.xyz;
|
||||
OUT.vsEyeRay = IN.normal.xyz;
|
||||
|
||||
return OUT;
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ float4 main( FarFrustumQuadConnectP IN,
|
|||
|
||||
// Use eye ray to get ws pos
|
||||
float4 worldPos = float4(eyePosWorld + IN.wsEyeRay * depth, 1.0f);
|
||||
|
||||
|
||||
// Get the light attenuation.
|
||||
float dotNL = dot(-lightDirection, normal);
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ switch( Generator::$platform )
|
|||
addEngineSrcDir('platform/threads');
|
||||
addEngineSrcDir('platform/async');
|
||||
addEngineSrcDir('platform/input');
|
||||
addEngineSrcDir('platform/output');
|
||||
addEngineSrcDir('app');
|
||||
addEngineSrcDir('app/net');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue