Files
lo-th-Comfyui_three_js/js/ThreeCanvas.js
T
AlekPet ee351ea6ba Inplemented select model form list
Support saves selected model in list when reloaded and loaded page
browser.
Write backend code get models in json format, and create file json default models for local server.
Add buttons refresh model list
2024-12-06 18:55:48 +03:00

992 lines
32 KiB
JavaScript

import * as THREE from "./lib/three.module.js";
import { OrbitControls } from "./lib/jsm/controls/OrbitControls.js";
import { RGBELoader } from './lib/jsm/loaders/RGBELoader.js';
import { GLTFLoader } from './lib/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from './lib/jsm/loaders/DRACOLoader.js';
import { EffectComposer } from './lib/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from './lib/jsm/postprocessing/RenderPass.js';
import { ShaderPass } from './lib/jsm/postprocessing/ShaderPass.js';
import { SobelOperatorShader } from './lib/jsm/shaders/SobelOperatorShader.js';
import { LuminosityShader } from './lib/jsm/shaders/LuminosityShader.js';
import { ExposureShader } from './lib/jsm/shaders/ExposureShader.js';
import { BrightnessContrastShader } from './lib/jsm/shaders/BrightnessContrastShader.js';
import { Depth, Outline, Inline, BlackAll, sobelShader, cannyEdgeShader, thresholdShader } from "./lib/Toon.js";
import { Hub } from "./lib/Hub.js";
import { Files } from "./lib/Files.js";
import { Tools } from "./lib/Tools.js";
import { $el } from "./lib/utils.js";
// Class ThreeCanvas
export class ThreeCanvas {
constructor(node, widget, params ={} ) {
const {
w = 512,
h = 512,
r = 1,
offset = 10,
savedData = {},
views3 = false,
fixCamers = true
} = params
this.widgeImageThree = widget;
this.node = node;
this.size = { w, h, r, offset };
this.savedData = savedData;
this.objects = [];
this.needResize = false;
this.pixelRatio = 1;
this.enableDrag = false;
this.withHelper = true;
this.fov = 55;
// lock scale if false
this.autoScale = !true;
this.autoAutoAnim = false;
this.useLinesComposer = true;
// Views3
this.VIEWS3 = views3
// Fix camers
this.fixCamers = fixCamers;
// List models
this.listModels = []
}
getDom(idx=0) {
return this.tools[idx].renderer.domElement;
}
getWrapper(idx=0){
return this.wrappers[idx]
}
createWrappers(canvasNames){
// Operators "Sobel", "Scharr", "Prewitt"
const linesOperators = ["Sobel", "Prewitt", "Scharr", "Canny"];
const listLines = $el("select.linesMethod_box",{
style: {
fontSize: "0.5rem",
color: "var(--p-gray-300)",
position: "absolute",
opacity: 0.7,
gap: "3px",
top: "-15px",
outline: 0
},
onchange: (e)=> {
this.sobelPassSelect = e.target.value
this.composer = null
this.render()
},
title: "Selectd operator for lines"
}, linesOperators.map((val, idx)=> $el("option", {
style: { fontSize: "1rem" },
value: idx,
textContent: val,
selected: val === "Sobel",
}))
)
// Wrapper
const wrapperCanvas = canvasNames.map((data, idx)=> {
const wrapper = $el(`div.wrapperThreeViewCanvas.threeview_wrapper_${data.name}`, {
style: { borderColor: data.color, },
});
if(idx === 1) wrapper.append(listLines)
wrapper.append(this.getDom(idx))
return wrapper
})
return wrapperCanvas
}
// get models function
async getModelRequest (pWrapper) {
// Create options elements
const addOptionsEl = (result) => {
const threeCanvasList = pWrapper.querySelector(".threeCanvasListModels")
const models_options = []
models_options.push($el("option", {textContent: "Select model", disabled: true, selected: true}))
threeCanvasList.innerHTML = ""
for(let type in result.models){
models_options.push($el("option", {textContent: `[${type[0].toUpperCase()+type.slice(1)}]`, disabled: true}))
models_options.push(...result.models[type].map((m)=> {
let path = m.path
const [subfolder, name] = path.split("/")
if(path.indexOf("ThreeViewModels/") === -1){
path = new URL(path, import.meta.url).href
} else {
path = `/view?filename=${name}&type=input&subfolder=${subfolder}`
}
this.listModels.push(name)
return $el("option", {value: path, textContent: m.name, dataset: {path: m.path}})
}))
}
threeCanvasList.append(...models_options)
}
// Get models
return fetch("/lth/models", { cache: "no-store" }).then((resp)=> resp?.json()).then((result)=>{
if(result?.models){
addOptionsEl(result)
}
}).catch((err)=>{
console.log("Error loading list, loading default list!")
fetch("./models_default.json", { cache: "no-store" }).then((resp)=>resp.json()).then((result)=>{
if(result){
addOptionsEl(result)
}
})
});
}
createPanel(){
// Add panel widget
const panelWrapper = $el("div.threeCanvasPanelWrapper", {}, [
$el(
"div.threeCanvasPanel",
[
$el("button.threeCanvasButton.threeCanvasAdd", {
textContent: "Load",
onclick: (e) => {
this.load()
},
}),
$el("button.threeCanvasButton.threeCanvasDel", {
textContent: "X",
onclick: (e) => this.clear(true),
}),
$el("select.threeCanvasListModels", {
onchange: (e) => {
const target = e.target
this.loaderGltf.load(target.value, ( glb ) => {
this.addModel( glb );
// Save currentModel path
this.savedData.currentModel = target.options[target.selectedIndex].dataset?.path
panelWrapper.parentElement.dispatchEvent( new CustomEvent("threeview_model_added", { bubbles: true, detail: {currentModel: this.savedData.currentModel}}) )
},
( data ) => {/* console.log( `Loaded data: ${data.loaded}/${data.total}` */},
( err ) => {
console.log( err );
this.addHeadTest();
})
},
}),
$el("button.threeCanvasListModelsRefresh", {
textContent: "↺",
onclick: (e)=>{
e.target.style.disabled = true
this.getModelRequest(panelWrapper).then(()=> {e.target.style.disabled = false})
}
}),
$el("button.threeCanvasButton.threeCanvasSize", {
textContent: "Canvas size",
onclick: (e) => {
try {
let w = +prompt("Widht:", this.size.w);
let h = +prompt("Height:", this.size.h);
// Simple check...
if (!w || w <= 0) w = this.size.w;
if (!h || h <= 0) h = this.size.h;
if (w) this.setCanvasSize(w, h);
} catch (error) {
console.log(error);
}
// add validate check size
},
}),
$el("div.threeCanvasViews3Box", {
},[
$el("button.threeCanvasButton.threeCanvasViews3", {
textContent: "All Views",
onclick: (e) => {
this.VIEWS3 = !this.VIEWS3;
this.panelWrapper.querySelector(".threeCanvasViews3_camerafix").style.display = this.VIEWS3 ? "block": "none";
// threeCanvas.fixCamers = threeCanvas.VIEWS3
this.setViews3(this.wrappers);
},
}),
$el("input.threeCanvasViews3_camerafix", {
type: "checkbox",
title: "Fix all camers",
checked: this.fixCamers,
style: {
display: "none",
},
onchange: (e)=>{
this.fixCamers = !!e.target.checked
this.composer = null
},
})]),
]
),
]);
this.getModelRequest(panelWrapper)
return panelWrapper
}
init( canvasNames ) {
// Calculate aspect ratio
this.aspectRatio = this.size.w / this.size.h;
// Set three.js scene
const scene = new THREE.Scene();
// Renderer setup 1 COLOR
const renderer1 = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer1.setSize( this.size.w, this.size.h, false );
renderer1.setPixelRatio( this.pixelRatio );
renderer1.setClearColor( 0x000000, 1 );
renderer1.shadowMap.enabled = true;
renderer1.toneMapping = THREE.ACESFilmicToneMapping;
renderer1.toneMappingExposure = 0.8;
//renderer1.outputColorSpace = THREE.SRGBColorSpace;
// renderer1.domElement.style.cssText = "position:absolute; margin:0; padding:0; border:1px solid black;";
renderer1.domElement.setAttribute("view", "LEFT")
renderer1.domElement.classList.add("threeview_renderer")
// Renderer setup 2 LINES
const renderer2 = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer2.setSize( this.size.w, this.size.h, false );
renderer2.setPixelRatio( this.pixelRatio );
renderer2.setClearColor( 0x000000, 1 );
renderer1.shadowMap.enabled = false;
renderer2.autoClear = false;
// renderer2.domElement.style.cssText = "position:absolute; margin:0; padding:0; border:1px solid red;";
renderer2.domElement.setAttribute("view", "TOP")
renderer2.domElement.classList.add("threeview_renderer")
// Renderer setup 3 DEPTH
const renderer3 = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer3.setSize( this.size.w, this.size.h, false );
renderer3.setPixelRatio( this.pixelRatio );
renderer3.setClearColor( 0x000000, 1 );
// renderer3.domElement.style.cssText = "position:absolute; margin:0; padding:0; border:1px solid yellow;";
renderer3.domElement.setAttribute("view", "FRONT")
renderer3.domElement.classList.add("threeview_renderer")
// Renderer setup 4 NORMAL
const renderer4 = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer4.setSize( this.size.w, this.size.h, false );
renderer4.setPixelRatio( this.pixelRatio );
renderer4.setClearColor( 0x8080ff, 1 );
// renderer4.domElement.style.cssText = "position:absolute; margin:0; padding:0; border:1px solid #8080ff;";
renderer4.domElement.setAttribute("view", "FRONT")
renderer4.domElement.classList.add("threeview_renderer")
// Camera setup 1
const camera1 = new THREE.PerspectiveCamera(this.fov, this.size.r, 0.1, 10);
camera1.position.set(0, 0, 5); // front
camera1.lookAt(0, 0, 0);
// Camera setup 2
const camera2 = new THREE.PerspectiveCamera(this.fov, this.size.r, 0.1, 10);
camera2.position.set(0, 0, 5); // top
camera2.lookAt(0, 0, 0);
// Camera setup 3
const camera3 = new THREE.PerspectiveCamera(this.fov, this.size.r, 0.1, 10);
camera3.position.set(0, 0, 5); // front
camera3.lookAt(0, 0, 0);
// Camera setup 4
const camera4 = new THREE.PerspectiveCamera(this.fov, this.size.r, 0.1, 10);
camera3.position.set(0, 0, 5); // front
camera3.lookAt(0, 0, 0);
const depthMaterial = new THREE.MeshDepthMaterial()
//depthMaterial.displacementScale = 10
const normalMaterial = new THREE.MeshNormalMaterial()
// Renderers
this.tools = [
{ type:'color', renderer: renderer1, camera: camera1, material:null },
{ type:'lines', renderer: renderer2, camera: camera2, material: null }, // delete normal map incorrect show lines
{ type:'depth', renderer: renderer3, camera: camera3, material:depthMaterial },
{ type:'normal', renderer: renderer4, camera: camera4, material:normalMaterial }
];
// Create wrappers for canvases
this.wrappers = this.createWrappers(canvasNames)
this.threeWrapper = $el("div.threeWrapper", {}, this.wrappers)
// Create panel
this.panelWrapper = this.createPanel()
// Views3
this.setViews3()
/*let m0 = new THREE.Mesh(new THREE.BoxGeometry(5,5,5))
let b0 = new THREE.BoxHelper(m0)
let m1 = new THREE.Mesh(new THREE.BoxGeometry(3,3,3))
let b1 = new THREE.BoxHelper(m1, 0xFF0000 )
scene.add(b0, b1)*/
const controls = new OrbitControls( camera1, renderer1.domElement );
controls.enableDamping = false;
controls.maxDistance = 8;
controls.minDistance = 0.01;
controls.target.set(0, 0, 0);
controls.update();
controls.addEventListener( 'change', this.render.bind(this));
//controls.addEventListener( 'end', this.sendFileToServer.bind(this, this.widgeImageThree.value));
// drop model direcly on view
if( this.enableDrag ){
document.body.addEventListener( 'dragover', function(e){ e.preventDefault() }, false );
document.body.addEventListener( 'dragend', function(e){ e.preventDefault() }, false );
document.body.addEventListener( 'dragleave', function(e){ e.preventDefault()}, false );
document.body.addEventListener( 'drop', this.drop.bind(this), false );
}
this.camera = camera1;
this.controls = controls;
this.scene = scene;
// hub for all setting
this.hub = new Hub( this );
this.helper = new THREE.Group()
this.scene.add(this.helper);
this.light = new THREE.Group()
this.scene.add(this.light);
this.initLight();
this.initEnvmap();
this.initLoader();
if(!this.savedData?.currentModel) this.addHeadTest();
this.render();
if(this.autoAutoAnim) this.animate();
}
initEnvmap( url = `./assets/clear.hdr` ){
const envUrl = new URL(url, import.meta.url);
const self = this;
new RGBELoader().load( envUrl, function ( texture ) {
texture.mapping = THREE.EquirectangularReflectionMapping;
//scene.background = texture;
self.scene.environment = texture;
//self.scene.environmentIntensity = 0.8
});
}
initLight(){
this.sun = new THREE.DirectionalLight( 0xFFFFFF, 0 );
this.light.add( this.sun, this.sun.target );
// debug sun position
//this.sunHelper = new THREE.CameraHelper( this.sun.shadow.camera )
//this.light.add( this.sunHelper );
//this.light.add( new THREE.AmbientLight( 0xffffFF, 0.2) );
}
setLight( option = {} ){
const o = {
position:[0,1,0],
target:[0,0,0],
intensity:3,
quality:1024,
bias:-0.0005,
radius:4,
range:0.2,
near:0.1,
far:4,
...option
}
const sun = this.sun;
sun.intensity = o.intensity;
sun.position.fromArray(o.position);
sun.target.position.fromArray(o.target);
//sun.updateWorldMatrix( true, true );
//sun.target.updateWorldMatrix( true, true );
const s = sun.shadow;
const c = s.camera;
c.top = c.right = o.range;
c.bottom = c.left = -o.range;
c.near = o.near;
c.far = o.far;
c.updateProjectionMatrix();
s.mapSize.width = s.mapSize.height = o.quality;
s.radius = o.radius;
s.bias = o.bias;
s.needsUpdate = true;
sun.castShadow = true;
if(this.sunHelper) this.sunHelper.update();
}
setViews3(){
if( this.VIEWS3 ){
this.threeWrapper.classList.add("threeWrapper3Views");
} else {
this.threeWrapper.classList.remove("threeWrapper3Views");
}
this.wrappers.slice(1).map((v)=> Object.assign(v.style, {
display: this.VIEWS3 ? "block" : "none"
}))
}
// import api directlly break index.html preview !!
setApi( api ){ this.api = api; }
initLoader(){
const dracoPath = new URL(`./lib/jsm/libs/draco/gltf/`, import.meta.url);
const dracoLoader = new DRACOLoader().setDecoderPath( dracoPath.href )
dracoLoader.setDecoderConfig({ type: 'js' });
this.loaderGltf = new GLTFLoader().setDRACOLoader(dracoLoader);
}
clear(b){
Tools.reset();
if(this.hub) this.hub.clear();
if( this.model ) this.scene.remove(this.model);
this.helper.children = [];
if(b) this.render();
}
async load(params = {}){
const loadData = await new Promise((res)=>{
Files.load({ type:'glb', callback: (content, fname, ftype) => res({content, fname, ftype}) })
})
if( !loadData ) {
console.log("Error load model!")
return;
}
const {content, fname, ftype} = loadData
this.directGlb( content, fname, params );
if(this.listModels.includes(fname)){
console.log(`Model name ${fname} already exist in the list loaded models!`)
return;
}
// Load file to server
const body = new FormData();
body.append("image", new Blob([content]), fname);
body.append("subfolder", "ThreeViewModels");
const resp = await this.api.fetchApi("/upload/image", {
method: "POST",
body,
});
if (resp.status === 200) {
const data = await resp.json();
let path = data.name;
if (data.subfolder) path = data.subfolder + "/" + path;
this.savedData.currentModel = path
}
}
drop( e ){
e.preventDefault();
const file = e.dataTransfer.files[0];
const reader = new FileReader();
const name = file.name;
const type = name.substring(name.lastIndexOf('.')+1, name.length );
const finalName = name.substring( name.lastIndexOf('/')+1, name.lastIndexOf('.') );
reader.readAsArrayBuffer( file );
if( type==='glb' ) e.stopPropagation()
reader.onload = function ( e ) {
if( type==='glb' ) this.directGlb( e.target.result, finalName )
}.bind(this);
}
directGlb( data, name ){
this.loaderGltf.parse( data, name, ( glb ) => { this.addModel( glb ); })
}
addHeadTest( url = `./assets/head.glb` ){
const self = this;
const scene = this.scene;
const headModel = new URL(url, import.meta.url);
let u = headModel.href;
this.defPath = u.substring( 0, u.lastIndexOf('/')+1 );
/*const light = new THREE.PointLight( 0x0080FF, 300 );
light.position.set(-5,5,-10)
scene.add( light );
const light2 = new THREE.PointLight( 0xff8000, 100 );
light2.position.set(5,-5,-5)
scene.add( light2 );
const light3 = new THREE.PointLight( 0xFFFFFF, 100 );
light3.position.set(0,5,5)
scene.add( light3 );*/
this.loaderGltf.load( headModel.href, ( glb ) => {
this.addModel( glb );
// Load camera data
if( this.savedData?.camera ){
this.loadCameraState( this.savedData.camera )
}
})
}
addModel( glb ){
this.clear();
const model = glb.scene || glb.scenes[0];
const clips = glb.animations || [];
// Apply automorph to model //!
// Convention model name is ModelNale__M__TypeOfMorph
const haveMorph = Tools.autoMorph( model );
// get skinning
const haveSkinning = Tools.autoSkinning( model );
// active shadow
Tools.autoShadow( model );
// [BUG] Set position, when delete model and add again, position unknown and no visible model
model.position.set(0,0,0)
model.updateMatrixWorld();
let b0 = new THREE.BoxHelper( model, 0x201924 );
//this.helper.add(b0)
b0.geometry.computeBoundingSphere()
b0.geometry.computeBoundingBox()
const box = b0.geometry.boundingBox;
const sizer = box.getSize(new THREE.Vector3()).length()
const radius = b0.geometry.boundingSphere.radius;
const center = box.getCenter(new THREE.Vector3());
//console.log( radius, sizer )
if(haveMorph) this.hub.addMorph( Tools.MorphModel );
//Tools.setMorph(Tools.MorphModel.face,'Smile_Open', 1)
let lightpos = center.clone().add( new THREE.Vector3(-radius*0.3,radius*2, radius) )
let pos = center.clone().add( new THREE.Vector3(0,0,radius*2) )
//let near = radius/4;
//let far = radius*4;
let near = sizer*0.1;
let far = sizer*2;
this.scene.add(model);
this.model = model;
this.setLight({
position:lightpos.toArray(),
target:center.toArray(),
range:radius*2,
near:near,
far:far,
})
// update camera and render
this.setCamera( pos, center, near, far );
}
/*addObjectToScene(type, parameters = {}) {
const objectNew = new ThreeObject(type, parameters);
if (parameters.update && parameters.update instanceof Function)
objectNew.updateObject = parameters.update.bind(objectNew);
this.objects.push(objectNew);
this.scene.add(objectNew.object);
this.render();
}*/
setCamera( position, center, near, far ) {
const self = this;
//this.controls.reset()
this.tools.map((data)=>{
const camera = data.camera
if( camera ){
camera.aspect = self.size.r;
camera.fov = self.fov;
camera.position.copy( position );
camera.lookAt( center );
camera.near = near;
camera.far = far;
camera.updateProjectionMatrix();
}
})
this.controls.minDistance = near;
this.controls.maxDistance = far;
this.controls.target.copy(center);
this.controls.update();
//this.controls.saveState();
}
resize() {
if (!this.needResize) return;
this.tools.map((data)=>{
const {renderer, camera} = data
renderer.setSize(this.size.w, this.size.h, false)
if(camera){
camera.aspect = this.size.r;
camera.updateProjectionMatrix();
}
})
if(this.composer) this.composer.setSize(this.size.w, this.size.h);
this.needResize = false;
}
animate() {
requestAnimationFrame(this.animate.bind(this));
this.objects.forEach((o) => o.updateObject());
this.render();
}
initComposer(renderer, camera){
const operatorLines = this?.sobelPassSelect ?? 0
// init only one composer
if(this.composer){
return;
}
const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(this.scene, camera);
composer.addPass(renderPass);
// color to grayscale conversion
const effectGrayScale = new ShaderPass( LuminosityShader );
composer.addPass( effectGrayScale );
const effectExposure = new ShaderPass( ExposureShader )
effectExposure.uniforms[ 'exposure' ].value = 0.75;
composer.addPass( effectExposure )
if(operatorLines <= 2){
const effectSobel = new ShaderPass( SobelOperatorShader );
effectSobel.uniforms[ 'resolution' ].value.x = this.size.w;
effectSobel.uniforms[ 'resolution' ].value.y = this.size.h;
// Set changed line type
effectSobel.uniforms.lineType.value = operatorLines
composer.addPass( effectSobel )
} else if(operatorLines == 3){
// Canny edge
const effectCannyEdge = new ShaderPass( cannyEdgeShader );
effectCannyEdge.uniforms.resolution.value.x = this.size.w;
effectCannyEdge.uniforms.resolution.value.y = this.size.h;
composer.addPass( effectCannyEdge )
}
// this.effectSobel = new ShaderPass(sobelShader);
// composer.addPass(this.effectSobel);
const thresholdPass = new ShaderPass(thresholdShader);
thresholdPass.uniforms[ 'threshold' ].value = 0.2;
composer.addPass(thresholdPass);
this.composer = composer;
}
async render() {
if (this.autoScale) this.resize();
//this.tools.forEach((data)=>data.renderer.render(this.scene, data.camera))
if(!this.hub.ready) this.hub.add(this.getDom(0))
this.tools.forEach((data)=>{
let camera = this.VIEWS3 && !this.fixCamers ? data.camera : this.camera
this.scene.overrideMaterial = !data.material ? null: data.material;
if( data.type !== 'color' ) this.helper.visible = false
else if( this.withHelper ) this.helper.visible = true
if( data.type === 'lines' ){
if(this.useLinesComposer){
this.initComposer(data.renderer, camera)
return this.composer.render(this.scene)
}
}
//this.scene.overrideMaterial = !data.material ? null: data.material;
return data.renderer.render(this.scene, camera)
})
}
setCanvasSize(w, h) {
if (this.size.w === w) return;
this.size.w = w;
this.size.h = h;
this.size.r = this.size.w / this.size.h;
this.tools.map((data)=>{
const {renderer, camera} = data
renderer.setSize(this.size.w, this.size.h, false)
if(camera){
camera.aspect = this.size.r;
camera.updateProjectionMatrix();
}
})
if(this?.node) this.node.title = `${this.node.type} [${this.size.w}x${this.size.h}]`;
this.render();
this.node?.onResize();
}
async update(widgetWidth, posY) {
let w = widgetWidth - this.size.offset;
if (this.size.w === w) return;
this.size.w = w;
this.size.h = w * this.size.r;
this.needResize = true;
}
// Get object saves property
getSavedOptions(){
return {
size: this.size,
currentModel: this.savedData.currentModel,
camera: this.saveCameraState()
}
}
saveCameraState(){
return {
position: this.camera.position.clone(),
quaternion: this.camera.quaternion.clone(),
target: this.controls.target.clone()
}
}
loadCameraState(savesData = null){
if(!savesData && Object.keys(savesData).length === 0) return;
this.camera.position.copy(savesData.position);
this.camera.quaternion.copy(savesData.quaternion);
this.controls.target.copy(savesData.target);
this.camera.updateMatrixWorld();
this.camera.updateProjectionMatrix();
this.controls.update();
this.render()
}
// Function send image to server
async sendFileToServer(fileName, idx) {
this.render();
return new Promise((res) => {
const uploadFile = async (blobFile) => {
try {
const resp = await fetch("/upload/image", {
method: "POST",
body: blobFile,
});
if (resp.status === 200) {
console.log(`Image saved successfully: ${fileName}`);
res(true);
} else {
console.error(`Error saving image: ${resp.status} - ${resp.statusText}`);
res(false);
}
} catch (error) {
console.error(`Error during file upload: ${error}`);
res(false);
}
};
this.getDom(idx).toBlob(function (blob) {
if (!blob) {
console.error("Blob creation failed for", fileName);
res(false);
return;
}
let formData = new FormData();
formData.append("image", blob, fileName);
formData.append("overwrite", "true");
uploadFile(formData);
}, "image/png");
});
}
}
/*
class ThreeObject {
constructor(type, parameters = {}) {
this.type = type;
this.addObject(parameters);
}
static constructorCall(constr, args){
return new constr(...args)
}
addObject(parameters = {}) {
const {
update = null,
geo = [],
mat = {},
pos = {},
scale = {}
} = parameters;
const _material = {
color: `rgb(${Math.floor(Math.random() * 255)},${Math.floor(
Math.random() * 255
)},${Math.floor(Math.random() * 255)})`,
wireframe: true,
...mat,
};
const _position = { x: 0, y: 0, z: 0, ...pos };
const _scale = { x: 1, y: 1, z: 1, ...scale };
let _geometry;
switch (this.type) {
case "model":
this.object = geo
geo.children[0].material = new THREE.MeshStandardMaterial({color:0xffffff})
break;
case "sphere":
_geometry = ThreeObject.constructorCall(THREE.SphereGeometry, geo)
break;
default:
_geometry = ThreeObject.constructorCall(THREE.BoxGeometry, geo);
}
if(["cube", "sphere"].includes(this.type)) {
const material = new THREE.MeshBasicMaterial(_material);
this.object = new THREE.Mesh(_geometry, material);
}
this.object.position.set(_position.x, _position.y, _position.z);
this.object.scale.set(_scale.x, _scale.y, _scale.z);
}
updateObject() {
this.object.rotation.y += 0.001;
const time = performance.now() / 1000;
const scale = Math.abs(Math.sin(time)) + 10
this.object.scale.set(scale,scale,scale)
}
}
*/