| /******************************************************************************* |
| * 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 - initial API and implementation |
| * |
| * SPDX-License-Identifier: EPL-1.0 |
| *******************************************************************************/ |
| package org.eclipse.apogy.core.ui.jme3.scene_objects; |
| |
| import java.awt.Color; |
| import java.awt.Font; |
| import java.awt.image.BufferedImage; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.Callable; |
| |
| 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.AbstractTransformationJME3SceneObject; |
| import org.eclipse.apogy.core.PositionedResult; |
| import org.eclipse.apogy.core.ResultNode; |
| import org.eclipse.apogy.core.invocator.OperationCallResult; |
| import org.eclipse.apogy.core.ui.ResultNodeSceneObject; |
| 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.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.jme3.asset.AssetManager; |
| import com.jme3.material.Material; |
| import com.jme3.math.ColorRGBA; |
| import com.jme3.math.Vector2f; |
| 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.scene.control.BillboardControl; |
| import com.jme3.util.BufferUtils; |
| |
| public class ResultNodeJME3Object extends AbstractTransformationJME3SceneObject<ResultNode> |
| implements ResultNodeSceneObject { |
| private static final Logger Logger = LoggerFactory.getLogger(ResultNodeJME3Object.class); |
| |
| public static final float DEFAULT_FLAG_POLE_HEIGHT = 1.0f; |
| |
| public static final float FLAG_SIZE_PER_PIXEL = 0.005f; |
| public static final ColorRGBA FLAG_POLE_COLOR = ColorRGBA.Yellow; |
| public static final ColorRGBA FLAG_BACKGROUND_COLOR = ColorRGBA.Black; |
| public static final ColorRGBA FLAG_TEXT_COLOR = ColorRGBA.Yellow; |
| |
| private boolean flagVisible = false; |
| private float flagPoleHeight = DEFAULT_FLAG_POLE_HEIGHT; |
| |
| private AssetManager assetManager; |
| |
| private Node pole = null; |
| private Geometry poleGeometry = null; |
| |
| private Node flag = null; |
| private Geometry flagGeometry = null; |
| |
| private BillboardControl billboardControl; |
| |
| public ResultNodeJME3Object(ResultNode node, JME3RenderEngineDelegate jme3RenderEngineDelegate) { |
| super(node, jme3RenderEngineDelegate); |
| |
| this.assetManager = this.jme3Application.getAssetManager(); |
| |
| Job job = new Job("ResultNodeJME3Object : Updating Geometry") { |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| updateGeometry(); |
| return Status.OK_STATUS; |
| } |
| }; |
| job.schedule(); |
| } |
| |
| @Override |
| public List<Geometry> getGeometries() { |
| List<Geometry> geometries = new ArrayList<Geometry>(); |
| |
| if (this.poleGeometry != null) |
| geometries.add(this.poleGeometry); |
| if (this.flagGeometry != null) |
| geometries.add(this.flagGeometry); |
| |
| geometries.addAll(super.getGeometries()); |
| |
| return geometries; |
| } |
| |
| @Override |
| public void dispose() { |
| if (this.billboardControl != null) { |
| this.billboardControl.setEnabled(false); |
| this.billboardControl.setSpatial(null); |
| } |
| |
| if (this.pole != null) { |
| this.pole.detachAllChildren(); |
| if (this.pole.getParent() != null) { |
| this.pole.getParent().detachChild(this.pole); |
| } |
| } |
| |
| super.dispose(); |
| } |
| |
| @Override |
| public void setFlagPoleHeight(float poleHeight) { |
| Logger.info("Setting Flag Pole Height to <" + poleHeight + ">."); |
| if (poleHeight > 0) { |
| this.flagPoleHeight = poleHeight; |
| updateGeometry(); |
| } |
| } |
| |
| @Override |
| public float getFlagPoleHeight() { |
| return this.flagPoleHeight; |
| } |
| |
| @Override |
| public void setFlagVisible(final boolean visible) { |
| Logger.info("Setting Flag Visible to <" + visible + ">."); |
| |
| this.flagVisible = visible; |
| |
| this.jme3Application.enqueue(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| if (ResultNodeJME3Object.this.flagVisible) { |
| if (ResultNodeJME3Object.this.pole != null) { |
| if (!getAttachmentNode().getChildren().contains(ResultNodeJME3Object.this.pole)) { |
| getAttachmentNode().attachChild(ResultNodeJME3Object.this.pole); |
| } |
| } |
| } else { |
| if (ResultNodeJME3Object.this.pole != null) |
| getAttachmentNode().detachChild(ResultNodeJME3Object.this.pole); |
| } |
| |
| return null; |
| } |
| }); |
| } |
| |
| @Override |
| public boolean isFlagVisible() { |
| return this.flagVisible; |
| } |
| |
| private void updateGeometry() { |
| this.jme3Application.enqueue(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| if (ResultNodeJME3Object.this.pole != null) |
| getAttachmentNode().detachChild(ResultNodeJME3Object.this.pole); |
| if (ResultNodeJME3Object.this.flag != null) |
| getAttachmentNode().detachChild(ResultNodeJME3Object.this.flag); |
| |
| getBillboardControl().setSpatial(null); |
| |
| // Creates the pole. |
| ResultNodeJME3Object.this.pole = createPole(ResultNodeJME3Object.this.flagPoleHeight); |
| |
| if (ResultNodeJME3Object.this.flagVisible) { |
| getAttachmentNode().attachChild(ResultNodeJME3Object.this.pole); |
| } |
| |
| // Creates the flag |
| ResultNodeJME3Object.this.flag = createFlag(getFlagText(), 16); |
| ResultNodeJME3Object.this.pole.attachChild(ResultNodeJME3Object.this.flag); |
| |
| return null; |
| } |
| }); |
| } |
| |
| private Node createPole(float poleHeight) { |
| List<Vector3f> verticesList = new ArrayList<Vector3f>(); |
| List<Integer> indexesList = new ArrayList<Integer>(); |
| |
| Vector3f p0 = new Vector3f(0, 0, 0); |
| Vector3f p1 = new Vector3f(0, 0, poleHeight); |
| verticesList.add(p0); |
| verticesList.add(p1); |
| indexesList.add(verticesList.indexOf(p0)); |
| indexesList.add(verticesList.indexOf(p1)); |
| |
| Mesh mesh = new Mesh(); |
| mesh.setMode(Mesh.Mode.Lines); |
| mesh.setBuffer(com.jme3.scene.VertexBuffer.Type.Position, 3, |
| BufferUtils.createFloatBuffer(JME3Utilities.convertToFloatArray(verticesList))); |
| mesh.setBuffer(com.jme3.scene.VertexBuffer.Type.Index, 2, |
| BufferUtils.createIntBuffer(JME3Utilities.convertToIntArray(indexesList))); |
| mesh.updateBound(); |
| mesh.updateCounts(); |
| |
| this.poleGeometry = new Geometry("Pole", mesh); |
| |
| Material mat = new Material(this.assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); |
| mat.setColor("Color", FLAG_POLE_COLOR.clone()); |
| this.poleGeometry.setMaterial(mat); |
| |
| Node node = new Node("Pole."); |
| node.setShadowMode(ShadowMode.Off); |
| node.attachChild(this.poleGeometry); |
| |
| return node; |
| } |
| |
| private String getFlagText() { |
| String text = "?"; |
| |
| if (getTopologyNode() != null) { |
| if (getTopologyNode().getDescription() != null) { |
| text = getTopologyNode().getDescription(); |
| } else if (getTopologyNode().getResult() != null) { |
| if (getTopologyNode().getResult().getTime() != null) { |
| text = getTopologyNode().getResult().getTime().toString(); |
| } |
| } |
| } |
| |
| return text; |
| } |
| |
| private Node createFlag(String text, int fontSize) { |
| Node node = new Node("Flag"); |
| node.setShadowMode(ShadowMode.Off); |
| |
| Node flagAttachmentPoint = new Node("Flag Attachment Point"); |
| flagAttachmentPoint.setLocalTranslation(0, 0, this.flagPoleHeight); |
| node.attachChild(flagAttachmentPoint); |
| |
| // First, create the image that will hold the text. |
| Font font = new Font("Serif", Font.BOLD, fontSize); |
| BufferedImage bufferedImage = createTextImage(text, font, 2); |
| |
| // Based on the image size, create the flag geometry. |
| float flagWidth = FLAG_SIZE_PER_PIXEL * bufferedImage.getWidth(); |
| float flagHeight = FLAG_SIZE_PER_PIXEL * bufferedImage.getHeight(); |
| Mesh flagMesh = createFlagMesh(flagWidth, flagHeight); |
| |
| this.flagGeometry = new Geometry("Flag Geometry", flagMesh); |
| Material mat = JME3Utilities.createMaterial(bufferedImage, this.assetManager); |
| this.flagGeometry.setMaterial(mat); |
| |
| flagAttachmentPoint.addControl(getBillboardControl()); |
| flagAttachmentPoint.attachChild(this.flagGeometry); |
| |
| return node; |
| } |
| |
| private Mesh createFlagMesh(float flagWidth, float flagHeight) { |
| Vector3f[] vertices = new Vector3f[4]; |
| vertices[0] = new Vector3f(0, 0, 0); |
| vertices[1] = new Vector3f(flagWidth, 0, 0); |
| vertices[2] = new Vector3f(0, flagHeight, 0); |
| vertices[3] = new Vector3f(flagWidth, flagHeight, 0); |
| |
| int[] indexes = { 2, 0, 1, 1, 3, 2 }; |
| |
| Vector2f[] texCoord = new Vector2f[4]; |
| texCoord[0] = new Vector2f(0, 0); |
| texCoord[1] = new Vector2f(1, 0); |
| texCoord[2] = new Vector2f(0, 1); |
| texCoord[3] = new Vector2f(1, 1); |
| |
| Mesh mesh = new Mesh(); |
| mesh.setBuffer(com.jme3.scene.VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(vertices)); |
| mesh.setBuffer(com.jme3.scene.VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoord)); |
| mesh.setBuffer(com.jme3.scene.VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(indexes)); |
| mesh.updateBound(); |
| mesh.updateCounts(); |
| |
| return mesh; |
| } |
| |
| private BillboardControl getBillboardControl() { |
| if (this.billboardControl == null) { |
| this.billboardControl = new BillboardControl(); |
| this.billboardControl.setAlignment(BillboardControl.Alignment.Screen); |
| } |
| |
| return this.billboardControl; |
| } |
| |
| private boolean isResultHasError() { |
| if (getTopologyNode() != null && getTopologyNode().getResult() instanceof PositionedResult) { |
| PositionedResult positionedResult = getTopologyNode().getResult(); |
| |
| if (positionedResult instanceof OperationCallResult) { |
| OperationCallResult operationCallResult = (OperationCallResult) positionedResult; |
| |
| if (operationCallResult.getExceptionContainer() != null) { |
| if (operationCallResult.getExceptionContainer().getException() != null) { |
| return true; |
| } |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| private ColorRGBA getFlagBackgroundColor() { |
| if (isResultHasError()) { |
| return ColorRGBA.Red; |
| } else { |
| return ColorRGBA.Black; |
| } |
| } |
| |
| private ColorRGBA getTextColor() { |
| if (isResultHasError()) { |
| return ColorRGBA.Black; |
| } else { |
| return ColorRGBA.Green; |
| } |
| } |
| |
| private BufferedImage createTextImage(String text, Font font, int borderWidth) { |
| Color textColor = JME3Utilities.convertToAWTColor(getTextColor()); |
| Color backgroundColor = JME3Utilities.convertToAWTColor(getFlagBackgroundColor()); |
| AbstractEImage original = EImagesUtilities.INSTANCE.createTextImage(text, font, textColor, backgroundColor, |
| borderWidth); |
| |
| int[] borderColor = JME3Utilities.convertToColorIntRGBA(FLAG_POLE_COLOR); |
| AbstractEImage imageWithBorder = EImagesUtilities.INSTANCE.addBorder(original, borderWidth, borderColor[0], |
| borderColor[1], borderColor[2]); |
| |
| return imageWithBorder.asBufferedImage(); |
| } |
| } |