/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /* * Copyright (C) 2006-2010 - Frictional Games * * This file is part of HPL1 Engine. */ #include "hpl1/engine/physics/Collider2D.h" #include "hpl1/engine/graphics/Mesh2d.h" #include "hpl1/engine/math/Math.h" #include "hpl1/engine/physics/Body2D.h" #include "hpl1/engine/physics/CollideData2D.h" #include "hpl1/engine/scene/GridMap2D.h" #include "hpl1/engine/scene/TileMap.h" #include "hpl1/engine/scene/World2D.h" #include "hpl1/engine/system/low_level_system.h" namespace hpl { ////////////////////////////////////////////////////////////////////////// // CONSTRUCTORS ////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------- cCollider2D::cCollider2D() { mpWorld = NULL; } //----------------------------------------------------------------------- cCollider2D::~cCollider2D() { } //----------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////////// // PUBLIC METHODS ////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------- tFlag cCollider2D::CollideBody(cBody2D *apBody, cCollideData2D *apData) { tFlag lCollision = eFlagBit_None; cRect2f CollideRect = apBody->GetBoundingBox(); cCollisionMesh2D *pCollMesh = apBody->GetCollisionMesh(); cVector2f vPushVector; cVector2f vLastPushVector; /////// TEST COLLISION WITH TILES // float fTileSize = mpWorld->GetTileMap()->GetTileSize(); // cRect2f TileRect = cRect2f(0, 0, fTileSize, fTileSize); for (int i = 0; i < mpWorld->GetTileMap()->GetTileLayerNum(); i++) { if (mpWorld->GetTileMap()->GetTileLayer(i)->HasCollision() == false) continue; iTileMapIt *pTileIt = mpWorld->GetTileMap()->GetRectIterator(CollideRect, i); while (pTileIt->HasNext()) { cTile *pTile = pTileIt->Next(); // TileRect.x = pTile->GetPosition().x - fTileSize / 2; // TileRect.y = pTile->GetPosition().y - fTileSize / 2; // This can be used for material properties. // cTileDataNormal *pTData = static_cast(pTile->GetTileData()); if (pTile->GetCollisionMesh() == NULL) continue; if (Collide(pCollMesh, pTile->GetCollisionMesh(), vPushVector)) { if (apData) apData->mlstTiles.push_back(cCollidedTile(pTile, i)); cVector3f vD; vD = apBody->GetPosition() - pTile->GetPosition(); if ((vD.x * vPushVector.x + vD.y * vPushVector.y) < 0.0f) vPushVector = vPushVector * -1; cVector3f vPos = apBody->GetPosition(); bool bAlterX = true; bool bAlterY = true; // reverse the latest push, maybe pos here instead? if (lCollision) { // If the current X push is grater use it instead. if (ABS(vPushVector.x) > ABS(vLastPushVector.x)) { vPos.x -= vLastPushVector.x; } else { bAlterX = false; } // If the current Y push is grater use it instead. if (ABS(vPushVector.y) > ABS(vLastPushVector.y)) { vPos.y -= vLastPushVector.y; bAlterY = true; } else { bAlterY = false; } } if (bAlterX) vPos.x += vPushVector.x; if (bAlterY) vPos.y += vPushVector.y; apBody->SetPosition(vPos); apBody->ResetLastPosition(); apBody->UpdateCollisionMesh(); // not really needed until layer change CollideRect = apBody->GetBoundingBox(); lCollision |= eFlagBit_0; vLastPushVector = vPushVector; // break; } } hplDelete(pTileIt); // if(bCollision)break; } /////// TEST COLLISION WITH BODIES iGridMap2DIt *pBodyIt = mpWorld->GetGridMapBodies()->GetRectIterator(CollideRect); while (pBodyIt->HasNext()) { cBody2D *pBody = static_cast(pBodyIt->Next()); if (apBody == pBody) continue; // eFlagBit_0 is probably just temporary. if (pBody->IsActive() && pBody->GetCollideType() & apBody->GetCollideFlag()) { pBody->UpdateCollisionMesh(); // Temp if (Collide(pCollMesh, pBody->GetCollisionMesh(), vPushVector)) { if (apData) apData->mlstBodies.push_back(pBody); cVector3f vD; vD = apBody->GetPosition() - pBody->GetPosition(); if ((vD.x * vPushVector.x + vD.y * vPushVector.y) < 0.0f) vPushVector = vPushVector * -1; cVector3f vPos = apBody->GetPosition(); vPos += vPushVector; apBody->SetPosition(vPos); apBody->ResetLastPosition(); // apBody->UpdateCollisionMesh(); // not really needed until layer change CollideRect = apBody->GetBoundingBox(); lCollision |= eFlagBit_0; break; } } } hplDelete(pBodyIt); /// Do some stuff when colliding if (lCollision) { /*cVector3f vPos = apBody->GetPosition() + vPushVector; apBody->SetPosition(vPos); apBody->ResetLastPosition();*/ mDebug.mvPushVec = vPushVector; // mDebug.mvPushPos = cVector2f(apBody->GetPosition().x,apBody->GetPosition().y); } return lCollision; } //----------------------------------------------------------------------- tFlag cCollider2D::CollideRect(cRect2f &aRect, tFlag alCollideFlags, cCollideData2D *apData) { tFlag lCollision = eFlagBit_None; cRect2f CollideRect = aRect; cCollisionMesh2D *pCollMesh = hplNew(cCollisionMesh2D, ()); pCollMesh->mvPos.resize(4); pCollMesh->mvNormal.resize(4); SetCollideMesh(pCollMesh, aRect); cVector2f vPushVector; cVector2f vLastPushVector; //// Check for all tiles if the flag is set if (alCollideFlags & eFlagBit_0) { // float fTileSize = mpWorld->GetTileMap()->GetTileSize(); // cRect2f TileRect = cRect2f(0, 0, fTileSize, fTileSize); for (int i = 0; i < mpWorld->GetTileMap()->GetTileLayerNum(); i++) { if (mpWorld->GetTileMap()->GetTileLayer(i)->HasCollision() == false) continue; iTileMapIt *pTileIt = mpWorld->GetTileMap()->GetRectIterator(CollideRect, i); while (pTileIt->HasNext()) { cTile *pTile = pTileIt->Next(); // TileRect.x = pTile->GetPosition().x - fTileSize / 2; // TileRect.y = pTile->GetPosition().y - fTileSize / 2; if (pTile->GetCollisionMesh() == NULL) continue; if (apData) apData->mlstTiles.push_back(cCollidedTile(pTile, i)); if (Collide(pCollMesh, pTile->GetCollisionMesh(), vPushVector)) { cVector3f vD; vD = cVector3f(aRect.x, aRect.y, 0) - pTile->GetPosition(); if ((vD.x * vPushVector.x + vD.y * vPushVector.y) < 0.0f) vPushVector = vPushVector * -1; cVector3f vPos; vPos.x = aRect.x; vPos.y = aRect.y; // reverse the latest push, maybe pos here instead? if (lCollision) { vPos -= vLastPushVector; } vPos += vPushVector; aRect.x = vPos.x; aRect.y = vPos.y; SetCollideMesh(pCollMesh, aRect); CollideRect = aRect; lCollision |= eFlagBit_0; vLastPushVector = vPushVector; } } hplDelete(pTileIt); } } iGridMap2DIt *pBodyIt = mpWorld->GetGridMapBodies()->GetRectIterator(CollideRect); while (pBodyIt->HasNext()) { cBody2D *pBody = static_cast(pBodyIt->Next()); if (pBody->IsActive() && pBody->GetCollideType() & alCollideFlags) { if (cMath::BoxCollision(CollideRect, pBody->GetBoundingBox())) { if (Collide(pCollMesh, pBody->GetCollisionMesh(), vPushVector)) { if (apData) apData->mlstBodies.push_back(pBody); lCollision |= pBody->GetCollideType(); /*Perhaps fix the push vector here?*/ } } } } hplDelete(pBodyIt); /// Do some stuff when colliding if (lCollision) { if (apData) apData->mvPushVec = vPushVector; } hplDelete(pCollMesh); return lCollision; } //----------------------------------------------------------------------- tFlag cCollider2D::CollideLine(const cVector2f &avStart, const cVector2f &avEnd, tFlag alCollideFlags, cCollideData2D *apData) { tFlag lCollision = eFlagBit_None; //// Check for all tiles if the flag is set if (alCollideFlags & eFlagBit_0) { /*float fTileSize = */ mpWorld->GetTileMap()->GetTileSize(); for (int i = 0; i < mpWorld->GetTileMap()->GetTileLayerNum(); i++) { if (mpWorld->GetTileMap()->GetTileLayer(i)->HasCollision() == false) continue; iTileMapIt *pTileIt = mpWorld->GetTileMap()->GetLineIterator(avStart, avEnd, i); while (pTileIt->HasNext()) { cTile *pTile = pTileIt->Next(); if (pTile->GetCollisionMesh() == NULL) continue; // Log("Found tile!\n"); if (apData) apData->mlstTiles.push_back(cCollidedTile(pTile, i)); lCollision |= eFlagBit_0; } hplDelete(pTileIt); } } // hplDelete(pCollMesh); return lCollision; } //----------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS ////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------- void cCollider2D::SetCollideMesh(cCollisionMesh2D *apMesh, cRect2f &aRect) { apMesh->mvPos[0] = cVector2f(aRect.x, aRect.y); apMesh->mvPos[1] = cVector2f(aRect.x + aRect.w, aRect.y); apMesh->mvPos[2] = cVector2f(aRect.x + aRect.w, aRect.y + aRect.h); apMesh->mvPos[3] = cVector2f(aRect.x, aRect.y + aRect.w); apMesh->mvNormal[0] = cVector2f(0, -1); apMesh->mvNormal[1] = cVector2f(1, 0); apMesh->mvNormal[2] = cVector2f(0, 1); apMesh->mvNormal[3] = cVector2f(-1, 0); } //----------------------------------------------------------------------- cVector2f FindMTD(cVector2f *pPushVector, int alNumVectors) { cVector2f MTD = pPushVector[0]; float mind2 = pPushVector[0].x * pPushVector[0].x + pPushVector[0].y * pPushVector[0].y; for (int i = 1; i < alNumVectors; i++) { float fD2 = pPushVector[i].x * pPushVector[i].x + pPushVector[i].y * pPushVector[i].y; if (fD2 < mind2) { mind2 = fD2; MTD = pPushVector[i]; } } return MTD; } //----------------------------------------------------------------------- bool cCollider2D::Collide(cCollisionMesh2D *apMeshA, cCollisionMesh2D *apMeshB, cVector2f &avMTD) { cVector2f vAxis[32]; int lAxisNum = 0; // Check separating planes for A for (int i = 0; i < (int)apMeshA->mvNormal.size(); i++) { vAxis[lAxisNum] = apMeshA->mvNormal[i]; if (AxisSeparateMeshes(vAxis[lAxisNum], apMeshA, apMeshB)) { return false; } lAxisNum++; } // Check separating planes for B for (int i = 0; i < (int)apMeshB->mvNormal.size(); i++) { vAxis[lAxisNum] = apMeshB->mvNormal[i]; if (AxisSeparateMeshes(vAxis[lAxisNum], apMeshA, apMeshB)) { return false; } lAxisNum++; } avMTD = FindMTD(vAxis, lAxisNum); return true; } //----------------------------------------------------------------------- bool cCollider2D::AxisSeparateMeshes(cVector2f &avAxis, cCollisionMesh2D *apMeshA, cCollisionMesh2D *apMeshB) { float fMinA, fMaxA; float fMinB, fMaxB; CalculateInterval(avAxis, apMeshA, fMinA, fMaxA); CalculateInterval(avAxis, apMeshB, fMinB, fMaxB); if (fMinA >= fMaxB || fMinB >= fMaxA) return true; float fD0 = fMaxA - fMinB; float fD1 = fMaxB - fMinA; float fDepth = (fD0 < fD1) ? fD0 : fD1; avAxis *= fDepth; return false; } //----------------------------------------------------------------------- void cCollider2D::CalculateInterval(const cVector2f &avAxis, cCollisionMesh2D *apMesh, float &afMin, float &afMax) { float fTemp = avAxis.x * apMesh->mvPos[0].x + avAxis.y * apMesh->mvPos[0].y; afMin = fTemp; afMax = fTemp; for (int i = 1; i < (int)apMesh->mvPos.size(); i++) { fTemp = avAxis.x * apMesh->mvPos[i].x + avAxis.y * apMesh->mvPos[i].y; if (fTemp < afMin) { afMin = fTemp; } else if (fTemp > afMax) { afMax = fTemp; } } } //----------------------------------------------------------------------- } // namespace hpl