Compare commits

...
6 changed files with 104 additions and 6 deletions
@@ -0,0 +1,76 @@
import { Object3D, ObjectLoader } from "three";
export class BoneJSONHelper {
model: Object3D;
constructor(model: Object3D) {
this.model = model;
}
private findHipBone(objectGroup: Object3D[]): Object3D | undefined {
let hipBone = undefined;
for (const child of objectGroup) {
if (child.type === "Bone" && child.name.toLowerCase().includes("mixamorighips")) {
hipBone = child;
break;
}
}
return hipBone;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public toJSON(): any {
if (!this.model.userData.isCharacter) {
return;
}
if (this.model.children.length < 1) {
return undefined;
}
// Find the hip bone
const hipBone = this.findHipBone(this.model.children[0].children);
if (!hipBone) {
console.error("No hip bone found in the model.");
return undefined;
}
return hipBone.toJSON();
}
/* Applies the bone tree in a recursive BFS manner */
private applyBoneTree(hipBone: Object3D, boneTree: Object3D): void {
hipBone.position.copy(boneTree.position);
hipBone.quaternion.copy(boneTree.quaternion);
hipBone.scale.copy(boneTree.scale);
const hipChildren = hipBone.children;
const treeChildren = boneTree.children;
// NOTE: Assumption made that the lengths match!
// This shouldn't be a problem unless the skeletal model is changed
for (let i = 0; i < hipChildren.length; i++) {
const hipChild = hipChildren[i];
const treeChild = treeChildren[i];
if (hipChild && treeChild) {
this.applyBoneTree(hipChild, treeChild);
}
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public poseFromBoneJSON(boneJSON: any): void {
const hipBone = this.findHipBone(this.model.children[0].children);
if (!hipBone) {
return;
}
const loader = new ObjectLoader();
const boneTree = loader.parse(boneJSON);
this.applyBoneTree(hipBone, boneTree);
}
}
@@ -3,6 +3,7 @@ import {
uploadNewScene as uploadNewSceneEndpoint,
updateExistingScene as updateExistingSceneEndpoint,
} from "~/api";
import { FetchProxy as fetch } from "@storyteller/tauri-utils";
export const uploadNewScene = async (file: File, sceneTitle: string) => {
const endpoint = uploadNewSceneEndpoint;
@@ -224,7 +224,7 @@ class Editor {
// life cycle fix
// Version and name.
this.version = 1.2;
this.version = 2.0;
// Clock, scene and camera essentials.
// global names
this.camera_name = "::CAM::";
@@ -187,7 +187,7 @@ export const ControlsTopButtons = () => {
return (
<div className="flex flex-col gap-2 pl-2 pt-2">
<div className="flex gap-1.5">
{/* <ButtonDropdown
<ButtonDropdown
label="File"
icon={faFile}
className="shadow-xl"
@@ -222,9 +222,9 @@ export const ControlsTopButtons = () => {
},
{
disabled:
!scene.value.isModified ||
scene.value.ownerToken ===
authentication.userInfo.value?.user_token,
!(scene.value.isModified &&
(scene.value.ownerToken === undefined || scene.value.ownerToken ===
authentication.userInfo.value?.user_token)),
// save scene should be disabled if there are no changes
label: "Save scene",
description: "Ctrl+S",
@@ -285,7 +285,7 @@ export const ControlsTopButtons = () => {
},
},
]}
/> */}
/>
<Button
icon={outlinerIsShowing.value ? faCheckSquare : faSquare}
@@ -29,6 +29,8 @@ export interface ObjectJSON {
locked: boolean;
visible: boolean | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rigData: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
user_data: Record<string, any>;
}
@@ -50,6 +52,8 @@ export class StoryTellerProxy3DObject {
locked: boolean;
visible: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
rigData: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
userData: Record<string, any>;
constructor(version: number, media_file_token: string) {
@@ -71,6 +75,7 @@ export class StoryTellerProxy3DObject {
this.specular = 0.5;
this.locked = false;
this.visible = true;
this.rigData = undefined;
this.userData = {};
this.userData.media_file_type = MediaFileType.None;
}
@@ -129,6 +134,7 @@ export class StoryTellerProxy3DObject {
locked: this.locked,
visible: this.visible,
user_data: this.userData,
rigData: this.rigData,
};
return json;
}
@@ -5,6 +5,7 @@ import {
ObjectJSON,
} from "./storyteller_proxy_3d_object";
import Scene from "../Editor/scene";
import { BoneJSONHelper } from "../Editor/KinHelpers/BoneJSONHelper";
interface LookUpDictionary {
[key: string]: StoryTellerProxy3DObject;
@@ -49,6 +50,10 @@ export class StoryTellerProxyScene {
proxyObject3D.locked = child.userData["locked"];
proxyObject3D.visible = child.visible;
proxyObject3D.userData = child.userData;
// Add rig data if present
const boneHelper = new BoneJSONHelper(child);
proxyObject3D.rigData = boneHelper.toJSON();
const json_data = await proxyObject3D.toJSON();
return json_data;
}
@@ -146,6 +151,16 @@ export class StoryTellerProxyScene {
new THREE.Vector3(-0.5, 1.5, 0),
version,
);
// Add any missing userdata back to the object
obj.userData = json_object.user_data;
// Add rig data if present
if (json_object.rigData) {
const boneHelper = new BoneJSONHelper(obj);
boneHelper.poseFromBoneJSON(json_object.rigData);
} else {
console.log("No rig data found");
}
} else if (token.includes("Point::")) {
const keyframe_uuid = token.replace("Point::", "");
obj = this.scene.createPoint(