Jump to content

How do I access vegetation objects?


Vince
 Share

Recommended Posts

I had to do this for my game, it's possible, just not with the vegetation system exactly.

You have to convert vegetation instances into real Entity objects.

Due to performance reasons, you can't convert every vegetation object to an entity, so you have to be smart, and look at the closest objects to the player.

I used a pool of about 50 objects and moved them to the closest trees around a player.

I would sticky a vegetation entity to a specific tree as well, so if you started cutting one tree down, and you moved a little bit, you could go back to that tree and finish it off.

Here is the C++ code. (Headers not included)

 

VegetationToEntity

-- Converts a vegetation layer into multiple VegetatedModel classes

bool findStringIC(const std::string & strHaystack, const std::string & strNeedle)
	{
		auto it = std::search(
				strHaystack.begin(), strHaystack.end(),
				strNeedle.begin(),   strNeedle.end(),
				[](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }
		);
		return (it != strHaystack.end() );
	}

	void VegetationToEntity::LoadVegetationLayer(World* world)
	{
		App* app = App::GetApp();
		if (app == nullptr)
		{
			return;
		}

		if (world->terrain == nullptr)
		{
			return;
		}

		std::map<std::string, bool> limitEntityMap = {
				{ "Antelope", true },
				{ "Deer", true },
				{ "Lamb", true },
				{ "Sheep", true },
				{ "Wolf", true },
		};

		std::map<std::string, std::string> vegNameEntMap = {
				{ "oak", "OakTree" },
				{ "maple", "MapleTree" },
				{ "birch", "BirchTree" },
				{ "green", "GreenheartTree" },
				{ "giantwood", "RedwoodTree" },
				{ "weep",  "WillowTree" },
				{ "mud", "MudHole" },
				{ "antelope", "Antelope" },
				{ "fallowdeer", "Deer" },
				{ "lamb", "Lamb" },
				{ "sheep", "Sheep" },
				{ "wolf", "Wolf" },
		};

		int vegCount = world->terrain->CountVegetationLayers();
		for (int i = 0; i < vegCount; i++)
		{
			VegetationLayer* layer = world->terrain->GetVegetationLayer(i);
			std::string modelPath = layer->modelpath;

			std::string parentName = "";
			System::Print(modelPath);
			for (auto it = vegNameEntMap.begin(); it != vegNameEntMap.end(); it++)
			{
				System::Print(it->first);
				if(findStringIC(modelPath, it->first))
				{
					parentName = it->second;
					break;
				}
			}

			if (parentName.empty())
			{
				continue;
			}

			Entity* model = world->FindEntity(parentName);
			if (model == nullptr)
			{
				continue;
			}

			// Hide Layer
			layer->viewrange = 0;

			if (limitEntityMap[parentName])
			{
				models.push_back(new VegetatedModel(layer, model, parentName));
				continue;
			}

			Vec3 scale = model->GetScale();

			AABB aabb = world->aabb;
			std::vector<Mat4> instances;
			layer->GetInstancesInAABB(aabb, instances);
			System::Print("Loading Layer: " + parentName + " of Size: " + std::to_string(instances.size()));
			for (unsigned int j = 0; j < instances.size(); j++)
			{
				Mat4 instance = instances[j];

				Entity* copy = model->Instance(true, false);
				copy->SetMatrix(instance, true);

				copy->SetScale(scale);
			}
		}
	}
	// Called manually in App::Loop
	void VegetationToEntity::UpdateWorld()
	{
		for (auto it = models.begin(); it != models.end(); it++)
		{
			VegetatedModel* model = *it;
			model->UpdateWorld();
		}
	}



The next class is where the work comes in. VegetatedModel represents a single Vegetation Layer as N number of entities. This will auto-populate the entities around the players positions as the player moves.
 

VegetatedModel::VegetatedModel(VegetationLayer* layer, Entity* model, std::string className)
	{
		this->layer = layer;
		this->cameraPosit = Vec3(0);

		for (int i = 0; i < VEG_MODEL_ENTITY_COUNT; i++)
		{
			Entity* copy = model->Instance(true, false);
			WFModel* model = Binder::InitCtor(copy, className);

			this->realEntities[i] = new VegetatedEntity(model, copy);
		}
	}

	void VegetatedModel::UpdateWorld()
	{
		Player* p = Player::GetPlayer();
		if (p == NULL)
		{
			return;
		}

		Vec3 playerPosit = p->GetPosition();
		if (this->cameraPosit.DistanceToPoint(playerPosit) < 4)
		{
			return;
		}

		this->cameraPosit = playerPosit;

		int boxSizeX = 30;
		int boxSizeZ = 30;
		AABB scope = AABB(playerPosit.x-boxSizeX, -1000, playerPosit.z-boxSizeZ, playerPosit.x+boxSizeX, 1000, playerPosit.z+boxSizeZ);

		World* world = World::GetCurrent();
		std::vector<Vec3> possiblePoints(0);
		std::vector<Mat4> instances(0);
		this->layer->GetInstancesInAABB(world->aabb, instances);
		for (unsigned int j = 0; j < instances.size(); j++)
		{
			Mat4 mat = instances[j];
			possiblePoints.push_back(mat.GetTranslation());
		}

		std::sort(possiblePoints.begin(), possiblePoints.end(), VegetatedModel::PointSort);

		// First VEG_MODEL_ENTITY_COUNT as we want to show only the closest point
		if (possiblePoints.size() > VEG_MODEL_ENTITY_COUNT)
		{
			possiblePoints.erase(possiblePoints.begin() + VEG_MODEL_ENTITY_COUNT, possiblePoints.end());
		}
		
		std::vector<VegetatedEntity*> entitiesNeedingPoints;
		std::vector<Vec3> pointsToRemove;
		int Count = Math::Min(possiblePoints.size(), VEG_MODEL_ENTITY_COUNT);
		for (int i = 0 ; i < Count; i++)
		{
			VegetatedEntity* vegEntity = this->realEntities[i];
			Entity* ent = vegEntity->GetEntity();
			
			bool found = false;
			for (int j = 0; j < Count; j++)
			{
				Vec3 posit = possiblePoints[j];
				if (vegEntity->GetVegetationPosition() == posit)
				{
					found = true;
					pointsToRemove.push_back(posit);
					break;
				}
			}

			if (!found)
			{
				entitiesNeedingPoints.push_back(vegEntity);
			}
		}

		std::vector<Vec3> newPossiblePoints;
		for (int i = 0; i < possiblePoints.size(); i++)
		{
			Vec3 possiblePoint = possiblePoints[i];
			bool found = false;
			for (auto it = pointsToRemove.begin(); it != pointsToRemove.end(); it++)
			{
				Vec3 pointRemove = *it;
				if (possiblePoint == pointRemove)
				{
					found = true;
					break;
				}
			}

			if (!found)
			{
				newPossiblePoints.push_back(possiblePoint);
			}
		}
		possiblePoints = newPossiblePoints;

		int numEntitiesToSet = Math::Min(possiblePoints.size(), entitiesNeedingPoints.size());
		for (int i = 0; i < numEntitiesToSet; i++)
		{
			VegetatedEntity* vegEntity = entitiesNeedingPoints[i];
			Entity* ent = vegEntity->GetEntity();
			WFModel* model = vegEntity->GetWFModel();

			Vec3 point = possiblePoints[i];
			vegEntity->SetVegetationPosition(point);
			model->Reset();
		}

		// Remove unused entities
		for (unsigned int i = numEntitiesToSet; i < entitiesNeedingPoints.size(); i++)
		{
			VegetatedEntity* vegEntity = entitiesNeedingPoints[i];
			Entity* ent = vegEntity->GetEntity();
			//vegEntity->SetVegetationPosition(Vec3(0, -123, 0));
			//ent->Hide();
		}
	}

	bool VegetatedModel::PointSort(Vec3 posit1, Vec3 posit2)
	{
		Player* p = Player::GetPlayer();
		if (p == NULL)
		{
			return 0;
		}

		double d1 = p->GetPosition().DistanceToPoint(posit1);
		double d2 = p->GetPosition().DistanceToPoint(posit2);

		return d1 < d2;
	}



 

  • Like 4
Link to comment
Share on other sites

8 hours ago, martyj said:

I had to do this for my game, it's possible, just not with the vegetation system exactly.

You have to convert vegetation instances into real Entity objects.

Due to performance reasons, you can't convert every vegetation object to an entity, so you have to be smart, and look at the closest objects to the player.

I used a pool of about 50 objects and moved them to the closest trees around a player.

I would sticky a vegetation entity to a specific tree as well, so if you started cutting one tree down, and you moved a little bit, you could go back to that tree and finish it off.

Here is the C++ code. (Headers not included)

 

VegetationToEntity

-- Converts a vegetation layer into multiple VegetatedModel classes


bool findStringIC(const std::string & strHaystack, const std::string & strNeedle)
	{
		auto it = std::search(
				strHaystack.begin(), strHaystack.end(),
				strNeedle.begin(),   strNeedle.end(),
				[](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }
		);
		return (it != strHaystack.end() );
	}

	void VegetationToEntity::LoadVegetationLayer(World* world)
	{
		App* app = App::GetApp();
		if (app == nullptr)
		{
			return;
		}

		if (world->terrain == nullptr)
		{
			return;
		}

		std::map<std::string, bool> limitEntityMap = {
				{ "Antelope", true },
				{ "Deer", true },
				{ "Lamb", true },
				{ "Sheep", true },
				{ "Wolf", true },
		};

		std::map<std::string, std::string> vegNameEntMap = {
				{ "oak", "OakTree" },
				{ "maple", "MapleTree" },
				{ "birch", "BirchTree" },
				{ "green", "GreenheartTree" },
				{ "giantwood", "RedwoodTree" },
				{ "weep",  "WillowTree" },
				{ "mud", "MudHole" },
				{ "antelope", "Antelope" },
				{ "fallowdeer", "Deer" },
				{ "lamb", "Lamb" },
				{ "sheep", "Sheep" },
				{ "wolf", "Wolf" },
		};

		int vegCount = world->terrain->CountVegetationLayers();
		for (int i = 0; i < vegCount; i++)
		{
			VegetationLayer* layer = world->terrain->GetVegetationLayer(i);
			std::string modelPath = layer->modelpath;

			std::string parentName = "";
			System::Print(modelPath);
			for (auto it = vegNameEntMap.begin(); it != vegNameEntMap.end(); it++)
			{
				System::Print(it->first);
				if(findStringIC(modelPath, it->first))
				{
					parentName = it->second;
					break;
				}
			}

			if (parentName.empty())
			{
				continue;
			}

			Entity* model = world->FindEntity(parentName);
			if (model == nullptr)
			{
				continue;
			}

			// Hide Layer
			layer->viewrange = 0;

			if (limitEntityMap[parentName])
			{
				models.push_back(new VegetatedModel(layer, model, parentName));
				continue;
			}

			Vec3 scale = model->GetScale();

			AABB aabb = world->aabb;
			std::vector<Mat4> instances;
			layer->GetInstancesInAABB(aabb, instances);
			System::Print("Loading Layer: " + parentName + " of Size: " + std::to_string(instances.size()));
			for (unsigned int j = 0; j < instances.size(); j++)
			{
				Mat4 instance = instances[j];

				Entity* copy = model->Instance(true, false);
				copy->SetMatrix(instance, true);

				copy->SetScale(scale);
			}
		}
	}
	// Called manually in App::Loop
	void VegetationToEntity::UpdateWorld()
	{
		for (auto it = models.begin(); it != models.end(); it++)
		{
			VegetatedModel* model = *it;
			model->UpdateWorld();
		}
	}



The next class is where the work comes in. VegetatedModel represents a single Vegetation Layer as N number of entities. This will auto-populate the entities around the players positions as the player moves.
 


VegetatedModel::VegetatedModel(VegetationLayer* layer, Entity* model, std::string className)
	{
		this->layer = layer;
		this->cameraPosit = Vec3(0);

		for (int i = 0; i < VEG_MODEL_ENTITY_COUNT; i++)
		{
			Entity* copy = model->Instance(true, false);
			WFModel* model = Binder::InitCtor(copy, className);

			this->realEntities[i] = new VegetatedEntity(model, copy);
		}
	}

	void VegetatedModel::UpdateWorld()
	{
		Player* p = Player::GetPlayer();
		if (p == NULL)
		{
			return;
		}

		Vec3 playerPosit = p->GetPosition();
		if (this->cameraPosit.DistanceToPoint(playerPosit) < 4)
		{
			return;
		}

		this->cameraPosit = playerPosit;

		int boxSizeX = 30;
		int boxSizeZ = 30;
		AABB scope = AABB(playerPosit.x-boxSizeX, -1000, playerPosit.z-boxSizeZ, playerPosit.x+boxSizeX, 1000, playerPosit.z+boxSizeZ);

		World* world = World::GetCurrent();
		std::vector<Vec3> possiblePoints(0);
		std::vector<Mat4> instances(0);
		this->layer->GetInstancesInAABB(world->aabb, instances);
		for (unsigned int j = 0; j < instances.size(); j++)
		{
			Mat4 mat = instances[j];
			possiblePoints.push_back(mat.GetTranslation());
		}

		std::sort(possiblePoints.begin(), possiblePoints.end(), VegetatedModel::PointSort);

		// First VEG_MODEL_ENTITY_COUNT as we want to show only the closest point
		if (possiblePoints.size() > VEG_MODEL_ENTITY_COUNT)
		{
			possiblePoints.erase(possiblePoints.begin() + VEG_MODEL_ENTITY_COUNT, possiblePoints.end());
		}
		
		std::vector<VegetatedEntity*> entitiesNeedingPoints;
		std::vector<Vec3> pointsToRemove;
		int Count = Math::Min(possiblePoints.size(), VEG_MODEL_ENTITY_COUNT);
		for (int i = 0 ; i < Count; i++)
		{
			VegetatedEntity* vegEntity = this->realEntities[i];
			Entity* ent = vegEntity->GetEntity();
			
			bool found = false;
			for (int j = 0; j < Count; j++)
			{
				Vec3 posit = possiblePoints[j];
				if (vegEntity->GetVegetationPosition() == posit)
				{
					found = true;
					pointsToRemove.push_back(posit);
					break;
				}
			}

			if (!found)
			{
				entitiesNeedingPoints.push_back(vegEntity);
			}
		}

		std::vector<Vec3> newPossiblePoints;
		for (int i = 0; i < possiblePoints.size(); i++)
		{
			Vec3 possiblePoint = possiblePoints[i];
			bool found = false;
			for (auto it = pointsToRemove.begin(); it != pointsToRemove.end(); it++)
			{
				Vec3 pointRemove = *it;
				if (possiblePoint == pointRemove)
				{
					found = true;
					break;
				}
			}

			if (!found)
			{
				newPossiblePoints.push_back(possiblePoint);
			}
		}
		possiblePoints = newPossiblePoints;

		int numEntitiesToSet = Math::Min(possiblePoints.size(), entitiesNeedingPoints.size());
		for (int i = 0; i < numEntitiesToSet; i++)
		{
			VegetatedEntity* vegEntity = entitiesNeedingPoints[i];
			Entity* ent = vegEntity->GetEntity();
			WFModel* model = vegEntity->GetWFModel();

			Vec3 point = possiblePoints[i];
			vegEntity->SetVegetationPosition(point);
			model->Reset();
		}

		// Remove unused entities
		for (unsigned int i = numEntitiesToSet; i < entitiesNeedingPoints.size(); i++)
		{
			VegetatedEntity* vegEntity = entitiesNeedingPoints[i];
			Entity* ent = vegEntity->GetEntity();
			//vegEntity->SetVegetationPosition(Vec3(0, -123, 0));
			//ent->Hide();
		}
	}

	bool VegetatedModel::PointSort(Vec3 posit1, Vec3 posit2)
	{
		Player* p = Player::GetPlayer();
		if (p == NULL)
		{
			return 0;
		}

		double d1 = p->GetPosition().DistanceToPoint(posit1);
		double d2 = p->GetPosition().DistanceToPoint(posit2);

		return d1 < d2;
	}



 

 

This looks cool but it seems it may not be that efficient? If I'm reading it correctly you're loading the tree model for every vegetation model that exists on startup. Then inside each of those models you're checking the distance to the player to determine if you should do something. Just thinking that's a lot of checks happening per game loop. Could you not go the opposite direction and do AABB check in a range around the player, then loop only through those to make them real models vs veg? Keep that list so the next iteration when you loop again you can tell which ones are no longer in the list and change those back to veg vs model? Seems like you'd loop through a lot less veg overall each iteration.

 

Where are you able to manipulate the veg system to hide specific trees? I didn't think they were accessible that way?

 

 

Link to comment
Share on other sites


 

Quote

This looks cool but it seems it may not be that efficient? If I'm reading it correctly you're loading the tree model for every vegetation model that exists on startup. Then inside each of those models you're checking the distance to the player to determine if you should do something. Just thinking that's a lot of checks happening per game loop. Could you not go the opposite direction and do AABB check in a range around the player, then loop only through those to make them real models vs veg? Keep that list so the next iteration when you loop again you can tell which ones are no longer in the list and change those back to veg vs model? Seems like you'd loop through a lot less veg overall each iteration.

 

Where are you able to manipulate the veg system to hide specific trees? I didn't think they were accessible that way?


We're not loading the model for tree every vegetation model. We do a model->Instnace. Have a look at VegetatedModel class. It loads VEG_MODEL_ENTITY_COUNT number of models via model->Instance.

For your second comment we do an AABB check around the player position like you have suggested. We find all entities in a "boxSize" around the player.

 

AABB scope = AABB(playerPosit.x-boxSizeX, -1000, playerPosit.z-boxSizeZ, playerPosit.x+boxSizeX, 1000, playerPosit.z+boxSizeZ);



For your last question, we hide everything on the Veg System and use real Instanced models instead.

 

 

====================

 

 

Edit:

 

I see what you're saying.

This code can be removed.

 

AABB aabb = world->aabb;
			std::vector<Mat4> instances;
			layer->GetInstancesInAABB(aabb, instances);
			System::Print("Loading Layer: " + parentName + " of Size: " + std::to_string(instances.size()));
			for (unsigned int j = 0; j < instances.size(); j++)
			{
				Mat4 instance = instances[j];

				Entity* copy = model->Instance(true, false);
				copy->SetMatrix(instance, true);

				copy->SetScale(scale);
			}

 

Link to comment
Share on other sites

13 hours ago, martyj said:

For your last question, we hide everything on the Veg System and use real Instanced models instead.

So you're using the veg system for positions/rotation only? If you are replacing every tree in the veg system with an instance of a model, that's what you said right? I would think you would lose performance of the veg system then if you did that right as that has billboards and such where your model instances wouldn't or are you doing your own LOD type thing and that's what the distance check is for?

 

I was thinking you were replacing the veg trees to instance models JUST around the player since if possible that would really be all you need to do, but the problem is being able to tab into the veg system to do something like that.

Link to comment
Share on other sites

3 hours ago, Rick said:

So you're using the veg system for positions/rotation only? If you are replacing every tree in the veg system with an instance of a model, that's what you said right? I would think you would lose performance of the veg system then if you did that right as that has billboards and such where your model instances wouldn't or are you doing your own LOD type thing and that's what the distance check is for?

We do loose some performance no doubt as w use Entity objects rather than billboards. We also loose some range as well due to this as well. You're correct that we use the veg system for position and rotation. If someone wanted to, they could extend the radius around this using custom LOD models to provide more of a "billboard" affect.

 

Quote

I was thinking you were replacing the veg trees to instance models JUST around the player since if possible that would really be all you need to do, but the problem is being able to tab into the veg system to do something like that.


We do "replace" vegetation items around the player only, but you can't mix the two. With Leadwerks you cannot hide a specific vegetation object. You either hide everything or nothing.


Benefits:
1. Allows animated models to be placed as easily as vegetation
2. Allows us to hide/show specific vegetation trees as players interact with them

Cons:
1. Less distance with vegetation since we don't support billboards
2. Performance hit of trading 1 vegetation layer vs 50 Entity objects.


World Factions uses this technique for 2 use cases.

1. Players can cut down any tree in the game. This allows us to place trees with the vegetation system, but provides user interactive content. The tree that the player cuts down is made of two Entities. A top half of tree, and a bottom trunk. When the player cuts the tree, we enable physics on the Tree and provide a random force on the top half to "cut it down"

2. Animals in the game. These are animated models with a small amount of AI added to them. We use the vegetation information for starting point. Players can kill specific animals so we need to hide them.



 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...