// NeL - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "std3d.h" #include "nel/3d/zone_symmetrisation.h" #include "nel/3d/zone.h" #include "nel/3d/tile_bank.h" using namespace std; using namespace NLMISC; namespace NL3D { // *************************************************************************** CZoneSymmetrisation::CZoneSymmetrisation() { } // *************************************************************************** /* Bit field 1-0 : state layer 0 3-2 : state layer 1 5-4 : state layer 2 7-6 : border state 9-8 : oriented border state 10 : corner flag */ // *************************************************************************** CZoneSymmetrisation::TState CZoneSymmetrisation::getTileState (uint patch, uint tile, uint layer) const { nlassert (layer<5); return (TState)((_TilesLayerStates[patch][tile]>>(layer*2))&0x3); } // *************************************************************************** CZoneSymmetrisation::TState CZoneSymmetrisation::getTileBorderState (uint patch, uint tile) const { return getTileState (patch, tile, 3); } // *************************************************************************** CZoneSymmetrisation::TState CZoneSymmetrisation::getOrientedTileBorderState (uint patch, uint tile) const { return getTileState (patch, tile, 4); } // *************************************************************************** bool CZoneSymmetrisation::getOrientedTileCorner (uint patch, uint tile) { return ( ( _TilesLayerStates[patch][tile] >> 10 ) & 1 ) != 0; } // *************************************************************************** void CZoneSymmetrisation::setTileState (uint patch, uint tile, uint layer, TState state) { uint16 &ref = _TilesLayerStates[patch][tile]; ref &= ~(3<<(layer*2)); ref |= ((uint16)state)<<(layer*2); } // *************************************************************************** void CZoneSymmetrisation::setTileBorderState (uint patch, uint tile, TState state) { setTileState (patch, tile, 3, state); } // *************************************************************************** void CZoneSymmetrisation::setOrientedTileBorderState (uint patch, uint tile, TState state) { setTileState (patch, tile, 4, state); } // *************************************************************************** void CZoneSymmetrisation::setOrientedTileCorner (uint patch, uint tile, bool corner) { uint16 &ref = _TilesLayerStates[patch][tile]; ref &= ~(1<<10); ref |= ((uint16)corner)<<(10); } // *************************************************************************** /* REMARKS: - I call "isotile" the set of connected tiles in the zone using the same tileset and a continus rotation value. This method determines the state of each tile in the zone. This state will be used to transform the tile during symmetrisation of the zone. The state can be Regular, Goofy or Nothing. If the state is Nothing, this tile is in an "isotile" that is not connected to a zone border. No matter the method you use to tranform it, it will not influence the neighbor zones. If the state is Regular, the rotation of this tile after symmetrisation will by given by this formula : tilerot = (4-tilerot)&3 If the state is Goofy, the rotation of this tile after symmetrisation will by given by this formula : tilerot = (4-tilerot+2)&3 - Getting the state of the tile: - A) We flag all the zone patches as Nothing. - B) We need to select patches having an open edge on the zone border. To do so, we use the snapCell and weldThreshold parameters. - C) Then, for each patches on the zone border, we need to know if they are Goofy or Regular. Y /\ | | 0 3 | ***** | * * This patch is regular | * * | ***** | 1 2 | | 2 1 | ***** | * * This patch is regular | * * | ***** | 3 0 | | 3 2 | ***** | * * This patch is goofy | * * | ***** | 0 1 | | 1 4 | ***** | * * This patch is goofy | * * | ***** | 2 3 -----------------------------------------> X - D) We flag each tiles as Nothing - E) We flag each tile on an opened edge using this formula: - If the patch is not Nothing do - tileIsGoofy = (patchIsGoofy) XOR ((tileRotation&1) != 0) - F) Then, we propagate the tile A flag across the tiles in the zone following this rules: - If A is not Nothing do - For each neighbor B of A do - If B is different from A do - If A is Regular (res Goofy), and B is Nothing, B will be Regular (res Goofy) - If A is Regular (res Goofy), and B is Goofy (res Regular), -> Error - Propagate B */ bool CZoneSymmetrisation::build (const std::vector &patchInfo, float snapCell, float weldThreshold, const CTileBank &bank, CError &errorDesc, const NLMISC::CMatrix &toOriginalSpace) { // * Clear errors errorDesc.Errors.clear (); // * Build the patches state // A) Resize arrays _TilesLayerStates.resize (patchInfo.size ()); // D) Resize the tile array uint i; for (i=0; i=0) && (remainder= bank.getTileSetCount())) { nlwarning("CZoneSymmetrisation::setTileState : tile %d has an unknown tileSet (%d)", tile, tileSet); return false; } // Set it only if not oriented if (!bank.getTileSet (tileSet)->getOriented ()) { // Set the tile state setTileState (patchId, currentTile, layer, state); } } } // Next tile currentTile += delta; } } } } return true; } // *************************************************************************** bool CZoneSymmetrisation::setOrientedTileState (const NL3D::CPatchInfo &patch, uint patchId, float snapCell, float weldThreshold, TState &state, const NLMISC::CMatrix &toOriginalSpace, const CTileBank &bank) { // Edges state TState edgesState[4] = { Nothing, Nothing, Nothing, Nothing }; // Vertices position sint32 vertPosU[4]; sint32 vertPosV[4]; // For each vertices uint i; for (i=0; i<4; i++) { // Snap the vertex CVector original = toOriginalSpace * patch.Patch.Vertices[i]; float valueU = original.x; float valueV = original.y; // Snap on U if (snapOnGrid (valueU, snapCell, weldThreshold)) vertPosU[i] = (sint32)((valueU+0.5f) / snapCell); else vertPosU[i] = 0x80000000; // Snap on V if (snapOnGrid (valueV, snapCell, weldThreshold)) vertPosV[i] = (sint32)((valueV+0.5f) / snapCell); else vertPosV[i] = 0x80000000; } // Patch flags bool regular = false; bool goofy = false; bool EdgeSnaped[4] = { false, false, false, false }; // For each edges for (i=0; i<4; i++) { // Vertex snapped and align on a common axis ? if ( ((uint32) vertPosU[i] != 0x80000000) || ((uint32) vertPosV[i] != 0x80000000) ) { // Snapped on U or V ? bool snapU = (vertPosU[i] == vertPosU[(i+1)&3]) && ((uint32) vertPosU[i] != 0x80000000); bool snapV = (vertPosV[i] == vertPosV[(i+1)&3]) && ((uint32) vertPosV[i] != 0x80000000); // If snapped on one, continue if (snapU || snapV) { // If snap on the both, error if (snapU && snapV) return false; // Is this edge Regular or Goofy ? edgesState[i] = (i&1)?Goofy:Regular; // Flag the patch if (edgesState[i] == Regular) regular = true; else goofy = true; // Edge snaped EdgeSnaped[i] = true; } } } // * Set the tiles // For each edges for (i=0; i<4; i++) { // Edge snapped ? if (EdgeSnaped[i]) { // For each tiles uint tileCount = ((i&1)!=0)?patch.OrderS:patch.OrderT; sint currentTile; sint delta; switch (i) { case 0: currentTile = 0; delta = patch.OrderS; break; case 1: currentTile = patch.OrderS*(patch.OrderT-1); delta = 1; break; case 2: currentTile = patch.OrderS-1; delta = patch.OrderS; break; case 3: currentTile = 0; delta = 1; break; default: currentTile = 0; delta = 1; break; } uint j; for (j=0; j= bank.getTileSetCount())) { nlwarning("CZoneSymmetrisation::setOrientedTileState : tile %d has an unknown tileSet (%d)", tile, tileSet); return false; } // Set it only if oriented if (bank.getTileSet (tileSet)->getOriented ()) { setTileState (patchId, currentTile, layer, edgesState[i]); } } } // Next tile currentTile += delta; } } } // For each corners for (i=0; i<4; i++) { // Corner snapped ? uint next = (i+1)&3; if (EdgeSnaped[i] && EdgeSnaped[next]) { // Flag tile as corner switch (i) { case 0: setOrientedTileCorner (patchId, patch.OrderS*(patch.OrderT-1), true); break; case 1: setOrientedTileCorner (patchId, patch.OrderS*patch.OrderT-1, true); break; case 2: setOrientedTileCorner (patchId, patch.OrderS-1, true); break; case 3: setOrientedTileCorner (patchId, 0, true); break; } } } return true; } // *************************************************************************** CVector2f st2uv (sint s, sint t, const CPatchInfo &patch) { return CVector2f ((((float)s)+0.5f)/(float)patch.OrderS, (((float)t)+0.5f)/(float)patch.OrderT); } // *************************************************************************** void uv2st (const CVector2f &in, sint &s, sint &t, const CPatchInfo &patch) { s = (sint)(in.x*(float)patch.OrderS); t = (sint)(in.y*(float)patch.OrderT); } // *************************************************************************** class CFillStackNode { public: CFillStackNode (uint16 patch, uint16 s, uint16 t, uint8 rotate, CZoneSymmetrisation::TState state) { Patch = patch; S = s; T = t; Edge = 0; Rotate = rotate; State = state; }; uint16 S; uint16 T; uint16 Patch; uint8 Edge; uint8 Rotate; CZoneSymmetrisation::TState State; }; // *************************************************************************** bool CZoneSymmetrisation::propagateTileState (uint patch, uint s, uint t, const std::vector &patchInfo, const CTileBank &bank, bool forceRegular) { // For each layer uint layer; for (layer=0; layer<3; layer++) { // Get the patch ptr const CPatchInfo *currentPatchPtr = &(patchInfo[patch]); // Get the tile index uint tile = s+t*currentPatchPtr->OrderS; // Get the tiles set used here uint tileIndex = currentPatchPtr->Tiles[tile].Tile[layer]; if (tileIndex != NL_TILE_ELM_LAYER_EMPTY) { // Valid tile number ? if (tileIndex >= (uint)bank.getTileCount ()) { nlwarning ("CZoneSymmetrisation::propagateTileState: Invalid tile index"); return false; } // Get the tile set used by this layer int tileSetToPropagate; int number; CTileBank::TTileType type; bank.getTileXRef (tileIndex, tileSetToPropagate, number, type); if ((tileSetToPropagate < 0) || (tileSetToPropagate >= bank.getTileSetCount())) { nlwarning("CZoneSymmetrisation::propagateTileState: tile %d has an unknown tileSet (%d)", tileIndex, tileSetToPropagate); } else { // Oriented ? bool oriented = bank.getTileSet (tileSetToPropagate)->getOriented (); // If oriented, must not be a corner if (!(oriented && getOrientedTileCorner (patch, tile))) { // Current node CFillStackNode currentNode (patch, s, t, currentPatchPtr->Tiles[tile].getTileOrient(layer), getTileState (patch, tile, layer)); // Propagate non-Nothing tiles if ( (!forceRegular && (currentNode.State != Nothing)) || (forceRegular && (currentNode.State == Nothing)) ) { // Force to Regular ? if (forceRegular) { setTileState (patch, tile, layer, Regular); currentNode.State = Regular; } // Fill stack vector stack; stack.push_back (currentNode); // While people in the stack while (!stack.empty ()) { // Pop last element currentNode = stack.back (); stack.pop_back (); do { // Set current patch pointer currentPatchPtr = &(patchInfo[currentNode.Patch]); // Get neighbor CFillStackNode neighborNode (currentNode.Patch, currentNode.S, currentNode.T, currentNode.Rotate, currentNode.State); switch (currentNode.Edge) { case 0: neighborNode.S--; break; case 1: neighborNode.T++; break; case 2: neighborNode.S++; break; case 3: neighborNode.T--; break; } // Is still in patch ? if ( (neighborNode.S>=patchInfo[currentNode.Patch].OrderS) || (neighborNode.T>=patchInfo[currentNode.Patch].OrderT) ) { // No, found new patch uint position; switch (currentNode.Edge) { case 0: position = neighborNode.T; break; case 1: position = neighborNode.S; break; case 2: position = patchInfo[currentNode.Patch].OrderT - neighborNode.T - 1; break; case 3: position = patchInfo[currentNode.Patch].OrderS - neighborNode.S - 1; break; default: position = 0; break; } // Get next patch uint patchOut; sint sOut; sint tOut; if (patchInfo[currentNode.Patch].getNeighborTile (currentNode.Patch, currentNode.Edge, position, patchOut, sOut, tOut, patchInfo)) { // Should be another patch nlassert (patchOut != currentNode.Patch); // Get patch id neighborNode.Patch = patchOut; // Coordinate must be IN the patch nlassert (sOut >= 0); nlassert (tOut >= 0); nlassert (sOut < patchInfo[neighborNode.Patch].OrderS); nlassert (tOut < patchInfo[neighborNode.Patch].OrderT); // Copy it neighborNode.S = sOut; neighborNode.T = tOut; // Find neighbor const CPatchInfo::CBindInfo &neighborBindInfo = patchInfo[currentNode.Patch].BindEdges[currentNode.Edge]; uint edgePatch; for (edgePatch=0; edgePatch<(uint)neighborBindInfo.NPatchs; edgePatch++) { if (neighborBindInfo.Next[edgePatch] == neighborNode.Patch) break; } // Must find one patch nlassert (edgePatch<(uint)neighborBindInfo.NPatchs); // Rotation neighborNode.Rotate = (currentNode.Rotate + 2 + neighborBindInfo.Edge[edgePatch] - currentNode.Edge) & 3; // Toggle the state ? if ((neighborNode.Rotate ^ currentNode.Rotate) & 1) { // Yes neighborNode.State = (neighborNode.State == Regular) ? Goofy : Regular; } } else { // No propagation, continue currentNode.Edge++; continue; } } // Neighbor patch const CPatchInfo *neighborPatchPtr = &(patchInfo[neighborNode.Patch]); // Get the tile index uint neighborTile = neighborNode.S+neighborNode.T*neighborPatchPtr->OrderS; // Look for the same tile set in the new tile uint neighborLayer; for (neighborLayer=0; neighborLayer<3; neighborLayer++) { // Get the tile index uint neighborTileIndex = neighborPatchPtr->Tiles[neighborTile].Tile[neighborLayer]; if (neighborTileIndex != NL_TILE_ELM_LAYER_EMPTY) { // Valid tile number ? if (neighborTileIndex >= (uint)bank.getTileCount ()) { nlwarning ("CZoneSymmetrisation::propagateTileState: Invalid tile index"); return false; } // Get tileset int neighborTileSet; int neighborNumber; CTileBank::TTileType neighborType; bank.getTileXRef (neighborTileIndex, neighborTileSet, neighborNumber, neighborType); // Same tileset ? Stop! if ( (neighborTileSet == tileSetToPropagate) && (neighborNode.Rotate == neighborPatchPtr->Tiles[neighborTile].getTileOrient(neighborLayer)) ) break; } } // Found ? if (neighborLayer<3) { // If oriented, must not be a corner if (!(oriented && getOrientedTileCorner (neighborNode.Patch, neighborTile))) { // Propagate in the new node ? TState neighborState = getTileState (neighborNode.Patch, neighborTile, neighborLayer); if (neighborState == Nothing) { // Set the state setTileState (neighborNode.Patch, neighborTile, neighborLayer, neighborNode.State); // Stack current node if some neighbor left to visit if (currentNode.Edge < 3) { currentNode.Edge++; stack.push_back (currentNode); } // Continue with the new node currentNode = neighborNode; } else if (neighborState != neighborNode.State) { // Error, same tile but not same state // nlwarning ("CZoneSymmetrisation::propagateTileState: error, find same iso surfaces with different state."); // No propagation, continue currentNode.Edge++; } else { // No propagation, continue currentNode.Edge++; } } else { // No propagation, continue currentNode.Edge++; } } else // No propagation, continue currentNode.Edge++; } while (currentNode.Edge<4); } } } } } } return true; } // *************************************************************************** } // NL3D