Сообщество программистов Autodesk в СНГ

ADN Club => AutoCAD .NET API => Тема начата: avc от 07-08-2018, 15:25:31

Название: Пересечение линии и параллелепипеда
Отправлено: avc от 07-08-2018, 15:25:31
Задачка: Есть бесконечная линия заданная как Point3d и Vector3d и есть габариты некоего объекта заданные как Extents3d. Пересекает ли линия габариты?
Вроде простая задачка, но что-то у меня выходит слишком сложное решение. Может есть готовый быстрый алгоритм на чистой векторной алгебре?
На всякий случай поясню зачем мне это надо: хочу избавится от вызова капризной внешней функции acedNEntSelPEx для поиска объектов под точкой. Но прежде чем искать реальные пересечения линии взгляда со всеми объектами чертежа, хотелось бы по быстрому отбросить объекты, у которых даже GeometricExtents не попадает на линию.
Вот велосипед, который я изобрел:
Код - C# [Выбрать]
  1.     /// <summary>
  2.     /// Луч в направлении direction пересекает габариты. на самом деле строятся другие габариты в плоскости Plane(picked, direction).
  3.     /// При пустом direction вызывате IsInside
  4.     /// </summary>
  5.     public static bool IsIntersect(this Point3d picked, Vector3d direction, Extents3d ext)
  6.     {
  7.       if (direction == new Vector3d()) // считаем что все происходит в плоскости XY
  8.         return picked.IsInside(ext);
  9.       // все вершины прямоугольника заданного Extents3d
  10.       Point3d[] extPoints = new Point3d[]{
  11.       ext.MinPoint,
  12.       new Point3d(ext.MaxPoint.X, ext.MinPoint.Y, ext.MinPoint.Z),
  13.       new Point3d(ext.MinPoint.X, ext.MaxPoint.Y, ext.MinPoint.Z),
  14.       new Point3d(ext.MinPoint.X, ext.MinPoint.Y, ext.MaxPoint.Z),
  15.       new Point3d(ext.MaxPoint.X, ext.MaxPoint.Y, ext.MinPoint.Z),
  16.       new Point3d(ext.MaxPoint.X, ext.MinPoint.Y, ext.MaxPoint.Z),
  17.       new Point3d(ext.MinPoint.X, ext.MaxPoint.Y, ext.MaxPoint.Z),
  18.       ext.MaxPoint
  19.       };
  20.       // процирование всех вершин на плоскость перпендикулярную direction и поиск габаритов этого множества точек
  21.       double minX = double.PositiveInfinity, minY = double.PositiveInfinity, minZ = double.PositiveInfinity;
  22.       double maxX = double.NegativeInfinity, maxY = double.NegativeInfinity, maxZ = double.NegativeInfinity;
  23.       using (Plane pl = new Plane(picked, direction))
  24.         foreach (Point3d p in extPoints)
  25.         {
  26.           Point3d proj = p.Project(pl, direction);
  27.           if (proj.X < minX) minX = proj.X;
  28.           else if (proj.X > maxX) maxX = proj.X;
  29.           if (proj.Y < minY) minY = proj.Y;
  30.           else if (proj.Y > maxY) maxY = proj.Y;
  31.           if (proj.Z < minZ) minZ = proj.Z;
  32.           else if (proj.Z > maxZ) maxZ = proj.Z;
  33.         }
  34.       // построение нового Extents3d по габаритам спроецированных точек (заведомо больше чем заданные!)
  35.       Extents3d projExt = new Extents3d(new Point3d(minX, minY, minZ), new Point3d(maxX, maxY, maxZ));
  36.       return picked.IsInside(projExt);
  37.     }
  38.  
  39.     /// <summary>
  40.     /// Gets a value indicating whether the specified point is inside the extents.
  41.     /// </summary>
  42.     /// <param name="pt">The instance to which the method applies.</param>
  43.     /// <param name="extents">The extents 3d supposed to contain the point.</param>
  44.     /// <returns>true if the point is inside the extents; otherwise, false.</returns>
  45.     public static bool IsInside(this Point3d pt, Extents3d extents, double tolerance = 0.0000001)
  46.     {
  47.       return
  48.         (pt.X > extents.MinPoint.X - tolerance) &&
  49.         (pt.Y > extents.MinPoint.Y - tolerance) &&
  50.         (pt.Z > extents.MinPoint.Z - tolerance) &&
  51.         (pt.X < extents.MaxPoint.X + tolerance) &&
  52.         (pt.Y < extents.MaxPoint.Y + tolerance) &&
  53.         (pt.Z < extents.MaxPoint.Z + tolerance);
  54.     }
Код рабочий, но наверно не быстрей, чем сразу вызывать brep.GetLineContainment. Особенно напрягает необходимость создавать и диспозить Plane - любой DisposableWrapper у меня вызывает подозрения :)
Название: Re: Пересечение линии и прямоугольника
Отправлено: Александр Ривилис от 07-08-2018, 15:59:20
Extents3d - это параллелепипед, а не прямоугольник.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: avc от 07-08-2018, 16:06:55
Extents3d - это параллелепипед, а не прямоугольник.
Долго искал, где ж я так опечатался :) Исправил
Название: Re: Пересечение линии и параллелепипеда
Отправлено: avc от 07-08-2018, 16:13:07
Кстати насчет прямоугольников. Вариант поиска пересечения линии с шестью ортогональными прямоугольниками-гранями этого параллелепипеда я тоже рассматривал, но даже не смог подступиться. Гуглятся школьные задачки с описанием линии формулами и т.п.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Александр Ривилис от 07-08-2018, 16:15:55
Думаю, что твой вариант самый быстрый, но не самый точный.
Кстати, для определения нахождения точки внутри параллелепипеда есть класс BoundBlock3d с методом Contains
Название: Re: Пересечение линии и параллелепипеда
Отправлено: avc от 07-08-2018, 17:15:57
Спасибо за весомое мнение  :)
Буду тестировать, что производительней: сразу искать пересечения, проверять Extents3d, или же (по вашей наводке) попробовать метод BoundBlock3d.IsDisjoint c линией описанной как BoundBlock3d с двумя нулевыми размерами...
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Дима_ от 07-08-2018, 22:43:02
Не проверял - но по моему так должно работать (возможно, на больших расстояниях и малых отклонениях не будет хватать точности - тогда вектор надо приводить не к 1, а к максимально используемому значению - но это вопрос "притирки-наладки" - идея в коде):
Код - C# [Выбрать]
  1.         private static Vector3d UnitVector(Vector3d v)
  2.         {
  3.             var arr = v.ToArray();
  4.             var mn = arr[v.LargestElement];
  5.             return new Vector3d(arr.Select(itm => itm / mn).ToArray());
  6.         }
  7.  
  8.         public static bool IsIntersect(Point3d pt, Vector3d vct, Extents3d ext)
  9.         {
  10.             Point3d[] extPoints = new Point3d[]{
  11.                                                   ext.MinPoint,
  12.                                                   new Point3d(ext.MaxPoint.X, ext.MinPoint.Y, ext.MinPoint.Z),
  13.                                                   new Point3d(ext.MinPoint.X, ext.MaxPoint.Y, ext.MinPoint.Z),
  14.                                                   new Point3d(ext.MinPoint.X, ext.MinPoint.Y, ext.MaxPoint.Z),
  15.                                                   new Point3d(ext.MaxPoint.X, ext.MaxPoint.Y, ext.MinPoint.Z),
  16.                                                   new Point3d(ext.MaxPoint.X, ext.MinPoint.Y, ext.MaxPoint.Z),
  17.                                                   new Point3d(ext.MinPoint.X, ext.MaxPoint.Y, ext.MaxPoint.Z),
  18.                                                   ext.MaxPoint
  19.                                                 };
  20.             var v = UnitVector(vct);
  21.             var vcs = extPoints.Select(ptt => UnitVector(pt.GetVectorTo(ptt))).ToList();
  22.             return vcs.Any(vc => v.X >= vc.X && v.Y >= vc.Y && v.Z >= vc.Z)&&
  23.                    vcs.Any(vc => v.X <= vc.X && v.Y <= vc.Y && v.Z <= vc.Z);
  24.         }
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Александр Ривилис от 07-08-2018, 22:46:22
Дима_,
А зачем нужен метод UnitVector если есть стандартный Vector3d.GetNormal() ?
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Дима_ от 07-08-2018, 22:52:01
Дима_,
А зачем нужен метод UnitVector если есть стандартный Vector3d.GetNormal() ?
А я забыл уж (помню, что был точно в лиспе, перегрузки посмотрел и проглядел видимо - подумал, видимо перепутал с чем-то) - давно под сам акад не писал (точнее, что под ним работает пишу, но до апи автокада совсем не касаюсь уже давно) - флешка в голове барахлит.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Дима_ от 07-08-2018, 22:57:08
Стоп - а GetNormal - это кажется не то? Мне единичный вектор нужен - сейчас посмотрю...
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Александр Ривилис от 07-08-2018, 22:57:57
Стоп - а GetNormal - это кажется не то? Мне единичный вектор нужен - сейчас посмотрю...
Посмотри-посмотри.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: avc от 07-08-2018, 23:15:10
Бррр. Хоть убей не усваиваю суть кода без типизации и на лямбдах. Но непременно попробую as is.  Спасибо.
GetNormal это как ни странно не нормаль какая-нибудь, а как раз единичный вектор, хотя по названию никак не догадаться, да.
Пока успел потестировать скорость с проверкой Extents и без нее. На моем велосипеде проверка не ускоряет, а затормаживает работу от полутора (на блоках) до 12 раз (на солидах). Похоже IntersectWith и GetLineContainment и так очень быстрые. Тысячи объектов за доли секунды. Это под отладчиком. Жаль только IntersectWith реализован только у половины Entity
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Дима_ от 07-08-2018, 23:23:37
Посмотри-посмотри.
Как ни странно - но нужен "тот" единичный вектор - который возвращает UnitVector. Во вложении пример который "не ловит" GetNormal.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Александр Ривилис от 07-08-2018, 23:33:34
Как ни странно - но нужен "тот" единичный вектор - который возвращает UnitVector.
Еще раз посмотрел его код. Он возвращает не единичный вектор в смысле аналитической геометрии.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Дима_ от 07-08-2018, 23:34:29
Хоть убей не усваиваю суть кода без типизации и на лямбдах
Надо привыкать мыслить абстрактно (не обращая внимание на синтаксис - хотя он и иногда раздражает). Суть простая - смотрим есть ли среди 8 единичных векторов (от исходной точки - до 8 угловых точек), такие, у которых все "координаты" больше и все меньше (то есть - чтоб был и тот и другой), чем у нашего вектора.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Дима_ от 07-08-2018, 23:36:25
Еще раз посмотрел его код. Он возвращает не единичный вектор в смысле аналитической геометрии.
Да - можете назвать его (тип вектора) в честь этой темы сайта :) - он нам и нужен.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: avc от 08-08-2018, 17:48:47
Весь день пытаюсь задействовать этот метод. Не работает. Изредка ловит некоторые объекты в некоторых точках.
Заметил что функция UnitVector разворачивает вектор с отрицательным максимумом. Взял по модулю. Не помогает.
Подумал, что тестирование идет только по лучу в направлении вектора. Попробовал добавить сравнение с обратным вектором. Не помогает.
Вот команда для тестирования (максимально упрощаю)
Код - C# [Выбрать]
  1.  [CommandMethod("Test")]
  2.     public static void TestCommand()
  3.     {
  4.       Document doc = AcadApp.DocumentManager.MdiActiveDocument;
  5.       Database db = doc.Database;
  6.       if (doc == null) return;
  7.       Editor ed = doc.Editor;
  8.       try
  9.       {
  10.         ObjectId spaceId = db.CurrentSpaceId;
  11.         Matrix3d ucs = ed.CurrentUserCoordinateSystem;
  12.         Vector3d dir = spaceId == SymbolUtilityServices.GetBlockModelSpaceId(db) ? // в модели
  13.           ((Point3d)AcadApp.GetSystemVariable("VIEWDIR")).GetAsVector().Negate().TransformBy(ucs)
  14.           : new Vector3d();
  15.         PromptPointResult ppr = ed.GetPoint("Click point");
  16.         if (ppr.Status != PromptStatus.OK) return;
  17.         Point3d picked = ppr.Value.TransformBy(ucs);
  18.         ed.WriteMessage("\r\n");
  19.         using (Transaction tr = db.TransactionManager.StartTransaction())
  20.         {
  21.           BlockTableRecord space = tr.GetObject(spaceId, OpenMode.ForRead) as BlockTableRecord;
  22.           foreach (ObjectId id in space)
  23.           {
  24.             if (!id.IsValid || id.IsErased || id.ObjectClass == dbAttrDef || id.ObjectClass == dbAttrRef || id.ObjectClass == dbProxy) // не работаем с объектами не имеющими GeometricExtents
  25.               continue;
  26.             Entity ent = tr.GetObject(id, OpenMode.ForRead) as Entity;
  27.             if (ent == null || !ent.Visible) continue;
  28.             Extents3d ext = ent.GeometricExtents;
  29.             if (picked.IsIntersect(dir, ext))
  30.               ed.WriteMessage($"Found by A>V>C>: {ent.GetRXClass().Name} #{ent.Id.OldIdPtr}\r\n");
  31.             if (IsIntersect(picked, dir, ext))
  32.               ed.WriteMessage($"Found by Дима_: {ent.GetRXClass().Name} #{ent.Id.OldIdPtr}\r\n");
  33.           }
  34.           tr.Commit();
  35.         }
  36.       }
  37.       catch (System.Exception ex) { ed.WriteMessage(ex.Message); }
  38.     }
Мой метод IsIntersect ловит все без проблем.
Замерил производительность - разницы нет. Хотя метод Димы выглядит куда как выигрышней. Значит все ресурсы пожирает GeometricExtents. Получается бороться нет смысла, буду просто тестировать пересечения с самим объектом, не проверяя пересечения с Extents.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Александр Ривилис от 08-08-2018, 18:35:39
Весь день пытаюсь задействовать этот метод. Не работает.
Честно говоря мне и самому показалось, что Дима_ что-то не учитывает, но так как на тестирование у меня не было времени, то я промолчал.
Значит все ресурсы пожирает GeometricExtents. Получается бороться нет смысла
Имеет смысл бороться только если многократно ищешь пересечение с одним и тем же примитивом. Тогда выгодно однократно получить GeometricExtents и использовать его в дальнейшем.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Дима_ от 08-08-2018, 19:48:56
У любой задачи есть простое, изящное, быстрое, но не правильное решение. Если скорость не меняется (а точнее, та что есть устраивает), то конечно тратить время не нужно - в проверке меня смутило только применение к вектору TransformBy(ucs) - в моем алгоритме он не нужен (идёт сравнение с "правильными" векторами), если только я опять не забыл и GeometricExtens не возвращает в UCS (пишу с телефона, не проверить сейчас).
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Александр Ривилис от 08-08-2018, 19:51:32
если только я опять не забыл и GeometricExtens не возвращает в UCS
Возвращает в WCS (или в OCS для примитивов в блоке).
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Александр Ривилис от 08-08-2018, 20:46:40
в проверке меня смутило только применение к вектору TransformBy(ucs)
Тут как раз avc прав. VIEWDIR получается в UCS. Поэтому его следует преобразовать в WCS.
(https://farm2.staticflickr.com/1771/43025333015_6379907d35_o.png)


Название: Re: Пересечение линии и параллелепипеда
Отправлено: avc от 08-08-2018, 21:19:20
И ViewDir, и GetPoint в UCS возвращают результат, к сожалению. Я на это не раз нарывался. Но собственно это все только для тестирования, можно тестить в WCS и убрать все эти мелкие танцы с бубном.
Попробую еще с GetNormal(). Но что-то не верится, что сработает.
Жаль, что не удалось найти простой способ. Даже если в моем случае нет смысла тестить на пересечение Extents, все равно пригодилось бы в других случаях.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Дима_ от 08-08-2018, 21:31:13
Ладно - становится интересно - приложи файл с объектами (которые не находятся).
Название: Re: Пересечение линии и параллелепипеда
Отправлено: avc от 08-08-2018, 21:59:21
да любой бокс можно помучить
Название: Re: Пересечение линии и параллелепипеда
Отправлено: avc от 08-08-2018, 22:26:36
Замена UnitVector на GetNormal не помогает. Вообще никода ничего не ловится
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Кирилл Захаров от 19-08-2018, 18:06:26
Здравствуйте, решил тоже поразмять мозги.
Предлагаю такое решение:
Код - C# [Выбрать]
  1.         /// <summary>
  2.         /// Возвращает 2 точки пересечения луча с параллепипедом Extents3d или пустой список если если пересечений нет
  3.         /// </summary>
  4.         /// <param name="rayPt"></param>
  5.         /// <param name="rayVector"></param>
  6.         /// <param name="extents"></param>
  7.         /// <returns></returns>
  8.         private static List<Point3d> CalcRayExtendsIntersection
  9.             (Point3d rayPt, Vector3d rayVector, Extents3d extents)
  10.         {
  11.             List<Point3d> pts = new List<Point3d>();
  12.             //Проверить на пересечение со всеми 6 гранями
  13.             //Каждая из 6 плоскостей граней параллельна двум осям системы координат,
  14.             //поэтому пересечение находится подстановкой максимальных и минимальных координат extents
  15.             //в каноническое уравнение прямой - http://mathprofi.ru/uravnenija_pryamoi_v_prostranstve.html
  16.             //После нахождения точки пересечения с плоскостью, определить попадает ли она в габариты соответствующей грани
  17.             double minX = extents.MinPoint.X;
  18.             double minY = extents.MinPoint.Y;
  19.             double minZ = extents.MinPoint.Z;
  20.             double maxX = extents.MaxPoint.X;
  21.             double maxY = extents.MaxPoint.Y;
  22.             double maxZ = extents.MaxPoint.Z;
  23.  
  24.             if(rayVector.X!=0)
  25.             {
  26.                 double x = minX;
  27.                 double n = ((x - rayPt.X) / rayVector.X);
  28.                 double y = n * rayVector.Y + rayPt.Y;
  29.                 double z = n * rayVector.Z + rayPt.Z;
  30.                 if (y >= minY && y <= maxY && z >= minZ && z <= maxZ)//Здесь можно использовать какой-нибудь допуск
  31.                 {
  32.                     pts.Add(new Point3d(x, y, z));
  33.                 }
  34.                 x = maxX;
  35.                 n = ((x - rayPt.X) / rayVector.X);
  36.                 y = n * rayVector.Y + rayPt.Y;
  37.                 z = n * rayVector.Z + rayPt.Z;
  38.                 if (y >= minY && y <= maxY && z >= minZ && z <= maxZ)
  39.                 {
  40.                     pts.Add(new Point3d(x, y, z));
  41.                     if (pts.Count > 1) return pts;
  42.                 }
  43.             }
  44.  
  45.             if (rayVector.Y != 0)
  46.             {
  47.                 double y = minY;
  48.                 double n = ((y - rayPt.Y) / rayVector.Y);
  49.                 double x = n * rayVector.X + rayPt.X;
  50.                 double z = n * rayVector.Z + rayPt.Z;
  51.                 if (x >= minX && x <= maxX && z >= minZ && z <= maxZ)
  52.                 {
  53.                     pts.Add(new Point3d(x, y, z));
  54.                     if (pts.Count > 1) return pts;
  55.                 }
  56.                 y = maxY;
  57.                 n = ((y - rayPt.Y) / rayVector.Y);
  58.                 x = n * rayVector.X + rayPt.X;
  59.                 z = n * rayVector.Z + rayPt.Z;
  60.                 if (x >= minX && x <= maxX && z >= minZ && z <= maxZ)
  61.                 {
  62.                     pts.Add(new Point3d(x, y, z));
  63.                     if (pts.Count > 1) return pts;
  64.                 }
  65.             }
  66.  
  67.  
  68.             if (rayVector.Z != 0)
  69.             {
  70.                 double z = minZ;
  71.                 double n = ((z - rayPt.Z) / rayVector.Z);
  72.                 double x = n * rayVector.X + rayPt.X;
  73.                 double y = n * rayVector.Y + rayPt.Y;
  74.                 if (x >= minX && x <= maxX && y >= minY && y <= maxY)
  75.                 {
  76.                     pts.Add(new Point3d(x, y, z));
  77.                     if (pts.Count > 1) return pts;
  78.                 }
  79.                 z = maxZ;
  80.                 n = ((z - rayPt.Z) / rayVector.Z);
  81.                 x = n * rayVector.X + rayPt.X;
  82.                 y = n * rayVector.Y + rayPt.Y;
  83.                 if (x >= minX && x <= maxX && y >= minY && y <= maxY)
  84.                 {
  85.                     pts.Add(new Point3d(x, y, z));
  86.                     if (pts.Count > 1) return pts;
  87.                 }
  88.  
  89.             }
  90.             return pts;
  91.  
  92.         }
  93.  
Выглядит громоздко, но, я думаю, так даже понятнее.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: avc от 20-08-2018, 17:39:39
Предлагаю такое решение
Гениально! Именно то, что я и пытался найти. Чистая незамутненная математика, без всяких DisposableWrapper и вызовов API. Я ожидал, что поиск пересечений со всеми плоскостями будет куда более громоздкий. И на практике ловит все объекты даже без введения допусков.
Спасибо!
Название: Re: Пересечение линии и параллелепипеда
Отправлено: avc от 20-08-2018, 17:58:20
И кстати, это работает на обеих направлениях линии от заданной точки, так что речь все-таки о линии, а не о луче. То, что мне и надо. Слово Ray в названиях несколько смутило.
Название: Re: Пересечение линии и параллелепипеда
Отправлено: Кирилл Захаров от 20-08-2018, 19:34:21
так что речь все-таки о линии, а не о луче
Да-да-да, линия, не луч.