Skip to content

Commit 85d089a

Browse files
committed
docs: clean up skinning ik
1 parent eb59ea6 commit 85d089a

File tree

2 files changed

+108
-194
lines changed

2 files changed

+108
-194
lines changed

apps/demo/src/app/animation-skinning-ik/animation-skinning-ik.component.ts

Lines changed: 108 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,90 @@
11
import { NgIf } from '@angular/common';
2-
import { Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, inject, ViewChild } from '@angular/core';
2+
import { ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, inject, ViewChild } from '@angular/core';
33
import {
4+
applyProps,
45
extend,
6+
injectBeforeRender,
57
injectNgtLoader,
68
NgtArgs,
7-
NgtBeforeRenderEvent,
89
NgtCanvas,
910
NgtPush,
1011
NgtState,
1112
NgtStore,
1213
} from 'angular-three';
1314
import { map } from 'rxjs';
1415
import * as THREE from 'three';
15-
import { CCDIKHelper, DRACOLoader, GLTFLoader, TransformControls } from 'three-stdlib';
16+
import { CCDIKHelper, CCDIKSolver, DRACOLoader, GLTFLoader, IKS, OrbitControls, TransformControls } from 'three-stdlib';
1617
import { DemoOrbitControls } from '../ui-orbit-controls/orbit-controls.component';
17-
import { AnimationSkinningIKStore } from './animation-skinning-ik.store';
1818

1919
extend({ TransformControls, CCDIKHelper });
2020

2121
@Component({
22-
selector: 'demo-ik-helper',
2322
standalone: true,
2423
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');
24+
<ngt-color *args="['#dddddd']" attach="background" />
25+
<ngt-fog-exp2 *args="['#ffffff', 0.17]" attach="fog" />
3626
37-
onBeforeRender() {
38-
this.ikStore.solver.update();
39-
}
40-
}
27+
<ngt-ambient-light [intensity]="8" color="#ffffff" />
4128
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-
}
29+
<ngt-primitive *args="[model$ | ngtPush : null]" (afterAttach)="onAfterModelAttach()" />
6530
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()"
31+
<ngt-cube-camera #cubeCamera *args="[0.05, 50, cubeRenderTarget]" />
32+
33+
<ng-container *ngIf="ooi['kira']">
34+
<ngt-cCDIK-helper *args="[ooi['kira'], iks, 0.01]" />
35+
</ng-container>
36+
37+
<demo-orbit-controls [minDistance]="0.2" [maxDistance]="1.5" (ready)="orbitControls = $any($event)" />
38+
39+
<ngt-transform-controls
40+
#transformControls
41+
*args="[camera, glDom]"
42+
[size]="0.75"
43+
[showX]="false"
44+
space="world"
7445
/>
7546
`,
76-
imports: [NgtArgs, NgtPush],
47+
imports: [NgtArgs, NgIf, DemoOrbitControls, NgtPush],
7748
schemas: [CUSTOM_ELEMENTS_SCHEMA],
7849
})
79-
export class Kira {
80-
private readonly ikStore = inject(AnimationSkinningIKStore);
81-
readonly v0 = new THREE.Vector3();
50+
export class Scene {
51+
readonly iks = [
52+
{
53+
target: 22, // "target_hand_l"
54+
effector: 6, // "hand_l"
55+
links: [
56+
{
57+
index: 5, // "lowerarm_l"
58+
enabled: true,
59+
rotationMin: new THREE.Vector3(1.2, -1.8, -0.4),
60+
rotationMax: new THREE.Vector3(1.7, -1.1, 0.3),
61+
},
62+
{
63+
index: 4, // "Upperarm_l"
64+
enabled: true,
65+
rotationMin: new THREE.Vector3(0.1, -0.7, -1.8),
66+
rotationMax: new THREE.Vector3(1.1, 0, -1.4),
67+
},
68+
],
69+
},
70+
];
71+
72+
readonly cubeRenderTarget = new THREE.WebGLCubeRenderTarget(1024);
73+
readonly material = new THREE.MeshBasicMaterial({ envMap: this.cubeRenderTarget.texture });
74+
75+
private readonly store = inject(NgtStore);
76+
readonly camera = this.store.get('camera');
77+
readonly glDom = this.store.get('gl', 'domElement');
78+
79+
private readonly cdr = inject(ChangeDetectorRef);
80+
81+
@ViewChild('transformControls') transformControls?: ElementRef<TransformControls>;
82+
@ViewChild('cubeCamera') cubeCamera?: ElementRef<THREE.CubeCamera>;
83+
orbitControls?: OrbitControls;
84+
solver?: CCDIKSolver;
85+
86+
private readonly v0 = new THREE.Vector3();
87+
readonly ooi: Record<string, THREE.Object3D> = {};
8288

8389
readonly model$ = injectNgtLoader(
8490
() => GLTFLoader,
@@ -90,79 +96,66 @@ export class Kira {
9096
}
9197
).pipe(
9298
map((gltf) => {
93-
const ooi: Record<string, THREE.Object3D> = {};
9499
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;
100+
if (n.name === 'head') this.ooi['head'] = n;
101+
if (n.name === 'lowerarm_l') this.ooi['lowerarm_l'] = n;
102+
if (n.name === 'Upperarm_l') this.ooi['Upperarm_l'] = n;
103+
if (n.name === 'hand_l') this.ooi['hand_l'] = n;
104+
if (n.name === 'target_hand_l') this.ooi['target_hand_l'] = n;
105+
if (n.name === 'boule') this.ooi['sphere'] = n;
106+
if (n.name === 'Kira_Shirt_left') this.ooi['kira'] = n;
102107
if ((n as THREE.Mesh).isMesh) n.frustumCulled = false;
103108
});
104-
this.ikStore.set({ OOI: ooi });
109+
this.cdr.detectChanges();
105110
return gltf.scene;
106111
})
107112
);
108113

109-
onAfterAttach() {
110-
this.ikStore.kiraReady();
114+
constructor() {
115+
injectBeforeRender(({ gl, scene }) => {
116+
if (this.ooi['sphere'] && this.cubeCamera) {
117+
this.ooi['sphere'].visible = false;
118+
this.ooi['sphere'].getWorldPosition(this.cubeCamera.nativeElement.position);
119+
this.cubeCamera.nativeElement.update(gl, scene);
120+
this.ooi['sphere'].visible = true;
121+
}
122+
123+
if (this.solver) {
124+
this.solver.update();
125+
}
126+
127+
const head = this.ooi['head'];
128+
const sphere = this.ooi['sphere'];
129+
if (head && sphere) {
130+
sphere.getWorldPosition(this.v0);
131+
head.lookAt(this.v0);
132+
head.rotation.set(head.rotation.x, head.rotation.y + Math.PI, head.rotation.z);
133+
}
134+
});
111135
}
112136

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);
137+
onAfterModelAttach() {
138+
this.orbitControls?.target.copy(this.ooi['sphere'].position);
139+
this.ooi['hand_l'].attach(this.ooi['sphere']);
140+
applyProps(this.ooi['sphere'], { material: this.material });
141+
142+
this.transformControls?.nativeElement.attach(this.ooi['target_hand_l']);
143+
this.ooi['kira'].add((this.ooi['kira'] as THREE.SkinnedMesh).skeleton.bones[0]);
144+
this.solver = new CCDIKSolver(this.ooi['kira'] as THREE.SkinnedMesh, this.iks as unknown as IKS[]);
145+
146+
if (this.transformControls && this.orbitControls) {
147+
this.transformControls.nativeElement.addEventListener(
148+
'mouseDown',
149+
() => (this.orbitControls!.enabled = false)
150+
);
151+
this.transformControls.nativeElement.addEventListener(
152+
'mouseUp',
153+
() => (this.orbitControls!.enabled = true)
154+
);
120155
}
121156
}
122157
}
123158

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-
166159
@Component({
167160
standalone: true,
168161
template: `
@@ -178,7 +171,6 @@ export class Scene {
178171
(created)="onCreated($event)"
179172
/>
180173
`,
181-
providers: [AnimationSkinningIKStore],
182174
imports: [NgtCanvas],
183175
})
184176
export default class DemoAnimationSkinningIK {

apps/demo/src/app/animation-skinning-ik/animation-skinning-ik.store.ts

Lines changed: 0 additions & 78 deletions
This file was deleted.

0 commit comments

Comments
 (0)