----------------------------------------------------- EXCERPT FROM: Sim/SimWS.cpp 2018/01/05 for Gnats 6066 ----------------------------------------------------- // // Add a SimObj to a particular position in a given geometry and give the // object reasonable default positions for the other geometries as well. // The goal is to have the geometric relationships between the new object // and the existing objects be the same for all geometries. // bool SimWorkspace::addSimObjAtPosition(SimObj* newSimObj, int newObjX, int newObjY, CanvasDefs::CanvasGeometry geom) { // ********************************************************************* // *** (1) Compute and Assign Coordinates for All Three Geometries *** // ********************************************************************* // okstat assignPosOk = // assignPosForAllViews_proportionalCanvas ( // newSimObj, newObjX, newObjY, geom); okstat assignPosOk = assignPosForAllViews_proximateNeighbors ( newSimObj, newObjX, newObjY, geom); assignPosOk; // (avoid compilation warning) // ********************************************* // *** (2) Add SimObj to the Sim Workspace *** // ********************************************* // The following results in the generation of a WS_OBJECT_ADDED callback. // An error indication indicates that the newSimObj has already been added. // Such a condition is also reported as an rwError. const bool addObjError = addSimObj (newSimObj); return addObjError; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- okstat SimWorkspace::assignPosForAllViews_proportionalCanvas( SimObj* newObj, int newObjX, int newObjY, CanvasDefs::CanvasGeometry geom) { // ***************************************************************** // *** Compute and Assign Coordinates for All Three Geometries *** // ***************************************************************** // This method computes the proportional positions of a SimObj based on // the canvas extents of the reference view ("geometry") and places it in // the same place on other views. For example, x=30%,y=20% on this views, // so put it at x=30%,y=20% on the other view. const int simWsWid = _workspaceSimWidth; const int simWsHgt = _workspaceSimHeight; const int accWsWid = _accountingWidth; const int accWsHgt = _accountingHeight; const int geoWsWid = _geospatialWsWidth; const int geoWsHgt = _geospatialWsHeight; // tentative initial values double simXreal (newObjX); double simYreal (newObjY); double accXreal (newObjX); double accYreal (newObjY); double geoXreal (newObjX); double geoYreal (newObjY); switch (geom) { case CanvasDefs::Geom_SIMULATION: { if (simWsWid >= 1) { // Proportionally scale X coordinates to their respective canvas accXreal *= (accWsWid / (double) simWsWid); geoXreal *= (geoWsWid / (double) simWsWid); } if (simWsHgt >= 1) { // Proportionally scale Y coordinates to their respective canvas accYreal *= (accWsHgt / (double) simWsHgt); geoYreal *= (geoWsHgt / (double) simWsHgt); } break; } case CanvasDefs::Geom_ACCOUNTING: { if (accWsWid >= 1) { // Proportionally scale X coordinates to their respective canvas simXreal *= (simWsWid / (double) accWsWid); geoXreal *= (geoWsWid / (double) accWsWid); } if (accWsHgt >= 1) { // Proportionally scale Y coordinates to their respective canvas simYreal *= (simWsHgt / (double) accWsHgt); geoYreal *= (geoWsHgt / (double) accWsHgt); } break; } case CanvasDefs::Geom_GEOSPATIAL: { if (geoWsWid >= 1) { // Proportionally scale X coordinates to their respective canvas simXreal *= (simWsWid / (double) geoWsWid); accXreal *= (accWsWid / (double) geoWsWid); } if (geoWsHgt >= 1) { simYreal *= (simWsHgt / (double) geoWsHgt); accYreal *= (accWsHgt / (double) geoWsHgt); } break; } } const int simX1 ((int) (0.5 + simXreal)); const int simY1 ((int) (0.5 + simYreal)); const int accX1 ((int) (0.5 + accXreal)); const int accY1 ((int) (0.5 + accYreal)); const int geoX1 ((int) (0.5 + geoXreal)); const int geoY1 ((int) (0.5 + geoYreal)); static const int MARG (50); const int simX2 (std::max (MARG, (std::min (simX1, simWsWid - MARG)))); const int simY2 (std::max (MARG, (std::min (simY1, simWsHgt - MARG)))); const int accX2 (std::max (MARG, (std::min (accX1, accWsWid - MARG)))); const int accY2 (std::max (MARG, (std::min (accY1, accWsHgt - MARG)))); const int geoX2 (std::max (MARG, (std::min (geoX1, geoWsWid - MARG)))); const int geoY2 (std::max (MARG, (std::min (geoY1, geoWsHgt - MARG)))); if (newObj) { newObj->setXY (simX2, simY2); newObj->setAccountingXY (accX2, accY2); newObj->setGeoXY (geoX2, geoY2); } return okstat (true); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- okstat SimWorkspace::assignPosForAllViews_proximateNeighbors( SimObj* newObj, int newObjX, int newObjY, CanvasDefs::CanvasGeometry geom) { static const char* mname ( "SimWorkspace::assignPosForAllViews_proximateNeighbors"); static int callCnt (0); ++callCnt; if (newObj == NULL) return okstat ("NULL new object"); //----->> // Retrieve list of current simulation objects on workspace. const QList objList = simObjList(); const int objCnt = objList.count(); // ******************************************************************* // *** Compute SimObj Distance-Squared Map from Existing SimObjs *** // ******************************************************************* // Note: We use Distance-Squared instead of Distance to avoid calculating // the square root. This is reasonable because we are interested in // assessing the closest objects. QMap objDistSquaredMap; // [Distance Squared, SimObj] QList encounteredSimObjCoords; // Can't use a QSet, no hash func. for (int inx = 0; inx < objCnt; ++inx) { SimObj* obj = objList [inx]; if (obj && (obj != newObj)) { QPoint objPos (obj->getX (geom), obj->getY (geom)); if (!encounteredSimObjCoords.contains (objPos)) { const int dx (newObjX - objPos.x()); const int dy (newObjY - objPos.y()); const int dist2 = (dx*dx) + (dy*dy); objDistSquaredMap.insert (dist2, obj); encounteredSimObjCoords.append (objPos); } } } const int proxObjCnt = objDistSquaredMap.count(); // ********************************************************************* // *** No proximate objects exist? -- Fallback to other algorithm *** // ********************************************************************* if (proxObjCnt == 0) { // Fall back to "proportional position with respect to scene rectangle" // algorithm. const okstat posOk = assignPosForAllViews_proportionalCanvas( newObj, newObjX, newObjY, geom); return posOk; //----->> } SimObj* closestObj = objDistSquaredMap.first(); const int closestObjDist2 = objDistSquaredMap.firstKey(); // const double closestObjDist = sqrt (closestObjDist2); if (!rwAssert (closestObj != NULL)) // not expected { return okstat ("Invalid closestObj"); //----->> } const int closestObjX = closestObj->getX (geom); const int closestObjY = closestObj->getY (geom); // ********************************************************** // *** Object is ON Existing Object? -- Place on Object *** // ********************************************************** if (closestObjDist2 == 0) { newObj->setXY (closestObj->getX(), closestObj->getY()); newObj->setAccountingXY ( closestObj->getAccountingX(), closestObj->getAccountingY()); newObj->setGeoXY (closestObj->getGeoX(), closestObj->getGeoY()); return okstat (true); //----->> } // ******************************************************************* // *** Only ONE Existing Object? -- Scale Difference in Position *** // ******************************************************************* if (proxObjCnt == 1) { const okstat posOk = assignPosForAllViews_projectFromRefObj( newObj, newObjX, newObjY, closestObj, geom); return posOk; //----->> } // ***************************************************************** // *** Multiple Available Other Objects: *** // *** Identify "Reasonably Optimal" Second Proximate Object *** // ***************************************************************** // Identify another SimObj, preferring one that is also close to the // new object coordinates. Try to pick one which is closer to the // new object coordinates than to its closest object. //-- QMap objDistSquaredMap; // [Distance Squared, SimObj] const QList distSquareKeys = objDistSquaredMap.keys(); const int distSquareKeyCnt = distSquareKeys.count(); SimObj* secRefProxObj (NULL); double leastRelativeDist (0.0); SimObj* leastRelativeDistObj (NULL); // Note: Skip the closest object. for (int inx = 1; inx < distSquareKeyCnt; ++inx) { const int dist2 = distSquareKeys [inx]; SimObj* obj = objDistSquaredMap.value (dist2); if (obj == NULL) continue; //----- const int objX = obj->getX (geom); const int objY = obj->getY (geom); const int newObjDx (newObjX - objX); const int newObjDy (newObjY - objY); const int newObjDist2 ((newObjDx*newObjDx) + (newObjDy*newObjDy)); const int closestObjDx (closestObjX - objX); const int closestObjDy (closestObjY - objY); const int closestObjDist2 ( (closestObjDx*closestObjDx) + (closestObjDy*closestObjDy)); if (newObjDist2 <= closestObjDist2) { secRefProxObj = obj; break; //----- } if (closestObjDist2 > 0) { const double relativeDist = sqrt (double (newObjDist2)) / sqrt (double (closestObjDist2)); if ((leastRelativeDistObj == NULL) || (relativeDist < leastRelativeDist)) { leastRelativeDistObj = obj; leastRelativeDist = relativeDist; } } } if (secRefProxObj == NULL) { if (leastRelativeDistObj == NULL) { const okstat posOk = assignPosForAllViews_projectFromRefObj( newObj, newObjX, newObjY, closestObj, geom); return posOk; //----->> } secRefProxObj = leastRelativeDistObj; } const okstat posOk1 = assignPosForAllViews_triangulateFromTwoRefObjs( newObj, newObjX, newObjY, closestObj, secRefProxObj, geom, CanvasDefs::Geom_SIMULATION); const okstat posOk2 = assignPosForAllViews_triangulateFromTwoRefObjs( newObj, newObjX, newObjY, closestObj, secRefProxObj, geom, CanvasDefs::Geom_ACCOUNTING); const okstat posOk3 = assignPosForAllViews_triangulateFromTwoRefObjs( newObj, newObjX, newObjY, closestObj, secRefProxObj, geom, CanvasDefs::Geom_GEOSPATIAL); if (!posOk1) return posOk1; //----->> if (!posOk2) return posOk2; //----->> if (!posOk3) return posOk3; //----->> return okstat (true); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- okstat SimWorkspace::assignPosForAllViews_projectFromRefObj ( SimObj* newObj, int newObjX, int newObjY, const SimObj* refObj, CanvasDefs::CanvasGeometry geom) { if (newObj == NULL) return okstat ("NULL new object"); //----->> if (refObj == NULL) return okstat ("NULL reference object"); //----->> // ******************************************************************** // *** (1) Assign Provided Coords to Object in Reference Geometry *** // ******************************************************************** newObj->setXY (geom, newObjX, newObjY); const int refObjX = refObj->getX (geom); const int refObjY = refObj->getY (geom); const int refObjDx (newObjX - refObjX); const int refObjDy (newObjY - refObjY); // Set aside canvas sizes const int refWsWid = getWorkspaceWidth (geom); const int refWsHgt = getWorkspaceHeight (geom); const int simWsWid = _workspaceSimWidth; const int simWsHgt = _workspaceSimHeight; const int accWsWid = _accountingWidth; const int accWsHgt = _accountingHeight; const int geoWsWid = _geospatialWsWidth; const int geoWsHgt = _geospatialWsHeight; static const int MARG (50); if (geom != CanvasDefs::Geom_SIMULATION) { // ******************************************************************** // *** (2) Project Delta from Reference Object in Simulation View *** // ******************************************************************** const int simRefX = refObj->getX(); const int simRefY = refObj->getY(); double simDx = refObjDx; double simDy = refObjDy; if ((simWsWid > 0) && (refWsWid > 1e-6)) simDx *= (simWsWid / (double) refWsWid); if ((simWsHgt > 0) && (refWsHgt > 1e-6)) simDy *= (simWsHgt / (double) refWsHgt); const int simX1 = (int) (simRefX + simDx + 0.5); const int simY1 = (int) (simRefY + simDy + 0.5); const int simX2 (std::max (MARG, (std::min (simX1, simWsWid-MARG)))); const int simY2 (std::max (MARG, (std::min (simY1, simWsHgt-MARG)))); newObj->setXY (simX2, simY2); } if (geom != CanvasDefs::Geom_ACCOUNTING) { // ******************************************************************** // *** (3) Project Delta from Reference Object in Accounting View *** // ******************************************************************** const int accRefX = refObj->getAccountingX(); const int accRefY = refObj->getAccountingY(); double accDx = refObjDx; double accDy = refObjDy; if ((accWsWid > 0) && (refWsWid > 1e-6)) accDx *= (accWsWid / (double) refWsWid); if ((accWsHgt > 0) && (refWsHgt > 1e-6)) accDy *= (accWsHgt / (double) refWsHgt); const int accX1 = (int) (accRefX + accDx + 0.5); const int accY1 = (int) (accRefY + accDy + 0.5); const int accX2 (std::max (MARG, (std::min (accX1, accWsWid-MARG)))); const int accY2 (std::max (MARG, (std::min (accY1, accWsHgt-MARG)))); newObj->setAccountingXY (accX2, accY2); } if (geom != CanvasDefs::Geom_GEOSPATIAL) { // ******************************************************************** // *** (4) Project Delta from Reference Object in Geospatial View *** // ******************************************************************** const int geoRefX = refObj->getGeoX(); const int geoRefY = refObj->getGeoY(); double geoDx = refObjDx; double geoDy = refObjDy; if ((geoWsWid > 0) && (refWsWid > 1e-6)) geoDx *= (geoWsWid / (double) refWsWid); if ((geoWsHgt > 0) && (refWsHgt > 1e-6)) geoDy *= (geoWsHgt / (double) refWsHgt); const int geoX1 = (int) (geoRefX + geoDx + 0.5); const int geoY1 = (int) (geoRefY + geoDy + 0.5); const int geoX2 (std::max (MARG, (std::min (geoX1, geoWsWid-MARG)))); const int geoY2 (std::max (MARG, (std::min (geoY1, geoWsHgt-MARG)))); newObj->setGeoXY (geoX2, geoY2); } // Single existing object case, done. return okstat (true); //----->> } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- okstat SimWorkspace::assignPosForAllViews_triangulateFromTwoRefObjs ( SimObj* newObj, int newObjX, int newObjY, const SimObj* refObj1, const SimObj* refObj2, CanvasDefs::CanvasGeometry sourceGeom, CanvasDefs::CanvasGeometry targGeom) { static const char* mname ( "SimWorkspace::assignPosForAllViews_triangulateFromTwoRefObjs"); static int callCnt (0); ++callCnt; if (newObj == NULL) return okstat ("NULL new object"); //----->> if (sourceGeom == targGeom) { newObj->setXY (sourceGeom, newObjX, newObjY); return okstat (true); //----->> } if (refObj1 == NULL) return okstat ("NULL first reference object"); //----->> if (refObj2 == NULL) return okstat ("NULL second reference object"); //----->> std::cout << mname << " [#" << callCnt << "]" << " ref1: \"" << qPrintable (refObj1->getCompleteName()) << "\"" << ", ref2: \"" << qPrintable (refObj2->getCompleteName()) << "\"" << std::endl; const int ref1SrcX = refObj1->getX (sourceGeom); const int ref1SrcY = refObj1->getY (sourceGeom); const int ref2SrcX = refObj2->getX (sourceGeom); const int ref2SrcY = refObj2->getY (sourceGeom); const int ref1TrgX = refObj1->getX (targGeom); const int ref1TrgY = refObj1->getY (targGeom); const int ref2TrgX = refObj2->getX (targGeom); const int ref2TrgY = refObj2->getY (targGeom); const int refDxSrc (ref2SrcX - ref1SrcX); const int refDySrc (ref2SrcY - ref1SrcY); const int refDxTrg (ref2TrgX - ref1TrgX); const int refDyTrg (ref2TrgY - ref1TrgY); const double refDistSrc = sqrt ((refDxSrc*refDxSrc) + (refDySrc*refDySrc)); const double refDistTrg = sqrt ((refDxTrg*refDxTrg) + (refDyTrg*refDyTrg)); const int r1NewDxSrc (newObjX - ref1SrcX); const int r1NewDySrc (newObjY - ref1SrcY); const double r1NewDistSrc = sqrt ((r1NewDxSrc*r1NewDxSrc) + (r1NewDySrc*r1NewDySrc)); const int r2NewDxSrc (newObjX - ref2SrcX); const int r2NewDySrc (newObjY - ref2SrcY); const double r2NewDistSrc = sqrt ((r2NewDxSrc*r2NewDxSrc) + (r2NewDySrc*r2NewDySrc)); const double trgSrcRatio = refDistTrg / refDistSrc; const double r1NewDistTrg = r1NewDistSrc * trgSrcRatio; const double r2NewDistTrg = r2NewDistSrc * trgSrcRatio; //---------------------------------------------------- // https://math.stackexchange.com/questions/543961/ // "Determine third point of triangle when two points // and all sides are known". // // Point A = refObj1 in target geometry // Point B = refObj2 in target geometry // Point C = newObj in target geometry //---------------------------------------------------- const double AB = refDistTrg; const double AB2 = (AB * AB); const double AC2 = (r1NewDistTrg * r1NewDistTrg); const double BC2 = (r2NewDistTrg * r2NewDistTrg); const double Cy = (AB2 + AC2 - BC2) / (2 * AB); const double Cx = sqrt (AC2 - (Cy*Cy)); const int trgObjX = (int) (Cy + 0.5); const int trgObjY = (int) (Cx + 0.5); newObj->setXY (targGeom, trgObjX, trgObjY); return okstat (true); }