/*============================================================================= D3DREOcean.cpp : implementation of the Ocean Rendering. Copyright (c) 2001 Crytek Studios. All Rights Reserved. Revision history: * Created by Honitch Andrey =============================================================================*/ #include "RenderPCH.h" #include "DriverD3D9.h" #include "D3DCGVProgram.h" #include "D3DCGPShader.h" #include "I3dengine.h" #include "../Common/NvTriStrip/NVTriStrip.h" #undef THIS_FILE static char THIS_FILE[] = __FILE__; //======================================================================= void CREOcean::GenerateIndices(int nLodCode) { CD3D9Renderer *r = gcpRendD3D; LPDIRECT3DDEVICE9 dv = gcpRendD3D->mfGetD3DDevice(); HRESULT h; SPrimitiveGroup pg; IDirect3DIndexBuffer9* ibuf; int size; int flags = D3DUSAGE_WRITEONLY; D3DPOOL Pool = D3DPOOL_MANAGED; ushort *dst; TArray Indicies; 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); size = pg.numIndices*sizeof(ushort); h = dv->CreateIndexBuffer(size, flags, D3DFMT_INDEX16, Pool, (IDirect3DIndexBuffer9**)&oi->m_pIndicies, NULL); ibuf = (IDirect3DIndexBuffer9*)oi->m_pIndicies; oi->m_nInds = pg.numIndices; h = ibuf->Lock(0, 0, (void **) &dst, 0); cryMemcpy(dst, &m_pIndices[nL][0], pg.numIndices*sizeof(ushort)); h = ibuf->Unlock(); 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); h = dv->CreateIndexBuffer(size, flags, D3DFMT_INDEX16, Pool, (IDirect3DIndexBuffer9**)&oi->m_pIndicies, NULL); ibuf = (IDirect3DIndexBuffer9*)oi->m_pIndicies; oi->m_nInds = Indicies.Num(); h = ibuf->Lock(0, 0, (void **) &dst, 0); cryMemcpy(dst, &Indicies[0], size); h = ibuf->Unlock(); } void CREOcean::UpdateTexture() { if (m_CustomTexBind[0]>0 && !CRenderer::CV_r_oceantexupdate) return; HRESULT h; CD3D9Renderer *r = gcpRendD3D; LPDIRECT3DDEVICE9 dv = gcpRendD3D->mfGetD3DDevice(); byte data[OCEANGRID][OCEANGRID][4]; double time0 = 0; ticks(time0); for(unsigned int y=0; ym_TexGenID++); STexPic *tp = r->m_TexMan->CreateTexture(name, OCEANGRID, OCEANGRID, 1, FT_NOMIPS | FT_NOSTREAM, FT2_NODXT, &data[0][0][0], eTT_DSDTBump, -1.0f, -1.0f, 0, NULL, 0, eTF_0888); m_CustomTexBind[0] = tp->m_Bind; } else { STexPicD3D *tp = (STexPicD3D *)gRenDev->m_TexMan->m_Textures[m_CustomTexBind[0]-TX_FIRSTBIND]; IDirect3DTexture9 *pID3DTexture = (IDirect3DTexture9*)tp->m_RefTex.m_VidTex; D3DLOCKED_RECT d3dlr; h = pID3DTexture->LockRect(0, &d3dlr, NULL, 0); D3DSURFACE_DESC ddsdDescDest; pID3DTexture->GetLevelDesc(0, &ddsdDescDest); if (d3dlr.Pitch == tp->m_Width*4) cryMemcpy(d3dlr.pBits, data, tp->m_Width*tp->m_Height*4); else { switch(ddsdDescDest.Format) { case D3DFMT_X8L8V8U8: { byte *pDst = (byte *)d3dlr.pBits; byte *pSrc = &data[0][0][0]; for (int i=0; im_Height; i++) { cryMemcpy(pDst, pSrc, tp->m_Width*4); pDst += d3dlr.Pitch; pSrc += tp->m_Width*4; } } break; case D3DFMT_L6V5U5: break; case D3DFMT_V8U8: { byte *pDst = (byte *)d3dlr.pBits; byte *pSrc = &data[0][0][0]; for (int i=0; im_Height; i++) { for (int j=0; jm_Width; j++) { pDst[j*2+0] = pSrc[j*4+0]; pDst[j*2+1] = pSrc[j*4+1]; } pDst += d3dlr.Pitch; pSrc += tp->m_Width*4; } } break; default: assert(0); } } h = pID3DTexture->UnlockRect(0); } unticks(time0); m_RS.m_StatsTimeTexUpdate = (float)(time0*1000.0*g_SecondsPerCycle); } void CREOcean::DrawOceanSector(SOceanIndicies *oi) { CD3D9Renderer *r = gcpRendD3D; LPDIRECT3DDEVICE9 dv = gcpRendD3D->mfGetD3DDevice(); HRESULT h; h = dv->SetIndices((IDirect3DIndexBuffer9 *)oi->m_pIndicies); for (int i=0; im_Groups.Num(); i++) { SPrimitiveGroup *g = &oi->m_Groups[i]; switch (g->type) { case PT_STRIP: if (FAILED(h=dv->DrawIndexedPrimitive(D3DPT_TRIANGLESTRIP, 0, 0, (OCEANGRID+1)*(OCEANGRID+1), g->offsIndex, g->numTris))) { r->Error("CREOcean::DrawOceanSector: DrawIndexedPrimitive error", h); return; } break; case PT_LIST: if (FAILED(h=dv->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, (OCEANGRID+1)*(OCEANGRID+1), g->offsIndex, g->numTris))) { r->Error("CREOcean::DrawOceanSector: DrawIndexedPrimitive error", h); return; } break; case PT_FAN: if (FAILED(h=dv->DrawIndexedPrimitive(D3DPT_TRIANGLEFAN, 0, 0, (OCEANGRID+1)*(OCEANGRID+1), g->offsIndex, g->numTris))) { r->Error("CREOcean::DrawOceanSector: DrawIndexedPrimitive error", h); return; } 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) { CD3D9Renderer *r = gcpRendD3D; 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) { float *pHM, *pTZ; int nFloats = (os->m_Flags & OSF_NEEDHEIGHTS) ? 1 : 0; if (nSplashes) nFloats++; pTZ = (float *)GetVBPtr((OCEANGRID+1)*(OCEANGRID+1)); 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 += nStep*2; } } } 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; nReleaseBuffer(m_pBuffer); m_pBuffer = NULL; } } void CREOcean::InitVB() { CD3D9Renderer *r = gcpRendD3D; LPDIRECT3DDEVICE9 dv = gcpRendD3D->mfGetD3DDevice(); HRESULT h; m_nNumVertsInPool = (OCEANGRID+1)*(OCEANGRID+1); int size = m_nNumVertsInPool * sizeof(struct_VERTEX_FORMAT_TEX2F); for (int i=0; iCreateVertexBuffer(size, D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, D3DFVF_TEX1, D3DPOOL_DEFAULT, (IDirect3DVertexBuffer9**)&m_pVertsPool[i], NULL); } m_nCurVB = 0; m_bLockedVB = false; { D3DVERTEXELEMENT9 elem[] = { {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, // position {0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0}, // normal D3DDECL_END() }; h = dv->CreateVertexDeclaration(&elem[0], (IDirect3DVertexDeclaration9**)&m_VertDecl); } { D3DVERTEXELEMENT9 elem[] = { {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, // position {0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0}, // normal {1, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_BLENDWEIGHT, 0}, // heightmpa/splashes D3DDECL_END() }; h = dv->CreateVertexDeclaration(&elem[0], (IDirect3DVertexDeclaration9**)&m_VertDeclHeightSplash); } } struct_VERTEX_FORMAT_TEX2F *CREOcean::GetVBPtr(int nVerts) { HRESULT h; struct_VERTEX_FORMAT_TEX2F *pVertices = NULL; if (nVerts > m_nNumVertsInPool) { assert(0); return NULL; } if (m_bLockedVB) UnlockVBPtr(); m_nCurVB++; if (m_nCurVB >= NUM_OCEANVBS) m_nCurVB = 0; IDirect3DVertexBuffer9 *pVB = (IDirect3DVertexBuffer9 *)m_pVertsPool[m_nCurVB]; h = pVB->Lock(0, 0, (void **) &pVertices, D3DLOCK_DISCARD); m_bLockedVB = true; return pVertices; } void CREOcean::UnlockVBPtr() { if (m_bLockedVB) { IDirect3DVertexBuffer9 *pVB = (IDirect3DVertexBuffer9 *)m_pVertsPool[m_nCurVB]; HRESULT hr = pVB->Unlock(); m_bLockedVB = false; } } void CREOcean::mfDrawOceanSectors() { float x, y; CD3D9Renderer *r = gcpRendD3D; LPDIRECT3DDEVICE9 dv = gcpRendD3D->mfGetD3DDevice(); HRESULT h; CVertexBuffer *vb = m_pBuffer; int i; m_RS.m_StatsNumRendOceanSectors = 0; ushort *saveInds = r->m_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; fMaxDist = (float)((int)(fMaxDist / fSize + 1.0f)) * fSize; 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()); } int nCurSize; bool bCurNeedBuffer = false; h = dv->SetVertexDeclaration((LPDIRECT3DVERTEXDECLARATION9)m_VertDecl); 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) { pTZ = (float *)GetVBPtr((OCEANGRID+1)*(OCEANGRID+1)); 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++; UnlockVBPtr(); if (nFloats) { IDirect3DVertexBuffer9 *pVB = (IDirect3DVertexBuffer9 *)m_pVertsPool[m_nCurVB]; h = dv->SetStreamSource(1, pVB, 0, sizeof(struct_VERTEX_FORMAT_TEX2F)); if (!bCurNeedBuffer) { h = dv->SetVertexDeclaration((LPDIRECT3DVERTEXDECLARATION9)m_VertDeclHeightSplash); bCurNeedBuffer = true; } } else if (bCurNeedBuffer) { h = dv->SetStreamSource(1, NULL, 0, 0); h = dv->SetVertexDeclaration((LPDIRECT3DVERTEXDECLARATION9)m_VertDecl); bCurNeedBuffer = false; } float param[4]; param[0] = os->x; param[1] = os->y; param[2] = 0; param[3] = 0; CCGVProgram_D3D *vpD3D = (CCGVProgram_D3D *)vp; SCGBind *pBind = vpD3D->mfGetParameterBind("PosOffset"); if (pBind) vpD3D->mfParameter4f(pBind, param); pBind = vpD3D->mfGetParameterBind("PosScale"); param[0] = fSize; param[1] = fSize; param[2] = fHeightScale; param[3] = 1; if (pBind) vpD3D->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]); } } void CREOcean::mfDrawOceanScreenLod() { } bool CREOcean::mfDraw(SShader *ef, SShaderPass *sfm) { double time0 = 0; ticks(time0); if (!m_pVertsPool[0]) InitVB(); if (!m_pBuffer) m_pBuffer = gRenDev->CreateBuffer((OCEANGRID+1)*(OCEANGRID+1), VERTEX_FORMAT_P3F_N, "Ocean", true); 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) { CVertexBuffer *vb = m_pBuffer; for (int i=0; im_VS[i].m_bLocked) gcpRendD3D->UnlockBuffer(vb, i); } LPDIRECT3DDEVICE9 dv = gcpRendD3D->mfGetD3DDevice(); HRESULT h; h = dv->SetStreamSource(0, (IDirect3DVertexBuffer9 *)vb->m_VS[VSF_GENERAL].m_VertBuf.m_pPtr, 0, m_VertexSize[vb->m_vertexformat]); 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 };