diff --git a/app/pages/PageEnigma/PageEnigmaComponent.tsx b/app/pages/PageEnigma/PageEnigmaComponent.tsx index 6f9f039eba..0d22a666a5 100644 --- a/app/pages/PageEnigma/PageEnigmaComponent.tsx +++ b/app/pages/PageEnigma/PageEnigmaComponent.tsx @@ -24,7 +24,7 @@ export const PageEnigmaComponent = () => { useEffect(()=>{ setTimeout(()=>dispatchAppUiState({ type: ACTION_TYPES.HIDE_EDITOR_LOADER - }), 3000); + }), 500); },[]); return ( diff --git a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx index 39082ebb35..3ed0d03d8d 100644 --- a/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx +++ b/app/pages/PageEnigma/comps/ControlsTopButtons/ControlsTopButtons.tsx @@ -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 = () => {}; diff --git a/app/pages/PageEnigma/contexts/EngineProvider.tsx b/app/pages/PageEnigma/contexts/EngineProvider.tsx index c10b04eb6e..899de9ff72 100644 --- a/app/pages/PageEnigma/contexts/EngineProvider.tsx +++ b/app/pages/PageEnigma/contexts/EngineProvider.tsx @@ -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 diff --git a/app/pages/PageEnigma/datastructures/clips/animation_track_clip.ts b/app/pages/PageEnigma/datastructures/clips/animation_clip.ts similarity index 88% rename from app/pages/PageEnigma/datastructures/clips/animation_track_clip.ts rename to app/pages/PageEnigma/datastructures/clips/animation_clip.ts index ea38c59885..50110653d4 100644 --- a/app/pages/PageEnigma/datastructures/clips/animation_track_clip.ts +++ b/app/pages/PageEnigma/datastructures/clips/animation_clip.ts @@ -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; diff --git a/app/pages/PageEnigma/datastructures/clips/audio_track_clip.ts b/app/pages/PageEnigma/datastructures/clips/audio_clip.ts similarity index 91% rename from app/pages/PageEnigma/datastructures/clips/audio_track_clip.ts rename to app/pages/PageEnigma/datastructures/clips/audio_clip.ts index 805741b550..cc5c19079c 100644 --- a/app/pages/PageEnigma/datastructures/clips/audio_track_clip.ts +++ b/app/pages/PageEnigma/datastructures/clips/audio_clip.ts @@ -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"; diff --git a/app/pages/PageEnigma/datastructures/clips/lipsync_track_clip.ts b/app/pages/PageEnigma/datastructures/clips/lipsync_clip.ts similarity index 94% rename from app/pages/PageEnigma/datastructures/clips/lipsync_track_clip.ts rename to app/pages/PageEnigma/datastructures/clips/lipsync_clip.ts index 412b8b0bc3..a06808e72c 100644 --- a/app/pages/PageEnigma/datastructures/clips/lipsync_track_clip.ts +++ b/app/pages/PageEnigma/datastructures/clips/lipsync_clip.ts @@ -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)); diff --git a/app/pages/PageEnigma/datastructures/clips/transform_track_clip.ts b/app/pages/PageEnigma/datastructures/clips/transform_clip.ts similarity index 89% rename from app/pages/PageEnigma/datastructures/clips/transform_track_clip.ts rename to app/pages/PageEnigma/datastructures/clips/transform_clip.ts index 729f36621f..f167843319 100644 --- a/app/pages/PageEnigma/datastructures/clips/transform_track_clip.ts +++ b/app/pages/PageEnigma/datastructures/clips/transform_clip.ts @@ -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; diff --git a/app/pages/PageEnigma/datastructures/scene/scene_object.ts b/app/pages/PageEnigma/datastructures/scene/scene_object.ts deleted file mode 100644 index 1242295f67..0000000000 --- a/app/pages/PageEnigma/datastructures/scene/scene_object.ts +++ /dev/null @@ -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); - } -} diff --git a/app/pages/PageEnigma/js/animation_engine.ts b/app/pages/PageEnigma/js/animation_engine.ts index fb155ea7d2..1ec3dc1bf7 100644 --- a/app/pages/PageEnigma/js/animation_engine.ts +++ b/app/pages/PageEnigma/js/animation_engine.ts @@ -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", diff --git a/app/pages/PageEnigma/js/api_manager.ts b/app/pages/PageEnigma/js/api_manager.ts index f1e484af16..f1ffe4b311 100644 --- a/app/pages/PageEnigma/js/api_manager.ts +++ b/app/pages/PageEnigma/js/api_manager.ts @@ -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 { - 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 { + 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 { + 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 => { + 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 { - 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 { - // 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 { + /** + * This recieves the bucket path of a media file + * @param media_file_token + * @returns + */ + private async getMediaFile(media_file_token: string): Promise { 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 { - - 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 { + 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 { + + private async uploadGLB( + file: File, + scene_media_file_token: string | null, + ): Promise { + 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 { + 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 { 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 { - // /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 { + return ""; } + } export default APIManager; diff --git a/app/pages/PageEnigma/js/audio_engine.ts b/app/pages/PageEnigma/js/audio_engine.ts index 17e36ac368..e6576636da 100644 --- a/app/pages/PageEnigma/js/audio_engine.ts +++ b/app/pages/PageEnigma/js/audio_engine.ts @@ -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. diff --git a/app/pages/PageEnigma/js/editor.js b/app/pages/PageEnigma/js/editor.ts similarity index 66% rename from app/pages/PageEnigma/js/editor.js rename to app/pages/PageEnigma/js/editor.ts index 055eff14a6..e10f37f004 100644 --- a/app/pages/PageEnigma/js/editor.js +++ b/app/pages/PageEnigma/js/editor.ts @@ -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; } diff --git a/app/pages/PageEnigma/js/lip_sync_engine.ts b/app/pages/PageEnigma/js/lip_sync_engine.ts index 15d808e1e7..1bfb590da2 100644 --- a/app/pages/PageEnigma/js/lip_sync_engine.ts +++ b/app/pages/PageEnigma/js/lip_sync_engine.ts @@ -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); } } diff --git a/app/pages/PageEnigma/js/scene.ts b/app/pages/PageEnigma/js/scene.ts index 125a2663d4..52693599cf 100644 --- a/app/pages/PageEnigma/js/scene.ts +++ b/app/pages/PageEnigma/js/scene.ts @@ -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() { diff --git a/app/pages/PageEnigma/js/timeline.ts b/app/pages/PageEnigma/js/timeline.ts index 4803e30349..208a7e20e9 100644 --- a/app/pages/PageEnigma/js/timeline.ts +++ b/app/pages/PageEnigma/js/timeline.ts @@ -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 { + public async addPlayableClip(clip: ClipUI): Promise { 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 { + public async createClipOffset(media_id: string, object_uuid:string, type: string): Promise { // 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 { + public async updateClip(clip_uuid: string, updates: AnyJson): Promise { } - async deleteClip(clip_uuid: string): Promise { + public async deleteClip(clip_uuid: string): Promise { } // 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 { + public async scrub(offset_frame: number): Promise { // 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 { + public async play(): Promise { 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 { + private async stop(): Promise { this.isPlaying = false console.log(`Stopping Timeline`) } diff --git a/app/pages/PageEnigma/js/transform_engine.ts b/app/pages/PageEnigma/js/transform_engine.ts index c5eeb170b0..e1e29ccf6e 100644 --- a/app/pages/PageEnigma/js/transform_engine.ts +++ b/app/pages/PageEnigma/js/transform_engine.ts @@ -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));