Compare commits

...
12 changed files with 186 additions and 34 deletions
+2
View File
@@ -29,6 +29,7 @@
"data:",
"https://*.fakeyou.com",
"https://*.storyteller.ai",
"https://www.googletagmanager.com",
"ipc://localhost",
"tauri://localhost"
],
@@ -41,6 +42,7 @@
"https://*.fakeyou.com",
"https://*.storyteller.ai",
"https://unpkg.com/detect-gpu@5.0.70/",
"https://www.googletagmanager.com",
"ipc://localhost",
"tauri://localhost"
],
+12 -3
View File
@@ -1,11 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<title>ArtCraft</title>
<meta charset="utf-8" />
<title>ArtCraft 3D</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-Y4Y8KMLPQ7"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-Y4Y8KMLPQ7');
</script>
<base href="/" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
@@ -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::";
@@ -10,13 +10,14 @@ import { cameras, selectedCameraId } from "~/pages/PageEnigma/signals/camera";
import { isPromptBoxFocused } from "~/pages/PageEnigma/signals/promptBox";
export const isSafariUserAgent = () => {
export const isSafariOrWebKit = () => {
const ua = navigator.userAgent;
const isWebKit = /AppleWebKit/i.test(ua) && !/Chrome|Chromium|Edg|OPR|CriOS|FxiOS/i.test(ua);
const isSafari = /^((?!chrome|android|crios|fxios).)*safari/i.test(ua);
return isSafari;
}
return isSafari || isWebKit;
};
export const hasApplePay = () => {
// @ts-expect-error Apple Pay is not defined in TypeScript
@@ -24,7 +25,7 @@ export const hasApplePay = () => {
}
export const isSafari = () => {
return isSafariUserAgent() || hasApplePay();
return isSafariOrWebKit() || hasApplePay();
}
export const isPointerLockSupported = () => {
@@ -22,6 +22,7 @@ import {
poseMode,
showPoseControls,
} from "../signals/selectedMode";
import { Euler } from "three";
export enum KinMode {
FK,
@@ -65,6 +66,7 @@ export class MouseControls {
private fkHelper: FKHelper;
private isBoneDragged: boolean = false;
private ignoreNextClick: boolean = false;
private manualMouseLock = false;
constructor(
camera: THREE.PerspectiveCamera,
@@ -377,6 +379,60 @@ export class MouseControls {
}
}
handleMousePointerLock() {
if (this.isMouseClicked && this.lockControls) {
if (this.lockControls.isLocked == false) {
this.lockControls.lock();
}
} else if (this.lockControls) {
if (this.lockControls.isLocked == true) {
this.lockControls.unlock();
}
}
}
handleMouseManualLock(event: MouseEvent) {
if (this.isMouseClicked && this.lockControls) {
// If the mouse is clicked and the lockControls is not locked, lock it
if (this.lockControls.isLocked == false) {
// Lock the mouse flag
this.lockControls.isLocked = true;
// Change the cursor to a dragging cursor
this.lockControls.domElement.style.cursor = "move";
console.log("Mouse locked manually");
return;
}
// If the mouse is clicked but controls also locked, move the camera with the mouse
const camera = this.lockControls.getObject();
const _euler = new Euler(0, 0, 0, 'YXZ');
const _MOUSE_SENSITIVITY = 0.002;
const pointerSpeed = 1.0;
const _PI_2 = Math.PI / 2;
const minPolarAngle = 0;
const maxPolarAngle = Math.PI;
_euler.setFromQuaternion(camera.quaternion);
_euler.y -= event.movementX * _MOUSE_SENSITIVITY * pointerSpeed;
_euler.x -= event.movementY * _MOUSE_SENSITIVITY * pointerSpeed;
_euler.x = Math.max(_PI_2 - maxPolarAngle, Math.min(_PI_2 - minPolarAngle, _euler.x));
camera.quaternion.setFromEuler(_euler);
} else if (this.lockControls) {
if (this.lockControls.isLocked == true) {
// Unlock the mouse flag
this.lockControls.isLocked = false;
// Change the cursor back to default
this.lockControls.domElement.style.cursor = "default";
console.log("Mouse unlocked manually");
}
}
}
// Sets new mouse location usually used in raycasts.
onMouseMove(event: MouseEvent) {
if (this.canvReference == undefined) {
@@ -392,18 +448,11 @@ export class MouseControls {
// this causes an issue https://discourse.threejs.org/t/unable-to-use-pointer-lock-api/11092
if (!isPointerLockSupported()) {
return;
this.handleMouseManualLock(event);
} else {
this.handleMousePointerLock();
}
if (this.isMouseClicked && this.lockControls) {
if (this.lockControls.isLocked == false) {
this.lockControls.lock();
}
} else if (this.lockControls) {
if (this.lockControls.isLocked == true) {
this.lockControls.unlock();
}
}
}
// When the mouse clicks the screen.
@@ -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(
-6
View File
@@ -24,12 +24,6 @@
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"~/*": [
"app/src/*"
]
},
// Vite takes care of building everything, not tsc.
"noEmit": true
},
+4 -5
View File
@@ -24,11 +24,10 @@
],
"baseUrl": ".",
"paths": {
"@frontend/login": [
"libs/login/src/index.ts"
],
"@frontend/soundboard": [
"libs/soundboard/src/index.ts"
"~/*": [
// Uncomment if 2D ever gets used
// "apps/editor2d/app/src/*",
"apps/editor3d/app/src/*"
]
}
}