blob: 593d2d3afe7cf9ebe10698100bcd3b8b384152d4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Agence spatiale canadienne / Canadian Space Agency
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pierre Allard,
* Regent L'Archeveque,
* Sebastien Gemme - initial API and implementation
*
* SPDX-License-Identifier: EPL-1.0
*
*******************************************************************************/
package org.eclipse.apogy.common.topology.ui.jme3.scene_objects;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import org.eclipse.apogy.common.EclipseUtils;
import org.eclipse.apogy.common.emf.transaction.ApogyCommonTransactionFacade;
import org.eclipse.apogy.common.topology.ApogyCommonTopologyPackage;
import org.eclipse.apogy.common.topology.URLNode;
import org.eclipse.apogy.common.topology.ui.MeshPresentationMode;
import org.eclipse.apogy.common.topology.ui.adapters.URLNodeSceneObject;
import org.eclipse.apogy.common.topology.ui.jme3.Activator;
import org.eclipse.apogy.common.topology.ui.jme3.JME3RenderEngineDelegate;
import org.eclipse.apogy.common.topology.ui.jme3.JME3Utilities;
import org.eclipse.apogy.common.topology.ui.jme3.JME3Utilities.OS;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jme3.asset.AssetManager;
import com.jme3.asset.plugins.FileLocator;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.SceneGraphVisitor;
import com.jme3.scene.Spatial;
import jme3tools.optimize.GeometryBatchFactory;
public class URLNodeJME3TopologyObject extends DefaultJME3SceneObject<URLNode> implements URLNodeSceneObject {
private static final Logger Logger = LoggerFactory.getLogger(URLNodeJME3TopologyObject.class);
/* Supported file extension, in order of preference. */
public final static String[] FILE_EXTENSIONS = new String[] { "j3o", "obj" };
private MeshPresentationMode presentationMode = MeshPresentationMode.SURFACE;
private AssetManager assetManager;
private float previousAxisLength = 1.0f;
private Geometry axisGeometry = null;
private Spatial loadedObject;
private Adapter urlAdapter = null;
public URLNodeJME3TopologyObject(URLNode topologyNode, JME3RenderEngineDelegate jme3RenderEngineDelegate) {
super(topologyNode, jme3RenderEngineDelegate);
if (this.jme3Application == null) {
throw new IllegalArgumentException("Error app == null !");
}
if (topologyNode == null) {
throw new IllegalArgumentException("topologyNode == null !");
}
this.assetManager = this.jme3Application.getAssetManager();
try {
this.assetManager.registerLocator(new File(".").getCanonicalPath(), FileLocator.class);
this.assetManager.registerLocator("/", FileLocator.class);
} catch (IOException e) {
Logger.error(e.getMessage(), e);
}
// Attaches the 3DAxis
getAttachmentNode().attachChild(getAxisGeometry());
// Loads the geometry from the URL
Job job = new Job("Loading <" + getTopologyNode().getUrl() + ">") {
@Override
protected IStatus run(IProgressMonitor monitor) {
requestUpdate();
getTopologyNode().eAdapters().add(getURLAdapter());
return Status.OK_STATUS;
}
};
job.schedule();
}
@Override
public void updateGeometry(float tpf) {
// Delete previous geometry
if (this.loadedObject != null) {
getAttachmentNode().detachChild(this.loadedObject);
}
// Loads the new geometry
this.loadedObject = loadGeometry(getTopologyNode().getUrl());
if (this.loadedObject != null) {
// If the loaded Object is a Node, optimize it.
if (this.loadedObject instanceof Node) {
Node optimizedNode = (Node) GeometryBatchFactory.optimize((Node) this.loadedObject);
getAttachmentNode().attachChild(optimizedNode);
} else {
getAttachmentNode().attachChild(this.loadedObject);
}
this.loadedObject.setShadowMode(ShadowMode.Cast);
} else {
Logger.error("Failed to load geometry from URL <" + getTopologyNode().getUrl() + ">.");
ApogyCommonTransactionFacade.INSTANCE.basicSet(getTopologyNode(),
ApogyCommonTopologyPackage.Literals.URL_NODE__VERTEX_COUNT, 0);
ApogyCommonTransactionFacade.INSTANCE.basicSet(getTopologyNode(),
ApogyCommonTopologyPackage.Literals.URL_NODE__POLYGON_COUNT, 0);
}
}
@Override
public List<Geometry> getGeometries() {
List<Geometry> geometries = new ArrayList<Geometry>();
if (this.loadedObject instanceof Node) {
Node node = (Node) this.loadedObject;
List<Geometry> found = JME3Utilities.getAllChildrenGeometry(node);
geometries.addAll(found);
} else if (this.loadedObject instanceof Geometry) {
geometries.add((Geometry) this.loadedObject);
}
geometries.addAll(super.getGeometries());
return geometries;
}
@Override
public void dispose() {
// Unregister listener.
if (getTopologyNode() != null) {
getTopologyNode().eAdapters().remove(getURLAdapter());
}
super.dispose();
}
@Override
public void setAxisVisible(boolean visible) {
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
if (visible) {
getAttachmentNode().attachChild(getAxisGeometry());
} else {
getAttachmentNode().detachChild(getAxisGeometry());
}
return null;
}
});
}
@Override
public void setAxisLength(double length) {
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
try {
float scale = (float) Math.abs(length) / URLNodeJME3TopologyObject.this.previousAxisLength;
// Scales existing axis.
getAxisGeometry().scale(scale);
URLNodeJME3TopologyObject.this.previousAxisLength = (float) length;
} catch (Throwable t) {
Logger.error("Failed to setAxisLength(" + length + ").", t);
}
return null;
}
});
}
@Override
public void setPresentationMode(MeshPresentationMode mode) {
Logger.info("Setting presentation mode to " + mode);
this.presentationMode = mode;
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
if (URLNodeJME3TopologyObject.this.loadedObject != null) {
ModeSceneGraphVisitor modeSceneGraphVisitor = new ModeSceneGraphVisitor(
URLNodeJME3TopologyObject.this.presentationMode);
URLNodeJME3TopologyObject.this.loadedObject.breadthFirstTraversal(modeSceneGraphVisitor);
}
return null;
}
});
}
@Override
public void setShadowMode(final org.eclipse.apogy.common.topology.ui.ShadowMode shadowMode) {
super.setShadowMode(shadowMode);
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
if (URLNodeJME3TopologyObject.this.loadedObject != null) {
ShadowModeSceneGraphVisitor shadowModeSceneGraphVisitor = new ShadowModeSceneGraphVisitor(
shadowMode);
URLNodeJME3TopologyObject.this.loadedObject.breadthFirstTraversal(shadowModeSceneGraphVisitor);
}
return null;
}
});
}
private Geometry getAxisGeometry() {
if (this.axisGeometry == null) {
this.axisGeometry = JME3Utilities.createAxis3D(1.0f, this.assetManager);
}
return this.axisGeometry;
}
private Spatial loadGeometry(String urlString) {
Spatial loadedModel = null;
if (urlString != null && urlString.length() > 0) {
String fileWithoutExtension = stripFileExtension(urlString);
int extensionIndex = 0;
while (extensionIndex < FILE_EXTENSIONS.length && loadedModel == null) {
String fileExtension = FILE_EXTENSIONS[extensionIndex];
String fileName = fileWithoutExtension + "." + fileExtension;
try {
Logger.info("Loading <" + fileName + "> ...");
URL url = EclipseUtils.resolveURL(Activator.getContext().getBundle(), fileName);
loadedModel = load(url);
} catch (Throwable t) {
}
extensionIndex++;
}
}
return loadedModel;
}
/**
* Loads an file.
*
* @param url The URL locating the J3O file.
* @return The Spatial loaded.
*/
private Spatial load(URL url) {
Spatial loadedModel = null;
try {
Logger.info("Loading <" + url + ">...");
long startTime = System.currentTimeMillis();
String fileName = convertURLToAssetString(url);
loadedModel = this.assetManager.loadModel(fileName);
long stopTime = System.currentTimeMillis();
if (loadedModel != null) {
ApogyCommonTransactionFacade.INSTANCE.basicSet(getTopologyNode(),
ApogyCommonTopologyPackage.Literals.URL_NODE__VERTEX_COUNT, loadedModel.getVertexCount());
ApogyCommonTransactionFacade.INSTANCE.basicSet(getTopologyNode(),
ApogyCommonTopologyPackage.Literals.URL_NODE__POLYGON_COUNT, loadedModel.getTriangleCount());
float loadTime = (stopTime - startTime) * 0.001f;
Logger.info("Sucessfully loaded <" + url + "> containing <" + getTopologyNode().getVertexCount()
+ "> vertices and <" + getTopologyNode().getPolygonCount() + "> triangles in <" + loadTime
+ "> seconds.");
}
} catch (Throwable t) {
}
return loadedModel;
}
/**
* Converts a URL to an asset string name.
*
* @param url The URL.
* @return The String conversion.
* @throws Exception if something goes wrong.
*/
private String convertURLToAssetString(URL url) throws Exception {
String string = null;
// Replaces spaces wthi %20 to ensure the URI
URL urlNoSpaces = new URL(url.toString().replaceAll(" ", "%20"));
if (urlNoSpaces.getProtocol().compareToIgnoreCase("file") == 0) {
File file = new File(urlNoSpaces.toURI());
if (isWindowsOS()) {
int indexOfDrive = file.toString().indexOf(":");
if (indexOfDrive > 0) {
string = file.toString().substring(indexOfDrive + 1);
} else {
string = file.toString();
}
string = string.replace("\\", "/");
} else {
string = file.toString();
}
} else {
throw new IllegalArgumentException("Only file URL are supported !");
}
return string;
}
private boolean isWindowsOS() {
return JME3Utilities.getOS() == OS.WINDOWS;
}
/**
* Returns the URl with the file extension stripped (including the dot.)
*
* @param url The url as a string.
* @return The url minus the dot and file extension.
*/
private String stripFileExtension(String url) {
int index = url.lastIndexOf(".");
return url.substring(0, index);
}
/**
* Return the Adapter used to listen for changes on the URL.
*
* @return
*/
private Adapter getURLAdapter() {
if (this.urlAdapter == null) {
this.urlAdapter = new AdapterImpl() {
@Override
public void notifyChanged(org.eclipse.emf.common.notify.Notification msg) {
if (msg.getFeatureID(URLNode.class) == ApogyCommonTopologyPackage.URL_NODE__URL) {
Job job = new Job("Loadding <" + getTopologyNode().getUrl() + ">.") {
@Override
protected IStatus run(IProgressMonitor monitor) {
requestUpdate();
return Status.OK_STATUS;
}
};
job.schedule();
}
};
};
}
return this.urlAdapter;
}
/**
* SceneGraphVisitor that applies a mesh presentation mode to all childrens.
*
* @author pallard
*/
private class ModeSceneGraphVisitor implements SceneGraphVisitor {
private MeshPresentationMode mode = MeshPresentationMode.SURFACE;
public ModeSceneGraphVisitor(MeshPresentationMode targetPresentationMode) {
this.mode = targetPresentationMode;
}
@Override
public void visit(Spatial spatial) {
if (spatial instanceof Geometry) {
Geometry geometry = (Geometry) spatial;
Mesh mesh = geometry.getMesh();
if (mesh != null) {
switch (this.mode.getValue()) {
case MeshPresentationMode.SURFACE_VALUE:
geometry.getMaterial().getAdditionalRenderState().setWireframe(false);
mesh.setMode(Mesh.Mode.Triangles);
break;
case MeshPresentationMode.WIREFRAME_VALUE:
geometry.getMaterial().getAdditionalRenderState().setWireframe(true);
mesh.setMode(Mesh.Mode.Triangles);
break;
case MeshPresentationMode.POINTS_VALUE:
geometry.getMaterial().getAdditionalRenderState().setWireframe(false);
mesh.setMode(Mesh.Mode.Points);
break;
}
}
}
}
}
/**
* SceneGraphVisitor that applies a mesh presentation mode to all childrens.
*
* @author pallard
*/
private class ShadowModeSceneGraphVisitor implements SceneGraphVisitor {
private org.eclipse.apogy.common.topology.ui.ShadowMode shadowMode = org.eclipse.apogy.common.topology.ui.ShadowMode.INHERIT;
public ShadowModeSceneGraphVisitor(org.eclipse.apogy.common.topology.ui.ShadowMode shadowMode) {
this.shadowMode = shadowMode;
}
@Override
public void visit(Spatial spatial) {
spatial.setShadowMode(JME3Utilities.convertToJMEShadowMode(this.shadowMode));
}
}
}