* Add PathFinder and friends.

This commit is contained in:
iProgramInCpp
2023-12-06 14:03:20 +02:00
parent 5554fc7c6a
commit 777bd6a3e2
13 changed files with 826 additions and 92 deletions

View File

@@ -155,6 +155,10 @@ add_library(reminecraftpe-core STATIC
world/level/levelgen/synth/Synth.cpp
world/level/levelgen/synth/ImprovedNoise.cpp
world/level/levelgen/synth/PerlinNoise.cpp
world/level/path/Node.cpp
world/level/path/Path.cpp
world/level/path/BinaryHeap.cpp
world/level/path/PathFinder.cpp
world/phys/HitResult.cpp
world/phys/Vec3.cpp
world/phys/AABB.cpp
@@ -185,6 +189,9 @@ add_library(reminecraftpe-core STATIC
world/level/storage/LevelSource.cpp
world/level/storage/MemoryChunkStorage.cpp
world/level/storage/ExternalFileLevelStorageSource.cpp
world/level/path/Node.cpp
world/level/path/Path.cpp
world/level/path/PathFinder.cpp
world/level/levelgen/feature/BirchFeature.cpp
world/level/levelgen/feature/LargeFeature.cpp
world/level/levelgen/feature/Feature.cpp

View File

@@ -0,0 +1,103 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#include <climits>
#include "BinaryHeap.hpp"
#include "common/Mth.hpp"
void BinaryHeap::insert(Node* pNode)
{
if (m_count == m_capacity)
{
m_capacity *= 2;
Node** newItems = new Node * [m_capacity];
for (int i = 0; i < m_count; i++)
{
newItems[i] = m_items[i];
}
SAFE_DELETE_ARRAY(m_items);
m_items = newItems;
}
m_items[m_count] = pNode;
pNode->field_0 = m_count;
m_count++;
inlined0(m_count - 1);
}
void BinaryHeap::inlined0(int num)
{
Node* var2 = m_items[num];
int var4;
for (float var3 = var2->field_C; num > 0; num = var4) {
var4 = (num - 1) >> 1;
Node* var5 = m_items[var4];
if (var3 >= var5->field_C) {
break;
}
m_items[num] = var5;
var5->field_0 = num;
}
m_items[num] = var2;
var2->field_0 = num;
}
void BinaryHeap::downHeap(int num)
{
Node* var2 = m_items[num];
float var3 = var2->field_C;
while (true) {
int var4 = 1 + (num << 1);
int var5 = var4 + 1;
if (var4 >= m_count) {
break;
}
Node* var6 = m_items[var4];
float var7 = var6->field_C;
Node* var8;
float var9;
if (var5 >= m_count) {
var8 = nullptr;
var9 = INFINITY;
}
else {
var8 = m_items[var5];
var9 = var8->field_C;
}
if (var7 < var9) {
if (var7 >= var3) {
break;
}
m_items[num] = var6;
var6->field_0 = num;
num = var4;
}
else {
if (var9 >= var3) {
break;
}
m_items[num] = var8;
var8->field_0 = num;
num = var5;
}
}
m_items[num] = var2;
var2->field_0 = num;
}

View File

@@ -0,0 +1,66 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#pragma once
#include "Node.hpp"
class BinaryHeap
{
public:
BinaryHeap()
{
m_count = 0;
m_capacity = 1024;
m_items = new Node*[m_capacity];
}
~BinaryHeap()
{
if (m_items)
delete[] m_items;
}
void insert(Node* pNode);
void inlined0(int i);
void downHeap(int i);
Node* removeTop() {
Node* pNode = m_items[0];
m_items[0] = m_items[--m_count];
m_items[m_count] = 0;
if (m_count > 0)
downHeap(0);
pNode->field_0 = -1;
return pNode;
}
void clear() {
m_count = 0;
}
int size() const {
return m_count;
}
void setDistance(Node* pNode, float distance) {
float oldDistance = pNode->field_C;
pNode->field_C = distance;
if (oldDistance >= distance)
downHeap(pNode->field_0);
else
inlined0(pNode->field_0);
}
private:
Node** m_items;
int m_count;
int m_capacity;
};

View File

@@ -0,0 +1,17 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#include "Node.hpp"
#include "common/Mth.hpp"
float Node::distanceTo(Node* node)
{
float dx = node->m_x - m_x;
float dy = node->m_y - m_y;
float dz = node->m_z - m_z;
return Mth::sqrt(dx * dx + dy * dy + dz * dz);
}

View File

@@ -0,0 +1,60 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#pragma once
struct Node
{
int field_0;
float field_4;
float field_8;
float field_C;
Node* field_10;
short m_x;
short m_y;
short m_z;
bool field_1A;
int m_hash;
Node()
{
init();
}
void setPos(int x, int y, int z)
{
m_x = short(x);
m_y = short(y);
m_z = short(z);
}
void setHash(int index)
{
m_hash = index;
}
void init()
{
field_0 = -1;
field_4 = 0.0f;
field_8 = 0.0f;
field_C = 0.0f;
field_10 = nullptr;
m_x = 0;
m_y = 0;
m_z = 0;
field_1A = false;
m_hash = 0;
}
bool equals(Node* node)
{
return m_hash == node->m_hash && m_x == node->m_x && m_y == node->m_y && m_z == node->m_z;
}
float distanceTo(Node* node);
};

View File

@@ -0,0 +1,36 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#include "Path.hpp"
Path::Path()
{
m_numNodes = 0;
m_pNodes = nullptr;
field_8 = 0;
}
Path::~Path()
{
clear();
}
void Path::setNodes(Node** pNodes, int nodeCount)
{
clear();
m_numNodes = nodeCount;
m_pNodes = pNodes;
for (int i = 0; i < nodeCount; i++)
{
// TODO: We are using the pNodes array for storage but duplicating the pNodes?
// This might cause a memory leak?
Node* oldNode = pNodes[i];
m_pNodes[i] = new Node;
*m_pNodes[i] = *oldNode;
}
}

View File

@@ -0,0 +1,39 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#pragma once
#include "Node.hpp"
class Path
{
public:
Path();
~Path();
void setNodes(Node** pNodes, int nodeCount);
void clear()
{
if (m_pNodes)
{
for (int i = 0; i < m_numNodes; i++)
delete m_pNodes[i];
delete[] m_pNodes;
}
m_pNodes = nullptr;
m_numNodes = 0;
field_8 = 0;
}
private:
int m_numNodes;
Node** m_pNodes;
int field_8;
};

View File

@@ -0,0 +1,320 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#include "PathFinder.hpp"
#include "world/level/Level.hpp"
#include "world/tile/DoorTile.hpp"
#include "world/entity/Entity.hpp"
static int dword_1CD868;
static int dword_1CD86C;
static int dword_1CD870;
constexpr int MakeNodeHash(int x, int y, int z)
{
// NOTE: Same as in Java Edition Beta 1.3_01
return (y & 0xFF) |
((x & 0x7FFF) << 8) |
((z & 0x7FFF) << 24) |
(x < 0 ? 0x80000000 : 0) |
(z < 0 ? 0x8000 : 0);
}
PathFinder::PathFinder()
{
m_pLevel = nullptr;
}
PathFinder::~PathFinder()
{
// NOTE: In v0.2.0, this is it. We're going to do more:
for (size_t i = 0; i < m_nodeSpillover.size(); i++)
delete m_nodeSpillover[i];
m_nodeSpillover.clear();
}
int PathFinder::isFree(Entity* pEntity, int x, int y, int z, const Node* node)
{
for (int x1 = x; x1 < x + node->m_x; x1++)
{
for (int y1 = y; y1 < y + node->m_y; y1++)
{
for (int z1 = z; z1 < z + node->m_z; z1++)
{
TileID id = m_pLevel->getTile(x1, y1, z1);
if (id < 0)
continue;
if (id == Tile::door_iron->m_ID || id == Tile::door_wood->m_ID)
{
if (!DoorTile::isOpen(m_pLevel->getData(x1, y1, z1)))
return 0;
continue;
}
Material* pMtl = Tile::tiles[id]->m_pMaterial;
if (pMtl->blocksMotion())
return 0;
if (pMtl == Material::water)
return -1;
if (pMtl == Material::lava)
return -2;
}
}
}
return 1; // Totally free!
}
Node* PathFinder::getNode(Entity* pEntity, int x, int y, int z, const Node* node, int a)
{
Node* pNode = nullptr;
if (isFree(pEntity, x, y, z, node) == 1)
pNode = getNode(x, y, z);
if (a > 0 && !pNode && isFree(pEntity, x, y + a, z, node) == 1)
{
y += a;
pNode = getNode(x, y, z);
}
if (!pNode || y < 0)
return nullptr;
int limit = y - 4;
while (true)
{
int is_free = isFree(pEntity, x, --y, z, node);
if (is_free != 1)
{
if (is_free == -2)
pNode = nullptr;
break;
}
if (y == limit)
{
pNode = nullptr;
break;
}
if (!y)
break;
pNode = getNode(x, y, z);
}
return pNode;
}
Node* PathFinder::getNode(int x, int y, int z)
{
NodeMap::iterator iter = m_nodeMap.find(MakeNodeHash(x, y, z));
if (iter != m_nodeMap.end())
return iter->second;
Node* pNode = new_Node(x, y, z);
dword_1CD868++;
m_nodeMap.insert_or_assign(MakeNodeHash(x, y, z), pNode);
return pNode;
}
int PathFinder::getNeighbors(Entity* pEntity, Node* node1, const Node* node2, Node* node3, float maxDist)
{
int nr = 0;
bool isf = isFree(pEntity, node1->m_x, node1->m_y, node1->m_z, node2) == 1;
Node* n1 = getNode(pEntity, node1->m_x, node1->m_y, node1->m_z + 1, node2, isf);
Node* n2 = getNode(pEntity, node1->m_x - 1, node1->m_y, node1->m_z, node2, isf);
Node* n3 = getNode(pEntity, node1->m_x + 1, node1->m_y, node1->m_z, node2, isf);
Node* n4 = getNode(pEntity, node1->m_x, node1->m_y, node1->m_z - 1, node2, isf);
if (n1 && !n1->field_1A && n1->distanceTo(node3) < maxDist) field_10038[nr++] = n1;
if (n2 && !n2->field_1A && n2->distanceTo(node3) < maxDist) field_10038[nr++] = n2;
if (n3 && !n3->field_1A && n3->distanceTo(node3) < maxDist) field_10038[nr++] = n3;
if (n4 && !n4->field_1A && n4->distanceTo(node3) < maxDist) field_10038[nr++] = n4;
return nr;
}
bool PathFinder::inlined_0(Path& path, Node* nodeEnd)
{
if (dword_1CD870 < dword_1CD868)
dword_1CD870 = dword_1CD868;
int number = 1;
Node* temp = nodeEnd;
while (temp->field_10)
{
temp = temp->field_10;
number++;
}
Node** pathNodes = new Node*[number];
int index = number - 1;
pathNodes[index--] = nodeEnd;
while (nodeEnd->field_10)
{
pathNodes[index--] = nodeEnd->field_10;
nodeEnd = nodeEnd->field_10;
}
path.setNodes(pathNodes, number);
return true;
}
bool PathFinder::findPath(Path& path, Entity* pEntity, Node* nodeStart, Node* nodeEnd, const Node* node3, float fp)
{
dword_1CD868 = 0;
nodeStart->field_4 = 0;
nodeStart->field_C = nodeStart->field_8 = nodeStart->distanceTo(nodeEnd);
m_binaryHeap.clear();
m_binaryHeap.insert(nodeStart);
Node* nodep = nodeStart;
while (true)
{
if (!m_binaryHeap.size())
break;
Node* pNode = m_binaryHeap.removeTop();
if (pNode->equals(nodeEnd))
return inlined_0(path, nodeEnd);
if (nodep->distanceTo(nodeEnd) > pNode->distanceTo(nodeEnd))
nodep = pNode;
pNode->field_1A = true;
int numNeighbors = getNeighbors(pEntity, pNode, node3, nodeEnd, fp);
for (int i = 0; i < numNeighbors; i++)
{
Node* otherNode = field_10038[i];
if (!otherNode->field_1A)
{
float dist = pNode->field_4 + pNode->distanceTo(otherNode);
if (otherNode->field_0 < 0 || otherNode->field_4 > dist)
{
otherNode->field_10 = pNode;
otherNode->field_4 = dist;
otherNode->field_8 = otherNode->distanceTo(nodeEnd);
if (otherNode->field_0 < 0)
{
otherNode->field_C = otherNode->field_4 + otherNode->field_8;
m_binaryHeap.insert(otherNode);
}
else
{
// Update distance
m_binaryHeap.setDistance(otherNode, otherNode->field_4 + otherNode->field_8);
}
}
}
}
}
if (nodep != nodeStart)
return inlined_0(path, nodeEnd);
return false; // no path found
}
bool PathFinder::findPath(Path& path, Entity* pEntity, float x, float y, float z, float d)
{
// uh?
m_nodeMap.clear();
m_nodeCount = 0;
// not treating spillover btw? or what
int x1 = Mth::floor(pEntity->m_hitbox.min.x);
int y1 = Mth::floor(pEntity->m_hitbox.min.y);
int z1 = Mth::floor(pEntity->m_hitbox.min.z);
Node* node1 = getNode(x1, y1, z1);
int x2 = Mth::floor(x - 0.5f * pEntity->field_88);
int y2 = Mth::floor(y);
int z2 = Mth::floor(z - 0.5f * pEntity->field_88);
Node* node2 = nullptr;
if (!m_pLevel->getTile(x2, y2 - 1, z2))
{
for (int x3 = x2; x3 <= Mth::floor(x + 0.5f * pEntity->field_88); x3++)
{
for (int z3 = z2; z3 <= Mth::floor(y + 0.5f * pEntity->field_88); z3++)
{
if (m_pLevel->getTile(x3, y2 - 1, z3))
{
node2 = getNode(x3, y2, z3);
break; // breaking out of the z3 loop only. Intended to break out of x3 too?
}
}
}
}
if (!node2)
node2 = getNode(x2, y2, z2);
int x4 = Mth::floor(pEntity->field_88 + 1.0f);
int y4 = Mth::floor(pEntity->field_8C + 1.0f);
int z4 = Mth::floor(pEntity->field_88 + 1.0f);
Node node3;
node3.setPos(x4, y4, z4);
node3.setHash(MakeNodeHash(x4, y4, z4));
bool foundPath = findPath(path, pEntity, node1, node2, &node3, d);
if (m_nodeCount > 2048)
{
// huh.
for (size_t i = 0; i < m_nodeSpillover.size(); i++)
delete m_nodeSpillover[i];
m_nodeSpillover.clear();
}
return foundPath;
}
Node* PathFinder::new_Node(int x, int y, int z)
{
int nodeID = m_nodeCount++;
Node* pNode;
if (m_nodeCount < MAX_NODE_COUNT)
{
// Allocate from node reserve
pNode = &m_nodeReserve[nodeID];
pNode->init();
pNode->setPos(x, y, z);
pNode->setHash(MakeNodeHash(x, y, z));
}
else
{
pNode = new Node;
pNode->setPos(x, y, z);
pNode->setHash(MakeNodeHash(x, y, z));
m_nodeSpillover.push_back(pNode);
}
return pNode;
}

View File

@@ -0,0 +1,48 @@
/********************************************************************
Minecraft: Pocket Edition - Decompilation Project
Copyright (C) 2023 iProgramInCpp
The following code is licensed under the BSD 1 clause license.
SPDX-License-Identifier: BSD-1-Clause
********************************************************************/
#pragma once
#include <vector>
#include <map>
#include "Path.hpp"
#include "BinaryHeap.hpp"
class Level;
class Entity;
#define MAX_NODE_COUNT (2048)
#define FIELD_10038_SIZE (32)
typedef std::map <int, Node*> NodeMap;
class PathFinder
{
public:
PathFinder();
~PathFinder();
int isFree(Entity*, int x, int y, int z, const Node* node);
Node* getNode(Entity*, int x, int y, int z, const Node* node, int a);
Node* getNode(int x, int y, int z);
int getNeighbors(Entity*, Node*, const Node*, Node*, float);
bool findPath(Path&, Entity*, Node*, Node*, const Node*, float);
bool findPath(Path&, Entity*, float, float, float, float);
private:
Node* new_Node(int x, int y, int z);
bool inlined_0(Path& path, Node* node2);
private:
Level* m_pLevel;
BinaryHeap m_binaryHeap;
NodeMap m_nodeMap;
Node m_nodeReserve[MAX_NODE_COUNT];
std::vector<Node*> m_nodeSpillover;
int m_nodeCount;
Node* field_10038[FIELD_10038_SIZE];
};

View File

@@ -39,11 +39,11 @@ public:
#pragma GCC diagnostic pop
// @NOTE: These are inlined.
inline bool isOpen(int data)
inline static bool isOpen(int data)
{
return (data & 4) != 0;
}
inline bool isTop(int data)
inline static bool isTop(int data)
{
return (data & 8) != 0;
}