using CSNetwork.GPDataType; using NUnit.Framework; using System.Collections.Generic; using System.Linq; using UnityEngine; using ARectF = BrewMonster.Scripts.ARect; using ARectI = BrewMonster.Scripts.ARect; using BlockArray = System.Collections.Generic.List; using WORD = System.UInt16; namespace BrewMonster.Scripts.World { public class A3DTerrain2Block { // Trerrain2 vertex format when use lightmap public struct A3DTRN2VERTEX1 { public float vPosX; // PositionX public float vPosY; // PositionY public float vPosZ; // PositionZ public float vNormalX; // NormalX public float vNormalY; // NormalY public float vNormalZ; // NormalZ public float u1, v1; // Texture coordinates project on xz public float u2, v2; // Texture coordinates project on xy public float u3, v3; // Texture coordinates project on yz public float u4, v4; // Texture coordinate of layer mask }; // Terrain2 vertex format when use vertex-light (not lightmap) public struct A3DTRN2VERTEX2 { public float vPosX; // Position X public float vPosY; // Position Y public float vPosZ; // Position Z public uint dwDiffuse; // Diffuse color public uint dwSpecular; // Specular color public float u1, v1; // Texture coordinates project on xz public float u2, v2; // Texture coordinates project on xy public float u3, v3; // Texture coordinates project on yz public float u4, v4; // Texture coordinate of layer mask }; public bool m_bDataLoaded = false; // Data loaded flag public bool IsDataLoaded() { return m_bDataLoaded; } public uint m_dwBlockFlags; // Block flags public A3DAABB m_aabbBlock; // Block postion and size in whole world public A3DTRN2VERTEX1[] m_aVertices1; // Vertex buffer, ps version public A3DTRN2VERTEX2[] m_aVertices2; // Vertex buffer, non-ps version public A3DTerrain2 m_pTerrain; // Terrain object public int m_iBlockGrid; // Each block has m_iBlockGrid * m_iBlockGrid terrain grids // Get block's position and size in whole world public A3DAABB GetBlockAABB() { return m_aabbBlock; } // Get flag for this whole block unable for GetPosHeight public bool IsNoPosHeight() { return (m_dwBlockFlags & Block_flags_masks.T2BKFLAG_NOPOSHEIGHT) != 0; } // Get vertex public A3DVECTOR3 GetVertexPos(int n) { if (m_aVertices1 != null && m_aVertices1.Length > n) return new A3DVECTOR3(m_aVertices1[n].vPosX, m_aVertices1[n].vPosY, m_aVertices1[n].vPosZ); else { //ASSERT(m_aVertices2); return new A3DVECTOR3(m_aVertices2[n].vPosX, m_aVertices2[n].vPosY, m_aVertices2[n].vPosZ); } } /* Copy block vertices to destination buffer. This function works like BitBlt. Copy vertex positions in a rectangle grid area to destination buffer, every vertex is like a pixel in BitBlt; pDestBuf: destination vertex buffer iWid, iHei: vertex rectangle area's size iDestPitch: destination vertex buffer pitch in A3DVECTOR3 sx, sy: grid rectangle area's left-top corner in block */ public bool CopyVertexPos(ref A3DVECTOR3[] pDestBuf, int iWid, int iHei, int iDestPitch, int sx, int sy) { WORD[] aIndexMaps = m_pTerrain.GetLODManager().GetIndexMaps(); int pDestLine = 0; int iSrcLine = sy * (m_iBlockGrid + 1) + sx; if (m_pTerrain.UseLightmapTech()) { //ASSERT(m_aVertices1); for (int i = 0; i < iHei; i++) { int destIndex = pDestLine; int iSrc = iSrcLine; pDestLine += iDestPitch; iSrcLine += m_iBlockGrid + 1; for (int j = 0; j < iWid; j++, destIndex++, iSrc++) { // Note: here we map index A3DTRN2VERTEX1 pSrc = m_aVertices1[aIndexMaps[iSrc]]; pDestBuf[destIndex].x = pSrc.vPosX; pDestBuf[destIndex].y = pSrc.vPosY; pDestBuf[destIndex].z = pSrc.vPosZ; } } } else { for (int i = 0; i < iHei; i++) { int pDest = pDestLine; int iSrc = iSrcLine; pDestLine += iDestPitch; iSrcLine += m_iBlockGrid + 1; for (int j = 0; j < iWid; j++, pDest++, iSrc++) { var pSrc = m_aVertices2[aIndexMaps[iSrc]]; pDestBuf[pDest].x = pSrc.vPosX; pDestBuf[pDest].y = pSrc.vPosY; pDestBuf[pDest].z = pSrc.vPosZ; } } } return true; } /* Copy grid faces to destination buffer. This function works like BitBlt. Copy face in a rectangle grid area to destination buffer, every face is like a pixel in BitBlt; pDestVert: destination vertex buffer pDestIdx: destination index buffer iWid, iHei: grid rectangle area's size iDstVertPitch: destination vertex buffer pitch in A3DVECTOR3 iBaseVert: Index of left-top corner vertex in vertex buffer sx, sy: grid rectangle area's left-top corner in block */ public bool CopyFaces(ref A3DVECTOR3[] pDestVert, ref WORD[] pDestIdx, int iWid, int iHei, int iDstVertPitch, int iBaseVert, int sx, int sy) { // Copy vertices at first. Notice the difference of iWid and iHei in this // function and in CopyVertexPos() CopyVertexPos(ref pDestVert, iWid + 1, iHei + 1, iDstVertPitch, sx, sy); // Build indices int pIndices = 0; for (int i = 0; i < iHei; i++) { WORD v0 = (WORD)iBaseVert; iBaseVert += iDstVertPitch; for (int j = 0; j < iWid; j++, pIndices += 6, v0++) { // v3 v0----v1 // | \ \ | // | \ \ | // | \ \ | // | \ \ | // v5---v4 v2 pDestIdx[pIndices] = v0; pDestIdx[pIndices + 1] = (WORD)(v0 + 1); pDestIdx[pIndices + 2] = (WORD)(v0 + iDstVertPitch + 1); pDestIdx[pIndices + 3] = v0; pDestIdx[pIndices + 4] = (WORD)(v0 + iDstVertPitch + 1); pDestIdx[pIndices + 5] = (WORD)(v0 + iDstVertPitch); } } // If this area contain block's right-top corner grid or left-bottom // corner grid, then we have to adjust index so that they are in // rendering order if (sx == 0 && sy + iHei == m_iBlockGrid) { // Contain left-bottom corner grid // v0----v1 // | / | // | / | // | / | // | / | // v2----v3 int offer = (iHei - 1) * iWid * 6; WORD v0 = pDestIdx[offer]; WORD v1 = pDestIdx[offer + 1]; WORD v2 = pDestIdx[offer + 5]; WORD v3 = pDestIdx[offer + 2]; pDestIdx[offer] = v0; pDestIdx[offer + 1] = v1; pDestIdx[offer + 2] = v2; pDestIdx[offer + 3] = v1; pDestIdx[offer + 4] = v3; pDestIdx[offer + 5] = v2; } if (sx + iWid == m_iBlockGrid && sy == 0) { // Contain right-top corner grid // v0----v1 // | / | // | / | // | / | // | / | // v2----v3 int offer = (iWid - 1) * 6; WORD v0 = pDestIdx[offer]; WORD v1 = pDestIdx[offer + 1]; WORD v2 = pDestIdx[offer + 5]; WORD v3 = pDestIdx[offer + 2]; pDestIdx[offer + 0] = v0; pDestIdx[offer + 1] = v1; pDestIdx[offer + 2] = v2; pDestIdx[offer + 3] = v1; pDestIdx[offer + 4] = v3; pDestIdx[offer + 5] = v2; } return true; } } public class A3DTerrain2 { public ACTBLOCKS m_pCurActBlocks; // Currently active block array public ARectF m_rcTerrain; // Whole terrain area in logic unit (metres) public float m_fBlockSize = 0; // Block size (on x and z axis) in logic unit (metres) public int m_iBlockGrid = 0; // Each block has m_iBlockGrid * m_iBlockGrid terrain grids public float m_fGridSize; // Terrain grid size (on x and z axis) in logic unit (metres) public A3DTerrain2LOD m_pLODMan; // LOD manager public int m_iNumActBlockRow; // Row and column number of active blocks public int m_iNumAllBlockRow = 0; // Row number of all blocks public int m_iNumAllBlockCol = 0; // Column number of all blocks public bool m_bVertexLight = false; // true, force to use vertex light rather than lightmap public List m_lstMesh; // Check lightmap or vertex-light is used public bool UseLightmapTech() { return !m_bVertexLight; } public A3DTerrain2LOD GetLODManager() { return m_pLODMan; } // Get grid faces of specified area public bool GetFacesOfArea(A3DVECTOR3 vCenter, int iGridWid, int iGridLen, ref A3DVECTOR3[] pVertBuf, ref WORD[] pIdxBuf) { //if (m_pCurActBlocks.rcArea.IsEmpty()) // return false; if(m_lstMesh == null || m_lstMesh.Count == 0) { return false; } float halfW = (iGridWid * m_fGridSize) / 2f; float halfL = (iGridLen * m_fGridSize) / 2f; Bounds area = new Bounds(EC_Utility.ToVector3(vCenter), new Vector3(halfW * 2, 1000f, halfL * 2)); float fInvGridSize = 1.0f / m_fGridSize; ARectI rcGrid = new ARectI(); rcGrid.left = (int)((vCenter.x - m_rcTerrain.left) * fInvGridSize) - (iGridWid >> 1); rcGrid.top = (int)((m_rcTerrain.top - vCenter.z) * fInvGridSize) - (iGridLen >> 1); rcGrid.right = rcGrid.left + iGridWid; rcGrid.bottom = rcGrid.top + iGridLen; return GetFacesOfArea(rcGrid, ref pVertBuf, ref pIdxBuf); } public bool GetFacesOfArea(ARectI rcGridArea, ref A3DVECTOR3[] pVertBuf, ref WORD[] pIdxBuf) { if (m_pCurActBlocks.rcArea.IsEmpty()) return false; float fInvGridSize = 1.0f / m_fGridSize; ARectI rcGrid = rcGridArea; int iMaxGrid = m_iNumAllBlockCol * m_iBlockGrid; AAssist.a_Clamp(ref rcGrid.left, 0, iMaxGrid); AAssist.a_Clamp(ref rcGrid.right, 0, iMaxGrid); iMaxGrid = m_iNumAllBlockRow * m_iBlockGrid; AAssist.a_Clamp(ref rcGrid.top, 0, iMaxGrid); AAssist.a_Clamp(ref rcGrid.bottom, 0, iMaxGrid); if (rcGrid.IsEmpty()) return false; ARectI rcBlock = new ARectI(); rcBlock.left = rcGrid.left / m_iBlockGrid; rcBlock.top = rcGrid.top / m_iBlockGrid; rcBlock.right = (rcGrid.right - 1) / m_iBlockGrid; rcBlock.bottom = (rcGrid.bottom - 1) / m_iBlockGrid; int r, c; // Ensure all blocks in active area and have been loaded for (r = rcBlock.top; r <= rcBlock.bottom; r++) { for (c = rcBlock.left; c <= rcBlock.right; c++) { if (!m_pCurActBlocks.rcArea.PtInRect(c, r) || m_pCurActBlocks.GetBlock(r, c, false) == null) return false; } } int pDestIdx = 0; A3DVECTOR3[] pVertDest = new A3DVECTOR3[0]; WORD[] pIdxTemp = new WORD[0]; for (r = rcBlock.top; r <= rcBlock.bottom; r++) { ARectI rect = new ARectI(); rect.left = rcBlock.left * m_iBlockGrid; rect.top = r * m_iBlockGrid; rect.right = rect.left + m_iBlockGrid; rect.bottom = rect.top + m_iBlockGrid; for (c = rcBlock.left; c <= rcBlock.right; c++) { A3DTerrain2Block pBlock = m_pCurActBlocks.GetBlock(r, c, false); //ASSERT(pBlock); ARectI rc = rcGrid & rect; //ASSERT(!rc.IsEmpty()); int dx = rc.left - rcGrid.left; int dy = rc.top - rcGrid.top; int iBaseVert = dy * (rcGrid.Width() + 1) + dx; pVertDest = pVertBuf.Skip(iBaseVert).ToArray(); pIdxTemp = pIdxBuf.Skip(pDestIdx).ToArray(); int sx = rc.left - rect.left; int sy = rc.top - rect.top; pBlock.CopyFaces(ref pVertDest, ref pIdxTemp, rc.Width(), rc.Height(), rcGrid.Width() + 1, iBaseVert, sx, sy); for (int i = iBaseVert; i < pVertBuf.Length; i++) { pVertBuf[i] = pVertDest[i - iBaseVert]; } for (int i = 0; i < pIdxBuf.Length; i++) { pIdxBuf[i] = pIdxTemp[i - pDestIdx]; } pDestIdx += rc.Width() * rc.Height() * 6; rect.left += m_iBlockGrid; rect.right += m_iBlockGrid; } } return true; } // Get height and normal of specified position public float GetPosHeight(A3DVECTOR3 vPos, ref A3DVECTOR3 pvNormal) { // Give a default value to normal if (pvNormal != null) pvNormal.Clear(); if (m_pCurActBlocks.rcArea.IsEmpty()) return 0.0f; // Currenly active area AABB ARectF rcActive = new ARectF(); rcActive.left = m_rcTerrain.left + m_pCurActBlocks.rcArea.left * m_fBlockSize; rcActive.top = m_rcTerrain.top - m_pCurActBlocks.rcArea.top * m_fBlockSize; rcActive.right = rcActive.left + m_pCurActBlocks.rcArea.Width() * m_fBlockSize; rcActive.bottom = rcActive.top - m_pCurActBlocks.rcArea.Height() * m_fBlockSize; if (vPos.x < rcActive.left || vPos.x > rcActive.right || vPos.z > rcActive.top || vPos.z < rcActive.bottom) return 0.0f; // Get block this position is in float fInvBlockSize = 1.0f / m_fBlockSize; int iCol = (int)((vPos.x - rcActive.left) * fInvBlockSize); int iRow = (int)(-(vPos.z - rcActive.top) * fInvBlockSize); AAssist.a_Clamp(ref iCol, 0, m_pCurActBlocks.rcArea.Width() - 1); AAssist.a_Clamp(ref iRow, 0, m_pCurActBlocks.rcArea.Height() - 1); int iBlock = iRow * m_pCurActBlocks.rcArea.Width() + iCol; A3DTerrain2Block pBlock = m_pCurActBlocks.aBlocks[iBlock]; if (pBlock == null || !pBlock.IsDataLoaded()) return 0.0f; // If whole block is a hole, return as if there is no block here if (pBlock.IsNoPosHeight()) return 0.0f; // Get block's AABB A3DAABB aabb = pBlock.GetBlockAABB(); // Get grid this position is in float fInvGridSize = 1.0f / m_fGridSize; iCol = (int)((vPos.x - aabb.Mins.x) * fInvGridSize); iRow = (int)(-(vPos.z - aabb.Maxs.z) * fInvGridSize); AAssist.a_Clamp(ref iCol, 0, m_iBlockGrid - 1); AAssist.a_Clamp(ref iRow, 0, m_iBlockGrid - 1); int iGrid = iRow * m_iBlockGrid + iCol; GRID Grid = m_pLODMan.GetGrids()[iGrid]; A3DVECTOR3 v0 = pBlock.GetVertexPos(Grid.v0); A3DVECTOR3 v1 = pBlock.GetVertexPos(Grid.v1); A3DVECTOR3 v2 = pBlock.GetVertexPos(Grid.v2); A3DVECTOR3 v3 = pBlock.GetVertexPos(Grid.v3); A3DVECTOR3 v4 = pBlock.GetVertexPos(Grid.v4); A3DVECTOR3 v5 = pBlock.GetVertexPos(Grid.v5); A3DVECTOR3 vDest; float dx, dz; if (iGrid == m_iBlockGrid - 1 || iGrid == m_iBlockGrid * (m_iBlockGrid - 1)) { // The grid is on right-top corner or left-bottom corner dx = vPos.x - v2.x; dz = vPos.z - v2.z; if (dx > dz) { vDest = v5 + (v4 - v5) * (dx / (v4.x - v5.x)); vDest += (v3 - v4) * (dz / (v3.z - v4.z)); if (pvNormal != null) { pvNormal = A3DVECTOR3.CrossProduct(v4 - v3, v5 - v3); pvNormal.Normalize(); } } else { vDest = v2 + (v0 - v2) * (dz / (v0.z - v2.z)); vDest += (v1 - v0) * (dx / (v1.x - v0.x)); if (pvNormal != null) { pvNormal = A3DVECTOR3.CrossProduct(v1 - v0, v2 - v0); pvNormal.Normalize(); } } } else { dx = vPos.x - v0.x; dz = vPos.z - v0.z; if (dx > -dz) { vDest = v0 + (v1 - v0) * (dx / (v1.x - v0.x)); vDest += (v2 - v1) * (dz / (v2.z - v1.z)); if (pvNormal != null) { pvNormal = A3DVECTOR3.CrossProduct(v1 - v0, v2 - v0); pvNormal.Normalize(); } } else { vDest = v3 + (v5 - v3) * (dz / (v5.z - v3.z)); vDest += (v4 - v5) * (dx / (v4.x - v5.x)); if (pvNormal != null) { pvNormal = A3DVECTOR3.CrossProduct(v4 - v3, v5 - v3); pvNormal.Normalize(); } } } return vDest.y; } } // Active blocks public struct ACTBLOCKS { public ARectI rcArea; // Active area represented in blocks public BlockArray aBlocks; // Active block array public ACTBLOCKS(ARectI rcArea, BlockArray aBlocks) { this.rcArea = rcArea; this.aBlocks = aBlocks; } // Get block object at specified row, column. public A3DTerrain2Block GetBlock(int r, int c, bool bClear) { //ASSERT(rcArea.PtInRect(c, r)); int iIndex = GetBlockIndex(r, c); A3DTerrain2Block pBlock = aBlocks[iIndex]; if (bClear) aBlocks[iIndex] = null; return pBlock; } // Set block object at specified row, column void SetBlock(int r, int c, A3DTerrain2Block pBlock) { //ASSERT(rcArea.PtInRect(c, r)); int iIndex = GetBlockIndex(r, c); aBlocks[iIndex] = pBlock; } // Get block index in aBlocks int GetBlockIndex(int r, int c) { return (r - rcArea.top) * rcArea.Width() + c - rcArea.left; } }; // Block flags & masks public class Block_flags_masks { public static uint T2BKFLAG_DEFAULT = 0x00, // The block flag default value T2BKFLAG_NORENDER = 0x01, // The whole block is not rendered T2BKFLAG_NOTRACE = 0x02, // The whole block is out of RayTrace T2BKFLAG_NOPOSHEIGHT = 0x04, // The whole block is unable to GetPosHeight T2BKFLAG_NORENDERWITHWATER = 0x08; // The whole block is not rendered with water }; // Vertex indices in a terrain grid public struct GRID { public int v0, v1, v2; // Face 1 public int v3, v4, v5; // Face 2 }; }