mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-08 11:01:35 +00:00
Merge branch 'test' into feature/loading-bar
This commit is contained in:
commit
3a8726a198
16 files changed
+556
-442
No files matched your search
@@ -24,7 +24,7 @@ export const PageEnigmaComponent = () => {
|
||||
useEffect(()=>{
|
||||
setTimeout(()=>dispatchAppUiState({
|
||||
type: ACTION_TYPES.HIDE_EDITOR_LOADER
|
||||
}), 3000);
|
||||
}), 500);
|
||||
},[]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,12 +10,12 @@ export const ControlsTopButtons = () => {
|
||||
|
||||
const handleButtonSave = () => {
|
||||
console.log(`SceneName is ${sceneName}`);
|
||||
editorEngine?.save();
|
||||
editorEngine?.saveScene(sceneName);
|
||||
};
|
||||
|
||||
const handleButtonLoadScene = () => {
|
||||
console.log(`Scene Token is ${sceneToken}`);
|
||||
// editorEngine?.dosomething();
|
||||
editorEngine?.loadScene(sceneToken);
|
||||
};
|
||||
|
||||
// const handleButtonCameraView = () => {
|
||||
@@ -26,12 +26,11 @@ export const ControlsTopButtons = () => {
|
||||
editorEngine?.start_playback();
|
||||
};
|
||||
const handleButtonLoad = () => {
|
||||
document.getElementById("load-upload")?.click();
|
||||
|
||||
|
||||
};
|
||||
const handleButtonRender = () => {
|
||||
console.log("Saving GLB to server for reference");
|
||||
editorEngine?._upload_for_testing();
|
||||
//editorEngine?.togglePlayback();
|
||||
|
||||
};
|
||||
const handleButtonPlay = () => {};
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export const EngineProvider = ({ children }: Props) => {
|
||||
}, []);
|
||||
|
||||
useEffect(()=>{
|
||||
if (editor && editor.can_initailize && dispatchAppUiState!==null){
|
||||
if (editor && editor.can_initialize && dispatchAppUiState!==null){
|
||||
console.log("initializing Editor");
|
||||
editor.initialize({
|
||||
dispatchAppUiState
|
||||
|
||||
+1
-13
@@ -1,19 +1,7 @@
|
||||
import { faL } from '@fortawesome/pro-solid-svg-icons';
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||
|
||||
export interface AnimationTrackClip {
|
||||
version: number
|
||||
media_id: string
|
||||
object_uuid: string
|
||||
type: "animation"
|
||||
location: "glb" | "remote"
|
||||
speed: number
|
||||
length: number
|
||||
clip_name: string
|
||||
}
|
||||
|
||||
export class AnimationTrackClip implements AnimationTrackClip {
|
||||
export class AnimationClip {
|
||||
version: number;
|
||||
media_id: string; // comes from the server
|
||||
object_uuid: string;
|
||||
+1
-8
@@ -14,14 +14,7 @@ class AudioData implements AudioData{
|
||||
}
|
||||
}
|
||||
|
||||
export interface AudioTrackClip {
|
||||
version: number;
|
||||
media_id: string;
|
||||
type: "audio";
|
||||
volume: number;
|
||||
}
|
||||
|
||||
export class AudioTrackClip implements AudioTrackClip {
|
||||
export class AudioClip {
|
||||
version: number;
|
||||
media_id: string;
|
||||
type: "audio" = "audio";
|
||||
+2
-9
@@ -16,13 +16,6 @@ class AudioData implements AudioData {
|
||||
}
|
||||
}
|
||||
|
||||
export interface LipSyncTrackClip {
|
||||
version: number;
|
||||
media_id: string;
|
||||
type: "lipsync";
|
||||
volume: number;
|
||||
}
|
||||
|
||||
class BlendShapeHelper {
|
||||
ah: number;
|
||||
ee: number;
|
||||
@@ -34,7 +27,7 @@ class BlendShapeHelper {
|
||||
}
|
||||
}
|
||||
|
||||
export class LipSyncTrackClip implements LipSyncTrackClip {
|
||||
export class LipSyncClip {
|
||||
version: number;
|
||||
media_id: string;
|
||||
type: "lipsync" = "lipsync";
|
||||
@@ -98,7 +91,7 @@ export class LipSyncTrackClip implements LipSyncTrackClip {
|
||||
}
|
||||
|
||||
|
||||
async play(object: THREE.Object3D) {
|
||||
async play(object: THREE.Object3D) {
|
||||
if (this.audio_data?.audioBuffer == null) { await this.download_audio(); }
|
||||
if (this.lipsync.face == null) {
|
||||
this.lipsync = new LipSync(await this._detect_face(object));
|
||||
+1
-12
@@ -1,17 +1,6 @@
|
||||
import { faL } from '@fortawesome/pro-solid-svg-icons';
|
||||
import * as THREE from 'three';
|
||||
|
||||
export interface TransformTrackClip {
|
||||
version: number;
|
||||
media_id: string;
|
||||
type: "transform";
|
||||
positions: THREE.Vector3[];
|
||||
rotations: THREE.Vector3[];
|
||||
scales: THREE.Vector3[];
|
||||
length: number;
|
||||
}
|
||||
|
||||
export class TransformTrackClip implements TransformTrackClip {
|
||||
export class TransformClip {
|
||||
version: number;
|
||||
media_id: string;
|
||||
object_uuid: string;
|
||||
@@ -1,121 +0,0 @@
|
||||
import { AudioTrackClip } from "../clips/audio_track_clip";
|
||||
import { AnimationTrackClip } from "../clips/animation_track_clip";
|
||||
import { TransformTrackClip } from "../clips/transform_track_clip";
|
||||
import { ClipOffset } from "../clips/clip_offset";
|
||||
|
||||
export interface Scene {
|
||||
version: number;
|
||||
glb_media_id: number; // Assuming a numerical ID, adjust as needed
|
||||
skybox_type: "cube" | "six_sided_cube" | "sphere";
|
||||
skybox_media_ids: { [key: string]: number };
|
||||
timeline: Timeline;
|
||||
}
|
||||
|
||||
export interface Timeline {
|
||||
version: number;
|
||||
last_scrubber_position: number;
|
||||
audio_track_clips: AudioTrackClip[]; // Assuming defined elsewhere
|
||||
transform_track_clips: TransformTrackClip[]; // Placeholder, define as needed
|
||||
animation_track_clips: AnimationTrackClip[]; // Placeholder, define as needed
|
||||
entities: Entity[];
|
||||
globalAudio: GlobalAudio;
|
||||
camera: Camera;
|
||||
}
|
||||
|
||||
export interface Entity {
|
||||
object_uuid: number;
|
||||
clip_offsets: ClipOffset[];
|
||||
}
|
||||
|
||||
export interface GlobalAudio {
|
||||
clip_offsets: ClipOffset[];
|
||||
}
|
||||
|
||||
export interface Camera {
|
||||
clip_offsets: ClipOffset[];
|
||||
}
|
||||
|
||||
export class Scene implements Scene {
|
||||
version: number;
|
||||
glb_media_id: number;
|
||||
skybox_type: "cube" | "six_sided_cube" | "sphere";
|
||||
skybox_media_ids: { [key: string]: number };
|
||||
timeline: Timeline;
|
||||
|
||||
constructor(
|
||||
version: number,
|
||||
glb_media_id: number,
|
||||
skybox_type: "cube" | "six_sided_cube" | "sphere",
|
||||
skybox_image_ids: { [key: string]: number },
|
||||
timeline: Timeline,
|
||||
)
|
||||
{
|
||||
this.version = version;
|
||||
this.glb_media_id = glb_media_id;
|
||||
this.skybox_type = skybox_type;
|
||||
this.skybox_media_ids = skybox_image_ids;
|
||||
this.timeline = timeline;
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
}
|
||||
|
||||
export class Timeline implements Timeline {
|
||||
version: number;
|
||||
last_scrubber_position: number;
|
||||
audio_track_clips: AudioTrackClip[];
|
||||
transform_track_clips: TransformTrackClip[];
|
||||
animation_track_clips: AnimationTrackClip[];
|
||||
entities: Entity[];
|
||||
globalAudio: GlobalAudio;
|
||||
camera: Camera;
|
||||
|
||||
constructor(
|
||||
version: number,
|
||||
last_scrubber_position: number,
|
||||
audio_track_clips: AudioTrackClip[],
|
||||
transform_track_clips: TransformTrackClip[],
|
||||
animation_track_clips: AnimationTrackClip[],
|
||||
entities: Entity[],
|
||||
globalAudio: GlobalAudio,
|
||||
camera: Camera,
|
||||
)
|
||||
{
|
||||
this.version = version;
|
||||
this.last_scrubber_position = last_scrubber_position;
|
||||
this.audio_track_clips = audio_track_clips;
|
||||
this.transform_track_clips = transform_track_clips;
|
||||
this.animation_track_clips = animation_track_clips;
|
||||
this.entities = entities;
|
||||
this.globalAudio = globalAudio;
|
||||
this.camera = camera;
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
return JSON.stringify(this);
|
||||
}
|
||||
// For testing purposes
|
||||
static generateMockJson(): string {
|
||||
const mock = {
|
||||
version: 1,
|
||||
last_scrubber_position: 5000,
|
||||
audio_track_clips: [
|
||||
{ version: 1, uuid: 0, media_id: 1, type: "audio", volume: 100 }
|
||||
],
|
||||
transform_track_clips: [
|
||||
{ version: 1, uuid: 1, media_id: 2, type: "transform", position: [{ x: 0, y: 0, z: 0 }], rotation: [{ x: 0, y: 0, z: 0 }], scale: [{ x: 1, y: 1, z: 1 }] }
|
||||
],
|
||||
animation_track_clips: [
|
||||
{ version: 1, uuid: 2, media_id: 3, type: "animation", speed: 1.0, length: 100, starting_offset: 0, clip_name: "some clip name" }
|
||||
],
|
||||
entities: [
|
||||
{ object_uuid: 0, clip_offsets: [] } // Assuming more details would be filled as necessary
|
||||
],
|
||||
globalAudio: { clip_offsets: [] }, // Assuming more details would be filled as necessary
|
||||
camera: { clip_offsets: [] } // Assuming more details would be filled as necessary
|
||||
};
|
||||
return JSON.stringify(mock);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,17 @@
|
||||
import { AnimationTrackClip } from "../datastructures/clips/animation_track_clip";
|
||||
import { AnimationClip } from "../datastructures/clips/animation_clip";
|
||||
import * as THREE from 'three';
|
||||
|
||||
export class AnimationEngine {
|
||||
clips: { [key: string]: AnimationTrackClip } = {};
|
||||
clips: { [key: string]: AnimationClip } = {};
|
||||
version: number;
|
||||
length: number;
|
||||
|
||||
constructor(version: number, length: number) {
|
||||
constructor(version: number) {
|
||||
this.clips = {};
|
||||
this.version = version;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
load_object(object_uuid: string, media_id:string, clip_name: string) {
|
||||
this.clips[object_uuid] = new AnimationTrackClip(
|
||||
this.clips[object_uuid] = new AnimationClip(
|
||||
this.version,
|
||||
media_id,
|
||||
"glb",
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Scene } from "../datastructures/scene/scene_object";
|
||||
import * as THREE from "three";
|
||||
import { GLTFExporter } from "three/addons/exporters/GLTFExporter.js";
|
||||
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
||||
import { TimelineCurrentReactState } from "./timeline";
|
||||
import { TimelineDataState } from "./timeline";
|
||||
|
||||
/**
|
||||
* Storyteller Studio API Manager
|
||||
* The source of truth of all these media items is the database in the cloud
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is designed to surface user customer facing messages as errors.
|
||||
*/
|
||||
type Data = { [key: string]: any };
|
||||
class APIManagerResponseSuccess {
|
||||
public user_message: String;
|
||||
public data: Data | null;
|
||||
constructor(user_message: String = "", data: Data | null = null) {
|
||||
this.data = data;
|
||||
this.user_message = user_message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is designed to surface user customer facing messages as errors.
|
||||
* Errors shouldn't be 404 or something confusing should be
|
||||
*/
|
||||
class APIManagerResponseError extends Error {
|
||||
constructor(message?: string) {
|
||||
super(message);
|
||||
this.name = "APIManagerResponseError";
|
||||
}
|
||||
}
|
||||
|
||||
// Basically the data will exist on the database
|
||||
class APIManager {
|
||||
baseUrl: String;
|
||||
|
||||
@@ -14,90 +41,218 @@ class APIManager {
|
||||
//this.baseUrl = "http://localhost:12345"
|
||||
}
|
||||
|
||||
// Function to save or update the scene
|
||||
// it will always return a boolean success and the media_file_token
|
||||
// they take an optional media_file_token as a parameter for the file to overwrite, which must belong to the user.
|
||||
//If it's an anonymous user, we check the anonymous visitor token/cookie
|
||||
async saveSceneState(
|
||||
/**
|
||||
* @param scene The 3JS Scene we want to save.
|
||||
* @param scene_name The Scene name we want to display
|
||||
* @param scene_media_file_token If null we will we will create a new save or copy the scene, if provided we will overwrite the scene.
|
||||
* @returns APIManagerResponseMessage
|
||||
*/
|
||||
public async saveSceneState(
|
||||
scene: THREE.Scene,
|
||||
timeline: TimelineCurrentReactState,
|
||||
media_file_token: string | null = null,
|
||||
): Promise<string> {
|
||||
if (media_file_token == null) {
|
||||
// create new scene.
|
||||
let file = await this.gltfExport(scene);
|
||||
let response = await this.uploadGLB(file);
|
||||
scene_name: string,
|
||||
scene_media_file_token: string | null = null,
|
||||
timeline_state: TimelineDataState | null,
|
||||
): Promise<APIManagerResponseSuccess> {
|
||||
const file = await this.gltfExport(scene);
|
||||
|
||||
let success = response["success"];
|
||||
if (success) {
|
||||
let media_file_token = response["media_file_token"];
|
||||
await this._saveSceneAndTimelineToJSONSpec(media_file_token, timeline);
|
||||
} else {
|
||||
throw Error("Saving the Scene Resulted in an Error Success False");
|
||||
}
|
||||
} else {
|
||||
// update scene or overwrite glb scene.
|
||||
// will overwrite the scene on db if token exists
|
||||
const upload_glb_response = await this.uploadGLB(
|
||||
file,
|
||||
scene_media_file_token,
|
||||
);
|
||||
const glb_media_file_token = upload_glb_response["media_file_token"];
|
||||
|
||||
// now write the scene
|
||||
const save_scene_timeline_response = await this.saveSceneAndTimeLineState(
|
||||
glb_media_file_token,
|
||||
scene_name,
|
||||
timeline_state,
|
||||
);
|
||||
const result_scene_media_file_token =
|
||||
save_scene_timeline_response["media_file_token"];
|
||||
|
||||
const data = { media_file_token: result_scene_media_file_token };
|
||||
return new APIManagerResponseSuccess("Scene Saved", data);
|
||||
}
|
||||
|
||||
|
||||
public async loadSceneState(scene_media_file_token: string | null): Promise<APIManagerResponseSuccess> {
|
||||
const api_base_url = "https://api.fakeyou.com";
|
||||
const url = `${api_base_url}/v1/media_files/file/${scene_media_file_token}`;
|
||||
const response = await fetch(url);
|
||||
const json = await JSON.parse(await response.text());
|
||||
const bucket_path = json["media_file"]["public_bucket_path"];
|
||||
const media_base_url = "https://storage.googleapis.com/vocodes-public";
|
||||
const media_url = `${media_base_url}${bucket_path}`; // gets you a bucket path
|
||||
|
||||
const file_response = await fetch(media_url);
|
||||
|
||||
if (!file_response.ok) {
|
||||
throw new APIManagerResponseError("Failed to download file");
|
||||
}
|
||||
return "";
|
||||
// Convert the response from a blob to json text
|
||||
let blob = await file_response.blob();
|
||||
const json_result: string = await new Promise((resolve, reject) => {
|
||||
let reader = new FileReader();
|
||||
reader.onloadend = () => resolve(JSON.parse(reader.result as string));
|
||||
reader.onerror = reject;
|
||||
reader.readAsText(blob);
|
||||
});
|
||||
|
||||
console.log(`loadSceneState: ${JSON.stringify(json_result)}`);
|
||||
|
||||
const scene_glb_media_file_id: string = json_result["glb_media_file_id"];
|
||||
|
||||
const media_bucket_path = await this.getMediaFile(scene_glb_media_file_id);
|
||||
console.log(`GLB ${media_bucket_path}`)
|
||||
let glbLoader = new GLTFLoader();
|
||||
// promisify this
|
||||
const loadGlb = (bucket_path: string): Promise<APIManagerResponseSuccess> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
glbLoader.load(bucket_path, (glb) => {
|
||||
if (glb) {
|
||||
const scene: THREE.Scene = glb.scene;
|
||||
const data = { "media_file_token": scene_glb_media_file_id , "scene": scene };
|
||||
console.log(`Data: ${data}`)
|
||||
resolve(new APIManagerResponseSuccess("Success Loaded", data));
|
||||
} else {
|
||||
throw new APIManagerResponseError("Failed to Load GLB Scene");
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return await loadGlb(media_bucket_path);
|
||||
}
|
||||
|
||||
// which will create or update/replace scene descriptor JSON files
|
||||
async _saveSceneAndTimelineToJSONSpec(
|
||||
media_file_token: string,
|
||||
timeline: TimelineCurrentReactState,
|
||||
): Promise<string> {
|
||||
const url = `${this.baseUrl}/v1/media_files/write/scene_file`;
|
||||
let uuid = uuidv4();
|
||||
return "";
|
||||
}
|
||||
|
||||
// media_file_token that describes the scene
|
||||
async loadScene(media_file_token: string): Promise<string> {
|
||||
// grab the json file from media files
|
||||
let json_response = `{
|
||||
|
||||
}`
|
||||
return ""
|
||||
}
|
||||
|
||||
// Provides bucket path to the actual file to download from the media file
|
||||
async getMediaFile(media_file_token: string): Promise<string> {
|
||||
/**
|
||||
* This recieves the bucket path of a media file
|
||||
* @param media_file_token
|
||||
* @returns
|
||||
*/
|
||||
private async getMediaFile(media_file_token: string): Promise<string> {
|
||||
let api_base_url = "https://api.fakeyou.com";
|
||||
let url = `${api_base_url}/v1/media_files/file/${media_file_token}`
|
||||
let url = `${api_base_url}/v1/media_files/file/${media_file_token}`;
|
||||
let response = await fetch(url);
|
||||
let json = await JSON.parse(await response.text());
|
||||
let bucketPath = json["media_file"]["public_bucket_path"];
|
||||
let media_base_url = "https://storage.googleapis.com/vocodes-public"
|
||||
let media_url = `${media_base_url}${bucketPath}` // gets you a bucket path
|
||||
// TODO get it as binary.
|
||||
return media_url
|
||||
}
|
||||
|
||||
async batchGetMedia(media_tokens: []): Promise<string> {
|
||||
|
||||
return "";
|
||||
let media_base_url = "https://storage.googleapis.com/vocodes-public";
|
||||
let media_url = `${media_base_url}${bucketPath}`; // gets you a bucket path
|
||||
return media_url;
|
||||
}
|
||||
|
||||
async gltfExport(scene: THREE.Scene) {
|
||||
let gltfExporter = new GLTFExporter();
|
||||
let uuid = uuidv4();
|
||||
// save the scene to keep ids positions etc
|
||||
let result = await gltfExporter.parseAsync(scene);
|
||||
/**
|
||||
This will save the scene to keep ids positions.
|
||||
It will also give the file a name which will be a uuidv4()
|
||||
@param scene The 3JS Scene we want to make a file to be uploaded as multipart form.
|
||||
*/
|
||||
private async gltfExport(scene: THREE.Scene): Promise<File> {
|
||||
const gltfExporter = new GLTFExporter();
|
||||
const uuid = uuidv4();
|
||||
const result = await gltfExporter.parseAsync(scene);
|
||||
const file = new File([JSON.stringify(result)], `${uuid}.glb`, {
|
||||
type: "application/json",
|
||||
});
|
||||
return file;
|
||||
}
|
||||
// two core functions to upload things to the server and get id's
|
||||
async uploadMedia(blob: any, fileName: string): Promise<string> {
|
||||
|
||||
private async uploadGLB(
|
||||
file: File,
|
||||
scene_media_file_token: string | null,
|
||||
): Promise<string> {
|
||||
const url = `${this.baseUrl}/v1/media_files/write/engine_asset`;
|
||||
let uuid = uuidv4();
|
||||
const form_data = new FormData();
|
||||
form_data.append("uuid_idempotency_token", uuid);
|
||||
|
||||
// update existing scene otherwise create new scene and use it's media_file_id
|
||||
if (scene_media_file_token != null) {
|
||||
form_data.append("media_file_token", scene_media_file_token);
|
||||
}
|
||||
|
||||
form_data.append("file", file);
|
||||
form_data.append("source", "file");
|
||||
form_data.append("media_file_subtype", "scene_import");
|
||||
form_data.append("media_file_class", "scene");
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: form_data,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to Send Data");
|
||||
} else {
|
||||
const json_data = await response.json();
|
||||
console.log(`uploadGLB: ${JSON.stringify(json_data)}`);
|
||||
return json_data; // or handle the response as appropriate
|
||||
}
|
||||
}
|
||||
|
||||
private async saveSceneAndTimeLineState(
|
||||
scene_media_file_token: string | null,
|
||||
scene_file_name: string,
|
||||
timeline_state: TimelineDataState | null, // only for now.
|
||||
): Promise<string> {
|
||||
const url = `${this.baseUrl}/v1/media_files/write/scene_file`;
|
||||
let uuid = uuidv4();
|
||||
|
||||
// turn json into a blob
|
||||
const scene_schema = {
|
||||
glb_media_file_id: scene_media_file_token,
|
||||
scene_name: scene_file_name,
|
||||
timeline: { objects: [] },
|
||||
};
|
||||
const json = JSON.stringify(scene_schema);
|
||||
const blob = new Blob([json], { type: "application/json" });
|
||||
const file_name = `${uuid}.json`;
|
||||
|
||||
const form_data = new FormData();
|
||||
|
||||
form_data.append("uuid_idempotency_token", uuid);
|
||||
form_data.append("file", blob, file_name);
|
||||
form_data.append("source", "file");
|
||||
form_data.append("type", "scene_json");
|
||||
form_data.append("source", "file");
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: form_data,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new APIManagerResponseError("Failed to Send GLB Data");
|
||||
} else {
|
||||
const json_data = await response.json();
|
||||
// example response {"success":true,"media_file_token":"m_r1ztnn501g2rn0vv2np08nd6zy2fvt"}
|
||||
console.log(`saveSceneAndTimeLineState: ${JSON.stringify(json_data)}`);
|
||||
// should return the same token if it is same file else new token.
|
||||
return json_data; // or handle the response as appropriate
|
||||
}
|
||||
}
|
||||
|
||||
public async uploadMedia(
|
||||
blob: any,
|
||||
fileName: string,
|
||||
): Promise<APIManagerResponseSuccess> {
|
||||
const url = `${this.baseUrl}/v1/media_uploads/upload`;
|
||||
let uuid = uuidv4();
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("uuid_idempotency_token", uuid);
|
||||
formData.append("file", blob, fileName);
|
||||
formData.append("source", "file");
|
||||
formData.append("type", "video");
|
||||
formData.append("source", "file");
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
@@ -108,37 +263,18 @@ class APIManager {
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to Send Data");
|
||||
throw new APIManagerResponseError("Upload Media Failed to send file");
|
||||
} else {
|
||||
const json_data = await response.json();
|
||||
console.log(`uploadMedia: ${JSON.stringify(json_data)}`);
|
||||
return json_data;
|
||||
}
|
||||
|
||||
return response.json(); // or handle the response as appropriate
|
||||
}
|
||||
|
||||
// to save to a file then upload the scene.
|
||||
async uploadGLB(file: File): Promise<string> {
|
||||
// /v1/media_files/write/engine_asset REPLACE with this api endpoint
|
||||
const url = `${this.baseUrl}/v1/media_files/upload/engine_asset`;
|
||||
let uuid = uuidv4();
|
||||
const formData = new FormData();
|
||||
formData.append("uuid_idempotency_token", uuid);
|
||||
formData.append("file", file);
|
||||
formData.append("source", "file");
|
||||
formData.append("media_file_subtype", "scene_import");
|
||||
formData.append("media_file_class", "scene");
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to Send Data");
|
||||
}
|
||||
return response.json(); // or handle the response as appropriate
|
||||
public async batchGetMedia(media_tokens: []): Promise<string> {
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default APIManager;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { faL } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { AudioTrackClip } from "../datastructures/clips/audio_track_clip";
|
||||
import { AudioClip } from "../datastructures/clips/audio_clip";
|
||||
|
||||
class AudioEngine {
|
||||
clips: { [key: string]: AudioTrackClip } = {};
|
||||
clips: { [key: string]: AudioClip } = {};
|
||||
audio_sources: { [key: string]: AudioBufferSourceNode } = {};
|
||||
version: number;
|
||||
playing: string[];
|
||||
@@ -17,7 +17,7 @@ class AudioEngine {
|
||||
// loads clips into the engine to cache
|
||||
loadClip(audio_media_id: string) {
|
||||
if(this.clips[audio_media_id] != null) { return; }
|
||||
this.clips[audio_media_id] = new AudioTrackClip(this.version, audio_media_id, 1.0);
|
||||
this.clips[audio_media_id] = new AudioClip(this.version, audio_media_id, 1.0);
|
||||
}
|
||||
|
||||
// plays from the timeline.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as THREE from "three";
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
import { TransformControls } from "three/addons/controls/TransformControls.js";
|
||||
import Scene from "./scene.ts";
|
||||
import APIManager from "./api_manager.ts";
|
||||
import Scene from "./scene.js";
|
||||
import APIManager from "./api_manager.js";
|
||||
import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
|
||||
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
|
||||
import { OutlinePass } from "three/addons/postprocessing/OutlinePass.js";
|
||||
@@ -12,58 +12,102 @@ import { SAOPass } from "three/addons/postprocessing/SAOPass.js";
|
||||
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
|
||||
import { BokehPass } from "three/addons/postprocessing/BokehPass.js";
|
||||
import { createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg";
|
||||
|
||||
import AudioEngine from "./audio_engine.ts";
|
||||
import TransformEngine from "./transform_engine.ts";
|
||||
import { TimeLine } from "./timeline.ts";
|
||||
import { ClipUI } from "../datastructures/clips/clip_offset.ts";
|
||||
|
||||
import { LipSync } from "./lipsync.js";
|
||||
import { LipSyncEngine } from "./lip_sync_engine.ts";
|
||||
import { AnimationEngine } from "./animation_engine.ts";
|
||||
import { faL } from "@fortawesome/pro-solid-svg-icons";
|
||||
import AudioEngine from "./audio_engine.js";
|
||||
import TransformEngine from "./transform_engine.js";
|
||||
import { TimeLine, TimelineDataState } from "./timeline.js";
|
||||
import { ClipUI } from "../datastructures/clips/clip_offset.js";
|
||||
import { LipSyncEngine } from "./lip_sync_engine.js";
|
||||
import { AnimationEngine } from "./animation_engine.js";
|
||||
import { ACTION_TYPES } from "../reducer";
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
import("ccapture.js")
|
||||
.then((module) => {
|
||||
const CCapture = module.CCapture;
|
||||
// You can use CCapture here
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to load CCapture:", error);
|
||||
});
|
||||
}
|
||||
|
||||
class EditorState {
|
||||
// {
|
||||
// action: "ShowLoadingIndicator"
|
||||
// source: "Editor"
|
||||
// data: { "message" : "saving scene" }
|
||||
// }
|
||||
|
||||
selected_object: THREE.Object3D | undefined;
|
||||
is_loading: boolean;
|
||||
|
||||
constructor() {
|
||||
this.selected_object = null;
|
||||
this.selected_object;
|
||||
this.is_loading = false;
|
||||
}
|
||||
}
|
||||
// Main editor class that will call everything else all you need to call is " initialize(); ".
|
||||
// Main editor class that will call everything else all you need to call is " initialize() ".
|
||||
class Editor {
|
||||
version: number;
|
||||
activeScene: Scene;
|
||||
camera: any;
|
||||
renderer: THREE.WebGLRenderer | undefined;
|
||||
clock: THREE.Clock | undefined;
|
||||
canvReference: any;
|
||||
composer: EffectComposer | undefined;
|
||||
effectFXAA: EffectComposer | undefined;
|
||||
outlinePass: OutlinePass | undefined;
|
||||
last_cam_pos: THREE.Vector3 | undefined;
|
||||
saoPass: SAOPass | undefined;
|
||||
outputPass: OutputPass | undefined;
|
||||
bloomPass: UnrealBloomPass | undefined;
|
||||
smaaPass: SMAAPass | undefined;
|
||||
bokehPass: BokehPass | undefined;
|
||||
control: TransformControls | undefined;
|
||||
raycaster: THREE.Raycaster | undefined;
|
||||
mouse: THREE.Vector2 | undefined;
|
||||
selected: THREE.Object3D | undefined;
|
||||
last_selected: THREE.Object3D | undefined;
|
||||
transform_interaction: any;
|
||||
rendering: boolean;
|
||||
api_manager: APIManager;
|
||||
stats: any;
|
||||
orbit: OrbitControls | undefined;
|
||||
locked: boolean;
|
||||
capturer: any;
|
||||
frame_buffer: any;
|
||||
render_timer: number;
|
||||
fps_number: number;
|
||||
cap_fps: number;
|
||||
playback: boolean;
|
||||
playback_location: number;
|
||||
max_length: number;
|
||||
audio_engine: AudioEngine;
|
||||
transform_engine: TransformEngine;
|
||||
lipsync_engine: LipSyncEngine;
|
||||
animation_engine: AnimationEngine;
|
||||
timeline: TimeLine;
|
||||
current_frame: number;
|
||||
|
||||
|
||||
renderPass: RenderPass | undefined;
|
||||
|
||||
current_scene_media_token_id: string | null;
|
||||
can_initialize: boolean;
|
||||
dispatchAppUiState: any; // todo figure out the type
|
||||
// Default params.
|
||||
constructor() {
|
||||
console.log(
|
||||
"If you see this message twice! then it rendered twice, if you see it once it's all good.",
|
||||
);
|
||||
|
||||
// Special async react lifecycle fix
|
||||
// For making sure the editor only gets created onece.
|
||||
this.can_initailize = false;
|
||||
this.can_initialize = false;
|
||||
let one_element = document.getElementById("created-one-element");
|
||||
//if (one_element != null) { return; }
|
||||
this.can_initailize = true;
|
||||
this.can_initialize = true;
|
||||
let newElement = document.createElement("div");
|
||||
newElement.id = "created-one-element";
|
||||
document.body.appendChild(newElement);
|
||||
// life cycle fix
|
||||
|
||||
// Version and name.
|
||||
this.version = "v0.1";
|
||||
this.version = 1.0;
|
||||
// Clock, scene and camera essentials.
|
||||
this.activeScene = null;
|
||||
this.camera = null;
|
||||
this.renderer = null;
|
||||
this.clock = null;
|
||||
this.activeScene = new Scene("" + this.version);
|
||||
this.activeScene.initialize();
|
||||
this.camera;
|
||||
this.renderer;
|
||||
this.clock;
|
||||
this.canvReference = null;
|
||||
this.composer;
|
||||
this.effectFXAA;
|
||||
@@ -75,18 +119,18 @@ class Editor {
|
||||
this.smaaPass;
|
||||
this.bokehPass;
|
||||
// Transform control and selection.
|
||||
this.control = null;
|
||||
this.raycaster = null;
|
||||
this.mouse = null;
|
||||
this.selected = null;
|
||||
this.last_selected = null;
|
||||
this.control;
|
||||
this.raycaster;
|
||||
this.mouse;
|
||||
this.selected;
|
||||
this.last_selected;
|
||||
this.transform_interaction;
|
||||
this.rendering = false;
|
||||
// API.
|
||||
this.api_manager = new APIManager();
|
||||
// Debug & Movement.
|
||||
this.stats = null;
|
||||
this.orbit = null;
|
||||
this.orbit;
|
||||
this.locked = false;
|
||||
// Recording params.
|
||||
this.capturer = null;
|
||||
@@ -101,9 +145,10 @@ class Editor {
|
||||
// Audio Engine Test.
|
||||
|
||||
this.audio_engine = new AudioEngine();
|
||||
this.transform_engine = new TransformEngine();
|
||||
this.transform_engine = new TransformEngine(this.version);
|
||||
this.lipsync_engine = new LipSyncEngine();
|
||||
this.animation_engine = new AnimationEngine();
|
||||
this.animation_engine = new AnimationEngine(this.version);
|
||||
|
||||
this.timeline = new TimeLine(
|
||||
this.audio_engine,
|
||||
this.transform_engine,
|
||||
@@ -112,25 +157,19 @@ class Editor {
|
||||
this.activeScene,
|
||||
);
|
||||
|
||||
this.test_box_uuid = null;
|
||||
this.current_frame = 0;
|
||||
this.test_playback = false;
|
||||
|
||||
//Reactland Callbacks
|
||||
this.current_scene_media_token_id = null;
|
||||
this.dispatchAppUiState = null;
|
||||
}
|
||||
|
||||
// Initializes the main scene and ThreeJS essentials.
|
||||
/*
|
||||
config :{
|
||||
dispatchAppUiState: dispatch function to react app
|
||||
}
|
||||
*/
|
||||
initialize(config) {
|
||||
if (this.can_initailize == false) {
|
||||
initialize(config:any) {
|
||||
if (this.can_initialize == false) {
|
||||
console.log(
|
||||
"Editor Already Initialized",
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.can_initailize == false;
|
||||
this.can_initialize = false;
|
||||
|
||||
// Gets the canvas.
|
||||
this.canvReference = document.getElementById("video-scene");
|
||||
@@ -148,17 +187,14 @@ class Editor {
|
||||
canvas: this.canvReference,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
|
||||
this.renderer.shadowMap.enabled = true;
|
||||
this.renderer.shadowMapSoft = true;
|
||||
this.clock = new THREE.Clock();
|
||||
// Resizes the renderer.
|
||||
this.renderer.setSize(width, height);
|
||||
//document.body.appendChild(this.renderer.domElement);
|
||||
//document.body.appendChild(this.renderer.domElement)
|
||||
window.addEventListener("resize", this.onWindowResize.bind(this));
|
||||
// Current scene for saving and loading.
|
||||
this.activeScene = new Scene();
|
||||
this._configure_post_pro();
|
||||
this.activeScene.initialize();
|
||||
// Controls and movement.
|
||||
this.orbit = new OrbitControls(this.camera, this.renderer.domElement);
|
||||
this.control = new TransformControls(this.camera, this.renderer.domElement);
|
||||
@@ -171,32 +207,95 @@ class Editor {
|
||||
this.onWindowResize();
|
||||
// Creates the main update loop.
|
||||
this.renderer.setAnimationLoop(this.update_loop.bind(this));
|
||||
this.test_playback = false;
|
||||
|
||||
this.timeline.scene = this.activeScene;
|
||||
|
||||
this._test_demo();
|
||||
|
||||
// saving state of the scene
|
||||
this.current_scene_media_token_id = null;
|
||||
//setup reactland Callbacks
|
||||
this.dispatchAppUiState = config.dispatchAppUiState
|
||||
this._test_demo()
|
||||
}
|
||||
|
||||
// uploading some objects for testing to get their media ids from my account.
|
||||
async _upload_for_testing() {
|
||||
// have to upload as a file first
|
||||
// Token comes in from the front end to load the scene from the site.
|
||||
public async loadScene(scene_token: string) {
|
||||
this.dispatchAppUiState({
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADER
|
||||
});
|
||||
|
||||
// This is the default scene.
|
||||
// "m_189p8hj0eyypbg74kkhcpehwpjhnkz" scene with the fox.
|
||||
//let result = await this.api_manager.saveSceneState(this.activeScene.scene)
|
||||
let result = await this.api_manager.getMediaFile(
|
||||
"m_189p8hj0eyypbg74kkhcpehwpjhnkz",
|
||||
if (scene_token != null) {
|
||||
this.current_scene_media_token_id = scene_token;
|
||||
}
|
||||
|
||||
const load_scene_state_response = await this.api_manager.loadSceneState(
|
||||
this.current_scene_media_token_id,
|
||||
);
|
||||
console.log(result);
|
||||
|
||||
console.log(load_scene_state_response)
|
||||
|
||||
const loaded_scene = load_scene_state_response.data["scene"]
|
||||
this.current_scene_media_token_id = load_scene_state_response.data["media_file_token"]
|
||||
this.activeScene.scene.children = loaded_scene.children;
|
||||
|
||||
this.activeScene.scene.children.forEach((child: THREE.Object3D) => {
|
||||
child.parent = this.activeScene.scene;
|
||||
|
||||
if (child.type == "DirectionalLight") {
|
||||
let pos = child.position;
|
||||
let rot = child.rotation;
|
||||
let light = this.activeScene._create_base_lighting();
|
||||
light.position.set(pos.x, pos.y, pos.z);
|
||||
light.rotation.set(rot.x, rot.y, rot.z);
|
||||
this.activeScene.scene.remove(child);
|
||||
}
|
||||
});
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: ACTION_TYPES.HIDE_EDITOR_LOADER
|
||||
});
|
||||
}
|
||||
|
||||
public removeTransformControls() {
|
||||
if (this.control == undefined) { return };
|
||||
if (this.outlinePass == undefined) { return };
|
||||
|
||||
this.last_selected = this.selected;
|
||||
this.control.detach();
|
||||
this.activeScene.scene.remove(this.control);
|
||||
this.outlinePass.selectedObjects = [];
|
||||
}
|
||||
|
||||
public async saveScene(name: string) {
|
||||
// remove controls when saving scene.
|
||||
this.removeTransformControls();
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADER
|
||||
});
|
||||
|
||||
const result = await this.api_manager.saveSceneState(
|
||||
this.activeScene.scene,
|
||||
name,
|
||||
this.current_scene_media_token_id,
|
||||
new TimelineDataState(),
|
||||
)
|
||||
|
||||
const scene_media_token_id = result.data["scene_media_token_id"]
|
||||
if (scene_media_token_id != null) {
|
||||
this.current_scene_media_token_id = scene_media_token_id
|
||||
}
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: ACTION_TYPES.HIDE_EDITOR_LOADER
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async _serialize_timeline() {
|
||||
// note the database from the server is the source of truth for all the data.
|
||||
// Test code here
|
||||
let object = await this.activeScene.load_glb(
|
||||
let object: any = await this.activeScene.load_glb(
|
||||
"./resources/models/fox/fox.glb",
|
||||
);
|
||||
|
||||
@@ -246,15 +345,23 @@ class Editor {
|
||||
async _test_demo() {
|
||||
// note the database from the server is the source of truth for all the data.
|
||||
// Test code here
|
||||
let object = await this.activeScene.load_glb(
|
||||
let object: any = await this.activeScene.load_glb(
|
||||
"./resources/models/fox/fox.glb",
|
||||
);
|
||||
|
||||
// Load timeline creates the the clips from the datastructure and loads them in here.
|
||||
// load object into the engine for lip syncing
|
||||
this.lipsync_engine.load_object(
|
||||
object.uuid,
|
||||
"m_f1jxx4zwy4da2zn0cvdqhha7kqkj72",
|
||||
);
|
||||
this.animation_engine.load_object(
|
||||
object.uuid,
|
||||
"/resources/models/fox/fox_idle.glb",
|
||||
"clip3",
|
||||
);
|
||||
// then it creates clip ui to load the playable clips
|
||||
// then refreshes the timeline.
|
||||
|
||||
// create the clip with the same id for a reference to the media
|
||||
this.timeline.addPlayableClip(
|
||||
@@ -286,11 +393,6 @@ class Editor {
|
||||
400,
|
||||
),
|
||||
);
|
||||
this.animation_engine.load_object(
|
||||
object.uuid,
|
||||
"/resources/models/fox/fox_idle.glb",
|
||||
"clip3",
|
||||
);
|
||||
}
|
||||
|
||||
// Configure post processing.
|
||||
@@ -298,6 +400,10 @@ class Editor {
|
||||
let width = this.canvReference.width;
|
||||
let height = this.canvReference.height;
|
||||
|
||||
if (this.renderer == undefined || this.camera == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.composer = new EffectComposer(this.renderer);
|
||||
this.renderPass = new RenderPass(this.activeScene.scene, this.camera);
|
||||
this.composer.addPass(this.renderPass);
|
||||
@@ -312,7 +418,6 @@ class Editor {
|
||||
this.outlinePass.edgeGlow = 0.2;
|
||||
this.outlinePass.edgeThickness = 1.0;
|
||||
this.outlinePass.pulsePeriod = 3;
|
||||
this.outlinePass.rotate = false;
|
||||
this.outlinePass.usePatternTexture = false;
|
||||
this.outlinePass.visibleEdgeColor.set(0xe66462);
|
||||
|
||||
@@ -350,11 +455,11 @@ class Editor {
|
||||
this.composer.addPass(this.smaaPass);
|
||||
this.composer.addPass(this.bokehPass);
|
||||
|
||||
this.outputPass = new OutputPass();
|
||||
this.composer.addPass(this.outputPass);
|
||||
this.outputPass = new OutputPass()
|
||||
this.composer.addPass(this.outputPass)
|
||||
}
|
||||
|
||||
create_parim(name) {
|
||||
create_parim(name: string) {
|
||||
let uuid = this.activeScene.instantiate(name);
|
||||
}
|
||||
|
||||
@@ -363,9 +468,9 @@ class Editor {
|
||||
this.activeScene.render_mode(this.rendering);
|
||||
|
||||
//if (this.rendering) {
|
||||
// this._remove_post_processing();
|
||||
// this._remove_post_processing()
|
||||
//} else {
|
||||
// this._add_post_processing();
|
||||
// this._add_post_processing()
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -376,28 +481,19 @@ class Editor {
|
||||
this.stopPlayback();
|
||||
} else {
|
||||
// REMOVE THIS NEXT LINE IN PROD ONLY FOR TEST!!!!!
|
||||
//this.activeScene.play_anim_demo(this.activeScene.activeCharacter);
|
||||
//this.activeScene.play_anim_demo(this.activeScene.activeCharacter)
|
||||
}
|
||||
}
|
||||
|
||||
save() {
|
||||
//console.log(this.control);
|
||||
//if(this.selected != null){ this.control.detach(this.selected); }
|
||||
//this.activeScene.scene.remove(this.control);
|
||||
//this.activeScene.scene.remove(this.activeScene.gridHelper);
|
||||
//this.save_manager.save(this.activeScene.scene, this._save_to_cloud.bind(this), this.audio_manager, this.timeline, this.activeScene.animations);
|
||||
//this.activeScene._createGrid();
|
||||
//this.audio_engine.playClip("m_f7jnwt3d1ddchatdk5vaqt0n4mb1hg");
|
||||
//console.log(this.selected);
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: ACTION_TYPES.SHOW_EDITOR_LOADER
|
||||
});
|
||||
|
||||
|
||||
// this.dispatchAppUiState({
|
||||
// type: ACTION_TYPES.HIDE_EDITOR_LOADER
|
||||
// });
|
||||
//console.log(this.control)
|
||||
//if(this.selected != null){ this.control.detach(this.selected) }
|
||||
//this.activeScene.scene.remove(this.control)
|
||||
//this.activeScene.scene.remove(this.activeScene.gridHelper)
|
||||
//this.save_manager.save(this.activeScene.scene, this._save_to_cloud.bind(this), this.audio_manager, this.timeline, this.activeScene.animations)
|
||||
//this.activeScene._createGrid()
|
||||
//this.audio_engine.playClip("m_f7jnwt3d1ddchatdk5vaqt0n4mb1hg")
|
||||
//console.log(this.selected)
|
||||
|
||||
if (this.selected == null) {
|
||||
return;
|
||||
@@ -407,28 +503,31 @@ class Editor {
|
||||
}
|
||||
|
||||
change_camera_view() {
|
||||
//let obj = this.activeScene.get_object_by_uuid(this.test_box_uuid);
|
||||
//this.transform_engine.clips[this.test_box_uuid].step(obj);
|
||||
//let obj = this.activeScene.get_object_by_uuid(this.test_box_uuid)
|
||||
//this.transform_engine.clips[this.test_box_uuid].step(obj)
|
||||
//console.log(this.transform_engine.clips[this.test_box_uuid].current_pos)
|
||||
//this.current_frame += 1;
|
||||
this.test_playback = !this.test_playback;
|
||||
//this.transform_engine.clips[this.test_box_uuid].reset(this.activeScene.get_object_by_uuid(this.test_box_uuid));
|
||||
//this.current_frame += 1
|
||||
//this.transform_engine.clips[this.test_box_uuid].reset(this.activeScene.get_object_by_uuid(this.test_box_uuid))
|
||||
}
|
||||
|
||||
// Basicly Unity 3D's update loop.
|
||||
update_loop(time) {
|
||||
update_loop(time: number) {
|
||||
// Updates debug stats.
|
||||
if (this.stats != null) {
|
||||
this.stats.update();
|
||||
}
|
||||
|
||||
if (this.clock == undefined || this.renderer == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let delta_time = this.clock.getDelta();
|
||||
|
||||
// All calls that are not super important like timeline go here.
|
||||
//this.activeScene.update(delta_time);
|
||||
//this.orbit.update(0.1);
|
||||
//this.activeScene.update(delta_time)
|
||||
//this.orbit.update(0.1)
|
||||
|
||||
//console.log(this.transform_engine.clips[this.test_box_uuid]);
|
||||
//console.log(this.transform_engine.clips[this.test_box_uuid])
|
||||
|
||||
this.timeline.update(delta_time);
|
||||
|
||||
@@ -442,13 +541,16 @@ class Editor {
|
||||
this.timeline.isPlaying = true;
|
||||
}
|
||||
|
||||
change_mode(type) {
|
||||
change_mode(type: any) {
|
||||
if (this.control == undefined) {
|
||||
return;
|
||||
}
|
||||
this.control.mode = type;
|
||||
this.transform_interaction = true;
|
||||
}
|
||||
|
||||
// Sets the fps to a specific number
|
||||
set_fps(fps_number) {
|
||||
set_fps(fps_number: number) {
|
||||
this.cap_fps = fps_number;
|
||||
}
|
||||
|
||||
@@ -456,15 +558,15 @@ class Editor {
|
||||
togglePlayback() {
|
||||
this.togglePlay();
|
||||
if (this.playback == false) {
|
||||
//this.stopPlayback();
|
||||
//this.stopPlayback()
|
||||
this.render_mode();
|
||||
} else {
|
||||
//this.startPlayback();
|
||||
//this.startPlayback()
|
||||
this.render_mode();
|
||||
}
|
||||
}
|
||||
|
||||
async loadWavAsBlob(url) {
|
||||
async loadWavAsBlob(url: string) {
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
return blob;
|
||||
@@ -512,14 +614,17 @@ class Editor {
|
||||
|
||||
// Initializes transform x y z changes.
|
||||
_initialize_control() {
|
||||
if (this.control == undefined) {
|
||||
return;
|
||||
}
|
||||
this.control.addEventListener("change", this.render_scene.bind(this));
|
||||
this.control.addEventListener(
|
||||
"dragging-changed",
|
||||
function (event) {
|
||||
this.orbit.enabled = !event.value;
|
||||
// this.update_properties();
|
||||
}.bind(this),
|
||||
);
|
||||
this.control.addEventListener("dragging-changed", (event: any) => {
|
||||
if (this.orbit == undefined) {
|
||||
return;
|
||||
}
|
||||
this.orbit.enabled = !event.value;
|
||||
// this.update_properties()
|
||||
});
|
||||
this.control.setSize(0.5); // Good default value for visuals.
|
||||
this.raycaster = new THREE.Raycaster();
|
||||
this.mouse = new THREE.Vector2();
|
||||
@@ -536,11 +641,13 @@ class Editor {
|
||||
render_scene() {
|
||||
if (this.composer != null) {
|
||||
this.composer.render();
|
||||
} else {
|
||||
} else if (this.renderer && this.camera) {
|
||||
this.renderer.render(this.activeScene.scene, this.camera);
|
||||
} else {
|
||||
console.error("Could not render to canvas no render or composer!");
|
||||
}
|
||||
|
||||
if (this.rendering) {
|
||||
if (this.rendering && this.renderer && this.clock) {
|
||||
this.playback_location++;
|
||||
let imgData = this.renderer.domElement.toDataURL();
|
||||
this.frame_buffer.push(imgData);
|
||||
@@ -556,9 +663,12 @@ class Editor {
|
||||
// Automaticly resize scene.
|
||||
onWindowResize() {
|
||||
// Calculate the maximum possible dimensions while maintaining the aspect ratio
|
||||
let width = window.innerWidth; // / aspect_adjust;
|
||||
let height = window.innerHeight; // / aspectRatio;
|
||||
let width = window.innerWidth; // / aspect_adjust
|
||||
let height = window.innerHeight; // / aspectRatio
|
||||
|
||||
if (this.camera == undefined || this.renderer == undefined) {
|
||||
return;
|
||||
}
|
||||
// Set the camera aspect to the desired aspect ratio
|
||||
this.camera.aspect = width / height;
|
||||
this.camera.updateProjectionMatrix();
|
||||
@@ -571,17 +681,28 @@ class Editor {
|
||||
}
|
||||
|
||||
// Sets new mouse location usually used in raycasts.
|
||||
onMouseMove(event) {
|
||||
onMouseMove(event: any) {
|
||||
const rect = this.canvReference.getBoundingClientRect();
|
||||
if (this.mouse == undefined) {
|
||||
return;
|
||||
}
|
||||
this.mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
|
||||
this.mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
|
||||
}
|
||||
|
||||
// When the mouse clicks the screen.
|
||||
onMouseClick() {
|
||||
if (
|
||||
this.raycaster == undefined ||
|
||||
this.mouse == undefined ||
|
||||
this.control == undefined ||
|
||||
this.outlinePass == undefined
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.raycaster.setFromCamera(this.mouse, this.camera);
|
||||
let interactable = [];
|
||||
this.activeScene.scene.children.forEach((child) => {
|
||||
let interactable: any[] = [];
|
||||
this.activeScene.scene.children.forEach((child: THREE.Object3D) => {
|
||||
if (child.name != "") {
|
||||
if (child.type == "Mesh" || child.type == "Object3D") {
|
||||
interactable.push(child);
|
||||
@@ -593,24 +714,21 @@ class Editor {
|
||||
if (intersects.length > 0) {
|
||||
if (intersects[0].object.type != "GridHelper") {
|
||||
let currentObject = intersects[0].object;
|
||||
while (currentObject.parent.type !== "Scene") {
|
||||
while (currentObject.parent && currentObject.parent.type !== "Scene") {
|
||||
currentObject = currentObject.parent;
|
||||
}
|
||||
this.selected = currentObject;
|
||||
if (this.selected.type == "Scene") {
|
||||
this.selected = intersects[0].object;
|
||||
}
|
||||
// this.update_properties();
|
||||
// this.update_properties()
|
||||
this.activeScene.scene.add(this.control);
|
||||
this.control.attach(this.selected);
|
||||
this.outlinePass.selectedObjects = [this.selected];
|
||||
this.transform_interaction = true;
|
||||
}
|
||||
} else if (this.transform_interaction == false) {
|
||||
this.last_selected = this.selected;
|
||||
this.control.detach(this.selected);
|
||||
this.activeScene.scene.remove(this.control);
|
||||
this.outlinePass.selectedObjects = [];
|
||||
this.removeTransformControls();
|
||||
} else {
|
||||
this.transform_interaction = false;
|
||||
}
|
||||
@@ -1,24 +1,22 @@
|
||||
|
||||
import { LipSyncTrackClip } from "../datastructures/clips/lipsync_track_clip";
|
||||
import { LipSync } from "./lipsync";
|
||||
import * as THREE from 'three';
|
||||
import { LipSyncClip } from "../datastructures/clips/lipsync_clip";
|
||||
|
||||
|
||||
// needs to be apart of the editor window.
|
||||
export class LipSyncEngine {
|
||||
|
||||
clips: { [key: string]: LipSyncTrackClip } = {}
|
||||
clips: { [key: string]: LipSyncClip } = {}
|
||||
audio_sources: { [key: string]: AudioBufferSourceNode } = {}
|
||||
version: number
|
||||
|
||||
constructor() {
|
||||
|
||||
this.clips = {}
|
||||
this.audio_sources = {}
|
||||
this.version = 1.0
|
||||
}
|
||||
|
||||
load_object(object_uuid: string, audio_media_id: string) {
|
||||
this.clips[object_uuid] = new LipSyncTrackClip(this.version, audio_media_id, 1.0);
|
||||
this.clips[object_uuid] = new LipSyncClip(this.version, audio_media_id, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ class Scene {
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.scene = new THREE.Scene();
|
||||
this._createGrid();
|
||||
this._create_base_lighting();
|
||||
this._create_skybox();
|
||||
@@ -98,6 +99,7 @@ class Scene {
|
||||
'/resources/skybox/night/Night_Moon_Burst_Cam_1_Back-Z.png',
|
||||
]);
|
||||
this.scene.background = texture;
|
||||
console.log("Backround creation..")
|
||||
}
|
||||
|
||||
// deafult image skybox.
|
||||
@@ -128,6 +130,7 @@ class Scene {
|
||||
|
||||
this.scene.add(directional_light);
|
||||
this.scene.add(directional_light.target);
|
||||
return directional_light;
|
||||
}
|
||||
|
||||
_createGrid() {
|
||||
|
||||
@@ -6,31 +6,54 @@ import AudioEngine from "./audio_engine";
|
||||
import TransformEngine from "./transform_engine";
|
||||
import LipSyncEngine from "./lip_sync_engine";
|
||||
import AnimationEngine from "./animation_engine";
|
||||
import { AudioClip } from "../models/track";
|
||||
import { AnimationClip } from "three";
|
||||
import { LipSyncClip } from "../datastructures/clips/lipsync_clip";
|
||||
import { TransformClip } from "../datastructures/clips/transform_clip";
|
||||
|
||||
// Every object uuid / entity has a track.
|
||||
export class TimelineCurrentReactState {
|
||||
isEditable: boolean
|
||||
export class TimelineCurrentState {
|
||||
isEditable: boolean
|
||||
selectedObjectID: number
|
||||
|
||||
constructor() {
|
||||
this.isEditable = true // can add clips to it
|
||||
this.selectedObjectID = 0
|
||||
}
|
||||
}
|
||||
|
||||
export class TimelineDataState {
|
||||
timelineItems: ClipUI[]
|
||||
scrubberPosition: number
|
||||
|
||||
// Data to serialize with the scene, used to load into each engine.
|
||||
audioClips:AudioClip[]
|
||||
animationClips:AnimationClip[]
|
||||
lipSyncClips:LipSyncClip[]
|
||||
transformClips:TransformClip[]
|
||||
|
||||
constructor(timelineItems: ClipUI[] = [],
|
||||
audioClips: AudioClip[] = [],
|
||||
animationClips: AnimationClip[] = [],
|
||||
transformClips: TransformClip[] = [],
|
||||
lipSyncClips: LipSyncClip[] = [],
|
||||
scrubberPosition:number = 0) {
|
||||
this.timelineItems = timelineItems
|
||||
this.scrubberPosition = scrubberPosition
|
||||
this.audioClips = audioClips
|
||||
this.animationClips = animationClips
|
||||
this.lipSyncClips = lipSyncClips
|
||||
this.transformClips = transformClips
|
||||
}
|
||||
}
|
||||
|
||||
export class TimeLine {
|
||||
|
||||
|
||||
timelineItems: ClipUI[]
|
||||
runningClips: ClipUI[]
|
||||
|
||||
//timerID: NodeJS.Timeout | null
|
||||
timeLineLimit: number
|
||||
scrubberPosition: number
|
||||
isPlaying: boolean
|
||||
|
||||
timelineState: TimelineCurrentReactState
|
||||
|
||||
// plays audio
|
||||
audioEngine: AudioEngine
|
||||
// key framing
|
||||
@@ -50,10 +73,9 @@ export class TimeLine {
|
||||
|
||||
this.timelineItems = []
|
||||
this.timeLineLimit = 60 * 10 // 10 seconds
|
||||
this.runningClips = []
|
||||
|
||||
this.isPlaying = false
|
||||
this.scrubberPosition = 0 // in frames into the tl
|
||||
this.timelineState = new TimelineCurrentReactState()
|
||||
|
||||
// this will be used to play the audio clips
|
||||
this.audioEngine = audioEngine
|
||||
@@ -64,60 +86,60 @@ export class TimeLine {
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
async addPlayableClip(clip: ClipUI): Promise<void> {
|
||||
public async addPlayableClip(clip: ClipUI): Promise<void> {
|
||||
this.timelineItems.push(clip)
|
||||
}
|
||||
|
||||
// when given a media id item it will create the clip.
|
||||
// Then the clip will be loaded by the engines, if they come from outside of the loaded scene.
|
||||
async createClipOffset(media_id: string, object_uuid:string, type: string): Promise<void> {
|
||||
public async createClipOffset(media_id: string, object_uuid:string, type: string): Promise<void> {
|
||||
// use engine to load based off media id and type animation | transform |
|
||||
|
||||
}
|
||||
|
||||
// this will update the state of the clips based off uuid easing?
|
||||
async updateClip(clip_uuid: string, updates: AnyJson): Promise<void> {
|
||||
public async updateClip(clip_uuid: string, updates: AnyJson): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
async deleteClip(clip_uuid: string): Promise<void> {
|
||||
public async deleteClip(clip_uuid: string): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
// Events that will trigger from react
|
||||
async clipDidEnterDropZone() {
|
||||
public async clipDidEnterDropZone() {
|
||||
|
||||
}
|
||||
|
||||
async clipDidExitDropZone() {
|
||||
public async clipDidExitDropZone() {
|
||||
|
||||
}
|
||||
|
||||
// timeline controls this.
|
||||
async scrubberDidStart(offset_frame: number) {
|
||||
public async scrubberDidStart(offset_frame: number) {
|
||||
|
||||
}
|
||||
|
||||
async scrub(offset_frame: number): Promise<void> {
|
||||
public async scrub(offset_frame: number): Promise<void> {
|
||||
// only stream through to the position and rotation keyframes
|
||||
// debounce not really
|
||||
}
|
||||
|
||||
async scrubberDidStop(offset_frame: number) {
|
||||
public async scrubberDidStop(offset_frame: number) {
|
||||
|
||||
}
|
||||
// public streaming events into the timeline from
|
||||
async setScrubberPosition(offset: number) {
|
||||
public async setScrubberPosition(offset: number) {
|
||||
this.scrubberPosition = offset // in ms
|
||||
}
|
||||
|
||||
// should play from the clip that is closest to the to scrubber
|
||||
async play(): Promise<void> {
|
||||
public async play(): Promise<void> {
|
||||
console.log(`Starting Timeline`)
|
||||
this.isPlaying = true
|
||||
}
|
||||
|
||||
async reset_scene() {
|
||||
private async reset_scene() {
|
||||
for (const element of this.timelineItems) {
|
||||
if (element.type == "transform") {
|
||||
let object = this.scene.get_object_by_uuid(element.object_uuid);
|
||||
@@ -139,7 +161,7 @@ export class TimeLine {
|
||||
}
|
||||
|
||||
// called by the editor update loop on each frame
|
||||
async update(deltatime:number) {
|
||||
public async update(deltatime:number) {
|
||||
if (this.isPlaying == false) return; // start and stop
|
||||
|
||||
if (this.scrubberPosition <= 0) {
|
||||
@@ -194,7 +216,7 @@ export class TimeLine {
|
||||
}
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
private async stop(): Promise<void> {
|
||||
this.isPlaying = false
|
||||
console.log(`Stopping Timeline`)
|
||||
}
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
import { TransformTrackClip } from "../datastructures/clips/transform_track_clip";
|
||||
import { TransformClip } from "../datastructures/clips/transform_clip";
|
||||
import * as THREE from 'three';
|
||||
|
||||
class TransformEngine {
|
||||
clips: { [key: string]: TransformTrackClip } = {};
|
||||
clips: { [key: string]: TransformClip } = {};
|
||||
version: number;
|
||||
length: number;
|
||||
|
||||
constructor(version: number, length: number) {
|
||||
constructor(version: number) {
|
||||
this.version = version;
|
||||
this.length = length;
|
||||
this.clips = {};
|
||||
}
|
||||
|
||||
loadObject(object_uuid: string, clip_length:number = 2) {
|
||||
this.clips[object_uuid] = new TransformTrackClip(this.version, object_uuid, clip_length, object_uuid); // replace the last object_uuid with the media ID when its ready.
|
||||
this.clips[object_uuid] = new TransformClip(this.version, object_uuid, clip_length, object_uuid); // replace the last object_uuid with the media ID when its ready.
|
||||
return this.clips[object_uuid]
|
||||
}
|
||||
|
||||
addFrame(object: THREE.Object3D, clip_length:number = 2) {
|
||||
if(this.clips[object.uuid] == null) {
|
||||
this.clips[object.uuid] = new TransformTrackClip(this.version, object.uuid, clip_length);
|
||||
this.clips[object.uuid] = new TransformClip(this.version, object.uuid, clip_length);
|
||||
}
|
||||
this.clips[object.uuid].add_position(object.position);
|
||||
this.clips[object.uuid].add_rotation(new THREE.Vector3(object.rotation.x, object.rotation.y, object.rotation.z));
|
||||
|
||||
Reference in new issue
Block a user