var Sculpteo3DViewerId = 0,
	Sculpteo3DViewerTech = User.tech3d,
    Sculpteo3DViewerCap_static = 1,
    Sculpteo3DViewerCap_flash = 2,
    Sculpteo3DViewerCap_java  = 4,
    Sculpteo3DViewerCap_webgl = 8,
    Sculpteo3DViewerCaps = 0;

if (Sculpteo3DViewerRenderTimeout == null)
	Sculpteo3DViewerRenderTimeout = '200,5000'

if (Sculpteo3DViewerTech == null)
	Sculpteo3DViewerTech = "auto";
Sculpteo3DViewerTechInit = Sculpteo3DViewerTech;

$.fn.animatedPng = function(o) {
	return this.each(function() {
		var defaults = {
				nb_images: 19,
				image_width: 240,
				delay: 150
		}
		var options = $.extend(defaults, o);
		var idx = 1;
		if ($(this).data("bg-idx")) {
			idx = $(this).data("bg-idx");
		} else {
			idx = 1;
		}
		if (idx == options.nb_images) {
			$(this).data("bg-idx", 1);
		} else {
			$(this).data("bg-idx", idx+1);
		}
		$(this).css("background-position", (idx-1)*options.image_width+"px 0px")
		$(this).delay(options.delay);
		$(this).queue(function() { $(this).animatedPng(options); $(this).dequeue(); });
	});
}

var BrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{
			string: navigator.userAgent,
			subString: "Chrome",
			identity: "Chrome"
		},
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari",
			versionSearch: "Version"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			   string: navigator.userAgent,
			   subString: "iPhone",
			   identity: "iPhone/iPod"
	    },
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};
BrowserDetect.init();

function ViewerSupportsScaling()
{
	return !(BrowserDetect.OS == "Mac" && BrowserDetect.browser != "Safari");
}

window.onbeforeunload = function() {
	if (! killViewer()) {
		return $(".onbeforeunload_text:first").text();
	}
}

function resizeViewer(sculpteo3Dviewer)
{
	var data = $(sculpteo3Dviewer).data('viewer');
	var viewerId = data['viewerId'];
	var viewer = $("#Sculpteo3DViewer"+viewerId);
	var _width = viewer.parent().width();
	var _height = viewer.parent().height();
	if (_width > _height)
		_width = _height;
	if (_height > _width)
		_height = _width;
	viewer.css('width', _width);
	viewer.css('height', _height);
	$("#Sculpteo3DImage" + viewerId).remove();
}

function flashSetCookie(key, val)
{
	Utils.setCookie(key, val, 0, "/");
}

function flashDelCookie(key)
{
	Utils.delCookie(key, "/");
}

Sculpteo3DViewerEventHandler = function(sculpteo3Dviewer, subEventHandler, defaultTech) {
	return function() {
		var data = $(sculpteo3Dviewer).data('viewer');
		var viewerTech = defaultTech || data['viewerTech'];

		if (viewerTech == "flash") {
			arguments = arguments[0];
		} else {
			var args = new Array();
			for (k=0; k < arguments.length; k++) {
				args[k] = String(arguments[k]);
			}
			arguments = args;
		}
		var event = String(arguments[0]);
		log('Sculpteo3DViewer eventHandler '+event);
		if (event == "progress") {
			data = $(sculpteo3Dviewer).data('viewer');
			data['viewerLoaded'] = true;
			log('Sculpteo3DViewer progress '+arguments[1]);
			if (arguments[1] == "100") {
				data['viewerReady'] = true;
				$(".module-loading").hide();
				$("._3dPanel .module-remotectrl").show();
			}
			$(sculpteo3Dviewer).data('viewer', data);
		} else if (event == "new") {
			log('Sculpteo3DViewer new');
		} else if (event == "init") {
			log('Sculpteo3DViewer init');
			data = $(sculpteo3Dviewer).data('viewer');
			data['viewerLoaded'] = true;
			$(sculpteo3Dviewer).data('viewer', data);
			if (viewerTech == "java") {
				setTimeout(function () { resizeViewer(sculpteo3Dviewer) }, 0);
			}
			$("#Sculpteo3DViewerSelect").show();
			var namespace = "Sculpteo3DViewer" + data['viewerId'];
			$(window).unbind("focus." + namespace).bind("focus."  + namespace,  function() {
				$(sculpteo3Dviewer).Sculpteo3DViewer('idle', false);
			});
			$(window).unbind("blur." + namespace).bind("blur." + namespace,  function() {
				$(sculpteo3Dviewer).Sculpteo3DViewer('idle', true);
			});
		} else if (event == "loadModelError") {
			Utils.popupMessage(gettext('Session error. You may need to leave this page to have it working again.'));
		} else if (event == "log") {
			$.post(URLS['x_remove_log'], { log: arguments[1], referrer: document.referrer, location: $(location).attr("href") });
		} else if (event == "warnings") {
			var warnings = eval(String(arguments[1]));
			for (i in warnings) {
				var warning = warnings[i];
				Utils.popupMessage(String.format(gettext(warning.id), warning.params))
			}
		} else if (event == "flash_warnings") {
			var uuid = arguments[1];
			$.ajax({
			    url: URLS['x_message_warnings'] + '?uuid=' + uuid,
			    cache:false,
			    success: function(data, status, req) {
				var warnings = data;
				if (warnings) {
					for (i in warnings) {
						var warning = warnings[i];
						Utils.popupMessage(String.format(gettext(warning.id), warning.params))
					}
				}
			    }
			});
		} else if (event == "flash_error") {
			var uuid = arguments[1];
			$.ajax({
			    url: URLS['x_message_error'] + '?uuid=' + uuid,
			    cache:false,
			    success: function(data, status, req) {
				var errorMessage = data;
				if (errorMessage) {
					if (subEventHandler)
						subEventHandler(["errorMessage", errorMessage]);
				}
			    }
			});
		} else if (event == "flash_loaded") {
			data['viewer'].init();
		} else if (event == "camera" || event == "click") {
			$(sculpteo3Dviewer).Sculpteo3DViewer('idle', false);
		} else {
			log('Sculpteo3DViewer Unhandled event '+event);
		}
		if (subEventHandler != null) {
			subEventHandler(arguments);
		}
	}
};

function getModelSrc(src, viewerTech) {
	if (typeof src == 'object') {
		if (viewerTech == "java") {
			if (src.java)
				return src.java + '?uuid=' + src.uuid;
			else
				return null;
		} else if (viewerTech == "flash") {
			var uuid = src.uuid;
			if (src.flash) {
				return src.flash + '?uuid=' + uuid;
			}
				return null;
		} else if (viewerTech == "webgl") {
			var uuid = src.uuid;
			if (src.webgl) {
				return src.webgl + '?uuid=' + uuid;
			} else {
				return null;
			}
		} else {
			// static: returns whole object for a full reload 
			return src;
		}
	} else {
		if (viewerTech == "flash") {
			return encodeURIComponent(src);
		} else {
			return src;
		}
	}
}

$.fn.Sculpteo3DViewerStatic = function(viewerId, options) {
	jself = $(this);
	var v = null;
	require(["imageloader"], function() {
		$(".Sculpteo3DViewer").Sculpteo3DViewer('loadViewer', 'static');
	});
}

$.fn.Sculpteo3DViewerWebGL = function(viewerId, options) {
	jself = $(this);
	require(["imageloader", "webgl/binaryReader", "webgl/sylvester"], function() {
		require(["webgl/glUtils", "webgl/svm"], function() {
			$(".Sculpteo3DViewer").Sculpteo3DViewer('loadViewer', 'webgl');
		});
	});
}

function killViewer(el) {
	if (!el) {
		el = $(".Sculpteo3DViewer");
	}
	var killed = true;
	$(el).each(function() {
		log("Kill viewer");
		killed &= $(el).Sculpteo3DViewer("kill");
	});
	return killed;
}

function addViewerSelect(jself, viewerId, options) {
	if (!options.show_viewer_select) {
		return;
	}
	// add the viewer selection links
	if ($(".select_technology").length == 0) {
		jself.after("<div id='Sculpteo3DViewerSelect' class='hidden' style='height:16px;line-height:16px;'><label class='inline' style='margin:0px; font-size: 9px;'>"+gettext("3D technology")+" : </label><span class='select_technology small' style='font-size: 9px;'></span></div>");
	}
	
	//var links = "<div id='Sculpteo3DViewerSelect' class='hidden' style='height:16px;line-height:16px;'>";
	var links = "";
	var others = Sculpteo3DViewerCaps;
	if (Sculpteo3DViewerTech == 'webgl')
		others &= ~Sculpteo3DViewerCap_webgl;
	if (Sculpteo3DViewerTech == 'java')
		others &= ~Sculpteo3DViewerCap_java;
	if (Sculpteo3DViewerTech == 'flash')
		others &= ~Sculpteo3DViewerCap_flash;
	if (Sculpteo3DViewerTech == 'static')
		others &= ~Sculpteo3DViewerCap_static;

	//links+="<label class='inline' style='margin:0px;'>3D technology : </label><span class='select_technology small'>";
	
	if (others) {
		if (Sculpteo3DViewerTechInit == 'auto')
			links += '<u class="Sculpteo3DViewer'+viewerId+'_auto" style="text-decoration:none;">Auto</u>';
		else
			links += '<a class="Sculpteo3DViewer'+viewerId+'_auto selectable">Auto</a>';
		if (Sculpteo3DViewerTechInit == 'webgl')
			links += ' | <u class="Sculpteo3DViewer'+viewerId+'_webgl" style="text-decoration:none;">WebGL</u>';
		else if (Sculpteo3DViewerCaps & Sculpteo3DViewerCap_webgl)
			links += ' | <a class="Sculpteo3DViewer'+viewerId+'_webgl selectable">WebGL</a>';
		if (Sculpteo3DViewerTechInit == 'java')
			links += ' | <u class="Sculpteo3DViewer'+viewerId+'_java" style="text-decoration:none;">Java</u>';
		else if (Sculpteo3DViewerCaps & Sculpteo3DViewerCap_java)
			links += ' | <a class="Sculpteo3DViewer'+viewerId+'_java selectable">Java</a>';
		if (Sculpteo3DViewerTechInit == 'flash')
			links += ' | <u class="Sculpteo3DViewer'+viewerId+'_flash" style="text-decoration:none;">Flash</u>';
		else if (Sculpteo3DViewerCaps & Sculpteo3DViewerCap_flash)
			links += ' | <a class="Sculpteo3DViewer'+viewerId+'_flash selectable">Flash</a>';
		if (Sculpteo3DViewerTechInit == 'static')
			links += ' | <u class="Sculpteo3DViewer'+viewerId+'_static" style="text-decoration:none;">JS</u>';
		else if (Sculpteo3DViewerCaps & Sculpteo3DViewerCap_static)
			links += ' | <a class="Sculpteo3DViewer'+viewerId+'_static selectable">JS</a>';
	} else {
		links += '<u class="Sculpteo3DViewer'+viewerId+'_auto">Auto</u>';
	}
	//links+="</span>";

	//$("#Sculpteo3DViewerSelect").html(links);
	$(".select_technology").html(links);
	
	$(".Sculpteo3DViewer" + viewerId+ "_auto.selectable").unbind('click').click(function(evt) { jself.Sculpteo3DViewer('selectViewer', 'auto'); });
	$(".Sculpteo3DViewer" + viewerId+ "_webgl.selectable").unbind('click').click(function(evt) { jself.Sculpteo3DViewer('selectViewer', 'webgl'); $(".module-remotectrl-content .auto-rotate").show(); });
	$(".Sculpteo3DViewer" + viewerId+ "_java.selectable").unbind('click').click(function(evt) { jself.Sculpteo3DViewer('selectViewer', 'java'); $(".module-remotectrl-content .auto-rotate").show(); });
	$(".Sculpteo3DViewer" + viewerId+ "_flash.selectable").unbind('click').click(function(evt) { jself.Sculpteo3DViewer('selectViewer', 'flash'); });
	$(".Sculpteo3DViewer" + viewerId+ "_static.selectable").unbind('click').click(function(evt) { jself.Sculpteo3DViewer('selectViewer', 'static'); });
}

function WebGLDetect()
{
	if (!window.WebGLRenderingContext)
		return false;

	var canvas;
	try {
		canvas = document.createElement('canvas');
	} catch(e) {
		return false;
	}

	var gl = null;
	var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
	for (var i = 0; i < names.length; ++i) {
		try {
			gl = canvas.getContext(names[i]);
		} catch(e) {}
		if (gl) {
			break;
		}
	}

	if (!gl)
		return false;
	return true;
}

function Sculpteo3DViewerDetectCaps()
{
	Sculpteo3DViewerCaps = Sculpteo3DViewerCap_static;
	if (WebGLDetect()) {
		Sculpteo3DViewerCaps |= Sculpteo3DViewerCap_webgl;
	}
	if (navigator.javaEnabled()) {
		Sculpteo3DViewerCaps |= Sculpteo3DViewerCap_java;
	}
	if (FlashDetect.versionAtLeast(10)) {
		Sculpteo3DViewerCaps |= Sculpteo3DViewerCap_flash;
	}

	/* workarounds */
	if (BrowserDetect.OS == "Mac" && BrowserDetect.browser == "Chrome")
		Sculpteo3DViewerCaps &= ~Sculpteo3DViewerCap_java;
	if (BrowserDetect.OS == "Mac" && BrowserDetect.browser == "Firefox" &&
	    typeof(BrowserDetect.version) == "number" &&
	    BrowserDetect.version >= 4)
		Sculpteo3DViewerCaps &= ~Sculpteo3DViewerCap_java;
	if (BrowserDetect.OS == "iPhone/iPod")
		Sculpteo3DViewerCaps &= ~Sculpteo3DViewerCap_webgl;

	Sculpteo3DViewerCaps &= Sculpteo3DViewerCapMask;
}

function fakeLoad(sculpteo3DViewer, url, callback)
{
	var viewer_data = $(sculpteo3DViewer).data("viewer");
	$.ajax({
		url: url + "&viewer=static",
		cache:false,
		success: function(data, status, req) {
			var warnings = req.getResponseHeader("X-Sculpteo-Warnings");
			var static_handler = new Sculpteo3DViewerEventHandler(sculpteo3DViewer, viewer_data.options.eventhandler, 'static');
			if (warnings) {
				static_handler("warnings", warnings);
			}
			static_handler("progress", "100");
			callback();
		},
		error: function(req, textStatus, errorThrown) {
			var error = req.getResponseHeader("X-Sculpteo-Message");
			var static_handler = new Sculpteo3DViewerEventHandler(sculpteo3DViewer, viewer_data.options.eventhandler, 'static');

			if (error) {
				static_handler("errorMessage", error);
			}
			static_handler("progress", "100");
		}
	});
}

function getSculpteo3DViewer(el) {
	return $(el).closest(".Sculpteo3DViewer");
}

function getSculpteo3DInternalViewer(el) {
	return $(el).closest(".Sculpteo3DViewer").Sculpteo3DViewer("viewer");
}

(function( $ ){
	var methods = {
		init : function( settings ) {
			return this.each(function(){
				var $this = $(this), 
					data = $this.data('viewer');
				if (data && data['viewerInited']) {
					return $this.Sculpteo3DViewer('options', settings );
				}
				$this.addClass("Sculpteo3DViewer");
				var defaults = {
					material:'color_plastic',
					bgcolor:[239,241,243],
					transform:'1,0,0,0',
					zoom: 1.2,
					autorotate:true,
					wireframe:false,
					pointcloud:false,
					orientation:false,
					resetview:false,
					issues:0,
					selectedfaces:false,
					showhelp: true,
					fading: true,
					bgImage:"",
					unitmm:0.0,
					mview_jnlp: URLS['mview_jnlp'],
					render: URLS['render'],
					rendertimeout: Sculpteo3DViewerRenderTimeout,
					rendersize: '1024,1024',
					quality:2,
					smooth_normals:false,
					translucency:0,
					bumpnoise:"",
					min_solidity: -1.0,
					max_solidity: -1.0,
					envmapurl: '',
					envmap: false,
					show_viewer_select: true,
					rendercache: false,
					uuid: '',
					update_geometry: true,
					update_material: true,
					age: 0
				};
				var options = $.extend(defaults, settings);
				Sculpteo3DViewerDetectCaps();
				var viewerTech = Sculpteo3DViewerTech;
				if (viewerTech == 'webgl' &&
				    !(Sculpteo3DViewerCaps & Sculpteo3DViewerCap_webgl))
					Sculpteo3DViewerTechInit = Sculpteo3DViewerTech = viewerTech = 'auto';
				if (viewerTech == 'java' &&
				    !(Sculpteo3DViewerCaps & Sculpteo3DViewerCap_java))
					Sculpteo3DViewerTechInit = Sculpteo3DViewerTech = viewerTech ='auto';
				if (viewerTech == 'flash' &&
				    !(Sculpteo3DViewerCaps & Sculpteo3DViewerCap_flash))
					Sculpteo3DViewerTechInit = Sculpteo3DViewerTech = viewerTech = 'auto';
				if (viewerTech == 'static' &&
				    !(Sculpteo3DViewerCaps & Sculpteo3DViewerCap_static))
					Sculpteo3DViewerTechInit = Sculpteo3DViewerTech = viewerTech ='auto';

				if (viewerTech == "auto") {
					/* prefer WebGL over Flash over Java over JS */
					if (Sculpteo3DViewerCaps & Sculpteo3DViewerCap_webgl)
						viewerTech = "webgl";
					else if (Sculpteo3DViewerCaps & Sculpteo3DViewerCap_flash)
						viewerTech = "flash";
					else if (Sculpteo3DViewerCaps & Sculpteo3DViewerCap_java)
						viewerTech = "java";
					else if (Sculpteo3DViewerCaps & Sculpteo3DViewerCap_static)
						viewerTech = "static";
					else
						viewerTech = "";
				}
				/* wait for files */
				if (options.src && options.src.depend_on_jobs && options.src.depend_on_jobs.length) {
					return $this.Sculpteo3DViewer('waitonjobs', options, viewerTech);
				}
				var viewerId = Sculpteo3DViewerId++ ;
				// make the event handler a global variable, for easier calls from Java and Flash
				var eventHandler = window['Sculpteo3DViewerEventHandler'+viewerId] = new Sculpteo3DViewerEventHandler($this, options.eventhandler);
				options._eventhandler = eventHandler;
				options.eventhandlerName = 'Sculpteo3DViewerEventHandler'+viewerId;
				
				$this.data('viewer', {
					viewerTech: viewerTech,
					viewerInited: true,
					viewerLoaded: false,
					viewerReady: false,
					options: options,
					viewerId: viewerId
				});
				if (options.src && getModelSrc(options.src, viewerTech) == null)
					viewerTech = "static";

				if (viewerTech == "webgl") {
					$this.Sculpteo3DViewerWebGL(viewerId, options);
				} else if (viewerTech == "static") {
					$this.Sculpteo3DViewerStatic(viewerId, options);
				} else if (viewerTech == "flash") {
					require(["swfobject", "RightClick"], function() {
						$(".Sculpteo3DViewer").Sculpteo3DViewer('loadViewer', 'flash');
					});
				} else if (navigator.javaEnabled()) {
					$(".Sculpteo3DViewer").Sculpteo3DViewer('loadViewer', 'java');
					$("._3dPanel .module-loading").show();
				} else {
					$this.data('viewer', {
						viewerTech: viewerTech,
						viewerInited: true,
						viewerLoaded: true,
						viewerReady: false,
						options: options,
						viewerId: viewerId
					});
					$this.html(String.format(gettext('JAVA_NOT_PRESENT'), URLS['preferences' ]));
				}
			});
		},
		
		waitonjobs: function(options, viewerTech) {
			var $this = $(this);

			// kill old viewer if it exists already
			$this.Sculpteo3DViewer('kill');

			var viewer = $this;

			$(".tech3d").hide();
			$(".module-loading").show();

			if (options.src && options.src.image) {
				viewer.html('<img src="' + options.src.image + '" width="' + viewer.width() + '" height="' + viewer.height() + '" class="relative"/>');
			}

			var viewer_started = false;
			var start_viewer = function() {
				if (!viewer_started) {
					$(".module-loading").hide();
					viewer.html("");
					viewer.Sculpteo3DViewer(options);
					viewer_started = true;
				}
			}

			var process_msg = function(msg) {
				log("process msg");
				log(msg);
				if (msg.java) {
					options.src.java = msg.java;
					if (viewerTech == "java") {
						start_viewer();
					}
				}

				if (msg.flash) {
					options.src.flash = msg.flash;
					if (viewerTech == "flash") {
						start_viewer();
					}
				}

				if (msg.webgl) {
					options.src.webgl = msg.webgl;
					if (viewerTech == "webgl") {
						start_viewer();
					}
				}

				if (msg.image) {
					options.src.image = msg.image;
				}
			}

			var jobs = options.src.depend_on_jobs;
			delete options.src.depend_on_jobs;

			if (viewerTech == "static") {
				start_viewer();
			}

			sculpteoJob(jobs).progress(function(job, msg) {
				process_msg(msg);
			}).success(function(job, msg) {
				process_msg(msg);
				$(".tech3d").show();
			});
		},

		loadViewer: function(viewerTech) {
			return this.each(function() {
				var data = $(this).data('viewer');
				if (!data || data['viewerTech'] != viewerTech || data['viewer'] || $(this).data('viewerLoading')) {
					return;
				}
				$(this).data('viewerLoading', true);
				var viewerId = data['viewerId'],
					options = data['options'];
				switch(viewerTech) {
					case 'static':
						data['viewer'] = new Sculpteo3DViewerStatic($(this), viewerId, options);
						break;
					case 'webgl':
						data['viewer'] = new Sculpteo3DViewerWebGL($(this), viewerId, options);
						break;
					case 'flash':
						$(this).Sculpteo3DViewerFlash(viewerId, options);
						data['viewer'] =  document.getElementById("Sculpteo3DViewer"+viewerId);
						break;
					case 'java':
						$(this).Sculpteo3DViewerJava(viewerId, options);
						data['viewer'] = document.getElementById("Sculpteo3DViewer"+viewerId).getSubApplet()
						break;
				}
				$(this).data('viewer', data);
				$(this).data('viewerLoading', false);
			});
		},
		
		options: function(options) {
			return this.each(function(){
				var $this = $(this), 
					data = $this.data('viewer');
				if (typeof(options.age) != 'undefined' && typeof(data['options'].age) != 'undefined') {
					if (options.age < data['options'].age) {
						data['options']._eventhandler('discard');
						return ;
					}
				}
				data['options'] = $.extend(data['options'], options);
				$this.data('viewer', data);
				var viewer = data['viewer'];
				if (!$.isEmptyObject(options) && data['viewerReady'] && viewer) {
					viewer.lockDisplay(true);

					if (options.fading!=null) {
						viewer.setFading(options.fading);
					}
	
					if (options.src!=null) {
						if (options.src.depend_on_jobs && options.src.depend_on_jobs.length) {
							return $this.Sculpteo3DViewer('waitonjobs', options, data['viewerTech']);
						}

						viewer.loadModel(getModelSrc(options.src, data['viewerTech']));
					}
					if (options.load!=null) {
						options.src = options.load;
						if (options.update_geometry == false &&
							data['viewerTech'] != "webgl" &&
							data['viewerTech'] != "java") {
							fakeLoad(this, options.load, function() {
								viewer.setUpdate();
							});
						} else {
							viewer.setModel(options.load, 0);
						}
					}
					if (options.showAll) {
						viewer.showAll();
					}
					if (options.hideAll != null) {
						viewer.hideAll(options.hideAll);
					}
					if (options.loadPost!=null) {
						viewer.loadModelPost(options.loadPost.url, options.loadPost.content);
					}
					if (options.resetview) {
						viewer.resetView();
					}
					if (options.autorotate != null) {
						viewer.setAutorotate(options.autorotate);
					}
					if (options.toggleAutorotate) {
						viewer.toggleAutorotate();
					}
					if (options.material) {
						viewer.setMaterial(options.material);
					}
					if (options.bgcolor) {
						viewer.setBackgroundColor(options.bgcolor[0],options.bgcolor[1],options.bgcolor[2]);
					}
					if (options.units) {
						viewer.setUnits(options.units);
					}
					if (options.wireframe!=null) {
						viewer.setWireframe(options.wireframe);
					}
					if (options.pointcloud!=null) {
						viewer.setPointCloud(options.pointcloud);
					}
					if (options.orientation!=null) {
						viewer.setOrientation(options.orientation);
					}
					if (options.showhelp!=null) {
						viewer.setShowHelp(options.showhelp);
					}
					if (options.issues!=null) {
						viewer.setIssues(options.issues);
					}
					if (options.selectedfaces!=null) {
						viewer.setSelectedFaces(options.selectedfaces);
					}
					if (options.bgImage) {
						viewer.setBackgroundImage(options.bgImage);
						viewer.setdisable3D(true);
					} 
					if (options.unsetBgImage) {
						options.bgImage = "";
						viewer.unsetBackgroundImage();
						viewer.setdisable3D(false);
					}
					if (options.rotateX90!=null) {
						rotateTo(viewer, rotateX90);
					}
					if (options.rotateY90!=null) {
						rotateTo(viewer, rotateY90);
					}
					if (options.rotateZ90!=null) {
						rotateTo(viewer, rotateZ90);
					}
					if (options.unitmm!=null) {
						viewer.setUnits(options.unitmm);
					}
					if (options.transform!=null) {
						viewer.setTransform(options.transform);
					}
					if (options.zoom!=null) {
						viewer.setZoom(options.zoom);
					}
					if (options.quality!=null) {
						viewer.setQuality(options.quality);
					}
					if (options.smooth_normals!=null) {
						viewer.setSmoothNormals(options.smooth_normals);
					}
					if (options.translucency!=null) {
						viewer.setTranslucency(options.translucency);
					}
					if (options.bumpnoise!=null) {
						viewer.setBumpNoise(options.bumpnoise);
					}
					if (options.solidity) {
						viewer.setSolidity(options.solidity[0],options.solidity[1]);
					}
					if (options.envmap!=null) {
						viewer.setEnvmap(options.envmap);
					}
	
					viewer.lockDisplay(false);
				}
			});
		},
		
		selectViewer: function(tech3d, noSetViewer) {
			return this.each(function(){
				var $this = $(this), 
					data = $this.data('viewer');
				if (tech3d == data['viewerTech']) {
					return;
				}
				if (!noSetViewer) {
					$.ajax({ url: '' + URLS['set_viewer'] + tech3d, async: false });
				}
				$this.Sculpteo3DViewer('kill');
				$this.html("");
				Sculpteo3DViewerTechInit = Sculpteo3DViewerTech = User.tech3d = tech3d;
				$("._3dPanel  .module-remotectrl").hide();
				// reload data, killviewer modifies it
				data = $this.data('viewer');
				$this.Sculpteo3DViewer(data['options']);
			});
		},

		switchForOverlay: function() {
			return this.each(function(){
				var $this = $(this), 
					data = $this.data('viewer');
				if (!data || data['viewerTech3d'] != 'java') {
					return;
				}
				data['previousTech3d'] = data['viewerTech3d'];
				$this.data('viewer', data);
				$this.Sculpteo3DViewer('selectViewer', 'static', true);
			});
		},

		restoreSwitchForOverlay: function() {
			return this.each(function(){
				var $this = $(this), 
					data = $this.data('viewer');
				var tech3d = data['previousTech3d'];
				if (!data || ! tech3d) {
					return;
				}
				data['previousTech3d'] = null;
				$this.data('viewer', data);
				$this.Sculpteo3DViewer('selectViewer', tech3d, true);
			});
		},

		idle: function(enabled) {
			var viewer = $(this).data('viewer')['viewer'];
			viewer.lockDisplay(enabled);
		},

		kill: function(callback) {
			var $this = $(this), 
			data = $this.data('viewer');
			if (!data) {
				if (callback) {
					callback();
				}
				return true;
			}
			
			var viewerTech = data['viewerTech'];
			if (viewerTech == 'java' && ! data['viewerLoaded']) {
				return false;
			}
			
			data['viewerInited'] = false;
			if (viewerTech == "java" || viewerTech == "flash") {
				// kill the applet before doing any jquery
				var viewer = document.getElementById("Sculpteo3DViewer"+data['viewerId']);
				if (viewer)
					viewer.parentNode.innerHTML = "";
			} else if (viewerTech == "static") {
				var viewer = data['viewer'];
				// clear the remote rendering timeouts
				if (viewer) {
					clearTimeout(viewer.timeout);
					clearTimeout(viewer.timeout2);
				}
			} else if (viewerTech == "webgl") {
				var viewer = data['viewer'];
				// clear the remote rendering timeouts
				if (viewer ) {
					clearInterval(viewer.tick);
					clearTimeout(viewer.timeout);
					clearTimeout(viewer.timeout2);
				}
			}
			$this.data('viewer', data);
			var namespace = "Sculpteo3DViewer" + data['viewerId'];
			$(window).unbind("focus." + namespace);
			$(window).unbind("blur." + namespace);
			if (callback) {
				callback();
			}
			return true;
		},
		
		destroy : function( ) {
			return this.each(function(){
				var $this = $(this), 
					data = $this.data('viewer');
				if (!data) {
					return;
				}
				$this.html("");
				data['viewerInited'] = false;
				data['viewerLoaded'] = false;
				data['viewerReady'] = false;
				$this.data('viewer', data);
			})
		},
		
		viewer : function() {
			var data = $(this).data('viewer');
			if (data) {
				return data['viewer'];
			}
			return false;
		}
	};
		
	$.fn.Sculpteo3DViewer = function( method ) {
		if ( methods[method] ) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.Sculpteo3DViewer' );
		}
	};
})( jQuery );

function rotateTo(viewer, func) {
	require(["Tween"], function() {
	
		viewer.setAutorotate(false);
		
		var current = ""+viewer.getTransform();
		if (Sculpteo3DViewerTech == "static") {
			viewer.setTransform(func(current));
			return;
		}
	
		var t = { 
					w : current.split(/,/)[0],
					x : current.split(/,/)[1],
					y : current.split(/,/)[2],
					z : current.split(/,/)[3]
				};
	
		var destination = func(current).split(/,/);
	
		var tweenW = new Tween(t, "w", Tween.strongEaseOut, current.split(/,/)[0]+0, destination[0]+0, .3, '');
		var tweenX = new Tween(t, "x", Tween.strongEaseOut, current.split(/,/)[1]+0, destination[1]+0, .3, '');
		var tweenY = new Tween(t, "y", Tween.strongEaseOut, current.split(/,/)[2]+0, destination[2]+0, .3, '');
		var tweenZ = new Tween(t, "z", Tween.strongEaseOut, current.split(/,/)[3]+0, destination[3]+0, .3, '');
		var a = new Object();
		a.onMotionChanged = function() {
			viewer.setTransform(t.w+","+t.x+","+t.y+","+t.z);
		};
		tweenW.addListener(a);
		tweenW.start();
		tweenX.start();
		tweenY.start();
		tweenZ.start();
	});
}

function rotateX90(t) {
   var G = (t+"").split(/,/);
   var gx, gy, gz, gw;
   gw = G[0];
   gx = G[1];
   gy = G[2];
   gz = G[3];

   var r = 0.707106781186548;
   var sx, sy, sz, sw;

   sw = r * gw + r * gx;
   sx = r * gx - r * gw;
   sy = r * gy + r * gz;
   sz = r * gz - r * gy;

   return(sw + ',' + sx + ',' + sy + ',' + sz);
}

function rotateY90(t) {
   var G = (t+"").split(/,/);
   var gx, gy, gz, gw;
   gw = G[0];
   gx = G[1];
   gy = G[2];
   gz = G[3];

   var r = 0.707106781186548;
   var sx, sy, sz, sw;

   sw = r * gw - r * gz;
   sx = r * gx - r * gy;
   sy = r * gy + r * gx;
   sz = r * gz + r * gw;

   return(sw + ',' + sx + ',' + sy + ',' + sz);
}

function rotateZ90(t) {
   var G = (t+"").split(/,/);
   var gx, gy, gz, gw;
   gw = G[0];
   gx = G[1];
   gy = G[2];
   gz = G[3];

   var r = 0.707106781186548;
   var sx, sy, sz, sw;

   sw = r * gw + r * gy;
   sx = r * gx - r * gz;
   sy = r * gy - r * gw;
   sz = r * gz + r * gx;

   return(sw + ',' + sx + ',' + sy + ',' + sz);
}


/*
Flash detection
Copyright (c) Copyright (c) 2007, Carl S. Yestrau All rights reserved.
Code licensed under the BSD License: http://www.featureblend.com/license.txt
Version: 1.0.4
*/
var FlashDetect = new function(){
    var self = this;
    self.installed = false;
    self.raw = "";
    self.major = -1;
    self.minor = -1;
    self.revision = -1;
    self.revisionStr = "";
    var activeXDetectRules = [
        {
            "name":"ShockwaveFlash.ShockwaveFlash.7",
            "version":function(obj){
                return getActiveXVersion(obj);
            }
        },
        {
            "name":"ShockwaveFlash.ShockwaveFlash.6",
            "version":function(obj){
                var version = "6,0,21";
                try{
                    obj.AllowScriptAccess = "always";
                    version = getActiveXVersion(obj);
                }catch(err){}
                return version;
            }
        },
        {
            "name":"ShockwaveFlash.ShockwaveFlash",
            "version":function(obj){
                return getActiveXVersion(obj);
            }
        }
    ];
    /**
     * Extract the ActiveX version of the plugin.
     * 
     * @param {Object} The flash ActiveX object.
     * @type String
     */
    var getActiveXVersion = function(activeXObj){
        var version = -1;
        try{
            version = activeXObj.GetVariable("$version");
        }catch(err){}
        return version;
    };
    /**
     * Try and retrieve an ActiveX object having a specified name.
     * 
     * @param {String} name The ActiveX object name lookup.
     * @return One of ActiveX object or a simple object having an attribute of activeXError with a value of true.
     * @type Object
     */
    var getActiveXObject = function(name){
        var obj = -1;
        try{
            obj = new ActiveXObject(name);
        }catch(err){
            obj = {activeXError:true};
        }
        return obj;
    };
    /**
     * Parse an ActiveX $version string into an object.
     * 
     * @param {String} str The ActiveX Object GetVariable($version) return value. 
     * @return An object having raw, major, minor, revision and revisionStr attributes.
     * @type Object
     */
    var parseActiveXVersion = function(str){
        var versionArray = str.split(",");//replace with regex
        return {
            "raw":str,
            "major":parseInt(versionArray[0].split(" ")[1], 10),
            "minor":parseInt(versionArray[1], 10),
            "revision":parseInt(versionArray[2], 10),
            "revisionStr":versionArray[2]
        };
    };
    /**
     * Parse a standard enabledPlugin.description into an object.
     * 
     * @param {String} str The enabledPlugin.description value.
     * @return An object having raw, major, minor, revision and revisionStr attributes.
     * @type Object
     */
    var parseStandardVersion = function(str){
        var descParts = str.split(/ +/);
        var majorMinor = descParts[2].split(/\./);
        var revisionStr = descParts[3];
        return {
            "raw":str,
            "major":parseInt(majorMinor[0], 10),
            "minor":parseInt(majorMinor[1], 10), 
            "revisionStr":revisionStr,
            "revision":parseRevisionStrToInt(revisionStr)
        };
    };
    /**
     * Parse the plugin revision string into an integer.
     * 
     * @param {String} The revision in string format.
     * @type Number
     */
    var parseRevisionStrToInt = function(str){
        return parseInt(str.replace(/[a-zA-Z]/g, ""), 10) || self.revision;
    };
    /**
     * Is the major version greater than or equal to a specified version.
     * 
     * @param {Number} version The minimum required major version.
     * @type Boolean
     */
    self.majorAtLeast = function(version){
        return self.major >= version;
    };
    /**
     * Is the minor version greater than or equal to a specified version.
     * 
     * @param {Number} version The minimum required minor version.
     * @type Boolean
     */
    self.minorAtLeast = function(version){
        return self.minor >= version;
    };
    /**
     * Is the revision version greater than or equal to a specified version.
     * 
     * @param {Number} version The minimum required revision version.
     * @type Boolean
     */
    self.revisionAtLeast = function(version){
        return self.revision >= version;
    };
    /**
     * Is the version greater than or equal to a specified major, minor and revision.
     * 
     * @param {Number} major The minimum required major version.
     * @param {Number} (Optional) minor The minimum required minor version.
     * @param {Number} (Optional) revision The minimum required revision version.
     * @type Boolean
     */
    self.versionAtLeast = function(major){
        var properties = [self.major, self.minor, self.revision];
        var len = Math.min(properties.length, arguments.length);
        for(i=0; i<len; i++){
            if(properties[i]>=arguments[i]){
                if(i+1<len && properties[i]==arguments[i]){
                    continue;
                }else{
                    return true;
                }
            }else{
                return false;
            }
        }
    };
    /**
     * Constructor, sets raw, major, minor, revisionStr, revision and installed public properties.
     */
    self.FlashDetect = function(){
        if(navigator.plugins && navigator.plugins.length>0){
            var type = 'application/x-shockwave-flash';
            var mimeTypes = navigator.mimeTypes;
            if(mimeTypes && mimeTypes[type] && mimeTypes[type].enabledPlugin && mimeTypes[type].enabledPlugin.description){
                var version = mimeTypes[type].enabledPlugin.description;
                var versionObj = parseStandardVersion(version);
                self.raw = versionObj.raw;
                self.major = versionObj.major;
                self.minor = versionObj.minor; 
                self.revisionStr = versionObj.revisionStr;
                self.revision = versionObj.revision;
                self.installed = true;
            }
        }else if(navigator.appVersion.indexOf("Mac")==-1 && window.execScript){
            var version = -1;
            for(var i=0; i<activeXDetectRules.length && version==-1; i++){
                var obj = getActiveXObject(activeXDetectRules[i].name);
                if(!obj.activeXError){
                    self.installed = true;
                    version = activeXDetectRules[i].version(obj);
                    if(version!=-1){
                        var versionObj = parseActiveXVersion(version);
                        self.raw = versionObj.raw;
                        self.major = versionObj.major;
                        self.minor = versionObj.minor; 
                        self.revision = versionObj.revision;
                        self.revisionStr = versionObj.revisionStr;
                    }
                }
            }
        }
    }();
};
FlashDetect.JS_RELEASE = "1.0.4";

