When I attach collision objects to the hex tiles, the game frame rate drops below 10-20 fps. in an effort to mitigate this, I am trying to convert the map to use a MultiMeshInstance instead. I was able to get this to work for the map size (75 145), but now frame rate are even worse, and I have yet to implement tile color, the collision area or the map wrapping feature. I am using the Kaby Lake HD 620 gfx card, and I wonder if this may be the cause. ''' hexMultiMesh->set_mesh(hexMesh); hexMultiMesh->set_transform_format(1); hexMultiMesh->set_instance_count(numRow numCol); hexMultiMeshInstance->set_multimesh(hexMultiMesh); for (int x = 0; x < numRow; x++) { for (int y = 0; y < numCol; y++) { hexMultiMesh->set_instance_transform(x numRow + y, Transform(Basis(), Vector3((sqrt(3) / 2) (x + y / 2.0) 2, 0.0, -y 1.5))); } } add_child(_hexMultiMeshInstance); ''' I'm wondering if there's a way to improve fps using either method, the multimesh or the individual mesh instances attached to custom hex node classes with collision objects.
Converting from MeshInstance to MultiMeshInstance
I'm don't know much about hex maps. But some general questions:
Did you try to use the GLES2 renderer instead of GLES3 ? (works usually better on weak "graphic cards")
How many meshes are you trying to render? (numRow, numCol)
How complex is that "_hexMesh". (i.e. number of vertices)
Use any light sources? If yes, a single one or more?
How complex is that collision shape?
- Edited
10,845 hex tiles. 26 verts per tile. Collision shape is the mesh turned into a convex shape. performance sucks just as bad when I use a simple shape of just 6 verts too. Haven't tried GLES 2. One directional light source. The multimesh made the fps drop below 0.5 so I'm sure that's out of the question. It runs fine when the hex tiles have no collider attached. The whole reason I want collision info is that I want to know when a tile is clicked on. Is there a better, less resource intensive way I can do this? I'll edit the source code into this comment when I get back to my pc. (Sorry, code made this comment too large)
#ifndef HEXTILE_HPP
#define HEXTILE_HPP
#include <Godot.hpp>
#include <Spatial.hpp>
#include <ArrayMesh.hpp>
#include <MeshInstance.hpp>
#include <ResourceLoader.hpp>
#include <SpatialMaterial.hpp>
namespace godot
{
class HexTile : public Spatial
{ GODOT_CLASS(HexTile, Spatial)
private:
ResourceLoader* _loader = ResourceLoader::get_singleton();
MeshInstance* _hexMeshInstance = MeshInstance::_new();
Ref<ArrayMesh> _hexMesh = _loader->load("res://assets/meshes/hex.obj");
Ref<SpatialMaterial> _waterMaterial = _loader->load("res://assets/materials/water.tres");
Ref<SpatialMaterial> _sandMaterial = _loader->load("res://assets/materials/sand.tres");
Ref<SpatialMaterial> _plainsMaterial = _loader->load("res://assets/materials/plains.tres");
Ref<SpatialMaterial> _grassMaterial = _loader->load("res://assets/materials/grass.tres");
Ref<SpatialMaterial> _mountainMaterial = _loader->load("res://assets/materials/mountain.tres");
Ref<SpatialMaterial> _peakMaterial = _loader->load("res://assets/materials/peak.tres");
MeshInstance* _forestMeshInstance = MeshInstance::_new();
Ref<ArrayMesh> _forestMesh = _loader->load("res://assets/meshes/forest.obj");
Ref<SpatialMaterial> _treeTopMaterial = _loader->load("res://assets/materials/tree_top.tres");
Ref<SpatialMaterial> _treeTrunkMaterial = _loader->load("res://assets/materials/tree_trunk.tres");
double _radius = 1.0;
double _height = _radius * 2;
double _width = (sqrt(3) / 2) * _height;
double _vertSpacing = _height * 0.75;
double _horizSpacing = _width;
Vector3 _position;
double _waterThreshold = 0.0;
double _sandThreshold = 0.1;
double _plainsThreshold = 0.175;
double _grassThreshold = 0.725;
double _mountainThreshold = 1.0;
double _forestThresholdLow = 0.15;
double _forestThresholdHigh = _grassThreshold;
public:
int _x;
int _y;
int _z;
double _elevation;
double _moisture;
void initialize(int pX, int pY, double pElevation, double pMoisture);
void update_visuals();
static void _register_methods();
void _init();
HexTile(); // Required for creation of game map, leave empty
};
}
#endif /* HEXTILE_HPP */
#include "hexTile.hpp"
using namespace godot;
HexTile::HexTile()
{
/* THIS IS NEEDED, LEAVE BLANK */
}
void HexTile::_register_methods()
{
register_method((char*)"_init", &HexTile::_init);
}
void HexTile::_init()
{
_hexMeshInstance->set_mesh(_hexMesh);
_hexMeshInstance->set_cast_shadows_setting(0);
_forestMeshInstance->set_mesh(_forestMesh);
_forestMeshInstance->set_cast_shadows_setting(1);
_forestMeshInstance->set_surface_material(1, _treeTopMaterial);
_forestMeshInstance->set_surface_material(0, _treeTrunkMaterial);
}
void HexTile::initialize(int pX, int pY, double pElevation, double pMoisture)
{
_x = pX;
_y = pY;
_z = -(pX + pY);
_elevation = pElevation;
_moisture = pMoisture;
_position = Vector3(_horizSpacing * (_y + _x / 2.0), 0, -_vertSpacing * _x);
add_child(_hexMeshInstance);
update_visuals();
}
void HexTile::update_visuals()
{
if (_elevation <= _waterThreshold) {
_hexMeshInstance->set_surface_material(0, _waterMaterial);
_position.y = -0.125;
} else if (_elevation <= _sandThreshold) {
_hexMeshInstance->set_surface_material(0, _sandMaterial);
_position.y = 0;
} else if (_elevation <= _plainsThreshold) {
_hexMeshInstance->set_surface_material(0, _plainsMaterial);
_position.y = 0;
} else if (_elevation <= _grassThreshold) {
_hexMeshInstance->set_surface_material(0, _grassMaterial);
_position.y = 0;
} else if (_elevation <= _mountainThreshold) {
_hexMeshInstance->set_surface_material(0, _mountainMaterial);
_position.y = pow(1 + _elevation - (_grassThreshold * 1.25), 4);
_hexMeshInstance->set_cast_shadows_setting(1);
} else {
_hexMeshInstance->set_surface_material(0, _peakMaterial);
_position.y = pow(1 + _elevation - (_grassThreshold * 1.25), 4);
_hexMeshInstance->set_cast_shadows_setting(1);
}
set_translation(_position);
if (_moisture > 0.1 && _elevation > _forestThresholdLow && _elevation < _forestThresholdHigh) {
add_child(_forestMeshInstance);
}
}
#ifndef PI
#define PI 3.14159265358979323846
#endif
#ifndef HEXMAP_HPP
#define HEXMAP_HPP
#include <stdint.h>
#include <stdlib.h>
#include <ctime>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <Godot.hpp>
#include <Node.hpp>
#include <SpatialMaterial.hpp>
#include <OpenSimplexNoise.hpp>
#include "human.hpp"
#include "hexTile.hpp"
namespace godot
{
class HexMap : public Node
{ GODOT_CLASS(HexMap, Node)
private:
HexTile*** _hexTile;
std::unordered_map<int, Human*> _humans;
int _numHumans = 0;
double _hexRadius = 1.0;
double _hexHeight = _hexRadius * 2;
double _hexWidth = (sqrt(3) / 2) * _hexHeight;
int _numRow = 75;
int _numCol = 145;
double _globeRadius = 25;
double _angleStep;
double _mapHeight = (_hexHeight * 0.75) * _numRow;
double _mapWidth = _hexWidth * _numCol;
OpenSimplexNoise* _noiseElevation = OpenSimplexNoise::_new();
int64_t _elevationOctaves = 4;
real_t _elevationPeriod = 5;
real_t _elevationLacunarity = 1.0;
real_t _elevationPersistance = 1.0;
OpenSimplexNoise* _noiseMoisture = OpenSimplexNoise::_new();
int64_t _moistureOctaves = 2;
real_t _moisturePeriod = 2.5;
real_t _moistureLacunarity = 1.5;
real_t _moisturePersistance = 1.0;
public:
static void _register_methods();
void _init();
void spawn_human(HexTile* pHex);
void remove_human(Human* pHuman);
void generate_map();
std::vector<HexTile*> get_area(int pX, int pY, int pRadius);
void elevate_area(int pX, int pY, int pRadius, double pElevation);
HexTile* get_hexTile(int pX, int pY);
double get_distance(HexTile* pHexA, HexTile* pHexB);
double get_height();
double get_width();
void set_numRow(int pRow);
int get_numRow() const;
void set_numCol(int pCol);
int get_numCol() const;
};
}
#endif /* HEXMAP_HPP */
#include "hexMap.hpp"
using namespace godot;
void HexMap::_register_methods()
{
}
void HexMap::_init()
{
generate_map();
}
void HexMap::spawn_human(HexTile* pHex)
{
Human* newHuman = Human::_new();
newHuman->set_id(_numHumans);
newHuman->set_parentHex(pHex);
pHex->add_child(newHuman);
_humans[_numHumans] = newHuman;
_numHumans++;
}
void HexMap::remove_human(Human* pHuman)
{ // Don't decrement _numHumans, the number is required as a unique ID for each human created.
pHuman->get_parent()->remove_child(pHuman);
_humans.erase(pHuman->get_id());
}
void HexMap::generate_map()
{
// Dynamic Array Allocation
_hexTile = new HexTile**[_numRow];
for (int i = 0; i < _numRow; i++) {
_hexTile[i] = new HexTile*[_numCol];
}
// Set Map Parameters
_angleStep = ((360.0 / _numCol) * (PI / 180.0));
_noiseElevation->set_octaves(_elevationOctaves);
_noiseElevation->set_period(_elevationPeriod);
_noiseElevation->set_lacunarity(_elevationLacunarity);
_noiseElevation->set_persistence(_elevationPersistance);
_noiseMoisture->set_octaves(_moistureOctaves);
_noiseMoisture->set_period(_moisturePeriod);
_noiseMoisture->set_lacunarity(_moistureLacunarity);
_noiseMoisture->set_persistence(_moisturePersistance);
srand((int) time(0));
_noiseElevation->set_seed(rand());
_noiseMoisture->set_seed(rand());
// Generate Empty Map
double currentAngle;
for (int x = 0; x < _numRow; x++) {
currentAngle = 0.0;
for (int y = 0; y < _numCol; y++) {
_hexTile[x][y] = HexTile::_new();
_hexTile[x][y]->initialize(
x,
y,
-0.25,
0
);
add_child(_hexTile[x][y]);
currentAngle += _angleStep;
}
}
// Generate Continents
int numSplats = (rand() % 10) + 10;
for (int i = 0; i < numSplats; i++) {
int size = (rand() % 10) + 10;
double intensity = ((rand() % 70) + 40) / 100.0;
int x = (rand() % _numRow);
int y = (rand() % _numCol);
elevate_area(x, y, size, intensity);
}
// Generate Lakes
int numLakes = (rand() % 5) + 25;
for (int i = 0; i < numLakes; i++) {
int size = (rand() % 5) + 5;
double intensity = ((rand() % 25) + 85) / 100.0;
int x = (rand() % _numRow);
int y = (rand() % _numCol);
elevate_area(x, y, size, -intensity);
}
// Add Noise
for (int x = 0; x < _numRow; x++) {
currentAngle = 0.0;
for (int y = 0; y < _numCol; y++) {
_hexTile[x][y]->_elevation += _noiseElevation->get_noise_3d(x, _globeRadius * sin(currentAngle), _globeRadius * cos(currentAngle)) * 0.75;
_hexTile[x][y]->_moisture += _noiseMoisture->get_noise_3d(x, _globeRadius * sin(currentAngle), _globeRadius * cos(currentAngle));
currentAngle += _angleStep;
}
}
// Update Hex Visuals
for (int x = 0; x < _numRow; x++) {
currentAngle = 0.0;
for (int y = 0; y < _numCol; y++) {
_hexTile[x][y]->update_visuals();
}
}
}
std::vector<HexTile*> HexMap::get_area(int pX, int pY, int pRadius)
{
std::vector<HexTile*> area;
for (int dX = -pRadius; dX < pRadius - 1; dX++) {
for (int dY = std::max(-pRadius + 1, -dX - pRadius); dY < std::min(pRadius, -dX + pRadius - 1); dY++) {
int x = pX + dX;;
int y = pY + dY;
if (x >= _numRow) {
x = x - _numRow;
} else if (x < 0) {
x = x + _numRow;
}
if (y >= _numCol) {
y = y - _numCol;
} else if (y < 0) {
y = y + _numCol;
}
area.push_back(_hexTile[x][y]);
}
}
return area;
}
void HexMap::elevate_area(int pX, int pY, int pRadius, double pElevation)
{
std::vector<HexTile*> area = get_area(pX, pY, pRadius);
for (int i = 0; i < area.size(); i++) {
area[i]->_elevation += (pElevation * (1 - (get_distance(_hexTile[pX][pY], area[i]) / pRadius)));
}
}
double HexMap::get_distance(HexTile* pHexA, HexTile* pHexB)
{
int dX;
int dY;
int dZ;
if (std::abs(pHexA->_x - pHexB->_x) > _numRow / 2) {
dX = _numRow - std::abs(pHexA->_x - pHexB->_x);
} else {
dX = std::abs(pHexA->_x - pHexB->_x);
}
if (std::abs(pHexA->_y - pHexB->_y) > _numCol / 2) {
dY = _numCol - std::abs(pHexA->_y - pHexB->_y);
} else {
dY = std::abs(pHexA->_y - pHexB->_y);
}
if (std::abs(pHexA->_z - pHexB->_z) > _numRow / 2) {
dZ = _numRow - std::abs(pHexA->_z - pHexB->_z);
} else {
dZ = std::abs(pHexA->_z - pHexB->_z);
}
return std::max(std::max(dX, dY), dZ);
}
HexTile* HexMap::get_hexTile(int pX, int pY)
{
return _hexTile[pX][pY];
}
double HexMap::get_height()
{
return _mapHeight;
}
double HexMap::get_width()
{
return _mapWidth;
}
void HexMap::set_numRow(int pRow)
{
_numRow = pRow;
}
int HexMap::get_numRow() const
{
return _numRow;
}
void HexMap::set_numCol(int pCol)
{
_numCol = pCol;
}
int HexMap::get_numCol() const
{
return _numCol;
}
- Edited
#ifndef ROOT_HPP
#define ROOT_HPP
#include <Godot.hpp>
#include "hexMap.hpp"
#include "cameraController.hpp"
namespace godot
{
class Root : public Node
{ GODOT_CLASS(Root, Node)
private:
HexMap* _hexMap = HexMap::_new();
CameraController* _cameraController = CameraController::_new();
Vector3 _cameraTranslation;
public:
static void _register_methods();
void _init();
void _process(float delta);
void map_to_camera();
};
}
#endif /* ROOT_HPP */
#include "root.hpp"
using namespace godot;
/* -----------------------------------------------------------------------------
TODO : Camera/Object culling
----------------------------------------------------------------------------- */
void Root::_register_methods()
{
register_method((char*)"_init", &Root::_init);
register_method((char*)"_process", &Root::_process);
}
void Root::_init()
{
add_child(_hexMap);
add_child(_cameraController);
_cameraController->set_translation(Vector3(_hexMap->get_width() / 2, 0, -(_hexMap->get_height() / 2)));
}
void Root::_process(float delta)
{
map_to_camera();
}
void Root::map_to_camera()
{
double l_widthsFromCamera;
int l_widthsToFix;
if (_cameraTranslation != _cameraController->get_translation()) {
for (int x = 0; x < _hexMap->get_numRow(); x++) {
for (int y = 0; y < _hexMap->get_numCol(); y++) {
l_widthsFromCamera = (_hexMap->get_hexTile(x, y)->get_translation().x - _cameraController->get_translation().x) / _hexMap->get_width();
if ((l_widthsFromCamera > -0.5) && (l_widthsFromCamera < 0.5)){
continue;
} else if (l_widthsFromCamera > 0) {
l_widthsFromCamera += 0.5;
} else {
l_widthsFromCamera -= 0.5;
}
l_widthsToFix = (int)l_widthsFromCamera;
_hexMap->get_hexTile(x, y)->translate(Vector3(-l_widthsToFix * _hexMap->get_width(), 0, 0));
}
}
_cameraTranslation = _cameraController->get_translation();
}
if (_cameraController->get_translation().z < -_hexMap->get_height()) {
_cameraController->set_translation(Vector3(_cameraController->get_translation().x, _cameraController->get_translation().y, -_hexMap->get_height()));
} else if (_cameraController->get_translation().z > 0) {
_cameraController->set_translation(Vector3(_cameraController->get_translation().x, _cameraController->get_translation().y, 0));
}
}