|
| 1 | +import { NgIf } from '@angular/common'; |
| 2 | +import { Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, inject, ViewChild } from '@angular/core'; |
| 3 | +import { |
| 4 | + extend, |
| 5 | + injectNgtLoader, |
| 6 | + NgtArgs, |
| 7 | + NgtBeforeRenderEvent, |
| 8 | + NgtCanvas, |
| 9 | + NgtPush, |
| 10 | + NgtState, |
| 11 | + NgtStore, |
| 12 | +} from 'angular-three'; |
| 13 | +import { map } from 'rxjs'; |
| 14 | +import * as THREE from 'three'; |
| 15 | +import { CCDIKHelper, DRACOLoader, GLTFLoader, TransformControls } from 'three-stdlib'; |
| 16 | +import { DemoOrbitControls } from '../ui-orbit-controls/orbit-controls.component'; |
| 17 | +import { AnimationSkinningIKStore } from './animation-skinning-ik.store'; |
| 18 | + |
| 19 | +extend({ TransformControls, CCDIKHelper }); |
| 20 | + |
| 21 | +@Component({ |
| 22 | + selector: 'demo-ik-helper', |
| 23 | + standalone: true, |
| 24 | + template: ` |
| 25 | + <ng-container *ngIf="(ooi$ | ngtPush).kira as kira"> |
| 26 | + <ngt-cCDIK-helper *args="[kira, iks, 0.01]" (beforeRender)="onBeforeRender()" /> |
| 27 | + </ng-container> |
| 28 | + `, |
| 29 | + imports: [NgtArgs, NgIf, NgtPush], |
| 30 | + schemas: [CUSTOM_ELEMENTS_SCHEMA], |
| 31 | +}) |
| 32 | +export class IKHelper { |
| 33 | + private readonly ikStore = inject(AnimationSkinningIKStore); |
| 34 | + readonly iks = this.ikStore.iks; |
| 35 | + readonly ooi$ = this.ikStore.select('OOI'); |
| 36 | + |
| 37 | + onBeforeRender() { |
| 38 | + this.ikStore.solver.update(); |
| 39 | + } |
| 40 | +} |
| 41 | + |
| 42 | +@Component({ |
| 43 | + selector: 'demo-sphere-camera', |
| 44 | + standalone: true, |
| 45 | + template: ` |
| 46 | + <ngt-cube-camera *args="[0.05, 50, cubeRenderTarget]" (beforeRender)="onBeforeRender($any($event))" /> |
| 47 | + `, |
| 48 | + imports: [NgtArgs], |
| 49 | + schemas: [CUSTOM_ELEMENTS_SCHEMA], |
| 50 | +}) |
| 51 | +export class SphereCamera { |
| 52 | + private readonly ikStore = inject(AnimationSkinningIKStore); |
| 53 | + readonly cubeRenderTarget = this.ikStore.get('cubeRenderTarget'); |
| 54 | + |
| 55 | + onBeforeRender({ object, state: { gl, scene } }: NgtBeforeRenderEvent<THREE.CubeCamera>) { |
| 56 | + const sphere = this.ikStore.OOI.sphere; |
| 57 | + if (sphere) { |
| 58 | + sphere.visible = false; |
| 59 | + sphere.getWorldPosition(object.position); |
| 60 | + object.update(gl, scene); |
| 61 | + sphere.visible = true; |
| 62 | + } |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +@Component({ |
| 67 | + selector: 'demo-kira', |
| 68 | + standalone: true, |
| 69 | + template: ` |
| 70 | + <ngt-primitive |
| 71 | + *args="[model$ | ngtPush : null]" |
| 72 | + (afterAttach)="onAfterAttach()" |
| 73 | + (beforeRender)="onBeforeRender()" |
| 74 | + /> |
| 75 | + `, |
| 76 | + imports: [NgtArgs, NgtPush], |
| 77 | + schemas: [CUSTOM_ELEMENTS_SCHEMA], |
| 78 | +}) |
| 79 | +export class Kira { |
| 80 | + private readonly ikStore = inject(AnimationSkinningIKStore); |
| 81 | + readonly v0 = new THREE.Vector3(); |
| 82 | + |
| 83 | + readonly model$ = injectNgtLoader( |
| 84 | + () => GLTFLoader, |
| 85 | + 'assets/kira.glb', |
| 86 | + (loader) => { |
| 87 | + const dracoLoader = new DRACOLoader(); |
| 88 | + dracoLoader.setDecoderPath('assets/draco/'); |
| 89 | + (loader as GLTFLoader).setDRACOLoader(dracoLoader); |
| 90 | + } |
| 91 | + ).pipe( |
| 92 | + map((gltf) => { |
| 93 | + const ooi: Record<string, THREE.Object3D> = {}; |
| 94 | + gltf.scene.traverse((n) => { |
| 95 | + if (n.name === 'head') ooi['head'] = n; |
| 96 | + if (n.name === 'lowerarm_l') ooi['lowerarm_l'] = n; |
| 97 | + if (n.name === 'Upperarm_l') ooi['Upperarm_l'] = n; |
| 98 | + if (n.name === 'hand_l') ooi['hand_l'] = n; |
| 99 | + if (n.name === 'target_hand_l') ooi['target_hand_l'] = n; |
| 100 | + if (n.name === 'boule') ooi['sphere'] = n; |
| 101 | + if (n.name === 'Kira_Shirt_left') ooi['kira'] = n; |
| 102 | + if ((n as THREE.Mesh).isMesh) n.frustumCulled = false; |
| 103 | + }); |
| 104 | + this.ikStore.set({ OOI: ooi }); |
| 105 | + return gltf.scene; |
| 106 | + }) |
| 107 | + ); |
| 108 | + |
| 109 | + onAfterAttach() { |
| 110 | + this.ikStore.kiraReady(); |
| 111 | + } |
| 112 | + |
| 113 | + onBeforeRender() { |
| 114 | + const head = this.ikStore.OOI.head; |
| 115 | + const sphere = this.ikStore.OOI.sphere; |
| 116 | + if (head && sphere) { |
| 117 | + sphere.getWorldPosition(this.v0); |
| 118 | + head.lookAt(this.v0); |
| 119 | + head.rotation.set(head.rotation.x, head.rotation.y + Math.PI, head.rotation.z); |
| 120 | + } |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +@Component({ |
| 125 | + standalone: true, |
| 126 | + template: ` |
| 127 | + <ngt-color *args="['#dddddd']" attach="background" /> |
| 128 | + <ngt-fog-exp2 *args="['#ffffff', 0.17]" attach="fog" /> |
| 129 | +
|
| 130 | + <ngt-ambient-light [intensity]="8" color="#ffffff" /> |
| 131 | +
|
| 132 | + <demo-kira /> |
| 133 | + <demo-sphere-camera /> |
| 134 | + <demo-ik-helper /> |
| 135 | +
|
| 136 | + <demo-orbit-controls |
| 137 | + [minDistance]="0.2" |
| 138 | + [maxDistance]="1.5" |
| 139 | + (ready)="ikStore.set({ orbitControls: $any($event) })" |
| 140 | + /> |
| 141 | +
|
| 142 | + <ngt-transform-controls |
| 143 | + #transformControls |
| 144 | + *args="[camera, glDom]" |
| 145 | + [size]="0.75" |
| 146 | + [showX]="false" |
| 147 | + space="world" |
| 148 | + /> |
| 149 | + `, |
| 150 | + imports: [NgtArgs, DemoOrbitControls, Kira, SphereCamera, IKHelper], |
| 151 | + schemas: [CUSTOM_ELEMENTS_SCHEMA], |
| 152 | +}) |
| 153 | +export class Scene { |
| 154 | + readonly ikStore = inject(AnimationSkinningIKStore); |
| 155 | + private readonly store = inject(NgtStore); |
| 156 | + readonly camera = this.store.get('camera'); |
| 157 | + readonly glDom = this.store.get('gl', 'domElement'); |
| 158 | + |
| 159 | + @ViewChild('transformControls') set transformControls({ nativeElement }: ElementRef<TransformControls>) { |
| 160 | + this.ikStore.set({ transformControls: nativeElement }); |
| 161 | + nativeElement.addEventListener('mouseDown', () => (this.ikStore.orbitControls.enabled = false)); |
| 162 | + nativeElement.addEventListener('mouseUp', () => (this.ikStore.orbitControls.enabled = true)); |
| 163 | + } |
| 164 | +} |
| 165 | + |
| 166 | +@Component({ |
| 167 | + standalone: true, |
| 168 | + template: ` |
| 169 | + <ngt-canvas |
| 170 | + [sceneGraph]="SceneGraph" |
| 171 | + [camera]="{ |
| 172 | + fov: 55, |
| 173 | + near: 0.001, |
| 174 | + far: 5000, |
| 175 | + position: [0.9728517749133652, 1.1044765132727201, 0.7316689528482836] |
| 176 | + }" |
| 177 | + [gl]="{ logarithmicDepthBuffer: true }" |
| 178 | + (created)="onCreated($event)" |
| 179 | + /> |
| 180 | + `, |
| 181 | + providers: [AnimationSkinningIKStore], |
| 182 | + imports: [NgtCanvas], |
| 183 | +}) |
| 184 | +export default class DemoAnimationSkinningIK { |
| 185 | + readonly SceneGraph = Scene; |
| 186 | + |
| 187 | + onCreated({ scene, camera, gl }: NgtState) { |
| 188 | + camera.lookAt(scene.position); |
| 189 | + gl.physicallyCorrectLights = true; |
| 190 | + } |
| 191 | +} |
0 commit comments