/**
* Hotspot material handles the parse of the material config and the loading of the needed resource.<br>
* In the end it provides a THREE.MeshBasicMaterial when the resources are loaded.
*
* @constructor FORGE.HotspotMaterial
* @param {FORGE.Viewer} viewer - The viewer reference.
* @extends {FORGE.BaseObject}
*/
FORGE.HotspotMaterial = function(viewer, hotspotUid)
{
/**
* Viewer reference.
* @name FORGE.HotspotMaterial#_viewer
* @type {FORGE.Viewer}
* @private
*/
this._viewer = viewer;
/**
* The hotspot uid
* @name FORGE.HotspotMaterial#_hotspotUid
* @type {string}
* @private
*/
this._hotspotUid = hotspotUid;
/**
* Hotspot material config
* @name FORGE.HotspotMaterial#_config
* @type {?HotspotMaterialConfig}
* @private
*/
this._config = null;
/**
* Material input type.<br>
* Can be one of the values listed in FORGE.HotspotMaterial.types
* @name FORGE.HotspotMaterial#_type
* @type {string}
* @private
*/
this._type = "";
/**
* THREE texture.
* @name FORGE.HotspotMaterial#_texture
* @type {THREE.Texture|THREE.CanvasTexture}
* @private
*/
this._texture = null;
/**
* THREE material.
* @name FORGE.HotspotMaterial#_material
* @type {THREE.RawShaderMaterial}
* @private
*/
this._material = null;
/**
* The opacity of the material (between 0 and 1)
* @name FORGE.HotspotMaterial#_opacity
* @type {number}
* @default
* @private
*/
this._opacity = 1;
/**
* The transparent flag of the material.<br>
* if you use a PNG file as texture with some transparency, you have to set this to true.
* @name FORGE.HotspotMaterial#_transparent
* @type {boolean}
* @default
* @private
*/
this._transparent = false;
/**
* The base color of the material.<br>
* Can be a number 0xff0000 or a string: rgb(255, 0, 0), rgb(100%, 0%, 0%), hsl(0, 100%, 50%), #ff0000.
* @name FORGE.HotspotMaterial#_opacity
* @type {(number|string)}
* @default
* @private
*/
this._color = 0xffffff;
/**
* The display object used for the texture
* @name FORGE.HotspotMaterial#_displayObject
* @type {FORGE.DisplayObject}
* @private
*/
this._displayObject = null;
/**
* Does this material needs update
* @name FORGE.HotspotMaterial#_update
* @type {boolean}
* @private
*/
this._update = false;
/**
* Ready flag
* @name FORGE.HotspotMaterial#_ready
* @type {boolean}
* @private
*/
this._ready = false;
/**
* The onReady event dispatcher.
* @name FORGE.HotspotMaterial#_onReady
* @type {FORGE.EventDispatcher}
* @private
*/
this._onReady = null;
FORGE.BaseObject.call(this, "HotspotMaterial");
};
FORGE.HotspotMaterial.prototype = Object.create(FORGE.BaseObject.prototype);
FORGE.HotspotMaterial.prototype.constructor = FORGE.HotspotMaterial;
/**
* Material input type list.
* @name FORGE.HotspotMaterial.types
* @type {Object}
* @const
*/
FORGE.HotspotMaterial.types = {};
/**
* @name FORGE.HotspotMaterial.types.IMAGE
* @type {string}
* @const
*/
FORGE.HotspotMaterial.types.IMAGE = "image";
/**
* @name FORGE.HotspotMaterial.types.SPRITE
* @type {string}
* @const
*/
FORGE.HotspotMaterial.types.SPRITE = "sprite";
/**
* @name FORGE.HotspotMaterial.types.VIDEO
* @type {string}
* @const
*/
FORGE.HotspotMaterial.types.VIDEO = "video";
/**
* @name FORGE.HotspotMaterial.types.PLUGIN
* @type {string}
* @const
*/
FORGE.HotspotMaterial.types.PLUGIN = "plugin";
/**
* @name FORGE.HotspotMaterial.types.GRAPHICS
* @type {string}
* @const
*/
FORGE.HotspotMaterial.types.GRAPHICS = "graphics";
/**
* Material sides list.
* @name FORGE.HotspotMaterial.sides
* @type {Object}
* @const
*/
FORGE.HotspotMaterial.sides = {};
/**
* @name FORGE.HotspotMaterial.sides.FRONT
* @type {string}
* @const
*/
FORGE.HotspotMaterial.sides.FRONT = "front";
/**
* @name FORGE.HotspotMaterial.sides.BACK
* @type {string}
* @const
*/
FORGE.HotspotMaterial.sides.BACK = "back";
/**
* @name FORGE.HotspotMaterial.sides.DOUBLE
* @type {string}
* @const
*/
FORGE.HotspotMaterial.sides.DOUBLE = "double";
/**
* Materials presets.
* @name FORGE.HotspotMaterial.presets
* @type {Object}
* @const
*/
FORGE.HotspotMaterial.presets = {};
/**
* @name FORGE.HotspotMaterial.presets.TRANSPARENT
* @type {HotspotMaterialConfig}
* @const
*/
FORGE.HotspotMaterial.presets.TRANSPARENT =
{
color: "#ffffff",
opacity: 0,
transparent: false
};
/**
* @name FORGE.HotspotMaterial.presets.DEBUG
* @type {HotspotMaterialConfig}
* @const
*/
FORGE.HotspotMaterial.presets.DEBUG =
{
color: "#00ff00",
opacity: 0.8,
transparent: false
};
/**
* Parse the configuration object.
* @method FORGE.HotspotMaterial#_parseConfig
* @param {HotspotMaterialConfig} config - Configuration object of the material.
* @private
*/
FORGE.HotspotMaterial.prototype._parseConfig = function(config)
{
this._opacity = (typeof config.opacity === "number") ? FORGE.Math.clamp(config.opacity, 0, 1) : 1;
this._transparent = (typeof config.transparent === "boolean") ? config.transparent : false;
this._color = (typeof config.color === "string") ? config.color : 0xffffff;
this._update = (typeof config.update === "boolean") ? config.update : false;
this._side = (typeof config.side === "string") ? config.side : FORGE.HotspotMaterial.sides.DOUBLE;
// Hotspot with image as background
if (typeof config.image !== "undefined" && config.image !== null)
{
this._setupWithImage(config.image);
}
// Hotspot with animated sprite as background
else if (typeof config.sprite !== "undefined" && config.sprite !== null)
{
this._setupWithSprite(config.sprite);
}
// Hotspot with video as background
else if (typeof config.video !== "undefined" && config.video !== null)
{
this._setupWithVideo(config.video);
}
// Hotspot with plugin that provide a texture as background
else if (typeof config.plugin !== "undefined" && config.plugin !== null)
{
this._setupWithPlugin(config.plugin);
}
// Hotspot with graphical options as background
else
{
this._setupWithGraphics();
}
};
/**
* Setup hotspot material with an image as texture.
* @method FORGE.HotspotMaterial#_setupWithImage
* @param {(string|ImageConfig)} config - The image configuration you want to load and use as a texture.
* @private
*/
FORGE.HotspotMaterial.prototype._setupWithImage = function(config)
{
this._type = FORGE.HotspotMaterial.types.IMAGE;
// If the config is a string, we assume that this is the image url.
// We convert the config into a image config object.
if(typeof config === "string")
{
config = { url: config };
}
//Force the render mode to canvas
config.renderMode = FORGE.Image.renderModes.CANVAS;
this._displayObject = new FORGE.Image(this._viewer, config);
this._displayObject.onLoadComplete.addOnce(this._imageLoadCompleteHandler, this);
};
/**
* Image loaded event handler for the image setup.
* @method FORGE.HotspotMaterial#_imageLoadCompleteHandler
* @param {FORGE.Event} event - load event
* @private
*/
FORGE.HotspotMaterial.prototype._imageLoadCompleteHandler = function(event)
{
var image = /** @type {FORGE.Image} */ (event.emitter);
this.log("image load complete : " + image.element.src);
this._createTextureFromImage(image);
};
/**
* Create a THREE.Texture from the loaded FORGE.Image
* @method FORGE.HotspotMaterial#_createTextureFromImage
* @param {FORGE.Image} image - The FORGE.Image used to create the texture.
* @private
*/
FORGE.HotspotMaterial.prototype._createTextureFromImage = function(image)
{
this._displayObject = image;
this._texture = new THREE.Texture();
this._texture.generateMipmaps = false;
this._texture.minFilter = THREE.LinearFilter;
this.setTextureFrame(image.frame);
this.log("create texture from image");
this._setupComplete();
};
/**
* Setup hotspot material with a sprite as texture.
* @method FORGE.HotspotMaterial#_setupWithSprite
* @param {(string|SpriteConfig)} config - The sprite configuration you want to load and use as a texture.
* @private
*/
FORGE.HotspotMaterial.prototype._setupWithSprite = function(config)
{
this._type = FORGE.HotspotMaterial.types.SPRITE;
this._update = true;
this._displayObject = new FORGE.Sprite(this._viewer, config);
this._displayObject.onLoadComplete.addOnce(this._spriteLoadCompleteHandler, this);
};
/**
* Sprite loaded event handler for the sprite setup.
* @method FORGE.HotspotMaterial#_spriteLoadCompleteHandler
* @param {FORGE.Event} event - load event
* @private
*/
FORGE.HotspotMaterial.prototype._spriteLoadCompleteHandler = function(event)
{
var sprite = /** @type {FORGE.Sprite} */ (event.emitter);
this.log("sprite load complete");
this._createTextureFromSprite(sprite);
};
/**
* Create a THREE.Texture from the loaded FORGE.Sprite
* @method FORGE.HotspotMaterial#_createTextureFromSprite
* @param {FORGE.Sprite} sprite - The FORGE.Sprite used to create the texture.
* @private
*/
FORGE.HotspotMaterial.prototype._createTextureFromSprite = function(sprite)
{
this._texture = new THREE.Texture();
this._texture.generateMipmaps = false;
this._texture.minFilter = THREE.LinearFilter;
this.setTextureFrame(sprite.frame);
this.log("create texture from sprite");
this._setupComplete();
};
/**
* Setup hotspot material with a video as texture.
* @method FORGE.HotspotMaterial#_setupWithVideo
* @param {(string|VideoConfig)} config - The video configuration you want to load and use as a texture.
* @private
*/
FORGE.HotspotMaterial.prototype._setupWithVideo = function(config)
{
this._type = FORGE.HotspotMaterial.types.VIDEO;
this._update = true;
this._displayObject = new FORGE.VideoHTML5(this._viewer, this._hotspotUid+"-material-video");
this._displayObject.currentTime = 100000;
this._displayObject.onLoadedMetaData.addOnce(this._videoLoadedMetaDataHandler, this);
this._displayObject.load(config.url);
};
/**
* Video meta data loaded event handler for the video setup.
* @method FORGE.HotspotMaterial#_videoLoadedMetaDataHandler
* @param {FORGE.Event} event - load event
* @private
*/
FORGE.HotspotMaterial.prototype._videoLoadedMetaDataHandler = function(event)
{
var video = /** @type {FORGE.VideoBase} */ (event.emitter);
video.play();
this.log("video load complete");
this._createTextureFromVideo(video);
};
/**
* Create a THREE.Texture from the loaded FORGE.Video
* @method FORGE.HotspotMaterial#_createTextureFromVideo
* @param {FORGE.VideoBase} video - The FORGE.Video used to create the texture.
* @private
*/
FORGE.HotspotMaterial.prototype._createTextureFromVideo = function(video)
{
this.log("create texture from video");
this._texture = new THREE.Texture();
this._texture.generateMipmaps = false;
this._texture.minFilter = THREE.LinearFilter;
this._texture.image = video.element;
this._setupComplete();
};
/**
* Create a hotspot with a plugin that will provide a texture as input.
* @method FORGE.HotspotMaterial#_setupWithPlugin
* @param {string} config - The plugin instance UID you want to use as a texture provider.<br>
* The plugin have to have a "texture" public property.
* @private
*/
FORGE.HotspotMaterial.prototype._setupWithPlugin = function(config)
{
this._type = FORGE.HotspotMaterial.types.PLUGIN;
var plugin = this._viewer.plugins.get(config);
if (typeof plugin === "undefined" || plugin === null)
{
this._viewer.plugins.onInstanceCreate.add(this._pluginInstanceCreateHandler, this);
}
else
{
this._setPlugin(plugin);
}
};
/**
* Plugin instance create event handler.
* @method FORGE.HotspotMaterial#_pluginInstanceCreateHandler
* @param {FORGE.Event} event - instance create event
* @private
*/
FORGE.HotspotMaterial.prototype._pluginInstanceCreateHandler = function(event)
{
var plugin = /** @type {FORGE.Plugin} */ (event.data);
if (plugin.uid === this._config.plugin)
{
this._viewer.plugins.onInstanceCreate.remove(this._pluginInstanceCreateHandler, this);
this._setPlugin(plugin);
}
};
/**
* Once the plugin is created by the manager we can set the plugin that we will provide the texture.
* This method will check if the instance is ready, if not will setup a listener.
* @method FORGE.HotspotMaterial#_setPlugin
* @param {FORGE.Plugin} plugin - The plugin that will provides the texture.
* @private
*/
FORGE.HotspotMaterial.prototype._setPlugin = function(plugin)
{
if (plugin.instanceReady === true)
{
this._createTextureFromPlugin(plugin);
}
else
{
plugin.onInstanceReady.addOnce(this._pluginInstanceReadyHandler, this);
}
};
/**
* Plugin instance ready event handler.
* @method FORGE.HotspotMaterial#_pluginInstanceReadyHandler
* @param {FORGE.Event} event - instance ready event
* @private
*/
FORGE.HotspotMaterial.prototype._pluginInstanceReadyHandler = function(event)
{
var plugin = /** @type {FORGE.Plugin} */ (event.emitter);
if (plugin.instance === null || plugin.instanceReady === false)
{
throw new Error("Plugin instance not available");
}
this._createTextureFromPlugin(plugin);
};
/**
* Create a texture from a plugin that provides a display object on a "texture" property.
* @method FORGE.HotspotMaterial#_createTextureFromPlugin
* @param {FORGE.Plugin} plugin - plugin that provides the texture.
* @private
*/
FORGE.HotspotMaterial.prototype._createTextureFromPlugin = function(plugin)
{
this._displayObject = plugin.instance.texture;
this._texture = new THREE.Texture(this._displayObject.dom);
this._texture.format = THREE.RGBAFormat;
this._texture.minFilter = THREE.LinearFilter;
this._texture.generateMipmaps = false;
this._texture.needsUpdate = true;
this.log("create texture from plugin");
this._setupComplete();
};
/**
* Create a hotspot material with graphical attributes.
* @method FORGE.HotspotMaterial#_setupWithGraphics
* @private
* @todo Make a config to use only graphical properties
*/
FORGE.HotspotMaterial.prototype._setupWithGraphics = function()
{
this._type = FORGE.HotspotMaterial.types.GRAPHICS;
this._setupComplete();
};
/**
* This is the final setup step when any of the loading or wainting instance ready process is complete.
* @method FORGE.HotspotMaterial#_setupComplete
* @private
*/
FORGE.HotspotMaterial.prototype._setupComplete = function()
{
this._createShaderMaterial();
this._ready = true;
if (this._onReady !== null)
{
this._onReady.dispatch();
}
};
/**
* Create the THREE.MeshBasicMaterial that will be used on a THREE.Mesh
* @method FORGE.HotspotMaterial#_createShaderMaterial
* @private
*/
FORGE.HotspotMaterial.prototype._createShaderMaterial = function()
{
this.log("create shader material");
if(this._viewer.renderer.view.current === null)
{
return;
}
if (this._material !== null)
{
this._material.dispose();
this._material = null;
}
var shader = FORGE.Utils.clone(this._viewer.renderer.view.current.shaderWTS.mapping);
if (this._type === FORGE.HotspotMaterial.types.GRAPHICS)
{
shader.fragmentShader = FORGE.ShaderChunk.wts_frag_color;
shader.uniforms.tColor = { type: "c", value: new THREE.Color(this._color) };
}
shader.uniforms.tOpacity = { type: "f", value: this._opacity };
var vertexShader = FORGE.ShaderLib.parseIncludes(shader.vertexShader);
var fragmentShader = FORGE.ShaderLib.parseIncludes(shader.fragmentShader);
this._material = new THREE.RawShaderMaterial(
{
fragmentShader: fragmentShader,
vertexShader: vertexShader,
uniforms: /** @type {FORGEUniform} */ (shader.uniforms),
side: this._getThreeSide(this._side),
name: "HotspotMaterial"
});
this._material.transparent = this._transparent;
this._material.needsUpdate = true;
};
/**
* Converts the side string to the side number of Three
* @method FORGE.HotspotMaterial#_getThreeSide
* @param {string} [side] the string that represents the side of the material from the HotspotMaterial.sides
* @return {number} [description]
* @private
*/
FORGE.HotspotMaterial.prototype._getThreeSide = function(side)
{
var result = THREE.DoubleSide; // Default is double
switch(side)
{
case FORGE.HotspotMaterial.sides.FRONT:
result = THREE.FrontSide;
break;
case FORGE.HotspotMaterial.sides.BACK:
result = THREE.BackSide;
break;
case FORGE.HotspotMaterial.sides.DOUBLE:
result = THREE.DoubleSide;
break;
}
return result;
};
FORGE.HotspotMaterial.prototype.updateShader = function()
{
this._createShaderMaterial();
};
/**
* Load a material configuration
* @method FORGE.HotspotMaterial#load
* @param {HotspotMaterialConfig} config - The hotspot material configuration object.
*/
FORGE.HotspotMaterial.prototype.load = function(config)
{
this._config = config;
this._ready = false;
this._parseConfig(this._config);
};
/**
* Update method taht will be called by the Hotspot.
* @method FORGE.HotspotMaterial#update
*/
FORGE.HotspotMaterial.prototype.update = function()
{
if (this._material && this._material.uniforms)
{
this._material.uniforms.tTexture.value = this._texture;
this._material.uniforms.tOpacity.value = this._opacity;
}
if(this._texture !== null && this._update === true)
{
this._texture.needsUpdate = true;
}
};
/**
* Set texture frame
* @method FORGE.HotspotMaterial#setTextureFrame
* @param {FORGE.Rectangle=} frame - texture frame
*/
FORGE.HotspotMaterial.prototype.setTextureFrame = function(frame)
{
// Only support type IMAGE and SPRITE
if (this._displayObject === null || (this._type !== FORGE.HotspotMaterial.types.IMAGE && this._type !== FORGE.HotspotMaterial.types.SPRITE))
{
return;
}
var textureFrame = frame || new FORGE.Rectangle(0, 0, this._displayObject.element.naturalWidth, this._displayObject.element.naturalHeight);
this._displayObject.frame = textureFrame;
this._texture.image = this._displayObject.canvas;
this._texture.image.crossOrigin = "anonymous";
this._texture.needsUpdate = true;
this.update();
};
/**
* Dump the material configuration
* @method FORGE.HotspotMaterial#dump
* @return {HotspotMaterialConfig}
*/
FORGE.HotspotMaterial.prototype.dump = function()
{
var dump =
{
color: this._color,
opacity: this._opacity,
transparent: this._transparent,
side: this._side,
update: this._update
};
switch(this._type)
{
case FORGE.HotspotMaterial.types.IMAGE:
dump.image = this._config.image;
break;
case FORGE.HotspotMaterial.types.SPRITE:
dump.sprite = this._config.sprite;
break;
case FORGE.HotspotMaterial.types.VIDEO:
dump.video = this._config.video;
break;
case FORGE.HotspotMaterial.types.PLUGIN:
dump.plugin = this._config.plugin;
break;
}
return dump;
};
/**
* Destroy sequence.
* @method FORGE.HotspotMaterial#destroy
*/
FORGE.HotspotMaterial.prototype.destroy = function()
{
if (this._texture !== null)
{
this._texture.dispose();
this._texture = null;
}
if (this._material !== null)
{
this._material.dispose();
this._material = null;
}
if(this._displayObject !== null)
{
// Don't destroy the texture from plugin.
// Let the plugin erase its own content.
if (this._type !== FORGE.HotspotMaterial.types.PLUGIN)
{
this._displayObject.destroy();
}
this._displayObject = null;
}
if(this._onReady !== null)
{
this._onReady.destroy();
this._onReady = null;
}
this._viewer = null;
FORGE.BaseObject.prototype.destroy.call(this);
};
/**
* Get the type of texture provider of this hotspot material.
* @name FORGE.HotspotMaterial#type
* @readonly
* @type {string}
*/
Object.defineProperty(FORGE.HotspotMaterial.prototype, "type",
{
/** @this {FORGE.HotspotMaterial} */
get: function()
{
return this._type;
}
});
/**
* Get the THREE.Texture used for this hotspot material.
* @name FORGE.HotspotMaterial#texture
* @readonly
* @type {THREE.Texture}
*/
Object.defineProperty(FORGE.HotspotMaterial.prototype, "texture",
{
/** @this {FORGE.HotspotMaterial} */
get: function()
{
return this._texture;
}
});
/**
* Get the THREE.MeshBasicMaterial used for this hotspot material.
* @name FORGE.HotspotMaterial#material
* @readonly
* @type {THREE.MeshBasicMaterial}
*/
Object.defineProperty(FORGE.HotspotMaterial.prototype, "material",
{
/** @this {FORGE.HotspotMaterial} */
get: function()
{
return this._material;
}
});
/**
* Get the opacity of this hotspot material.
* @name FORGE.HotspotMaterial#opacity
* @readonly
* @type {number}
*/
Object.defineProperty(FORGE.HotspotMaterial.prototype, "opacity",
{
/** @this {FORGE.HotspotMaterial} */
get: function()
{
return this._opacity;
}
});
/**
* Get the transparent flag of this hotspot material.
* @name FORGE.HotspotMaterial#transparent
* @readonly
* @type {boolean}
*/
Object.defineProperty(FORGE.HotspotMaterial.prototype, "transparent",
{
/** @this {FORGE.HotspotMaterial} */
get: function()
{
return this._transparent;
}
});
/**
* Get the color of this hotspot material.
* @name FORGE.HotspotMaterial#color
* @readonly
* @type {(string|number)}
*/
Object.defineProperty(FORGE.HotspotMaterial.prototype, "color",
{
/** @this {FORGE.HotspotMaterial} */
get: function()
{
return this._color;
}
});
/**
* Get the side of this hotspot material.
* @name FORGE.HotspotMaterial#side
* @readonly
* @type {(string)}
*/
Object.defineProperty(FORGE.HotspotMaterial.prototype, "side",
{
/** @this {FORGE.HotspotMaterial} */
get: function()
{
return this._side;
}
});
/**
* Get the displayObject of this hotspot material.
* @name FORGE.HotspotMaterial#displayObject
* @readonly
* @type {(FORGE.Image|FORGE.DisplayObject)}
*/
Object.defineProperty(FORGE.HotspotMaterial.prototype, "displayObject",
{
/** @this {FORGE.HotspotMaterial} */
get: function()
{
return this._displayObject;
}
});
/**
* Get the ready flag of this hotspot material.
* @name FORGE.HotspotMaterial#ready
* @readonly
* @type {boolean}
*/
Object.defineProperty(FORGE.HotspotMaterial.prototype, "ready",
{
/** @this {FORGE.HotspotMaterial} */
get: function()
{
return this._ready;
}
});
/**
* Get the "onReady" {@link FORGE.EventDispatcher} of this hotspot material.<br/>
* Dispatched when the material texture is loaded and ready to be used by a THREE.Texture.
* @name FORGE.HotspotMaterial#onReady
* @readonly
* @type {FORGE.EventDispatcher}
*/
Object.defineProperty(FORGE.HotspotMaterial.prototype, "onReady",
{
/** @this {FORGE.HotspotMaterial} */
get: function()
{
if (this._onReady === null)
{
this._onReady = new FORGE.EventDispatcher(this);
}
return this._onReady;
}
});