| /******************************************************************************* |
| * 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; |
| } |
| |
| } |