diff --git a/src/js/main.js b/src/js/main.js index 239f12a12a..162a3ae0e6 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -1,8 +1,8 @@ const env = require('../../env.json') -if (env.mode) { +/* if (env.mode) { process.env.NODE_ENV = env.mode -} +} */ const {app, ipcMain, BrowserWindow, dialog, powerSaveBlocker} = electron = require('electron') diff --git a/src/js/shared/IK/utils/jsUtils.js b/src/js/shared/IK/utils/jsUtils.js new file mode 100644 index 0000000000..3c2b6a075e --- /dev/null +++ b/src/js/shared/IK/utils/jsUtils.js @@ -0,0 +1,6 @@ +Array.prototype.remove = function(object) { + let indexOf = this.indexOf(object) + if(indexOf !== -1) { + this.splice(indexOf, 1) + } +} \ No newline at end of file diff --git a/src/js/shared/reducers/shot-generator.js b/src/js/shared/reducers/shot-generator.js index e3c870004b..9b0efc94df 100644 --- a/src/js/shared/reducers/shot-generator.js +++ b/src/js/shared/reducers/shot-generator.js @@ -1020,6 +1020,16 @@ const sceneObjectsReducer = (state = {}, action) => { } return withDisplayNames(draft) + + case 'CREATE_OBJECTS': + if ( + action.payload.objects == null || + action.payload.objects.length === 0 + ) return + for(let object of action.payload.objects) { + draft[object.id] = object + } + return withDisplayNames(draft) case 'DELETE_OBJECTS': if ( @@ -1221,19 +1231,22 @@ const sceneObjectsReducer = (state = {}, action) => { draft[action.payload.id].skeleton = draft[action.payload.id].skeleton || {} for (let bone of action.payload.skeleton) { let rotation = bone.rotation - let { x, y, z } = bone.position + let position = bone.position if(draft[action.payload.id].skeleton[bone.name]) { draft[action.payload.id].skeleton[bone.name].rotation = !rotation ? draft[action.payload.id].skeleton[bone.name].rotation : { x: rotation.x, y: rotation.y, z: rotation.z } draft[action.payload.id].skeleton[bone.name].position = !bone.position ? draft[action.payload.id].skeleton[bone.name].position : - { x: x, y: y, z: z } + { x: position.x, y: position.y, z: position.z } } else { - draft[action.payload.id].skeleton[bone.name] = { - rotation: { x: rotation.x, y: rotation.y, z: rotation.z }, - position: { x: x, y: y, z: z }, - } + draft[action.payload.id].skeleton[bone.name] = {} + draft[action.payload.id].skeleton[bone.name].rotation = !rotation ? + {} : + { x: rotation.x, y: rotation.y, z: rotation.z } + draft[action.payload.id].skeleton[bone.name].position = !bone.position ? + {} : + { x: position.x, y: position.y, z: position.z } } } @@ -1708,6 +1721,7 @@ module.exports = { deselectAttachable: id => ({ type: 'DESELECT_ATTACHABLE', payload: id}), createObject: values => ({ type: 'CREATE_OBJECT', payload: values }), + createObjects: objects => ({ type: 'CREATE_OBJECTS', payload: {objects} }), updateObject: (id, values) => ({ type: 'UPDATE_OBJECT', payload: { id, ...values } }), // batch update diff --git a/src/js/shot-generator/Character.js b/src/js/shot-generator/Character.js index 9398cc3240..5a0b822b54 100644 --- a/src/js/shot-generator/Character.js +++ b/src/js/shot-generator/Character.js @@ -181,6 +181,7 @@ const Character = React.memo(({ largeRenderer, deleteObjects, updateObjects, + defaultPosePreset, ...props }) => { const [ready, setReady] = useState(false) // ready to load? @@ -199,11 +200,13 @@ const Character = React.memo(({ scene.remove(object.current.bonesHelper) scene.remove(object.current.orthoIcon) scene.remove(object.current) + object.current.userData.id === null object.current.remove(SGIkHelper.getInstance()) SGIkHelper.getInstance().deselectControlPoint() SGIkHelper.getInstance().removeFromParent(id) objectRotationControl.current.deselectObject() object.current.bonesHelper = null + scene.remove(object.current) object.current = null } } @@ -212,7 +215,11 @@ const Character = React.memo(({ useEffect(() => { setReady(false) setLoaded(false) - // return function cleanup () { } + return () => { + // Because when we switch models we recreate character(create new component) we need to set prev component object's id to null + if(object.current) + object.current.userData.id = null + } }, [props.model]) useEffect(() => { @@ -276,10 +283,10 @@ const Character = React.memo(({ object.current.userData.boneLengthScale = boneLengthScale object.current.userData.parentRotation = parentRotation object.current.userData.parentPosition = parentPosition + object.current.resetToStandardSkeleton = resetToStandardSkeleton scene.add(object.current.bonesHelper) let domElement = largeRenderer.current.domElement - objectRotationControl.current = new ObjectRotationControl(scene, camera, domElement, object.current.uuid) let boneRotation = objectRotationControl.current boneRotation.setUpdateCharacter((name, rotation) => {updateCharacterSkeleton({ @@ -297,7 +304,7 @@ const Character = React.memo(({ } return function cleanup () { - setAttachables(object.current ? object.current.attachables : null) + setAttachables(!object.current ? null : object.current.attachables ? object.current.attachables.concat([]) : null) setModelChange(!object.current ? false : object.current.attachables ? true : false) doCleanup() // setLoaded(false) @@ -333,17 +340,17 @@ const Character = React.memo(({ const fullyUpdateSkeleton = () => { let skeleton = object.current.userData.skeleton let changedSkeleton = [] - - let inverseMatrixWorld = object.current.getInverseMatrixWorld() let position = new THREE.Vector3() + let scalarForBones = 1 + if(props.posePresetId === defaultPosePreset.id) + scalarForBones = object.current.userData.boneLengthScale === 100 ? 100 : 1 for(let i = 0; i < skeleton.bones.length; i++) { let bone = skeleton.bones[i] - + if(bone.name.includes("leaf")) continue let rotation = bone.rotation - bone.applyMatrix(object.current.matrixWorld) - position = bone.position.clone() - bone.applyMatrix(inverseMatrixWorld) - position.multiplyScalar( object.current.userData.boneLengthScale === 100 ? 100 : 1) + + position = bone.position.clone().applyMatrix4(object.current.getInverseMatrixWorld()) + position.multiplyScalar(scalarForBones) changedSkeleton.push({ name: bone.name, position: { @@ -361,14 +368,22 @@ const Character = React.memo(({ updateCharacterIkSkeleton({id, skeleton:changedSkeleton}) } + const saveAttachablesPositions = () => { + if(!object.current || !object.current.attachables) return + object.current.updateWorldMatrix(true, true) + for(let i = 0; i < object.current.attachables.length; i++) { + object.current.attachables[i].saveToStore() + } + } + let startingDeviceRotation = useRef(null) let currentBoneSelected = useRef(null) const updateSkeleton = () => { let skeleton = object.current.userData.skeleton + if (Object.values(props.skeleton).length) { fixRootBone() - for (bone of skeleton.bones) { let userState = props.skeleton[bone.name] let systemState = originalSkeleton.current.getBoneByName(bone.name).clone() @@ -376,6 +391,7 @@ const Character = React.memo(({ bone.rotation.x = state.rotation.x bone.rotation.y = state.rotation.y bone.rotation.z = state.rotation.z + } } else { let skeleton = object.current.userData.skeleton @@ -461,6 +477,8 @@ const Character = React.memo(({ object.current.position.z = props.y object.current.position.y = props.z object.current.orthoIcon.position.copy(object.current.position) + saveAttachablesPositions() + } }, [props.model, props.x, props.y, props.z, ready]) @@ -480,7 +498,6 @@ const Character = React.memo(({ object.current.rotation.y = props.rotation object.current.orthoIcon.icon.material.rotation = props.rotation + Math.PI } - } }, [props.model, props.rotation, ready]) @@ -491,6 +508,22 @@ const Character = React.memo(({ updateSkeleton() } + const resetToStandardSkeleton = () => { + let skeleton = object.current.userData.skeleton + if (Object.values(props.skeleton).length) { + fixRootBone() + for (bone of skeleton.bones) { + let userState = defaultPosePreset.state.skeleton[bone.name] + let systemState = originalSkeleton.current.getBoneByName(bone.name).clone() + let state = userState || systemState + bone.rotation.x = state.rotation.x + bone.rotation.y = state.rotation.y + bone.rotation.z = state.rotation.z + bone.updateMatrixWorld(true) + } + } + } + const fixRootBone = () => { let { boneLengthScale, parentRotation, parentPosition } = object.current.userData let skeleton = object.current.userData.skeleton @@ -512,6 +545,7 @@ const Character = React.memo(({ if (!props.posePresetId) return resetPose() fullyUpdateSkeleton() + saveAttachablesPositions() }, [props.posePresetId]) useEffect(() => { @@ -520,8 +554,20 @@ const Character = React.memo(({ if (!props.handSkeleton) return resetPose() updateSkeletonHand() + saveAttachablesPositions() }, [props.handPosePresetId, props.handSkeleton]) + useEffect(() => { + if(!props.characterPresetId) return + if(object.current && object.current.attachables) { + let attachablesToDelete = [] + for(let i = 0; i < object.current.attachables.length; i++) { + attachablesToDelete.push(object.current.attachables[i].userData.id) + } + deleteObjects(attachablesToDelete) + } + }, [props.characterPresetId]) + // HACK force reset skeleton pose on Board UUID change useEffect(() => { if (!ready) return @@ -529,15 +575,16 @@ const Character = React.memo(({ console.log(type, id, 'changed boards') resetPose() + if(props.handSkeleton && Object.keys(props.handSkeleton).length > 0) + updateSkeletonHand() }, [boardUid]) useEffect(() => { if (!ready) return if (!object.current) return - // console.log(type, id, 'skeleton') + //console.log(type, id, 'skeleton') updateSkeleton() - if(props.handSkeleton && Object.keys(props.handSkeleton).length > 0) updateSkeletonHand() }, [props.model, props.skeleton, ready]) @@ -545,11 +592,14 @@ const Character = React.memo(({ useEffect(() => { + if (!ready) return + if (props.model !== object.current.userData.modelSettings.id) return if (object.current) { if (object.current.userData.modelSettings.height) { let originalHeight = object.current.userData.originalHeight let scale = props.height / originalHeight object.current.scale.set( scale, scale, scale ) + object.current.userData.height = props.height } else { object.current.scale.setScalar( props.height ) } @@ -663,7 +713,7 @@ const Character = React.memo(({ } } - setAttachables( null) + setAttachables(null) } setModelChange(false) } diff --git a/src/js/shot-generator/Components.js b/src/js/shot-generator/Components.js index 4b1f66f0f4..9cc25954bb 100644 --- a/src/js/shot-generator/Components.js +++ b/src/js/shot-generator/Components.js @@ -36,6 +36,7 @@ const { selectObjectToggle, // createObject, + createObjects, updateObject, deleteObjects, groupObjects, @@ -49,6 +50,8 @@ const { // loadScene, // saveScene, updateCharacterSkeleton, + updateCharacterIkSkeleton, + getDefaultPosePreset, setActiveCamera, setCameraShot, // resetScene, @@ -934,14 +937,19 @@ const saveCharacterPresets = state => presetsStorage.saveCharacterPresets({ char const CharacterPresetsEditor = connect( state => ({ characterPresets: state.presets.characters, - models: state.models + models: state.models, + sceneObjects: getSceneObjects(state), + defaultPosePreset: getDefaultPosePreset() }), { updateObject, - selectCharacterPreset: (sceneObject, characterPresetId, preset) => (dispatch, getState) => { + createObjects, + updateCharacterIkSkeleton, + selectCharacterPreset: (sceneObject, characterPresetId, preset, scene, defaultPosePreset) => (dispatch, getState) => { dispatch(updateObject(sceneObject.id, { // set characterPresetId characterPresetId, + posePresetId: null, // apply preset values to character model height: preset.state.height, @@ -959,10 +967,22 @@ const CharacterPresetsEditor = connect( endomorphic: preset.state.morphTargets.endomorphic }, - name: sceneObject.name || preset.name + name: sceneObject.name || preset.name, + skeleton: defaultPosePreset.state.skeleton })) + if(preset.state.attachables) { + + } + let character = scene.children.filter(child => child.userData.id === sceneObject.id)[0] + let skinnedMesh = character.getObjectByProperty("type", "SkinnedMesh") + skinnedMesh.skeleton.pose() + character.resetToStandardSkeleton() + character.updateWorldMatrix(true, true) + let attachables = initializeAttachables(sceneObject, preset) + if(attachables) + dispatch(createObjects(attachables)) }, - createCharacterPreset: ({ id, name, sceneObject }) => (dispatch, getState) => { + createCharacterPreset: ({ id, name, sceneObject, attachables, defaultPosePreset }) => (dispatch, getState) => { // add the character data to a named preset let preset = { id, @@ -986,6 +1006,12 @@ const CharacterPresetsEditor = connect( } } + if(attachables.length) { + preset.state.attachables = attachables + preset.state.presetPosition = { x:sceneObject.x, y: sceneObject.y, z: sceneObject.z }, + preset.state.presetRotation = sceneObject.rotation + } + // start the undo-able operation dispatch(undoGroupStart()) @@ -999,17 +1025,24 @@ const CharacterPresetsEditor = connect( dispatch(updateObject(sceneObject.id, { // set the preset id characterPresetId: id, + posePresetId: null, // use the preset’s name (if none assigned) - name: sceneObject.name || preset.name + name: sceneObject.name || preset.name, + skeleton: defaultPosePreset.state.skeleton })) // end the undo-able operation dispatch(undoGroupEnd()) + + let attachablesList = initializeAttachables(sceneObject, preset) + if(attachablesList) + dispatch(createObjects(attachablesList)) } } )( // TODO could optimize by only passing sceneObject properties we actually care about - React.memo(({ sceneObject, characterPresets, selectCharacterPreset, createCharacterPreset }) => { + React.memo(({ sceneObject, characterPresets, selectCharacterPreset, createCharacterPreset, sceneObjects, defaultPosePreset, updateCharacterIkSkeleton }) => { + const { scene } = useContext(SceneContext) const onCreateCharacterPresetClick = event => { // show a prompt to get the desired preset name let id = THREE.Math.generateUUID() @@ -1019,10 +1052,43 @@ const CharacterPresetsEditor = connect( value: `Character ${shortId(id)}` }, require('electron').remote.getCurrentWindow()).then(name => { if (name != null && name != '' && name != ' ') { + let character = scene.children.filter(child => child.userData.id === sceneObject.id)[0] + + let attachables = [] + if(character && character.attachables) { + let skinnedMesh = character.getObjectByProperty("type", "SkinnedMesh") + skinnedMesh.skeleton.pose() + character.resetToStandardSkeleton() + character.updateWorldMatrix(true, true) + for(let i = 0; i < character.attachables.length; i++) { + let attachable = character.attachables[i] + let attachableSceneObject = sceneObjects[character.attachables[i].userData.id] + let position = character.attachables[i].worldPosition() + let rotation = character.attachables[i].worldQuaternion() + let matrix = attachable.matrix.clone() + matrix.premultiply(attachable.parent.matrixWorld) + matrix.decompose(position, rotation, new THREE.Vector3()) + let euler = new THREE.Euler().setFromQuaternion(rotation) + attachables.push({ + x: position.x, + y: position.y, + z: position.z, + model: attachableSceneObject.model, + name: attachableSceneObject.name, + size: attachableSceneObject.size, + rotation: {x: euler.x, y: euler.y, z: euler.z}, + bindBone: attachableSceneObject.bindBone, + + }) + } + + } createCharacterPreset({ id, name, - sceneObject + sceneObject, + attachables: attachables, + defaultPosePreset }) } }).catch(err => { @@ -1033,7 +1099,7 @@ const CharacterPresetsEditor = connect( const onSelectCharacterPreset = event => { let characterPresetId = event.target.value let preset = characterPresets[characterPresetId] - selectCharacterPreset(sceneObject, characterPresetId, preset) + selectCharacterPreset(sceneObject, characterPresetId, preset, scene, defaultPosePreset) } return h( @@ -1063,6 +1129,57 @@ const CharacterPresetsEditor = connect( }) ) +const initializeAttachables = (sceneObject, preset) => { + let attachables = preset.state.attachables + if(attachables) { + let newAttachables = [] + let currentParent = new THREE.Group() + currentParent.position.set(sceneObject.x, sceneObject.z, sceneObject.y) + currentParent.rotation.set(0, sceneObject.rotation, 0 ) + currentParent.updateMatrixWorld(true) + let prevParent = new THREE.Group() + let attachableObject = new THREE.Object3D() + for(let i = 0; i < attachables.length; i++) { + let attachable = attachables[i] + prevParent.position.set(preset.state.presetPosition.x, preset.state.presetPosition.z, preset.state.presetPosition.y) + prevParent.rotation.set(0, preset.state.presetRotation, 0 ) + prevParent.updateMatrixWorld(true) + let newAttachable = {} + newAttachable.attachToId = sceneObject.id + newAttachable.id = THREE.Math.generateUUID() + newAttachable.loaded = false + newAttachable.model = attachable.model + newAttachable.name = attachable.name + newAttachable.type = attachable.type + newAttachable.size = attachable.size + newAttachable.type = "attachable" + newAttachable.bindBone = attachable.bindBone + + attachableObject.position.set(attachable.x, attachable.y, attachable.z) + attachableObject.rotation.set(attachable.rotation.x, attachable.rotation.y, attachable.rotation.z) + attachableObject.updateMatrixWorld(true) + prevParent.add(attachableObject) + attachableObject.applyMatrix(prevParent.getInverseMatrixWorld()) + + prevParent.position.copy(currentParent.position) + prevParent.rotation.copy(currentParent.rotation) + prevParent.updateMatrixWorld(true) + attachableObject.updateMatrixWorld(true) + let {x, y, z } = attachableObject.worldPosition() + newAttachable.x = x + newAttachable.y = y + newAttachable.z = z + let quaternion = attachableObject.worldQuaternion() + let euler = new THREE.Euler().setFromQuaternion(quaternion) + newAttachable.rotation = { x: euler.x, y: euler.y, z: euler.z } + newAttachables.push(newAttachable) + } + return newAttachables + } else { + return false + } +} + const CHARACTER_HEIGHT_RANGE = { character: { min: 1.4732, max: 2.1336 }, child: { min: 1.003, max: 1.384 }, diff --git a/src/js/shot-generator/SceneManager.js b/src/js/shot-generator/SceneManager.js index 168e42f955..9a4dbe43f1 100644 --- a/src/js/shot-generator/SceneManager.js +++ b/src/js/shot-generator/SceneManager.js @@ -19,6 +19,7 @@ const { updateCharacterIkSkeleton, createPosePreset, updateWorldEnvironment, + getDefaultPosePreset, getSceneObjects, getSelections, @@ -68,7 +69,8 @@ const SceneManager = connect( devices: state.devices, meta: state.meta, // HACK force reset skeleton pose on Board UUID change - _boardUid: state.board.uid + _boardUid: state.board.uid, + defaultPosePreset: getDefaultPosePreset() }), { updateObject, @@ -86,7 +88,7 @@ const SceneManager = connect( undoGroupEnd } )( - ({ world, sceneObjects, updateObject, selectObject, selectObjectToggle, remoteInput, largeCanvasRef, smallCanvasRef, selections, selectedBone, machineState, transition, animatedUpdate, selectBone, mainViewCamera, updateCharacterSkeleton, updateCharacterIkSkeleton, largeCanvasSize, activeCamera, aspectRatio, devices, meta, _boardUid, updateWorldEnvironment, attachments, undoGroupStart, undoGroupEnd, orthoCamera, camera, setCamera, selectedAttachable, updateObjects, deleteObjects }) => { + ({ world, sceneObjects, updateObject, selectObject, selectObjectToggle, remoteInput, largeCanvasRef, smallCanvasRef, selections, selectedBone, machineState, transition, animatedUpdate, selectBone, mainViewCamera, updateCharacterSkeleton, updateCharacterIkSkeleton, largeCanvasSize, activeCamera, aspectRatio, devices, meta, _boardUid, updateWorldEnvironment, attachments, undoGroupStart, undoGroupEnd, orthoCamera, camera, setCamera, selectedAttachable, updateObjects, deleteObjects, defaultPosePreset }) => { const { scene } = useContext(SceneContext) // const modelCacheDispatch = useContext(CacheContext) @@ -642,6 +644,7 @@ const SceneManager = connect( largeRenderer, deleteObjects, updateObjects:updateObjects, + defaultPosePreset:defaultPosePreset, ...props } ] diff --git a/src/js/shot-generator/attachables/Attachable.js b/src/js/shot-generator/attachables/Attachable.js index 7256b396b6..8738b0d33b 100644 --- a/src/js/shot-generator/attachables/Attachable.js +++ b/src/js/shot-generator/attachables/Attachable.js @@ -4,7 +4,7 @@ window.THREE = window.THREE || THREE const React = require('react') const { useRef, useEffect, useState } = React const ObjectRotationControl = require("../../shared/IK/objects/ObjectRotationControl") - +require("../../shared/IK/utils/jsUtils") // return a group which can report intersections const groupFactory = () => { let group = new THREE.Group() @@ -55,6 +55,7 @@ const Attachable = React.memo(({ scene, id, updateObject, sceneObject, loaded, m const characterObject = useRef() const objectRotationControl = useRef(); const [ready, setReady] = useState(false) // ready to load? + const [needsInitialization, setInitialization] = useState(false) const setLoaded = loaded => updateObject(id, { loaded }) const domElement = useRef() const isBoneSelected = useRef() @@ -67,11 +68,20 @@ const Attachable = React.memo(({ scene, id, updateObject, sceneObject, loaded, m container.current.userData.bindedId = props.attachToId container.current.userData.isRotationEnabled = false container.current.rebindAttachable = rebindAttachable + container.current.saveToStore = saveToStore isBoneSelected.current = false return function cleanup () { - container.current.parent.remove(container.current) - let indexOf = characterObject.current.attachables.indexOf(container.current) - characterObject.current.attachables.splice(indexOf, 1) + setReady(false) + setLoaded(false) + if(container.current.parent) + container.current.parent.remove(container.current) + if( characterObject.current) { + characterObject.current.attachables.remove(container.current) + characterObject.current = null + } + + objectRotationControl.current = null + container.current = null } }, []) @@ -81,7 +91,14 @@ const Attachable = React.memo(({ scene, id, updateObject, sceneObject, loaded, m }, [props.model]) useEffect(() => { - if (ready) { + if (needsInitialization && !loaded) { + + characterObject.current = scene.children.filter(child => child.userData.id === props.attachToId)[0] + if(!characterObject.current) { + setReady(false) + setInitialization(false) + return + } container.current.remove(...container.current.children) let isSkinnedMesh = false // Traverses passed model and clones it's meshes to container @@ -108,7 +125,6 @@ const Attachable = React.memo(({ scene, id, updateObject, sceneObject, loaded, m deleteObjects([id]) } // Sets up bind bone - characterObject.current = scene.children.filter(child => child.userData.id === props.attachToId)[0] let skinnedMesh = characterObject.current.getObjectByProperty("type", "SkinnedMesh") let skeleton = skinnedMesh.skeleton let bone = skeleton.getBoneByName(props.bindBone) @@ -116,12 +132,20 @@ const Attachable = React.memo(({ scene, id, updateObject, sceneObject, loaded, m container.current.userData.bindBone = props.bindBone // Applies character scale for case when character is scaled up to 100 - container.current.scale.multiplyScalar(props.size / characterObject.current.scale.x) + let scale = props.size / characterObject.current.scale.x + container.current.scale.set(scale, scale, scale) bone.add(container.current) container.current.updateMatrixWorld(true, true) // Adds a container of attachable to character if it doesn't exist and adds current attachable - if(!skinnedMesh.parent.attachables) skinnedMesh.parent.attachables = [] - skinnedMesh.parent.attachables.push(container.current) + if(!characterObject.current.attachables) { + characterObject.current.attachables = [] + characterObject.current.attachables.push(container.current) + } else { + let isAdded = characterObject.current.attachables.some(attachable => attachable.uuid === container.current.uuid) + if(!isAdded) { + characterObject.current.attachables.push(container.current) + } + } // Sets up object rotation control for manipulation of attachale rotation objectRotationControl.current = new ObjectRotationControl(scene, camera, domElement.current, characterObject.current.uuid) @@ -137,12 +161,14 @@ const Attachable = React.memo(({ scene, id, updateObject, sceneObject, loaded, m z : euler.z, } } )}) + setReady(true) } - }, [ready]) + }, [ready, characterObject.current, needsInitialization]) useEffect(() => { if ( !ready ) return + if ( !characterObject.current ) return // Applies position to container. // Position for container should always be in world space but container always attached to bone // We need to take it out of bone space and apply world position @@ -155,11 +181,12 @@ const Attachable = React.memo(({ scene, id, updateObject, sceneObject, loaded, m container.current.updateMatrixWorld(true) container.current.applyMatrix(parentInverseMatrixWorld) container.current.updateMatrixWorld(true) - }, [props.x, props.y, props.z, ready]) + }, [props.x, props.y, props.z, ready, characterObject.current]) useEffect(() => { if ( !ready ) return if ( !props.rotation ) return + if ( !characterObject.current ) return characterObject.current.updateWorldMatrix(true, true) let parentMatrixWorld = container.current.parent.matrixWorld let parentInverseMatrixWorld = container.current.parent.getInverseMatrixWorld() @@ -169,10 +196,11 @@ const Attachable = React.memo(({ scene, id, updateObject, sceneObject, loaded, m container.current.updateMatrixWorld(true) container.current.applyMatrix(parentInverseMatrixWorld) container.current.updateMatrixWorld(true) - }, [props.rotation, ready]) + }, [props.rotation, ready, characterObject.current]) useEffect(() => { if(!ready) return + if ( !characterObject.current ) return let outlineParameters = {} if(isSelected) { window.addEventListener("keydown", keyDownEvent, false) @@ -212,38 +240,50 @@ const Attachable = React.memo(({ scene, id, updateObject, sceneObject, loaded, m useEffect(() => { let character = scene.children.filter(child => child.userData.id === props.attachToId)[0] - if(character && modelData){ - setReady(true) + if(character && modelData) { + setInitialization(true) + } else { + setReady(false) + setInitialization(false) } - }, [modelData, ready, scene.children.length]) + + }, [modelData, scene.children.length, characterObject.current, needsInitialization ]) useEffect(() => { if(!ready) return + if(!objectRotationControl.current) return objectRotationControl.current.setCamera(camera) }, [ready, camera]) useEffect(() => { if(!ready) return + if(! container.current) return container.current.userData.bindBone = props.bindBone }, [props.bindBone]) useEffect(() => { if(!ready) return + if(!characterObject.current) return let scale = container.current.parent.uuid === scene.uuid ? props.size : props.size / characterObject.current.scale.x container.current.scale.set( scale, scale, scale ) - }, [props.size]) + }, [props.size, characterObject.current]) const rebindAttachable = (characterScale) => { + if(!container.current) return + let prevCharacter = characterObject.current characterObject.current = scene.children.filter(child => child.userData.id === props.attachToId)[0] - + characterObject.current.updateMatrixWorld(true, true) let skinnedMesh = characterObject.current.getObjectByProperty("type", "SkinnedMesh") let skeleton = skinnedMesh.skeleton let bone = skeleton.getBoneByName(props.bindBone) domElement.current = largeRenderer.current.domElement container.current.userData.bindBone = props.bindBone - + prevCharacter.updateMatrixWorld(true) + prevCharacter.updateWorldMatrix(true, true) let prevParent = container.current.parent + prevCharacter.attachables.remove(container.current) + prevParent.remove(container.current) container.current.applyMatrix(prevCharacter.matrixWorld) container.current.applyMatrix(characterObject.current.getInverseMatrixWorld()) @@ -251,13 +291,31 @@ const Attachable = React.memo(({ scene, id, updateObject, sceneObject, loaded, m container.current.scale.set( props.size, props.size, props.size ) container.current.scale.multiplyScalar(1 / characterScale) container.current.updateWorldMatrix(true, true) + // Adds a container of attachable to character if it doesn't exist and adds current attachable - if(!skinnedMesh.parent.attachables) skinnedMesh.parent.attachables = [] - skinnedMesh.parent.attachables.push(container.current) + if(!characterObject.current.attachables) { + characterObject.current.attachables = [] + characterObject.current.attachables.push(container.current) + } else { + let isAdded = characterObject.current.attachables.some(attachable => attachable.uuid === container.current.uuid) + if(!isAdded) { + characterObject.current.attachables.push(container.current) + } + } let position = container.current.worldPosition() updateObject(id, { x: position.x, y: position.y, z: position.z}) } + const saveToStore = () => { + let position = container.current.worldPosition() + let quaternion = container.current.worldQuaternion() + let matrix = container.current.matrix.clone() + matrix.premultiply(container.current.parent.matrixWorld) + matrix.decompose(position, quaternion, new THREE.Vector3()) + let euler = new THREE.Euler().setFromQuaternion(quaternion) + updateObject(id, { x: position.x, y: position.y, z: position.z, rotation: {x: euler.x, y: euler.y, z: euler.z}}) + } + const keyDownEvent = (event) => { switchManipulationState(event) } const switchManipulationState = (event) => { diff --git a/src/js/shot-generator/attachables/AttachableInfo.js b/src/js/shot-generator/attachables/AttachableInfo.js index 84557d5a1c..e544ba63c5 100644 --- a/src/js/shot-generator/attachables/AttachableInfo.js +++ b/src/js/shot-generator/attachables/AttachableInfo.js @@ -22,10 +22,10 @@ const AttachableInfoItem = React.memo(({ } const buttonName = useMemo(() => bindBoneName, [bindBoneName]) const attachableName = useMemo(() => { - return !sceneObject.displayName ? '' : sceneObject.displayName + return !sceneObject? '' : !sceneObject.displayName ? '' : sceneObject.displayName }) - return h(['div.attachable-card', + return sceneObject ? h(['div.attachable-card', ['div.attachable-card___title', ['div.attachable-card___label', attachableName], ['a.attachable-card__discard[href=#]', { onClick: () => { onDelete(sceneObject) }}, 'X'] @@ -39,7 +39,7 @@ const AttachableInfoItem = React.memo(({ }, buttonName] ]]], getNumberSlider(sceneObject) - ]) + ]) : [] }) const ListItem = React.memo(({ props, attachable }) => { diff --git a/src/js/xr/src/SceneManagerXR.js b/src/js/xr/src/SceneManagerXR.js index db19344b1d..c0b410e8a7 100644 --- a/src/js/xr/src/SceneManagerXR.js +++ b/src/js/xr/src/SceneManagerXR.js @@ -29,6 +29,7 @@ const { // action creators selectObject, updateObject, + updateCharacterIkSkeleton, getSelectedAttachable } = require('../../shared/reducers/shot-generator') @@ -119,13 +120,14 @@ const SceneContent = connect( }), { selectObject, - updateObject + updateObject, + updateCharacterIkSkeleton, } )( ({ aspectRatio, sceneObjects, world, activeCamera, selections, models, - characterIds, modelObjectIds, lightIds, virtualCameraIds, imageIds, attachablesIds, selectedAttachable, + characterIds, modelObjectIds, lightIds, virtualCameraIds, imageIds, attachablesIds, selectedAttachable, updateCharacterIkSkeleton, updateObject, resources, getAsset }) => { @@ -619,16 +621,18 @@ const SceneContent = connect( { characterIds.map(id => - getAsset(getFilepathForModelByType(sceneObjects[id])) - ? + // getAsset(getFilepathForModelByType(sceneObjects[id])) + // ? + + isSelected={selections.includes(id)} + updateSkeleton= {updateCharacterIkSkeleton} /> - : null + // : null ) } @@ -670,7 +674,8 @@ const SceneContent = connect( gltf={getAsset(getFilepathForModelByType(sceneObjects[id]))} sceneObject={sceneObjects[id]} isSelected={ selectedAttachable === id ? true : false} - modelSettings={models[sceneObjects[id].model] || undefined}/> + modelSettings={models[sceneObjects[id].model] || undefined} + updateObject={updateObject}/> : null ) diff --git a/src/js/xr/src/components/Attachable.js b/src/js/xr/src/components/Attachable.js index e71da8c632..754ffd72b5 100644 --- a/src/js/xr/src/components/Attachable.js +++ b/src/js/xr/src/components/Attachable.js @@ -1,5 +1,5 @@ const THREE = require('three') -const { useMemo, useEffect, useRef } = React = require('react') +const { useMemo, useEffect, useRef, useState } = React = require('react') const { useUpdate, useThree } = require('react-three-fiber') @@ -36,7 +36,7 @@ const meshFactory = source => { return mesh } -const Attachable = React.memo(({ gltf, sceneObject, isSelected }) => { +const Attachable = React.memo(({ gltf, sceneObject, isSelected, updateObject}) => { const characterObject = useRef(null) const { scene } = useThree() const ref = useUpdate( @@ -64,11 +64,14 @@ const Attachable = React.memo(({ gltf, sceneObject, isSelected }) => { return [] }, [sceneObject.model, gltf]) - useEffect(() => { ref.current.rebindAttachable = rebindAttachable + ref.current.saveToStore = saveToStore }, []) + useEffect(() => { + }, [ref.current]) + useEffect(() => { traverseMeshMaterials(ref.current, material => { if (material.emissive) { @@ -90,10 +93,17 @@ const Attachable = React.memo(({ gltf, sceneObject, isSelected }) => { let skinnedMesh = characterObject.current.getObjectByProperty("type", "SkinnedMesh") let bone = skinnedMesh.skeleton.bones.find(b => b.name === sceneObject.bindBone) bone.add(ref.current) - if(!characterObject.current.attachables) characterObject.current.attachables = [] - characterObject.current.attachables.push(ref.current) + if(!characterObject.current.attachables) { + characterObject.current.attachables = [] + characterObject.current.attachables.push(ref.current) + } else { + let isAdded = characterObject.current.attachables.some(attachable => attachable.uuid === ref.current.uuid) + if(!isAdded) { + characterObject.current.attachables.push(ref.current) + } + } ref.current.updateMatrixWorld(true) - }, [scene.children]) + }, [scene.children.length]) useEffect(() => { if(!characterObject.current) return @@ -130,26 +140,38 @@ const Attachable = React.memo(({ gltf, sceneObject, isSelected }) => { let prevCharacter = characterObject.current characterObject.current = scene.children[1].children.filter(child => child.userData.id === sceneObject.attachToId)[0] if(!characterObject.current) return + let skinnedMesh = characterObject.current.getObjectByProperty("type", "SkinnedMesh") let skeleton = skinnedMesh.skeleton let bone = skeleton.getBoneByName(sceneObject.bindBone) - ref.current.applyMatrix(prevCharacter.matrixWorld) - ref.current.applyMatrix(characterObject.current.getInverseMatrixWorld()) bone.add(ref.current) + let scale = sceneObject.size / characterObject.current.scale.x + ref.current.scale.set(scale, scale, scale) ref.current.updateWorldMatrix(true, true) - if(!ref.current.children.length) { - gltf.scene.traverse(child => { - if (child.isMesh) { - let mesh = meshFactory(child) - mesh.userData.type === 'attachable' - ref.current.add(mesh) - } - }) - } // Adds a ref of attachable to character if it doesn't exist and adds current attachable - if(!characterObject.current.attachables) characterObject.current.attachables = [] - characterObject.current.attachables.push(ref.current) + if(!characterObject.current.attachables) { + characterObject.current.attachables = [] + characterObject.current.attachables.push(ref.current) + } else { + let isAdded = characterObject.current.attachables.some(attachable => attachable.uuid === ref.current.uuid) + if(!isAdded) { + characterObject.current.attachables.push(ref.current) + } + } + + saveToStore() + } + + const saveToStore = () => { + let position = ref.current.worldPosition()// new THREE.Vector3() + let quaternion = ref.current.worldQuaternion() + let matrix = ref.current.matrix.clone() + matrix.premultiply(ref.current.parent.matrixWorld) + matrix.decompose(position, quaternion, new THREE.Vector3()) + let rot = new THREE.Euler().setFromQuaternion(quaternion, 'XYZ') + updateObject(sceneObject.id, { x: position.x, y: position.y, z: position.z, + rotation: {x: rot.x, y: rot.y, z: rot.z}}) } return { +const Character = React.memo(({ gltf, sceneObject, modelSettings, isSelected, updateSkeleton }) => { + const [ready, setReady] = useState(false) + const attachablesList = useRef([]) const ref = useUpdate( self => { self.traverse(child => child.layers.enable(VirtualCamera.VIRTUAL_CAMERA_LAYER)) } ) - useEffect(() => { - return () => { - if(ref.current.attachables && attachablesList.length ) { - attachablesList = ref.current.attachables.concat([]) - for(let i = 0; i < attachablesList.length; i++) { - if(attachablesList[i].parent) - attachablesList[i].parent.remove(attachablesList[i]) - } - isUnmounted[sceneObject.id] = true - } - } - }, []) - - useEffect(() => { - - }, [ref.current]) - const [skeleton, lod, originalSkeleton, armature, originalHeight] = useMemo( () => { + if(ref.current && ref.current.attachables) { + attachablesList.current = ref.current.attachables.concat([]) + for(let i = 0; i < attachablesList.current.length; i++) { + if(attachablesList.current[i].parent) { + attachablesList.current[i].parent.remove(attachablesList.current[i]) + } + } + } + if(!gltf) { + setReady(false) + return [null, null, null, null, null] + } let lod = new THREE.LOD() let { scene } = cloneGltf(gltf) @@ -107,7 +101,27 @@ const Character = React.memo(({ gltf, sceneObject, modelSettings, isSelected }) let bbox = new THREE.Box3().setFromObject(lod) originalHeight = bbox.max.y - bbox.min.y } - isClonned[sceneObject.id] = true + // We need to override skeleton when model is changed because in store skeleton position is still has values for prevModel + let newBones = [] + for(let i = 0; i < skeleton.bones.length; i++) { + let bone = skeleton.bones[i] + let position = bone.position + let rotation = sceneObject.skeleton[bone.name] ? sceneObject.skeleton[bone.name].rotation : bone.rotation + newBones.push({ + name: bone.name, + position: { + x: position.x, + y: position.y, + z: position.z + }, + rotation: { + x: rotation.x, + y: rotation.y, + z: rotation.z + } + }) + } + setReady(true) return [skeleton, lod, originalSkeleton, armature, originalHeight] }, [gltf] @@ -116,23 +130,22 @@ const Character = React.memo(({ gltf, sceneObject, modelSettings, isSelected }) useEffect(() => { if(!lod) return if(!ref.current) return - if(attachablesList.length && isUnmounted[sceneObject.id] && isClonned[sceneObject.id]) { + if(attachablesList.current.length) { ref.current.attachables = [] - for(let i = 0; i < attachablesList.length; i++) { - attachablesList[i].rebindAttachable(sceneObject.height / ref.current.userData.originalHeight) + // Updating skeleton to original bones position + for (let i = 0; i < skeleton.bones.length; i++) { + let bone = skeleton.bones[i] + if(!bone) continue + let originalbone = originalSkeleton.bones[i] + bone.position.copy(originalbone.position) + bone.updateMatrixWorld() } - attachablesList = [] - isUnmounted[sceneObject.id] = false - isClonned[sceneObject.id] = false - } - return () => { - if(ref.current.attachables && ref.current) { - attachablesList = ref.current.attachables.concat([]) - isUnmounted[sceneObject.id] = true + for(let i = 0; i < attachablesList.current.length; i++) { + attachablesList.current[i].rebindAttachable(sceneObject.height / ref.current.userData.originalHeight) } + attachablesList.current = [] } - }, [ref.current, lod, attachablesList.length]) - + }, [ref.current, attachablesList.current.length, lod, ready]) useMemo(() => { if (!skeleton) return @@ -140,6 +153,7 @@ const Character = React.memo(({ gltf, sceneObject, modelSettings, isSelected }) let hasModifications = Object.values(sceneObject.skeleton).length > 0 if (hasModifications) { + // let position = new THREE.Vector3() // go through all the bones in the skeleton for (bone of skeleton.bones) { // if user data exists for a bone, use it @@ -154,17 +168,16 @@ const Character = React.memo(({ gltf, sceneObject, modelSettings, isSelected }) if (bone.rotation.equals(state.rotation) == false) { // rotate the bone bone.rotation.setFromVector3(state.rotation) - if(state.position) - bone.position.set(state.position.x, state.position.y, state.position.z) // and update bone.updateMatrixWorld() } + } } else { // reset the pose skeleton.pose() } - }, [skeleton, sceneObject.skeleton]) + }, [skeleton, sceneObject.skeleton, ready]) useMemo(() => { if (!skeleton) return @@ -183,15 +196,16 @@ const Character = React.memo(({ gltf, sceneObject, modelSettings, isSelected }) bone.rotation.z = handBone.rotation.z } } - }, [skeleton, sceneObject.skeleton, sceneObject.handSkeleton]) + }, [skeleton, sceneObject.skeleton, sceneObject.handSkeleton, ready]) const bodyScale = useMemo( () => sceneObject.height / originalHeight, - [sceneObject.height] + [sceneObject.height, ready] ) // headScale (0.8...1.2) useMemo(() => { + if(!skeleton) return let headBone = skeleton.getBoneByName('Head') if (headBone) { // in prior versions, the head was scaled proportionally to the body @@ -200,15 +214,17 @@ const Character = React.memo(({ gltf, sceneObject, modelSettings, isSelected }) // now we just use the user's percentage value directly headBone.scale.setScalar(sceneObject.headScale) } - }, [skeleton, sceneObject.headScale]) + }, [skeleton, sceneObject.headScale, ready]) useMemo(() => { + if(!lod) return lod.children.forEach(skinnedMesh => { skinnedMesh.material.emissive.set(sceneObject.tintColor) }) - }, [sceneObject.tintColor]) + }, [sceneObject.tintColor, ready]) useMemo(() => { + if(!lod) return if (modelSettings && modelSettings.validMorphTargets && modelSettings.validMorphTargets.length) { lod.children.forEach(skinnedMesh => { skinnedMesh.material.morphTargets = skinnedMesh.material.morphNormals = true @@ -221,7 +237,7 @@ const Character = React.memo(({ gltf, sceneObject, modelSettings, isSelected }) skinnedMesh.material.morphTargets = skinnedMesh.material.morphNormals = false }) } - }, [modelSettings, sceneObject.morphTargets]) + }, [modelSettings, sceneObject.morphTargets, ready]) useMemo(() => { if(!ref.current) return @@ -238,10 +254,9 @@ const Character = React.memo(({ gltf, sceneObject, modelSettings, isSelected }) ref.current.remove(BonesHelper.getInstance()) ref.current.remove(IKHelper.getInstance()) } - }, [ref.current, isSelected]) + }, [ref.current, isSelected, ready]) - return lod - ? null : null} @@ -255,10 +270,9 @@ const Character = React.memo(({ gltf, sceneObject, modelSettings, isSelected }) rotation={[0, sceneObject.rotation, 0]} scale={[bodyScale, bodyScale, bodyScale]} > - - + + - : null }) module.exports = Character diff --git a/src/js/xr/src/use-interactions-manager.js b/src/js/xr/src/use-interactions-manager.js index 11b1c708fe..fe7d213477 100644 --- a/src/js/xr/src/use-interactions-manager.js +++ b/src/js/xr/src/use-interactions-manager.js @@ -555,7 +555,6 @@ const useInteractionsManager = ({ // include all interactables (Model Object, Character, Virtual Camera, etc) let list = scene.__interaction.filter(o => o.userData.type !== 'ui') - // setup the GPU picker getGpuPicker().setupScene(list, getExcludeList(scene))