Files

256 lines
8.9 KiB
JavaScript

class Editor {
constructor({ orientation, model, rows, cols,id }) {
this.id = id;
this.orientation = orientation;
this.model = model;
this.rows = rows;
this.cols = cols;
this.isInitialCapturing = false;
this.capturedFrames = 0;
this.frames = [];
this.isCapturing = false;
this.captureCount = 0;
this.scene = null;
this.camera = null;
this.renderer = null;
this.mixer = null;
this.clock = null;
this.gModel = null;
}
async init() {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.5, 1000);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.domElement.id = 'editor'+this.id;
this.renderer.setClearColor(0x0000ff);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('container').appendChild(this.renderer.domElement);
const ambientLight = new THREE.AmbientLight(0xeeeeee, 0.5);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
directionalLight.position.set(5, 30, 57.5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
this.scene.add(directionalLight);
this.clock = new THREE.Clock();
this.gModel = await this.loadModel().catch(console.error);
if (this.gModel) this.captureInitialFrames();
}
async update({ orientation, model, rows, cols }) {
if (orientation && orientation !== this.orientation) {
this.orientation = orientation;
this.updateOrientation(this.gModel);
this.captureInitialFrames();
}
if (model && model !== this.model) {
this.model = model;
this.gModel = await this.loadModel();
this.captureInitialFrames();
}
if (rows && rows !== this.rows) {
this.rows = rows;
this.captureInitialFrames();
}
if (cols && cols !== this.cols) {
this.cols = cols;
this.captureInitialFrames();
}
}
loadModel() {
return new Promise((resolve, reject) => {
const loader = new THREE.FBXLoader();
if (!this.model) {
reject('No model selected');
return;
}
loader.load('/extensions/ComfyUI-Benripack/3d_models/'+this.model, (object) => {
let box = new THREE.Box3().setFromObject(object);
let size = box.getSize(new THREE.Vector3());
const maxAxis = Math.max(size.x, size.y, size.z);
object.scale.multiplyScalar(100.0 / maxAxis);
object.traverse((child) => {
if (child.isMesh && !child.name.includes('Joints')) {
child.castShadow = true;
child.receiveShadow = true;
// set color
//const material = new THREE.MeshPhongMaterial({ color: 0xaaaaaa });
//child.material.color = material.color;
}
});
if (this.gModel) this.scene.remove(this.gModel);
this.updateOrientation(object);
this.scene.add(object);
box.getCenter(object.position).multiplyScalar(-1);
object.position.y = 0;
if (object.animations.length > 0) {
this.mixer = new THREE.AnimationMixer(object);
const action = this.mixer.clipAction(object.animations[0]);
action.play();
}
box = new THREE.Box3().setFromObject(object);
size = box.getSize(new THREE.Vector3());
this.camera.position.set(0, size.y/2 + 20, 120);
this.camera.lookAt(new THREE.Vector3(0, size.y/2, 0));
resolve(object);
});
});
}
updateOrientation(object) {
switch (this.orientation) {
case 'Front': object.rotation.y = 0; break;
case 'Rotate': object.rotation.y = 0; break;
case 'Back': object.rotation.y = Math.PI; break;
case 'Left': object.rotation.y = -Math.PI / 2; break;
case 'Right': object.rotation.y = Math.PI / 2; break;
}
}
captureInitialFrames() {
this.isInitialCapturing = true;
this.capturedFrames = 0;
this.frames = [];
const captureNextFrame = () => {
if (this.capturedFrames < this.rows * this.cols) {
if (this.mixer) {
var t = this.mixer._actions[0]._clip.duration;
this.mixer.setTime(t * (this.capturedFrames / (this.rows * this.cols)));
}
if(this.orientation == "Rotate") this.gModel.rotation.y = 2 * Math.PI * (this.capturedFrames / (this.rows * this.cols));
this.renderer.render(this.scene, this.camera);
this.captureFrame();
this.capturedFrames++;
requestAnimationFrame(captureNextFrame);
} else {
this.isInitialCapturing = false;
if (this.mixer) this.mixer.setTime(0);
requestAnimationFrame(this.animate.bind(this));
}
};
setTimeout(() => {
captureNextFrame();
}, 2000);
}
async captureFrame() {
this.renderer.render(this.scene, this.camera);
this.frames.push(this.renderer.domElement.toDataURL());
this.captureCount++;
if (this.captureCount >= (this.rows * this.cols)) {
this.isCapturing = false;
console.log('Captured frames', this.frames);
var animation = await this.createAnimationImage(this.rows, this.cols, this.frames);
window.parent.postMessage({ type: 'capture', frames: this.frames, animation: animation, id: this.id }, '*');
}
}
animate() {
requestAnimationFrame(this.animate.bind(this));
if (!this.isInitialCapturing) {
if(this.mixer) this.mixer.update(this.clock.getDelta());
if(this.orientation == "Rotate"){
this.gModel.rotation.y += 0.01
this.gModel.rotation.y %= 2 * Math.PI
}
}
this.renderer.render(this.scene, this.camera);
}
startCapture() {
this.frames = [];
this.isCapturing = true;
this.captureCount = 0;
}
createAnimationImage(rows, cols, frames) {
return new Promise((resolve, reject) => {
if (frames.length === 0) {
reject('No frames to create animation image');
return;
}
// Create a temporary image to get dimensions
const tempImg = new Image();
tempImg.onload = () => {
const frameWidth = tempImg.width;
const frameHeight = tempImg.height;
// Create a canvas that will hold all frames in a grid
this.canvas = document.createElement('canvas');
this.canvas.width = frameWidth * cols;
this.canvas.height = frameHeight * rows;
const ctx = this.canvas.getContext('2d');
// Function to draw each frame
const drawFrame = (index) => {
if (index >= frames.length || index >= rows * cols) {
// All frames have been drawn or grid is full, resolve with the result
console.log("Länge: " + this.canvas.toDataURL().length,this.id)
resolve(this.canvas.toDataURL());
return;
}
const img = new Image();
img.onload = () => {
const col = index % cols;
const row = Math.floor(index / cols);
ctx.drawImage(img, col * frameWidth, row * frameHeight, frameWidth, frameHeight);
drawFrame(index + 1);
};
img.onerror = reject;
img.src = frames[index];
};
// Start drawing frames
drawFrame(0);
};
tempImg.onerror = reject;
tempImg.src = frames[0];
});
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
}
// Event listeners
function onMessage(event) {
if(!window.editor) window.editor = {};
if (!event.data.orientation) return;
if (window.editor && window.editor[event.data.id]) {
window.editor[event.data.id].update(event.data);
} else {
window.editor[event.data.id] = new Editor(event.data);
window.editor[event.data.id].init();
}
}
window.addEventListener('resize', () => {
for (const key in window.editor) window.editor[key].onWindowResize();
})
window.addEventListener('message', onMessage);