From cd28c30484421ada268267f28c3c568bbd81b769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Cou=C3=A9raud?= Date: Fri, 9 Feb 2024 18:31:48 +0100 Subject: [PATCH] Refactored train drawing logic to be reusable elsewhere. --- src/guisim/GUIVehicle.cpp | 177 +++--------------- src/guisim/GUIVehicle.h | 6 - src/microsim/MSVehicle.cpp | 78 +++++++- src/microsim/MSVehicle.h | 7 +- src/microsim/transportables/CMakeLists.txt | 2 + src/microsim/transportables/MSTrainHelper.cpp | 147 +++++++++++++++ src/microsim/transportables/MSTrainHelper.h | 134 +++++++++++++ 7 files changed, 385 insertions(+), 166 deletions(-) create mode 100644 src/microsim/transportables/MSTrainHelper.cpp create mode 100644 src/microsim/transportables/MSTrainHelper.h diff --git a/src/guisim/GUIVehicle.cpp b/src/guisim/GUIVehicle.cpp index e0b68489d66b..cdcbee8872a2 100644 --- a/src/guisim/GUIVehicle.cpp +++ b/src/guisim/GUIVehicle.cpp @@ -57,9 +57,9 @@ #include #include #include +#include #include #include - #include "GUIVehicle.h" #include "GUIPerson.h" #include "GUIContainer.h" @@ -309,61 +309,23 @@ GUIVehicle::drawAction_drawLinkItems(const GUIVisualizationSettings& s) const { void GUIVehicle::drawAction_drawCarriageClass(const GUIVisualizationSettings& s, bool asImage) const { - const bool s2 = s.secondaryShape; RGBColor current = GLHelper::getColor(); RGBColor darker = current.changedBrightness(-51); const double exaggeration = (s.vehicleSize.getExaggeration(s, this) * s.vehicleScaler.getScheme().getColor(getScaleValue(s, s.vehicleScaler.getActive()))); - const double totalLength = getVType().getLength(); - double upscaleLength = exaggeration; - if (exaggeration > 1 && totalLength > 5) { - // reduce the length/width ratio because this is not useful at high zoom - const double widthLengthFactor = totalLength / 5; - const double shrinkFactor = MIN2(widthLengthFactor, sqrt(upscaleLength)); - upscaleLength /= shrinkFactor; - } - const double locomotiveLength = getVehicleType().getParameter().locomotiveLength * upscaleLength; if (exaggeration == 0) { return; } - const double defaultLength = getVehicleType().getParameter().carriageLength * upscaleLength; - const double carriageGap = getVehicleType().getParameter().carriageGap * upscaleLength; - const double length = totalLength * upscaleLength; - const double halfWidth = getVehicleType().getWidth() / 2.0 * exaggeration; - GLHelper::popMatrix(); // undo initial translation and rotation - const double xCornerCut = 0.3 * exaggeration; - const double yCornerCut = 0.4 * exaggeration; - // round to closest integer - const int numCarriages = MAX2(1, 1 + (int)((length - locomotiveLength) / (defaultLength + carriageGap) + 0.5)); - assert(numCarriages > 0); - double carriageLengthWithGap = length / numCarriages; - double carriageLength = carriageLengthWithGap - carriageGap; - double firstCarriageLength = carriageLength; - if (defaultLength != locomotiveLength && numCarriages > 1) { - firstCarriageLength = locomotiveLength; - carriageLengthWithGap = (length - locomotiveLength) / (numCarriages - 1); - carriageLength = carriageLengthWithGap - carriageGap; - } - const int firstPassengerCarriage = defaultLength == locomotiveLength || numCarriages == 1 + // bool reversed = + MSTrainHelper trainHelper(this, isReversed() && s.drawReversed, s.secondaryShape); + int numCarriages = trainHelper.getNumCarriages(); + const int firstPassengerCarriage = trainHelper.getDefaultLength() == trainHelper.getLocomotiveLength() || numCarriages == 1 || (getVClass() & (SVC_RAIL_ELECTRIC | SVC_RAIL_FAST | SVC_RAIL)) == 0 ? 0 : 1; const int noPersonsBackCarriages = (getVehicleType().getGuiShape() == SUMOVehicleShape::TRUCK_SEMITRAILER || getVehicleType().getGuiShape() == SUMOVehicleShape::TRUCK_1TRAILER) && numCarriages > 1 ? 1 : 0; const int firstContainerCarriage = numCarriages == 1 || getVehicleType().getGuiShape() == SUMOVehicleShape::TRUCK_1TRAILER ? 0 : 1; const int seatsPerCarriage = (int)ceil(getVType().getPersonCapacity() / (numCarriages - firstPassengerCarriage - noPersonsBackCarriages)); const int containersPerCarriage = (int)ceil(getVType().getContainerCapacity() / (numCarriages - firstContainerCarriage)); - // lane on which the carriage front is situated - MSLane* lane = myLane; - int furtherIndex = 0; - // lane on which the carriage back is situated - MSLane* backLane = myLane; - int backFurtherIndex = furtherIndex; - // offsets of front and back - double carriageOffset = myState.pos(); - if (getLaneChangeModel().isOpposite()) { - // @note this still produces some artifacts while not fully on the current lane - carriageOffset = MIN2(carriageOffset + getLength(), lane->getLength()); - } - double carriageBackOffset = carriageOffset - firstCarriageLength; - // handle seats + // Handle seats. int requiredSeats = getNumPassengers(); int requiredPositions = getNumContainers(); if (requiredSeats > 0) { @@ -372,65 +334,24 @@ GUIVehicle::drawAction_drawCarriageClass(const GUIVisualizationSettings& s, bool if (requiredPositions > 0) { myContainerPositions.clear(); } + GLHelper::popMatrix(); // undo initial translation and rotation + const double xCornerCut = 0.3 * exaggeration; + const double yCornerCut = 0.4 * exaggeration; Position front, back; - double angle = 0.; - // position parking vehicle beside the road or track - const double lateralOffset = (isParking() && getNextStopParameter()->posLat == INVALID_DOUBLE - ? (getLane()->getWidth() * (MSGlobals::gLefthand ? -1 : 1)) - : -getLateralPositionOnLane()); - - // draw individual carriages - double curCLength = firstCarriageLength; - int firstCarriageNo = 0; // default case - we're going forwards - const bool reversed = drawReversed(s) || getLaneChangeModel().isOpposite(); - if (reversed) { - firstCarriageNo = numCarriages - 1; - if (numCarriages > 1) { - carriageBackOffset = carriageOffset - carriageLength; - } - } - - //std::cout << SIMTIME << " veh=" << getID() << " curCLength=" << curCLength << " loc=" << locomotiveLength << " car=" << carriageLength << " tlen=" << totalLength << " len=" << length << "\n"; + double angle = 0.0; + double curCLength = trainHelper.getFirstCarriageLength(); + std::vector carriages = trainHelper.getCarriages(); for (int i = 0; i < numCarriages; ++i) { - if (i == firstCarriageNo) { - curCLength = firstCarriageLength; - if (firstCarriageNo > 0) { - // previous loop iteration has adjusted backpos for a normal carriage so have to correct - carriageBackOffset += carriageLengthWithGap; - carriageBackOffset -= firstCarriageLength + carriageGap; - } - } else { - curCLength = carriageLength; - } - while (carriageOffset < 0) { - MSLane* prev = getPreviousLane(lane, furtherIndex); - if (prev != lane) { - carriageOffset += prev->getLength(); - } else { - // no lane available for drawing. - carriageOffset = 0; - } - lane = prev; - } - while (carriageBackOffset < 0) { - MSLane* prev = getPreviousLane(backLane, backFurtherIndex); - if (prev != backLane) { - carriageBackOffset += prev->getLength(); - } else { - // no lane available for drawing. - carriageBackOffset = 0; - } - backLane = prev; - } - front = lane->getShape(s2).positionAtOffset(carriageOffset * lane->getLengthGeometryFactor(s2), lateralOffset); - back = backLane->getShape(s2).positionAtOffset(carriageBackOffset * lane->getLengthGeometryFactor(s2), lateralOffset); + front = carriages[i]->front; + back = carriages[i]->back; if (front == back) { - // no place for drawing available + // No place for drawing available. continue; } const double drawnCarriageLength = front.distanceTo2D(back); angle = atan2((front.x() - back.x()), (back.y() - front.y())) * (double) 180.0 / (double) M_PI; // if we are in reverse 'first' carriages are drawn last so the >= test doesn't work + const bool reversed = trainHelper.isReversed(); if (reversed) { if (i <= numCarriages - firstPassengerCarriage) { computeSeats(back, front, SUMO_const_waitingPersonWidth, seatsPerCarriage, exaggeration, requiredSeats, mySeatPositions); @@ -446,14 +367,16 @@ GUIVehicle::drawAction_drawCarriageClass(const GUIVisualizationSettings& s, bool computeSeats(front, back, SUMO_const_waitingContainerWidth, containersPerCarriage, exaggeration, requiredPositions, myContainerPositions); } } + curCLength = (i == trainHelper.getFirstCarriageNo() ? trainHelper.getFirstCarriageLength() : trainHelper.getCarriageLength()); GLHelper::pushMatrix(); glTranslated(front.x(), front.y(), getType()); glRotated(angle, 0, 0, 1); + double halfWidth = trainHelper.getHalfWidth(); if (!asImage || !GUIBaseVehicleHelper::drawAction_drawVehicleAsImage(s, getVType().getImgFile(), this, getVType().getWidth() * exaggeration, curCLength)) { switch (getVType().getGuiShape()) { case SUMOVehicleShape::TRUCK_SEMITRAILER: case SUMOVehicleShape::TRUCK_1TRAILER: - if (i == firstCarriageNo) { // at the moment amReversed is only ever set for rail - so has no impact in this call + if (i == trainHelper.getFirstCarriageNo()) { // at the moment amReversed is only ever set for rail - so has no impact in this call GUIBaseVehicleHelper::drawAction_drawVehicleAsPoly(s, getVType().getGuiShape(), getVType().getWidth() * exaggeration, curCLength, 0, false, reversed); } else { GLHelper::setColor(current); @@ -461,7 +384,7 @@ GUIVehicle::drawAction_drawCarriageClass(const GUIVisualizationSettings& s, bool } break; default: { - if (i == firstCarriageNo) { + if (i == trainHelper.getFirstCarriageNo()) { GLHelper::setColor(darker); } else { GLHelper::setColor(current); @@ -478,7 +401,7 @@ GUIVehicle::drawAction_drawCarriageClass(const GUIVisualizationSettings& s, bool glVertex2d(halfWidth - xCornerCut, 0); glEnd(); // indicate front of the head of the train - if (i == firstCarriageNo) { + if (i == trainHelper.getFirstCarriageNo()) { glTranslated(0, 0, 0.1); glColor3d(0, 0, 0); glBegin(GL_TRIANGLE_FAN); @@ -500,8 +423,6 @@ GUIVehicle::drawAction_drawCarriageClass(const GUIVisualizationSettings& s, bool } } GLHelper::popMatrix(); - carriageOffset -= (curCLength + carriageGap); - carriageBackOffset -= carriageLengthWithGap; } if (getVType().getGuiShape() == SUMOVehicleShape::RAIL_CAR) { GLHelper::pushMatrix(); @@ -517,7 +438,7 @@ GUIVehicle::drawAction_drawCarriageClass(const GUIVisualizationSettings& s, bool glTranslated(front.x(), front.y(), getType()); const double degAngle = RAD2DEG(getAngle() + M_PI / 2.); glRotated(degAngle, 0, 0, 1); - glScaled(exaggeration, upscaleLength, 1); + glScaled(exaggeration, trainHelper.getUpscaleLength(), 1); if (mySeatPositions.size() == 0) { mySeatPositions.push_back(Seat(back, DEG2RAD(angle))); } @@ -785,60 +706,6 @@ GUIVehicle::drawRouteHelper(const GUIVisualizationSettings& s, ConstMSRoutePtr r } -MSLane* -GUIVehicle::getPreviousLane(MSLane* current, int& furtherIndex) const { - if (furtherIndex < (int)myFurtherLanes.size()) { - return myFurtherLanes[furtherIndex++]; - } else { - // try to use route information - int routeIndex = getRoutePosition(); - bool resultInternal; - if (MSGlobals::gUsingInternalLanes && MSNet::getInstance()->hasInternalLinks()) { - if (myLane->isInternal()) { - if (furtherIndex % 2 == 0) { - routeIndex -= (furtherIndex + 0) / 2; - resultInternal = false; - } else { - routeIndex -= (furtherIndex + 1) / 2; - resultInternal = false; - } - } else { - if (furtherIndex % 2 != 0) { - routeIndex -= (furtherIndex + 1) / 2; - resultInternal = false; - } else { - routeIndex -= (furtherIndex + 2) / 2; - resultInternal = true; - } - } - } else { - routeIndex -= furtherIndex; - resultInternal = false; - } - furtherIndex++; - if (routeIndex >= 0) { - if (resultInternal) { - const MSEdge* prevNormal = myRoute->getEdges()[routeIndex]; - for (MSLane* cand : prevNormal->getLanes()) { - for (MSLink* link : cand->getLinkCont()) { - if (link->getLane() == current) { - if (link->getViaLane() != nullptr) { - return link->getViaLane(); - } else { - return const_cast(link->getLaneBefore()); - } - } - } - } - } else { - return myRoute->getEdges()[routeIndex]->getLanes()[0]; - } - } - } - return current; -} - - double GUIVehicle::getLastLaneChangeOffset() const { return STEPS2TIME(getLaneChangeModel().getLastLaneChangeOffset()); diff --git a/src/guisim/GUIVehicle.h b/src/guisim/GUIVehicle.h index 16c5f5d7efe8..d3a9f29cb4d5 100644 --- a/src/guisim/GUIVehicle.h +++ b/src/guisim/GUIVehicle.h @@ -184,12 +184,6 @@ class GUIVehicle : public MSVehicle, public GUIBaseVehicle { * passengerSeats are computed beginning at firstPassengerCarriage */ void drawAction_drawCarriageClass(const GUIVisualizationSettings& s, bool asImage) const; - /* @brief return the previous lane in this vehicles route including internal lanes - * @param[in] current The lane of which the predecessor should be returned - * @param[in,out] routeIndex The index of the current or previous non-internal edge in the route - */ - MSLane* getPreviousLane(MSLane* current, int& furtherIndex) const; - /// @brief retrieve information about the current stop state std::string getStopInfo() const; diff --git a/src/microsim/MSVehicle.cpp b/src/microsim/MSVehicle.cpp index 86509497a775..20366f02c135 100644 --- a/src/microsim/MSVehicle.cpp +++ b/src/microsim/MSVehicle.cpp @@ -62,6 +62,9 @@ #include #include #include +#include +#include +#include #include "MSEdgeControl.h" #include "MSVehicleControl.h" #include "MSInsertionControl.h" @@ -71,19 +74,16 @@ #include "MSStop.h" #include "MSStoppingPlace.h" #include "MSParkingArea.h" -#include "devices/MSDevice_Transportable.h" -#include #include "MSMoveReminder.h" -#include #include "MSLane.h" #include "MSJunction.h" -#include "MSVehicle.h" #include "MSEdge.h" #include "MSVehicleType.h" #include "MSNet.h" #include "MSRoute.h" #include "MSLeaderInfo.h" #include "MSDriverState.h" +#include "MSVehicle.h" //#define DEBUG_PLAN_MOVE //#define DEBUG_PLAN_MOVE_LEADERINFO @@ -7566,6 +7566,7 @@ MSVehicle::setExitManoeuvre() { MSVehicle::Manoeuvre::Manoeuvre() : myManoeuvreStop(""), myManoeuvreStartTime(0), myManoeuvreCompleteTime(0), myManoeuvreType(MSVehicle::MANOEUVRE_NONE), myGUIIncrement(0) {} + MSVehicle::Manoeuvre::Manoeuvre(const Manoeuvre& manoeuvre) { myManoeuvreStop = manoeuvre.myManoeuvreStop; myManoeuvreStartTime = manoeuvre.myManoeuvreStartTime; @@ -7574,6 +7575,7 @@ MSVehicle::Manoeuvre::Manoeuvre(const Manoeuvre& manoeuvre) { myGUIIncrement = manoeuvre.myGUIIncrement; } + MSVehicle::Manoeuvre& MSVehicle::Manoeuvre::operator=(const Manoeuvre& manoeuvre) { myManoeuvreStop = manoeuvre.myManoeuvreStop; @@ -7584,6 +7586,7 @@ MSVehicle::Manoeuvre::operator=(const Manoeuvre& manoeuvre) { return *this; } + bool MSVehicle::Manoeuvre::operator!=(const Manoeuvre& manoeuvre) { return (myManoeuvreStop != manoeuvre.myManoeuvreStop || @@ -7594,16 +7597,19 @@ MSVehicle::Manoeuvre::operator!=(const Manoeuvre& manoeuvre) { ); } + double MSVehicle::Manoeuvre::getGUIIncrement() const { return (myGUIIncrement); } + MSVehicle::ManoeuvreType MSVehicle::Manoeuvre::getManoeuvreType() const { return (myManoeuvreType); } + MSVehicle::ManoeuvreType MSVehicle::getManoeuvreType() const { return (myManoeuvre.getManoeuvreType()); @@ -7615,6 +7621,7 @@ MSVehicle::setManoeuvreType(const MSVehicle::ManoeuvreType mType) { myManoeuvre.setManoeuvreType(mType); } + void MSVehicle::Manoeuvre::setManoeuvreType(const MSVehicle::ManoeuvreType mType) { myManoeuvreType = mType; @@ -7652,6 +7659,7 @@ MSVehicle::Manoeuvre::configureEntryManoeuvre(MSVehicle* veh) { return (true); } + bool MSVehicle::Manoeuvre::configureExitManoeuvre(MSVehicle* veh) { // At the moment we only want to set for parking areas @@ -7694,6 +7702,7 @@ MSVehicle::Manoeuvre::configureExitManoeuvre(MSVehicle* veh) { return (true); } + bool MSVehicle::Manoeuvre::entryManoeuvreIsComplete(MSVehicle* veh) { // At the moment we only want to consider parking areas - need to check because we could be setting up a manoeuvre @@ -7737,11 +7746,14 @@ bool MSVehicle::Manoeuvre::manoeuvreIsComplete() const { return (MSNet::getInstance()->getCurrentTimeStep() >= myManoeuvreCompleteTime); } + + bool MSVehicle::manoeuvreIsComplete() const { return (myManoeuvre.manoeuvreIsComplete()); } + std::pair MSVehicle::estimateTimeToNextStop() const { if (hasStops()) { @@ -7836,6 +7848,7 @@ MSVehicle::estimateTimeToNextStop() const { } } + double MSVehicle::getStopDelay() const { if (hasStops() && myStops.front().pars.until >= 0) { @@ -7857,6 +7870,7 @@ MSVehicle::getStopDelay() const { } } + double MSVehicle::getStopArrivalDelay() const { if (hasStops() && myStops.front().pars.arrival >= 0) { @@ -7878,6 +7892,7 @@ MSVehicle::getCurrentEdge() const { return myLane != nullptr ? &myLane->getEdge() : getEdge(); } + const MSEdge* MSVehicle::getNextEdgePtr() const { if (myLane == nullptr || (myCurrEdge + 1) == myRoute->end()) { @@ -7892,4 +7907,59 @@ MSVehicle::getNextEdgePtr() const { } } + +const MSLane* +MSVehicle::getPreviousLane(const MSLane* current, int& furtherIndex) const { + if (furtherIndex < (int)myFurtherLanes.size()) { + return myFurtherLanes[furtherIndex++]; + } else { + // try to use route information + int routeIndex = getRoutePosition(); + bool resultInternal; + if (MSGlobals::gUsingInternalLanes && MSNet::getInstance()->hasInternalLinks()) { + if (myLane->isInternal()) { + if (furtherIndex % 2 == 0) { + routeIndex -= (furtherIndex + 0) / 2; + resultInternal = false; + } else { + routeIndex -= (furtherIndex + 1) / 2; + resultInternal = false; + } + } else { + if (furtherIndex % 2 != 0) { + routeIndex -= (furtherIndex + 1) / 2; + resultInternal = false; + } else { + routeIndex -= (furtherIndex + 2) / 2; + resultInternal = true; + } + } + } else { + routeIndex -= furtherIndex; + resultInternal = false; + } + furtherIndex++; + if (routeIndex >= 0) { + if (resultInternal) { + const MSEdge* prevNormal = myRoute->getEdges()[routeIndex]; + for (MSLane* cand : prevNormal->getLanes()) { + for (MSLink* link : cand->getLinkCont()) { + if (link->getLane() == current) { + if (link->getViaLane() != nullptr) { + return link->getViaLane(); + } else { + return const_cast(link->getLaneBefore()); + } + } + } + } + } else { + return myRoute->getEdges()[routeIndex]->getLanes()[0]; + } + } + } + return current; +} + + /****************************************************************************/ diff --git a/src/microsim/MSVehicle.h b/src/microsim/MSVehicle.h index 19cf26d5ba55..17d315cac46c 100644 --- a/src/microsim/MSVehicle.h +++ b/src/microsim/MSVehicle.h @@ -2056,7 +2056,12 @@ class MSVehicle : public MSBaseVehicle { inline double accelThresholdForWaiting() const { return 0.5 * getCarFollowModel().getMaxAccel(); } - + + /* @brief return the previous lane in this vehicles route including internal lanes + * @param[in] current The lane of which the predecessor should be returned + * @param[in,out] routeIndex The index of the current or previous non-internal edge in the route + */ + const MSLane* getPreviousLane(const MSLane* current, int& furtherIndex) const; protected: diff --git a/src/microsim/transportables/CMakeLists.txt b/src/microsim/transportables/CMakeLists.txt index 6d4820252d8c..f292ecba0a27 100644 --- a/src/microsim/transportables/CMakeLists.txt +++ b/src/microsim/transportables/CMakeLists.txt @@ -31,6 +31,8 @@ set(microsim_transportables_STAT_SRCS MSTransportable.h MSTransportableControl.cpp MSTransportableControl.h + MSTrainHelper.cpp + MSTrainHelper.h ${microsim_transportables_JPS_SRCS} ) diff --git a/src/microsim/transportables/MSTrainHelper.cpp b/src/microsim/transportables/MSTrainHelper.cpp new file mode 100644 index 000000000000..5ae3f85e9996 --- /dev/null +++ b/src/microsim/transportables/MSTrainHelper.cpp @@ -0,0 +1,147 @@ +/****************************************************************************/ +// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo +// Copyright (C) 2001-2024 German Aerospace Center (DLR) and others. +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0/ +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License 2.0 are satisfied: GNU General Public License, version 2 +// or later which is available at +// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later +/****************************************************************************/ +/// @file MSTrainHelper.cpp +/// @author Benjamin Coueraud +/// @date Fri, 8 Feb 2024 +/// +// A class that helps computing positions of a train's carriages. +/****************************************************************************/ +#include + +#include +#include +#include +#include "MSTrainHelper.h" + +// =========================================================================== +// method definitions +// =========================================================================== +std::vector +MSTrainHelper::getCarriageShapes(void) { + std::vector carriageShapes; + for (const Carriage* carriage: myCarriages) { + Position direction = carriage->front - carriage->back; + Position perp = Position(-direction.y(), direction.x()); + PositionVector shape; + shape.push_back(carriage->front + perp*myHalfWidth); + shape.push_back(carriage->front - perp*myHalfWidth); + shape.push_back(carriage->back - perp*myHalfWidth); + shape.push_back(carriage->back + perp*myHalfWidth); + carriageShapes.push_back(shape); + } + return carriageShapes; +} + + +void +MSTrainHelper::computeTrainDimensions(double exaggeration) { + const MSVehicleType& vtype = myTrain->getVehicleType(); + const double totalLength = vtype.getLength(); + myUpscaleLength = exaggeration; + if (exaggeration > 1 && totalLength > 5) { + // Reduce the length/width ratio because this is not useful at high zoom. + const double widthLengthFactor = totalLength / 5; + const double shrinkFactor = MIN2(widthLengthFactor, sqrt(myUpscaleLength)); + myUpscaleLength /= shrinkFactor; + } + myLocomotiveLength = vtype.getParameter().locomotiveLength * myUpscaleLength; + myDefaultLength = vtype.getParameter().carriageLength * myUpscaleLength; + myCarriageGap = vtype.getParameter().carriageGap * myUpscaleLength; + myLength = totalLength * myUpscaleLength; + myHalfWidth = 0.5 * vtype.getWidth() * myUpscaleLength; + myNumCarriages = MAX2(1, 1 + (int)((myLength - myLocomotiveLength) / (myDefaultLength + myCarriageGap) + 0.5)); // Round to closest integer. + assert(myNumCarriages > 0); + myCarriageLengthWithGap = myLength / myNumCarriages; + myCarriageLength = myCarriageLengthWithGap - myCarriageGap; + myFirstCarriageLength = myCarriageLength; + if (myDefaultLength != myLocomotiveLength && myNumCarriages > 1) { + myFirstCarriageLength = myLocomotiveLength; + myCarriageLengthWithGap = (myLength - myLocomotiveLength) / (myNumCarriages - 1); + myCarriageLength = myCarriageLengthWithGap - myCarriageGap; + } +} + + +void +MSTrainHelper::computeCarriages(bool secondaryShape, bool reversed) { + myCarriages.clear(); + + const MSLane* lane = myTrain->getLane(); // Lane on which the carriage's front is situated. + int furtherIndex = 0; + const MSLane* backLane = lane; // Lane on which the carriage's back is situated. + int backFurtherIndex = furtherIndex; + // Offsets of front and back parts of a carriage. + double carriageOffset = myTrain->getPositionOnLane(); + if (myTrain->getLaneChangeModel().isOpposite()) { + // This still produces some artifacts when not fully on the current lane. + carriageOffset = MIN2(carriageOffset + myTrain->getLength(), lane->getLength()); + } + double carriageBackOffset = carriageOffset - myFirstCarriageLength; + + double curCLength = myFirstCarriageLength; + myFirstCarriageNo = 0; // default case - we're going forwards + myIsReversed = (myTrain->isReversed() && reversed) || myTrain->getLaneChangeModel().isOpposite(); + if (myIsReversed) { + myFirstCarriageNo = myNumCarriages - 1; + if (myNumCarriages > 1) { + carriageBackOffset = carriageOffset - myCarriageLength; + } + } + + const double lateralOffset = (myTrain->isParking() && myTrain->getNextStopParameter()->posLat == INVALID_DOUBLE + ? (myTrain->getLane()->getWidth() * (MSGlobals::gLefthand ? -1 : 1)) + : -myTrain->getLateralPositionOnLane()); + + for (int i = 0; i < myNumCarriages; ++i) { + Carriage* carriage = new Carriage(); + if (i == myFirstCarriageNo) { + curCLength = myFirstCarriageLength; + if (myFirstCarriageNo > 0) { + // Previous loop iteration has adjusted backpos for a normal carriage so have to correct + carriageBackOffset += myCarriageLengthWithGap; + carriageBackOffset -= myFirstCarriageLength + myCarriageGap; + } + } else { + curCLength = myCarriageLength; + } + while (carriageOffset < 0) { + const MSLane* prev = myTrain->getPreviousLane(lane, furtherIndex); + if (prev != lane) { + carriageOffset += prev->getLength(); + } else { + // No lane available. + carriageOffset = 0; + } + lane = prev; + } + while (carriageBackOffset < 0) { + const MSLane* prev = myTrain->getPreviousLane(backLane, backFurtherIndex); + if (prev != backLane) { + carriageBackOffset += prev->getLength(); + } else { + // No lane available. + carriageBackOffset = 0; + } + backLane = prev; + } + + carriage->front = lane->getShape(secondaryShape).positionAtOffset(carriageOffset * lane->getLengthGeometryFactor(secondaryShape), lateralOffset); + carriage->back = backLane->getShape(secondaryShape).positionAtOffset(carriageBackOffset * lane->getLengthGeometryFactor(secondaryShape), lateralOffset); + // TODO: compute the doors. + myCarriages.push_back(carriage); + + carriageOffset -= (curCLength + myCarriageGap); + carriageBackOffset -= myCarriageLengthWithGap; + } +} \ No newline at end of file diff --git a/src/microsim/transportables/MSTrainHelper.h b/src/microsim/transportables/MSTrainHelper.h new file mode 100644 index 000000000000..6d8c41f61f78 --- /dev/null +++ b/src/microsim/transportables/MSTrainHelper.h @@ -0,0 +1,134 @@ +/****************************************************************************/ +// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo +// Copyright (C) 2014-2024 German Aerospace Center (DLR) and others. +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0/ +// This Source Code may also be made available under the following Secondary +// Licenses when the conditions for such availability set forth in the Eclipse +// Public License 2.0 are satisfied: GNU General Public License, version 2 +// or later which is available at +// https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html +// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later +/****************************************************************************/ +/// @file MSTrainHelper.h +/// @author Benjamin Coueraud +/// @date Wed, 07 Fev 2024 +/// +// A class that helps computing positions of a train's carriages. +/****************************************************************************/ +#pragma once +#include + +#include +#include + + +// =========================================================================== +// class declarations +// =========================================================================== +class MSVehicle; + + +// =========================================================================== +// class definitions +// =========================================================================== +/** + * @class MSTrainHelper + * @brief A class that helps computing positions of a train's carriages. + * + */ +class MSTrainHelper { +public: + MSTrainHelper(const MSVehicle* vehicle, bool reversed, bool secondaryShape=false, double exaggeration=1.0) + : myTrain(vehicle) { + computeTrainDimensions(exaggeration); + computeCarriages(secondaryShape, reversed); + } + + ~MSTrainHelper() { + for (const Carriage* carriage: myCarriages) { + delete carriage; + } + } + + inline double getUpscaleLength(void) const { + return myUpscaleLength; + } + + inline double getLocomotiveLength(void) const { + return myLocomotiveLength; + } + + inline double getDefaultLength(void) const { + return myDefaultLength; + } + + inline double getCarriageGap(void) const { + return myCarriageGap; + } + + inline double getLength(void) const { + return myLength; + } + + inline double getHalfWidth(void) const { + return myHalfWidth; + } + + inline int getNumCarriages(void) const { + return myNumCarriages; + } + + inline double getCarriageLengthWithGap(void) const { + return myCarriageLengthWithGap; + } + + inline double getCarriageLength(void) const { + return myCarriageLength; + } + + inline double getFirstCarriageLength(void) const { + return myFirstCarriageLength; + } + + inline int getFirstCarriageNo(void) const { + return myFirstCarriageNo; + } + + inline bool isReversed(void) const { + return myIsReversed; + } + + struct Carriage { + Position front; + Position back; + std::vector doors; + }; + + inline std::vector getCarriages(void) const { + return myCarriages; + } + + // Compute the rectangles from the front and back positions. + std::vector getCarriageShapes(void); + +private: + const MSVehicle* myTrain; + double myUpscaleLength; + double myLocomotiveLength; + double myDefaultLength; + double myCarriageGap; + double myLength; + double myHalfWidth; + int myNumCarriages; + double myCarriageLengthWithGap; + double myCarriageLength; + double myFirstCarriageLength; + int myFirstCarriageNo; + bool myIsReversed; + std::vector myCarriages; + + void computeTrainDimensions(double exaggeration); + void computeCarriages(bool secondaryShape, bool reversed); +}; \ No newline at end of file