|
| 1 | +import { NgIf, NgTemplateOutlet } from '@angular/common'; |
| 2 | +import { CUSTOM_ELEMENTS_SCHEMA, Component, ContentChild, Directive, Input, TemplateRef, effect } from '@angular/core'; |
| 3 | +import { extend, injectNgtRef, signalStore, type NgtAnyRecord, type NgtLineSegments } from 'angular-three'; |
| 4 | +import * as THREE from 'three'; |
| 5 | +import { LineBasicMaterial, LineSegments } from 'three'; |
| 6 | + |
| 7 | +export type NgtsEdgesState = { |
| 8 | + threshold: number; |
| 9 | + color: THREE.ColorRepresentation; |
| 10 | + geometry: THREE.BufferGeometry; |
| 11 | + userData: NgtAnyRecord; |
| 12 | +}; |
| 13 | + |
| 14 | +declare global { |
| 15 | + interface HTMLElementTagNameMap { |
| 16 | + /** |
| 17 | + * @extends ngt-line-segments |
| 18 | + */ |
| 19 | + 'ngts-edges': NgtsEdgesState & NgtLineSegments; |
| 20 | + } |
| 21 | +} |
| 22 | + |
| 23 | +extend({ LineSegments, LineBasicMaterial }); |
| 24 | + |
| 25 | +@Directive({ selector: 'ng-template[ngtsEdgesContent]', standalone: true }) |
| 26 | +export class NgtsEdgesContent {} |
| 27 | + |
| 28 | +@Component({ |
| 29 | + selector: 'ngts-edges', |
| 30 | + standalone: true, |
| 31 | + template: ` |
| 32 | + <ngt-line-segments [ref]="edgesRef" [raycast]="nullRaycast" ngtCompound> |
| 33 | + <ng-container *ngIf="content; else defaultMaterial" [ngTemplateOutlet]="content" /> |
| 34 | + <ng-template #defaultMaterial> |
| 35 | + <ngt-line-basic-material [color]="color()" /> |
| 36 | + </ng-template> |
| 37 | + </ngt-line-segments> |
| 38 | + `, |
| 39 | + imports: [NgIf, NgTemplateOutlet], |
| 40 | + schemas: [CUSTOM_ELEMENTS_SCHEMA], |
| 41 | +}) |
| 42 | +export class NgtsEdges { |
| 43 | + nullRaycast = () => null; |
| 44 | + |
| 45 | + @ContentChild(NgtsEdgesContent, { read: TemplateRef }) content?: TemplateRef<unknown>; |
| 46 | + |
| 47 | + private inputs = signalStore<NgtsEdgesState>({ threshold: 15, color: 'black' }); |
| 48 | + |
| 49 | + @Input() edgesRef = injectNgtRef<LineSegments>(); |
| 50 | + |
| 51 | + @Input({ alias: 'threshold' }) set _threshold(threshold: number) { |
| 52 | + this.inputs.set({ threshold }); |
| 53 | + } |
| 54 | + |
| 55 | + @Input({ alias: 'color' }) set _color(color: THREE.ColorRepresentation) { |
| 56 | + this.inputs.set({ color }); |
| 57 | + } |
| 58 | + |
| 59 | + @Input({ alias: 'geometry' }) set _geometry(geometry: THREE.BufferGeometry) { |
| 60 | + this.inputs.set({ geometry }); |
| 61 | + } |
| 62 | + |
| 63 | + @Input({ alias: 'userData' }) set _userData(userData: NgtAnyRecord) { |
| 64 | + this.inputs.set({ userData }); |
| 65 | + } |
| 66 | + |
| 67 | + private geometry = this.inputs.select('geometry'); |
| 68 | + private threshold = this.inputs.select('threshold'); |
| 69 | + |
| 70 | + color = this.inputs.select('color'); |
| 71 | + |
| 72 | + constructor() { |
| 73 | + effect(() => { |
| 74 | + const edges = this.edgesRef.nativeElement; |
| 75 | + if (!edges) return; |
| 76 | + const parent = this.edgesRef.nativeElement.parent as THREE.Mesh; |
| 77 | + if (parent) { |
| 78 | + const [geometry, threshold] = [this.geometry() || parent.geometry, this.threshold()]; |
| 79 | + if (geometry !== edges.userData['currentGeom'] || threshold !== edges.userData['currentThreshold']) { |
| 80 | + edges.userData['currentGeom'] = geometry; |
| 81 | + edges.userData['currentThreshold'] = threshold; |
| 82 | + edges.geometry = new THREE.EdgesGeometry(geometry, threshold); |
| 83 | + } |
| 84 | + } |
| 85 | + }); |
| 86 | + } |
| 87 | +} |
0 commit comments