|
| 1 | +import { |
| 2 | + ChangeDetectionStrategy, |
| 3 | + Component, |
| 4 | + computed, |
| 5 | + CUSTOM_ELEMENTS_SCHEMA, |
| 6 | + effect, |
| 7 | + input, |
| 8 | + output, |
| 9 | +} from '@angular/core'; |
| 10 | +import { |
| 11 | + injectBeforeRender, |
| 12 | + injectStore, |
| 13 | + NgtArgs, |
| 14 | + NgtOverwrite, |
| 15 | + NgtThreeElement, |
| 16 | + NgtVector3, |
| 17 | + omit, |
| 18 | + pick, |
| 19 | +} from 'angular-three'; |
| 20 | +import { mergeInputs } from 'ngxtension/inject-inputs'; |
| 21 | +import * as THREE from 'three'; |
| 22 | +import { TrackballControls } from 'three-stdlib'; |
| 23 | + |
| 24 | +export type NgtsTrackballControlsOptions = Omit< |
| 25 | + NgtOverwrite< |
| 26 | + NgtThreeElement<typeof TrackballControls>, |
| 27 | + { target?: NgtVector3; camera?: THREE.Camera; domElement?: HTMLElement; regress: boolean; makeDefault: boolean } |
| 28 | + >, |
| 29 | + 'attach' | 'addEventListener' | 'removeEventListener' | 'parameters' | '___ngt_args__' |
| 30 | +>; |
| 31 | + |
| 32 | +const defaultOptions: NgtsTrackballControlsOptions = { |
| 33 | + regress: false, |
| 34 | + makeDefault: false, |
| 35 | +}; |
| 36 | + |
| 37 | +@Component({ |
| 38 | + selector: 'ngts-trackball-controls', |
| 39 | + template: ` |
| 40 | + <ngt-primitive *args="[controls()]" [parameters]="parameters()"> |
| 41 | + <ng-content /> |
| 42 | + </ngt-primitive> |
| 43 | + `, |
| 44 | + schemas: [CUSTOM_ELEMENTS_SCHEMA], |
| 45 | + changeDetection: ChangeDetectionStrategy.OnPush, |
| 46 | + imports: [NgtArgs], |
| 47 | +}) |
| 48 | +export class NgtsTrackballControls { |
| 49 | + options = input(defaultOptions, { transform: mergeInputs(defaultOptions) }); |
| 50 | + protected parameters = omit(this.options, ['makeDefault', 'camera', 'regress', 'domElement']); |
| 51 | + |
| 52 | + changed = output<THREE.Event>(); |
| 53 | + started = output<THREE.Event>(); |
| 54 | + ended = output<THREE.Event>(); |
| 55 | + |
| 56 | + private store = injectStore(); |
| 57 | + |
| 58 | + private camera = pick(this.options, 'camera'); |
| 59 | + private regress = pick(this.options, 'regress'); |
| 60 | + private domElement = pick(this.options, 'domElement'); |
| 61 | + private makeDefault = pick(this.options, 'makeDefault'); |
| 62 | + |
| 63 | + protected controls = computed(() => { |
| 64 | + const camera = this.camera(); |
| 65 | + if (camera) return new TrackballControls(camera as THREE.PerspectiveCamera); |
| 66 | + return new TrackballControls(this.store.camera()); |
| 67 | + }); |
| 68 | + |
| 69 | + constructor() { |
| 70 | + injectBeforeRender( |
| 71 | + () => { |
| 72 | + const controls = this.controls(); |
| 73 | + if (controls.enabled) controls.update(); |
| 74 | + }, |
| 75 | + { priority: -1 }, |
| 76 | + ); |
| 77 | + |
| 78 | + effect((onCleanup) => { |
| 79 | + const makeDefault = this.makeDefault(); |
| 80 | + if (!makeDefault) return; |
| 81 | + |
| 82 | + const controls = this.controls(); |
| 83 | + const oldControls = this.store.snapshot.controls; |
| 84 | + this.store.update({ controls }); |
| 85 | + onCleanup(() => void this.store.update({ controls: oldControls })); |
| 86 | + }); |
| 87 | + |
| 88 | + effect((onCleanup) => { |
| 89 | + const [controls, domElement] = [ |
| 90 | + this.controls(), |
| 91 | + this.domElement() || this.store.snapshot.events.connected || this.store.gl.domElement(), |
| 92 | + this.store.invalidate(), |
| 93 | + this.regress(), |
| 94 | + ]; |
| 95 | + controls.connect(domElement); |
| 96 | + onCleanup(() => void controls.dispose()); |
| 97 | + }); |
| 98 | + |
| 99 | + effect((onCleanup) => { |
| 100 | + const [controls, invalidate, performanceRegress, regress] = [ |
| 101 | + this.controls(), |
| 102 | + this.store.invalidate(), |
| 103 | + this.store.performance.regress(), |
| 104 | + this.regress(), |
| 105 | + ]; |
| 106 | + |
| 107 | + const changeCallback: (e: THREE.Event) => void = (e) => { |
| 108 | + invalidate(); |
| 109 | + if (regress) performanceRegress(); |
| 110 | + this.changed.emit(e); |
| 111 | + }; |
| 112 | + |
| 113 | + const startCallback = this.started.emit.bind(this.started); |
| 114 | + const endCallback = this.ended.emit.bind(this.ended); |
| 115 | + |
| 116 | + controls.addEventListener('change', changeCallback); |
| 117 | + controls.addEventListener('start', startCallback); |
| 118 | + controls.addEventListener('end', endCallback); |
| 119 | + |
| 120 | + onCleanup(() => { |
| 121 | + controls.removeEventListener('change', changeCallback); |
| 122 | + controls.removeEventListener('start', startCallback); |
| 123 | + controls.removeEventListener('end', endCallback); |
| 124 | + }); |
| 125 | + }); |
| 126 | + |
| 127 | + effect(() => { |
| 128 | + const [controls] = [this.controls(), this.store.viewport()]; |
| 129 | + controls.handleResize(); |
| 130 | + }); |
| 131 | + } |
| 132 | +} |
0 commit comments