Vince Posted September 1, 2019 Share Posted September 1, 2019 Just wondering how i'd access a vegetation object so I can assign stats via an array and toggle visible true/false and make the tree/bush fall/hide it when a user interacts with it with an axe. Quote Link to comment Share on other sites More sharing options...
gamecreator Posted September 1, 2019 Share Posted September 1, 2019 Welcome to the forums. Unfortunately this was asked before and the answer is that you can't because Leadwerks has a custom vegetation system. You would have to place models manually but I don't know how slow that would be. Quote Link to comment Share on other sites More sharing options...
martyj Posted September 8, 2019 Share Posted September 8, 2019 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; } 4 Quote Link to comment Share on other sites More sharing options...
gamecreator Posted September 8, 2019 Share Posted September 8, 2019 Whoa! I didn't know you could do that! Very cool and thank you for sharing the code. Quote Link to comment Share on other sites More sharing options...
Rick Posted September 9, 2019 Share Posted September 9, 2019 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? Quote Link to comment Share on other sites More sharing options...
martyj Posted September 10, 2019 Share Posted September 10, 2019 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); } Quote Link to comment Share on other sites More sharing options...
Rick Posted September 10, 2019 Share Posted September 10, 2019 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. Quote Link to comment Share on other sites More sharing options...
martyj Posted September 11, 2019 Share Posted September 11, 2019 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. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.