/* * 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.ui; import java.awt.EventQueue; import java.awt.Frame; import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.ArrayList; import java.util.Locale; import com.jaamsim.Graphics.DisplayEntity; import com.jaamsim.Graphics.Region; import com.jaamsim.basicsim.Entity; import com.jaamsim.controllers.RenderManager; import com.jaamsim.datatypes.IntegerVector; import com.jaamsim.input.BooleanInput; import com.jaamsim.input.EntityInput; import com.jaamsim.input.FileInput; import com.jaamsim.input.Input; import com.jaamsim.input.InputAgent; import com.jaamsim.input.IntegerListInput; import com.jaamsim.input.KeyedVec3dInput; import com.jaamsim.input.Keyword; import com.jaamsim.input.KeywordIndex; import com.jaamsim.input.StringInput; import com.jaamsim.input.Vec3dInput; import com.jaamsim.math.Transform; import com.jaamsim.math.Vec3d; import com.jaamsim.math.Vec4d; import com.jaamsim.units.DistanceUnit; public class View extends Entity { private static final ArrayList<View> allInstances; public static final int OMNI_VIEW_ID = -1; public static final int NO_VIEW_ID = 0; private static int nextID = 1; private final int viewID; private boolean keepWindowOpen; // used by GUIFrame to determine whether a window is open or closed @Keyword(description = "The region in which the view's coordinates are given.", example = "View1 Region { Region1 }") private final EntityInput<Region> region; @Keyword(description = "The position the view camera is looking at.", example = "View1 ViewCenter { 0 0 0 m }") private final Vec3dInput center; @Keyword(description = "The position the view camera is looking from.", example = "View1 ViewPosition { 0 0 50 m }") private final Vec3dInput position; @Keyword(description = "The size of the window in pixels (width, height).", example = "View1 WindowSize { 500 300 }") private final IntegerListInput windowSize; @Keyword(description = "The position of the upper left corner of the window in pixels measured" + "from the top left corner of the screen.", example = "View1 WindowPosition { 220 110 }") private final IntegerListInput windowPos; @Keyword(description = "Text to place in the title bar of the view window. The window must " + "be closed and re-opened manually after changing the title.", example = "View1 TilteBarText { 'An Example Title' }") private final StringInput titleBar; @Keyword(description = "If TRUE, the view window is displayed on screen.", example = "View1 ShowWindow { FALSE }") private final BooleanInput showWindow; @Keyword(description = "A Boolean indicating whether the view can be panned or rotated.", example = "View1 Movable { FALSE }") private final BooleanInput movable; @Keyword(description = "A Boolean indicating whether the view is locked to a downward view (the 2D default).", example = "View1 Lock2D { FALSE }") private final BooleanInput lock2D; @Keyword(description = "The (optional) entity for this view to follow. Setting this input makes the view ignore ViewCenter " + "and interprets ViewPosition as a relative offset to this entity.", example = "View1 FollowEntity { Ship1 }") private final EntityInput<DisplayEntity> followEntityInput; @Keyword(description = "The (optional) scripted curve for the view position to follow.", example = "View1 ScriptedViewPosition { { { 0 h } { 0 0 0 m } } { { 100 h } { 100 0 0 m } } }") private final KeyedVec3dInput positionScriptInput; @Keyword(description = "The (optional) scripted curve for the view center to follow.", example = "View1 ScriptedViewCenter { { { 0 h } { 0 0 0 m } } { { 100 h } { 100 0 0 m } } }") private final KeyedVec3dInput centerScriptInput; @Keyword(description = "The image file to use as the background for this view.", example = "View1 SkyboxImage { '/resources/images/sky_map_2048x1024.jpg' }") private final FileInput skyboxImage; private final Object setLock = new Object(); private double cachedSimTime = 0; static { allInstances = new ArrayList<>(); } { region = new EntityInput<>(Region.class, "Region", "Graphics", null); this.addInput(region); center = new Vec3dInput("ViewCenter", "Graphics", new Vec3d()); center.setUnitType(DistanceUnit.class); center.setPromptReqd(false); this.addInput(center); position = new Vec3dInput("ViewPosition", "Graphics", new Vec3d(5.0d, -5.0d, 5.0d)); position.setUnitType(DistanceUnit.class); position.setPromptReqd(false); this.addInput(position); IntegerVector defSize = new IntegerVector(2); defSize.add(GUIFrame.VIEW_WIDTH); defSize.add(GUIFrame.VIEW_HEIGHT); windowSize = new IntegerListInput("WindowSize", "Graphics", defSize); windowSize.setValidCount(2); windowSize.setValidRange(1, 8192); windowSize.setPromptReqd(false); this.addInput(windowSize); IntegerVector defPos = new IntegerVector(2); defPos.add(GUIFrame.COL2_START); defPos.add(GUIFrame.TOP_START); windowPos = new IntegerListInput("WindowPosition", "Graphics", defPos); windowPos.setValidCount(2); windowPos.setValidRange(-8192, 8192); windowPos.setPromptReqd(false); this.addInput(windowPos); titleBar = new StringInput("TitleBarText", "Graphics", null); this.addInput(titleBar); showWindow = new BooleanInput("ShowWindow", "Graphics", false); showWindow.setPromptReqd(false); this.addInput(showWindow); movable = new BooleanInput("Movable", "Graphics", true); this.addInput(movable); lock2D = new BooleanInput("Lock2D", "Graphics", false); this.addInput(lock2D); followEntityInput = new EntityInput<>(DisplayEntity.class, "FollowEntity", "Graphics", null); this.addInput(followEntityInput); positionScriptInput = new KeyedVec3dInput("ScriptedViewPosition", "Graphics"); positionScriptInput.setUnitType(DistanceUnit.class); this.addInput(positionScriptInput); centerScriptInput = new KeyedVec3dInput("ScriptedViewCenter", "Graphics"); centerScriptInput.setUnitType(DistanceUnit.class); this.addInput(centerScriptInput); skyboxImage = new FileInput("SkyboxImage", "Graphics", null); this.addInput(skyboxImage); } public View() { allInstances.add(this); viewID = nextID++; } public static ArrayList<View> getAll() { return allInstances; } @Override public void kill() { super.kill(); allInstances.remove(this); } private static class WindowSizePosUpdater implements Runnable { private final IntegerVector pos; private final IntegerVector size; private final Frame window; public WindowSizePosUpdater(Frame w, IntegerVector p, IntegerVector s) { window = w; pos = p; size = s; } @Override public void run() { if (pos != null) window.setLocation(pos.get(0), pos.get(1)); if (size != null) window.setSize(size.get(0), size.get(1)); } void doUpdate() { if (EventQueue.isDispatchThread()) { this.run(); return; } try { EventQueue.invokeAndWait(this); } catch (InvocationTargetException | InterruptedException e) {} //ignore } } @Override public void updateForInput( Input<?> in ) { super.updateForInput( in ); if (in == windowPos) { final Frame window = RenderManager.getOpenWindowForView(this); if (window != null) new WindowSizePosUpdater(window, windowPos.getValue(), null).doUpdate(); return; } if (in == windowSize) { final Frame window = RenderManager.getOpenWindowForView(this); if (window != null) new WindowSizePosUpdater(window, null, windowSize.getValue()).doUpdate(); return; } if (in == lock2D) { GUIFrame.updateUI(); } } public Vec3d getGlobalPosition() { synchronized (setLock) { // Check if this is following a script if (positionScriptInput.hasKeys()) { return positionScriptInput.getValueForTime(cachedSimTime); } // Is this view following an entity? DisplayEntity follow = followEntityInput.getValue(); if (follow != null) { Vec3d ret = follow.getGlobalPosition(); ret.add3(position.getValue()); return ret; } Vec3d tmp = position.getValue(); Vec4d ret = new Vec4d(tmp.x, tmp.y, tmp.z, 1.0d); if (region.getValue() != null) { Transform regTrans = region.getValue().getRegionTrans(); regTrans.apply(ret, ret); } return ret; } } public Vec3d getGlobalCenter() { synchronized (setLock) { // Check if this is following a script if (centerScriptInput.hasKeys()) { return centerScriptInput.getValueForTime(cachedSimTime); } DisplayEntity follow = followEntityInput.getValue(); if (follow != null) { return follow.getGlobalPosition(); } Vec3d tmp = center.getValue(); Vec4d ret = new Vec4d(tmp.x, tmp.y, tmp.z, 1.0d); if (region.getValue() != null) { Transform regTrans = region.getValue().getRegionTrans(); regTrans.apply(ret, ret); } return ret; } } /** * updateCenterAndPos is used only by the mouse interaction code. It takes the camera view center and camera position in global * coordinates and sets the corresponding inputs (in region coordinates). * @param center - view center in world coordinates * @param pos - camera position in world coordinates */ public void updateCenterAndPos(Vec3d center, Vec3d pos) { synchronized (setLock){ if (isScripted()) return; Vec3d tempPos = new Vec3d(pos); Vec3d tempCent = new Vec3d(center); if (region.getValue() != null) { Transform regTrans = region.getValue().getRegionTrans(); regTrans.inverse(regTrans); regTrans.multAndTrans(pos, tempPos); regTrans.multAndTrans(center, tempCent); } // If this is following an entity, subtract that entity's position from the camera position (as it is interpreted as relative) if (isFollowing()) { tempPos.sub3(followEntityInput.getValue().getGlobalPosition(), tempPos); } KeywordIndex kw = InputAgent.formatPointInputs(this.position.getKeyword(), tempPos, "m"); InputAgent.apply(this, kw); kw = InputAgent.formatPointInputs(this.center.getKeyword(), tempCent, "m"); InputAgent.apply(this, kw); } } public String getTitle() { if (titleBar.getValue() != null) return titleBar.getValue(); else return this.getName(); } public boolean showWindow() { return showWindow.getValue(); } public Region getRegion() { return region.getValue(); } public void setRegion(Region reg) { InputAgent.applyArgs(this, region.getKeyword(), reg.getName()); } public void setPosition(Vec3d pos) { KeywordIndex kw = InputAgent.formatPointInputs(position.getKeyword(), pos, "m"); InputAgent.apply(this, kw); } public void setCenter(Vec3d cent) { KeywordIndex kw = InputAgent.formatPointInputs(center.getKeyword(), cent, "m"); InputAgent.apply(this, kw); } public void setWindowPos(int x, int y, int width, int height) { ArrayList<String> tokens = new ArrayList<>(2); IntegerVector pos = windowPos.getValue(); if (pos.get(0) != x || pos.get(1) != y) { tokens.add(String.format((Locale)null, "%d", x)); tokens.add(String.format((Locale)null, "%d", y)); KeywordIndex kw = new KeywordIndex(this.windowPos.getKeyword(), tokens, null); InputAgent.apply(this, kw); tokens.clear(); } IntegerVector size = windowSize.getValue(); if (size.get(0) != width || size.get(1) != height) { tokens.add(String.format((Locale)null, "%d", width)); tokens.add(String.format((Locale)null, "%d", height)); KeywordIndex kw = new KeywordIndex(this.windowSize.getKeyword(), tokens, null); InputAgent.apply(this, kw); } } public IntegerVector getWindowPos() { return windowPos.getValue(); } public IntegerVector getWindowSize() { return windowSize.getValue(); } public void setKeepWindowOpen(boolean b) { keepWindowOpen = b; } public boolean getKeepWindowOpen() { return keepWindowOpen; } public int getID() { return viewID; } public boolean isMovable() { return movable.getValue(); } public boolean isFollowing() { return followEntityInput.getValue() != null; } public boolean isScripted() { return positionScriptInput.hasKeys() || centerScriptInput.hasKeys(); } public void setLock2D(boolean bLock2D) { synchronized (setLock) { ArrayList<String> toks = new ArrayList<>(); toks.add(bLock2D ? "TRUE" : "FALSE"); KeywordIndex kw = new KeywordIndex(lock2D.getKeyword(), toks, null); InputAgent.apply(this, kw); } } public boolean is2DLocked() { return lock2D.getValue(); } public URI getSkyboxTexture() { URI file = skyboxImage.getValue(); if (file == null || file.toString().equals("")) { return null; } return file; } public void update(double simTime) { cachedSimTime = simTime; } }