LeCodesdocs

Ray, Plane и Noise

Три небольших класса 3D-математики/утилит. Ray + Plane — набор для tap-to-place: преврати экранный тап в луч камеры, пересеки его с плоскостью земли, размести в точке попадания. Для рейкаста против тел, а не математической плоскости, используй Physics.raycast (он принимает origin/dir у Ray). Noise — нативный сэмплер Перлина/фрактала для рельефа, покачивания и органичного движения.

Обзор

TypeScript
const ground = new Plane([0, 1, 0], [0, 0, 0])       // Y-up плоскость через начало координат

scene.addEventListener('click', ev => {
  const ray = scene.camera.getRay(ev.clientX, ev.clientY)
  const t = ground.intersectRay(ray)
  if (t !== null) marker.position = ray.getPoint(t)  // размести в точке тапа по полу
})

Ray

TypeScript
new Ray(origin: Vec3Like, dir: Vec3Like)   // dir не обязан быть нормализован

ray.origin           // Vec3
ray.dir              // Vec3
ray.getPoint(t)      // Vec3 — origin + t·dir

Обычный источник — камера: scene.camera.getRay(screenX, screenY) строит луч пикинга от камеры через экранный пиксель (логические px — ev.clientX/clientY подставляются напрямую). См. Camera.

Note

параметр getPoint(t) — в единицах длины dir, мировые единицы только когда dir нормализован. dir лучей камеры берётся из направления взгляда хоста; нормализуй (ray.dir.normalize()) прежде, чем трактовать t как дистанцию в метрах.

Plane

Бесконечная геометрическая плоскость, хранимая как единичная normal + скалярное смещение d (dot(normal, point)).

TypeScript
new Plane(normal: Vec3Like, point: Vec3Like)       // normal нормализуется за тебя
Plane.fromPoints(a, b, c): Plane                   // через три точки (нормаль из обхода)
Plane.fromCoefficients(a, b, c, d): Plane          // из ax + by + cz = d

plane.normal                        // Vec3, единичной длины (только чтение)
plane.d                             // number (только чтение)

plane.intersectRay(ray): number | null    // параметр t ≥ 0 или null (параллельно / позади начала)
plane.intersectLine(ray): number | null   // то же, но попадания позади начала разрешены (t может быть < 0)
plane.signedDistance(point): number       // положительное на стороне нормали
plane.projectPoint(point): Vec3           // ближайшая точка на плоскости

Оба метода пересечения возвращают параметр луча t, не точку — передай его в ray.getPoint(t).

TypeScript
// держим персонажа над наклонной платформой
const surface = Plane.fromPoints(p0, p1, p2)
if (surface.signedDistance(hero.position) < 0) {
  hero.position = surface.projectPoint(hero.position)
}

Noise

Нативный сэмплер FastNoise Перлина/фрактала. Детерминирован: одни и те же координаты всегда возвращают одно значение, поэтому анимируй, двигая точку сэмпла (напр. используй время как одну ось).

TypeScript
const noise = new Noise()

noise.get(x, y)          // сэмпл 2D-шума
noise.get3d(x, y, z)     // сэмпл 3D-шума

noise.frequency = 0.01           // get/set; по умолчанию 0.01 — пространственный масштаб (выше = мельче)
noise.octaves = 4                // get/set; по умолчанию 1 — фрактальные слои (>1 добавляет детали)
noise.fractalLacunarity = 2      // get/set; по умолчанию 2 — шаг частоты между октавами
noise.fractalGain = 0.5          // get/set; по умолчанию 0.5 — шаг амплитуды между октавами
TypeScript
// мягкое покачивание и качание в покое
const n = new Noise()
n.frequency = 0.5
let t = 0
setLoop(dt => {
  t += dt
  ghost.y = 1 + n.get(t, 0) * 0.2
  ghost.eulerAngles = [0, n.get(t, 100) * 15, 0]
})

Подводные камни

TypeScript
// ✗ трактовать результат intersectRay как точку попадания
marker.position = ground.intersectRay(ray)      // это скаляр t
// ✓
const t = ground.intersectRay(ray)
if (t !== null) marker.position = ray.getPoint(t)

// ✗ ждать, что плоскость заблокирует Physics.raycast — Plane это математика, не тело
// ✓ дай полу Shape (+ Physics { motion: 'static' }), чтобы сделать его кликабельным/твёрдым

Смотрите также