package net.mgsx.gltf.scene3d.scene; import com.badlogic.gdx.graphics.Camera; import com.badlogic.gdx.graphics.g3d.Attribute; import com.badlogic.gdx.graphics.g3d.Environment; import com.badlogic.gdx.graphics.g3d.ModelBatch; import com.badlogic.gdx.graphics.g3d.RenderableProvider; import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; import com.badlogic.gdx.graphics.g3d.attributes.DirectionalLightsAttribute; import com.badlogic.gdx.graphics.g3d.attributes.PointLightsAttribute; import com.badlogic.gdx.graphics.g3d.attributes.SpotLightsAttribute; import com.badlogic.gdx.graphics.g3d.environment.BaseLight; import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight; import com.badlogic.gdx.graphics.g3d.environment.PointLight; import com.badlogic.gdx.graphics.g3d.environment.SpotLight; import com.badlogic.gdx.graphics.g3d.model.Node; import com.badlogic.gdx.graphics.g3d.utils.DepthShaderProvider; import com.badlogic.gdx.graphics.g3d.utils.RenderableSorter; import com.badlogic.gdx.graphics.g3d.utils.ShaderProvider; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Disposable; import com.badlogic.gdx.utils.ObjectMap.Entry; import net.mgsx.gltf.scene3d.lights.DirectionalShadowLight; import net.mgsx.gltf.scene3d.lights.PointLightEx; import net.mgsx.gltf.scene3d.lights.SpotLightEx; import net.mgsx.gltf.scene3d.shaders.PBRCommon; import net.mgsx.gltf.scene3d.shaders.PBRShaderProvider; import net.mgsx.gltf.scene3d.utils.EnvironmentCache; import net.mgsx.gltf.scene3d.utils.EnvironmentUtil; /** * Convient manager class for: model instances, animators, camera, environment, lights, batch/shaderProvider * * @author mgsx * */ public class SceneManager implements Disposable { private final Array<RenderableProvider> renderableProviders = new Array<RenderableProvider>(); private ModelBatch batch; private ModelBatch depthBatch; private SceneSkybox skyBox; /** Shouldn't be null. */ public Environment environment = new Environment(); protected final EnvironmentCache computedEnvironement = new EnvironmentCache(); public Camera camera; private RenderableSorter renderableSorter; private PointLightsAttribute pointLights = new PointLightsAttribute(); private SpotLightsAttribute spotLights = new SpotLightsAttribute(); public SceneManager() { this(24); } public SceneManager(int maxBones) { this(PBRShaderProvider.createDefault(maxBones), PBRShaderProvider.createDefaultDepth(maxBones)); } public SceneManager(ShaderProvider shaderProvider, DepthShaderProvider depthShaderProvider) { this(shaderProvider, depthShaderProvider, new SceneRenderableSorter()); } public SceneManager(ShaderProvider shaderProvider, DepthShaderProvider depthShaderProvider, RenderableSorter renderableSorter) { this.renderableSorter = renderableSorter; batch = new ModelBatch(shaderProvider, renderableSorter); depthBatch = new ModelBatch(depthShaderProvider); float lum = 1f; environment.set(new ColorAttribute(ColorAttribute.AmbientLight, lum, lum, lum, 1)); } public ModelBatch getBatch() { return batch; } public void setBatch(ModelBatch batch) { this.batch = batch; } public void setShaderProvider(ShaderProvider shaderProvider) { batch.dispose(); batch = new ModelBatch(shaderProvider, renderableSorter); } public void setDepthShaderProvider(DepthShaderProvider depthShaderProvider) { depthBatch.dispose(); depthBatch = new ModelBatch(depthShaderProvider); } public void addScene(Scene scene){ addScene(scene, true); } public void addScene(Scene scene, boolean appendLights){ renderableProviders.add(scene); if(appendLights){ for(Entry<Node, BaseLight> e : scene.lights){ environment.add(e.value); } } } /** * should be called in order to perform light culling, skybox update and animations. * @param delta */ public void update(float delta){ if(camera != null){ updateEnvironment(); for(RenderableProvider r : renderableProviders){ if(r instanceof Updatable){ ((Updatable) r).update(camera, delta); } } if(skyBox != null) skyBox.update(camera, delta); } } protected void updateEnvironment(){ computedEnvironement.setCache(environment); pointLights.lights.clear(); spotLights.lights.clear(); if(environment != null) { for(Attribute a : environment){ if(a instanceof PointLightsAttribute){ pointLights.lights.addAll(((PointLightsAttribute) a).lights); computedEnvironement.replaceCache(pointLights); }else if(a instanceof SpotLightsAttribute){ spotLights.lights.addAll(((SpotLightsAttribute) a).lights); computedEnvironement.replaceCache(spotLights); }else{ computedEnvironement.set(a); } } } cullLights(); } protected void cullLights(){ PointLightsAttribute pla = environment.get(PointLightsAttribute.class, PointLightsAttribute.Type); if(pla != null){ for(PointLight light : pla.lights){ if(light instanceof PointLightEx){ PointLightEx l = (PointLightEx) light; if(l.range != null && !camera.frustum.sphereInFrustum(l.position, l.range)){ pointLights.lights.removeValue(l, true); } } } } SpotLightsAttribute sla = environment.get(SpotLightsAttribute.class, SpotLightsAttribute.Type); if(sla != null){ for(SpotLight light : sla.lights){ if(light instanceof SpotLightEx){ SpotLightEx l = (SpotLightEx) light; if(l.range != null && !camera.frustum.sphereInFrustum(l.position, l.range)){ spotLights.lights.removeValue(l, true); } } } } } /** * render all scenes. * because shadows use frame buffers, if you need to render scenes to a frame buffer, you should instead * first call {@link #renderShadows()}, bind your frame buffer and then call {@link #renderColors()} */ public void render(){ if(camera == null) return; renderShadows(); renderColors(); } /** * Render shadows only to interal frame buffers. * (useful when you're using your own frame buffer to render scenes) */ @SuppressWarnings("deprecation") public void renderShadows(){ DirectionalLight light = getFirstDirectionalLight(); if(light instanceof DirectionalShadowLight){ DirectionalShadowLight shadowLight = (DirectionalShadowLight)light; shadowLight.begin(); renderDepth(shadowLight.getCamera()); shadowLight.end(); environment.shadowMap = shadowLight; }else{ environment.shadowMap = null; } } /** * Render only depth (packed 32 bits), usefull for post processing effects. * You typically render it to a FBO with depth enabled. */ public void renderDepth(){ renderDepth(camera); } private void renderDepth(Camera camera){ depthBatch.begin(camera); depthBatch.render(renderableProviders); depthBatch.end(); } /** * Render colors only. You should call {@link #renderShadows()} before. * (useful when you're using your own frame buffer to render scenes) */ public void renderColors(){ PBRCommon.enableSeamlessCubemaps(); computedEnvironement.shadowMap = environment.shadowMap; batch.begin(camera); batch.render(renderableProviders, computedEnvironement); if(skyBox != null) batch.render(skyBox); batch.end(); } public DirectionalLight getFirstDirectionalLight(){ DirectionalLightsAttribute dla = environment.get(DirectionalLightsAttribute.class, DirectionalLightsAttribute.Type); if(dla != null){ for(DirectionalLight dl : dla.lights){ if(dl instanceof DirectionalLight){ return (DirectionalLight)dl; } } } return null; } public void setSkyBox(SceneSkybox skyBox) { this.skyBox = skyBox; } public void setAmbientLight(float lum) { environment.get(ColorAttribute.class, ColorAttribute.AmbientLight).color.set(lum, lum, lum, 1); } public void setCamera(Camera camera) { this.camera = camera; } public void removeScene(Scene scene) { renderableProviders.removeValue(scene, true); for(Entry<Node, BaseLight> e : scene.lights){ environment.remove(e.value); } } public Array<RenderableProvider> getRenderableProviders() { return renderableProviders; } public void updateViewport(int width, int height) { if(camera != null){ camera.viewportWidth = width; camera.viewportHeight = height; camera.update(true); } } public int getActiveLightsCount(){ return EnvironmentUtil.getLightCount(computedEnvironement); } public int getTotalLightsCount(){ return EnvironmentUtil.getLightCount(environment); } @Override public void dispose() { batch.dispose(); depthBatch.dispose(); } }