/* NAME: CartoonRenderer.cpp DESCRIPTION: Cartoon-style renderer. COPYRIGHT: Copyright (c) 1999-2005, Quesa Developers. All rights reserved. For the current release of Quesa, please see: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: o Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. o Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. o Neither the name of Quesa nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ___________________________________________________________________________ */ /* ___________________________________________________________________________ REMARKS Goal ---- The main idea of cartoon-style rendering is to use relatively few distinct colors, giving a look that is somewhat "flat" but not as flat as using the NULL illumination shader or using nothing but ambient light. We also wish to emphasize the outlines. Implementation -------------- Instead of letting OpenGL lighting produce shading effects, we use only ambient light, and use a special texture to produce the shading. Currently we use only two grayscale values in the texture. One could use a few more, but with too many distinct values, the image would not look "cartoony". We generate texture coordinates in such a way that triangles facing the camera receive a lighter shade than triangles that are more nearly edge-on. Since the geometry may already have a texture, the natural way to combine the shading texture with the original texture is to use the multitexturing extension. However, if that extension is not available, we fall back to combining the textures by drawing the geometry twice, using blend mode. Usage ----- One way to get the Cartoon renderer is to enumerate subclasses of the renderer class, as the Geom Test sample does. Another way would be to use code like this: TQ3ObjectType cartoonType; Q3ObjectHierarchy_GetTypeFromString( "Quesa Cartoon", &cartoonType ); TQ3RendererObject theRenderer = Q3Renderer_NewFromType( cartoonType ); A TriMesh can be forced to be rendered by the standard interactive renderer by attaching an object property of type 'NCar'. The data in the property does not matter. Transparent trimeshes will always be rendered by the interactive renderer. ___________________________________________________________________________ */ #include "CartoonRenderer.h" #include "GLPrefix.h" #include #include #include "IRPrefix.h" #include "IRGeometry.h" #include "IRTexture.h" #include "GLDrawContext.h" #include "GLUtils.h" #include static int sLastGLError = 0; #define kNonCartoonProperty Q3_FOUR_CHARACTER_CONSTANT('N', 'C', 'a', 'r') #define kQ3ClassNameRendererCartoon "Quesa Cartoon" const int kShadingTextureWidth = 32; #if Q3_DEBUG #define CHECK_GL_ERROR Q3_ASSERT( (sLastGLError = glGetError()) == GL_NO_ERROR ) #else #define CHECK_GL_ERROR #endif static TQ3ObjectType sRendererType = 0; // In lieu of glext.h #ifndef GL_ARB_multitexture #define GL_TEXTURE0_ARB 0x84C0 #define GL_TEXTURE1_ARB 0x84C1 #endif #if QUESA_OS_WIN32 typedef void (__stdcall * EQ3ActiveTextureARBProcPtr) (GLenum texture); typedef void (__stdcall * EQ3ClientActiveTextureARBProcPtr) (GLenum texture); #else typedef void (* EQ3ActiveTextureARBProcPtr) (GLenum texture); typedef void (* EQ3ClientActiveTextureARBProcPtr) (GLenum texture); #endif namespace { const int kNumLightsToSet = 8; const int kMinContourSize = 40; const int kFullContourSize = 400; const float kMaxContourWidth = 2.5f; class CCartoonRendererQuesa; struct CartoonRendererData { TQ3InteractiveData irData; CCartoonRendererQuesa* cartooner; }; class StSaveLightingState { public: StSaveLightingState() { glPushAttrib( GL_LIGHTING_BIT ); } ~StSaveLightingState() { glPopAttrib(); } }; #pragma mark class CCartoonRendererQuesa class CCartoonRendererQuesa { public: CCartoonRendererQuesa(void); ~CCartoonRendererQuesa(void); void SubmitTriMesh( TQ3ViewObject theView, CartoonRendererData *cartoonInstanceData, TQ3TriMeshData* geomData, TQ3Boolean hadAttributeTexture, const TQ3Vector3D* vertNormals, const TQ3Param2D* texCoords ); void Init( bool inHasMultiTexture ); void DrawArrays( const TQ3TriMeshData* geomData, const TQ3Vector3D* vertNormals, const TQ3Param2D* texCoords, float* pFloatDiffuseColor, bool bAllreadyTextured ); void DrawArraysFakeMultitexture( const TQ3TriMeshData* geomData, const TQ3Vector3D* vertNormals, const TQ3Param2D* texCoords, float* pFloatDiffuseColor, bool bAllreadyTextured ); void SetInited(bool bOnOff = true); bool IsInited(); bool m_bInited; void* m_PlatformGLContextSaved; void DrawJustLocalTexture(); void DrawContours( TQ3ViewObject theView, TQ3TriMeshData* geomData, TQ3BackfacingStyle inBackfacing ); float CalcContourWidth( TQ3ViewObject theView, TQ3TriMeshData* geomData ); void DrawContourArrays( float lineWidth, const TQ3TriMeshData* geomData ); TQ3Param2D* GenShadeTVerts( int nVerts, const TQ3Vector3D* pMemNormals ); std::vector m_arrShadeTVerts; void RebuildShading(); void SetShadeWidth(int nWidth) { m_nShadeWidth = nWidth; } int GetShadeWidth() const { return m_nShadeWidth; } int m_nShadeWidth; void SetShadeLightness(int n) { m_nShadeLightness = n; } int GetShadeLightness() const { return m_nShadeLightness; } int m_nShadeLightness; void* GetLocalTextureMemory(); void BuildLocalTexture(); void DeleteLocalTexture(); void DrawLocalTexture(bool bAllreadyTextured); GLuint m_nLocalTextureID; void InitExtensions(); void SetClientActiveTextureARB(int n); void SetActiveTextureARB(int n); void DisableMultiTexturing(); EQ3ActiveTextureARBProcPtr m_glActiveTextureARB; EQ3ClientActiveTextureARBProcPtr m_glClientActiveTextureARB; GLboolean m_savedLightEnabled[ kNumLightsToSet ]; GLboolean m_savedLightingEnabled; GLfloat m_savedAmbientLight[ 4 ]; }; } CCartoonRendererQuesa::CCartoonRendererQuesa() : m_bInited( false ) , m_PlatformGLContextSaved( NULL ) , m_nLocalTextureID( 0 ) , m_glActiveTextureARB( NULL ) , m_glClientActiveTextureARB( NULL ) { SetShadeLightness(130); SetShadeWidth(7); } CCartoonRendererQuesa::~CCartoonRendererQuesa() { DeleteLocalTexture(); } void CCartoonRendererQuesa::InitExtensions() { void** arrPtrs[] = { (void**)&m_glActiveTextureARB, (void**)&m_glClientActiveTextureARB, 0}; char* arrNames[] = { "glActiveTextureARB", "glClientActiveTextureARB", 0}; int nIx; for(nIx = 0; arrPtrs[nIx]; nIx++) { *arrPtrs[nIx] = (void*)GLGetProcAddress(arrNames[nIx]); } // nIx } void CCartoonRendererQuesa::SetClientActiveTextureARB(int n) { if (m_glClientActiveTextureARB != NULL) { m_glClientActiveTextureARB(GL_TEXTURE0_ARB + n); } } void CCartoonRendererQuesa::SetActiveTextureARB(int n) { if (m_glActiveTextureARB != NULL) { m_glActiveTextureARB(GL_TEXTURE0_ARB + n); } } void CCartoonRendererQuesa::DisableMultiTexturing() { int nIx; for(nIx = 1; nIx >= 0; nIx--) { SetClientActiveTextureARB(nIx); glDisableClientState(GL_TEXTURE_COORD_ARRAY); SetActiveTextureARB(nIx); glDisable(GL_TEXTURE_2D); } // nIx } void CCartoonRendererQuesa::DeleteLocalTexture() { if(0 == m_nLocalTextureID) { return; } glBindTexture(GL_TEXTURE_2D, m_nLocalTextureID); glDeleteTextures(1, &m_nLocalTextureID); m_nLocalTextureID = 0; } void* CCartoonRendererQuesa::GetLocalTextureMemory() { static char s_image[kShadingTextureWidth * 4]; return s_image; } void CCartoonRendererQuesa::BuildLocalTexture() { // save the current bound texture, if any GLint savedTexture = 0; glGetIntegerv( GL_TEXTURE_BINDING_2D, &savedTexture ); CHECK_GL_ERROR; DeleteLocalTexture(); glGenTextures(1, &m_nLocalTextureID); CHECK_GL_ERROR; glBindTexture(GL_TEXTURE_2D, m_nLocalTextureID); CHECK_GL_ERROR; glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Not default glPixelStorei(GL_PACK_ALIGNMENT, 1); CHECK_GL_ERROR; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, kShadingTextureWidth, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, GetLocalTextureMemory()); CHECK_GL_ERROR; // restore previous texture glBindTexture( GL_TEXTURE_2D, savedTexture ); } void CCartoonRendererQuesa::DrawJustLocalTexture() { if (0 != m_nLocalTextureID) { CHECK_GL_ERROR; glBindTexture(GL_TEXTURE_2D, m_nLocalTextureID); CHECK_GL_ERROR; glEnable(GL_TEXTURE_2D); CHECK_GL_ERROR; glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); CHECK_GL_ERROR; } else { Q3_ASSERT( !"no local texture" ); } } void CCartoonRendererQuesa::DrawLocalTexture(bool bAllreadyTextured) { if(true == bAllreadyTextured) { SetActiveTextureARB(1); SetClientActiveTextureARB(1); } else { SetClientActiveTextureARB(1); glDisableClientState(GL_TEXTURE_COORD_ARRAY); SetActiveTextureARB(1); glDisable(GL_TEXTURE_2D); SetActiveTextureARB(0); SetClientActiveTextureARB(0); } DrawJustLocalTexture(); } void CCartoonRendererQuesa::RebuildShading() { struct UByteColor { unsigned char r; unsigned char g; unsigned char b; }; UByteColor* pMemRgb = (UByteColor*) GetLocalTextureMemory(); int nIx; int nSz = kShadingTextureWidth; UByteColor rgbShade = {m_nShadeLightness, m_nShadeLightness, m_nShadeLightness}; UByteColor rgbWhite = {255, 255, 255}; for(nIx = 0; nIx < nSz; ++nIx, ++pMemRgb) { if (nIx >= m_nShadeWidth) { *pMemRgb = rgbShade; } else { *pMemRgb = rgbWhite; } } // nIx BuildLocalTexture(); } void CCartoonRendererQuesa::Init( bool inHasMultiTexture ) { if (inHasMultiTexture) { InitExtensions(); } SetInited(); RebuildShading(); } static void* GetCurrentGLContext() { void* theContext = NULL; #if QUESA_OS_MACINTOSH theContext = aglGetCurrentContext(); #elif QUESA_OS_WIN32 theContext = wglGetCurrentContext(); #endif return theContext; } void CCartoonRendererQuesa::SetInited(bool bOnOff) { if(true == bOnOff) { m_PlatformGLContextSaved = GetCurrentGLContext(); } else { m_PlatformGLContextSaved = 0; } m_bInited = bOnOff; } bool CCartoonRendererQuesa::IsInited() { void* h = GetCurrentGLContext(); if((m_PlatformGLContextSaved == h) && (true == m_bInited)) { return true; } return false; } TQ3Param2D* CCartoonRendererQuesa::GenShadeTVerts( int nVerts, const TQ3Vector3D* pMemNormals ) { if ((int)m_arrShadeTVerts.size() < nVerts) { m_arrShadeTVerts.resize(nVerts); memset(&m_arrShadeTVerts[0], 0, sizeof(TQ3Param2D) * nVerts); } TQ3Param2D* pMemShadeTVerts = &m_arrShadeTVerts[0]; glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); TQ3Matrix4x4 modelViewMatrix; glGetFloatv(GL_MODELVIEW_MATRIX, &modelViewMatrix.value[0][0]); float fDotProd; for (int nIx = 0; nIx < nVerts; ++nIx, ++pMemNormals, ++pMemShadeTVerts) { float x = pMemNormals->x; // locals for faster access float y = pMemNormals->y; float z = pMemNormals->z; // Transform the normal vector from world to eye coordinates TQ3Vector3D eyeNormal = { x * modelViewMatrix.value[0][0] + y * modelViewMatrix.value[1][0] + z * modelViewMatrix.value[2][0], x * modelViewMatrix.value[0][1] + y * modelViewMatrix.value[1][1] + z * modelViewMatrix.value[2][1], x * modelViewMatrix.value[0][2] + y * modelViewMatrix.value[1][2] + z * modelViewMatrix.value[2][2] }; float eyeNormalLen = Q3FastVector3D_Length( &eyeNormal ); if (eyeNormalLen < FLT_EPSILON) { fDotProd = 0.1f;// ?? } else { fDotProd = (1.0f - fabs(eyeNormal.z) / eyeNormalLen) * 0.5f; } if (fDotProd <= 0.0f) { fDotProd = 0.01f; } pMemShadeTVerts->u = fDotProd; pMemShadeTVerts->v = 0; } // nIx return &m_arrShadeTVerts[0]; } static void SetUpLight( float inAmbientLevel = 1.4f ) { glEnable( GL_LIGHTING ); GLfloat brightAmbient[] = { inAmbientLevel, inAmbientLevel, inAmbientLevel, 1.0f }; glLightModelfv( GL_LIGHT_MODEL_AMBIENT, brightAmbient ); glDisable( GL_LIGHT0 ); glDisable( GL_LIGHT1 ); glDisable( GL_LIGHT2 ); glDisable( GL_LIGHT3 ); glDisable( GL_LIGHT4 ); glDisable( GL_LIGHT5 ); glDisable( GL_LIGHT6 ); glDisable( GL_LIGHT7 ); } void CCartoonRendererQuesa::DrawContours( TQ3ViewObject theView, TQ3TriMeshData* geomData, TQ3BackfacingStyle inBackfacing ) { if (inBackfacing == kQ3BackfacingStyleRemove) { float lineWidth = CalcContourWidth( theView, geomData ); DrawContourArrays( lineWidth, geomData ); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); glPolygonMode(GL_FRONT, GL_FILL); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); } else { glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glDisable(GL_CULL_FACE); } } /* CalcContourWidth If an object is small on screen, it should not be overwhelmed by the contour lines. */ float CCartoonRendererQuesa::CalcContourWidth( TQ3ViewObject theView, TQ3TriMeshData* geomData ) { // Compute local to screen matrix. TQ3Matrix4x4 localToWorld, worldToFrustum, frustumToWindow, localToFrustum, localToWindow; Q3View_GetLocalToWorldMatrixState( theView, &localToWorld ); Q3View_GetWorldToFrustumMatrixState( theView, &worldToFrustum ); Q3View_GetFrustumToWindowMatrixState( theView, &frustumToWindow ); Q3Matrix4x4_Multiply( &localToWorld, &worldToFrustum, &localToFrustum ); Q3Matrix4x4_Multiply( &localToFrustum, &frustumToWindow, &localToWindow ); // Get the corners of the bounding box. TQ3Point3D corners[8]; corners[0] = corners[1] = corners[2] = corners[3] = geomData->bBox.min; corners[4] = corners[5] = corners[6] = corners[7] = geomData->bBox.max; corners[1].x = corners[7].x; corners[2].y = corners[7].y; corners[3].z = corners[7].z; corners[4].x = corners[0].x; corners[5].y = corners[0].y; corners[6].z = corners[0].z; // Transform the corners to screen coordinates. Q3Point3D_To3DTransformArray( corners, &localToWindow, corners, 8, sizeof(TQ3Point3D), sizeof(TQ3Point3D) ); // Find the largest screen distance between two corners. float maxDistSq = -1.0f; for (int i = 0; i < 7; ++i) { for (int j = i + 1; j < 8; ++j) { float oneDistSq = Q3FastPoint3D_DistanceSquared( &corners[i], &corners[j] ); if (oneDistSq > maxDistSq) { maxDistSq = oneDistSq; } } } float maxDist = sqrt( maxDistSq ); // Compute the line width. float lineWidth = kMaxContourWidth; if (maxDist < kMinContourSize) { lineWidth = 0.0f; } else if (maxDist < kFullContourSize) { lineWidth *= (maxDist - kMinContourSize) / (kFullContourSize - kMinContourSize); } return lineWidth; } void CCartoonRendererQuesa::DrawContourArrays( float lineWidth, const TQ3TriMeshData* geomData ) { if (lineWidth < FLT_EPSILON) { return; } DisableMultiTexturing(); glEnable(GL_CULL_FACE); glDisable(GL_LIGHTING); glColor3f(0, 0, 0); glEnable(GL_DEPTH_TEST); glCullFace(GL_FRONT); glDisable(GL_TEXTURE_2D); glPolygonMode(GL_BACK, GL_LINE); glVertexPointer( 3, GL_FLOAT, 0, geomData->points ); glEnable( GL_LINE_SMOOTH ); glLineWidth( lineWidth ); glEnableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDrawElements(GL_TRIANGLES, geomData->numTriangles * 3, GL_UNSIGNED_INT, geomData->triangles ); } static void GetVertexDataFromTriMesh( const TQ3TriMeshData& inData, const TQ3Param2D*& outTextureCoords, const TQ3Vector3D*& outVertexNormals ) { outTextureCoords = NULL; outVertexNormals = NULL; for (int attNum = 0; attNum < (int)inData.numVertexAttributeTypes; ++attNum) { switch (inData.vertexAttributeTypes[attNum].attributeType) { case kQ3AttributeTypeNormal: outVertexNormals = (const TQ3Vector3D*) inData.vertexAttributeTypes[attNum].data; break; case kQ3AttributeTypeSurfaceUV: case kQ3AttributeTypeShadingUV: outTextureCoords = (const TQ3Param2D*) inData.vertexAttributeTypes[attNum].data; break; } } } void CCartoonRendererQuesa::DrawArrays( const TQ3TriMeshData* geomData, const TQ3Vector3D* vertNormals, const TQ3Param2D* texCoords, float* pFloatDiffuseColor, bool bAllreadyTextured) { try { if (bAllreadyTextured) { SetActiveTextureARB(0); SetClientActiveTextureARB(0); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnable(GL_TEXTURE_2D); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer( 2, GL_FLOAT, 0, texCoords ); } DrawLocalTexture(bAllreadyTextured); if(0 != pFloatDiffuseColor) { glColor3fv(pFloatDiffuseColor); } else { glColor3ub(255, 255, 0); } glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnableClientState(GL_TEXTURE_COORD_ARRAY); TQ3Param2D* tCoords = GenShadeTVerts(geomData->numPoints, vertNormals); glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glTexCoordPointer(2, GL_FLOAT, 0, tCoords); glVertexPointer(3, GL_FLOAT, 0, geomData->points); SetUpLight( bAllreadyTextured? 1.4f : 1.1f ); glDrawElements( GL_TRIANGLES, geomData->numTriangles * 3, GL_UNSIGNED_INT, geomData->triangles ); } catch(...) { } } void CCartoonRendererQuesa::DrawArraysFakeMultitexture( const TQ3TriMeshData* geomData, const TQ3Vector3D* vertNormals, const TQ3Param2D* texCoords, float* pFloatDiffuseColor, bool bAllreadyTextured ) { try { glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glEnable(GL_TEXTURE_2D); glEnableClientState(GL_TEXTURE_COORD_ARRAY); if (0 != pFloatDiffuseColor) { glColor3fv(pFloatDiffuseColor); } else { glColor3ub(255, 255, 0); } if (bAllreadyTextured) { glTexCoordPointer(2, GL_FLOAT, 0, texCoords); glVertexPointer(3, GL_FLOAT, 0, geomData->points); SetUpLight( 2.0f ); glDrawElements(GL_TRIANGLES, geomData->numTriangles * 3, GL_UNSIGNED_INT, geomData->triangles ); glEnable( GL_BLEND ); glBlendFunc( GL_DST_COLOR, GL_ZERO ); glDepthMask( GL_FALSE ); glDepthFunc( GL_EQUAL ); } DrawJustLocalTexture(); TQ3Param2D* tCoords = GenShadeTVerts( geomData->numPoints, vertNormals ); glMatrixMode(GL_TEXTURE); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glTexCoordPointer(2, GL_FLOAT, 0, tCoords); glVertexPointer(3, GL_FLOAT, 0, geomData->points); SetUpLight( bAllreadyTextured? 1.4f : 1.1f ); glDrawElements(GL_TRIANGLES, geomData->numTriangles * 3, GL_UNSIGNED_INT, geomData->triangles ); if (bAllreadyTextured) { glDisable( GL_BLEND ); glDepthMask( GL_TRUE ); glDepthFunc( GL_LESS ); } } catch (...) { } } static TQ3XFunctionPointer GetInteractiveRendererMethod(TQ3XMethodType methodType) { static TQ3XObjectClass s_pInteractiveRendererClass = NULL; if (NULL == s_pInteractiveRendererClass) { s_pInteractiveRendererClass = Q3XObjectHierarchy_FindClassByType(kQ3RendererTypeInteractive); } if (NULL == s_pInteractiveRendererClass) { return 0; } return Q3XObjectClass_GetMethod( s_pInteractiveRendererClass, methodType ); } void CCartoonRendererQuesa::SubmitTriMesh( TQ3ViewObject theView, CartoonRendererData *cartoonInstanceData, TQ3TriMeshData* geomData, TQ3Boolean hadAttributeTexture, const TQ3Vector3D* vertNormals, const TQ3Param2D* texCoords ) { TQ3InteractiveData* instanceData = &cartoonInstanceData->irData; // Lazy initialization if (! IsInited()) { Init( instanceData->glExtensions.multitexture == kQ3True ); } StSaveLightingState saveLight; SetActiveTextureARB(0); SetClientActiveTextureARB(0); if ( (geomData->numTriangles == 0) || (vertNormals == NULL) ) { return; } TQ3ColorRGB whiteRGBColor = { 1.0f, 1.0f, 1.0f }; float* pFloatDiffuseColor = &whiteRGBColor.r; if ( (instanceData->stateGeomDiffuseColour != NULL) && ((! instanceData->stateTextureActive) || (instanceData->stateViewIllumination == kQ3IlluminationTypeNULL)) ) { pFloatDiffuseColor = &instanceData->stateGeomDiffuseColour->r; } bool bAlreadyTextured = (texCoords != NULL) && instanceData->stateTextureActive; // We will use only ambient light, hence we do not need normals for OpenGL. // We will only use normals to compute texture coordinates for shading. glDisableClientState( GL_NORMAL_ARRAY ); DrawContours( theView, geomData, instanceData->stateBackfacing ); if (m_glActiveTextureARB == NULL) { DrawArraysFakeMultitexture( geomData, vertNormals, texCoords, pFloatDiffuseColor, bAlreadyTextured ); } else { DrawArrays( geomData, vertNormals, texCoords, pFloatDiffuseColor, bAlreadyTextured ); } SetActiveTextureARB(0); SetClientActiveTextureARB(0); IRRenderer_Texture_Postamble(theView, instanceData, hadAttributeTexture, (TQ3Boolean) (texCoords != NULL) ); DisableMultiTexturing(); } static bool IsGeomMarkedNonCartoon( TQ3Object inObject ) { return (kQ3Success == Q3Object_GetProperty( inObject, kNonCartoonProperty, 0, NULL, NULL )); } static bool IsGeomTransparent( const TQ3InteractiveData* inInstanceData ) { bool isTransparent = (inInstanceData->stateGeomTransparencyColour != NULL) && ( (inInstanceData->stateGeomTransparencyColour->r < 1.0f) || (inInstanceData->stateGeomTransparencyColour->g < 1.0f) || (inInstanceData->stateGeomTransparencyColour->b < 1.0f) ); return isTransparent; } static TQ3Status Cartoon_Geometry_Submit_TriMesh(TQ3ViewObject theView, CartoonRendererData *cartoonInstanceData, TQ3GeometryObject theGeom, TQ3TriMeshData *geomData) { TQ3InteractiveData* irInstanceData = &cartoonInstanceData->irData; TQ3Status theStatus = kQ3Success; // Activate our context GLDrawContext_SetCurrent(irInstanceData->glContext, kQ3False); // Update our state for this object and the texture mapping TQ3Boolean hadAttributeTexture; hadAttributeTexture = IRGeometry_Attribute_Handler(theView, geomData->triMeshAttributeSet, irInstanceData, kQ3XAttributeMaskGeometry | kQ3XAttributeMaskSurfaceShader); // Extract vertex normals and UVs, if any, from trimesh data. const TQ3Param2D* texCoords; const TQ3Vector3D* normals; GetVertexDataFromTriMesh( *geomData, texCoords, normals ); // Translucent objects, objects without vertex normals, or objects marked // with a special property, will be passed to the // standard OpenGL renderer. if ( (normals == NULL) || IsGeomMarkedNonCartoon( theGeom ) || IsGeomTransparent( irInstanceData ) ) { TQ3XRendererSubmitGeometryMethod irMethod = (TQ3XRendererSubmitGeometryMethod) GetInteractiveRendererMethod( kQ3GeometryTypeTriMesh ); theStatus = irMethod( theView, irInstanceData, theGeom, geomData ); } else { cartoonInstanceData->cartooner->SubmitTriMesh( theView, cartoonInstanceData, geomData, hadAttributeTexture, normals, texCoords ); } return theStatus; } static TQ3XFunctionPointer GetInteractiveRendererSubmitGeomMethod(TQ3ObjectType geomType) { TQ3XFunctionPointer theMethod = NULL; TQ3XRendererSubmitGeometryMetaHandlerMethod irGeomMetaHander = (TQ3XRendererSubmitGeometryMetaHandlerMethod) GetInteractiveRendererMethod( kQ3XMethodTypeRendererSubmitGeometryMetaHandler ); if (irGeomMetaHander != NULL) { theMethod = (*irGeomMetaHander)( geomType ); } return theMethod; } static TQ3XFunctionPointer cartoon_submit_geom_metahandler( TQ3ObjectType geomType ) { TQ3XFunctionPointer theMethod = NULL; if (geomType == kQ3GeometryTypeTriMesh) { theMethod = (TQ3XFunctionPointer) Cartoon_Geometry_Submit_TriMesh; } else { theMethod = GetInteractiveRendererSubmitGeomMethod( geomType ); } return theMethod; } static TQ3Status cartoon_interactive_nickname(unsigned char *dataBuffer, TQ3Uns32 bufferSize, TQ3Uns32 *actualDataSize) { // Return the amount of space we need *actualDataSize = (TQ3Uns32)strlen(kQ3ClassNameRendererCartoon) + 1; // If we have a buffer, return the nick name if (dataBuffer != NULL) { // Clamp the buffer size if (bufferSize < *actualDataSize) *actualDataSize = bufferSize; // Return the string Q3Memory_Copy(kQ3ClassNameRendererCartoon, dataBuffer, (*actualDataSize)-1); dataBuffer[(*actualDataSize)-1] = 0x00; } return(kQ3Success); } static TQ3Status cartoon_new_object( TQ3Object theObject, void *privateData, void *paramData ) { #pragma unused(paramData) TQ3Status theStatus; CCartoonRendererQuesa* newCartooner = new(std::nothrow) CCartoonRendererQuesa; if (newCartooner == NULL) { theStatus = kQ3Failure; } else { TQ3XObjectNewMethod irNewMethod = (TQ3XObjectNewMethod) GetInteractiveRendererMethod( kQ3XMethodTypeObjectNew ); theStatus = irNewMethod( theObject, privateData, paramData ); if (theStatus == kQ3Success) { CartoonRendererData* instanceData = (CartoonRendererData *) privateData; instanceData->cartooner = newCartooner; } else { delete newCartooner; } } return theStatus; } static void cartoon_delete_object( TQ3Object theObject, void *privateData ) { TQ3XObjectDeleteMethod irDeleteMethod = (TQ3XObjectDeleteMethod) GetInteractiveRendererMethod( kQ3XMethodTypeObjectDelete ); irDeleteMethod( theObject, privateData ); CartoonRendererData* instanceData = (CartoonRendererData *) privateData; delete instanceData->cartooner; } static TQ3XFunctionPointer ca_cartoon_metahandler(TQ3XMethodType methodType) { TQ3XFunctionPointer theMethod = NULL; switch(methodType) { case kQ3XMethodTypeObjectNew: theMethod = (TQ3XFunctionPointer) cartoon_new_object; break; case kQ3XMethodTypeObjectDelete: theMethod = (TQ3XFunctionPointer) cartoon_delete_object; break; case kQ3XMethodTypeRendererGetNickNameString: theMethod = (TQ3XFunctionPointer) cartoon_interactive_nickname; break; case kQ3XMethodTypeRendererSubmitGeometryMetaHandler: theMethod = (TQ3XFunctionPointer) cartoon_submit_geom_metahandler; break; case kQ3GeometryTypeTriMesh: theMethod = (TQ3XFunctionPointer) Cartoon_Geometry_Submit_TriMesh; break; default: theMethod = GetInteractiveRendererMethod(methodType); break; } return theMethod; } TQ3Status CartoonRenderer_Register() { // Register the class // TQ3XObjectClass theClass = Q3XObjectHierarchy_RegisterClass( kQ3SharedTypeRenderer, &sRendererType, kQ3ClassNameRendererCartoon, ca_cartoon_metahandler, NULL, 0, sizeof(CartoonRendererData)); return(theClass == NULL ? kQ3Failure : kQ3Success); } void CartoonRenderer_Unregister() { TQ3Status qd3dStatus; TQ3XObjectClass theClass; // Find the renderer class theClass = Q3XObjectHierarchy_FindClassByType(sRendererType); if (theClass == NULL) return; // Unregister the class qd3dStatus = Q3XObjectHierarchy_UnregisterClass(theClass); }