/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2002-2011 Ausenco Engineering Canada Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jaamsim.Graphics;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import com.jaamsim.DisplayModels.ArrowModel;
import com.jaamsim.DisplayModels.DisplayModel;
import com.jaamsim.DisplayModels.ImageModel;
import com.jaamsim.DisplayModels.PolylineModel;
import com.jaamsim.DisplayModels.ShapeModel;
import com.jaamsim.DisplayModels.TextModel;
import com.jaamsim.basicsim.Entity;
import com.jaamsim.basicsim.ObjectType;
import com.jaamsim.basicsim.Simulation;
import com.jaamsim.input.BooleanInput;
import com.jaamsim.input.ColourInput;
import com.jaamsim.input.EntityInput;
import com.jaamsim.input.EntityListInput;
import com.jaamsim.input.EnumInput;
import com.jaamsim.input.Input;
import com.jaamsim.input.InputAgent;
import com.jaamsim.input.InputErrorException;
import com.jaamsim.input.Keyword;
import com.jaamsim.input.KeywordIndex;
import com.jaamsim.input.Output;
import com.jaamsim.input.RelativeEntityInput;
import com.jaamsim.input.Vec3dInput;
import com.jaamsim.input.Vec3dListInput;
import com.jaamsim.math.Color4d;
import com.jaamsim.math.Mat4d;
import com.jaamsim.math.Quaternion;
import com.jaamsim.math.Transform;
import com.jaamsim.math.Vec3d;
import com.jaamsim.render.DisplayModelBinding;
import com.jaamsim.render.RenderUtils;
import com.jaamsim.ui.FrameBox;
import com.jaamsim.units.AngleUnit;
import com.jaamsim.units.DimensionlessUnit;
import com.jaamsim.units.DistanceUnit;
import com.jogamp.newt.event.KeyEvent;
/**
* Encapsulates the methods and data needed to display a simulation object in the 3D environment.
* Extends the basic functionality of entity in order to have access to the basic system
* components like the eventManager.
*/
public class DisplayEntity extends Entity {
@Keyword(description = "The location of the object in {x, y, z} coordinates.",
exampleList = {"-3.922 -1.830 0.000 m"})
protected final Vec3dInput positionInput;
@Keyword(description = "The point within the object that is located at the coordinates of "
+ "its Position input. Expressed with respect to a unit box centered "
+ "about { 0 0 0 }.",
exampleList = {"-0.5 -0.5 0.0"})
protected final Vec3dInput alignmentInput;
@Keyword(description = "The size of the object in {x, y, z} coordinates. If only the x- and "
+ "y-dimensions are given then the z-dimension is assumed to be zero.",
exampleList = {"15 12 0 m"})
protected final Vec3dInput sizeInput;
@Keyword(description = "Euler angles defining the rotation of the object.",
exampleList = {"0 0 90 deg"})
private final Vec3dInput orientationInput;
@Keyword(description = "A list of points in {x, y, z} coordinates that define a polyline. "
+ "When only two coordinates are given it is assumed that z = 0." ,
exampleList = {"{ 1.0 1.0 0.0 m } { 2.0 2.0 0.0 m } { 3.0 3.0 0.0 m }",
"{ 1.0 1.0 m } { 2.0 2.0 m } { 3.0 3.0 m }"})
protected final Vec3dListInput pointsInput;
@Keyword(description = "The type of curve interpolation used for line type entities.",
exampleList = {"LINEAR", "BEZIER", "SPLINE"})
protected final EnumInput<PolylineInfo.CurveType> curveTypeInput;
@Keyword(description = "If a Region is specified, the Position and Orientation inputs for "
+ "the present object will be relative to the Position and Orientation "
+ "of the specified Region. If the specified Region is moved or "
+ "rotated, the present object with move to maintain it relative "
+ "position and orientation.",
exampleList = {"Region1"})
protected final EntityInput<Region> regionInput;
@Keyword(description = "If an object is specified, the Position input for the present object "
+ "will be relative to the Position for the specified object. If the "
+ "specified object is moved, the present object will move to maintain "
+ "its relative position.",
exampleList = {"DisplayEntity1"})
protected final RelativeEntityInput relativeEntity;
@Keyword(description = "The graphic representation of the object. If a list of DisplayModels "
+ "is entered, each one will be displayed provided that its DrawRange "
+ "input is satisfied. This feature allows the object's appearance to "
+ "change with its distance from the View window's camera.",
exampleList = {"ColladaModel1", "ColladaModel1 ColladaModel2"})
protected final EntityListInput<DisplayModel> displayModelListInput;
@Keyword(description = "If TRUE, the object is displayed in the View windows.",
exampleList = {"FALSE"})
private final BooleanInput show;
@Keyword(description = "If TRUE, the object is active and used in simulation runs.",
exampleList = {"FALSE"})
private final BooleanInput active;
@Keyword(description = "If TRUE, the object will respond to mouse clicks and can be "
+ "positioned by dragging with the mouse.",
exampleList = {"FALSE"})
private final BooleanInput movable;
private final Vec3d position = new Vec3d();
private final Vec3d size = new Vec3d(1.0d, 1.0d, 1.0d);
private final Vec3d orient = new Vec3d();
private final Vec3d align = new Vec3d();
private final ArrayList<DisplayModel> displayModelList = new ArrayList<>();
private Region currentRegion;
private ArrayList<DisplayModelBinding> modelBindings;
private final HashMap<String, Tag> tagMap = new HashMap<>();
{
positionInput = new Vec3dInput("Position", "Graphics", new Vec3d());
positionInput.setUnitType(DistanceUnit.class);
this.addInput(positionInput);
alignmentInput = new Vec3dInput("Alignment", "Graphics", new Vec3d());
this.addInput(alignmentInput);
sizeInput = new Vec3dInput("Size", "Graphics", new Vec3d(1.0d, 1.0d, 1.0d));
sizeInput.setUnitType(DistanceUnit.class);
sizeInput.setValidRange(0.0d, Double.POSITIVE_INFINITY);
this.addInput(sizeInput);
orientationInput = new Vec3dInput("Orientation", "Graphics", new Vec3d());
orientationInput.setUnitType(AngleUnit.class);
this.addInput(orientationInput);
ArrayList<Vec3d> defPoints = new ArrayList<>();
defPoints.add(new Vec3d(0.0d, 0.0d, 0.0d));
defPoints.add(new Vec3d(1.0d, 0.0d, 0.0d));
pointsInput = new Vec3dListInput("Points", "Graphics", defPoints);
pointsInput.setValidCountRange( 2, Integer.MAX_VALUE );
pointsInput.setUnitType(DistanceUnit.class);
this.addInput(pointsInput);
curveTypeInput = new EnumInput<>(PolylineInfo.CurveType.class, "CurveType", "Graphics", PolylineInfo.CurveType.LINEAR);
this.addInput(curveTypeInput);
regionInput = new EntityInput<>(Region.class, "Region", "Graphics", null);
this.addInput(regionInput);
relativeEntity = new RelativeEntityInput("RelativeEntity", "Graphics", null);
relativeEntity.setEntity(this);
this.addInput(relativeEntity);
displayModelListInput = new EntityListInput<>( DisplayModel.class, "DisplayModel", "Graphics", null);
this.addInput(displayModelListInput);
displayModelListInput.setUnique(false);
active = new BooleanInput("Active", "Key Inputs", true);
active.setHidden(true);
this.addInput(active);
show = new BooleanInput("Show", "Graphics", true);
this.addInput(show);
movable = new BooleanInput("Movable", "Graphics", true);
this.addInput(movable);
}
/**
* Constructor: initializing the DisplayEntity's graphics
*/
public DisplayEntity() {
ObjectType type = this.getObjectType();
if (type == null)
return;
// Set the default DisplayModel
displayModelListInput.setDefaultValue(type.getDefaultDisplayModel());
this.setDisplayModelList(type.getDefaultDisplayModel());
// Set the default size
sizeInput.setDefaultValue(type.getDefaultSize());
this.setSize(type.getDefaultSize());
// Set the default Alignment
alignmentInput.setDefaultValue(type.getDefaultAlignment());
this.setAlignment(type.getDefaultAlignment());
// Choose which set of keywords to show
this.setGraphicsKeywords();
}
@Override
public void earlyInit() {
super.earlyInit();
this.resetGraphics();
}
/**
* Restores the initial appearance of this entity.
*/
public void resetGraphics() {
this.setPosition(positionInput.getValue());
this.setSize(sizeInput.getValue());
this.setAlignment(alignmentInput.getValue());
this.setOrientation(orientationInput.getValue());
this.setDisplayModelList(displayModelListInput.getValue());
this.setRegion(regionInput.getValue());
}
private void showStandardGraphicsKeywords(boolean bool) {
positionInput.setHidden(!bool);
sizeInput.setHidden(!bool);
alignmentInput.setHidden(!bool);
orientationInput.setHidden(!bool);
}
private void showPolylineGraphicsKeywords(boolean bool) {
pointsInput.setHidden(!bool);
curveTypeInput.setHidden(!bool);
}
public boolean usePointsInput() {
ArrayList<DisplayModel> dmList = displayModelListInput.getValue();
if (dmList == null || dmList.isEmpty())
return false;
boolean isPoly = dmList.get(0) instanceof PolylineModel;
boolean isArrow = dmList.get(0) instanceof ArrowModel;
return isPoly || isArrow;
}
private void setGraphicsKeywords() {
// No displaymodel
if (this instanceof OverlayEntity || displayModelListInput.getValue() == null) {
showStandardGraphicsKeywords(false);
showPolylineGraphicsKeywords(false);
regionInput.setHidden(true);
relativeEntity.setHidden(true);
show.setHidden(true);
movable.setHidden(true);
return;
}
// Polyline type displaymodel
if (usePointsInput()) {
showStandardGraphicsKeywords(false);
showPolylineGraphicsKeywords(true);
return;
}
// Standard displaymodel
showStandardGraphicsKeywords(true);
showPolylineGraphicsKeywords(false);
}
@Override
public void validate()
throws InputErrorException {
super.validate();
if (getDisplayModelList() != null) {
for (DisplayModel dm : getDisplayModelList()) {
if (!dm.canDisplayEntity(this)) {
error("Invalid DisplayModel: %s for this DisplayEntity", dm.getName());
}
}
}
}
@Override
public void setInputsForDragAndDrop() {
// Determine whether the entity should sit on top of the x-y plane
boolean alignBottom = true;
ArrayList<DisplayModel> displayModels = displayModelListInput.getValue();
if (displayModels != null && displayModels.size() > 0) {
DisplayModel dm0 = displayModels.get(0);
if (dm0 instanceof ShapeModel || dm0 instanceof ImageModel || dm0 instanceof TextModel )
alignBottom = false;
}
if (this instanceof Graph || this.usePointsInput() || this instanceof Region) {
alignBottom = false;
}
if (alignBottom)
InputAgent.applyArgs(this, "Alignment", "0.0", "0.0", "-0.5");
}
/**
* Destroys the branchGroup hierarchy for the entity
*/
@Override
public void kill() {
// Kill the label
if (! this.testFlag(FLAG_GENERATED)) {
EntityLabel label = EntityLabel.getLabel(this);
if (label != null)
label.kill();
}
// Kill the DisplayEntity
super.kill();
// Clear the properties
currentRegion = null;
}
public Region getCurrentRegion() {
return currentRegion;
}
/**
* Removes the entity from its current region and assigns a new region
* @param newRegion - the region the entity will be assigned to
*/
public void setRegion( Region newRegion ) {
currentRegion = newRegion;
}
/**
* Update any internal stated needed by either renderer. This is a transition method to get away from
* java3D onto the new renderer.
*
* The JaamSim renderer will only call updateGraphics() while the Java3D renderer will call both
* updateGraphics() and render()
*/
public void updateGraphics(double simTime) {
}
private void calculateEulerRotation(Vec3d val, Vec3d euler) {
double sinx = Math.sin(euler.x);
double siny = Math.sin(euler.y);
double sinz = Math.sin(euler.z);
double cosx = Math.cos(euler.x);
double cosy = Math.cos(euler.y);
double cosz = Math.cos(euler.z);
// Calculate a 3x3 rotation matrix
double m00 = cosy * cosz;
double m01 = -(cosx * sinz) + (sinx * siny * cosz);
double m02 = (sinx * sinz) + (cosx * siny * cosz);
double m10 = cosy * sinz;
double m11 = (cosx * cosz) + (sinx * siny * sinz);
double m12 = -(sinx * cosz) + (cosx * siny * sinz);
double m20 = -siny;
double m21 = sinx * cosy;
double m22 = cosx * cosy;
double x = m00 * val.x + m01 * val.y + m02 * val.z;
double y = m10 * val.x + m11 * val.y + m12 * val.z;
double z = m20 * val.x + m21 * val.y + m22 * val.z;
val.set3(x, y, z);
}
public Vec3d getPositionForAlignment(Vec3d alignment) {
Vec3d temp = new Vec3d(alignment);
synchronized (position) {
temp.sub3(align);
temp.mul3(size);
calculateEulerRotation(temp, orient);
temp.add3(position);
}
return temp;
}
public Vec3d getGlobalPositionForAlignment(Vec3d alignment) {
Vec3d temp = new Vec3d(alignment);
synchronized (position) {
temp.sub3(align);
temp.mul3(size);
calculateEulerRotation(temp, orient);
temp.add3(this.getGlobalPosition());
}
return temp;
}
public Vec3d getOrientation() {
synchronized (position) {
return new Vec3d(orient);
}
}
public void setOrientation(Vec3d orientation) {
synchronized (position) {
orient.set3(orientation);
}
}
public void setSize(Vec3d size) {
synchronized (position) {
this.size.set3(size);
}
}
public Vec3d getPosition() {
synchronized (position) {
return new Vec3d(position);
}
}
public DisplayEntity getRelativeEntity() {
return relativeEntity.getValue();
}
public ArrayList<String> getRelativeEntityOptions() {
return relativeEntity.getValidOptions();
}
public ArrayList<String> getRegionOptions() {
return regionInput.getValidOptions();
}
/**
* Returns the transformation that converts a point in the entity's
* coordinates to the global coordinate system.
* <p>
* The entity's coordinate system is centred on the entity's alignment point
* and its axes are rotated by the entity's orientation angles. It is NOT
* scaled by the entity's size, so the coordinates still have units of
* metres. The effects of the RelativeEntity and Region inputs are included
* in the transformation.
* @return global coordinates for the point.
*/
public Transform getGlobalTrans() {
return getGlobalTransForSize(size);
}
/**
* Returns the equivalent global transform for this entity as if 'sizeIn' where the actual
* size.
* @param sizeIn
* @param simTime
* @return
*/
public Transform getGlobalTransForSize(Vec3d sizeIn) {
// Okay, this math may be hard to follow, this is effectively merging two TRS transforms,
// The first is a translation only transform from the alignment parameter
// Then a transform is built up based on position and orientation
// As size is a non-uniform scale it can not be represented by the jaamsim TRS Transform and therefore
// not actually included in this result, except to adjust the alignment
// Alignment transformations
Vec3d temp = new Vec3d(sizeIn);
temp.mul3(align);
temp.scale3(-1.0d);
Transform alignTrans = new Transform(temp);
// Orientation transformation
Quaternion rot = new Quaternion();
rot.setEuler3(orient);
Transform ret = new Transform(null, rot, 1);
// Combine the alignment and orientation transformations
ret.merge(ret, alignTrans);
// Convert the alignment/orientation transformation to the global coordinate system
if (currentRegion != null)
ret.merge(currentRegion.getRegionTransForVectors(), ret);
// Offset the transformation by the entity's global position vector
ret.getTransRef().add3(getGlobalPosition());
return ret;
}
/**
* Returns the transformation that converts a point in the global
* coordinate system to the entity's coordinates.
* <p>
* The entity's coordinate system is centred on the entity's alignment point
* and its axes are rotated by the entity's orientation angles. It is NOT
* scaled by the entity's size, so the coordinates still have units of
* metres. The effects of the RelativeEntity and Region inputs are included
* in the transformation.
* @return local coordinates for the point.
*/
public Transform getEntityTransForSize(Vec3d sizeIn) {
Transform trans = new Transform();
getGlobalTransForSize(sizeIn).inverse(trans);
return trans;
}
/**
* Returns the global transform with scale factor all rolled into a Matrix4d
* @return
*/
public Mat4d getTransMatrix() {
Transform trans = getGlobalTrans();
Mat4d ret = new Mat4d();
trans.getMat4d(ret);
ret.scaleCols3(getSize());
return ret;
}
/**
* Returns the inverse global transform with scale factor all rolled into a Matrix4d
* @return
*/
public Mat4d getInvTransMatrix() {
return RenderUtils.getInverseWithScale(getGlobalTrans(), size);
}
/**
* Return the position in the global coordinate system
* @return
*/
public Vec3d getGlobalPosition() {
return getGlobalPosition(getPosition());
}
/**
* Convert the specified local coordinate to the global coordinate system
* @param pos - a position in the entity's local coordinate system
* @return
*/
public Vec3d getGlobalPosition(Vec3d pos) {
Vec3d ret = new Vec3d(pos);
// Position is relative to another entity
DisplayEntity ent = this.getRelativeEntity();
if (ent != null) {
if (currentRegion != null)
currentRegion.getRegionTransForVectors().multAndTrans(ret, ret);
ret.add3(ent.getGlobalPosition());
return ret;
}
// Position is given in a local coordinate system
if (currentRegion != null)
currentRegion.getRegionTrans().multAndTrans(ret, ret);
return ret;
}
/*
* Returns the center relative to the origin
*/
public Vec3d getAbsoluteCenter() {
Vec3d cent = this.getPositionForAlignment(new Vec3d());
DisplayEntity ent = this.getRelativeEntity();
if (ent != null)
cent.add3(ent.getGlobalPosition());
return cent;
}
/**
* Returns the extent for the DisplayEntity
*/
public Vec3d getSize() {
synchronized (position) {
return new Vec3d(size);
}
}
public Vec3d getAlignment() {
synchronized (position) {
return new Vec3d(align);
}
}
public void setAlignment(Vec3d align) {
synchronized (position) {
this.align.set3(align);
}
}
public void setPosition(Vec3d pos) {
synchronized (position) {
position.set3(pos);
}
}
/**
* Set the global position for this entity, this takes into account the region
* transform and sets the local position accordingly
* @param pos - The new position in the global coordinate system
*/
public void setInputForGlobalPosition(Vec3d pos) {
Vec3d localPos = this.getLocalPosition(pos);
setPosition(localPos);
KeywordIndex kw = InputAgent.formatPointInputs(positionInput.getKeyword(), localPos, "m");
InputAgent.apply(this, kw);
}
public void setGlobalPosition(Vec3d pos) {
setPosition(getLocalPosition(pos));
}
/**
* Returns the local coordinates for this entity corresponding to the
* specified global coordinates.
* @param pos - a position in the global coordinate system
*/
public Vec3d getLocalPosition(Vec3d pos) {
Vec3d localPos = new Vec3d(pos);
// Position is relative to another entity
DisplayEntity ent = this.getRelativeEntity();
if (ent != null) {
localPos.sub3(ent.getGlobalPosition());
if (currentRegion != null)
currentRegion.getInverseRegionTransForVectors().multAndTrans(localPos, localPos);
return localPos;
}
// Position is given in a local coordinate system
if (currentRegion != null)
currentRegion.getInverseRegionTrans().multAndTrans(pos, localPos);
return localPos;
}
/*
* move object to argument point based on alignment
*/
public void setPositionForAlignment(Vec3d alignment, Vec3d position) {
// Calculate the difference between the desired point and the current aligned position
Vec3d diff = this.getPositionForAlignment(alignment);
diff.sub3(position);
diff.scale3(-1.0d);
// add the difference to the current position and set the new position
diff.add3(getPosition());
setPosition(diff);
}
public void setGlobalPositionForAlignment(Vec3d alignment, Vec3d position) {
// Calculate the difference between the desired point and the current aligned position
Vec3d diff = this.getGlobalPositionForAlignment(alignment);
diff.sub3(position);
diff.scale3(-1.0d);
// add the difference to the current position and set the new position
diff.add3(getPosition());
setPosition(diff);
}
/**
* Returns the transformation to global coordinates from the local
* coordinate system determined by the entity's Region and RelativeEntity
* inputs.
* <p>
* Note that this local coordinate system is centred on the position of
* the RelativeEntity, not on the position of this entity.
* @return transformation to global coordinates.
*/
public Transform getGlobalPositionTransform() {
Transform ret = new Transform(null, null, 1.0d);
// Position is relative to another entity
DisplayEntity relEnt = this.getRelativeEntity();
if (relEnt != null) {
if (currentRegion != null)
ret = currentRegion.getRegionTransForVectors();
ret.getTransRef().add3(relEnt.getGlobalPosition());
return ret;
}
// Position is given in a local coordinate system
if (currentRegion != null)
ret = currentRegion.getRegionTrans();
return ret;
}
public ArrayList<DisplayModel> getDisplayModelList() {
return displayModelList;
}
public void setDisplayModelList(ArrayList<DisplayModel> dmList) {
displayModelList.clear();
if (dmList == null)
return;
for (DisplayModel dm : dmList) {
displayModelList.add(dm);
}
clearBindings(); // Clear this on any change, and build it lazily later
}
public final void clearBindings() {
modelBindings = null;
}
public ArrayList<DisplayModelBinding> getDisplayBindings() {
if (modelBindings == null) {
// Populate the model binding list
if (getDisplayModelList() == null) {
modelBindings = new ArrayList<>();
return modelBindings;
}
modelBindings = new ArrayList<>(getDisplayModelList().size());
for (int i = 0; i < getDisplayModelList().size(); ++i) {
DisplayModel dm = getDisplayModelList().get(i);
modelBindings.add(dm.getBinding(this));
}
}
return modelBindings;
}
public void dragged(Vec3d distance) {
Vec3d newPos = this.getPosition();
newPos.add3(distance);
if (Simulation.isSnapToGrid())
newPos = Simulation.getSnapGridPosition(newPos);
KeywordIndex kw = InputAgent.formatPointInputs(positionInput.getKeyword(), newPos, "m");
InputAgent.apply(this, kw);
if (!usePointsInput())
return;
ArrayList<Vec3d> points = pointsInput.getValue();
if (points == null || points.isEmpty())
return;
Vec3d dist = new Vec3d(newPos);
dist.sub3(points.get(0));
kw = InputAgent.formatPointsInputs(pointsInput.getKeyword(), pointsInput.getValue(), dist);
InputAgent.apply(this, kw);
}
public boolean isActive() {
return active.getValue();
}
public boolean getShow() {
return show.getValue();
}
public boolean isMovable() {
return movable.getValue();
}
public void handleKeyPressed(int keyCode, char keyChar, boolean shift, boolean control, boolean alt) {
if (!isMovable())
return;
Vec3d pos = getPosition();
double inc = Simulation.getIncrementSize();
if (Simulation.isSnapToGrid())
inc = Math.max(inc, Simulation.getSnapGridSpacing());
switch (keyCode) {
case KeyEvent.VK_LEFT:
pos.x -= inc;
break;
case KeyEvent.VK_RIGHT:
pos.x +=inc;
break;
case KeyEvent.VK_UP:
if (shift)
pos.z += inc;
else
pos.y += inc;
break;
case KeyEvent.VK_DOWN:
if (shift)
pos.z -= inc;
else
pos.y -= inc;
break;
}
if (Simulation.isSnapToGrid())
pos = Simulation.getSnapGridPosition(pos);
KeywordIndex kw = InputAgent.formatPointInputs(positionInput.getKeyword(), pos, "m");
InputAgent.apply(this, kw);
}
public void handleKeyReleased(int keyCode, char keyChar, boolean shift, boolean control, boolean alt) {
if (keyCode == KeyEvent.VK_DELETE) {
this.kill();
FrameBox.setSelectedEntity(null, false);
return;
}
}
public void handleMouseClicked(short count, Vec3d globalCoord) {}
public boolean handleDrag(Vec3d currentPt, Vec3d firstPt) {
return false;
}
/**
* An overloadable method that is called when the 'create link' feature is enabled and selection changes
* @param ent
*/
public void linkTo(DisplayEntity nextEnt) {
// Do nothing in default behavior
}
/**
* This method updates the DisplayEntity for changes in the given input
*/
@Override
public void updateForInput( Input<?> in ) {
super.updateForInput( in );
if( in == positionInput ) {
this.setPosition( positionInput.getValue() );
return;
}
if( in == sizeInput ) {
this.setSize( sizeInput.getValue() );
return;
}
if( in == orientationInput ) {
this.setOrientation( orientationInput.getValue() );
return;
}
if( in == alignmentInput ) {
this.setAlignment( alignmentInput.getValue() );
return;
}
if( in == regionInput ) {
this.setRegion(regionInput.getValue());
}
if (in == displayModelListInput) {
this.setDisplayModelList( displayModelListInput.getValue() );
}
// If Points were input, then use them to set the start and end coordinates
if( in == pointsInput || in == curveTypeInput) {
invalidateScreenPoints();
return;
}
}
private final Object screenPointLock = new Object();
private PolylineInfo[] cachedPointInfo;
private ArrayList<Vec3d> cachedCurvePoints;
protected final void invalidateScreenPoints() {
synchronized(screenPointLock) {
cachedPointInfo = null;
cachedCurvePoints = null;
}
}
public final PolylineInfo[] getScreenPoints(double simTime) {
synchronized(screenPointLock) {
if (cachedPointInfo == null)
cachedPointInfo = this.buildScreenPoints(simTime);
return cachedPointInfo;
}
}
public PolylineInfo[] buildScreenPoints(double simTime) {
PolylineInfo[] ret = new PolylineInfo[1];
ret[0] = new PolylineInfo(pointsInput.getValue(), getCurveType(), ColourInput.BLACK, 1);
return ret;
}
public ArrayList<Vec3d> getPoints() {
synchronized(screenPointLock) {
return new ArrayList<>(pointsInput.getValue());
}
}
public ArrayList<Vec3d> getCurvePoints() {
synchronized(screenPointLock) {
if (cachedCurvePoints == null)
cachedCurvePoints = this.buildCurvePoints();
return cachedCurvePoints;
}
}
private ArrayList<Vec3d> buildCurvePoints() {
ArrayList<Vec3d> ret = null;
switch (this.getCurveType()) {
case LINEAR:
ret = getPoints();
break;
case BEZIER:
ret = PolylineInfo.getBezierPoints(getPoints());
break;
case SPLINE:
ret = PolylineInfo.getSplinePoints(getPoints());
break;
default:
assert(false);
error("Invalid CurveType");
}
return ret;
}
/**
* Returns the local coordinates for a specified fractional distance along a polyline.
* @param frac - fraction of the total graphical length of the polyline
* @return local coordinates for the specified position
*/
public Vec3d getPositionOnPolyline(double simTime, double frac) {
ArrayList<Vec3d> curvePoints = this.getCurvePoints();
// Calculate the cumulative graphical lengths along the polyline
double[] cumLengthList = this.getCumulativeLengths(simTime);
// Find the insertion point by binary search
double dist = frac * cumLengthList[cumLengthList.length-1];
int k = Arrays.binarySearch(cumLengthList, dist);
// Exact match
if (k >= 0)
return curvePoints.get(k);
// Error condition
if (k == -1)
return new Vec3d();
// Insertion index = -k-1
int index = -k - 1;
// Interpolate the final position between the two points
if (index == cumLengthList.length) {
return new Vec3d(curvePoints.get(index-1));
}
double fracInSegment = (dist - cumLengthList[index-1]) /
(cumLengthList[index] - cumLengthList[index-1]);
Vec3d vec = new Vec3d();
vec.interpolate3(curvePoints.get(index-1),
curvePoints.get(index),
fracInSegment);
return vec;
}
/**
* Returns the local coordinates for a sub-section of the polyline specified by a first and
* last fractional distance.
* @param frac0 - fractional distance for the start of the sub-polyline
* @param frac1 - fractional distance for the end of the sub-polyline
* @return array of local coordinates for the sub-polyline
*/
public ArrayList<Vec3d> getSubPolyline(double simTime, double frac0, double frac1) {
ArrayList<Vec3d> curvePoints = this.getCurvePoints();
ArrayList<Vec3d> ret = new ArrayList<>();
// Calculate the cumulative graphical lengths along the polyline
double[] cumLengthList = this.getCumulativeLengths(simTime);
// Find the insertion point for the first distance using binary search
double dist0 = frac0 * cumLengthList[cumLengthList.length-1];
int k = Arrays.binarySearch(cumLengthList, dist0);
if (k == -1)
error("Unable to find position in polyline using binary search.");
// Interpolate the position of the first node
int index;
if (k >= 0) {
ret.add(curvePoints.get(k));
index = k + 1;
if (index == cumLengthList.length)
return ret;
}
else {
Vec3d vec;
index = -k - 1;
if (index == cumLengthList.length) {
vec = new Vec3d(curvePoints.get(index-1));
}
else {
double fracInSegment = (dist0 - cumLengthList[index-1]) /
(cumLengthList[index] - cumLengthList[index-1]);
vec = new Vec3d();
vec.interpolate3(curvePoints.get(index-1),
curvePoints.get(index),
fracInSegment);
}
ret.add(vec);
}
// Loop through the indices following the insertion point
double dist1 = frac1 * cumLengthList[cumLengthList.length-1];
while (index < cumLengthList.length && cumLengthList[index] < dist1) {
ret.add(curvePoints.get(index));
index++;
}
if (index == cumLengthList.length)
return ret;
// Interpolate the position of the last node
Vec3d vec = new Vec3d();
double fracInSegment = (dist1 - cumLengthList[index-1]) /
(cumLengthList[index] - cumLengthList[index-1]);
vec.interpolate3(curvePoints.get(index-1),
curvePoints.get(index),
fracInSegment);
ret.add(vec);
return ret;
}
/**
* Returns the cumulative graphics lengths for the nodes along the polyline.
* @return array of cumulative graphical lengths
*/
private double[] getCumulativeLengths(double simTime) {
ArrayList<Vec3d> curvePoints = this.getCurvePoints();
int n = curvePoints.size();
double[] cumLengthList = new double[n];
cumLengthList[0] = 0.0;
for (int i = 1; i < n; i++) {
Vec3d vec = new Vec3d();
vec.sub3(curvePoints.get(i), curvePoints.get(i-1));
cumLengthList[i] = cumLengthList[i-1] + vec.mag3();
}
return cumLengthList;
}
public boolean selectable() {
return true;
}
protected PolylineInfo.CurveType getCurveType() {
return curveTypeInput.getValue();
}
public final void setTagColour(String tagName, Color4d ca) {
Color4d cas[] = new Color4d[1] ;
cas[0] = ca;
setTagColours(tagName, cas);
}
public final void setTagColours(String tagName, Color4d[] cas) {
Tag t = tagMap.get(tagName);
if (t == null) {
t = new Tag(cas, null, true);
tagMap.put(tagName, t);
return;
}
if (t.colorsMatch(cas))
return;
else
tagMap.put(tagName, new Tag(cas, t.sizes, t.visible));
}
public final void setTagSize(String tagName, double size) {
double s[] = new double[1] ;
s[0] = size;
setTagSizes(tagName, s);
}
public final void setTagSizes(String tagName, double[] sizes) {
Tag t = tagMap.get(tagName);
if (t == null) {
t = new Tag(null, sizes, true);
tagMap.put(tagName, t);
return;
}
if (t.sizesMatch(sizes))
return;
else
tagMap.put(tagName, new Tag(t.colors, sizes, t.visible));
}
public final void setTagVisibility(String tagName, boolean isVisible) {
Tag t = tagMap.get(tagName);
if (t == null) {
t = new Tag(null, null, isVisible);
tagMap.put(tagName, t);
return;
}
if (t.visMatch(isVisible))
return;
else
tagMap.put(tagName, new Tag(t.colors, t.sizes, isVisible));
}
/**
* Get all tags for this entity
* @return
*/
public HashMap<String, Tag> getTagSet() {
return tagMap;
}
////////////////////////////////////////////////////////////////////////
// Outputs
////////////////////////////////////////////////////////////////////////
@Output(name = "Position",
description = "The present {x, y, z} coordinates of the DisplayEntity in its region.",
unitType = DistanceUnit.class,
sequence = 0)
public Vec3d getPosOutput(double simTime) {
return getPosition();
}
@Output(name = "Size",
description = "The present {x, y, z} components of the DisplayEntity's size.",
unitType = DistanceUnit.class,
sequence = 1)
public Vec3d getSizeOutput(double simTime) {
return getSize();
}
@Output(name = "Orientation",
description = "The present {x, y, z} euler angles of the DisplayEntity's rotation.",
unitType = AngleUnit.class,
sequence = 2)
public Vec3d getOrientOutput(double simTime) {
return getOrientation();
}
@Output(name = "Alignment",
description = "The present {x, y, z} coordinates of a point on the DisplayEntity that aligns "
+ "direction with the position output. Each component should be in the range "
+ "[-0.5, 0.5].",
unitType = DimensionlessUnit.class,
sequence = 3)
public Vec3d getAlignOutput(double simTime) {
return getAlignment();
}
}