holodepth

Framework entegrasyonları · Angular + Three

Pointer Bağları & Picking: Host'tan İsabet'e

Angular'da tıklama çoğu zaman canvas HostListener ile girer; hangi mesh'in seçildiği ise ışın isabetidir. Bu sayfa DOM bağını Three picking'e bağlar: R3F onClick prop yüzeyinin kopyası değildir.

Holodepth bu yaprağı DOM events 101 veya WebXR uzamsal UI olarak yazmaz. R3F isabet yüzeyi Pointer olayları & picking yaprağındadır. Raycast matematik Three etkileşim omurgasındadır. Host tuvali Host & kök ; Zone tik Kare döngüsü & NgZone yapraklarındadır. HTML Canvas pointer Pointer sistemi ile akrabadır. glTF clickable metadata bu kapının konusu değildir.

Sayfayı bitirdiğinizde şu cümleyi kurabilmelisiniz: «Pointer canvas host'tan girer. NDC + Raycaster isabet üretir. Dinleme Zone dışında, seçim output'u seyrek Zone içinde olabilir. Overlay CSS hit'i yutabilir. Her rAF pick yoktur. Sonraki kapı Zone kaçışı & performanstır.»

Bu sayfanın sınırı · komşu konularla ayrım

Bu sayfa Angular + Three'de pointer bağları / picking köprüsünü anlatır:

  • HostListener giriş · NDC haritası
  • Raycaster servis · Output seçim
  • Zone frekansı · overlay CSS
  • Üç etkileşim profili

Bilinçli olarak dışarıda bırakılan konular:

Kısa ayrım: Bu sayfa «Angular host'tan isabet nasıl bağlanır?» · R3F picking «Fiber prop yüzeyi» · Three raycast «ışın matematiği» · NgZone «dinleme / tik frekansı».

Giriş kapısı: HostListener

R3F'de isabet mesh prop'una yazılır. Angular + klasik Three'de olay çoğu zaman canvas (veya host div) üzerinde @HostListener('pointerdown') / pointermove / template (click) ile yakalanır. Holodepth: tek giriş kapısı seçin; hem window hem canvas hem child div'e dağınık bağlamak teşhisi öldürür.

angular-three benzeri kataloglar mesh düzeyinde event soyutlayabilir; altta yine host koordinatı + raycast vardır. Soyutlamayı sihir saymayın.

Olay DOM'dan girer; isabet grafıtan çıkar.

İstemci px → NDC

clientX/Y canvas getBoundingClientRect ile normalize edilip [-1, 1] NDC'ye çevrilir; Raycaster setFromCamera bunu yer. Holodepth: CSS scale / letterbox'ta rect güncel olmalı; stale rect sapmış isabet üretir. Formül Three omurgasında derinleşir; burada köprü: host ölçüsü yanlışsa pick her zaman yanlışdır.

Raycaster sahipliği

Her click'te new THREE.Raycaster() allocate etmek gereksizdir. Holodepth: host veya injectable bir picker servisinde tek Raycaster + yeniden kullanılan Vector2. intersectsObject / layers listesini dar tutun; tüm scene traverse pahalıdır.

Aday kümesi · daraltın
Küme Ne zaman? Risk
Yalnız seçilebilir mesh'ler Ürün vitrini Liste güncel mi?
Group recursive Montaj Süs çocuklar isabet çalar
Tüm scene Debug Kalabalıkta pahalı

Zone frekansı

Pointermove selini Zone içinde dinlemek CD yangınıdır. Holodepth: dinlemeyi OutsideAngular'da tutun (veya passive host); seçim id değişince seyrek zone.run / Output emit. Her move'da şablon hover rengi için Angular state yağmurundan kaçının; materyal rengi Object3D tarafında da güncellenebilir.

NgZone yaprağı genel tik politikasını mühürledi; burada yalnız pointer şeridinin aynı diplomasiye uyduğunu kilitleyoruz.

Output: seçimi dışarı ver

İsabet sonucu host içinde kalmamalı; üst özellik @Output() partSelected (veya output()) ile id / name / userData taşır. Holodepth: yetkiyi yalnız client pick id'sine bağlamayın; sunucu doğrulaması gereken eylem pick'i sinyal kabul eder, karar vermez.

Missed (boş tıklama): seçimi temizleyen ayrı çıktı veya null emit. R3F onPointerMissed ile aynı ürün ihtiyacı, farklı API.

Occluder ve süs mesh

Öndeki şeffaf / görünmez collider arkadaki butonu çalar. raycast = () => {} veya aday listesinden çıkarma politikası yazın. visible=false her sürüme/ayıra göre raycast'ı kapatmayabilir; bilinçli katılım şarttır.

DOM overlay diplomasi

Angular template'te canvas üstü HTML toolbar sık görülür. pointer-events: none / hedef düğmede auto bölgeleri net yazılmazsa ya 3D ölür ya toolbar tıklanmaz. Host kök sayfasındaki kutu buranın zeminidir.

Overlay hit yerse, Raycaster'a iş kalmaz.

Her rAF pick yok

Sürekli raycast drag/snap istisnasında seyreltilir. Varsayılan: pointer olayında pick. Move selini throttle edin. Bu, Zone kaçışı yaprağındaki vergi eşiğinin etkileşim kuzenidir.

Güvenli bağ: simüle pick

Örnekler öğretim amaçlıdır. Kamera/scene host'tan gelir. Angular event temeli öğretilmez.

Simüle · HostListener + Output.

import {
  Component, ElementRef, EventEmitter, HostListener, Input, NgZone, Output, ViewChild,
} from '@angular/core'
import * as THREE from 'three'

@Component({
  selector: 'app-pick-host',
  template: `<canvas #glCanvas class="pick-canvas"></canvas>`,
  styles: [`.pick-canvas { display:block; width:100%; height:100%; }`],
})
export class PickHostComponent {
  @ViewChild('glCanvas', { static: true })
  private canvasRef!: ElementRef<HTMLCanvasElement>

  @Input({ required: true }) camera!: THREE.Camera
  /** Yalnız seçilebilir kökler · tüm scene değil */
  @Input() pickRoots: THREE.Object3D[] = []

  @Output() partSelected = new EventEmitter<string | null>()

  private readonly raycaster = new THREE.Raycaster()
  private readonly pointer = new THREE.Vector2()

  constructor(private readonly zone: NgZone) {}

  @HostListener('click', ['$event'])
  onClick(ev: MouseEvent): void {
    // Eğitim: dinleme host canvas'ta. Zone baskısını azaltmak için
    // listen Outside'da da kurulabilir; emit seyrek Zone içinde.
    const hit = this.pick(ev.clientX, ev.clientY)
    this.zone.run(() => this.partSelected.emit(hit))
  }

  private pick(clientX: number, clientY: number): string | null {
    const canvas = this.canvasRef.nativeElement
    const rect = canvas.getBoundingClientRect()
    if (rect.width < 1 || rect.height < 1) return null

    this.pointer.x = ((clientX - rect.left) / rect.width) * 2 - 1
    this.pointer.y = -((clientY - rect.top) / rect.height) * 2 + 1
    this.raycaster.setFromCamera(this.pointer, this.camera)

    const hits = this.raycaster.intersectObjects(this.pickRoots, true)
    if (!hits.length) return null
    return hits[0].object.name || hits[0].object.uuid
  }
}

Simüle · throttle'lu hover (Outside).

import { NgZone } from '@angular/core'

/** pointermove yağmurunu ~50ms'ye indir · Angular state yok */
export function attachThrottledPointerMove(
  zone: NgZone,
  el: HTMLElement,
  onMove: (x: number, y: number) => void,
): () => void {
  let last = 0
  let raf = 0
  const handler = (ev: PointerEvent) => {
    const now = performance.now()
    if (now - last < 50) return
    last = now
    const x = ev.clientX
    const y = ev.clientY
    zone.runOutsideAngular(() => {
      cancelAnimationFrame(raf)
      raf = requestAnimationFrame(() => onMove(x, y))
    })
  }
  el.addEventListener('pointermove', handler, { passive: true })
  return () => {
    el.removeEventListener('pointermove', handler)
    cancelAnimationFrame(raf)
  }
}

Güvenlik: pick id ile yetki açmayın. cursor'u document.body'de unutmayın. Dinleyiciyi OnDestroy'da çıkarın (yükleme yaprağındaki ömür ruhu).

Picking tuzakları

  1. Stale rect: resize sonrası NDC sapması.
  2. Tüm scene pick: süs isabeti + maliyet.
  3. Zone içi move: CD yangını.
  4. Overlay yutması: CSS pointer-events.
  5. rAF pick: gereksiz sürekli test.
  6. Missed yok: seçim temizlenmez.
  7. R3F onClick ezberi: mesh prop aramak.

Üç etkileşim profili

  • 01 · Bağ

    Vitrin seçimi

    Giriş
    click HostListener
    Aday
    Dar pickRoots
    Risk
    Occluder
  • 02 · Bağ

    Hover önizleme

    Move
    Throttle + Outside
    Görsel
    Materyal / emissive
    Risk
    Her move Output
  • 03 · Bağ

    DOM + Canvas

    CSS
    pointer-events bölgeleri
    HUD
    Angular template
    Risk
    Toolbar'ın tüm hit'i yutması

Erişilebilirlik notu

Kritik seçimleri yalnız pointer'a kilitlemeyin. Holodepth: eşdeğer Angular list / klavye kontrolü sunun. Canvas'a anlamlı aria-label düşünün.

Holodepth perspektifleri

Tek giriş kapısı

Dağınık listener teşhisi öldürür.

Adayı daraltın

pickRoots ürün listesidir; scene.dump değildir.

Frekansı ayırın

Dinleme Outside; UI emit seyrek.

Teşhis maddeleri

  1. HostListener / bind doğru elemanda mı?
  2. getBoundingClientRect güncel mi?
  3. pickRoots dar mı?
  4. Zone içi move yağmuru var mı?
  5. Missed / null emit var mı?
  6. Overlay pointer-events yazılı mı?
  7. rAF pick varsayılan mı olmuş?
  8. R3F prop veya glTF bu sayfaya sızmış mı?

Holodepth içgörüsü

«Tıklamıyor» çoğu zaman raycast formülü değil; rect, overlay, aday kümesi veya Zone selidir. Önce host bağını okuyun.

Sonraki kapı: Zone kaçışı

Etkileşim ve yük oturunca kalan soru: ne zaman daha agresif Zone / imperatif ada açılır? Sonraki sayfa Zone kaçışı & performans bu eşiği anlatır. Angular + Three izleğinin son yaprağıdır.