mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-31 15:55:54 -04:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad7a0bcc7f | ||
|
|
9bc3a57390 | ||
|
|
109a9dd1a3 | ||
|
|
b6807a2a3d | ||
|
|
4d738c0384 | ||
|
|
7227156711 | ||
|
|
a438dd13dc | ||
|
|
f7b76a7abe | ||
|
|
9435af3e2a |
@@ -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"
|
||||
],
|
||||
|
||||
@@ -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.
|
||||
|
||||
+5
-5
@@ -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(
|
||||
|
||||
@@ -24,12 +24,6 @@
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": [
|
||||
"app/src/*"
|
||||
]
|
||||
},
|
||||
// Vite takes care of building everything, not tsc.
|
||||
"noEmit": true
|
||||
},
|
||||
|
||||
@@ -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/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user