/** * System takes care of updating all the entities (based on their component data) * @param apiSystem R3.API.System * @constructor */ /** * R3.System.GU * @constructor */ R3.System.GUI = function(options) { if (R3.Utils.UndefinedOrNull(options)) { options = {}; } R3.System.call(this); if (R3.Utils.UndefinedOrNull(options.guis)) { options.guis = []; } this.guis = options.guis; this.guiCreatedSubscription = null; this.guiRemovedSubscription = null; this.buildGUISubscription = null; this.clearGUISubscription = null; this.beforeRenderSubscription = null; this.removeComponentSubscription = null; // this.faces = []; // // this.exclusiveMode = false; // // // this.meshDeletedSubscription = null; // // this.meshSelectedSubscription = null; // // this.meshDeselectedSubscription = null; // // this.newEntitySubscription = null; // // this.meshSelectionObjects = {}; // // this.sourceChangedSubscription = null; // // // // this.windowResizeSubscription = null; // // this.meshFaceSelectedSubscription = null; // // this.meshFaceDeselectedSubscription = null; }; R3.System.GUI.prototype = Object.create(R3.System.prototype); R3.System.GUI.prototype.constructor = R3.System.GUI; R3.System.GUI.prototype.start = function() { this.guiCreatedSubscription = R3.Event.Subscribe( R3.Event.GUI_CREATED, this.guiCreated.bind(this) ); this.guiRemovedSubscription = R3.Event.Subscribe( R3.Event.GUI_REMOVED, this.guiRemoved.bind(this) ); this.buildGUISubscription = R3.Event.Subscribe( R3.Event.BUILD_GUI, this.buildGUI.bind(this) ); this.clearGUISubscription = R3.Event.Subscribe( R3.Event.CLEAR_GUI, this.clearGUI.bind(this) ); this.beforeRenderSubscription = R3.Event.Subscribe( R3.Event.BEFORE_RENDER, this.beforeRender.bind(this) ); this.removeComponentSubscription = R3.Event.Subscribe( R3.Event.REMOVE_COMPONENT, this.removeComponent.bind(this) ); R3.System.prototype.start.call(this); // this.windowResizeSubscription = R3.Event.Subscribe( // R3.Event.WINDOW_RESIZE, // this.windowResize.bind(this) // ); // // this.meshFaceSelectedSubscription = R3.Event.Subscribe( // R3.Event.MESH_FACE_SELECTED, // this.meshFaceSelected.bind(this) // ); // // this.meshFaceDeselectedSubscription = R3.Event.Subscribe( // R3.Event.MESH_FACE_DESELECTED, // this.meshFaceDeselected.bind(this) // ); // // this.guis = R3.EntityManager.Instance.findComponentsByConstructor(R3.GUI); // this.guis.map( // function(gui){ // this.initialize(gui); // }.bind(this) // ); // this.meshDeletedSubscription = R3.Event.Subscribe( // R3.Event.REMOVE_MESH, // this.meshDeleted.bind(this) // ); // // this.meshSelectedSubscription = R3.Event.Subscribe( // R3.Event.MESH_SELECTED, // this.meshSelected.bind(this) // ); // // this.meshDeselectedSubscription = R3.Event.Subscribe( // R3.Event.MESH_DESELECTED, // this.meshDeslected.bind(this) // ); // // this.componentRemovedSubscription = R3.Event.Subscribe( // R3.Event.REMOVE_COMPONENT, // this.removeComponent.bind(this) // ); // // this.sourceChangedSubscription = R3.Event.Subscribe( // R3.Event.CAST_SOURCE_CHANGED, // this.castSourceChanged.bind(this) // ); // // this.destinationChangedSubscription = R3.Event.Subscribe( // R3.Event.RECEIVE_DESTINATION_CHANGED, // this.receiveDestinationChanged.bind(this) // ); }; // R3.System.GUI.prototype.windowResize = function(data) { // }; R3.System.GUI.prototype.beforeRender = function() { this.guis.map( function(gui) { if (typeof gui.instance.update === 'function') { gui.instance.update(); } } ); }; R3.System.GUI.prototype.removeComponent = function(data) { this.guis.map( function(gui) { gui.instance._panels.map( function(panel) { if (panel._label === data.component.name) { panel.getNode().getElement().parentElement.removeChild(panel.getNode().getElement()) } } ) } ); }; R3.System.GUI.prototype.guiCreated = function(gui) { R3.Utils.PushUnique(this.guis, gui); }; R3.System.GUI.prototype.guiRemoved = function(gui) { gui.dispose(); var index = this.guis.indexOf(gui); if (index === -1) { console.warn('gui system out of sync'); } else { this.guis.splice(index, 1); } }; // /** // * Push the mesh to our backup components, if in exclusiveMode (menu at top is selected), // * otherwise, just to our normal components // * @param data // */ // R3.System.GUI.prototype.meshSelected = function(data) { // // if (this.exclusiveMode) { // R3.Utils.PushUnique(this.backupComponents, data.mesh); // } else { // R3.Utils.PushUnique(this.components, data.mesh); // } // // }; // // /** // * Same as selected above, but removes the mesh from the components // * @param data // */ // R3.System.GUI.prototype.meshDeslected = function(data) { // // var index = -1; // // if (this.exclusiveMode) { // index = this.backupComponents.indexOf(data.mesh); // if (index !== -1) { // this.backupComponents.splice(index, 1); // } // } else { // index = this.components.indexOf(data.mesh); // if (index !== -1) { // this.components.splice(index, 1); // } // } // // }; // // R3.System.GUI.prototype.meshFaceSelected = function(data) { // // this.faces.push(data.face); // // this.buildGUI({ // components : this.faces // }) // }; // // R3.System.GUI.prototype.meshFaceDeselected = function(data) { // // var index = this.faces.indexOf(data.face); // // if (index !== -1) { // this.faces.splice(index, 1); // // if (this.faces.length === 0) { // this.buildGUI({}); // } else { // this.buildGUI({components : this.faces}) // } // // } else { // console.warn('could not remove face'); // } // }; /** * This function responds to the BUILD_GUI event, data contains the components to build a GUI for data. * * If we send data with components - go into exclusive mode, backup the currently selected components and rebuild the gui * * If we send data without components - go out of exclusive mode, restore the backup of the currently selected components * and rebuild based on the meshes * * If we don't send any data (null or undefined) - some parameters changed and just rebuild the gui. * * @param data */ /** * clear GUI */ R3.System.GUI.prototype.clearGUI = function() { this.guis.map( function(gui) { gui.clear(); } ); }; R3.System.GUI.prototype.buildGUI = function(component) { this.guis.map( function(gui) { /** * First, start fresh - remove all folders */ gui.clear(); var addComponent = function(group, component, property) { if ( component.hasOwnProperty(property) || typeof component[property] === 'function' ) { if ( property === 'componentType' || property === 'register' || property === 'linked' || property === 'loaded' || property === 'selected' ) { return; } if (component.guiInfo && component.guiInfo[property] && component.guiInfo[property].options) { gui.addSelect(group, component, property); return; } if (component[property] instanceof R3.Color) { gui.addColor(group, component, property); return; } if (typeof component[property] === 'boolean') { gui.addCheckbox(group, component, property); return; } if (typeof component[property] === 'number') { gui.addNumber(group, component, property); return; } if (typeof component[property] === 'string') { gui.addString(group, component, property); return; } if (typeof component[property] === 'function') { gui.addButton(group, component, property); return; } } }; var panel = gui.addPanel(component.name); gui.addGroup(panel, 'Main'); var traverseComponent = function(component) { var properties = Object.keys(component); properties.sort(); properties.splice(properties.indexOf('id'), 1); properties.splice(properties.indexOf('name'), 1); properties.unshift('name'); properties.unshift('id'); properties.map( function(property) { if (component.hasOwnProperty(property)) { if (component.guiInfo && component.guiInfo.hasOwnProperty(property)) { /** * Skip properties in guiInfos - they need to go into subgroups */ return; } addComponent(panel, component, property); } } ); properties.map( function(property) { if (component.hasOwnProperty(property)) { if (!(component.guiInfo && component.guiInfo.hasOwnProperty(property))) { /** * Skip properties *NOT* in guiInfos */ return; } var group = panel.addSubGroup( { label : property } ); addComponent(group, component, property); } } ); }; traverseComponent(component); Object.keys(component.idToObject).map( function(id) { var _component = R3.EntityManager.Instance.findComponentById(id); if (_component.id === component.id) { return; } if (_component instanceof R3.Color) { return; } gui.addGroup(panel, _component.name); traverseComponent(_component); } ); var functions = []; for (var fn in component) { if ( typeof component[fn] === 'function') { if ( // fn === 'clone' || fn === 'remove' || fn === 'save' // fn === 'saveToRemoteAPI' ) { functions.push(fn); } } } functions.sort(); var actions = gui.addGroup(panel, 'Actions'); functions.map( function(fn) { addComponent(actions, component, fn); } ) // var guiInfos = Object.keys(__component.guiInfo); // // var linkedObjects = Object.keys(__component.idToObject); // // // // // .reduce( // function(result, key) { // // var // // result.push(key); // return result; // }, // [] // ); // // var numberGroup = panel.addGroup( // { // label : 'Numbers' // } // ); // // var stringGroup = panel.addGroup( // { // label : 'Strings' // } // ); // // // var addComponent = function(group, component) { // return function(property) { // if (component.hasOwnProperty(property)) { // // if (typeof component[property] === 'number') { // gui.addNumber(numberGroup, component, property) // } // // if (typeof component[property] === 'string') { // gui.addString(stringGroup, component, property) // } // // if (typeof component[property] === 'function') { // gui.addButton(group, component, property) // } // } // }; // }; // // Object.keys(__component).map(addComponent(panel, __component)); // // Object.keys(__component.idToObject).map( // function(id) { // // if (id === __component.id) { // return; // } // // var component = R3.EntityManager.Instance.findComponentById(id); // // var group = gui.addGroup(panel, component.name); // // Object.keys(component).map(addComponent(group, component)); // } // ); }.bind(this) ); return; this.guis.map(function(gui){ /** * First, start fresh - remove all folders */ gui.instance.destroy(); gui.removeAllFolders(); // gui.domElement.instance.parentElement.appendChild(gui.instance.domElement); if (data) { if (data.components) { /** * Check if we are not already in exclusive mode, because we only want to make a backup if * it does not already exist */ if (!this.exclusiveMode) { this.exclusiveMode = true; /** * Backup the current selection */ this.backupComponents = this.components.map( function(component) { return component; } ); } this.components = data.components.map( function(component) { return component; } ); } else { if (this.exclusiveMode) { this.exclusiveMode = false; /** * Time to restore the backup */ this.components = this.backupComponents.map( function(component) { return component; } ) } else { console.log('we are already not in mesh select mode - not doing anything with the backup'); } } } /** * For all mesh components - (if not in exclusive mode) - try to discover all materials, textures, etc */ if (!this.exclusiveMode) { /** * We first remove everything which is not a Mesh */ this.components = this.components.filter( function(component) { return (component instanceof R3.D3.Mesh); } ); } /** * Check if we have components to build a GUI for */ if (R3.Utils.UndefinedOrNull(this.components.length) || this.components.length < 1) { // console.log('no components selected'); return; } /** * Now continue to discover materials, textures, images etc. children of this component */ if (!this.exclusiveMode) { this.components = this.components.reduce( function(result, component) { var components = component.getChildrenComponents(); R3.Utils.PushUnique(result, component); components.map(function(component){ R3.Utils.PushUnique(result, component); }); return result; }.bind(this), [] ); } /** * Sort the components by component type */ this.components.sort( function(a, b) { if (a.componentType > b.componentType) { return 1; } if (a.componentType < b.componentType) { return -1; } return 0; } ); /** * Split the components into groups of componentType */ var componentGroups = this.components.reduce( function(result, component) { var componentData = result.pop(); if (component.componentType === componentData.componentType) { /** * This is the first component */ componentData.components.push(component); result.push(componentData); return result; } if (component.componentType !== componentData.componentType) { result.push(componentData); result.push({ componentType : component.componentType, components:[component] }); return result; } }, [ { componentType : this.components[0].componentType, components : [] } ] ); /** * We have all the components split into groups - now add the individual components */ this.components.map( function(component) { /** * Check for possible duplicates * @type {boolean} */ var duplicate = false; componentGroups.map(function(componentGroup){ if ( componentGroup.componentType === component.componentType && componentGroup.components.length === 1 && componentGroup.components[0] === component ) { duplicate = true; } }); if (!duplicate) { R3.Utils.PushUnique( componentGroups, { componentType : component.componentType, components : [component] } ); } } ); /** * componentGroups should now contain the whole list of stuff we want to build GUI for. */ componentGroups.map( function(componentGroup){ if (componentGroup.components.length < 1) { console.warn('invalid number of components'); } var templateObject = { template : { /** * Doing this here is just to put parent at the top of the gui */ 'parent' : componentGroup.components[0].parent }, affected : [componentGroup.components[0]], componentType : componentGroup.componentType }; for (var property in componentGroup.components[0]) { if ( componentGroup.components[0].hasOwnProperty(property) || typeof componentGroup.components[0][property] === 'function' ) { if (typeof componentGroup.components[0][property] === 'function') { templateObject.template[property] = function(__property) { return function() { this.affected.map( function(component) { component[__property].bind(component)(); } ) }.bind(templateObject); }(property); } else { templateObject.template[property] = componentGroup.components[0][property]; } } } var componentTemplate = componentGroup.components.reduce( function(result, component) { if (component === componentGroup.components[0]) { /** * This is the first component, just return */ return result; } /** * Now start to filter out the properties */ for (var property in component) { if ( component.hasOwnProperty(property) ) { if (!result.template.hasOwnProperty(property)) { continue; } if ( result.template[property] instanceof R3.Vector2 || result.template[property] instanceof R3.Vector3 || result.template[property] instanceof R3.Vector4 || result.template[property] instanceof R3.Quaternion ) { if (!result.template[property].equals(component[property])) { delete result.template[property]; } continue; } if (result.template[property] !== component[property]) { delete result.template[property]; } } } /** * Store the affected component */ result.affected.push(component); return result; }, templateObject ); /** * componentTemplate now contains for this particular component type group - all * the properties which are modifiable, and also the objects affected by this property changes */ var name; if (R3.Utils.UndefinedOrNull(componentTemplate.template.name)) { name = R3.GetComponentName(componentTemplate) + ' (All Selected (' + componentTemplate.affected.length + '))'; } else { name = componentTemplate.template.name; } var folder = gui.addFolder(name); if (!folder) { return; } for (var templateProperty in componentTemplate.template) { if ( componentTemplate.template.hasOwnProperty(templateProperty) || typeof (componentTemplate.template[templateProperty]) === 'function' ) { if (typeof (componentTemplate.template[templateProperty]) === 'function') { folder.add(componentTemplate.template, templateProperty); continue; } /** * We only want to affect runtime vectors because their onchange will execute updateInstance() */ if ( componentTemplate.template[templateProperty] instanceof R3.Vector2 || componentTemplate.template[templateProperty] instanceof R3.Vector3 || componentTemplate.template[templateProperty] instanceof R3.Vector4 ) { this.buildVectorControl(folder, componentTemplate, templateProperty); continue; } if (componentTemplate.template[templateProperty] instanceof R3.Quaternion) { this.buildQuaternionControl(folder, componentTemplate, templateProperty); } if ( templateProperty.indexOf('parent') === 0 ) { if (componentTemplate.template[templateProperty] instanceof Array) { console.warn('read-only property :' + templateProperty); } else { this.buildParentSelectionControl(folder, componentTemplate, templateProperty); } continue; } if (componentTemplate.template[templateProperty] instanceof Array) { if ( templateProperty === 'vertices' || templateProperty === 'faces' ) { continue; } if (templateProperty === 'uvs') { this.buildUVManagerControl(folder, componentTemplate, templateProperty); } if ( componentTemplate.template.linkedComponents && componentTemplate.template.linkedComponents[templateProperty] instanceof Array ) { this.buildArrayManagerControl(folder, componentTemplate, templateProperty); } continue; } if (componentTemplate.template[templateProperty] instanceof R3.Color) { this.buildColorControl(folder, componentTemplate, templateProperty); continue; } if (typeof componentTemplate.template[templateProperty] === 'object') { if ( componentTemplate.template[templateProperty] instanceof R3.Component || ( componentTemplate.template.linkedComponents && componentTemplate.template.linkedComponents[templateProperty] ) ) { this.buildSelectControl(folder, componentTemplate, templateProperty) } else { this.buildObjectControl(folder, componentTemplate, templateProperty); } continue; } this.buildControl(folder, componentTemplate, templateProperty); } } }.bind(this) ); }.bind(this)); }; // R3.System.GUI.prototype.meshDeleted = function(data) { // // data.meshes.map(function(mesh){ // this.meshDeslected({ // mesh : mesh // }) // }.bind(this)); // // this.buildGUI(null); // }; // // R3.System.GUI.prototype.removeComponent = function(data) { // // var index = this.backupComponents.indexOf(data.component); // if (index !== -1) { // this.backupComponents.splice(index, 1); // } // // index = this.components.indexOf(data.component); // if (index !== -1) { // this.components.splice(index, 1); // } // // }; // // R3.System.GUI.prototype.castSourceChanged = function(data) { // this.buildGUI(null); // }; // // R3.System.GUI.prototype.receiveDestinationChanged = function(data) { // this.buildGUI(null); // }; R3.System.GUI.prototype.stop = function() { this.guis.map( function(gui) { gui.dispose(); } ); this.guiCreatedSubscription.remove(); this.guiRemovedSubscription.remove(); this.buildGUISubscription.remove(); this.clearGUISubscription.remove(); this.beforeRenderSubscription.remove(); this.removeComponentSubscription.remove(); R3.System.prototype.stop.call(this); };