Skip to content

Commit

Permalink
add full body ik without constrain
Browse files Browse the repository at this point in the history
  • Loading branch information
RuiChen0101 committed Jun 26, 2024
1 parent 877c06d commit f4e5c63
Show file tree
Hide file tree
Showing 12 changed files with 569 additions and 8 deletions.
7 changes: 6 additions & 1 deletion src/3d/Camera.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import { CameraControls } from '@react-three/drei';
import { Component, createRef, ReactNode } from 'react';
import XYZValue from './value/xyz_value';

interface CameraProps {
disabled?: boolean;
pos?: XYZValue;
lookAt?: XYZValue;
}

class Camera extends Component<CameraProps> {
private _cameraRef = createRef<CameraControls>();

componentDidMount() {
if (!this._cameraRef.current) return;
this._cameraRef.current.setLookAt(0, 250, 350, 0, 100, 0, false);
const pos = this.props.pos || new XYZValue(0, 250, 350);
const lookAt = this.props.lookAt || new XYZValue(0, 100, 0);
this._cameraRef.current.setLookAt(pos.x, pos.y, pos.z, lookAt.x, lookAt.y, lookAt.z, false);
}

render(): ReactNode {
Expand Down
39 changes: 39 additions & 0 deletions src/3d/FullBody.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.full-body {
width: 100vw;
height: 100vh;
position: relative;

.overlay {
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: flex-start;
align-self: start;

.overlay-area {
margin: 8px;
padding: 8px 8px 32px 8px;
background-color: #f5f5f5;
border: 1px solid #e0e0e0;
border-radius: 10px;

.iteration {
pointer-events: none;
-webkit-user-select: none;
/* Safari */
-ms-user-select: none;
/* IE 10 and IE 11 */
user-select: none;
/* Standard syntax */
}
}
}

.canvas {
width: 100vw;
height: 100vh;
background-color: #f0f0f0;
border-radius: 10px;
}
}
139 changes: 139 additions & 0 deletions src/3d/FullBody.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import Camera from './Camera';
import XYZValue from './value/xyz_value';
import { Canvas } from '@react-three/fiber';
import { Component, ReactNode } from 'react';
import FullBodyFABRIK from './fabrik/full-body-fabrik';

import './FullBody.scss';

import TargetVisualizer from './visualizer/TargetVisualizer';
import BoneVisualizerFunc from './visualizer/BoneVisualizer';
import Character, { importTemplate } from './character/character';

interface FullBodyState {
isLoading: boolean;
cameraEnabled: boolean;
fabrikIteration: number;
char: Character;
}

class FullBody extends Component<any, FullBodyState> {

private _fabrik: FullBodyFABRIK = new FullBodyFABRIK();

private _targetLH: XYZValue = new XYZValue(0, 0, 0);
private _targetRH: XYZValue = new XYZValue(0, 0, 0);
private _targetLF: XYZValue = new XYZValue(0, 0, 0);
private _targetRF: XYZValue = new XYZValue(0, 0, 0);

constructor(props: any) {
super(props);
this.state = {
isLoading: true,
cameraEnabled: true,
fabrikIteration: 0,
char: importTemplate(),
}
}

componentDidMount(): void {
this._targetLH = this.state.char.find('hand_l')!.world[0];
this._targetRH = this.state.char.find('hand_r')!.world[0];
this._targetLF = this.state.char.find('foot_l')!.world[0];
this._targetRF = this.state.char.find('foot_r')!.world[0];
this.setState({
isLoading: false
});
}

private _onTargetLHMove = (pos: XYZValue): void => {
this._targetLH = pos;
this._resolve();
}

private _onTargetRHMove = (pos: XYZValue): void => {
this._targetRH = pos;
this._resolve();
}

private _onTargetLFMove = (pos: XYZValue): void => {
this._targetLF = pos;
this._resolve();
}

private _onTargetRFMove = (pos: XYZValue): void => {
this._targetRF = pos;
this._resolve();
}

private _resolve = (): void => {
const targets = {
leftHand: this._targetLH,
rightHand: this._targetRH,
leftFoot: this._targetLF,
rightFoot: this._targetRF,
};
const char = this.state.char;
const iteration = this._fabrik.resolve(char, targets);
this.setState({
fabrikIteration: iteration,
char: char,
});
}

render(): ReactNode {
if (this.state.isLoading) {
return <div>Loading...</div>;
}
return (
<div className="full-body">
<div className="overlay">
<div className="overlay-area">
<span className="iteration">iteration: {this.state.fabrikIteration}</span>
</div>
</div>
<div className="canvas">
<Canvas>
<Camera disabled={!this.state.cameraEnabled} pos={new XYZValue(0, 150, 150)} />
<ambientLight color="#0f0f0f" intensity={1} />
<hemisphereLight color="#ffffff" groundColor="#0f0f0f" intensity={1} />
<gridHelper args={[10000, 1000]} />
<axesHelper args={[100]} />
{/* <CharacterVisualizer char={this.state.char} /> */}
<BoneVisualizerFunc root={this.state.char.root} nodeSize={0.5} stickSize={0.3} />
<TargetVisualizer
initPos={this._targetLH}
size={2}
onTargetMove={this._onTargetLHMove}
onDragStart={() => this.setState({ cameraEnabled: false })}
onDragEnd={() => this.setState({ cameraEnabled: true })}
/>
<TargetVisualizer
initPos={this._targetRH}
size={2}
onTargetMove={this._onTargetRHMove}
onDragStart={() => this.setState({ cameraEnabled: false })}
onDragEnd={() => this.setState({ cameraEnabled: true })}
/>
<TargetVisualizer
initPos={this._targetLF}
size={2}
onTargetMove={this._onTargetLFMove}
onDragStart={() => this.setState({ cameraEnabled: false })}
onDragEnd={() => this.setState({ cameraEnabled: true })}
/>
<TargetVisualizer
initPos={this._targetRF}
size={2}
onTargetMove={this._onTargetRFMove}
onDragStart={() => this.setState({ cameraEnabled: false })}
onDragEnd={() => this.setState({ cameraEnabled: true })}
/>
</Canvas>
</div>
</div>
)
}
}

export default FullBody;
4 changes: 4 additions & 0 deletions src/3d/character/bone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ class Bone {
return true;
}

public canLeadTo(name: string): boolean {
return this._childrenNames.has(name);
}

public list(): Bone[] {
const list: Bone[] = [];
for (const bone of this) {
Expand Down
56 changes: 56 additions & 0 deletions src/3d/character/character.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Bone from './bone';
import XYZValue from '../value/xyz_value';

class Character {
private _root: Bone;

public get root(): Bone {
return this._root;
}

public set root(b: Bone) {
this._root = b;
}

constructor(root: Bone) {
this._root = root;
}

// in-order traversal
*[Symbol.iterator](): Generator<Bone> {
yield* this._root;
}

public copy(): Character {
return new Character(this._root.copy());
}

public find(name: string): Bone | undefined {
return this._root.find(name);
}
}

import templateJson from '../../assets/template.json';

const importTemplate = (): Character => {
const bone = parseBoneFromJson(templateJson['root']);
bone.rotation.x = -90;
return new Character(bone);
}

const parseBoneFromJson = (json: any): Bone => {
const pos = new XYZValue(json['base']['tx'], json['base']['ty'], json['base']['tz']);
const rot = new XYZValue(json['base']['rx'], json['base']['ry'], json['base']['rz']);
const bone = new Bone(json['name'], pos, rot);

for (const child of json['children']) {
bone.addChild(parseBoneFromJson(child));
}

return bone;
}

export default Character;
export {
importTemplate
}
Loading

0 comments on commit f4e5c63

Please sign in to comment.