toto10's picture
5abb6291403b19368a3b842ea4f13b1f23694d90948b2da0ae23e85acf2bb1e9
fb54f6e
raw
history blame
21.8 kB
import React, {useRef, useEffect} from 'react';
import {OrbitControls} from 'three/addons/controls/OrbitControls.js';
import * as THREE from 'three';
import {TransformControls} from 'three/examples/jsm/controls/TransformControls';
import {OBJLoader} from 'three/addons/loaders/OBJLoader.js';
import {STLLoader} from 'three/addons/loaders/STLLoader.js';
import {FBXLoader} from 'three/addons/loaders/FBXLoader.js';
import {DRACOLoader} from 'three/addons/loaders/DRACOLoader.js';
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js';
import {ColladaLoader} from 'three/addons/loaders/ColladaLoader.js';
import {VRMLoaderPlugin, VRMUtils} from "@pixiv/three-vrm";
import {setAnimationProgress} from './AnimationPanel'
let _playing = true;
let _renderer;
let _container;
let _camera;
let _groundMesh;
let _groundGrid;
let _axis;
let _action;
let _mixer;
let _totalTime;
let _currentTime;
let _progress;
let _controlByProgressBar;
let _secondCamera;
let _scene;
let previewWidth = 300;
let previewHeight = 300;
let _width;
let _height;
let _transformControls;
let _orbitController;
let _selectedObject;
let _currentVRM;
let _mainObjectCounter = 1;
export function setPreviewSize(previewSize) {
if (previewSize === "1:1") {
previewWidth = 300;
previewHeight = 300;
} else if (previewSize === "2:3") {
previewWidth = 300;
previewHeight = 450;
} else if (previewSize === "3:2") {
previewWidth = 450;
previewHeight = 300;
}
}
export function removeObject(objName) {
const object = _scene.getObjectByName(objName);
if (object) {
_scene.remove(object);
_transformControls.detach();
}
if (_currentVRM && _currentVRM.scene.name === objName) {
_currentVRM = undefined;
}
}
export function handlePoseSelectedObject(objName, transformControlsMode) {
if (transformControlsMode && transformControlsMode !== "none") {
if (_currentVRM) {
const boneNode = _currentVRM.humanoid.getNormalizedBoneNode(objName);
if (boneNode) {
_transformControls.setMode(transformControlsMode);
_transformControls.attach(boneNode);
}
}
} else {
_transformControls.detach();
}
}
export function handleSelectedObject(objName, transformControlsMode) {
if (objName !== "Scene" && transformControlsMode && transformControlsMode !== "none") {
_selectedObject = _scene.getObjectByName(objName);
_transformControls.setMode(transformControlsMode);
_transformControls.attach(_selectedObject);
} else {
_transformControls.detach();
}
}
export function refreshSceneTree() {
window.updateObjects(convertThreeJsObjects());
}
export function setFar(far) {
_camera.far = far;
_camera.updateProjectionMatrix();
_secondCamera.far = far;
_secondCamera.updateProjectionMatrix();
}
export function setNear(near) {
_camera.near = near;
_camera.updateProjectionMatrix();
_secondCamera.near = near;
_secondCamera.updateProjectionMatrix();
}
export function setFOV(fov) {
_camera.fov = fov;
_camera.updateProjectionMatrix();
_secondCamera.fov = fov;
_secondCamera.updateProjectionMatrix();
}
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
onWindowResize();
}
});
});
const onWindowResize = () => {
_width = _container.clientWidth;
_height = _container.clientHeight;
_camera.aspect = _width / _height;
_camera.updateProjectionMatrix();
_renderer.setSize(_width, _height);
};
function checkDivVisible(div) {
if ((div.offsetWidth > 0) && (div.offsetHeight > 0)) {
return true;
}
return false;
}
function ThreeJsScene({uploadedModelFile}) {
const containerRef = useRef();
const managerRef = useRef();
const sceneRef = useRef();
useEffect(() => {
_container = containerRef.current;
_renderer = new THREE.WebGLRenderer({antialias: true, preserveDrawingBuffer: true,});
_renderer.domElement.style.width = '100%';
_renderer.domElement.style.height = '100%';
_container.appendChild(_renderer.domElement);
observer.observe(_container);
_scene = new THREE.Scene();
_scene.name = "Scene";
sceneRef.current = _scene;
_width = _container.clientWidth;
_height = _container.clientHeight;
managerRef.current = new THREE.LoadingManager();
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444);
hemisphereLight.name = "Hemisphere Light";
hemisphereLight.position.set(0, 200, 0);
_scene.add(hemisphereLight);
const directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.name = "Directional Light";
directionalLight.position.set(0, 200, 100);
directionalLight.castShadow = true;
directionalLight.shadow.camera.top = 180;
directionalLight.shadow.camera.bottom = -100;
directionalLight.shadow.camera.left = -120;
directionalLight.shadow.camera.right = 120;
_scene.add(directionalLight);
_camera = new THREE.PerspectiveCamera(45, _width / _height, 0.1, 1000);
_camera.position.x = -30;
_camera.position.y = 40;
_camera.position.z = 30;
_camera.lookAt(_scene.position);
_renderer.setSize(_width, _height);
const groundMaterial = new THREE.MeshBasicMaterial({color: 0xbebebe});
_groundMesh = new THREE.Mesh(new THREE.PlaneBufferGeometry(2000, 2000), groundMaterial);
_groundMesh.name = "Ground";
_groundMesh.rotation.x = -Math.PI / 2;
_groundMesh.receiveShadow = true;
_scene.add(_groundMesh);
_groundGrid = new THREE.GridHelper(2000, 20, 0x000000, 0x000000);
_groundGrid.name = "Grid";
_groundGrid.material.opacity = 0.2;
_groundGrid.material.transparent = true;
_scene.add(_groundGrid);
_axis = new THREE.AxesHelper(2000);
_axis.name = "Axis";
_scene.add(_axis);
_orbitController = new OrbitControls(_camera, _renderer.domElement);
_orbitController.mouseButtons = {LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.PAN, RIGHT: THREE.MOUSE.PAN};
const clock = new THREE.Clock();
_secondCamera = new THREE.PerspectiveCamera(45, previewWidth / previewHeight, 0.1, 1000);
_secondCamera.name = "Preview Camera";
_secondCamera.position.copy(_camera.position);
_secondCamera.quaternion.copy(_camera.quaternion);
const orbitController2 = new OrbitControls(_secondCamera, _renderer.domElement);
orbitController2.mouseButtons = {LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.PAN, RIGHT: THREE.MOUSE.PAN};
_scene.add(_secondCamera);
_transformControls = new TransformControls(_camera, _renderer.domElement);
_transformControls.name = "Transform Controls";
_transformControls.addEventListener('dragging-changed', (event) => {
_orbitController.enabled = !event.value;
orbitController2.enabled = !event.value;
});
_scene.add(_transformControls);
const animate = () => {
requestAnimationFrame(animate);
if (checkDivVisible(_container)) {
_orbitController.update();
let delta = clock.getDelta();
if (_mixer && _playing) {
_currentTime = _mixer.time % _totalTime;
_progress = ((_currentTime / _totalTime) * 100).toFixed(1);
setAnimationProgress(_progress);
}
if (_mixer && (_playing || _controlByProgressBar)) {
_mixer.update(delta);
if (!_currentVRM) {
_controlByProgressBar = false;
}
}
if ((_currentVRM && _playing) || (_currentVRM && _controlByProgressBar)) {
_currentVRM.update(delta);
_controlByProgressBar = false;
}
_renderer.setViewport(0, 0, _container.clientWidth, _container.clientHeight);
_renderer.setScissor(0, 0, _container.clientWidth, _container.clientHeight);
_renderer.setScissorTest(true);
_camera.updateProjectionMatrix();
_renderer.render(_scene, _camera);
orbitController2.update()
_renderer.setViewport(0, 0, previewWidth, previewHeight);
_renderer.setScissor(0, 0, previewWidth, previewHeight);
_renderer.setScissorTest(true);
_secondCamera.aspect = previewWidth / previewHeight;
_secondCamera.updateProjectionMatrix();
_renderer.render(_scene, _secondCamera);
}
};
if (window.updateObjects) {
window.updateObjects(convertThreeJsObjects());
}
window.addEventListener('resize', onWindowResize);
animate();
return () => {
// 组件卸载时的清理操作
window.removeEventListener('resize', onWindowResize);
};
}, []);
function isGLTF1(contents) {
let resultContent;
if (typeof contents === 'string') {
resultContent = contents;
} else {
const magic = THREE.LoaderUtils.decodeText(new Uint8Array(contents, 0, 4));
if (magic === 'glTF') {
const version = new DataView(contents).getUint32(4, true);
return version < 2;
} else {
resultContent = THREE.LoaderUtils.decodeText(new Uint8Array(contents));
}
}
const json = JSON.parse(resultContent);
return (json.asset !== undefined && json.asset.version[0] < 2);
}
function loadOBJ(event) {
const contents = event.target.result;
const mainObject = new OBJLoader().parse(contents);
mainObject.name = "mainObject" + _mainObjectCounter.toString();
_mainObjectCounter++;
scaleObjectToProper(mainObject);
_scene.add(mainObject);
window.updateObjects(convertThreeJsObjects());
}
function loadSTL(event) {
const contents = event.target.result;
const geometry = new STLLoader().parse(contents);
const material = new THREE.MeshStandardMaterial();
geometry.sourceType = "stl";
//geometry.sourceFile = file.name;
const mainObject = new THREE.Mesh(geometry, material);
mainObject.name = "mainObject" + _mainObjectCounter.toString();
_mainObjectCounter++;
scaleObjectToProper(mainObject);
_scene.add(mainObject);
window.updateObjects(convertThreeJsObjects());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
function loadFBX(event) {
const contents = event.target.result;
const loader = new FBXLoader(managerRef.current);
const mainObject = loader.parse(contents);
_mixer = new THREE.AnimationMixer(mainObject);
if (mainObject.animations[0]) {
_action = _mixer.clipAction(mainObject.animations[0]);
if (_playing) {
_action.play();
}
const clip = _action.getClip();
_totalTime = clip.duration;
}
mainObject.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
mainObject.name = "mainObject" + _mainObjectCounter.toString();
_mainObjectCounter++;
scaleObjectToProper(mainObject);
_scene.add(mainObject);
window.updateObjects(convertThreeJsObjects());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
function loadGLTF(event) {
const contents = event.target.result;
let loader;
if (isGLTF1(contents)) {
alert('Import of glTF asset not possible. Only versions >= 2.0 are supported. Please try to upgrade the file to glTF 2.0 using glTF-Pipeline.');
} else {
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/file=extensions/sd-3dmodel-loader/js/');
loader = new GLTFLoader(managerRef.current);
loader.setDRACOLoader(dracoLoader);
}
loader.parse(contents, '', function (result) {
const resultScene = result.scene;
resultScene.name = "mainObject" + _mainObjectCounter.toString();
_mainObjectCounter++;
resultScene.animations.push(...result.animations);
_scene.add(resultScene);
window.updateObjects(convertThreeJsObjects());
});
}
function loadGLB(event) {
const contents = event.target.result;
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/file=extensions/sd-3dmodel-loader/js/');
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
loader.parse(contents, '', function (result) {
const resultScene = result.scene;
resultScene.name = "mainObject" + _mainObjectCounter.toString();
_mainObjectCounter++;
resultScene.animations.push(...result.animations);
_scene.add(resultScene);
window.updateObjects(convertThreeJsObjects());
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
function loadDAE(event) {
const contents = event.target.result;
const loader = new ColladaLoader(managerRef.current);
const collada = loader.parse(contents);
collada.scene.name = "mainObject" + _mainObjectCounter.toString();
_mainObjectCounter++;
_scene.add(collada.scene);
window.updateObjects(convertThreeJsObjects());
}
// eslint-disable-next-line react-hooks/exhaustive-deps
function loadVRM(event) {
const contents = event.target.result;
const loader = new GLTFLoader(managerRef.current);
loader.register((parser) => {
return new VRMLoaderPlugin(parser);
});
loader.parse(contents, '', function (result) {
const vrm = result.userData.vrm;
if (!_currentVRM) {
_currentVRM = vrm;
}
const resultScene = vrm.scene;
resultScene.name = "mainObject" + _mainObjectCounter.toString();
_mainObjectCounter++;
scaleObjectToProper(resultScene);
_scene.add(resultScene);
window.updateObjects(convertThreeJsObjects());
});
}
useEffect(() => {
if (uploadedModelFile) {
const filename = uploadedModelFile.name;
const extension = filename.split('.').pop().toLowerCase();
const reader = new FileReader();
//removeMainObject();
switch (extension) {
case 'obj':
reader.addEventListener('load', loadOBJ, false);
reader.readAsText(uploadedModelFile);
break;
case 'stl':
reader.addEventListener('load', loadSTL, false);
if (reader.readAsBinaryString !== undefined) {
reader.readAsBinaryString(uploadedModelFile);
} else {
reader.readAsArrayBuffer(uploadedModelFile);
}
break;
case 'fbx':
reader.addEventListener('load', loadFBX, false);
reader.readAsArrayBuffer(uploadedModelFile);
break;
case 'gltf':
reader.addEventListener('load', loadGLTF, false);
reader.readAsArrayBuffer(uploadedModelFile);
break;
case 'glb':
reader.addEventListener('load', loadGLB, false);
reader.readAsArrayBuffer(uploadedModelFile);
break;
case 'dae':
reader.addEventListener('load', loadDAE, false);
reader.readAsText(uploadedModelFile);
break;
case 'vrm':
reader.addEventListener('load', loadVRM, false);
reader.readAsArrayBuffer(uploadedModelFile);
break;
}
}
}, [uploadedModelFile, loadOBJ, loadSTL, loadFBX, loadGLTF, loadDAE, loadVRM]);
return (
<div ref={containerRef} style={{width: '100%', height: '100%'}}></div>
);
}
function scaleObjectToProper(object) {
const boundingBox = new THREE.Box3();
boundingBox.setFromObject(object);
if (object.geometry) {
const center = new THREE.Vector3();
boundingBox.getCenter(center);
object.geometry.translate(-center.x, -center.y, -center.z);
}
const expectRadius = 20;
const radius = boundingBox.getBoundingSphere(new THREE.Sphere()).radius;
const modelScale = expectRadius / radius;
object.scale.set(modelScale, modelScale, modelScale);
}
export function loadPoseModel(poseModelFileName) {
if (poseModelFileName && !_currentVRM) {
let manager = new THREE.LoadingManager();
let path = "/file=extensions/sd-3dmodel-loader/models/" + poseModelFileName;
//let path = "/file=/modelss/" + poseModelFileName;
const ext = poseModelFileName.split('.').pop().toLowerCase()
switch (ext) {
case "vrm": {
const loader = new GLTFLoader(manager);
loader.crossOrigin = 'anonymous';
loader.register((parser) => {
return new VRMLoaderPlugin(parser);
});
loader.load(
path,
(gltf) => {
const vrm = gltf.userData.vrm;
const resultScene = vrm.scene;
resultScene.name = "mainObject" + _mainObjectCounter.toString();
_mainObjectCounter++;
scaleObjectToProper(resultScene);
_scene.add(resultScene);
_currentVRM = vrm;
vrm.scene.traverse((obj) => {
obj.frustumCulled = false;
});
VRMUtils.rotateVRM0(vrm);
}
)
break;
}
case "fbx": {
const loader = new FBXLoader(manager);
loader.load(path, (object) => {
object.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
object.name = "mainObject";
scaleObjectToProper(object);
_scene.add(object);
});
break;
}
}
}
}
export function convertThreeJsObjects() {
return _scene.toJSON();
}
export default ThreeJsScene;
export function setBgColor(bgColor) {
const backgroundColor = new THREE.Color(bgColor.hex);
_renderer.setClearColor(backgroundColor);
}
export function setPlaying() {
_playing = !_playing;
if (_action && _playing) {
_action.play();
}
}
export function setStopPlaying() {
if (_action) {
_action.stop();
}
_playing = false;
}
export function setVisible(objName, visible) {
if (_scene) {
const selectedObject = _scene.getObjectByName(objName);
selectedObject.visible = visible;
}
}
export function controlAnimationProgress(e) {
if (_action && !_playing) {
_currentTime = e / 100 * _totalTime;
_action.time = _currentTime;
_mixer.time = _currentTime;
_controlByProgressBar = true;
}
}
export function setRendererImage(sendImage, controlNetIndex, type) {
if (controlNetIndex !== '') {
_renderer.domElement.toBlob((blob) => {
const image = new Image();
image.onload = function () {
const canvas = document.createElement('canvas');
const canvas2 = document.createElement('canvas');
canvas.width = _width;
canvas.height = _height;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
const imageData = ctx.getImageData(0, image.height - previewHeight, previewWidth, previewHeight);
canvas2.width = previewWidth;
canvas2.height = previewHeight;
const ctx2 = canvas2.getContext('2d');
canvas2.width = previewWidth;
canvas2.height = previewHeight;
ctx2.putImageData(imageData, 0, 0);
canvas2.toBlob((blob2) => {
const file = new File([blob2], "pose.png")
const dt = new DataTransfer();
dt.items.add(file);
sendImage(type, controlNetIndex, dt);
});
};
image.src = URL.createObjectURL(blob);
});
} else {
alert('No ControlNet Selected');
}
}