diff --git a/assets/Data/entity/creatures/human.json b/assets/Data/entity/creatures/human.json index 904e0c9b..a18534d6 100644 --- a/assets/Data/entity/creatures/human.json +++ b/assets/Data/entity/creatures/human.json @@ -588,7 +588,8 @@ "priorityCategory" : "IDLE" } }, - "path" : "Models/creatures/person2/person2_1.glb" + "path" : "Models/creatures/person2/person2_1.glb", + "lodPath": "Models/creatures/person2/person2_1_lod.glb" } }, "viewModelData" : { diff --git a/assets/Models/creatures/person2/person2_1_lod.glb b/assets/Models/creatures/person2/person2_1_lod.glb new file mode 100644 index 00000000..c60aac60 Binary files /dev/null and b/assets/Models/creatures/person2/person2_1_lod.glb differ diff --git a/src/main/java/electrosphere/collision/PhysicsEntityUtils.java b/src/main/java/electrosphere/collision/PhysicsEntityUtils.java index 4c2ff63a..4f7a3ff1 100644 --- a/src/main/java/electrosphere/collision/PhysicsEntityUtils.java +++ b/src/main/java/electrosphere/collision/PhysicsEntityUtils.java @@ -68,6 +68,7 @@ public class PhysicsEntityUtils { if(physicsTemplate.getKinematic()){ categoryBit = Collidable.TYPE_STATIC_BIT; } + CollisionEngine.lockOde(); if(physicsTemplate.getKinematic()){ DGeom geom = null; switch(physicsTemplate.getType()){ @@ -364,6 +365,7 @@ public class PhysicsEntityUtils { ClientPhysicsSyncTree.attachTree(rVal); } } + CollisionEngine.unlockOde(); } @@ -383,6 +385,7 @@ public class PhysicsEntityUtils { if(physicsTemplate.getKinematic()){ categoryBit = Collidable.TYPE_STATIC_BIT; } + CollisionEngine.lockOde(); if(physicsTemplate.getKinematic()){ DGeom geom = null; switch(physicsTemplate.getType()){ @@ -686,6 +689,7 @@ public class PhysicsEntityUtils { ServerPhysicsSyncTree.attachTree(rVal); } } + CollisionEngine.unlockOde(); } diff --git a/src/main/java/electrosphere/data/entity/graphics/NonproceduralModel.java b/src/main/java/electrosphere/data/entity/graphics/NonproceduralModel.java index dd29d208..ebb46725 100644 --- a/src/main/java/electrosphere/data/entity/graphics/NonproceduralModel.java +++ b/src/main/java/electrosphere/data/entity/graphics/NonproceduralModel.java @@ -31,6 +31,11 @@ public class NonproceduralModel { */ private Map meshColorMap; + /** + * The path to the lower-resolution model + */ + private String lodPath; + /** * Gets the path of the model * @return The path of the model @@ -94,6 +99,22 @@ public class NonproceduralModel { public void setMeshColorMap(Map meshColorMap) { this.meshColorMap = meshColorMap; } + + /** + * Gets the path to the lower resolution model + * @return The path to the lower resolution model + */ + public String getLODPath(){ + return lodPath; + } + + /** + * Sets the path to the lower resolution model + * @param lodPath The path to the lower resolution model + */ + public void setLODPath(String lodPath){ + this.lodPath = lodPath; + } } diff --git a/src/main/java/electrosphere/entity/DrawableUtils.java b/src/main/java/electrosphere/entity/DrawableUtils.java index 767f23af..bde0f546 100644 --- a/src/main/java/electrosphere/entity/DrawableUtils.java +++ b/src/main/java/electrosphere/entity/DrawableUtils.java @@ -1,5 +1,14 @@ package electrosphere.entity; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.joml.Vector3f; + +import electrosphere.data.entity.graphics.NonproceduralModel; +import electrosphere.engine.Globals; +import electrosphere.entity.state.idle.ClientIdleTree; import electrosphere.renderer.actor.Actor; /** @@ -36,4 +45,55 @@ public class DrawableUtils { return entity.containsKey(EntityDataStrings.HAS_UNIQUE_MODEL); } + /** + * Applies non-procedural model data to the entity + * @param entity The entity + * @param modelData The model data + */ + public static void applyNonproceduralModel(Entity entity, NonproceduralModel modelData){ + if(modelData == null){ + throw new Error("Null model data!"); + } + if(modelData.getPath() == null){ + throw new Error("Path undefined!"); + } + //make the entity drawable + EntityCreationUtils.makeEntityDrawable(entity, modelData.getPath()); + Actor creatureActor = EntityUtils.getActor(entity); + + //add low-res model path to actor if present + if(modelData.getLODPath() != null){ + creatureActor.setLowResPath(modelData.getLODPath()); + Globals.assetManager.addModelPathToQueue(modelData.getLODPath()); + } + + + //idle tree & generic stuff all entities have + if(modelData.getIdleData() != null){ + ClientIdleTree.attachTree(entity, modelData.getIdleData()); + } + + //apply uniforms + if(modelData.getUniforms() != null){ + Map> meshUniformMap = modelData.getUniforms(); + Set meshNames = meshUniformMap.keySet(); + for(String meshName : meshNames){ + Map uniforms = meshUniformMap.get(meshName); + Set uniformNames = uniforms.keySet(); + for(String uniformName : uniformNames){ + Object value = uniforms.get(uniformName); + creatureActor.setUniformOnMesh(meshName, uniformName, value); + } + } + } + + //apply mesh color map + if(modelData.getMeshColorMap() != null){ + Map meshColorMap = modelData.getMeshColorMap(); + for(Entry entry : meshColorMap.entrySet()){ + creatureActor.setUniformOnMesh(entry.getKey(), "color", entry.getValue()); + } + } + } + } diff --git a/src/main/java/electrosphere/entity/types/common/CommonEntityUtils.java b/src/main/java/electrosphere/entity/types/common/CommonEntityUtils.java index 31a5df5b..f0efdbac 100644 --- a/src/main/java/electrosphere/entity/types/common/CommonEntityUtils.java +++ b/src/main/java/electrosphere/entity/types/common/CommonEntityUtils.java @@ -1,12 +1,7 @@ package electrosphere.entity.types.common; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - import org.joml.Quaterniond; import org.joml.Vector3d; -import org.joml.Vector3f; import org.ode4j.ode.DBody; import electrosphere.client.interact.ClientInteractionEngine; @@ -31,6 +26,7 @@ import electrosphere.data.entity.graphics.GraphicsTemplate; import electrosphere.data.entity.item.Item; import electrosphere.engine.Globals; import electrosphere.engine.assetmanager.PhysicsMeshQueueItem; +import electrosphere.entity.DrawableUtils; import electrosphere.entity.Entity; import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityDataStrings; @@ -55,7 +51,6 @@ import electrosphere.entity.state.gravity.ServerGravityTree; import electrosphere.entity.state.growth.ClientGrowthComponent; import electrosphere.entity.state.growth.ServerGrowthComponent; import electrosphere.entity.state.hitbox.HitboxCollectionState; -import electrosphere.entity.state.idle.ClientIdleTree; import electrosphere.entity.state.idle.ServerIdleTree; import electrosphere.entity.state.inventory.ClientInventoryState; import electrosphere.entity.state.inventory.InventoryUtils; @@ -169,32 +164,8 @@ public class CommonEntityUtils { // if(rawType.getGraphicsTemplate() != null){ GraphicsTemplate graphicsTemplate = rawType.getGraphicsTemplate(); - if(graphicsTemplate.getModel() != null && graphicsTemplate.getModel().getPath() != null && EntityUtils.getActor(entity) == null && generateDrawable == true){ - EntityCreationUtils.makeEntityDrawable(entity, graphicsTemplate.getModel().getPath()); - } - //idle tree & generic stuff all entities have - if(graphicsTemplate.getModel() != null && graphicsTemplate.getModel().getIdleData() != null){ - ClientIdleTree.attachTree(entity, graphicsTemplate.getModel().getIdleData()); - } - if(graphicsTemplate.getModel() != null && graphicsTemplate.getModel().getUniforms() != null){ - Actor creatureActor = EntityUtils.getActor(entity); - Map> meshUniformMap = graphicsTemplate.getModel().getUniforms(); - Set meshNames = meshUniformMap.keySet(); - for(String meshName : meshNames){ - Map uniforms = meshUniformMap.get(meshName); - Set uniformNames = uniforms.keySet(); - for(String uniformName : uniformNames){ - Object value = uniforms.get(uniformName); - creatureActor.setUniformOnMesh(meshName, uniformName, value); - } - } - } - if(graphicsTemplate.getModel() != null && graphicsTemplate.getModel().getMeshColorMap() != null){ - Actor creatureActor = EntityUtils.getActor(entity); - Map meshColorMap = graphicsTemplate.getModel().getMeshColorMap(); - for(Entry entry : meshColorMap.entrySet()){ - creatureActor.setUniformOnMesh(entry.getKey(), "color", entry.getValue()); - } + if(graphicsTemplate.getModel() != null && EntityUtils.getActor(entity) == null && generateDrawable == true){ + DrawableUtils.applyNonproceduralModel(entity, graphicsTemplate.getModel()); } } Actor creatureActor = EntityUtils.getActor(entity); diff --git a/src/main/java/electrosphere/renderer/actor/Actor.java b/src/main/java/electrosphere/renderer/actor/Actor.java index bf28f8e2..9a696b1b 100644 --- a/src/main/java/electrosphere/renderer/actor/Actor.java +++ b/src/main/java/electrosphere/renderer/actor/Actor.java @@ -44,11 +44,32 @@ public class Actor { */ public static final int INVALID_ANIMATION = -1; + /** + * full-resolution lod level + */ + public static final int LOD_LEVEL_FULL = 1; + + /** + * lower-resolution lod level + */ + public static final int LOD_LEVEL_LOWER = 0; + + /** * the model path of the model backing the actor */ private String modelPath; + /** + * Path to the low res model + */ + private String lowResPath; + + /** + * The LOD level of the actor + */ + private int lodLevel = Actor.LOD_LEVEL_FULL; + /** * used for statically binding textures in pipelines that dont bind textures on a per-mesh level * ie when in the ui, this can be set to bind a texture at the actor level @@ -124,6 +145,16 @@ public class Actor { * Sets scaling applied at the actor level */ private float scale = 1.0f; + + /** + * The transform matrix + */ + private Matrix4d modelMatrix = new Matrix4d(); + + /** + * The world position vector + */ + private Vector3d worldPos = new Vector3d(); /** * Creates an achor @@ -438,7 +469,7 @@ public class Actor { * Calculates the node transforms for the actor * @param model The model that backs the actor */ - void calculateNodeTransforms(Model model){ + private void calculateNodeTransforms(Model model){ model.updateNodeTransform(boneRotators,staticMorph); for(Bone bone : model.getBones()){ //store position @@ -478,8 +509,8 @@ public class Actor { public void applySpatialData(Matrix4d modelMatrix, Vector3d worldPos){ Model model = Globals.assetManager.fetchModel(modelPath); if(model != null){ - model.setModelMatrix(modelMatrix); - model.setWorldPos(worldPos); + this.modelMatrix.set(modelMatrix); + this.worldPos.set(worldPos); } } @@ -489,14 +520,29 @@ public class Actor { */ public void draw(RenderPipelineState renderPipelineState, OpenGLState openGLState){ Globals.profiler.beginAggregateCpuSample("Actor.draw"); - Model model = Globals.assetManager.fetchModel(modelPath); - if(model != null && Actor.isWithinFrustumBox(renderPipelineState,model,frustumCull)){ + + //fetch the model + String pathToFetch = this.modelPath; + if(this.lodLevel == Actor.LOD_LEVEL_LOWER && this.lowResPath != null){ + pathToFetch = this.lowResPath; + } + Model model = Globals.assetManager.fetchModel(pathToFetch); + + if(model == null){ + Globals.profiler.endCpuSample(); + return; + } + model.setModelMatrix(this.modelMatrix); + model.setWorldPos(this.worldPos); + + //frustum cull then draw + if(Actor.isWithinFrustumBox(renderPipelineState,model,frustumCull)){ this.applyAnimationMasks(model); meshMask.processMeshMaskQueue(); model.setMeshMask(meshMask); model.setTextureMask(textureMap); for(ActorShaderMask shaderMask : shaderMasks){ - if(shaderMask.getModelName().equals(modelPath)){ + if(shaderMask.getModelName().equals(pathToFetch)){ model.getShaderMask().put(shaderMask.getMeshName(),shaderMask); } } @@ -534,13 +580,18 @@ public class Actor { */ public boolean isStaticDrawCall(){ return - this.animationQueue.size() == 0 && - this.meshMask.getBlockedMeshes().size() == 0 && - this.textureMap == null && - this.shaderMasks.size() == 0 && - this.textureOverride == null && - this.uniformMap.isEmpty() && - this.staticMorph == null + //is low lod level + this.lodLevel == Actor.LOD_LEVEL_LOWER || + //actor doesn't have anything complicated render-wise (animations, custom textures, etc) + ( + this.animationQueue.size() == 0 && + this.meshMask.getBlockedMeshes().size() == 0 && + this.textureMap == null && + this.shaderMasks.size() == 0 && + this.textureOverride == null && + this.uniformMap.isEmpty() && + this.staticMorph == null + ) ; } @@ -873,4 +924,37 @@ public class Actor { this.scale = scale; } + /** + * Gets the path to the low res model + * @return Path to the low res model + */ + public String getLowResPath() { + return lowResPath; + } + + /** + * Sets the path to the low res model + * @param lowResPath The path to the low res model + */ + public void setLowResPath(String lowResPath) { + this.lowResPath = lowResPath; + } + + /** + * Gets the lod level + * @return The lod level + */ + public int getLodLevel(){ + return this.lodLevel; + } + + /** + * Sets the lod level of the actor + * @param lodLevel The lod level + */ + public void setLodLevel(int lodLevel){ + this.lodLevel = lodLevel; + } + + } diff --git a/src/main/java/electrosphere/renderer/target/DrawTargetEvaluator.java b/src/main/java/electrosphere/renderer/target/DrawTargetEvaluator.java index 592fe619..4926d3ca 100644 --- a/src/main/java/electrosphere/renderer/target/DrawTargetEvaluator.java +++ b/src/main/java/electrosphere/renderer/target/DrawTargetEvaluator.java @@ -19,6 +19,12 @@ import electrosphere.renderer.pipelines.MainContentPipeline; * Evaluates the draw targets */ public class DrawTargetEvaluator { + + + /** + * Cutoff after which we start using LOD models + */ + public static final int LOD_CUTOFF = 100; /** * Evaluates the draw targets @@ -54,6 +60,19 @@ public class DrawTargetEvaluator { Vector3d position = EntityUtils.getPosition(currentEntity); //fetch actor Actor currentActor = EntityUtils.getActor(currentEntity); + + //evaluate LOD level + if(currentActor.getLowResPath() != null){ + posVec.set(position); + double dist = posVec.distance(cameraCenter); + if(dist < LOD_CUTOFF){ + currentActor.setLodLevel(Actor.LOD_LEVEL_FULL); + } else { + currentActor.setLodLevel(Actor.LOD_LEVEL_LOWER); + } + } + + //queue calls accordingly if(!DrawableUtils.hasUniqueModel(currentEntity) && currentActor.isStaticDrawCall()){ //calculate camera-modified vector3d Vector3d cameraModifiedPosition = positionVec.set(position).sub(cameraCenter);