/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2013 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.DisplayModels;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.util.ArrayList;
import java.util.Arrays;
import com.jaamsim.Graphics.BillboardText;
import com.jaamsim.Graphics.OverlayText;
import com.jaamsim.Graphics.TextBasics;
import com.jaamsim.basicsim.Entity;
import com.jaamsim.controllers.RenderManager;
import com.jaamsim.datatypes.IntegerVector;
import com.jaamsim.input.BooleanInput;
import com.jaamsim.input.ColourInput;
import com.jaamsim.input.Input;
import com.jaamsim.input.Keyword;
import com.jaamsim.input.StringChoiceInput;
import com.jaamsim.input.StringListInput;
import com.jaamsim.input.Vec3dInput;
import com.jaamsim.math.Color4d;
import com.jaamsim.math.Transform;
import com.jaamsim.math.Vec3d;
import com.jaamsim.math.Vec4d;
import com.jaamsim.render.BillboardStringProxy;
import com.jaamsim.render.DisplayModelBinding;
import com.jaamsim.render.LineProxy;
import com.jaamsim.render.OverlayStringProxy;
import com.jaamsim.render.PolygonProxy;
import com.jaamsim.render.RenderProxy;
import com.jaamsim.render.RenderUtils;
import com.jaamsim.render.StringProxy;
import com.jaamsim.render.TessFontKey;
import com.jaamsim.render.VisibilityInfo;
public class TextModel extends DisplayModel {
@Keyword(description = "The font to be used for the text.",
exampleList = { "Arial" })
private final StringChoiceInput fontName;
@Keyword(description = "The font styles to be applied to the text, e.g. Bold, Italic. ",
exampleList = { "Bold" })
private final StringListInput fontStyle;
@Keyword(description = "The colour of the text, specified by a colour keyword or RGB values.",
exampleList = { "red", "skyblue", "135 206 235" })
private final ColourInput fontColor;
@Keyword(description = "If TRUE, then a drop shadow appears for the text.",
exampleList = { "TRUE" })
private final BooleanInput dropShadow;
@Keyword(description = "The colour for the drop shadow, specified by a colour keyword or "
+ "RGB values.",
exampleList = { "red", "skyblue", "135 206 235" })
private final ColourInput dropShadowColor;
@Keyword(description = "The { x, y, z } coordinates of the drop shadow's offset, expressed "
+ "as a decimal fraction of the text height.",
exampleList = { "0.1 -0.1 0.001" })
private final Vec3dInput dropShadowOffset;
private int style; // Font Style
private static final int defFont;
public static final ArrayList<String> validFontNames;
public static final ArrayList<String> validStyles;
static {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[ ] fontNames = ge.getAvailableFontFamilyNames();
Arrays.sort(fontNames);
validFontNames = new ArrayList<>(Arrays.asList(fontNames));
int def = validFontNames.indexOf("Verdana");
if (def > -1)
defFont = def;
else
defFont = 0;
validStyles = new ArrayList<>();
validStyles.add("BOLD");
validStyles.add("ITALIC");
}
{
fontName = new StringChoiceInput("FontName", "Key Inputs", defFont);
fontName.setChoices(validFontNames);
this.addInput(fontName);
fontColor = new ColourInput("FontColour", "Key Inputs", ColourInput.BLACK);
this.addInput(fontColor);
this.addSynonym(fontColor, "FontColor");
fontStyle = new StringListInput("FontStyle", "Key Inputs", new ArrayList<String>(0));
fontStyle.setValidOptions(validStyles);
fontStyle.setCaseSensitive(false);
this.addInput(fontStyle);
dropShadow = new BooleanInput("DropShadow", "Key Inputs", false);
this.addInput(dropShadow);
dropShadowColor = new ColourInput("DropShadowColour", "Key Inputs", ColourInput.BLACK);
this.addInput(dropShadowColor);
this.addSynonym(dropShadowColor, "DropShadowColor");
dropShadowOffset = new Vec3dInput("DropShadowOffset", "Key Inputs", new Vec3d(-0.1d, -0.1d, -0.001d));
this.addInput(dropShadowOffset);
style = Font.PLAIN;
}
@Override
public void updateForInput(Input<?> in) {
super.updateForInput(in);
if (in == fontStyle) {
style = getStyle(fontStyle.getValue());
}
}
private static int getStyle(ArrayList<String> strArray) {
int ret = Font.PLAIN;
for(String each: strArray ) {
if(each.equalsIgnoreCase("Bold") ) {
ret += Font.BOLD;
}
else if (each.equalsIgnoreCase("Italic")) {
ret += Font.ITALIC;
}
}
return ret;
}
@Override
public DisplayModelBinding getBinding(Entity ent) {
if (ent instanceof BillboardText) {
return new BillboardBinding(ent, this);
} else if (ent instanceof TextBasics) {
return new Binding(ent, this);
} else if (ent instanceof OverlayText){
return new OverlayBinding(ent, this);
}
assert(false);
return null;
}
@Override
public boolean canDisplayEntity(Entity ent) {
return (ent instanceof TextBasics) || (ent instanceof OverlayText);
}
private class Binding extends DisplayModelBinding {
private TextBasics labelObservee;
private String textCache;
private Transform transCache;
private Color4d colorCache;
private double heightCache;
private TessFontKey fkCache;
private boolean dropShadowCache;
private Vec3d dsOffsetCache;
private Color4d dsColorCache;
private boolean editModeCache;
private int insertPosCache;
private int numSelectedCache;
private VisibilityInfo viCache;
private ArrayList<RenderProxy> cachedProxies = null;
public Binding(Entity ent, DisplayModel dm) {
super(ent, dm);
try {
labelObservee = (TextBasics)ent;
} catch (ClassCastException e) {
// The observee is not a display entity
labelObservee = null;
}
}
@Override
public void collectProxies(double simTime, ArrayList<RenderProxy> out) {
if (labelObservee == null || !labelObservee.getShow()) {
return;
}
String text = labelObservee.getCachedText();
double height = labelObservee.getTextHeight();
Color4d color = fontColor.getValue();
if (!labelObservee.getFontColorInput().isDefault()) {
color = labelObservee.getFontColorInput().getValue();
}
int stl = style;
if (!labelObservee.getFontStyleInput().isDefault()) {
stl = getStyle(labelObservee.getFontStyleInput().getValue());
}
String fn = fontName.getChoice();
if (!labelObservee.getFontNameInput().isDefault())
fn = labelObservee.getFontNameInput().getChoice();
TessFontKey fk = new TessFontKey(fn, stl);
Vec3d textSize = RenderManager.inst().getRenderedStringSize(fk, height, text);
Transform trans = labelObservee.getGlobalTransForSize(textSize);
boolean ds = dropShadow.getValue();
if (!labelObservee.getDropShadowInput().isDefault()) {
ds = labelObservee.getDropShadowInput().getValue();
}
Color4d dsColor = dropShadowColor.getValue();
if (!labelObservee.getDropShadowColorInput().isDefault()) {
dsColor = labelObservee.getDropShadowColorInput().getValue();
}
Vec3d dsOffset = dropShadowOffset.getValue();
if (!labelObservee.getDropShadowOffsetInput().isDefault()) {
dsOffset = labelObservee.getDropShadowOffsetInput().getValue();
}
boolean editMode = labelObservee.isEditMode();
int insertPos = labelObservee.getInsertPosition();
int numSelected = labelObservee.getNumberSelected();
VisibilityInfo vi = getVisibilityInfo();
boolean dirty = false;
dirty = dirty || !compare(textCache, text);
dirty = dirty || !compare(transCache, trans);
dirty = dirty || dirty_col4d(colorCache, color);
dirty = dirty || heightCache != height;
dirty = dirty || !compare(fkCache, fk);
dirty = dirty || dropShadowCache != ds;
dirty = dirty || dirty_col4d(dsColorCache, dsColor);
dirty = dirty || dirty_vec3d(dsOffsetCache, dsOffset);
dirty = dirty || editMode != editModeCache;
dirty = dirty || insertPos != insertPosCache;
dirty = dirty || numSelected != numSelectedCache;
dirty = dirty || !compare(viCache, vi);
textCache = text;
transCache = trans;
colorCache = color;
heightCache = height;
fkCache = fk;
dropShadowCache = ds;
dsColorCache = dsColor;
dsOffsetCache = dsOffset;
editModeCache = editMode;
insertPosCache = insertPos;
numSelectedCache = numSelected;
viCache = vi;
if (cachedProxies != null && !dirty) {
// Nothing changed
out.addAll(cachedProxies);
registerCacheHit("TextLabel");
return;
}
registerCacheMiss("TextLabel");
if (trans == null) {
return;
}
cachedProxies = new ArrayList<>();
// If the text is being edited, show the selection and the text insertion mark
if (editMode) {
double length = RenderManager.inst().getRenderedStringLength(fk, height, text);
double ycoord = 0.5*height*1.5d;
double zcoord = 0.01*height;
// Highlight the selected text
if (numSelected != 0) {
int startPos = Math.min(insertPos, insertPos + numSelected);
int endPos = Math.max(insertPos, insertPos + numSelected);
// Calculate the position of the selected text in metres relative to the centre of the string
double start = RenderManager.inst().getOffsetForStringPosition(fk, height, text, startPos) - 0.5d*length;
double end = RenderManager.inst().getOffsetForStringPosition(fk, height, text, endPos) - 0.5d*length;
ArrayList<Vec4d> rect = new ArrayList<>();
rect.add(new Vec4d( start, ycoord, -zcoord, 1.0d ));
rect.add(new Vec4d( start, -ycoord, -zcoord, 1.0d ));
rect.add(new Vec4d( end, -ycoord, -zcoord, 1.0d ));
rect.add(new Vec4d( end, ycoord, -zcoord, 1.0d ));
Vec3d scale = new Vec3d(1.0d, 1.0d, 1.0d);
cachedProxies.add(new PolygonProxy(rect, trans, scale,
ColourInput.LIGHT_GREY, false, 1, vi, labelObservee.getEntityNumber()));
}
// Show the text insertion mark
double insert = RenderManager.inst().getOffsetForStringPosition(fk, height, text, insertPos) - 0.5d*length;
ArrayList<Vec4d> points = new ArrayList<>();
points.add(new Vec4d( insert, -ycoord, zcoord, 1.0d ));
points.add(new Vec4d( insert, ycoord, zcoord, 1.0d ));
RenderUtils.transformPointsLocal(trans, points, 0);
cachedProxies.add(new LineProxy(points, ColourInput.BLACK, 1, vi, labelObservee.getEntityNumber()));
}
// Show the text
cachedProxies.add(new StringProxy(text, fk, color, trans, height, vi, labelObservee.getEntityNumber()));
// Show the drop shadow
if (ds) {
Transform dsTrans = new Transform(trans);
Vec3d shadowTrans = new Vec3d(dsOffset);
shadowTrans.scale3(height);
shadowTrans.add3(dsTrans.getTransRef());
dsTrans.setTrans(shadowTrans);
cachedProxies.add(new StringProxy(text, fk, dsColor, dsTrans, height, vi, labelObservee.getEntityNumber()));
}
out.addAll(cachedProxies);
}
}
private class OverlayBinding extends DisplayModelBinding {
private OverlayText labelObservee;
private String textCache;
private Color4d colorCache;
private IntegerVector posCache;
private int heightCache;
private boolean alignRightCache;
private boolean alignBottomCache;
private TessFontKey fkCache;
private boolean dropShadowCache;
private Vec3d dsOffsetCache;
private Color4d dsColorCache;
private VisibilityInfo viCache;
private ArrayList<RenderProxy> cachedProxies = null;
public OverlayBinding(Entity ent, DisplayModel dm) {
super(ent, dm);
try {
labelObservee = (OverlayText)ent;
} catch (ClassCastException e) {
// The observee is not a display entity
labelObservee = null;
}
}
@Override
public void collectProxies(double simTime, ArrayList<RenderProxy> out) {
if (labelObservee == null || !labelObservee.getShow()) {
return;
}
String text = labelObservee.getCachedText();
IntegerVector pos = labelObservee.getScreenPosition();
int height = labelObservee.getTextHeight();
boolean alignRight = labelObservee.getAlignRight();
boolean alignBottom = labelObservee.getAlignBottom();
Color4d color = fontColor.getValue();
if (!labelObservee.getFontColorInput().isDefault()) {
color = labelObservee.getFontColorInput().getValue();
}
int stl = style;
if (!labelObservee.getFontStyleInput().isDefault()) {
stl = getStyle(labelObservee.getFontStyleInput().getValue());
}
String fn = fontName.getChoice();
if (!labelObservee.getFontNameInput().isDefault())
fn = labelObservee.getFontNameInput().getChoice();
TessFontKey fk = new TessFontKey(fn, stl);
boolean ds = dropShadow.getValue();
if (!labelObservee.getDropShadowInput().isDefault()) {
ds = labelObservee.getDropShadowInput().getValue();
}
Color4d dsColor = dropShadowColor.getValue();
if (!labelObservee.getDropShadowColorInput().isDefault()) {
dsColor = labelObservee.getDropShadowColorInput().getValue();
}
Vec3d dsOffset = new Vec3d(dropShadowOffset.getValue());
if (!labelObservee.getDropShadowOffsetInput().isDefault()) {
dsOffset = new Vec3d(labelObservee.getDropShadowOffsetInput().getValue());
}
dsOffset.scale3(height);
VisibilityInfo vi = getVisibilityInfo();
boolean dirty = false;
dirty = dirty || !compare(textCache, text);
dirty = dirty || dirty_col4d(colorCache, color);
dirty = dirty || !compare(posCache, pos);
dirty = dirty || heightCache != height;
dirty = dirty || alignRightCache != alignRight;
dirty = dirty || alignBottomCache != alignBottom;
dirty = dirty || !compare(fkCache, fk);
dirty = dirty || dropShadowCache != ds;
dirty = dirty || dirty_col4d(dsColorCache, dsColor);
dirty = dirty || dirty_vec3d(dsOffsetCache, dsOffset);
dirty = dirty || !compare(viCache, vi);
textCache = text;
colorCache = color;
posCache = pos;
heightCache = height;
alignRightCache = alignRight;
alignBottomCache = alignBottom;
fkCache = fk;
dropShadowCache = ds;
dsColorCache = dsColor;
dsOffsetCache = dsOffset;
viCache = vi;
if (cachedProxies != null && !dirty) {
// Nothing changed
out.addAll(cachedProxies);
registerCacheHit("OverlayText");
return;
}
registerCacheMiss("OverlayText");
cachedProxies = new ArrayList<>();
if (ds) {
cachedProxies.add(new OverlayStringProxy(text, fk, dsColor, height,
pos.get(0) + (dsOffset.x * (alignRight ? -1 : 1)),
pos.get(1) - (dsOffset.y * (alignBottom ? -1 : 1)),
alignRight, alignBottom, vi));
}
cachedProxies.add(new OverlayStringProxy(text, fk, color, height, pos.get(0), pos.get(1),
alignRight, alignBottom, vi));
out.addAll(cachedProxies);
}
@Override
protected void collectSelectionBox(double simTime, ArrayList<RenderProxy> out) {
// No selection widgets for now
}
}
private class BillboardBinding extends DisplayModelBinding {
private BillboardText labelObservee;
private String textCache;
private Color4d colorCache;
private Vec3d posCache;
private int heightCache;
private TessFontKey fkCache;
private boolean dropShadowCache;
private Vec3d dsOffsetCache;
private Color4d dsColorCache;
private VisibilityInfo viCache;
private ArrayList<RenderProxy> cachedProxies = null;
public BillboardBinding(Entity ent, DisplayModel dm) {
super(ent, dm);
try {
labelObservee = (BillboardText)ent;
} catch (ClassCastException e) {
// The observee is not a display entity
labelObservee = null;
}
}
@Override
public void collectProxies(double simTime, ArrayList<RenderProxy> out) {
if (labelObservee == null || !labelObservee.getShow()) {
return;
}
String text = labelObservee.getCachedText();
int height = (int)labelObservee.getTextHeight();
Color4d color = fontColor.getValue();
if (!labelObservee.getFontColorInput().isDefault()) {
color = labelObservee.getFontColorInput().getValue();
}
int stl = style;
if (!labelObservee.getFontStyleInput().isDefault()) {
stl = getStyle(labelObservee.getFontStyleInput().getValue());
}
String fn = fontName.getChoice();
if (!labelObservee.getFontNameInput().isDefault())
fn = labelObservee.getFontNameInput().getChoice();
TessFontKey fk = new TessFontKey(fn, stl);
boolean ds = dropShadow.getValue();
if (!labelObservee.getDropShadowInput().isDefault()) {
ds = labelObservee.getDropShadowInput().getValue();
}
Color4d dsColor = dropShadowColor.getValue();
if (!labelObservee.getDropShadowColorInput().isDefault()) {
dsColor = labelObservee.getDropShadowColorInput().getValue();
}
Vec3d dsOffset = new Vec3d(dropShadowOffset.getValue());
if (!labelObservee.getDropShadowOffsetInput().isDefault()) {
dsOffset = new Vec3d(labelObservee.getDropShadowOffsetInput().getValue());
}
dsOffset.scale3(height);
Vec3d pos = labelObservee.getGlobalPosition();
VisibilityInfo vi = getVisibilityInfo();
boolean dirty = false;
dirty = dirty || !compare(textCache, text);
dirty = dirty || dirty_col4d(colorCache, color);
dirty = dirty || heightCache != height;
dirty = dirty || dirty_vec3d(posCache, pos);
dirty = dirty || !compare(fkCache, fk);
dirty = dirty || dropShadowCache != ds;
dirty = dirty || dirty_col4d(dsColorCache, dsColor);
dirty = dirty || dirty_vec3d(dsOffsetCache, dsOffset);
dirty = dirty || !compare(viCache, vi);
textCache = text;
colorCache = color;
posCache = pos;
heightCache = height;
fkCache = fk;
dropShadowCache = ds;
dsColorCache = dsColor;
dsOffsetCache = dsOffset;
viCache = vi;
if (cachedProxies != null && !dirty) {
// Nothing changed
out.addAll(cachedProxies);
registerCacheHit("OverlayText");
return;
}
registerCacheMiss("OverlayText");
cachedProxies = new ArrayList<>();
if (ds) {
cachedProxies.add(new BillboardStringProxy(text, fk, dsColor, height, pos, dsOffset.x, dsOffset.y, vi));
}
cachedProxies.add(new BillboardStringProxy(text, fk, color, height, pos, 0, 0, vi));
out.addAll(cachedProxies);
}
@Override
protected void collectSelectionBox(double simTime, ArrayList<RenderProxy> out) {
// No selection widgets for now
}
}
public TessFontKey getTessFontKey() {
return new TessFontKey(fontName.getChoice(), style);
}
public static TessFontKey getDefaultTessFontKey() {
return new TessFontKey("Verdana", Font.PLAIN);
}
public Color4d getFontColor() {
return fontColor.getValue();
}
}