/** * System takes care of updating all the entities (based on their component data) * @param apiSystem GameLib.API.System * @constructor */ GameLib.System.GUI = function( apiSystem ) { GameLib.System.call( this, apiSystem ); this.guis = []; this.components = []; /** * When we want to show a specific set of components - we backup the current components and then restore them * later when we are done. * @type {null} */ this.backupComponents = []; this.exclusiveMode = false; this.buildGUISubscription = null; this.meshDeletedSubscription = null; this.meshSelectedSubscription = null; this.meshDeselectedSubscription = null; this.newEntitySubscription = null; this.meshSelectionObjects = {}; }; GameLib.System.GUI.prototype = Object.create(GameLib.System.prototype); GameLib.System.GUI.prototype.constructor = GameLib.System.GUI; GameLib.System.GUI.prototype.start = function() { GameLib.System.prototype.start.call(this); this.guis = GameLib.EntityManager.Instance.queryComponents(GameLib.GUI); /** * Add some GUI behaviour */ dat.GUI.prototype.removeEmtpyFolders = function() { for (var property in this.__folders) { if (this.__folders.hasOwnProperty(property)){ var folder = this.__folders[property]; if (folder.__listening.length === 0) { folder.close(); this.__ul.removeChild(folder.domElement.parentNode); delete this.__folders[property]; this.onResize(); } } } }; dat.GUI.prototype.listen = function(controller) { const init = this.__listening.length === 0; this.__listening.push(controller); delete this.closed; Object.defineProperty(this, 'closed', { get: function() { return this.params.closed; }, set: function(v) { // console.log('override here too'); this.params.closed = v; if (this.params.closed) { this.dom.addClass(this.__ul, 'closed'); cancelAnimationFrame(this.animationId); } else { this.dom.removeClass(this.__ul, 'closed'); this.updateDisplaysCallback = function() { /** * We store the animationFrameId so we can remove this callback later */ this.animationId = requestAnimationFrame(this.updateDisplaysCallback.bind(this)); this.__listening.map(function(controller){ controller.updateDisplay(); }); }.bind(this); this.animationId = requestAnimationFrame(this.updateDisplaysCallback); } // For browsers that aren't going to respect the CSS transition, // Lets just check our height against the window height right off // the bat. this.onResize(); if (this.__closeButton) { this.__closeButton.innerHTML = v ? 'Open Controls' : 'Close Controls'; } }, configurable: true }); }; dat.GUI.prototype.removeAllFolders = function() { for (var property in this.__folders) { if (this.__folders.hasOwnProperty(property)){ var folder = this.__folders[property]; /** * Theres a big 'TODO' in the controller remove() function to actually remove the listener * That's what we are going to do now.. - because it really fucks with the framerate eventually */ cancelAnimationFrame(folder.animationId); folder.__controllers.map( function(controller) { controller.remove(); } ); folder.__controllers = []; folder.__listening = []; /** * Call UpdateDisplays with */ folder.close(); this.__ul.removeChild(folder.domElement.parentNode); delete this.__folders[property]; this.onResize(); } } }; this.guis.map(function(gui){ gui.domElement.instance.parentElement.appendChild(gui.instance.domElement); }); this.buildGUISubscription = this.subscribe( GameLib.Event.BUILD_GUI, this.buildGUI ); this.meshDeletedSubscription = this.subscribe( GameLib.Event.REMOVE_MESH, this.meshDeleted ); this.meshSelectedSubscription = this.subscribe( GameLib.Event.MESH_SELECTED, this.meshSelected ); this.meshDeselectedSubscription = this.subscribe( GameLib.Event.MESH_DESELECTED, this.meshDeslected ); this.newEntitySubscription = this.subscribe( GameLib.Event.NEW_ENTITY, this.newEntity ); this.componentRemovedSubscription = this.subscribe( GameLib.Event.REMOVE_COMPONENT, this.removeComponent ) }; GameLib.System.GUI.prototype.onChange = function(property, subProperty, affected) { return function(value) { affected.map(function(component){ component[property][subProperty] = value; if (component instanceof GameLib.D3.Mesh && property === 'rotation') { component.useQuaternion = false; } if (component instanceof GameLib.D3.Mesh && property === 'quaternion') { component.useQuaternion = true; } if (typeof component[property].updateInstance === 'function') { component[property].updateInstance(property); } else if (typeof component[property][subProperty].updateInstance === 'function') { component[property][subProperty].updateInstance(subProperty); } else { component.updateInstance(property); } }); } }; GameLib.System.GUI.prototype.controller = function(folder, object, property, subProperty, step, listen, affected, min, max) { if (GameLib.Utils.UndefinedOrNull(min)) { min = -100; } if (GameLib.Utils.UndefinedOrNull(max)) { max = 100; } if ( // property === 'chassisConnectionPointLocal' || property === 'axleLocal' || property === 'directionLocal' ) { min = -1; max = 1; step = 1; } var handle = folder.add( object[property], subProperty, min, max, step ).name(property + '.' + subProperty); handle.onChange(this.onChange(property, subProperty, affected)); if (listen) { handle.listen(); } return handle; }; GameLib.System.GUI.prototype.buildQuaternionControl = function(folder, componentTemplate, property) { var step = 0.1; var object = componentTemplate.template; var listen = false; if (componentTemplate.affected.length === 1) { /** * If the template only affects a single object - put the handle on this so we can listen for changes */ object = componentTemplate.affected[0]; listen = true; } var affected = componentTemplate.affected; this.controller(folder, object, property, 'x', step, listen, affected); this.controller(folder, object, property, 'y', step, listen, affected); this.controller(folder, object, property, 'z', step, listen, affected); this.controller(folder, object, property, 'w', step, listen, affected); this.controller(folder, object, property, 'angle', 0.001, listen, affected, -Math.PI, Math.PI); folder.add( object[property]['axis'], 'x', -1, 1, 0.01 ).name('quaternion.axis.x').onChange( function(value) { affected.map(function(component){ component.useQuaternion = true; component[property]['axis'].x = Number(value); component.updateInstance(); }) } ); folder.add( object[property]['axis'], 'y', -1, 1, 0.01 ).name('quaternion.axis.y').onChange( function(value) { affected.map(function(component){ component.useQuaternion = true; component[property]['axis'].y = Number(value); component.updateInstance(); }) } ); folder.add( object[property]['axis'], 'z', -1, 1, 0.01 ).name('quaternion.axis.z').onChange( function(value) { affected.map(function(component){ component.useQuaternion = true; component[property]['axis'].z = Number(value); component.updateInstance(); }) } ); }; GameLib.System.GUI.prototype.buildVectorControl = function(folder, componentTemplate, property) { var step = 0.01; var object = componentTemplate.template; var listen = false; if (componentTemplate.affected.length === 1) { /** * If the template only affects a single object - put the handle on this so we can listen for changes */ object = componentTemplate.affected[0]; listen = true; } var affected = componentTemplate.affected; var controllers = []; if (GameLib.Utils.isVector4(object[property])) { controllers.push(this.controller(folder, object, property, 'w', step, listen, affected)); } controllers.push(this.controller(folder, object, property, 'x', step, listen, affected)); controllers.push(this.controller(folder, object, property, 'y', step, listen, affected)); if ( GameLib.Utils.isVector3(object[property]) || GameLib.Utils.isVector4(object[property]) ) { controllers.push(this.controller(folder, object, property, 'z', step, listen, affected)); } }; /** * Builds an Entity Selection control * @param folder * @param componentTemplate */ GameLib.System.GUI.prototype.buildParentSelectionControl = function(folder, componentTemplate, property) { var constructor = null; if (property === 'parentEntity') { constructor = GameLib.Entity; } if (property === 'parentMesh') { constructor = GameLib.D3.Mesh; } if (property === 'parentWorld') { constructor = GameLib.D3.PhysicsWorld; } if (property === 'parentScene') { constructor = GameLib.D3.Scene; } var options = GameLib.EntityManager.Instance.queryComponents(constructor).reduce( function(result, object) { result[object.name] = object; return result; }, { 'none' : null } ); var object = componentTemplate.template; var affected = componentTemplate.affected; folder.add(object, property, options).listen().onChange( function(value) { var newComponent = null; if (value !== 'null') { newComponent = GameLib.EntityManager.Instance.findComponentById(value); } affected.map( function(component) { component[property] = newComponent; if (property === 'parentEntity') { GameLib.Event.Emit( GameLib.Event.PARENT_ENTITY_CHANGE, { originalEntity : this.initialValue, newEntity : newComponent, object : component } ); } if (property === 'parentWorld') { GameLib.Event.Emit( GameLib.Event.PARENT_WORLD_CHANGE, { originalWorld : this.initialValue, newWorld : newComponent, object : component } ) } if (property === 'parentScene') { GameLib.Event.Emit( GameLib.Event.PARENT_SCENE_CHANGE, { originalScene: this.initialValue, newScene: newComponent, object: component } ); } }.bind(this) ); if (property === 'parentEntity') { GameLib.Event.Emit( GameLib.Event.BUILD_GUI, null ); } this.initialValue = newComponent; } ); }; GameLib.System.GUI.prototype.buildArrayManagerControl = function( folder, componentTemplate, property ) { var constructors = componentTemplate.template.linkedObjects[property]; if (constructors instanceof Array) { /** * All good */ } else { /** * There is a data mismatch */ console.error('data mismatch - something not an array'); return; } var object = componentTemplate.template; var array = object[property]; var addArrayItem = function(item, index){ var name = 'invalid item'; if (item && item.name) { name = item.name; } var controller = folder.add( { 'remove' : function() { componentTemplate.affected.map(function(component){ component[property].splice(index, 1); folder.remove(controller); }); } }, 'remove' ).name('remove ' + property + '[' + index + '] - ' + name); folder.updateDisplay(); }; array.map(addArrayItem); var idObject = {}; var selectionObject = GameLib.EntityManager.Instance.queryComponents(constructors).reduce( function(result, component) { result[component.name] = component; idObject[component.id] = component; return result; }, { 'none' : null } ); var activeSelection = { component: null, add: function () { componentTemplate.affected.map(function (component) { if (component[property].indexOf(activeSelection.component) === -1) { component[property].push(activeSelection.component); GameLib.Event.Emit( GameLib.Event.ARRAY_ITEM_ADDED, { component : component, property : property, item : activeSelection.component } ); // addArrayItem(activeSelection.component, component[property].length - 1); } }); GameLib.Event.Emit( GameLib.Event.BUILD_GUI ); } }; folder.add(activeSelection, 'component', selectionObject).name('select ' + property).onChange( function(value){ if (value === 'null') { activeSelection['component'] = null; } else { activeSelection['component'] = idObject[value]; } } ).listen(); folder.add(activeSelection, 'add').name('add to ' + property); }; GameLib.System.GUI.prototype.buildColorControl = function(folder, componentTemplate, property) { var object = componentTemplate.template; var tempObject = { hexColor : object[property].toHex() }; folder.addColor( tempObject, 'hexColor' ).name(property).listen().onChange( function(value) { componentTemplate.affected.map( function(component) { component[property].fromHex(value); component[property].updateInstance(); } ) } ); folder.add( tempObject, 'hexColor' ).name(property).listen().onChange( function(value) { componentTemplate.affected.map( function(component) { component[property].fromHex(value); component[property].updateInstance(); } ) } ) }; GameLib.System.GUI.prototype.buildSelectControl = function(folder, componentTemplate, property) { /** * We need to discover the constructor for this component */ var constructor = null; if (componentTemplate.template[property]) { constructor = componentTemplate.template[property].constructor; } else { if (componentTemplate.template.linkedObjects[property]) { constructor = componentTemplate.template.linkedObjects[property]; } } var object = componentTemplate.template; var objects = GameLib.EntityManager.Instance.queryComponents(constructor); var idObject = {}; var options = objects.reduce( function(result, obj) { result[obj.name] = obj; idObject[obj.id] = obj; return result; }, { 'none' : null } ); folder.add( object, property, options ).name(property).listen().onChange( function (value) { var newComponent = null; if (value !== 'null') { newComponent = idObject[value]; } componentTemplate.affected.map( function(component) { component[property] = newComponent; component.updateInstance(); } ); this.initialValue = newComponent; } ); }; GameLib.System.GUI.prototype.buildControl = function(folder, componentTemplate, property) { var object = componentTemplate.template; var listen = false; if (componentTemplate.affected.length === 1) { /** * If the template only affects a single object - put the handle on this so we can listen for changes */ object = componentTemplate.affected[0]; listen = true; } var componentType = componentTemplate.componentType; var controllers = []; if ( GameLib.Utils.isString(object[property]) || GameLib.Utils.isBoolean(object[property]) ) { controllers.push(folder.add(object, property)); } if (GameLib.Utils.isNumber(object[property])) { var grain = 0.001; if (object.grain) { grain = object.grain; } if (property === 'systemType') { controllers.push( folder.add( object, property, { 'animation' : GameLib.System.SYSTEM_TYPE_ANIMATION, 'gui' : GameLib.System.SYSTEM_TYPE_GUI, 'input' : GameLib.System.SYSTEM_TYPE_INPUT, 'render' : GameLib.System.SYSTEM_TYPE_RENDER, 'storage' : GameLib.System.SYSTEM_TYPE_STORAGE, 'linking' : GameLib.System.SYSTEM_TYPE_LINKING, 'physics' : GameLib.System.SYSTEM_TYPE_PHYSICS, 'custom code' : GameLib.System.SYSTEM_TYPE_CUSTOM } ) ); } else if (property === 'broadphaseType') { controllers.push( folder.add( object, property, { 'naive': GameLib.D3.Broadphase.BROADPHASE_TYPE_NAIVE, 'grid': GameLib.D3.Image.BROADPHASE_TYPE_GRID, 'sap': GameLib.D3.Image.BROADPHASE_TYPE_SAP } ) ); } else if (property === 'solverType') { controllers.push( folder.add( object, property, { 'gs': GameLib.D3.Solver.GS_SOLVER, 'split': GameLib.D3.Solver.SPLIT_SOLVER } ) ); } else if (property === 'meshType') { controllers.push( folder.add( object, property, { 'normal' : GameLib.D3.Mesh.MESH_TYPE_NORMAL, 'curve' : GameLib.D3.Mesh.MESH_TYPE_CURVE, 'skinned' : GameLib.D3.Mesh.MESH_TYPE_SKINNED, 'plane' : GameLib.D3.Mesh.MESH_TYPE_PLANE, 'sphere' : GameLib.D3.Mesh.MESH_TYPE_SPHERE, 'box' : GameLib.D3.Mesh.MESH_TYPE_BOX, 'cylinder' : GameLib.D3.Mesh.MESH_TYPE_CYLINDER, 'text' : GameLib.D3.Mesh.MESH_TYPE_TEXT } ) ); } else if (property === 'cameraType') { controllers.push( folder.add( object, property, { 'perspective' : GameLib.D3.Camera.CAMERA_TYPE_PERSPECTIVE, 'orthographic' : GameLib.D3.Camera.CAMERA_TYPE_ORTHOGONAL } ) ); } else if (property === 'materialType') { controllers.push( folder.add( object, property, { 'standard': GameLib.D3.Material.MATERIAL_TYPE_STANDARD, 'basic': GameLib.D3.Material.MATERIAL_TYPE_BASIC, 'phong': GameLib.D3.Material.MATERIAL_TYPE_PHONG, 'points': GameLib.D3.Material.MATERIAL_TYPE_POINTS, 'line basic' : GameLib.D3.Material.MATERIAL_TYPE_LINE_BASIC } ) ); } else if (property === 'side') { controllers.push( folder.add( object, property, { 'double': GameLib.D3.Material.TYPE_DOUBLE_SIDE, 'front': GameLib.D3.Material.TYPE_FRONT_SIDE, 'back': GameLib.D3.Material.TYPE_BACK_SIDE } ) ); } else if (property === 'combine') { controllers.push( folder.add( object, property, { 'multiply': GameLib.D3.Material.TYPE_MULTIPLY_OPERATION, 'mix': GameLib.D3.Material.TYPE_MIX_OPERATION, 'add': GameLib.D3.Material.TYPE_ADD_OPERATION } ) ); } else if (property === 'vertexColors') { controllers.push( folder.add( object, property, { 'none': GameLib.D3.Material.TYPE_NO_COLORS, 'face': GameLib.D3.Material.TYPE_FACE_COLORS, 'vertex': GameLib.D3.Material.TYPE_VERTEX_COLORS } ) ); } else if (property === 'blending') { controllers.push( folder.add( object, property, { 'normal': GameLib.D3.Material.TYPE_NORMAL_BLENDING, 'additive': GameLib.D3.Material.TYPE_ADDITIVE_BLENDING, 'subtractive': GameLib.D3.Material.TYPE_SUBTRACTIVE_BLENDING, 'multiply': GameLib.D3.Material.TYPE_MULTIPLY_BLENDING } ) ); } else if (property === 'blendSrc') { controllers.push( folder.add( object, property, { 'zero': GameLib.D3.Material.TYPE_ZERO_FACTOR, 'one': GameLib.D3.Material.TYPE_ONE_FACTOR, 'source color': GameLib.D3.Material.TYPE_SRC_COLOR_FACTOR, 'one minus source color': GameLib.D3.Material.TYPE_ONE_MINUS_SRC_COLOR_FACTOR, 'source alpha': GameLib.D3.Material.TYPE_SRC_ALPHA_FACTOR, 'one minus source alpha': GameLib.D3.Material.TYPE_ONE_MINUS_SRC_ALPHA_FACTOR, 'destination alpha': GameLib.D3.Material.TYPE_DST_ALPHA_FACTOR, 'one minus destination alpha': GameLib.D3.Material.TYPE_ONE_MINUS_DST_ALPHA_FACTOR, 'destination color': GameLib.D3.Material.TYPE_DST_COLOR_FACTOR, 'one minus destination color': GameLib.D3.Material.TYPE_ONE_MINUS_DST_COLOR_FACTOR, 'source alpha saturate': GameLib.D3.Material.TYPE_SRC_ALPHA_SATURATE_FACTOR } ) ); } else if (property === 'blendDst') { controllers.push( folder.add( object, property, { 'zero': GameLib.D3.Material.TYPE_ZERO_FACTOR, 'one': GameLib.D3.Material.TYPE_ONE_FACTOR, 'source color': GameLib.D3.Material.TYPE_SRC_COLOR_FACTOR, 'one minus source color': GameLib.D3.Material.TYPE_ONE_MINUS_SRC_COLOR_FACTOR, 'source alpha': GameLib.D3.Material.TYPE_SRC_ALPHA_FACTOR, 'one minus source alpha': GameLib.D3.Material.TYPE_ONE_MINUS_SRC_ALPHA_FACTOR, 'destination alpha': GameLib.D3.Material.TYPE_DST_ALPHA_FACTOR, 'one minus destination alpha': GameLib.D3.Material.TYPE_ONE_MINUS_DST_ALPHA_FACTOR, 'destination color': GameLib.D3.Material.TYPE_DST_COLOR_FACTOR, 'one minus destination color': GameLib.D3.Material.TYPE_ONE_MINUS_DST_COLOR_FACTOR, 'source alpha saturate': GameLib.D3.Material.TYPE_SRC_ALPHA_SATURATE_FACTOR } ) ); } else if (property === 'blendEquation') { controllers.push( folder.add( object, property, { 'add': GameLib.D3.Material.TYPE_ADD_EQUATION, 'subtract': GameLib.D3.Material.TYPE_SUBTRACT_EQUATION, 'reverse subtract': GameLib.D3.Material.TYPE_REVERSE_SUBTRACT_EQUATION, 'min': GameLib.D3.Material.TYPE_MIN_EQUATION, 'max': GameLib.D3.Material.TYPE_MAX_EQUATION } ) ); } else if (property === 'depthFunc') { controllers.push( folder.add( object, property, { 'never': GameLib.D3.Material.TYPE_NEVER_DEPTH, 'always': GameLib.D3.Material.TYPE_ALWAYS_DEPTH, 'less depth': GameLib.D3.Material.TYPE_LESS_DEPTH, 'less equal depth': GameLib.D3.Material.TYPE_LESS_EQUAL_DEPTH, 'equal depth': GameLib.D3.Material.TYPE_EQUAL_DEPTH, 'greated equal depth': GameLib.D3.Material.TYPE_GREATER_EQUAL_DEPTH, 'greated depth': GameLib.D3.Material.TYPE_GREATER_DEPTH, 'not equal depth': GameLib.D3.Material.TYPE_NOT_EQUAL_DEPTH } ) ); } else if (property === 'wrapS') { controllers.push( folder.add( object, property, { 'repeat': GameLib.D3.Texture.TYPE_REPEAT_WRAPPING, 'clamp': GameLib.D3.Texture.TYPE_CLAMP_TO_EDGE_WRAPPING, 'mirrored repeat': GameLib.D3.Texture.TYPE_MIRRORED_REPEAT_WRAPPING } ) ); } else if (property === 'wrapT') { controllers.push( folder.add( object, property, { 'repeat': GameLib.D3.Texture.TYPE_REPEAT_WRAPPING, 'clamp': GameLib.D3.Texture.TYPE_CLAMP_TO_EDGE_WRAPPING, 'mirrored repeat': GameLib.D3.Texture.TYPE_MIRRORED_REPEAT_WRAPPING } ) ); } else if (property === 'format') { controllers.push( folder.add( object, property, { 'alpha': GameLib.D3.Texture.TYPE_ALPHA_FORMAT, 'rgb': GameLib.D3.Texture.TYPE_RGB_FORMAT, 'rgba': GameLib.D3.Texture.TYPE_RGBA_FORMAT, 'luminance': GameLib.D3.Texture.TYPE_LUMINANCE_FORMAT, 'luminance alpha': GameLib.D3.Texture.TYPE_LUMINANCE_ALPHA_FORMAT, 'depth': GameLib.D3.Texture.TYPE_DEPTH_FORMAT } ) ); } else if (property === 'mapping') { controllers.push( folder.add( object, property, { 'uv': GameLib.D3.Texture.TYPE_UV_MAPPING, 'cube reflection': GameLib.D3.Texture.TYPE_CUBE_REFLECTION_MAPPING, 'cube refraction': GameLib.D3.Texture.TYPE_CUBE_REFRACTION_MAPPING, 'equi rectangular reflection': GameLib.D3.Texture.TYPE_EQUI_RECTANGULAR_REFLECTION_MAPPING, 'equi rectangular refraction': GameLib.D3.Texture.TYPE_EQUI_RECTANGULAR_REFRACTION_MAPPING, 'spherical reflection': GameLib.D3.Texture.TYPE_SPHERICAL_REFLECTION_MAPPING, 'cube uv reflection': GameLib.D3.Texture.TYPE_CUBE_UV_REFLECTION_MAPPING, 'cube uv refraction': GameLib.D3.Texture.TYPE_CUBE_UV_REFRACTION_MAPPING } ) ); } else if (property === 'magFilter') { controllers.push( folder.add( object, property, { 'nearest': GameLib.D3.Texture.TYPE_NEAREST_FILTER, 'nearest mipmap nearest': GameLib.D3.Texture.TYPE_NEAREST_MIPMAP_NEAREST_FILTER, 'nearest mipmap linear': GameLib.D3.Texture.TYPE_NEAREST_MIPMAP_LINEAR_FILTER, 'linear': GameLib.D3.Texture.TYPE_LINEAR_FILTER, 'linear mipmap nearest': GameLib.D3.Texture.TYPE_LINEAR_MIPMAP_NEAREST_FILTER, 'linear mipmap linear': GameLib.D3.Texture.TYPE_LINEAR_MIPMAP_LINEAR_FILTER } ) ); } else if (property === 'minFilter') { controllers.push( folder.add( object, property, { 'nearest': GameLib.D3.Texture.TYPE_NEAREST_FILTER, 'nearest mipmap nearest': GameLib.D3.Texture.TYPE_NEAREST_MIPMAP_NEAREST_FILTER, 'nearest mipmap linear': GameLib.D3.Texture.TYPE_NEAREST_MIPMAP_LINEAR_FILTER, 'linear': GameLib.D3.Texture.TYPE_LINEAR_FILTER, 'linear mipmap nearest': GameLib.D3.Texture.TYPE_LINEAR_MIPMAP_NEAREST_FILTER, 'linear mipmap linear': GameLib.D3.Texture.TYPE_LINEAR_MIPMAP_LINEAR_FILTER } ) ); } else if (componentType === GameLib.Component.COMPONENT_TEXTURE && property === 'typeId') { controllers.push( folder.add( object, property, { 'normal': GameLib.D3.Texture.TEXTURE_TYPE_NORMAL, 'cube': GameLib.D3.Texture.TEXTURE_TYPE_CUBE, 'canvas': GameLib.D3.Texture.TEXTURE_TYPE_CANVAS } ) ); } else if (property === 'textureType') { controllers.push( folder.add( object, property, { 'unsigned byte': GameLib.D3.Texture.TYPE_UNSIGNED_BYTE, 'byte': GameLib.D3.Texture.TYPE_BYTE, 'short': GameLib.D3.Texture.TYPE_SHORT, 'unsigned short': GameLib.D3.Texture.TYPE_UNSIGNED_SHORT, 'int': GameLib.D3.Texture.TYPE_INT, 'unsigned int': GameLib.D3.Texture.TYPE_UNSIGNED_INT, 'float': GameLib.D3.Texture.TYPE_FLOAT, 'half float': GameLib.D3.Texture.TYPE_HALF_FLOAT } ) ); } else if (property === 'encoding') { controllers.push( folder.add( object, property, { 'linear': GameLib.D3.Texture.TYPE_LINEAR_ENCODING, 'srgb': GameLib.D3.Texture.TYPE_SRGB_ENCODING, 'gamma': GameLib.D3.Texture.TYPE_GAMMA_ENCODING, 'rgbe': GameLib.D3.Texture.TYPE_RGBE_ENCODING, 'log luv': GameLib.D3.Texture.TYPE_LOG_LUV_ENCODING, 'rgbm7': GameLib.D3.Texture.TYPE_RGBM7_ENCODING, 'rgbm16': GameLib.D3.Texture.TYPE_RGBM16_ENCODING, 'rgbd': GameLib.D3.Texture.TYPE_RGBD_ENCODING } ) ); } else if (property === 'lightType') { controllers.push( folder.add( object, property, { 'ambient': GameLib.D3.Light.LIGHT_TYPE_AMBIENT, 'directional': GameLib.D3.Light.LIGHT_TYPE_DIRECTIONAL, 'spot': GameLib.D3.Light.LIGHT_TYPE_SPOT, 'point': GameLib.D3.Light.LIGHT_TYPE_POINT } ) ); } else if (property === 'eventId') { var options = {}; for (var i = 0; i < 200; i++) { try { options[GameLib.Event.GetEventName(i)] = i; } catch (error) { } } controllers.push( folder.add( object, property, options ) ); } else if (property === 'functionType') { controllers.push( folder.add( object, property, { 'rotation': GameLib.D3.Animation.ANIMATION_FUNCTION_TYPE_ROTATION, 'translation': GameLib.D3.Animation.ANIMATION_FUNCTION_TYPE_TRANSLATION, 'scale': GameLib.D3.Animation.ANIMATION_FUNCTION_TYPE_SCALE } ) ); } else { /** * Try to guess a scale for this property */ if ( property === 'opacity' || property === 'metalness' || property === 'roughness' ) { controllers.push(folder.add(object, property, 0, 1.0, 0.001)); } else if ( property === 'shininess' || property === 'fov' ) { controllers.push(folder.add(object, property, -255, 255, 1)); } else if ( property === 'aspect' || property === 'wireframeLineWidth' || property === 'lineWidth' ) { controllers.push(folder.add(object, property, 0, 5, 0.001)); } else if ( property === 'bumpScale' || property === 'normalScale' || property === 'displacementScale' || property === 'heightMapScale' || property === 'intensity' ) { controllers.push(folder.add(object, property, -10, 10, 0.001)); } else if ( property === 'minX' || property === 'minY' || property === 'minZ' || property === 'maxX' || property === 'maxY' || property === 'maxZ' || property === 'offsetX' ) { controllers.push(folder.add(object, property, -100, 100, 0.01)); } else if ( property === 'widthSegments' || property === 'radiusSegments' || property === 'heightSegments' || property === 'particlesPerSecond' ) { controllers.push(folder.add(object, property, 1, 1000, 1)); } else if ( property === 'width' || property === 'height' || property === 'depth' || property === 'radius' ) { controllers.push(folder.add(object, property, 0, 1000, 0.1)); } else if ( property === 'near' || property === 'distanceGrain' || property === 'envMapIntensity' ) { controllers.push(folder.add(object, property, -10, 100, 0.001)); } else if ( property === 'bumpScale' ) { controllers.push(folder.add(object, property, 0, 20, 0.001)); } else if ( property === 'heightOffset' || property === 'rotationFactor' ) { controllers.push(folder.add(object, property, -100, 100, 0.001)); } else if ( property === 'friction' ) { controllers.push(folder.add(object, property, 0, 1000, 0.01)); } else if ( property === 'radiusTop' || property === 'radiusBottom' ) { controllers.push(folder.add(object, property, 0, 100, 0.1)); } else if ( property === 'mass' ) { controllers.push(folder.add(object, property, 0, 1000, 0.1)); } else if ( property === 'sensitivity' ) { controllers.push(folder.add(object, property, 1, 50, 1)); } else if ( property === 'thetaLength' || property === 'angle' ) { controllers.push(folder.add(object, property, -Math.PI * 2, Math.PI * 2, 0.01)); } else { controllers.push(folder.add(object, property, -1000, 1000, 0.1)); } } } controllers.map( function(controller) { if (property === 'name') { controller.onFinishChange( function(__handle, __folder) { return function(value) { componentTemplate.affected.map( function(component){ component[property] = value; component.updateInstance(property); } ); var li = __folder.domElement.getElementsByClassName('title')[0]; li.innerHTML = value; } }(controller, folder) ); } else { controller.onChange( function (value) { if (typeof this.initialValue === 'number') { value = Number(value); } componentTemplate.affected.map( function(component){ component[property] = value; component.updateInstance(property); } ); } ); } if (listen) { controller.listen(); } } ); }; /** * Push the mesh to our backup components, if in exclusiveMode (menu at top is selected), * otherwise, just to our normal components * @param data */ GameLib.System.GUI.prototype.meshSelected = function(data) { if (this.exclusiveMode) { GameLib.Utils.PushUnique(this.backupComponents, data.mesh); } else { GameLib.Utils.PushUnique(this.components, data.mesh); } }; /** * Same as selected above, but removes the mesh from the components * @param data */ GameLib.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); } } }; /** * 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 */ GameLib.System.GUI.prototype.buildGUI = function(data) { 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 GameLib.D3.Mesh); } ); } /** * Check if we have components to build a GUI for */ if (GameLib.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(); GameLib.Utils.PushUnique(result, component); components.map(function(component){ GameLib.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) { GameLib.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 parentEntity at the top of the gui */ 'parentEntity' : componentGroup.components[0].parentEntity }, 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 GameLib.Vector2 || result.template[property] instanceof GameLib.Vector3 || result.template[property] instanceof GameLib.Vector4 || result.template[property] instanceof GameLib.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 (GameLib.Utils.UndefinedOrNull(componentTemplate.template.name)) { name = GameLib.Component.GetComponentName(componentTemplate.componentType) + ' (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 GameLib.Vector2 || componentTemplate.template[templateProperty] instanceof GameLib.Vector3 || componentTemplate.template[templateProperty] instanceof GameLib.Vector4 ) { this.buildVectorControl(folder, componentTemplate, templateProperty); continue; } if (componentTemplate.template[templateProperty] instanceof GameLib.Quaternion) { this.buildQuaternionControl(folder, componentTemplate, templateProperty); } if ( templateProperty === 'parentEntity' || templateProperty === 'parentWorld' || templateProperty === 'parentMesh' || templateProperty === 'parentScene' ) { this.buildParentSelectionControl(folder, componentTemplate, templateProperty); continue; } if (componentTemplate.template[templateProperty] instanceof Array) { if ( templateProperty === 'vertices' || templateProperty === 'faces' ) { continue; } if ( componentTemplate.template.linkedObjects && componentTemplate.template.linkedObjects[templateProperty] instanceof Array ) { this.buildArrayManagerControl(folder, componentTemplate, templateProperty); } continue; } if (componentTemplate.template[templateProperty] instanceof GameLib.Color) { this.buildColorControl(folder, componentTemplate, templateProperty); continue; } if (typeof componentTemplate.template[templateProperty] === 'object') { if ( componentTemplate.template[templateProperty] instanceof GameLib.Component || ( componentTemplate.template.linkedObjects && componentTemplate.template.linkedObjects[templateProperty] ) ) { this.buildSelectControl(folder, componentTemplate, templateProperty) } else { //TODO: maybe start including some other types of objects //console.log('ignored : ' + templateProperty); } continue; } this.buildControl(folder, componentTemplate, templateProperty); // if ( // component.linkedObjects && // component.linkedObjects[property] // ) { // if (property === 'parentEntity') { // this.buildEntitySelectionControlFromArray( // folder, // component, // property, // entityManager // ) // } else if (component.linkedObjects[property] instanceof Array) { // this.buildArrayManager( // folder, // component, // property, // component.linkedObjects[property], // entityManager // ) // } else { // this.buildSelectControl(folder, component, property, entityManager, component.linkedObjects[property]); // } // // } else if (typeof (component[property]) === 'object') { // // if (this.isColor(component[property])) { // this.buildControl(folder, component, property); // } else { // //console.log('ignored: ' + property); // } // } else { // this.buildControl(folder, component, property, entityManager); // } } } }.bind(this) ); }.bind(this)); }; GameLib.System.GUI.prototype.meshDeleted = function(data) { data.meshes.map(function(mesh){ this.meshDeslected({ mesh : mesh }) }.bind(this)); this.buildGUI(null); }; GameLib.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); } }; GameLib.System.GUI.prototype.newEntity = function(data) { }; GameLib.System.GUI.prototype.stop = function() { GameLib.System.prototype.stop.call(this); this.guis.map(function(gui){ gui.domElement.instance.parentElement.removeChild(gui.instance.domElement); }); delete dat.GUI.removeEmtpyFolders; delete dat.GUI.removeAllFolders; this.buildGUISubscription.remove(); this.meshDeletedSubscription.remove(); this.meshSelectedSubscription.remove(); this.meshDeselectedSubscription.remove(); this.newEntitySubscription.remove(); this.componentRemovedSubscription.remove(); this.guis = []; };