blob: 3cc5909a65551200a24e03fc5911dcc7007e9e1c [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 - initial API and implementation
*
* SPDX-License-Identifier: EPL-1.0
*******************************************************************************/
package org.eclipse.apogy.core.environment.earth.atmosphere.ui.jme3.scene_objects;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import javax.vecmath.Matrix4d;
import org.eclipse.apogy.common.images.AbstractEImage;
import org.eclipse.apogy.common.images.EImagesUtilities;
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.scene_objects.DefaultJME3SceneObject;
import org.eclipse.apogy.core.environment.Worksite;
import org.eclipse.apogy.core.environment.earth.ApogyEarthEnvironmentPackage;
import org.eclipse.apogy.core.environment.earth.GeographicCoordinates;
import org.eclipse.apogy.core.environment.earth.atmosphere.ApogyEarthAtmosphereEnvironmentPackage;
import org.eclipse.apogy.core.environment.earth.atmosphere.EarthAtmosphereWorksite;
import org.eclipse.apogy.core.environment.earth.atmosphere.EarthAtmosphereWorksiteNode;
import org.eclipse.apogy.core.environment.earth.atmosphere.ui.jme3.utils.AtmosphereJME3Utilities;
import org.eclipse.apogy.core.environment.earth.atmosphere.ui.scene_objects.EarthAtmosphereWorksiteSceneObject;
import org.eclipse.apogy.core.environment.earth.surface.EarthSurfaceWorksite;
import org.eclipse.apogy.core.environment.earth.surface.EarthSurfaceWorksiteNode;
import org.eclipse.apogy.core.environment.earth.surface.ui.jme3.EarthSurfaceEnvironmentJMEConstants;
import org.eclipse.apogy.core.environment.earth.surface.ui.jme3.EnvironmentUIJME3Utilities;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.impl.AdapterImpl;
import org.eclipse.swt.graphics.RGB;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
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.texture.Texture2D;
import com.jme3.texture.plugins.AWTLoader;
public class EarthAtmosphereWorksiteNodeJME3Object extends DefaultJME3SceneObject<EarthAtmosphereWorksiteNode>
implements EarthAtmosphereWorksiteSceneObject {
private static final Logger Logger = LoggerFactory.getLogger(EarthAtmosphereWorksiteNodeJME3Object.class);
public static long IMAGE_RETRY_WAIT_TIME_MS = 1 * 60 * 1000;
public static float CUTTOFF_HEIGHT_METERS = 250.0f;
private static ColorRGBA HORIZON_COLOR = new ColorRGBA(0f, 1f, 0f, 1.0f);
private final AWTLoader awtLoader = new AWTLoader();
private Adapter adapter;
private boolean axisVisible = false;
private float axisLength = 1.0f;
private boolean planeVisible = true;
private float gridSize = 10.0f;
private float planeSize = 100.0f;
private boolean azimuthVisible = true;
private boolean azimuthLinesVisible = true;
private boolean elevationLinesVisible = true;
private float altitude = 0.0f;
private final AssetManager assetManager;
private final Geometry horizonGeometry = null;
private Geometry gridGeometry = null;
private Geometry axisGeometry = null;
private Node skyNode = null;
private Node azimuthDisplayNode = null;
private Node azimuthDisplayCirclesNode = null;
private Node elevationDisplayCirclesNode = null;
private Node horizon;
private long lastImageTryTime = -1;
public EarthAtmosphereWorksiteNodeJME3Object(EarthAtmosphereWorksiteNode node,
JME3RenderEngineDelegate jme3RenderEngineDelegate) {
super(node, jme3RenderEngineDelegate);
this.assetManager = this.jme3Application.getAssetManager();
// Creates geometry.
requestUpdate();
// Listens for changes on the Worksite.
node.eAdapters().add(getAdapter());
if (node.getWorksite() instanceof EarthAtmosphereWorksite) {
EarthAtmosphereWorksite earthAtmosphereWorksite = (EarthAtmosphereWorksite) node.getWorksite();
earthAtmosphereWorksite.eAdapters().add(getAdapter());
}
}
@Override
public void updateGeometry(float tpf) {
if (this.horizon != null)
getAttachmentNode().detachChild(this.horizon);
if (this.gridGeometry != null)
getAttachmentNode().detachChild(this.gridGeometry);
if (this.axisGeometry != null)
getAttachmentNode().detachChild(this.axisGeometry);
if (this.skyNode != null)
getAttachmentNode().detachChild(this.skyNode);
// Adds the axis
this.axisGeometry = JME3Utilities.createAxis3D(this.axisLength, this.assetManager);
if (this.axisVisible)
getAttachmentNode().attachChild(this.axisGeometry);
// Adds the grid
this.gridGeometry = createGridGeometry();
if (this.planeVisible)
getAttachmentNode().attachChild(this.gridGeometry);
this.skyNode = createSkyNode();
getAttachmentNode().attachChild(this.skyNode);
this.horizon = createHorizon(this.altitude);
getAttachmentNode().attachChild(this.horizon);
}
@Override
public List<Geometry> getGeometries() {
List<Geometry> geometries = new ArrayList<Geometry>();
geometries.add(this.gridGeometry);
geometries.add(this.axisGeometry);
geometries.add(this.horizonGeometry);
return geometries;
}
@Override
public void setAxisVisible(boolean newAxisVisible) {
this.axisVisible = newAxisVisible;
Logger.info("Setting Axis visibility to <" + newAxisVisible + ">");
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
if (EarthAtmosphereWorksiteNodeJME3Object.this.axisGeometry != null) {
if (EarthAtmosphereWorksiteNodeJME3Object.this.axisVisible) {
getAttachmentNode().attachChild(EarthAtmosphereWorksiteNodeJME3Object.this.axisGeometry);
} else {
getAttachmentNode().detachChild(EarthAtmosphereWorksiteNodeJME3Object.this.axisGeometry);
}
} else {
Logger.error("Failed to set Axis visibility to <" + newAxisVisible + ">.");
}
return null;
}
});
}
@Override
public void setAxisLength(double newLength) {
this.axisLength = (float) newLength;
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
Logger.info("Setting Axis Length to <" + newLength + ">.");
try {
if (EarthAtmosphereWorksiteNodeJME3Object.this.axisGeometry != null) {
getAttachmentNode().detachChild(EarthAtmosphereWorksiteNodeJME3Object.this.axisGeometry);
}
// Updates axis geometry
EarthAtmosphereWorksiteNodeJME3Object.this.axisGeometry = JME3Utilities.createAxis3D(
EarthAtmosphereWorksiteNodeJME3Object.this.axisLength,
EarthAtmosphereWorksiteNodeJME3Object.this.assetManager);
// If axis geometry is visible, attach the new geometry.
if (EarthAtmosphereWorksiteNodeJME3Object.this.axisVisible) {
getAttachmentNode().attachChild(EarthAtmosphereWorksiteNodeJME3Object.this.axisGeometry);
}
} catch (Throwable t) {
Logger.error("Failed to setAxisLength(" + newLength + ").", t);
}
return null;
}
});
}
@Override
public void setPlaneVisible(boolean newPlaneVisible) {
this.planeVisible = newPlaneVisible;
Logger.info("Setting Plane visibility to <" + newPlaneVisible + ">.");
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
if (EarthAtmosphereWorksiteNodeJME3Object.this.gridGeometry != null) {
if (EarthAtmosphereWorksiteNodeJME3Object.this.planeVisible) {
getAttachmentNode().attachChild(EarthAtmosphereWorksiteNodeJME3Object.this.gridGeometry);
} else {
getAttachmentNode().detachChild(EarthAtmosphereWorksiteNodeJME3Object.this.gridGeometry);
}
}
return null;
}
});
}
@Override
public void setPlaneParameters(double newGridSize, double newPlaneSize) {
Logger.info("Setting Plane grid size to <" + newGridSize + "> and grid size to <" + newPlaneSize + ">.");
if (newGridSize > 0 && newPlaneSize > 0) {
this.gridSize = (float) newGridSize;
this.planeSize = (float) newPlaneSize;
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
// Detach previous geometry.
if (EarthAtmosphereWorksiteNodeJME3Object.this.gridGeometry != null) {
getAttachmentNode().detachChild(EarthAtmosphereWorksiteNodeJME3Object.this.gridGeometry);
}
EarthAtmosphereWorksiteNodeJME3Object.this.gridGeometry = createGridGeometry();
if (EarthAtmosphereWorksiteNodeJME3Object.this.planeVisible) {
getAttachmentNode().attachChild(EarthAtmosphereWorksiteNodeJME3Object.this.gridGeometry);
} else {
getAttachmentNode().detachChild(EarthAtmosphereWorksiteNodeJME3Object.this.gridGeometry);
}
return null;
}
});
} else {
Logger.error(
"Failed to set Plane grid size to <" + newGridSize + "> and grid size to <" + newPlaneSize + ">.");
}
}
@Override
public void setAzimuthVisible(boolean newAzimuthVisible) {
this.azimuthVisible = newAzimuthVisible;
Logger.info("Setting Azimuth visibility to <" + newAzimuthVisible + ">.");
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
if (EarthAtmosphereWorksiteNodeJME3Object.this.skyNode != null) {
if (EarthAtmosphereWorksiteNodeJME3Object.this.azimuthVisible) {
EarthAtmosphereWorksiteNodeJME3Object.this.skyNode
.attachChild(EarthAtmosphereWorksiteNodeJME3Object.this.azimuthDisplayNode);
} else {
EarthAtmosphereWorksiteNodeJME3Object.this.skyNode
.detachChild(EarthAtmosphereWorksiteNodeJME3Object.this.azimuthDisplayNode);
}
} else {
Logger.error("Failed to set Azimuth visibility to <" + newAzimuthVisible + ">.");
}
return null;
}
});
}
@Override
public void setElevationLinesVisible(boolean newElevationLinesVisible) {
this.elevationLinesVisible = newElevationLinesVisible;
Logger.info("Setting Elevation Lines visibility to <" + newElevationLinesVisible + ">.");
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
if (EarthAtmosphereWorksiteNodeJME3Object.this.skyNode != null) {
if (EarthAtmosphereWorksiteNodeJME3Object.this.elevationLinesVisible) {
EarthAtmosphereWorksiteNodeJME3Object.this.skyNode
.attachChild(EarthAtmosphereWorksiteNodeJME3Object.this.elevationDisplayCirclesNode);
} else {
EarthAtmosphereWorksiteNodeJME3Object.this.skyNode
.detachChild(EarthAtmosphereWorksiteNodeJME3Object.this.elevationDisplayCirclesNode);
}
} else {
Logger.error("Failed to set Elevation Lines visibility to <" + newElevationLinesVisible + ">.");
}
return null;
}
});
}
@Override
public void setAzimuthLinesVisible(boolean newAzimuthLinesVisible) {
this.azimuthLinesVisible = newAzimuthLinesVisible;
Logger.info("Setting Azimuth Lines visibility to <" + newAzimuthLinesVisible + ">.");
this.jme3Application.enqueue(new Callable<Object>() {
@Override
public Object call() throws Exception {
if (EarthAtmosphereWorksiteNodeJME3Object.this.skyNode != null) {
if (EarthAtmosphereWorksiteNodeJME3Object.this.azimuthLinesVisible) {
EarthAtmosphereWorksiteNodeJME3Object.this.skyNode
.attachChild(EarthAtmosphereWorksiteNodeJME3Object.this.azimuthDisplayCirclesNode);
} else {
EarthAtmosphereWorksiteNodeJME3Object.this.skyNode
.detachChild(EarthAtmosphereWorksiteNodeJME3Object.this.azimuthDisplayCirclesNode);
}
} else {
Logger.error("Failed to set Azimuth Lines visibility to <" + newAzimuthLinesVisible + ">.");
}
return null;
}
});
}
private Node createSkyNode() {
Node node = new Node("Worksite Sky");
this.azimuthDisplayNode = EnvironmentUIJME3Utilities.createAzimuthDisplay(this.assetManager);
node.attachChild(this.azimuthDisplayNode);
// Azimuth Circle displays
this.azimuthDisplayCirclesNode = EnvironmentUIJME3Utilities.createAzimuthCirclesDisplay(this.assetManager);
node.attachChild(this.azimuthDisplayCirclesNode);
// Elevation circle display
this.elevationDisplayCirclesNode = EnvironmentUIJME3Utilities.createElevationCirclesDisplay(this.assetManager);
node.attachChild(this.elevationDisplayCirclesNode);
Matrix4d m = new Matrix4d();
m.setIdentity();
node.setLocalTransform(JME3Utilities.createTransform(m));
return node;
}
private Geometry createGridGeometry() {
Mesh gridMesh = EnvironmentUIJME3Utilities.createGrid(this.gridSize, this.planeSize);
Geometry geometry = new Geometry("Grid", gridMesh);
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", EarthSurfaceEnvironmentJMEConstants.DEFAULT_GRID_COLOR.clone());
geometry.setMaterial(mat);
return geometry;
}
/**
* Returns the Spherical Cap (half-sphere used to represent the horizon and hide
* the sky).
*
* @return The geometry.
*/
private Node createHorizon(float altitude) {
Node node = new Node("Worksite Earth Surface");
node.attachChild(createCurvedHorizonGeometry(altitude));
return node;
}
private Node createCurvedHorizonGeometry(float altitude) {
double gamma = AtmosphereJME3Utilities.getHorizonAngle(altitude);
double a = CUTTOFF_HEIGHT_METERS;
double b = a * Math.tan(Math.toRadians(90) - Math.abs(gamma));
List<Vector3f> profile = AtmosphereJME3Utilities.getHyperbola(a, b,
EarthSurfaceEnvironmentJMEConstants.AZIMUTH_DISPLAY_RADIUS);
Mesh mesh = AtmosphereJME3Utilities.getParabolaGeometry(profile);
Geometry geometry = new Geometry("Horizon", mesh);
geometry.setMaterial(createHorizonMaterial());
geometry.setShadowMode(ShadowMode.Off);
geometry.getMaterial().getAdditionalRenderState().setWireframe(false);
Node node = new Node("Curved Horizon");
node.attachChild(geometry);
return node;
}
/**
* Creates the material used on the horizon geometry.
*
* @return The Material.
*/
private Material createHorizonMaterial() {
Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
AbstractEImage textureImage = null;
// If it failed last time.
if (this.lastImageTryTime != -1) {
// Check if it is time to try again.
long now = System.currentTimeMillis();
if ((now - this.lastImageTryTime) > IMAGE_RETRY_WAIT_TIME_MS) {
textureImage = getTextureImage();
}
} else {
textureImage = getTextureImage();
}
if (textureImage != null) {
Texture2D texture = createTexture(textureImage);
mat.setTexture("ColorMap", texture);
} else {
mat.setColor("Color", HORIZON_COLOR.clone());
}
mat.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
return mat;
}
private AbstractEImage getTextureImage() {
try {
if (getTopologyNode() != null && getTopologyNode().getWorksite() instanceof EarthAtmosphereWorksite) {
EarthAtmosphereWorksite earthAtmosphereWorksite = (EarthAtmosphereWorksite) getTopologyNode()
.getWorksite();
if (earthAtmosphereWorksite.getGeographicalCoordinates() != null) {
AbstractEImage image = AtmosphereJME3Utilities.getImageFromWMS(this.getTopologyNode(),
earthAtmosphereWorksite.getGeographicalCoordinates(), 50000);
if (image != null) {
this.lastImageTryTime = -1;
} else {
this.lastImageTryTime = System.currentTimeMillis();
}
return image;
}
}
} catch (Exception e) {
this.lastImageTryTime = System.currentTimeMillis();
Logger.error(e.getMessage(), e);
}
return null;
}
private Texture2D createTexture(AbstractEImage textureImage) {
int width = textureImage.getWidth();
int height = textureImage.getHeight();
RGB rgb = new RGB(255, 255, 255);
AbstractEImage background = EImagesUtilities.INSTANCE.createUniformColorImage(width, height, rgb.red, rgb.green,
rgb.blue, 255);
AbstractEImage newImage = EImagesUtilities.INSTANCE.applyOverlay(background, textureImage, false);
AbstractEImage rotatedImage = EImagesUtilities.INSTANCE.rotate(newImage, Math.PI / 2, false);
com.jme3.texture.Image img = this.awtLoader.load(rotatedImage.asBufferedImage(), true);
Texture2D texture = new Texture2D(img);
return texture;
}
private Adapter getAdapter() {
if (this.adapter == null) {
this.adapter = new AdapterImpl() {
@Override
public void notifyChanged(Notification msg) {
if (msg.getNotifier() instanceof EarthSurfaceWorksiteNode) {
// Worksite has changed.
if (msg.getFeatureID(
EarthSurfaceWorksiteNode.class) == ApogyEarthAtmosphereEnvironmentPackage.EARTH_ATMOSPHERE_WORKSITE__WORKSITE_NODE) {
// Unregister from old Worksite.
if (msg.getOldValue() instanceof Worksite) {
Worksite oldWorksite = (Worksite) msg.getOldValue();
oldWorksite.eAdapters().remove(getAdapter());
}
if (msg.getNewValue() instanceof Worksite) {
Worksite newWorksite = (Worksite) msg.getNewValue();
newWorksite.eAdapters().add(getAdapter());
if (newWorksite instanceof EarthAtmosphereWorksite) {
// EarthAtmosphereWorksite earthAtmosphereWorksite = (EarthAtmosphereWorksite)
// newWorksite;
requestUpdate();
}
}
}
} else if (msg.getNotifier() instanceof EarthAtmosphereWorksite) {
if (msg.getFeatureID(
EarthSurfaceWorksite.class) == ApogyEarthEnvironmentPackage.EARTH_WORKSITE__GEOGRAPHICAL_COORDINATES) {
GeographicCoordinates coord = (GeographicCoordinates) msg.getNewValue();
if (coord != null) {
EarthAtmosphereWorksiteNodeJME3Object.this.altitude = (float) coord.getElevation();
requestUpdate();
}
}
}
}
};
}
return this.adapter;
}
}