From 6fd1c661ffbfe96c6ec91647c7831b02bb42dd0e Mon Sep 17 00:00:00 2001 From: Simon32220 <Simon.Schnitzler@gmx.de> Date: Wed, 4 Sep 2024 20:21:28 -0700 Subject: [PATCH] Revert "Revert "Assignment3"" This reverts commit e0a06df8bb8f05a93a18f3fa812f5062aa3c9755. --- .../homework3/Schnitzler/README.md | 44 ++ .../SchnitzlerSimulationProgram.java | 357 ++++++++++++++++ .../Schnitzler/SimpleMoverOpenDis7.java | 403 ++++++++++++++++++ .../SimplePathMoverManagerOpenDis7.java | 175 ++++++++ .../homework3/Schnitzler/package-info.java | 10 + 5 files changed, 989 insertions(+) create mode 100644 assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/README.md create mode 100644 assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SchnitzlerSimulationProgram.java create mode 100644 assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimpleMoverOpenDis7.java create mode 100644 assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimplePathMoverManagerOpenDis7.java create mode 100644 assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/package-info.java diff --git a/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/README.md b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/README.md new file mode 100644 index 0000000000..7d4497eba4 --- /dev/null +++ b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/README.md @@ -0,0 +1,44 @@ +## Homework 3: Example Simulation Recording using OpenDIS Network Streams + +<!-- Viewable at https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/assignments/src/MV3500Cohort2024JulySeptember/homework3/README.md --> + +### Description + +Using the [OpenDIS ExampleSimulationProgram](../../../../../examples/src/OpenDis7Examples/ExampleSimulationProgram.java) and implement a simple DES. +In the DES a SimpleMover and a SimplePathMoverManager are used. The SimpleMover is moving along a given path. +The SimpleMover is modified in this way, that every time if the movement is changed an ESPDU is sent via DisChannel, but at least every 5 seconds with the current position. + +### Assignment + +1. Adapt the functionality for [OpenDIS ExampleSimulationProgram](../../../../examples/src/OpenDis7Examples/ExampleSimulationProgram.java), modifying provided code +2. Experiment with the enumeration values that set up each entity and PDU. What works for you? What makes sense for your future work? +3. Adapt or replace the UML diagrams to describe what you have going on. +4. Record, save and replay your result stream using [PduRecorder](https://savage.nps.edu/opendis7-java/javadoc/edu/nps/moves/dis7/utilities/stream/PduRecorder.html) or [Wireshark](https://www.wireshark.org) + * see local [assignments/src/pduLog](../../../pduLog) subdirectory for latest opendis log files + * Coming soon, we will also (again have) [X3D-Edit](https://savage.nps.edu/X3D-Edit) for DIS stream recording/replay +5. Observe good-practice conventions in the [assignments README](../../../README.md) and [current-course README](../README.md) instructions. + +This assignment presents a Problem Prototyping opportunity. +While some minimal functionality is expected, the general outline of +a networking problem and proposed solution holds great interest. +Think of it as warmup preparation for your future work. + +This is also a freeplay opportunity. +You have the option to pick one or more of the provided course example programs +and adapt the source to demonstrate a new client-server handshake protocol of interest. + +Be sure to provide a rationale that justifies why the networking choices you made +(TCP/UDP, unicast/multicast, etc.) are the best for the problem you are addressing. + +You may find that the prior [homework2 README](../homework2/README.md) still provides +helpful details on what specific deliverables are expected in each homework assignment. + +Team efforts are encouraged, though if you choose a team approach be sure to justify why. +This is a good warmup prior to final projects. Have fun with Java networking! + +### Prior Assignment, August 2019 + +In 2019, students worked together on a single project to check wireless multicast connectivity recently deployed on NPS campus. + +See their experimental results in the [NPS Multicast Connectivity Report](../../MV3500Cohort2019JulySeptember/homework3). + diff --git a/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SchnitzlerSimulationProgram.java b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SchnitzlerSimulationProgram.java new file mode 100644 index 0000000000..cfb786ec30 --- /dev/null +++ b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SchnitzlerSimulationProgram.java @@ -0,0 +1,357 @@ +/** + * Copyright (c) 2008-2023, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved. + * This work is provided under a BSD open-source license, see project license.html or license.txt + * @author brutzman@nps.edu for the original dis part + * @author simonschnitzler for the simulation part + */ +package MV3500Cohort2024JulySeptember.homework3.Schnitzler; + +import edu.nps.moves.dis7.pdus.*; +import edu.nps.moves.dis7.utilities.DisChannel; +import edu.nps.moves.dis7.utilities.PduFactory; +import java.awt.geom.Point2D; +import java.time.LocalDateTime; +import java.util.logging.Level; +import java.util.logging.Logger; +import simkit.Schedule; +import simkit.util.SimplePropertyDumper; + +/** The purpose of this inheritable class is to provide an easily modifiable + * example simulation program that includes DIS-capable entities performing + * tasks of interest, and then reporting activity via PDUs to the network. + * Default program initialization includes PDU recording turned on by default. + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramLog.txt" target="_blank">ExampleSimulationProgramLog.txt</a> + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramPduCaptureLog.dislog" target="_blank">ExampleSimulationProgramPduCaptureLog.dislog</a> + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramFlowDiagram.pdf" target="_blank">ExampleSimulationProgramFlowDiagram.pdf</a> + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramWireshark.png" target="_blank">ExampleSimulationProgramWireshark.png</a> + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/blob/master/examples/src/OpenDis7Examples/ExampleSimulationProgramSequenceDiagram.png" target="_blank">ExampleSimulationProgramSequenceDiagram.png</a> + */ +public class SchnitzlerSimulationProgram +{ + /* **************************** infrastructure code, modification is seldom needed ************************* */ + + private String descriptor = this.getClass().getSimpleName(); + /** DIS channel defined by network address/port combination includes multiple utility capabilities */ + protected DisChannel disChannel; + /** Factory object used to create new PDU instances */ + protected PduFactory pduFactory; + + /** seconds per loop for real-time or simulation execution */ + private double simulationTimeStepDuration = 1.0; // seconds TODO encapsulate + /** initial simulation time in seconds */ + double simulationTimeInitial = 0.0; + /** current simulation time in seconds */ + double simulationTimeSeconds = simulationTimeInitial; + /** Maximum number of simulation loops */ + int MAX_LOOP_COUNT = 20; + + String narrativeMessage1 = new String(); + String narrativeMessage2 = new String(); + String narrativeMessage3 = new String(); + + /** EntityID settings for entity 1 */ + protected EntityID entityID_1 = new EntityID(); + /** EntityID settings for entity 2 */ + protected EntityID entityID_2 = new EntityID(); + /** ESPDU for entity 1 */ + protected EntityStatePdu entityStatePdu_1; + /** ESPDU for entity 2 */ + protected EntityStatePdu entityStatePdu_2; + /** FirePdu for entity 1 first weapon (if any) */ + protected FirePdu firePdu_1a; + /** FirePdu for entity 1 second weapon (if any) */ + protected FirePdu firePdu_1b; + /** MunitionDescriptor for these weapons */ + protected MunitionDescriptor munitionDescriptor1; + + + // hey programmer, what other state do you want? this is a good place to declare it... + + /** + * Constructor to create an instance of this class. + * Design goal: additional built-in initialization conveniences can go here + * to keep your efforts focused on the runSimulation() method. + */ + // base constructor is not invoked automatically by other constructors + // https://stackoverflow.com/questions/581873/best-way-to-handle-multiple-constructors-in-java + public SchnitzlerSimulationProgram() + { + initialize(); + } + /** + * Constructor to create an instance of this class. + * @param newDescriptor describes this program, useful for logging and debugging + */ + public SchnitzlerSimulationProgram(String newDescriptor) + { + descriptor = newDescriptor; + initialize(); + } + /** + * Utility Constructor that allows your example simulation program to override default network address and port + * @param address network address to use + * @param port corresponding network port to use + */ + public SchnitzlerSimulationProgram(String address, int port) + { + disChannel.setNetworkAddress (address); + disChannel.setNetworkPort (port); + disChannel.setVerboseComments (true); // TODO rename library method to disambiguate CommentPDU + // TODO still seems really chatty... add silent mode? + disChannel.setVerboseDisNetworkInterface(true); // Default false + disChannel.setVerbosePduRecorder (true); // default false + initialize(); + } + + /** Initialize channel setup for OpenDis7 and report a test PDU + * @see initializeDisChannel + * @see initializeSimulationEntities */ + private void initialize() + { + initializeDisChannel(); // must come first, uses PduFactory + + //initializeSimulationEntities(); // set unchanging parameters + + disChannel.join(); // TODO further functionality expected + + String timeStepMessage = "Simulation timestep duration " + getSimulationTimeStepDuration() + " seconds"; + disChannel.sendCommentPdu(simulationTimeSeconds, DisChannel.COMMENTPDU_SIMULATION_TIMESTEP, timeStepMessage); + // additional constructor initialization can go here + } + + /** Initialize channel setup for OpenDis7 and report a test PDU */ + private void initializeDisChannel() + { + if (disChannel == null) + disChannel = new DisChannel(); + else + { + disChannel.printlnTRACE ("*** warning, duplicate invocation of initializeDisChannel() ignored"); + return; + } + pduFactory = disChannel.getPduFactory(); + disChannel.setDescriptor(this.getClass().getSimpleName()); // ExampleSimulationProgram might be a superclass + disChannel.setUpNetworkInterface(); + disChannel.printlnTRACE ("just checking: disChannel.getNetworkAddress()=" + disChannel.getNetworkAddress() + + ", getNetworkPort()=" + disChannel.getNetworkPort()); + disChannel.getDisNetworkInterface().setVerbose(true); // sending and receipt + disChannel.printlnTRACE ("just checking: hasVerboseSending()=" + disChannel.getDisNetworkInterface().hasVerboseSending() + + ", hasVerboseReceipt()=" + disChannel.getDisNetworkInterface().hasVerboseReceipt()); + disChannel.getPduRecorder().setVerbose(true); + + // TODO confirm whether recorder is explicitly started by programmer (or not) + +// disChannel.sendCommentPdu(VariableRecordType.OTHER, "DisThreadedNetworkInterface.initializeDisChannel() complete"); // hello channel, debug + } + + /** + * Set up the DES. A single SimpleMoverOpenDis7 starting at (0.0,0.0) + * with a maximum speed of 30.0 and the following + * waypoints:<br>(20.0,100.0),<br>(80.0,10.0),<br>(140.0,150.0) + */ + private void initializeSimulation(){ + Point2D startingPoint = new Point2D.Double(0.0,0.0); + double maxSpeed = 30.0; + SimpleMoverOpenDis7 simpleMover = new SimpleMoverOpenDis7(startingPoint, maxSpeed, disChannel); + simpleMover.setName("Fred"); + + Point2D[] path = new Point2D[]{new Point2D.Double(20.0,100.0), + new Point2D.Double(80.0,10.0),new Point2D.Double(140.0,150.0)}; + + SimplePathMoverManagerOpenDis7 simplePathMoverManager = new SimplePathMoverManagerOpenDis7(path,true); + + simplePathMoverManager.addSimEventListener(simpleMover); + simpleMover.addSimEventListener(simplePathMoverManager); + + SimplePropertyDumper simplePropertyDumper = new SimplePropertyDumper(); + simplePathMoverManager.addPropertyChangeListener(simplePropertyDumper); + simpleMover.addPropertyChangeListener(simplePropertyDumper); + simplePropertyDumper.setDumpSource(true); + + System.out.println(simpleMover); + System.out.println(simplePathMoverManager); + + Schedule.setVerbose(true); + Schedule.reset(); + Schedule.setPauseAfterEachEvent(true); + + } + + /** + * This runSimulationLoops() method is for you, a customizable programmer-modifiable + * code block for defining and running a new simulation of interest. + * + * Welcome! Other parts of this program handle bookkeeping and plumbing tasks so that + * you can focus on your model entities and activities. + * Expandable support includes DIS EntityStatePdu, FirePdu and CommentPdu all available for + * modification and sending in a simulation loop. + * Continuous improvement efforts seek to make this program as easy and straightforward + * as possible for DIS simulationists to use and adapt. + * All of the other methods are setup, teardown and configuration that you may find + * interesting, even helpful, but don't really have to worry about. + */ + @SuppressWarnings("SleepWhileInLoop") // yes we might do that + public void runSimulationLoops () + { + try + { + int simulationLoopCount = 0; + boolean simulationComplete = false; // sentinel variable as termination condition, are we done yet? + + // TODO reset Clock Time for today's date and timestamp to zero, providing consistent outputs for each simulation run + String timeMessage = "Simulation time " + simulationTimeSeconds + " at LocalDateTime " + LocalDateTime.now(); + disChannel.sendCommentPdu(simulationTimeSeconds, DisChannel.COMMENTPDU_TIME, timeMessage); + // TODO replace enumeration with disChannel.COMMENTPDU_TIME + // TODO fix VariableRecordType.TIME_AMP_DATE_VALID + + // =================================================================================================== + // loop the simulation while allowed, programmer can set additional conditions to break out and finish + + initializeSimulation(); + + while (simulationLoopCount < MAX_LOOP_COUNT) // are we done yet? + { + simulationLoopCount++; + // good practice: increment loop counter as first action in that loop + + + // ============================================================================================= + // * your own simulation code starts here! ***************************************************** + // ============================================================================================= + + double simTime = Schedule.getSimTime(); + while(simTime < simulationLoopCount * getSimulationTimeStepDuration()){ + Schedule.startSimulation(); + simTime = Schedule.getSimTime(); + } + + + // make your reports: narrative code for CommentPdu here (set all to empty strings to avoid sending) + narrativeMessage1 = "MV3500 ExampleSimulationProgram"; + narrativeMessage2 = "runSimulation() loop " + simulationLoopCount; + narrativeMessage3 = ""; // intentionally blank for testing + + // your loop termination condition goes here + if (simulationLoopCount >= MAX_LOOP_COUNT) // for example + { + simulationComplete = true; + } + // ============================================================================================= + // * your own simulation code is finished here! ************************************************ + // ============================================================================================= + + // staying synchronized with timestep: wait duration for elapsed time in this loop + // Thread.sleep needs a (long) parameter for milliseconds, which are clumsy to use sometimes + Thread.sleep((long)(getSimulationTimeStepDuration() * 1000)); // units of seconds * (1000 msec/sec) = milliseconds + System.out.println ("... [Pausing for " + getSimulationTimeStepDuration() + " seconds]"); + + + // =============================== + // current loop now finished, check whether to terminate if simulation complete, otherwise continue + if (simulationComplete || (simulationLoopCount > 10000)) // for example; including fail-safe condition is good + { + System.out.println ("... [loop termination condition met, simulationComplete=" + simulationComplete + "]"); // ", final loopCount=" + loopCount + + System.out.flush(); + break; + } + simulationTimeSeconds += getSimulationTimeStepDuration(); // good practice: increment simulationTime as lastst action in that loop + + } // end of simulation loop, continue until done + // ===================================================================================================// ===================================================================================================// ===================================================================================================// =================================================================================================== + + narrativeMessage2 = "runSimulation() completed successfully"; // all done, so tell everyone else on the channel + // TODO better javadoc needs to be autogenerated for VariableRecordType enumerations + disChannel.sendCommentPdu(DisChannel.COMMENTPDU_NARRATIVE, narrativeMessage1, narrativeMessage2, narrativeMessage3); + System.out.println ("... [final=completion CommentPdu successfully sent for simulation]"); + +// disChannel.getPduRecorder(). TODO record XML as well + disChannel.leave(); // embedded SimulationManager is expected to send appropriate PDUs for entity, application shutdown + } + catch (InterruptedException iex) // handle any exception that your code might choose to provoke! + { + Logger.getLogger(SchnitzlerSimulationProgram.class.getSimpleName()).log(Level.SEVERE, null, iex); + } + + } + + /** + * Initial execution via main() method: handle args array of command-line initialization (CLI) arguments here + * @param args command-line parameters: network address and port + */ + protected void handleArguments (String[] args) + { + // initial execution: handle args array of initialization arguments here + if (args.length == 2) + { + if ((args[0] != null) && !args[0].isEmpty()) + thisProgram.disChannel.setNetworkAddress(args[0]); + if ((args[1] != null) && !args[1].isEmpty()) + thisProgram.disChannel.setNetworkPort(Integer.parseInt(args[1])); + } + else if (args.length != 0) + { + System.err.println("Usage: " + thisProgram.getClass().getSimpleName() + " [address port]"); + System.exit(-1); + } + } + + /** + * Get simple descriptor (such as parent class name) for this network interface, used in trace statements + * @return simple descriptor name + */ + public String getDescriptor() { + return descriptor; + } + + /** + * Set new simple descriptor (such as parent class name) for this network interface, used in trace statements + * @param newDescriptor simple descriptor name for this interface + */ + public void setDescriptor(String newDescriptor) { + if (newDescriptor == null) + newDescriptor = ""; + this.descriptor = newDescriptor; + } + + /** + * parameter accessor method + * @return the simulationTimeStepDuration in seconds + */ + public double getSimulationTimeStepDuration() { + return simulationTimeStepDuration; + } + + /** + * parameter accessor method + * @param timeStepDurationSeconds the simulationTimeStepDuration in seconds to set + */ + public void setSimulationTimeStepDuration(double timeStepDurationSeconds) { + this.simulationTimeStepDuration = timeStepDurationSeconds; + } + + /** Locally instantiable copy of program, can be subclassed. */ + protected static SchnitzlerSimulationProgram thisProgram; + + /** + * Main method is first executed when a program instance is loaded. + * @see <a href="https://docs.oracle.com/javase/tutorial/getStarted/application/index.html" target="_blank">Java Tutorials: A Closer Look at the "Hello World!" Application</a> + * @param args command-line parameters: network address and port. + * Command-line arguments are an array of optional String parameters that are passed from execution environment during invocation + */ + public static void main(String[] args) + { + thisProgram = new SchnitzlerSimulationProgram("test constructor"); // create instance of self within static main() method + + thisProgram.disChannel.printlnTRACE("main() started..."); + + thisProgram.handleArguments(args); // process any command-line invocation arguments + + thisProgram.runSimulationLoops(); // ... your simulation execution code goes in there ... + + thisProgram.disChannel.tearDownNetworkInterface(); // make sure no processes are left lingering + + thisProgram.disChannel.printlnTRACE("complete."); // report successful completion + + System.exit(0); // ensure all threads and sockets released + } +} diff --git a/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimpleMoverOpenDis7.java b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimpleMoverOpenDis7.java new file mode 100644 index 0000000000..7b5a9f439a --- /dev/null +++ b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimpleMoverOpenDis7.java @@ -0,0 +1,403 @@ +package MV3500Cohort2024JulySeptember.homework3.Schnitzler; + +import edu.nps.moves.dis7.entities.deu.platform.land.Leopard2A7PLUS; +import edu.nps.moves.dis7.enumerations.ForceID; +import edu.nps.moves.dis7.pdus.EntityID; +import edu.nps.moves.dis7.pdus.EntityStatePdu; +import edu.nps.moves.dis7.pdus.Vector3Float; +import edu.nps.moves.dis7.utilities.DisChannel; +import edu.nps.moves.dis7.utilities.PduFactory; +import java.awt.geom.Point2D; +import static java.lang.Double.NaN; +import simkit.Schedule; +import simkit.SimEntityBase; + +/** + * Represent a unit that moves with constant velocity. Its responsibilities are + * to maintain its state and respond to events directing it to perform only + * maneuver, specifically to move to a given waypoint at its maximum speed. + * + * @author simonschnitzler + */ +public class SimpleMoverOpenDis7 extends SimEntityBase { + + /** max time between two ESPDUs */ + public static final double HEARTBEATTIME = 5.0; + + /** 2D Vector with NaN as values */ + public static final Point2D NaP = new Point2D.Double(NaN, NaN); + + private Point2D initialLocation; + private double maxSpeed; + + /** Dis extension */ + private DisChannel disChannel; + + /** last stop location of the simpleMover */ + protected Point2D lastStopLocation; + /** current velocity of the mover */ + protected Point2D velocity; + /** current destination of the simpleMover */ + protected Point2D destination; + /** start move time of the simpleMover */ + protected double startMoveTime; + + /** EntityID settings for simpleMover */ + private EntityID entityID = new EntityID(); + + /** ESPDU for simpleMover */ + private EntityStatePdu entityStatePdu; + + /** PduFactory */ + private PduFactory pduFactory; + + + + + /** + * Instantiate lastStopLocation, velocity, and destination as NaP each + */ + public SimpleMoverOpenDis7() { + lastStopLocation = (Point2D) NaP.clone(); + velocity = (Point2D) NaP.clone(); + destination = (Point2D) NaP.clone(); + } + + /** + * Instantiate a new SimpleMover with the given initialLocation and + * maxSpeed. + * + * @param initialLocation Given initial location of this mover + * @param maxSpeed Given maximum speed of mover + */ + public SimpleMoverOpenDis7(Point2D initialLocation, double maxSpeed) { + this(); + setInitialLocation(initialLocation); + setMaxSpeed(maxSpeed); + } + + /** + * Instantiate a new SimpleMover with the given initialLocation and + * maxSpeed. + * + * @param initialLocation Given initial location of this mover + * @param maxSpeed Given maximum speed of mover + * @param disChannel Given DisChannel to use for send ESPDU + */ + public SimpleMoverOpenDis7(Point2D initialLocation, double maxSpeed, DisChannel disChannel) { + this(initialLocation, maxSpeed); + setDisChannel(disChannel); + initializeEntity(); + } + + /** + * Initialize simulation entity for the simpleMover. + */ + private void initializeEntity(){ + if (pduFactory == null) + pduFactory = disChannel.getPduFactory(); + entityStatePdu = pduFactory.makeEntityStatePdu(); + + // Your model setup: define participants. who's who in this zoo? + // Assuming you keep track of entity objects... here is some support for for Entity 1. + + // PDU objects are already declared and instances created, so now set their values. + // who is who in our big zoo, sufficient for global participation if we need it + entityID.setSiteID(1).setApplicationID(2).setEntityID(3);// made-up example ID; + disChannel.addEntity(entityID); + + + entityStatePdu.setEntityID(entityID); + entityStatePdu.setForceId(ForceID.FRIENDLY); + entityStatePdu.setEntityType(new Leopard2A7PLUS()); + entityStatePdu.setMarking("Mover #1"); + entityStatePdu.getMarkingString(); // use Netbeans Debug breakpoint here to check left justified... + } + + private void sendESPDU(){ + Point2D currentLocation = getCurrentLocation(); + entityStatePdu.getEntityLocation().setX(currentLocation.getX()); + entityStatePdu.getEntityLocation().setY(currentLocation.getY()); + entityStatePdu.getEntityLocation().setZ(0.0); + Point2D currentVelocity = getVelocity(); + Vector3Float currentVelocityFloat = new Vector3Float(); + currentVelocityFloat.setX((float)currentVelocity.getX()); + currentVelocityFloat.setY((float)currentVelocity.getY()); + currentVelocityFloat.setZ(0.0f); + entityStatePdu.setEntityLinearVelocity(currentVelocityFloat); + entityStatePdu.setTimestampSeconds(Schedule.getSimTime()); + + disChannel.sendSinglePdu(entityStatePdu); + } + + /** + * Set lastStopLocation to initialLocation<br> + * set velocity to (0,0)<br> + * set destination to NaP<br> + * set startMoveTime to the current SimTime + */ + @Override + public void reset() { + super.reset(); + lastStopLocation.setLocation(getInitialLocation()); + velocity.setLocation(0.0, 0.0); + destination.setLocation(NaP); + startMoveTime = Schedule.getSimTime(); + } + + /** + * Fire property changes for all state variables with the initial values. + */ + public void doRun() { + firePropertyChange("lastStopLocation", getLastStopLocation()); + firePropertyChange("velocity", getVelocity()); + firePropertyChange("destination", getDestination()); + firePropertyChange("startMoveTime", getStartMoveTime()); + + sendESPDU(); + waitDelay("HeartBeat", HEARTBEATTIME, this); + } + + /** + * If no changes happen, a heartbeat is send. + * + * @param mover which is meant to send a heartbeat + */ + public void doHeartBeat(SimpleMoverOpenDis7 mover){ + sendESPDU(); + + waitDelay("HeartBeat", HEARTBEATTIME, mover); + } + + /** + * set destination to the given destination and firePropertyChange<br> + * schedule StartMove(this) if the distance to the destination is greater + * than 0.0 + * + * @param destination the new destination for the mover + */ + public void doMoveTo(Point2D destination) { + Point2D oldDestination = getDestination(); + this.destination.setLocation(destination); + firePropertyChange("destination", oldDestination, getDestination()); + + if (lastStopLocation.distance(destination) > 0.0) { + waitDelay("StartMove", 0.0, this); + } + } + + /** + * Compute the distance to the destination <br> + * compute the moveTime to destination <br> + * compute velocity with maxSpeed and direction to the destination <br> + * set startMoveTime to the current SimTime <br> + * Schedule an EndMove event with the given mover as parameter and the delay + * of the computed moveTime + * + * @param mover instance which starts its move + */ + public void doStartMove(SimpleMoverOpenDis7 mover) { + double distance = lastStopLocation.distance(destination); + double moveTime = distance / getMaxSpeed(); + + Point2D oldVelocity = getVelocity(); + this.velocity.setLocation((destination.getX() - lastStopLocation.getX()) / moveTime, + (destination.getY() - lastStopLocation.getY()) / moveTime); + firePropertyChange("velocity", oldVelocity, getVelocity()); + + double oldStartMoveTime = getStartMoveTime(); + startMoveTime = Schedule.getSimTime(); + firePropertyChange("startMoveTime", oldStartMoveTime, getStartMoveTime()); + + waitDelay("EndMove", moveTime, mover); + + interrupt("HeartBeat", mover); + sendESPDU(); + waitDelay("HeartBeat", HEARTBEATTIME, mover); + + } + + /** + * Set lastStopLocation to the destination<br> + * set velocity to 0.0<br> + * set destination to NaP + * + * @param mover instance which ends its move + */ + public void doEndMove(SimpleMoverOpenDis7 mover) { + Point2D oldLastStopLocation = getLastStopLocation(); + lastStopLocation.setLocation(getDestination()); + firePropertyChange("lastStopLocation", oldLastStopLocation, getLastStopLocation()); + + Point2D oldVelocity = getVelocity(); + velocity.setLocation(0.0, 0.0); + firePropertyChange("velocity", oldVelocity, getVelocity()); + + Point2D oldDestination = getDestination(); + destination.setLocation(NaP); + firePropertyChange("destination", oldDestination, getDestination()); + + interrupt("HeartBeat", mover); + sendESPDU(); + waitDelay("HeartBeat", HEARTBEATTIME, mover); + } + + /** + * Schedule an OrderStop event with this mover instance as parameter and a + * delay of 0.0 + */ + public void doOrderStop() { + waitDelay("Stop", 0.0, this); + } + + /** + * Set lastStopLocation to the currentLocation<br> + * set startMoveTime to the current SimTime<br> + * set velocity to 0.0<br> + * cancel the EndMove event for the given mover instance + * + * @param mover instance which stops its move + */ + public void doStop(SimpleMoverOpenDis7 mover){ + Point2D oldLastStopLocation = getLastStopLocation(); + lastStopLocation.setLocation(getCurrentLocation()); + firePropertyChange("lastStopLocation", oldLastStopLocation, getLastStopLocation()); + + double oldStartMoveTime = getStartMoveTime(); + startMoveTime = Schedule.getSimTime(); + firePropertyChange("startMoveTime", oldStartMoveTime, getStartMoveTime()); + + Point2D oldVelocity = getVelocity(); + velocity.setLocation(0.0, 0.0); + firePropertyChange("velocity", oldVelocity, getVelocity()); + + interrupt("EndMove",mover); + + interrupt("HeartBeat", mover); + sendESPDU(); + waitDelay("HeartBeat", HEARTBEATTIME, mover); + } + + /** + * Return current location. + * + * @return this mover's current location based on linear equation of motion + */ + public Point2D getCurrentLocation() { + double elapsedTime = Schedule.getSimTime() - getStartMoveTime(); + return new Point2D.Double(lastStopLocation.getX() + elapsedTime * velocity.getX(), + lastStopLocation.getY() + elapsedTime * velocity.getY()); + } + + /** + * Return initial location. + * @return initialLocation + */ + public Point2D getInitialLocation() { + return (Point2D) initialLocation.clone(); + } + + /** + * Set initial Location + * + * @param initialLocation initial location of the mover. + */ + public void setInitialLocation(Point2D initialLocation) { + this.initialLocation = (Point2D) initialLocation.clone(); + } + + /** + * Get max speed. + * + * @return maxSpeed of the mover. + */ + public double getMaxSpeed() { + return maxSpeed; + } + + /** + * Set max speed + * + * @param maxSpeed of the mover. + * @throws IllegalArgumentException if the speed is negative. + */ + public void setMaxSpeed(double maxSpeed) { + if (maxSpeed < 0.0) { + throw new IllegalArgumentException("maxSpeed must be >= 0.0: " + maxSpeed); + } + this.maxSpeed = maxSpeed; + } + + /** + * Get last stop location. + * + * @return lastStopLocation of the mover. + */ + public Point2D getLastStopLocation() { + return (Point2D) lastStopLocation.clone(); + } + + /** + * Get velocity. + * + * @return velocity of the mover. + */ + public Point2D getVelocity() { + return (Point2D) velocity.clone(); + } + + /** + * Get destination. + * + * @return destination of the mover. + */ + public Point2D getDestination() { + return (Point2D) destination.clone(); + } + + /** + * Get start move time. + * + * @return startMoveTime time since the last StartMove event. + */ + public double getStartMoveTime() { + return startMoveTime; + } + + /** + * Get dis channel. + * + * @return disChannel of the simulation. + */ + public DisChannel getDisChannel() { + return disChannel; + } + + + /** + * Set DisChannel. + * + * @param disChannel of the simulation. + * @throws IllegalArgumentException if the disChannel is null. + */ + public void setDisChannel(DisChannel disChannel) { + if (disChannel == null) { + throw new IllegalArgumentException("disChannel is: " + disChannel); + } + this.disChannel = disChannel; + } + + /** + * Override normal toString() to return current location and velocity + * + * @return Name (current location, current velocity) + */ + @Override + public String toString() { + Point2D currentLocation = getCurrentLocation(); + return String.format("%s (%,.3f, %,.3f) [%,.3f, %,.3f]", getName(), + currentLocation.getX(), currentLocation.getY(), + velocity.getX(), velocity.getY()); + } + +} diff --git a/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimplePathMoverManagerOpenDis7.java b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimplePathMoverManagerOpenDis7.java new file mode 100644 index 0000000000..3860affbdf --- /dev/null +++ b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/SimplePathMoverManagerOpenDis7.java @@ -0,0 +1,175 @@ +package MV3500Cohort2024JulySeptember.homework3.Schnitzler; + +import java.awt.geom.Point2D; +import simkit.SimEntityBase; + +/** + * Represent a move manager. Each instance of SimplePathMoverManager will + * control a single SimpleMover, directing it along a pre-determined list of + * waypoints (Point2D instances). This component has only one state variable + * next. + * + * @author simonschnitzler + */ +public class SimplePathMoverManagerOpenDis7 extends SimEntityBase { + + private Point2D[] path; + private boolean startOnRun; + + /** next waypoint index */ + protected int nextWaypointIndex; + + /** + * Zero-argument constructor + */ + public SimplePathMoverManagerOpenDis7() { + } + + /** + * Instantiate with given parameters + * + * @param path given path + * @param startOnRun if startOnRun is true, start from Run event + */ + public SimplePathMoverManagerOpenDis7(Point2D[] path, boolean startOnRun) { + this(); + setPath(path); + setStartOnRun(startOnRun); + } + + /** + * Set nextWaypointIndex to the initial value 0; + */ + @Override + public void reset() { + super.reset(); + nextWaypointIndex = 0; + } + + /** + * Fire property change for the state variable nextWaypointIndex<br> + * If startOnRun is true schedule a MoveTo event with the waypoint at index + * nextWaypointIndex as parameter and with a delay of 0.0. + */ + public void doRun() { + firePropertyChange("nextWaypointIndex", getNextWaypointIndex()); + + if (isStartOnRun()) { + waitDelay("MoveTo", 0.0, getPath(getNextWaypointIndex())); + } + } + + /** + * Does nothing - meant to be "heard" by another component + * + * @param waypoint which is the next destination + */ + public void doMoveTo(Point2D waypoint) { + } + + /** + * Add 1 to the nextWaypointIndex and fire a property change<br> + * If the new nextWaypointIndex is less than the length of the path, then + * schedule a MoveTo event with a delay of 0.0 and the waypoint at the index + * of the new nextWaypointIndex as parameter.<br> + * If the new nextWaypointIndex is equal to the length of the path, then + * schedule a Stop event with a delay of 0.0 + * + * @param mover which ends its move + */ + public void doEndMove(SimpleMoverOpenDis7 mover) { + int oldNextWaypointIndex = getNextWaypointIndex(); + nextWaypointIndex += 1; + firePropertyChange("nextWaypointIndex", oldNextWaypointIndex, getNextWaypointIndex()); + + if (getNextWaypointIndex() < path.length) { + waitDelay("MoveTo", 0.0, getPath(getNextWaypointIndex())); + } + if (getNextWaypointIndex() == path.length) { + waitDelay("Stop", 0.0); + } + } + + /** + * Schedule an OrderStop event with a delay of 0.0 + */ + public void doStop(){ + waitDelay("OrderStop",0.0); + } + + /** + * Does nothing - meant to be "heard" by another component + */ + public void doOrderStop(){ + } + + /** + * Get path. + * @return the path + */ + public Point2D[] getPath() { + return path.clone(); + } + + /** + * Get waypoint at given index of the path. + * + * @param index of the waypoint + * @return the waypoint of the path at the given index + */ + public Point2D getPath(int index) { + return (Point2D) path[index].clone(); + } + + /** + * Set the path. + * + * @param path the path to set + * @throws IllegalArgumentException if the path length is not greater than 0 + */ + public void setPath(Point2D[] path) { + if (path.length > 0) { + this.path = path.clone(); + } else { + throw new IllegalArgumentException("path must be at least lengt 1: " + path.length); + } + } + + /** + * Set the waypoint of the path at a given index. + * + * @param index of the waypoint to set + * @param path the waypoint of the path to set at the given index + */ + public void setPath(int index, Point2D path) { + this.path[index] = (Point2D) path.clone(); + } + + /** + * Get startOnRun Boolean. + * + * @return startOnRun + */ + public boolean isStartOnRun() { + return startOnRun; + } + + /** + * Set startOnRunBoolean. + * + * @param startOnRun boolean if should start from the beginning. + */ + public void setStartOnRun(boolean startOnRun) { + this.startOnRun = startOnRun; + } + + /** + * Get next waypoint index. + * + * @return nextWaypointIndex + */ + public int getNextWaypointIndex() { + return nextWaypointIndex; + } + +} diff --git a/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/package-info.java b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/package-info.java new file mode 100644 index 0000000000..308438e0b4 --- /dev/null +++ b/assignments/src/MV3500Cohort2024JulySeptember/homework3/Schnitzler/package-info.java @@ -0,0 +1,10 @@ +/** + * Final project assignments supporting the NPS MOVES MV3500 Networked Graphics course. + * + * @see <a href="https://gitlab.nps.edu/Savage/NetworkedGraphicsMV3500/-/tree/master/assignments" target="_blank">networkedGraphicsMV3500 assignments</a> + * @see java.lang.Package + * @see <a href="https://stackoverflow.com/questions/22095487/why-is-package-info-java-useful" target="_blank">StackOverflow: why-is-package-info-java-useful</a> + * @see <a href="https://stackoverflow.com/questions/624422/how-do-i-document-packages-in-java" target="_blank">StackOverflow: how-do-i-document-packages-in-java</a> + */ + +package MV3500Cohort2024JulySeptember.homework3.Schnitzler; -- GitLab