Лучевое трассирование (Raycasting) — это технология, используемая в компьютерной [[Graphics (Графика)|графике]] и игровой разработке для определения того, какой объект или поверхность "встречается" с лучом, отправленным из определенной точки в определенном направлении.
Это метод часто используется для различных задач:
- Проверка видимости объектов
- Поиск пути (pathfinding)
- Выбор объектов мышью или прикосновением
- Физика проектайлов и стрельба
- Обнаружение препятствий
- Планирование траектории движения
- Анализ видеопотока для обнаружения движения или определенных объектов
### Принцип работы
1. **Отправка луча**: Луч отправляется из начальной точки (`Origin`) в определенном направлении (`Direction`).
2. **Проверка пересечения**: Математический алгоритм вычисляет пересечение луча с объектами или поверхностями в сцене.
3. **Анализ данных**: После того, как найдены все пересечения, они обычно сортируются по удаленности от начальной точки. Вы можете получить первое пересечение или же все пересечения в зависимости от вашей задачи.
### Производительность
1. **Оптимизация количества лучей**: Чем меньше лучей вы используете, тем лучше. Лучи — это вычислительно затратная операция.
2. **Ограничение дистанции**: Укажите максимальное расстояние для луча, чтобы уменьшить количество возможных пересечений для проверки.
3. **Layer Masking (Маскирование слоёв)**: Используйте маски слоёв, чтобы лучи проверяли только определенные объекты, исключая другие.
4. **Избегайте постоянных проверок**: Не используйте Raycasting в `Update()` без необходимости, так как это может снизить производительность.
### Применение в Unity
В Unity это можно сделать с помощью метода `Physics.Raycast` для 3D или `Physics2D.Raycast` для 2D.
```csharp
RaycastHit hit;
if (Physics.Raycast(transform.position, transform.forward, out hit, 100f))
{
Debug.Log("Мы столкнулись с " + hit.collider.name);
}
```
### Написание аналога `Physics.Raycast` на чистом C\#
Для создания аналога кода из Unity на чистом C#, вы можете определить структуру `RaycastHit` и использовать метод, который возвращает эту структуру при успешной трассировке луча.
Предположим, нам нужно проверить, стоит ли перед юнитом любой другой юнит на расстоянии до 100 единиц.
##### Производительность
1. **Цикл по юнитам**: Эффективность этого метода зависит от количества юнитов. Операционная сложность будет O(n), где n — число юнитов.
2. **Дополнительные оптимизации**: Для большого количества юнитов, использование структур данных типа октодерева (Octree) может быть полезным.
#### Код на чистом C\#
###### Классы и структуры
```csharp
using System;
using System.Collections.Generic;
public struct Vector3
{
public float x, y, z;
// ... операторы и методы для работы с векторами
public static float Dot(Vector3 a, Vector3 b)
{
return a.x * b.x + a.y * b.y + a.z * b.z;
}
}
public struct Ray
{
public Vector3 Origin;
public Vector3 Direction;
// ... Конструкторы и методы
}
public struct RaycastHit
{
public string ColliderName;
public float Distance;
}
public class Unit
{
public Vector3 Position;
public float Radius;
public string Name;
}
```
###### Метод для трассировки луча
```csharp
public bool Raycast(Ray ray, List<Unit> units, out RaycastHit hitInfo, float maxDistance)
{
float closestT = maxDistance;
bool hasHit = false;
hitInfo = new RaycastHit();
foreach (Unit unit in units)
{
if (IsUnitInFront(ray, unit, out float t))
{
if (t < closestT)
{
closestT = t;
hitInfo.ColliderName = unit.Name;
hitInfo.Distance = t;
hasHit = true;
}
}
}
return hasHit;
}
// Параметр `t` здесь выступает как коэффициент, который показывает, на каком
// расстоянии вдоль направления луча (`Direction`) произошло пересечение с
// юнитом.
public bool IsUnitInFront(Ray ray, Unit targetUnit, out float t)
{
Vector3 oc = ray.Origin - targetUnit.Position;
float a = Vector3.Dot(ray.Direction, ray.Direction);
float b = 2.0f * Vector3.Dot(oc, ray.Direction);
float c = Vector3.Dot(oc, oc) - targetUnit.Radius * targetUnit.Radius;
float discriminant = b * b - 4 * a * c;
if (discriminant < 0)
{
t = 0;
return false;
}
else
{
t = (-b - (float)Math.Sqrt(discriminant)) / (2.0f * a);
return true;
}
}
```
###### Пример использования
```csharp
List<Unit> allUnits = new List<Unit>
{
new Unit { Position = new Vector3 { x = 0, y = 0, z = 5 }, Radius = 1, Name = "Unit1" },
new Unit { Position = new Vector3 { x = 0, y = 0, z = 10 }, Radius = 1, Name = "Unit2" }
};
Ray ray = new Ray { Origin = new Vector3 { x = 0, y = 0, z = 0 }, Direction = new Vector3 { x = 0, y = 0, z = 1 } };
if (Raycast(ray, allUnits, out RaycastHit hitInfo, 100f))
{
Console.WriteLine("Мы столкнулись с " + hitInfo.ColliderName);
}
else
{
Console.WriteLine("Столкновений нет.");
}
```