/*============================================================================= GLREOcean.cpp : implementation of the Ocean Rendering. Copyright (c) 2001 Crytek Studios. All Rights Reserved. Revision history: * Created by Honitch Andrey =============================================================================*/ #include "RenderPCH.h" #include "GL_Renderer.h" #include "I3dengine.h" #include "../Common/NvTriStrip/NVTriStrip.h" #include "GLCGVProgram.h" #undef THIS_FILE static char THIS_FILE[] = __FILE__; //======================================================================= void CREOcean::mfReset() { } void CREOcean::GenerateIndices(int nLodCode) { SPrimitiveGroup pg; TArray Indicies; int size; if (m_OceanIndicies[nLodCode]) return; SOceanIndicies *oi = new SOceanIndicies; m_OceanIndicies[nLodCode] = oi; if (!(nLodCode & ~LOD_MASK)) { int nL = nLodCode & LOD_MASK; pg.offsIndex = 0; pg.numIndices = m_pIndices[nL].Num(); pg.numTris = pg.numIndices-2; pg.type = PT_STRIP; oi->m_Groups.AddElem(pg); oi->m_nInds = pg.numIndices; int size = pg.numIndices * sizeof(ushort); oi->m_pIndicies = new ushort[size]; memcpy(oi->m_pIndicies, &m_pIndices[nL][0], size); return; } int nLod = nLodCode & LOD_MASK; int nl = 1<m_Groups.AddElem(pg); // Left pg.offsIndex = Indicies.Num(); iIndex = nGrid*(nGrid-1); if (!(nLodCode & (1<m_Groups.AddElem(pg); // Bottom pg.offsIndex = Indicies.Num(); iIndex = 0; if (!(nLodCode & (1<m_Groups.AddElem(pg); // Right pg.offsIndex = Indicies.Num(); iIndex = nGrid-1; if (!(nLodCode & (1<m_Groups.AddElem(pg); // Top pg.offsIndex = Indicies.Num(); iIndex = nGrid*(nGrid-1)+nGrid-1; if (!(nLodCode & (1<m_Groups.AddElem(pg); size = Indicies.Num()*sizeof(ushort); oi->m_pIndicies = new ushort[size]; oi->m_nInds = Indicies.Num(); cryMemcpy(oi->m_pIndicies, &Indicies[0], size); } void CREOcean::UpdateTexture() { if (m_CustomTexBind[0]>0 && !CRenderer::CV_r_oceantexupdate) return; CGLRenderer *r = gcpOGL; float data[OCEANGRID][OCEANGRID][2]; double time0 = 0; ticks(time0); if (m_CustomTexBind[0] <= 0) { uint tnum = 0; glGenTextures(1, &tnum); assert(tnum<14000); m_CustomTexBind[0] = tnum; r->SetTexture(tnum, eTT_Base); glTexImage2D(GL_TEXTURE_2D, 0, GL_DSDT_NV, OCEANGRID, OCEANGRID, 0, GL_DSDT_NV, GL_FLOAT, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); r->SetTexture(0, eTT_Base); } for(unsigned int y=0; ySetTexture(m_CustomTexBind[0], eTT_Base); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, OCEANGRID, OCEANGRID, GL_DSDT_NV, GL_FLOAT, &data[0][0][0]); r->SetTexture(0, eTT_Base); unticks(time0); m_RS.m_StatsTimeTexUpdate = (float)(time0*1000.0*g_SecondsPerCycle); } void CREOcean::DrawOceanSector(SOceanIndicies *oi) { for (int i=0; im_Groups.Num(); i++) { SPrimitiveGroup *g = &oi->m_Groups[i]; switch (g->type) { case PT_STRIP: glDrawElements(GL_TRIANGLE_STRIP, g->numIndices, GL_UNSIGNED_SHORT, &oi->m_pIndicies[g->offsIndex]); break; case PT_LIST: glDrawElements(GL_TRIANGLES, g->numIndices, GL_UNSIGNED_SHORT, &oi->m_pIndicies[g->offsIndex]); break; case PT_FAN: glDrawElements(GL_TRIANGLE_FAN, g->numIndices, GL_UNSIGNED_SHORT, &oi->m_pIndicies[g->offsIndex]); break; } gRenDev->m_nPolygons += g->numTris; } } static _inline int Compare(SOceanSector *& p1, SOceanSector *& p2) { if(p1->m_Flags > p2->m_Flags) return 1; else if(p1->m_Flags < p2->m_Flags) return -1; return 0; } static _inline float sCalcSplash(SSplash *spl, float fX, float fY) { CGLRenderer *r = gcpOGL; float fDeltaTime = r->m_RP.m_RealTime - spl->m_fStartTime; float fScaleFactor = 1.0f / (r->m_RP.m_RealTime - spl->m_fLastTime + 1.0f); float vDelt[2]; // Calculate 2D distance vDelt[0] = spl->m_Pos[0] - fX; vDelt[1] = spl->m_Pos[1] - fY; float fSqDist = vDelt[0]*vDelt[0] + vDelt[1]*vDelt[1]; // Inverse square root unsigned int *n1 = (unsigned int *)&fSqDist; unsigned int nn = 0x5f3759df - (*n1 >> 1); float *n2 = (float *)&nn; float fDistSplash = 1.0f / ((1.5f - (fSqDist * 0.5f) * *n2 * *n2) * *n2); // Emulate sin waves float fDistFactor = fDeltaTime*10.0f - fDistSplash + 4.0f; fDistFactor = CLAMP(fDistFactor, 0.0f, 1.0f); float fRad = (fDistSplash - fDeltaTime*10) * 0.4f / 3.1416f * 1024.0f; float fSin = gRenDev->m_RP.m_tSinTable[QRound(fRad)&0x3ff] * fDistFactor; return fSin * fScaleFactor * spl->m_fForce; } float *CREOcean::mfFillAdditionalBuffer(SOceanSector *os, int nSplashes, SSplash *pSplashes[], int& nCurSize, int nLod, float fSize) { CGLRenderer *r = gcpOGL; int nSize = sizeof(struct_VERTEX_FORMAT_P3F_N_COL4UB_TEX2F) * r->m_RP.m_MaxVerts / sizeof(float); float *pHM, *pTZ; //int nFloats = (os->m_Flags & OSF_NEEDHEIGHTS) ? 1 : 0; //if (nSplashes) // nFloats++; int nFloats = 2; if (gcpOGL->m_RP.m_NumFences) { if (nCurSize+(OCEANGRID+1)*(OCEANGRID+1)*nFloats >= nSize) { nCurSize = 0; //r->EF_SetFence(true); } pTZ = &r->m_RP.m_Ptr.VBPtr_0->x+nCurSize; nCurSize += (OCEANGRID+1)*(OCEANGRID+1)*nFloats; } else pTZ = &gcpOGL->m_RP.m_Ptr.VBPtr_0->x; pHM = pTZ; int nStep = 1<x; float fY = m_Pos[ty][tx][1]*fSize+os->y; float fZ = GetHMap(fX, fY); pTZ[0] = fZ; pTZ += nIncr; } } } else if (!(os->m_Flags & OSF_NEEDHEIGHTS)) { for (int ty=0; tyx; float fY = m_Pos[ty][tx][1]*fSize+os->y; float fSplash = 0; for (int n=0; nx; float fY = m_Pos[ty][tx][1]*fSize+os->y; float fZ = GetHMap(fX, fY); pTZ[0] = fZ; float fSplash = 0; for (int n=0; nm_RP.m_RendIndices; I3DEngine *eng = (I3DEngine *)iSystem->GetI3DEngine(); float fMaxDist = eng->GetMaxViewDistance() / 1.0f; CCamera cam = r->GetCamera(); Vec3d mins; Vec3d maxs; Vec3d cameraPos = cam.GetPos(); float fCurX = (float)((int)cameraPos[0] & ~255) + 128.0f; float fCurY = (float)((int)cameraPos[1] & ~255) + 128.0f; float fSize = (float)CRenderer::CV_r_oceansectorsize; float fHeightScale = (float)CRenderer::CV_r_oceanheightscale; if (fSize != m_fSectorSize) { m_fSectorSize = fSize; for (int i=0; i<256; i++) { m_OceanSectorsHash[i].Free(); } } float fWaterLevel = eng->GetWaterLevel(); CVProgram *vp = NULL; int nMaxSplashes = CLAMP(CRenderer::CV_r_oceanmaxsplashes, 0, 16); //x = y = 0; //x = fCurX; //y = fCurY; m_VisOceanSectors.SetUse(0); int osNear = -1; int nMinLod = 65536; for (y=fCurY-fMaxDist; ym_Flags & (OSF_FIRSTTIME | OSF_VISIBLE))) continue; mins.x = x+m_MinBound.x*fSize; mins.y = y+m_MinBound.y*fSize; mins.z = fWaterLevel+m_MinBound.z*fHeightScale; maxs.x = x+fSize+m_MaxBound.x*fSize; maxs.y = y+fSize+m_MaxBound.y*fSize; maxs.z = fWaterLevel+m_MaxBound.z*fHeightScale; int cull = cam.IsAABBVisible_hierarchical( AABB(mins,maxs) ); if (cull != CULL_EXCLUSION) { Vec3d vCenter = (mins + maxs) * 0.5f; os->nLod = GetLOD(cameraPos, vCenter); if (os->nLod < nMinLod) { nMinLod = os->nLod; osNear = m_VisOceanSectors.Num(); } os->m_Frame = gRenDev->m_cEF.m_Frame; os->m_Flags &= ~OSF_LODUPDATED; m_VisOceanSectors.AddElem(os); } } } if (m_VisOceanSectors.Num()) { LinkVisSectors(fSize); ::Sort(&m_VisOceanSectors[0], m_VisOceanSectors.Num()); } bool bCurNeedBuffer = false; int nSize = sizeof(struct_VERTEX_FORMAT_P3F_N_COL4UB_TEX2F) * r->m_RP.m_MaxVerts / sizeof(float); int nCurSize = 0; float *pHM, *pTZ; int nIndVP = CRenderer::CV_r_waterreflections ? 0 : 1; for (i=0; inLod; bool bL = (nLod < GetSectorByPos(os->x-fSize, os->y)->nLod); bool bR = (nLod < GetSectorByPos(os->x+fSize, os->y)->nLod); bool bT = (nLod < GetSectorByPos(os->x, os->y+fSize)->nLod); bool bB = (nLod < GetSectorByPos(os->x, os->y-fSize)->nLod); int nLodCode = nLod + (bL<m_RP.m_Splashes.Num(); nS++) { SSplash *spl = &r->m_RP.m_Splashes[nS]; float fCurRadius = spl->m_fCurRadius; if (spl->m_Pos[0]-fCurRadius > os->x+fSize || spl->m_Pos[1]-fCurRadius > os->y+fSize || spl->m_Pos[0]+fCurRadius < os->x || spl->m_Pos[1]+fCurRadius < os->y) continue; pSplashes[nSplashes++] = spl; if (nSplashes == nMaxSplashes) break; } if (os->m_Flags & OSF_FIRSTTIME) { if (gcpOGL->m_RP.m_NumFences) { if (nCurSize+(OCEANGRID+1)*(OCEANGRID+1) >= nSize) { nCurSize = 0; //r->EF_SetFence(true); } pTZ = &r->m_RP.m_Ptr.VBPtr_0->x+nCurSize; nCurSize += (OCEANGRID+1)*(OCEANGRID+1); } else pTZ = &gcpOGL->m_RP.m_Ptr.VBPtr_0->x; pHM = pTZ; float fMinLevel = 99999.0f; bool bBlend = false; bool bNeedHeights = false; for (uint ty=0; tyx; float fY = m_Pos[ty][tx][1]*fSize+os->y; float fZ = GetHMap(fX, fY); pTZ[0] = fZ; pTZ += 2; fMinLevel = min(fMinLevel, fZ); if (!bBlend && fWaterLevel-fZ <= 1.0f) bBlend = true; if (fWaterLevel-fZ < 16.0f) bNeedHeights = true; } } os->m_Flags &= ~OSF_FIRSTTIME; if (fMinLevel <= fWaterLevel) os->m_Flags |= OSF_VISIBLE; //bNeedHeights = bBlend = false; if (bNeedHeights) os->m_Flags |= OSF_NEEDHEIGHTS; if (bBlend) { os->m_Flags |= OSF_BLEND; os->RenderState = GS_DEPTHWRITE | GS_BLSRC_SRCALPHA | GS_BLDST_ONEMINUSSRCALPHA; } else os->RenderState = GS_DEPTHWRITE; } else if ((os->m_Flags & OSF_NEEDHEIGHTS) || nSplashes) pHM = mfFillAdditionalBuffer(os, nSplashes, pSplashes, nCurSize, nLod, fSize); m_RS.m_StatsNumRendOceanSectors++; r->EF_SetState(os->RenderState); int nVP = 0; if (os->m_Flags & OSF_NEEDHEIGHTS) nVP += OVP_HEIGHT; if (nSplashes) nVP++; CVProgram *curVP = m_VPs[nVP]; if (!curVP) continue; if (vp != curVP) { vp = curVP; vp->mfSet(true, NULL, 0); } int nFloats = (os->m_Flags & OSF_NEEDHEIGHTS) ? 1 : 0; if (nSplashes) nFloats++; if (nFloats) { if (glVertexAttribPointerNV) glVertexAttribPointerNV(1, 2, GL_FLOAT, sizeof(float)*2, pHM); if (!bCurNeedBuffer) { glEnableClientState(GL_VERTEX_ATTRIB_ARRAY1_NV); bCurNeedBuffer = true; } } else if (bCurNeedBuffer) { glDisableClientState(GL_VERTEX_ATTRIB_ARRAY1_NV); bCurNeedBuffer = false; } float param[4]; param[0] = os->x; param[1] = os->y; param[2] = 0; param[3] = 0; CCGVProgram_GL *vpGL = (CCGVProgram_GL *)vp; SCGBind *pBind = vpGL->mfGetParameterBind("PosOffset"); if (pBind) vpGL->mfParameter4f(pBind, param); pBind = vpGL->mfGetParameterBind("PosScale"); param[0] = fSize; param[1] = fSize; param[2] = fHeightScale; param[3] = 1; if (pBind) vpGL->mfParameter4f(pBind, param); /*if (nSplashes) { static Vec3d sPos; static float sTime; sPos = Vec3d(306, 1942, 0); if ((GetAsyncKeyState(VK_NUMPAD1) & 0x8000)) sTime = r->m_RP.m_RealTime; float fForce = 1.5f; cgBindIter *pBind = vpGL->mfGetParameterBind("Splash1"); if (pBind) { param[0] = sPos[0]; param[1] = sPos[1]; param[2] = sPos[2]; param[3] = 0; vpGL->mfParameter4f(pBind, param); } pBind = vpGL->mfGetParameterBind("Time"); if (pBind) { float fDeltaTime = r->m_RP.m_RealTime - sTime; float fScaleFactor = 1.0f / (r->m_RP.m_RealTime - sTime + 1.0f); param[0] = fForce * fScaleFactor; param[1] = 0; param[2] = -fDeltaTime*10; param[3] = fDeltaTime*10.0f; vpGL->mfParameter4f(pBind, param); } }*/ DrawOceanSector(m_OceanIndicies[nLodCode]); //if (bCurNeedBuffer && glSetFenceNV) // glSetFenceNV(r->m_RP.mBufs[r->m_RP.m_CurFence].mFence, GL_ALL_COMPLETED_NV); } if (bCurNeedBuffer) glDisableClientState(GL_VERTEX_ATTRIB_ARRAY1_NV); if (glSetFenceNV) glSetFenceNV(m_pBuffer->m_fence, GL_ALL_COMPLETED_NV); m_pBuffer->m_bFenceSet = true; } #define A(row,col) a[(col<<2)+row] #define B(row,col) b[(col<<2)+row] #define P(row,col) product[(col<<2)+row] static void matmul4( GLfloat *product, const GLfloat *a, const GLfloat *b ) { int i; for (i=0; i<4; i++) { float ai0=A(i,0), ai1=A(i,1), ai2=A(i,2), ai3=A(i,3); P(i,0) = ai0 * B(0,0) + ai1 * B(1,0) + ai2 * B(2,0) + ai3 * B(3,0); P(i,1) = ai0 * B(0,1) + ai1 * B(1,1) + ai2 * B(2,1) + ai3 * B(3,1); P(i,2) = ai0 * B(0,2) + ai1 * B(1,2) + ai2 * B(2,2) + ai3 * B(3,2); P(i,3) = ai0 * B(0,3) + ai1 * B(1,3) + ai2 * B(2,3) + ai3 * B(3,3); } } #undef A #undef B #undef P static void transform_point(float out[4], const float m[16], const float in[4]) { #define M(row,col) m[col*4+row] out[0] = M(0, 0) * in[0] + M(0, 1) * in[1] + M(0, 2) * in[2] + M(0, 3) * in[3]; out[1] = M(1, 0) * in[0] + M(1, 1) * in[1] + M(1, 2) * in[2] + M(1, 3) * in[3]; out[2] = M(2, 0) * in[0] + M(2, 1) * in[1] + M(2, 2) * in[2] + M(2, 3) * in[3]; out[3] = M(3, 0) * in[0] + M(3, 1) * in[1] + M(3, 2) * in[2] + M(3, 3) * in[3]; #undef M } void CREOcean::mfDrawOceanScreenLod() { CGLRenderer *r = gcpOGL; int nScreenY = r->GetHeight(); int nScreenX = r->GetWidth(); if(!nScreenY || !nScreenX) return; float ProjectionMatrix[16]; float ModelMatrix[16]; int Viewport[4]; glGetFloatv(GL_MODELVIEW_MATRIX, ModelMatrix); glGetFloatv(GL_PROJECTION_MATRIX, ProjectionMatrix); glGetIntegerv(GL_VIEWPORT, Viewport); float m[16], A[16]; matmul4(A, ProjectionMatrix, ModelMatrix); QQinvertMatrixf(m, A); m_DWQVertices.Free(); m_DWQIndices.Free(); int fParts = 25; int nScreenXP = nScreenX/fParts; int nScreenYP = nScreenY/fParts; if(!nScreenYP || !nScreenXP) return; unsigned short nIdx = 0; int y_size = int(float(nScreenY)/(nScreenYP)+1.f); const Vec3d vCamPos = r->GetCamera().GetPos(); I3DEngine *eng = (I3DEngine *)iSystem->GetI3DEngine(); float fWaterLevel = eng->GetWaterLevel(); int nSize = 256; float fSize = (float)nSize; float fiSize = 1.0f / fSize; bool bWaterVisible = false; float *pBuf, *pBuf1; //if (r->m_RP.m_NumFences) //r->EF_SetFence(true); pBuf = &r->m_RP.m_Ptr.VBPtr_0->x; pBuf1 = pBuf; float fScale = fSize / OCEANGRID; float fiScale = 1.0f / fScale; for(int x=0; x<=nScreenX/(nScreenXP)*(nScreenXP); x+=int(nScreenXP)) { for(int y=0; y<=nScreenY/(nScreenYP)*(nScreenYP); y+=int(nScreenYP)) { Vec3d n, p; n((float)x, (float)y, 0.0f); float in[4], out[4]; /* transformation coordonnees normalisees entre -1 et 1 */ in[0] = (n.x - Viewport[0]) * 2 / Viewport[2] - 1.0f; in[1] = (n.y - Viewport[1]) * 2 / Viewport[3] - 1.0f; in[2] = 2.0f * n.z - 1.0f; in[3] = 1.0; transform_point(out, m, in); if (out[3] == 0.0f) continue; p.x = out[0] / out[3]; p.y = out[1] / out[3]; p.z = out[2] / out[3]; //r->UnProjectFromScreen(n.x, n.y, n.z, &p.x, &p.y, &p.z); float z1 = vCamPos.z - p.z; float t; if(z1<0.0001f) z1=0.0001f; { float z2 = vCamPos.z - fWaterLevel; t = z2/z1; if( t > 300*(vCamPos.z - fWaterLevel) ) t = 300*(vCamPos.z - fWaterLevel); p = p - vCamPos; p = vCamPos + p*t; } float fX = p.x * fiScale; float fY = p.y * fiScale; long nX = QInt(fX); long nY = QInt(fY); register float dx = fX - (float)nX; register float dy = fY - (float)nY; register float dix = 1.0f - dx; register float diy = 1.0f - dy; nX &= OCEANGRID-1; nY &= OCEANGRID-1; fX = (float)QInt(p.x * fiSize); fY = (float)QInt(p.y * fiSize); long nX1 = (nX+1);// & (OCEANGRID-1); long nY1 = (nY+1);// & (OCEANGRID-1); register float d0, d1; Vec3d Pos, Nor; d0 = dix * m_Pos[nY][nX][0] + dx * m_Pos[nY][nX1][0]; d1 = dix * m_Pos[nY1][nX][0] + dx * m_Pos[nY1][nX1][0]; Pos.x = (diy * d0 + dy * d1) + fX; d0 = dix * m_Pos[nY][nX][1] + dx * m_Pos[nY][nX1][1]; d1 = dix * m_Pos[nY1][nX][1] + dx * m_Pos[nY1][nX1][1]; Pos.y = (diy * d0 + dy * d1) + fY; d0 = dix * m_HX[nY][nX] + dx * m_HX[nY][nX1]; d1 = dix * m_HX[nY1][nX] + dx * m_HX[nY1][nX1]; Pos.z = -(diy * d0 + dy * d1); d0 = dix * m_Normals[nY][nX].x + dx * m_Normals[nY][nX1].x; d1 = dix * m_Normals[nY1][nX].x + dx * m_Normals[nY1][nX1].x; Nor.x = diy * d0 + dy * d1; d0 = dix * m_Normals[nY][nX].y + dx * m_Normals[nY][nX1].y; d1 = dix * m_Normals[nY1][nX].y + dx * m_Normals[nY1][nX1].y; Nor.y = diy * d0 + dy * d1; d0 = dix * m_Normals[nY][nX].z + dx * m_Normals[nY][nX1].z; d1 = dix * m_Normals[nY1][nX].z + dx * m_Normals[nY1][nX1].z; Nor.z = diy * d0 + dy * d1; pBuf1[0] = Pos.x; pBuf1[1] = Pos.y; pBuf1[2] = Pos.z; pBuf1[3] = Nor.x; pBuf1[4] = Nor.y; pBuf1[5] = Nor.z; float fZ = GetHMap(Pos.x * fSize, Pos.y * fSize); pBuf1[6] = fZ; if(fZ <= fWaterLevel) bWaterVisible = true; pBuf1 += 7; if(xEF_SetState(GS_DEPTHWRITE | GS_BLSRC_SRCALPHA | GS_BLDST_ONEMINUSSRCALPHA); glVertexAttribPointerNV(0, 3, GL_FLOAT, sizeof(float)*7, pBuf); glEnableClientState(GL_VERTEX_ATTRIB_ARRAY0_NV); glVertexAttribPointerNV(2, 3, GL_FLOAT, sizeof(float)*7, pBuf+3); glEnableClientState(GL_VERTEX_ATTRIB_ARRAY2_NV); glVertexAttribPointerNV(1, 1, GL_FLOAT, sizeof(float)*7, pBuf+6); glEnableClientState(GL_VERTEX_ATTRIB_ARRAY1_NV); CVProgram *vp = m_VPQ; if (!vp) return; if (vp) vp->mfSet(true, false); glDrawElements(GL_TRIANGLES, m_DWQIndices.Num(), GL_UNSIGNED_SHORT, &m_DWQIndices[0]); if (vp) vp->mfSet(false, false); glDisableClientState(GL_VERTEX_ATTRIB_ARRAY2_NV); glDisableClientState(GL_VERTEX_ATTRIB_ARRAY1_NV); glDisableClientState(GL_VERTEX_ATTRIB_ARRAY0_NV); } bool CREOcean::mfDraw(SShader *ef, SShaderPass *sfm) { double time0 = 0; ticks(time0); if (CRenderer::CV_r_oceanrendtype == 0) mfDrawOceanSectors(); else mfDrawOceanScreenLod(); unticks(time0); m_RS.m_StatsTimeRendOcean = (float)(time0*1000.0*g_SecondsPerCycle); // gRenDev->PostLoad(); return true; } bool CREOcean::mfPreDraw(SShaderPass *sl) { return true; } char BoxSides[0x40*8] = { 0,0,0,0, 0,0,0,0, //00 0,4,6,2, 0,0,0,4, //01 7,5,1,3, 0,0,0,4, //02 0,0,0,0, 0,0,0,0, //03 0,1,5,4, 0,0,0,4, //04 0,1,5,4, 6,2,0,6, //05 7,5,4,0, 1,3,0,6, //06 0,0,0,0, 0,0,0,0, //07 7,3,2,6, 0,0,0,4, //08 0,4,6,7, 3,2,0,6, //09 7,5,1,3, 2,6,0,6, //0a 0,0,0,0, 0,0,0,0, //0b 0,0,0,0, 0,0,0,0, //0c 0,0,0,0, 0,0,0,0, //0d 0,0,0,0, 0,0,0,0, //0e 0,0,0,0, 0,0,0,0, //0f 0,2,3,1, 0,0,0,4, //10 0,4,6,2, 3,1,0,6, //11 7,5,1,0, 2,3,0,6, //12 0,0,0,0, 0,0,0,0, //13 0,2,3,1, 5,4,0,6, //14 1,5,4,6, 2,3,0,6, //15 7,5,4,0, 2,3,0,6, //16 0,0,0,0, 0,0,0,0, //17 0,2,6,7, 3,1,0,6, //18 0,4,6,7, 3,1,0,6, //19 7,5,1,0, 2,6,0,6, //1a 0,0,0,0, 0,0,0,0, //1b 0,0,0,0, 0,0,0,0, //1c 0,0,0,0, 0,0,0,0, //1d 0,0,0,0, 0,0,0,0, //1e 0,0,0,0, 0,0,0,0, //1f 7,6,4,5, 0,0,0,4, //20 0,4,5,7, 6,2,0,6, //21 7,6,4,5, 1,3,0,6, //22 0,0,0,0, 0,0,0,0, //23 7,6,4,0, 1,5,0,6, //24 0,1,5,7, 6,2,0,6, //25 7,6,4,0, 1,3,0,6, //26 0,0,0,0, 0,0,0,0, //27 7,3,2,6, 4,5,0,6, //28 0,4,5,7, 3,2,0,6, //29 6,4,5,1, 3,2,0,6, //2a 0,0,0,0, 0,0,0,0, //2b 0,0,0,0, 0,0,0,0, //2c 0,0,0,0, 0,0,0,0, //2d 0,0,0,0, 0,0,0,0, //2e 0,0,0,0, 0,0,0,0, //2f 0,0,0,0, 0,0,0,0, //30 0,0,0,0, 0,0,0,0, //31 0,0,0,0, 0,0,0,0, //32 0,0,0,0, 0,0,0,0, //33 0,0,0,0, 0,0,0,0, //34 0,0,0,0, 0,0,0,0, //35 0,0,0,0, 0,0,0,0, //36 0,0,0,0, 0,0,0,0, //37 0,0,0,0, 0,0,0,0, //38 0,0,0,0, 0,0,0,0, //39 0,0,0,0, 0,0,0,0, //3a 0,0,0,0, 0,0,0,0, //3b 0,0,0,0, 0,0,0,0, //3c 0,0,0,0, 0,0,0,0, //3d 0,0,0,0, 0,0,0,0, //3e 0,0,0,0, 0,0,0,0, //3f };