de.javagl.jgltf.viewer.AbstractGltfViewer Maven / Gradle / Ivy
/*
* www.javagl.de - JglTF
*
* Copyright 2015-2016 Marco Hutter - http://www.javagl.de
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package de.javagl.jgltf.viewer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
import java.util.logging.Logger;
import de.javagl.jgltf.model.CameraModel;
import de.javagl.jgltf.model.GltfAnimations;
import de.javagl.jgltf.model.GltfModel;
import de.javagl.jgltf.model.NodeModel;
import de.javagl.jgltf.model.Optionals;
import de.javagl.jgltf.model.animation.Animation;
import de.javagl.jgltf.model.animation.AnimationManager;
import de.javagl.jgltf.model.animation.AnimationManager.AnimationPolicy;
import de.javagl.jgltf.model.animation.AnimationRunner;
/**
* Abstract base implementation of a {@link GltfViewer}
*
* @param The type of the render component of this viewer
*/
public abstract class AbstractGltfViewer implements GltfViewer
{
/**
* The logger used in this class
*/
private static final Logger logger =
Logger.getLogger(AbstractGltfViewer.class.getName());
/**
* A supplier of the viewport size. This will be passed to the
* {@link RenderedGltfModel} constructor, and eventually provide the data
* for the uniforms that have the VIEWPORT
semantic.
*/
private final Supplier viewportSupplier = new Supplier()
{
private final float viewport[] = new float[4];
@Override
public float[] get()
{
viewport[0] = 0;
viewport[1] = 0;
viewport[2] = getWidth();
viewport[3] = getHeight();
return viewport;
}
};
/**
* A supplier for the aspect ratio. This will provide the aspect ratio
* of the rendering window. (If this was null
,
* then the aspect ratio of the glTF camera would be
* used, but this would hardly ever match the actual aspect
* ratio of the rendering component...)
*/
private final DoubleSupplier aspectRatioSupplier = () ->
{
return (double)getWidth() / getHeight();
};
/**
* The {@link ViewConfiguration}
*/
private final ViewConfiguration viewConfiguration;
/**
* Tasks that have to be executed before the next rendering pass,
* on the rendering thread
*/
private final List beforeRenderTasks;
/**
* The map from {@link GltfModel} instances to their
* {@link RenderedGltfModel} counterparts
*/
private final Map renderedGltfModels;
/**
* The map from {@link GltfModel} instances to all
* {@link RenderedCamera} instances that have been
* created for them
*/
private final Map> modelRenderedCameras;
/**
* The list of {@link GltfModel} instances that have been added.
*/
private final List gltfModels;
/**
* The {@link AnimationManager}
*/
private final AnimationManager animationManager;
/**
* The {@link AnimationRunner}
*/
private final AnimationRunner animationRunner;
/**
* The map from {@link GltfModel} instances to the lists of
* model {@link Animation}s that have been created for the
* glTF animations
*/
private final Map> modelAnimations;
/**
* Default constructor
*/
protected AbstractGltfViewer()
{
this.viewConfiguration =
new ViewConfiguration(viewportSupplier);
this.beforeRenderTasks = Collections.synchronizedList(
new ArrayList());
this.renderedGltfModels =
new LinkedHashMap();
this.modelRenderedCameras =
new LinkedHashMap>();
this.gltfModels = new ArrayList();
this.animationManager =
GltfAnimations.createAnimationManager(AnimationPolicy.LOOP);
this.animationManager.addAnimationManagerListener(a ->
{
triggerRendering();
});
this.animationRunner = new AnimationRunner(animationManager);
this.modelAnimations = new LinkedHashMap>();
setAnimationsRunning(true);
}
@Override
public final void setRenderedCamera(RenderedCamera renderedCamera)
{
viewConfiguration.setRenderedCamera(renderedCamera);
triggerRendering();
}
@Override
public final void setAnimationsRunning(boolean running)
{
if (running)
{
animationRunner.start();
}
else
{
animationRunner.stop();
}
}
@Override
public abstract C getRenderComponent();
/**
* Returns the {@link GlContext} of this viewer
*
* @return The {@link GlContext} of this viewer
*/
protected abstract GlContext getGlContext();
@Override
public final void addGltfModel(GltfModel gltfModel)
{
Objects.requireNonNull(gltfModel, "The gltfModel may not be null");
gltfModels.add(gltfModel);
addBeforeRenderTask(() -> createRenderedGltf(gltfModel));
triggerRendering();
List renderedCameras = createRenderedCameras(gltfModel);
modelRenderedCameras.put(gltfModel, renderedCameras);
// If no camera has been defined, set the current camera
// to be the first camera of the given model.
if (viewConfiguration.getRenderedCamera() == null)
{
if (!renderedCameras.isEmpty())
{
RenderedCamera renderedCamera = renderedCameras.get(0);
viewConfiguration.setRenderedCamera(renderedCamera);
}
}
}
/**
* Create a {@link RenderedGltfModel} for the given {@link GltfModel}, and
* store it in the {@link #renderedGltfModels} map
*
* @param gltfModel The {@link GltfModel}
*/
private void createRenderedGltf(GltfModel gltfModel)
{
logger.info("Creating rendered glTF");
GlContext glContext = getGlContext();
RenderedGltfModel renderedGltfModel = new DefaultRenderedGltfModel(
glContext, gltfModel, viewConfiguration);
renderedGltfModels.put(gltfModel, renderedGltfModel);
List currentModelAnimations =
GltfAnimations.createModelAnimations(
gltfModel.getAnimationModels());
modelAnimations.put(gltfModel, currentModelAnimations);
animationManager.addAnimations(currentModelAnimations);
}
@Override
public void removeGltfModel(GltfModel gltfModel)
{
Objects.requireNonNull(gltfModel, "The gltfModel may not be null");
gltfModels.remove(gltfModel);
modelRenderedCameras.remove(gltfModel);
addBeforeRenderTask(() -> deleteRenderedGltfModel(gltfModel));
List currentModelAnimations = modelAnimations.get(gltfModel);
if (currentModelAnimations != null)
{
animationManager.removeAnimations(currentModelAnimations);
}
modelAnimations.remove(gltfModel);
triggerRendering();
}
/**
* Delete the {@link RenderedGltfModel} that is associated with the given
* {@link GltfModel}
*
* @param gltfModel The {@link GltfModel}
*/
private void deleteRenderedGltfModel(GltfModel gltfModel)
{
RenderedGltfModel renderedGltfModel = renderedGltfModels.get(gltfModel);
if (renderedGltfModel == null)
{
logger.warning(
"No renderedGltfModel found for gltfModel " + gltfModel);
return;
}
logger.info("Deleting rendered glTF");
renderedGltfModel.delete();
renderedGltfModels.remove(gltfModel);
}
/**
* Create all {@link RenderedCamera} instances that can be created from
* the given model. This will create one {@link RenderedCamera} instance
* for each node that refers to a camera.
*
* @param gltfModel The {@link GltfModel}
* @return The {@link RenderedCamera} instances
*/
private List createRenderedCameras(GltfModel gltfModel)
{
List renderedCameras = new ArrayList();
List nodeModels = gltfModel.getNodeModels();
List cameraModels = gltfModel.getCameraModels();
for (int i = 0; i < nodeModels.size(); i++)
{
NodeModel nodeModel = nodeModels.get(i);
CameraModel cameraModel = nodeModel.getCameraModel();
if (cameraModel != null)
{
int cameraIndex = cameraModels.indexOf(cameraModel);
String nodeName = Optionals.of(
nodeModel.getName(), "node " + i);
String cameraName = Optionals.of(
cameraModel.getName(), "camera " + cameraIndex);
String name = cameraName + " at " + nodeName;
RenderedCamera renderedCamera = new DefaultRenderedCamera(name,
nodeModel, cameraModel, aspectRatioSupplier);
renderedCameras.add(renderedCamera);
}
}
return renderedCameras;
}
@Override
public List getRenderedCameras()
{
List renderedCameras = new ArrayList();
for (GltfModel gltfModel : gltfModels)
{
renderedCameras.addAll(modelRenderedCameras.get(gltfModel));
}
return Collections.unmodifiableList(renderedCameras);
}
/**
* Add a task to be executed once, before the next rendering pass,
* on the rendering thread
*
* @param beforeRenderTask The task to be executed
*/
private void addBeforeRenderTask(Runnable beforeRenderTask)
{
beforeRenderTasks.add(beforeRenderTask);
}
/**
* The method that may be called by implementations to execute the
* actual rendering pass. It will call {@link #prepareRender()}
* (then execute all tasks that have been scheduled internally,
* for execution on the rendering thread), and then call
* {@link #render()}.
*/
protected final void doRender()
{
prepareRender();
beforeRender();
render();
}
/**
* Will be called at the beginning of each rendering pass. May be
* used to do basic setup, e.g. to make the required GL context
* current.
*/
protected abstract void prepareRender();
/**
* Will be called between {@link #prepareRender()} and {@link #render()},
* and process all {@link #beforeRenderTasks}
*/
private void beforeRender()
{
synchronized (beforeRenderTasks)
{
while (beforeRenderTasks.size() > 0)
{
Runnable beforeRenderTask = beforeRenderTasks.get(0);
beforeRenderTask.run();
beforeRenderTasks.remove(0);
}
}
}
/**
* The actual rendering method. Subclasses implementing this method
* will usually call {@link #renderGltfModels()}.
*/
protected abstract void render();
/**
* Render all glTF models that have been added via
* {@link #addGltfModel(GltfModel)}
*/
protected final void renderGltfModels()
{
for (RenderedGltfModel renderedGltfModel : renderedGltfModels.values())
{
renderedGltfModel.render();
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy