3653 lines
106 KiB
JavaScript
3653 lines
106 KiB
JavaScript
/* Copyright 2017 Jocly
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* As a special exception, the copyright holders give permission to link the
|
|
* code of portions of this program with the OpenSSL library under certain
|
|
* conditions as described in each individual source file and distribute
|
|
* linked combinations including the program with the OpenSSL library. You
|
|
* must comply with the GNU Affero General Public License in all respects
|
|
* for all of the code used other than as permitted herein. If you modify
|
|
* file(s) with this exception, you may extend this exception to your
|
|
* version of the file(s), but you are not obligated to do so. If you do not
|
|
* wish to do so, delete this exception statement from your version. If you
|
|
* delete this exception statement from all source files in the program,
|
|
* then also delete it in the license file.
|
|
*/
|
|
|
|
exports.view = View = {
|
|
Game: {},
|
|
Board: {},
|
|
};
|
|
|
|
if (window.JoclyXdViewCleanup)
|
|
window.JoclyXdViewCleanup();
|
|
|
|
(function () {
|
|
|
|
window.JoclyXdViewCleanup = function () {
|
|
var renderer = threeCtx && threeCtx.renderer;
|
|
if (renderer) {
|
|
renderer.forceContextLoss();
|
|
renderer.context = null;
|
|
renderer.domElement = null;
|
|
delete threeCtx.renderer;
|
|
}
|
|
if (arStream)
|
|
AR(null);
|
|
}
|
|
|
|
var area, currentSkin, logger, xdv, VSIZE, VHALF, htStateMachine, threeCtx = null,
|
|
SCALE3D = 0.001, resourcesMap = {}, resources, arStream = null;
|
|
|
|
// hack to ensure mouse and touch events do not collide
|
|
var lastTouchStart = 0, lastJoclyclick = 0;
|
|
|
|
/* ======================================== */
|
|
|
|
var LOADING_TEXT_RESOURCE = "";
|
|
|
|
if (typeof CustomEvent == "undefined") {
|
|
function CustomEvent(event, params) {
|
|
params = params || { bubbles: false, cancelable: false };
|
|
var evt = document.createEvent('Event');
|
|
evt.initEvent(event, params.bubbles, params.cancelable);
|
|
return evt;
|
|
};
|
|
CustomEvent.prototype = window.Event.prototype;
|
|
window.CustomEvent = CustomEvent;
|
|
}
|
|
|
|
var Class = function () {
|
|
};
|
|
(function () {
|
|
var initializing = false, fnTest = /xyz/.test(function () {
|
|
}) ? /\b_super\b/ : /.*/;
|
|
Class.extend = function (prop) {
|
|
var _super = this.prototype;
|
|
initializing = true;
|
|
var prototype = new this();
|
|
initializing = false;
|
|
for (var name in prop) {
|
|
prototype[name] = typeof prop[name] == "function"
|
|
&& typeof _super[name] == "function"
|
|
&& fnTest.test(prop[name]) ? (function (name, fn) {
|
|
return function () {
|
|
var tmp = this._super;
|
|
this._super = _super[name];
|
|
var ret = fn.apply(this, arguments);
|
|
this._super = tmp;
|
|
return ret;
|
|
};
|
|
})(name, prop[name]) : prop[name];
|
|
}
|
|
function Class(args) {
|
|
if (!initializing && this.init)
|
|
if (arguments.length > 0 && args.jBlocksArgsList)
|
|
this.init.apply(this, args);
|
|
else
|
|
this.init.apply(this, arguments);
|
|
}
|
|
Class.prototype = prototype;
|
|
Class.prototype.constructor = Class;
|
|
Class.extend = arguments.callee;
|
|
return Class;
|
|
};
|
|
})();
|
|
|
|
/* ======================================== */
|
|
|
|
var WebRTC;
|
|
var logResourcesLoad = false;
|
|
|
|
function Log() {
|
|
console.info.apply(console, arguments);
|
|
}
|
|
|
|
View.Board.Log = Log;
|
|
View.Game.Log = Log;
|
|
|
|
function HTStateMachine() { }
|
|
HTStateMachine.prototype = new JHStateMachine();
|
|
|
|
HTStateMachine.prototype.smError = function () {
|
|
//console.info("=>",arguments);
|
|
}
|
|
HTStateMachine.prototype.smWarning = function () {
|
|
//console.info("=>",arguments);
|
|
};
|
|
HTStateMachine.prototype.smDebug = function () {
|
|
//console.info("=>",arguments);
|
|
}
|
|
|
|
function Diff(oOld, oNew) {
|
|
var diff = {};
|
|
var diffSet = false;
|
|
for (var i in oNew) {
|
|
if (oNew.hasOwnProperty(i)) {
|
|
if (!oOld.hasOwnProperty(i)) {
|
|
diff[i] = oNew[i];
|
|
diffSet = true;
|
|
} else if (typeof oNew[i] == "object") {
|
|
var diff0 = Diff(oOld[i], oNew[i]);
|
|
if (diff0) {
|
|
diff[i] = diff0;
|
|
diffSet = true;
|
|
}
|
|
} else if (oNew[i] != oOld[i]) {
|
|
diff[i] = oNew[i];
|
|
diffSet = true;
|
|
}
|
|
}
|
|
}
|
|
return diffSet ? diff : null;
|
|
}
|
|
|
|
var resLoadingMask = null;
|
|
var resLoadingCount = 0;
|
|
function IncrementResLoading() {
|
|
if (resLoadingCount++ == 0) {
|
|
resLoadingMask = $(".jocly-res-loading-mask");
|
|
if (resLoadingMask.length == 0)
|
|
resLoadingMask = $("<div/>").addClass("jocly-res-loading-mask").css({
|
|
position: "absolute",
|
|
top: 0,
|
|
left: 0,
|
|
width: $("body").width(),
|
|
height: $("body").height(),
|
|
"background-color": "rgba(0,0,0,.8)",
|
|
"background-image": "url(" + LOADING_TEXT_RESOURCE + ")",
|
|
"background-position": "center center",
|
|
"background-repeat": "no-repeat",
|
|
"z-index": 100000,
|
|
}).appendTo($("body"));
|
|
else
|
|
resLoadingMask.show();
|
|
}
|
|
}
|
|
function DecrementResLoading() {
|
|
if (--resLoadingCount == 0) {
|
|
if (resLoadingMask)
|
|
resLoadingMask.hide();
|
|
}
|
|
}
|
|
|
|
var materialMaps = {};
|
|
function GetMaterialMap(map, callback) {
|
|
var $this = this;
|
|
if (materialMaps[map])
|
|
callback(materialMaps[map]);
|
|
else {
|
|
var loader = new THREE.TextureLoader();
|
|
loader.setCrossOrigin("anonymous");
|
|
if (logResourcesLoad)
|
|
console.log("Loading map", map);
|
|
IncrementResLoading();
|
|
loader.load(
|
|
// ressource url
|
|
map,
|
|
// Function when resource is loaded
|
|
function (texture) {
|
|
materialMaps[map] = texture;
|
|
if (logResourcesLoad)
|
|
console.log("Loaded", map);
|
|
DecrementResLoading();
|
|
threeCtx.animControl.trigger();
|
|
callback(materialMaps[map]);
|
|
},
|
|
// Function called when download progresses
|
|
function (xhr) {
|
|
//console.log( (xhr.loaded / xhr.total * 100) + '% loaded' );
|
|
},
|
|
// Function called when download errors
|
|
function (xhr) {
|
|
if (logResourcesLoad)
|
|
console.log("(not) Loaded", map);
|
|
DecrementResLoading();
|
|
threeCtx.animControl.trigger();
|
|
callback(null);
|
|
});
|
|
}
|
|
}
|
|
|
|
var pendingGetResource = [];
|
|
function GetResource(res, callback) {
|
|
var resource = resources[res];
|
|
if (resource === undefined) {
|
|
resource = resources[res] = {
|
|
pending: [callback],
|
|
status: "loading",
|
|
}
|
|
|
|
var getResFnt = null;
|
|
var m = /^(.*\|)(.*?)$/.exec(res);
|
|
if (m) {
|
|
var prefix = m[1];
|
|
var url = m[2];
|
|
for (var r in resourcesMap) {
|
|
var m2 = /^(.*\|)(.*?)$/.exec(r);
|
|
if (m2)
|
|
if (prefix == m[1] && url.substr(-m2[2].length) == m2[2]) {
|
|
getResFnt = resourcesMap[r];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (/^image\|/.test(res)) {
|
|
var imgSrc = /^image\|(.*)/.exec(res)[1];
|
|
if (logResourcesLoad)
|
|
console.log("Loading resource", res);
|
|
function HandleImage(image) {
|
|
resource.image = image;
|
|
if (logResourcesLoad)
|
|
console.log("Loaded", res);
|
|
resource.status = "loaded";
|
|
resource.imgSrc = imgSrc;
|
|
DecrementResLoading();
|
|
for (var i = 0; i < resource.pending.length; i++)
|
|
resource.pending[i](resource.image, imgSrc);
|
|
resource.pending = null;
|
|
if (threeCtx)
|
|
threeCtx.animControl.trigger();
|
|
}
|
|
IncrementResLoading();
|
|
if (getResFnt) {
|
|
getResFnt(function (data) {
|
|
var image = new Image();
|
|
image.onload = function () {
|
|
HandleImage(image);
|
|
}
|
|
image.src = data;
|
|
});
|
|
} else {
|
|
var image = new Image();
|
|
image.onload = function () {
|
|
HandleImage(image)
|
|
}
|
|
image.src = imgSrc;
|
|
}
|
|
} else if (/^smoothedfilegeo\|/.test(res)) {
|
|
if (logResourcesLoad)
|
|
console.log("Loading resource", res);
|
|
if (!threeCtx) {
|
|
delete resources[res];
|
|
pendingGetResource.push([res, callback]);
|
|
return;
|
|
}
|
|
var m = /^smoothedfilegeo\|([^\|]*)\|(.*)$/.exec(res);
|
|
var smooth = parseInt(m[1]);
|
|
var file = m[2];
|
|
IncrementResLoading();
|
|
function HandleGeoMat(geometry, materials) {
|
|
if (logResourcesLoad)
|
|
console.log("Loaded", res);
|
|
|
|
// not sure of the side effects here but this removes the console
|
|
// warnings "THREE.DirectGeometry.fromGeometry(): Undefined vertexUv"
|
|
for (var i = 0; i < geometry.faceVertexUvs.length; i++) {
|
|
for (var j = 0; j < geometry.faceVertexUvs[i].length; j++) {
|
|
var uv = geometry.faceVertexUvs[i][j];
|
|
if (uv === undefined)
|
|
geometry.faceVertexUvs[i][j] = [{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }];
|
|
}
|
|
for (; j < geometry.faces.length; j++)
|
|
geometry.faceVertexUvs[i].push([{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }]);
|
|
}
|
|
|
|
if (smooth > 0) {
|
|
var modifier = new THREE.SubdivisionModifier(smooth);
|
|
modifier.modify(geometry);
|
|
}
|
|
resource.status = "loaded";
|
|
DecrementResLoading();
|
|
resource.geometry = geometry;
|
|
resource.materials = materials;
|
|
for (var i = 0; i < resource.pending.length; i++)
|
|
resource.pending[i](geometry, materials);
|
|
resource.pending = null;
|
|
threeCtx.animControl.trigger(3000);
|
|
}
|
|
if (getResFnt) {
|
|
getResFnt(function (data) {
|
|
try {
|
|
var parsed = threeCtx.loader.parse(JSON.parse(data));
|
|
HandleGeoMat(parsed.geometry, parsed.materials);
|
|
} catch (e) {
|
|
debugger;
|
|
}
|
|
});
|
|
} else
|
|
threeCtx.loader.load(file, HandleGeoMat);
|
|
} else if (/^json\|/.test(res)) {
|
|
if (logResourcesLoad)
|
|
console.log("Loading resource", res);
|
|
IncrementResLoading();
|
|
var url = /^json\|(.*)/.exec(res)[1];
|
|
function JSONResult(event, data) {
|
|
if (logResourcesLoad)
|
|
console.log("Loaded", res);
|
|
var path = /^(\.)?(.*)$/.exec(url);
|
|
if (data.url.substr(-path[2].length) == path[2]) {
|
|
$(document).unbind("jocly.json-resource", JSONResult);
|
|
resource.status = "loaded";
|
|
DecrementResLoading();
|
|
resource.data = data.data;
|
|
for (var i = 0; i < resource.pending.length; i++)
|
|
resource.pending[i](resource.data);
|
|
resource.pending = null;
|
|
if (threeCtx)
|
|
threeCtx.animControl.trigger();
|
|
} else
|
|
console.warn("Expecting", url, "got", data.url)
|
|
|
|
}
|
|
$(document).bind("jocly.json-resource", JSONResult);
|
|
$("<script/>").attr("type", "text/javascript").attr("jocly-type", "json-resource").attr("src", url).appendTo($("head"));
|
|
} else if (/^json2\|/.test(res)) {
|
|
if (logResourcesLoad)
|
|
console.log("Loading resource", res);
|
|
IncrementResLoading();
|
|
var url = /^json2\|(.*)/.exec(res)[1];
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.onreadystatechange = function () {
|
|
if (xhr.readyState == XMLHttpRequest.DONE) {
|
|
if (logResourcesLoad)
|
|
console.log("Loaded", res);
|
|
var data = JSON.parse(xhr.responseText);
|
|
resource.status = "loaded";
|
|
DecrementResLoading();
|
|
resource.data = data;
|
|
for (var i = 0; i < resource.pending.length; i++)
|
|
resource.pending[i](resource.data);
|
|
resource.pending = null;
|
|
if (threeCtx)
|
|
threeCtx.animControl.trigger();
|
|
}
|
|
}
|
|
xhr.open('GET', url, true);
|
|
xhr.send(null);
|
|
|
|
} else if (/^font\|/.test(res)) {
|
|
if (logResourcesLoad)
|
|
console.info("font path", fontPath);
|
|
var fontPath = /^font\|(.*)/.exec(res)[1];
|
|
IncrementResLoading();
|
|
var fontLoader = new THREE.FontLoader();
|
|
fontLoader.load(fontPath, function (font) {
|
|
DecrementResLoading();
|
|
resource.status = "loaded";
|
|
resource.font = font;
|
|
for (var i = 0; i < resource.pending.length; i++)
|
|
resource.pending[i](font);
|
|
resource.pending = null;
|
|
if (threeCtx)
|
|
threeCtx.animControl.trigger();
|
|
});
|
|
}
|
|
} else if (resource.status == "loading") {
|
|
resource.pending.push(callback);
|
|
} else {
|
|
if (/^image\|/.test(res)) {
|
|
callback(resource.image, resource.imgSrc);
|
|
} else if (/^smoothedfilegeo\|/.test(res)) {
|
|
callback(resource.geometry, resource.materials);
|
|
} else if (/^json\|/.test(res)) {
|
|
callback(resource.data);
|
|
} else if (/^json2\|/.test(res)) {
|
|
callback(resource.data);
|
|
} else if (/^font\|/.test(res)) {
|
|
callback(resource.font);
|
|
}
|
|
}
|
|
}
|
|
|
|
function ResumePendingResources() {
|
|
if (threeCtx)
|
|
while (pendingGetResource.length) {
|
|
var call = pendingGetResource.shift();
|
|
GetResource.call(null, call[0], call[1]);
|
|
}
|
|
}
|
|
|
|
|
|
/* ======================================== */
|
|
|
|
var XDView = Class.extend({
|
|
init: function () {
|
|
this.gadgets = {};
|
|
this.resources = {};
|
|
this.game = null;
|
|
this.initDone = false;
|
|
this.ratio = 0;
|
|
this.center = null;
|
|
this.getMaterialMap = GetMaterialMap;
|
|
},
|
|
createGadget: function (id, options) {
|
|
if (this.ratio > 0) {
|
|
if (options.base === undefined)
|
|
options.base = {};
|
|
options.base.ratio = this.ratio;
|
|
options.base.center = this.center;
|
|
}
|
|
this.gadgets[id] = new Gadget(id, options);
|
|
},
|
|
updateGadget: function (id, options, delay, callback) {
|
|
var gadget = this.gadgets[id];
|
|
if (gadget) {
|
|
if (arguments.length < 3 || delay === undefined)
|
|
delay = 0;
|
|
if (arguments.length < 4 || callback === undefined)
|
|
callback = function () { }
|
|
gadget.update(options, delay, callback);
|
|
}
|
|
},
|
|
removeGadget: function (id) {
|
|
var gadget = this.gadgets[id];
|
|
if (gadget) {
|
|
gadget.unbuild();
|
|
delete this.gadgets[id];
|
|
}
|
|
},
|
|
showGadget: function (id) {
|
|
this.updateGadget(id, {
|
|
base: {
|
|
visible: true,
|
|
},
|
|
});
|
|
},
|
|
hideGadget: function (id) {
|
|
this.updateGadget(id, {
|
|
base: {
|
|
visible: false,
|
|
},
|
|
});
|
|
},
|
|
updateArea: function (ratio, center) {
|
|
this.ratio = ratio;
|
|
this.center = center;
|
|
for (var gi in this.gadgets)
|
|
this.gadgets[gi].update({
|
|
base: {
|
|
ratio: ratio,
|
|
center: center,
|
|
},
|
|
});
|
|
},
|
|
redisplayGadgets: function () {
|
|
for (var gi in this.gadgets) {
|
|
var gadget = this.gadgets[gi];
|
|
gadget.update({});
|
|
}
|
|
},
|
|
unbuildGadgets: function () {
|
|
for (var gi in this.gadgets)
|
|
this.gadgets[gi].unbuild();
|
|
},
|
|
saveGadgetProps: function (id, props, saveName) {
|
|
var gadget = this.gadgets[id];
|
|
if (gadget)
|
|
gadget.saveProps(props, saveName);
|
|
},
|
|
restoreGadgetProps: function (id, saveName, delay, callback) {
|
|
var gadget = this.gadgets[id];
|
|
if (gadget)
|
|
gadget.restoreProps(saveName, delay, callback);
|
|
else if (callback)
|
|
callback();
|
|
},
|
|
listScene: function () {
|
|
console.log("listScene:");
|
|
var accu = [];
|
|
accu["faces"] = 0;
|
|
//var crlf="<br>";
|
|
var crlf = " :: ";
|
|
var output = "========= Scene summary ===========";
|
|
var nbLights = -1;
|
|
|
|
|
|
function getFaces(obj, nbFaces) {
|
|
if (obj.geometry) {
|
|
var gg = obj.geometry;
|
|
if (gg.faces) nbFaces += gg.faces.length;
|
|
}
|
|
if (obj.getDescendants) {
|
|
var children = obj.getDescendants();
|
|
if (children) nbFaces += getFaces(children, nbFaces);
|
|
}
|
|
return nbFaces;
|
|
}
|
|
if (threeCtx) if (threeCtx.scene) {
|
|
var threeScene = threeCtx.scene;
|
|
nbLights = threeScene.__lights.length;
|
|
console.log(threeScene);
|
|
var obj = threeScene.getDescendants();
|
|
for (var o in obj) {
|
|
var nbf = getFaces(obj[o], 0);
|
|
accu["faces"] += nbf;
|
|
//console.log(obj[o].name+" has "+nbf+" faces");
|
|
/*if(obj[o].geometry){
|
|
var gg=obj[o].geometry;
|
|
if (gg.faces) accu["faces"]+=gg.faces.length;
|
|
console.log(obj[o].name+" has "+gg.faces.length+" faces");
|
|
}*/
|
|
}
|
|
}
|
|
output += crlf + "nb lights: " + nbLights;
|
|
output += crlf + "Nb faces: " + accu["faces"];
|
|
console.log(output);
|
|
},
|
|
})
|
|
|
|
/* ======================================== */
|
|
|
|
function InitGlobals() {
|
|
xdv = new XDView();
|
|
VSIZE = 12600;
|
|
VHALF = VSIZE / 2;
|
|
htStateMachine = null;
|
|
threeCtx = null;
|
|
SCALE3D = 0.001;
|
|
resourcesMap = {};
|
|
resources = {};
|
|
area = null;
|
|
currentSkin = null;
|
|
logger = null;
|
|
}
|
|
InitGlobals();
|
|
|
|
/* ======================================== */
|
|
|
|
var Gadget = Class.extend({
|
|
init: function (id, options) {
|
|
this.id = id;
|
|
this.options = $.extend(true, {
|
|
base: {
|
|
visible: false,
|
|
}
|
|
}, options);
|
|
this.avatar = null;
|
|
this.savedProps = {};
|
|
},
|
|
mergeOptions: function () {
|
|
return $.extend(true,
|
|
{
|
|
x: 0,
|
|
y: 0,
|
|
z: 0,
|
|
},
|
|
this.options.base,
|
|
currentSkin["3d"] ? this.options["3d"] : this.options["2d"],
|
|
this.options[currentSkin.name]);
|
|
},
|
|
build: function (options) {
|
|
if (this.avatar)
|
|
return;
|
|
if (arguments.length == 0)
|
|
options = this.mergeOptions();
|
|
},
|
|
unbuild: function () {
|
|
if (this.avatar) {
|
|
this.avatar.remove();
|
|
this.avatar = null;
|
|
}
|
|
},
|
|
canDisplay: function (options) {
|
|
if (currentSkin === undefined || currentSkin === null)
|
|
return false;
|
|
if (arguments.length == 0)
|
|
options = this.mergeOptions();
|
|
return options.visible &&
|
|
((!currentSkin["3d"] && options.ratio !== undefined && options.center !== undefined) ||
|
|
(currentSkin["3d"] /* && 3D requirements */));
|
|
},
|
|
update: function (options, delay, callback) {
|
|
if (arguments.length < 2 || delay === undefined)
|
|
delay = 0;
|
|
if (arguments.length < 3 || callback === undefined)
|
|
callback = function () { };
|
|
if (currentSkin !== undefined && currentSkin !== null) {
|
|
var xdMap = currentSkin["3d"] ? "3d" : "2d";
|
|
if (options.base)
|
|
for (var i in options.base) {
|
|
if (this.options[xdMap])
|
|
delete this.options[xdMap][i];
|
|
if (this.options[currentSkin.name])
|
|
delete this.options[currentSkin.name][i];
|
|
}
|
|
if (options[xdMap])
|
|
for (var i in options[xdMap])
|
|
if (this.options[currentSkin.name])
|
|
delete this.options[currentSkin.name][i];
|
|
$.extend(true, this.options, options);
|
|
var aOptions = this.mergeOptions();
|
|
if (!this.avatar && this.canDisplay(aOptions)) {
|
|
var avatarType = avatarTypes[aOptions.type];
|
|
if (avatarType !== undefined)
|
|
this.avatar = new avatarType(this, aOptions);
|
|
}
|
|
if (typeof delay == "object") {
|
|
if (delay[currentSkin.name] !== undefined)
|
|
delay = delay[currentSkin.name];
|
|
else if (delay[xdMap] !== undefined)
|
|
delay = delay[xdMap];
|
|
else if (delay.base !== undefined)
|
|
delay = delay.base;
|
|
else
|
|
delay = 0;
|
|
}
|
|
if (this.avatar)
|
|
this.avatar.update(aOptions, delay, callback);
|
|
} else
|
|
$.extend(true, this.options, options);
|
|
},
|
|
saveProps: function (props, saveName) {
|
|
var save = {};
|
|
for (var oi in this.options) {
|
|
var optCat = this.options[oi];
|
|
for (var i in props) {
|
|
var prop = props[i];
|
|
if (optCat[prop] !== undefined) {
|
|
save[oi] = save[oi] || {};
|
|
save[oi][prop] = optCat[prop];
|
|
}
|
|
}
|
|
}
|
|
this.savedProps[saveName] = save;
|
|
},
|
|
restoreProps: function (saveName, delay, callback) {
|
|
if (this.savedProps[saveName] !== undefined)
|
|
this.update(this.savedProps[saveName], delay, callback);
|
|
else if (callback)
|
|
callback();
|
|
},
|
|
});
|
|
|
|
/* ======================================== */
|
|
|
|
var updateOp = 1;
|
|
|
|
var GadgetAvatar = Class.extend({
|
|
init: function (gadget, options) {
|
|
this.gadget = gadget;
|
|
this.options = options;
|
|
this.SCALE3D = SCALE3D;
|
|
this.animCounts = {};
|
|
},
|
|
remove: function () {
|
|
},
|
|
display: function (options) {
|
|
},
|
|
update: function (options, delay, callback) {
|
|
var aOptions = $.extend(true, {}, this.options, options);
|
|
aOptions.updateOp = updateOp++;
|
|
aOptions.updateCallback = callback;
|
|
this.display(aOptions, delay, callback);
|
|
if (aOptions.visible)
|
|
this.show();
|
|
else
|
|
this.hide();
|
|
this.options = aOptions;
|
|
},
|
|
show: function () {
|
|
},
|
|
hide: function () {
|
|
},
|
|
animStart: function (options) {
|
|
if (options === undefined) {
|
|
console.error("animStart without options");
|
|
debugger;
|
|
return;
|
|
}
|
|
if (options.updateOp === undefined) {
|
|
console.error("animStart without options");
|
|
debugger;
|
|
return;
|
|
}
|
|
if (this.object3d)
|
|
this.object3d.matrixAutoUpdate = true;
|
|
if (this.animCounts[options.updateOp] === undefined)
|
|
this.animCounts[options.updateOp] = 1;
|
|
else
|
|
this.animCounts[options.updateOp]++;
|
|
},
|
|
animEnd: function (options) {
|
|
if (options === undefined) {
|
|
console.error("animEnd without options");
|
|
debugger;
|
|
return;
|
|
}
|
|
if (options.updateOp === undefined) {
|
|
console.error("animEnd without options");
|
|
debugger;
|
|
return;
|
|
}
|
|
if (this.animCounts[options.updateOp] === undefined) {
|
|
console.error("animEnd without animCount");
|
|
debugger;
|
|
return;
|
|
}
|
|
if (--this.animCounts[options.updateOp] == 0) {
|
|
if (this.object3d)
|
|
this.object3d.matrixAutoUpdate = false;
|
|
options.updateCallback();
|
|
delete this.animCounts[options.updateOp];
|
|
}
|
|
},
|
|
getResource: GetResource,
|
|
});
|
|
|
|
var GadgetElement = GadgetAvatar.extend({
|
|
init: function (gadget, options) {
|
|
options = $.extend(true, {
|
|
display: function () { },
|
|
}, options);
|
|
this._super.apply(this, arguments);
|
|
this.options = $.extend(true, {
|
|
x: 0,
|
|
y: 0,
|
|
z: 0,
|
|
width: 1000,
|
|
height: 1000,
|
|
tag: "div",
|
|
opacity: 1,
|
|
rotate: 0,
|
|
css: {},
|
|
}, options);
|
|
this.element = $("<" + this.options.tag + "/>").css({
|
|
"position": "absolute",
|
|
"z-index": this.options.z,
|
|
}).hide().addClass("jocly-gadget").appendTo(area);
|
|
if (this.options.initialClasses)
|
|
this.element.addClass(this.options.initialClasses);
|
|
},
|
|
display: function (options, delay) {
|
|
var $this = this;
|
|
if (this.element) {
|
|
this.displayElement.call(this, !this.displayCalled, options, delay);
|
|
this.displayCalled = true;
|
|
} else if (delay) {
|
|
this.animStart(options);
|
|
setTimeout(function () { $this.animEnd(options); }, delay);
|
|
}
|
|
},
|
|
displayElement: function (force, options, delay) {
|
|
var $this = this;
|
|
this.element.css($.extend(true, this.options.css, options.css));
|
|
if (
|
|
force ||
|
|
this.aWidth === undefined || this.aHeight === undefined ||
|
|
options.ratio != this.options.ratio ||
|
|
options.center.x != this.options.center.x ||
|
|
options.center.y != this.options.center.y ||
|
|
options.width != this.options.width ||
|
|
options.height != this.options.height ||
|
|
options.x != this.options.x ||
|
|
options.y != this.options.y ||
|
|
options.z != this.options.z
|
|
) {
|
|
this.aWidth = options.width * options.ratio;
|
|
this.aHeight = options.height * options.ratio;
|
|
var left = options.x * options.ratio + options.center.x - this.aWidth / 2;
|
|
var top = options.y * options.ratio + options.center.y - this.aHeight / 2;
|
|
if (delay) {
|
|
this.animStart(options);
|
|
this.element.css({
|
|
"z-index": options.z,
|
|
}).animate({
|
|
width: this.aWidth,
|
|
height: this.aHeight,
|
|
left: left,
|
|
top: top,
|
|
}, delay, function () { $this.animEnd(options); });
|
|
} else {
|
|
this.element.css({
|
|
width: this.aWidth,
|
|
height: this.aHeight,
|
|
left: left,
|
|
top: top,
|
|
"z-index": options.z,
|
|
});
|
|
}
|
|
this.options.display(this.element, this.aWidth, this.aHeight);
|
|
}
|
|
if (force ||
|
|
options.classes != this.options.classes) {
|
|
if (this.options.classes)
|
|
this.element.removeClass(this.options.classes);
|
|
this.element.addClass(options.classes);
|
|
}
|
|
if (force ||
|
|
options.click != this.options.click) {
|
|
//this.element.unbind(JocGame.CLICK);
|
|
this.element.unbind(JocGame.MOUSEMOVE_EVENT);
|
|
this.element.unbind(JocGame.MOUSEDOWN_EVENT);
|
|
this.element.unbind(JocGame.MOUSEUP_EVENT);
|
|
if (options.click) {
|
|
var iOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false);
|
|
(function () {
|
|
var mouseDown = false;
|
|
var notified = false;
|
|
var downPosition = [0, 0];
|
|
$this.element.bind(JocGame.MOUSEDOWN_EVENT, function (event) {
|
|
event.preventDefault();
|
|
if (iOS && event.type == "mousedown")
|
|
return;
|
|
if (event.type == "touchstart")
|
|
lastTouchStart = Date.now();
|
|
if (event.type == "mousedown" && Date.now() - lastTouchStart < 500)
|
|
return;
|
|
mouseDown = true;
|
|
downPosition = GetEventPosition(event);
|
|
});
|
|
$this.element.bind(JocGame.MOUSEUP_EVENT, function (event) {
|
|
event.preventDefault();
|
|
if (iOS && event.type == "mouseup")
|
|
return;
|
|
mouseDown = false;
|
|
if (event.type == "joclyclick")
|
|
lastJoclyclick = Date.now();
|
|
if (event.type == "mouseup" && Date.now() - lastJoclyclick < 500)
|
|
return;
|
|
if (event.type == 'mouseup' || event.type == 'joclyclick') {
|
|
options.click.call($this);
|
|
} else {
|
|
event.stopPropagation();
|
|
var newevent = new CustomEvent("joclyclick", {});
|
|
var x, y;
|
|
if (event.originalEvent.changedTouches && event.originalEvent.changedTouches.length > 0) {
|
|
x = event.originalEvent.changedTouches[0].pageX;
|
|
y = event.originalEvent.changedTouches[0].pageY;
|
|
} else if (event.originalEvent.touches && event.originalEvent.touches.length > 0) {
|
|
x = event.originalEvent.touches[0].pageX;
|
|
y = event.originalEvent.touches[0].pageY;
|
|
} else {
|
|
console.warn("Invalid touch event");
|
|
return;
|
|
}
|
|
var target = document.elementFromPoint(x, y);
|
|
target.dispatchEvent(newevent);
|
|
}
|
|
});
|
|
$this.element.bind(JocGame.MOUSEMOVE_EVENT, function (event) {
|
|
event.preventDefault();
|
|
if (iOS && event.type == "mousemove")
|
|
return;
|
|
if (mouseDown && !notified) {
|
|
var position = GetEventPosition(event);
|
|
var dx = position[0] - downPosition[0];
|
|
var dy = position[1] - downPosition[1];
|
|
if (dx * dx + dy * dy > 100) {
|
|
notified = true;
|
|
options.click.call($this);
|
|
}
|
|
}
|
|
});
|
|
})();
|
|
}
|
|
}
|
|
/*
|
|
if(force ||
|
|
options.holdClick != this.options.holdClick) {
|
|
this.element.unbind("holdclick");
|
|
if(options.holdClick)
|
|
this.element.bind("holdclick",options.holdClick);
|
|
}
|
|
*/
|
|
if (force ||
|
|
options.rotate != this.options.rotate) {
|
|
while (options.rotate < 0)
|
|
options.rotate += 360;
|
|
options.rotate %= 360;
|
|
var rotate = options.rotate;
|
|
var rotate0 = this.options.rotate;
|
|
if (rotate - this.options.rotate > 180)
|
|
rotate0 = 360;
|
|
else if (this.options.rotate - rotate > 180)
|
|
rotate += 360;
|
|
if (delay) {
|
|
this.animStart(options);
|
|
$({ deg: rotate0 }).animate({ deg: rotate }, {
|
|
step: function (now) {
|
|
$this.element.css('transform', 'rotate(' + now + 'deg)');
|
|
},
|
|
duration: delay,
|
|
complete: function () {
|
|
$this.animEnd(options);
|
|
},
|
|
});
|
|
} else
|
|
this.element.css({
|
|
"transform": "rotate(" + options.rotate + "deg)",
|
|
});
|
|
} else if (delay) {
|
|
this.animStart(options);
|
|
setTimeout(function () {
|
|
$this.animEnd(options);
|
|
}, 0);
|
|
}
|
|
if (force ||
|
|
options.opacity != this.options.opacity) {
|
|
if (delay) {
|
|
this.animStart(options);
|
|
this.element.stop().animate({
|
|
"opacity": options.opacity,
|
|
}, delay, function () { $this.animEnd(options); });
|
|
} else
|
|
this.element.css({
|
|
"opacity": options.opacity,
|
|
});
|
|
}
|
|
},
|
|
show: function () {
|
|
this.element.show();
|
|
},
|
|
hide: function () {
|
|
this.element.hide();
|
|
},
|
|
remove: function () {
|
|
this._super.apply(this, arguments);
|
|
this.element.unbind(JocGame.CLICK);
|
|
//this.element.unbind("holdclick");
|
|
this.element.remove();
|
|
},
|
|
});
|
|
|
|
var GadgetImage = GadgetElement.extend({
|
|
displayElement: function (force, options) {
|
|
var $this = this;
|
|
this._super.apply(this, arguments);
|
|
if (force || this.options.file != options.file) {
|
|
this.options.file = options.file;
|
|
GetResource("image|" + options.file, function (image, imgSrc) {
|
|
if (imgSrc == $this.options.file)
|
|
$this.element.css({
|
|
"background-image": "url(" + image.src + ")",
|
|
"background-size": "100% 100%",
|
|
"background-repeat": "no-repeat",
|
|
});
|
|
else
|
|
console.log("file has changed to", $this.options.file, "(", imgSrc, ")");
|
|
});
|
|
}
|
|
},
|
|
});
|
|
|
|
var GadgetCanvas = GadgetElement.extend({
|
|
init: function (gadget, options) {
|
|
options = $.extend({
|
|
tag: "canvas",
|
|
draw: function () { },
|
|
}, options);
|
|
this._super.call(this, gadget, options);
|
|
this.canvasContext = this.element[0].getContext("2d");
|
|
},
|
|
displayElement: function (force, options) {
|
|
this._super.apply(this, arguments);
|
|
this.element.attr("width", this.aWidth).attr("height", this.aHeight);
|
|
//this.canvasContext.save();
|
|
this.canvasContext.clearRect(0, 0, options.width, options.height);
|
|
this.canvasContext.translate(this.aWidth / 2, this.aHeight / 2);
|
|
this.canvasContext.scale(options.ratio, options.ratio);
|
|
this.options.draw.call(this, this.canvasContext, 1 / options.ratio);
|
|
//this.canvasContext.restore();
|
|
}
|
|
});
|
|
|
|
var GadgetHexagon = GadgetCanvas.extend({
|
|
init: function (gadget, options) {
|
|
var $this = this;
|
|
var R = options.radius;
|
|
var L = R * Math.sqrt(3) / 2;
|
|
options = $.extend({
|
|
lineWidthFactor: 1,
|
|
}, options, {
|
|
draw: function (ctx, pixSize) {
|
|
ctx.lineWidth = pixSize * $this.options.lineWidthFactor;
|
|
ctx.beginPath();
|
|
ctx.moveTo(-L, L / 2);
|
|
ctx.lineTo(0, R);
|
|
ctx.lineTo(L, L / 2);
|
|
ctx.lineTo(L, -L / 2);
|
|
ctx.lineTo(0, -R);
|
|
ctx.lineTo(-L, -L / 2);
|
|
ctx.closePath();
|
|
if ($this.options.strokeStyle) {
|
|
ctx.strokeStyle = $this.options.strokeStyle;
|
|
ctx.stroke();
|
|
}
|
|
if ($this.options.fillStyle) {
|
|
ctx.fillStyle = $this.options.fillStyle;
|
|
ctx.fill();
|
|
}
|
|
},
|
|
});
|
|
this._super.call(this, gadget, options);
|
|
this.element.attr("width", options.width).attr("height", options.height);
|
|
this.canvasContext = this.element[0].getContext("2d");
|
|
},
|
|
});
|
|
|
|
/*
|
|
var GadgetSprite=GadgetCanvas.extend({
|
|
init: function(gadget,options) {
|
|
this._super.apply(this,arguments);
|
|
this.displayArgs=null;
|
|
},
|
|
displayElement: function(force,options) {
|
|
var $this=this;
|
|
this._super.apply(this,arguments);
|
|
if(force || this.options.file!=options.file) {
|
|
GetResource("image|"+options.file, function(image) {
|
|
$this.image=image;
|
|
if($this.displayArgs && $this.options.clipx!==undefined && $this.options.clipy!==undefined &&
|
|
$this.options.clipwidth!==undefined && $this.options.clipheight!==undefined)
|
|
$this.drawImage.apply($this,$this.displayArgs);
|
|
});
|
|
}
|
|
if(force || this.options.clipx!=options.clipx
|
|
|| this.options.clipy!=options.clipy
|
|
|| this.options.clipwidth!=options.clipwidth
|
|
|| this.options.clipheight!=options.clipheight
|
|
) {
|
|
if(this.image && options.clipx!==undefined && options.clipy!==undefined &&
|
|
options.clipwidth!==undefined && options.clipheight!==undefined) {
|
|
this.drawImage.call(this,force,options);
|
|
} else
|
|
this.displayArgs=arguments;
|
|
}
|
|
if(this.image && options.clipx!==undefined && options.clipy!==undefined &&
|
|
options.clipwidth!==undefined && options.clipheight!==undefined)
|
|
this.drawImage.apply(this,arguments);
|
|
else
|
|
this.displayArgs=arguments;
|
|
},
|
|
drawImage: function(force,options) {
|
|
this.canvasContext.save();
|
|
var x0=parseInt(options.clipx+.5);
|
|
var y0=parseInt(options.clipy+.5);
|
|
var cx0=parseInt(options.clipwidth+.5);
|
|
var cy0=parseInt(options.clipheight+.5);
|
|
var x1=0;
|
|
var y1=0;
|
|
var cx1=parseInt(this.aWidth+.5);
|
|
var cy1=parseInt(this.aHeight+.5);
|
|
|
|
this.canvasContext.scale(cx1,cy1);
|
|
this.canvasContext.imageSmoothingEnabled=true;
|
|
|
|
this.canvasContext.drawImage(this.image,x0,y0,cx0,cy0,x1,y1,1,1);
|
|
//this.canvasContext.drawImage(this.image,x0,y0,cx0,cy0,x1,y1,cx1,cy1);
|
|
this.canvasContext.restore();
|
|
this.displayArgs=null;
|
|
},
|
|
});
|
|
*/
|
|
|
|
var GadgetSprite = GadgetCanvas.extend({
|
|
init: function (gadget, options) {
|
|
this._super.apply(this, arguments);
|
|
this.displayArgs = null;
|
|
},
|
|
displayElement: function (force, options) {
|
|
var $this = this;
|
|
this._super.apply(this, arguments);
|
|
if (force || this.options.file != options.file) {
|
|
GetResource("image|" + options.file, function (image, imgSrc) {
|
|
if (imgSrc == $this.options.file) {
|
|
$this.image = image;
|
|
$this.element.css({
|
|
"background-image": "url(" + image.src + ")",
|
|
"background-size": "100% 100%",
|
|
"background-repeat": "no-repeat",
|
|
});
|
|
}
|
|
if ($this.displayArgs && $this.options.clipx !== undefined && $this.options.clipy !== undefined &&
|
|
$this.options.clipwidth !== undefined && $this.options.clipheight !== undefined)
|
|
$this.drawImage.apply($this, $this.displayArgs);
|
|
});
|
|
}
|
|
if (force || this.options.clipx != options.clipx
|
|
|| this.options.clipy != options.clipy
|
|
|| this.options.clipwidth != options.clipwidth
|
|
|| this.options.clipheight != options.clipheight
|
|
) {
|
|
if (this.image && options.clipx !== undefined && options.clipy !== undefined &&
|
|
options.clipwidth !== undefined && options.clipheight !== undefined) {
|
|
this.drawImage.call(this, force, options);
|
|
} else
|
|
this.displayArgs = arguments;
|
|
}
|
|
if (this.image && options.clipx !== undefined && options.clipy !== undefined &&
|
|
options.clipwidth !== undefined && options.clipheight !== undefined)
|
|
this.drawImage.apply(this, arguments);
|
|
else
|
|
this.displayArgs = arguments;
|
|
},
|
|
drawImage: function (force, options) {
|
|
var rx = (options.clipwidth / this.aWidth);
|
|
var ry = (options.clipheight / this.aHeight);
|
|
var bcx = parseInt(this.image.width / rx + .5);
|
|
var bcy = parseInt(this.image.height / ry + .5);
|
|
var bs = "" + bcx + "px " + bcy + "px";
|
|
this.element.css({
|
|
"width": parseInt(this.aWidth + .5),
|
|
"height": parseInt(this.aHeight + .5),
|
|
"background-image": options.file,
|
|
"background-size": bs,
|
|
"background-position": "-" + (parseInt(options.clipx / rx + .5)) + "px -" + (parseInt(options.clipy / ry + .5)) + "px",
|
|
});
|
|
},
|
|
});
|
|
|
|
var GadgetDisk = GadgetElement.extend({
|
|
init: function (gadget, options) {
|
|
this._super.apply(this, arguments);
|
|
},
|
|
displayElement: function (force, options) {
|
|
this._super.apply(this, arguments);
|
|
this.element.css({
|
|
"border-radius": "50%",
|
|
});
|
|
},
|
|
});
|
|
|
|
var GadgetObject3D = GadgetAvatar.extend({
|
|
init: function (gadget, options) {
|
|
var $this = this;
|
|
this._super.apply(this, arguments);
|
|
this.displayCalled = false;
|
|
this.options = $.extend(true, {
|
|
x: 0.0,
|
|
y: 0.0,
|
|
z: 0.0,
|
|
color: null,
|
|
castShadow: true,
|
|
receiveShadow: false,
|
|
harbor: true,
|
|
}, options);
|
|
this.createObject();
|
|
},
|
|
createObject: function () {
|
|
},
|
|
objectReady: function (object3d) {
|
|
var $this = this;
|
|
this.object3d = object3d;
|
|
object3d.castShadow = this.options.castShadow;
|
|
object3d.receiveShadow = this.options.receiveShadow;
|
|
object3d.name = this.gadget.id;
|
|
object3d.matrixAutoUpdate = false;
|
|
this.shouldUpdate = true;
|
|
this.update(this.options);
|
|
//object3d.visible=this.options.visible;
|
|
if (this.options.harbor)
|
|
threeCtx.harbor.add(object3d);
|
|
else
|
|
threeCtx.scene.add(object3d);
|
|
},
|
|
display: function (options, delay) {
|
|
var $this = this;
|
|
if (this.object3d) {
|
|
this.shouldUpdate = false;
|
|
this.displayObject3D.call(this, !this.displayCalled, options, delay);
|
|
this.displayCalled = true;
|
|
if (this.shouldUpdate)
|
|
this.object3d.updateMatrix();
|
|
}
|
|
if (delay) {
|
|
$this.animStart(options);
|
|
setTimeout(function () { $this.animEnd(options); }, delay);
|
|
}
|
|
},
|
|
displayObject3D: function (force, options, delay) {
|
|
var $this = this;
|
|
threeCtx.animControl.trigger((isNaN(delay) ? 0 : delay) + 200);
|
|
if (force ||
|
|
options.x != this.options.x ||
|
|
options.y != this.options.y ||
|
|
options.z != this.options.z
|
|
) {
|
|
this.shouldUpdate = true;
|
|
if (delay) {
|
|
this.animStart(options);
|
|
new TWEEN.Tween(this.object3d.position).to({
|
|
x: options.x * SCALE3D,
|
|
y: options.z * SCALE3D,
|
|
z: options.y * SCALE3D,
|
|
}, delay).easing(options.positionEasing ? options.positionEasing : TWEEN.Easing.Cubic.EaseInOut).onComplete(function () {
|
|
$this.animEnd(options);
|
|
}).onUpdate(function (ratio) {
|
|
if (options.positionEasingUpdate)
|
|
options.positionEasingUpdate.call($this, ratio);
|
|
}).start();
|
|
} else {
|
|
this.object3d.position.x = options.x * SCALE3D;
|
|
this.object3d.position.y = options.z * SCALE3D;
|
|
this.object3d.position.z = options.y * SCALE3D;
|
|
}
|
|
}
|
|
if (force ||
|
|
options.click != this.options.click) {
|
|
if (this.options.click)
|
|
this.object3d.off("mouseup");
|
|
if (options.click) {
|
|
//if(!threeCtx.cameraControls.hasBeenDragged())
|
|
this.object3d.on("mouseup", function () {
|
|
//if(!threeCtx.cameraControls.hasBeenDragged())
|
|
options.click.call();
|
|
});
|
|
}
|
|
}
|
|
/*
|
|
if(force ||
|
|
options.holdClick != this.options.holdClick) {
|
|
if(this.options.holdClick)
|
|
this.object3d.off("holdclick");
|
|
if(options.holdClick) {
|
|
//if(!threeCtx.cameraControls.hasBeenDragged())
|
|
this.object3d.on("holdclick",function(eventData){
|
|
if(!threeCtx.cameraControls.hasBeenDragged())
|
|
options.holdClick.call($this,eventData);
|
|
}
|
|
);
|
|
}
|
|
}
|
|
*/
|
|
if (force ||
|
|
options.castShadow != this.options.castShadow) {
|
|
this.object3d.castShadow = options.castShadow
|
|
}
|
|
if (force ||
|
|
options.receiveShadow != this.options.receiveShadow) {
|
|
this.object3d.receiveShadow = options.receiveShadow
|
|
}
|
|
},
|
|
show: function () {
|
|
if (arStream && !this.options.harbor)
|
|
return this.hide();
|
|
if (this.object3d) {
|
|
this.object3d.visible = true;
|
|
if (this.object3d.children) {
|
|
for (var c = 0; c < this.object3d.children.length; c++) {
|
|
var part = this.object3d.children[c];
|
|
if (part.joclyVisible === undefined || part.joclyVisible)
|
|
part.visible = true;
|
|
else
|
|
part.visible = false;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
hide: function () {
|
|
if (this.object3d) {
|
|
this.object3d.visible = false;
|
|
if (this.object3d.children) {
|
|
for (var c = 0; c < this.object3d.children.length; c++)
|
|
this.object3d.children[c].visible = false;
|
|
}
|
|
}
|
|
},
|
|
remove: function () {
|
|
this._super.apply(this, arguments);
|
|
if (this.object3d) {
|
|
if (this.options.click)
|
|
this.object3d.off("mouseup");
|
|
/*
|
|
if(this.options.holdClick)
|
|
this.object3d.off("holdclick");
|
|
*/
|
|
if (this.object3d.parent)
|
|
this.object3d.parent.remove(this.object3d);
|
|
this.object3d = null;
|
|
}
|
|
},
|
|
getMaterialMap: GetMaterialMap,
|
|
});
|
|
|
|
var GadgetMesh = GadgetObject3D.extend({
|
|
init: function (gadget, options) {
|
|
options = $.extend(true, {
|
|
rotate: 0,
|
|
rotateX: 0,
|
|
rotateY: 0,
|
|
scale: [1, 1, 1],
|
|
materials: {},
|
|
smooth: 0,
|
|
opacity: 1,
|
|
flatShading: false,
|
|
morphing: [],
|
|
}, options, {
|
|
});
|
|
this._super.call(this, gadget, options);
|
|
},
|
|
displayObject3D: function (force, options, delay) {
|
|
var $this = this;
|
|
this._super.apply(this, arguments);
|
|
if (force ||
|
|
options.rotate != this.options.rotate ||
|
|
options.rotateX != this.options.rotateX ||
|
|
options.rotateY != this.options.rotateY
|
|
) {
|
|
this.shouldUpdate = true;
|
|
var delta = options.rotate - this.options.rotate;
|
|
if (delta > 180)
|
|
options.rotate -= 360;
|
|
else if (delta < -180)
|
|
options.rotate += 360;
|
|
delta = options.rotateX - this.options.rotateX;
|
|
if (delta > 180)
|
|
options.rotateX -= 360;
|
|
else if (delta < -180)
|
|
options.rotateX += 360;
|
|
delta = options.rotateY - this.options.rotateY;
|
|
if (delta > 180)
|
|
options.rotateY -= 360;
|
|
else if (delta < -180)
|
|
options.rotateY += 360;
|
|
if (delay) {
|
|
this.animStart(options);
|
|
new TWEEN.Tween(this.object3d.rotation).to({
|
|
x: options.rotateX * (Math.PI / 180),
|
|
y: options.rotate * (Math.PI / 180),
|
|
z: options.rotateY * (Math.PI / 180),
|
|
}, delay).easing(options.rotateEasing ? options.rotateEasing : TWEEN.Easing.Cubic.EaseInOut).onComplete(function () {
|
|
$this.animEnd(options);
|
|
}).start();
|
|
} else {
|
|
this.object3d.rotation.x = options.rotateX * (Math.PI / 180);
|
|
this.object3d.rotation.y = options.rotate * (Math.PI / 180);
|
|
this.object3d.rotation.z = options.rotateY * (Math.PI / 180);
|
|
}
|
|
}
|
|
if (force ||
|
|
options.scale[0] != this.options.scale[0] ||
|
|
options.scale[1] != this.options.scale[1] ||
|
|
options.scale[2] != this.options.scale[2]
|
|
) {
|
|
this.shouldUpdate = true;
|
|
if (delay) {
|
|
this.animStart(options);
|
|
new TWEEN.Tween(this.object3d.scale).to({
|
|
x: options.scale[0],
|
|
y: options.scale[2],
|
|
z: options.scale[1],
|
|
}, delay).easing(options.scaleEasing ? options.scaleEasing : TWEEN.Easing.Cubic.EaseInOut).onComplete(function () {
|
|
$this.animEnd(options);
|
|
}).start();
|
|
} else {
|
|
this.object3d.scale.set(options.scale[0], options.scale[2], options.scale[1]);
|
|
/*if ((options.scale[0] > 0) &&
|
|
(options.scale[1] > 0) &&
|
|
(options.scale[2] > 0)
|
|
)
|
|
this.object3d.scale.set(options.scale[0],options.scale[2],options.scale[1]);
|
|
else{
|
|
var g=this.object3d.geometry;
|
|
g.dynamic = true;
|
|
for(var i = 0; i<g.faces.length; i++) {
|
|
g.faces[i].normal.x = -1*g.faces[i].normal.x;
|
|
g.faces[i].normal.y = -1*g.faces[i].normal.y;
|
|
g.faces[i].normal.z = -1*g.faces[i].normal.z;
|
|
}
|
|
g.computeVertexNormals();
|
|
g.computeFaceNormals();
|
|
g.applyMatrix(new THREE.Matrix4().makeScale( options.scale[0], options.scale[2], options.scale[1] ) );
|
|
}*/
|
|
}
|
|
}
|
|
if (force ||
|
|
options.color != this.options.color
|
|
) {
|
|
if (this.object3d.material && this.object3d.material.color !== undefined)
|
|
if (options.color !== null)
|
|
this.object3d.material.color.setHex(options.color);
|
|
|
|
/*
|
|
if(options.color===null)
|
|
this.object3d.material.color.setHex(0xffffff);
|
|
else
|
|
this.object3d.material.color.setHex(options.color);
|
|
*/
|
|
}
|
|
if (force ||
|
|
options.opacity != this.options.opacity
|
|
) {
|
|
if (this.object3d.material && this.object3d.material.opacity !== undefined) {
|
|
if (options.opacity === null)
|
|
options.opacity = 1;
|
|
if (delay) {
|
|
this.animStart(options);
|
|
new TWEEN.Tween(this.object3d.material).to({
|
|
opacity: options.opacity,
|
|
}, delay).easing(options.opacityEasing ? options.opacityEasing : TWEEN.Easing.Cubic.EaseInOut).onComplete(function () {
|
|
$this.animEnd(options);
|
|
}).start();
|
|
} else
|
|
this.object3d.material.opacity = options.opacity;
|
|
|
|
}
|
|
}
|
|
if (force ||
|
|
options.morphing.toString() != this.options.morphing.toString()
|
|
) {
|
|
this.shouldUpdate = true;
|
|
if (options.morphing.length > 0) {
|
|
if (this.object3d.material && this.object3d.material.materials &&
|
|
this.object3d.material.materials.length > 0 && !this.object3d.material.materials[0].morphTargets) {
|
|
for (var i = 0; i < this.object3d.material.materials.length; i++)
|
|
this.object3d.material.materials[i].morphTargets = true;
|
|
}
|
|
if (delay) {
|
|
this.animStart(options);
|
|
new TWEEN.Tween(this.object3d.morphTargetInfluences).to(options.morphing,
|
|
delay).easing(options.morphingEasing ? options.morphingEasing : TWEEN.Easing.Cubic.EaseInOut).onComplete(function () {
|
|
$this.animEnd(options);
|
|
}).start();
|
|
} else {
|
|
for (var i = 0; i < options.morphing.length && i < this.object3d.morphTargetInfluences.length; i++)
|
|
this.object3d.morphTargetInfluences[i] = options.morphing[i];
|
|
}
|
|
}
|
|
}
|
|
if (this.object3d.material && options.materials) {
|
|
if (force) {
|
|
if (this.object3d.material.materials) {
|
|
for (var m in this.object3d.material.materials) {
|
|
var mat = $this.object3d.material.materials[m];
|
|
if (options.materials[mat.name]) {
|
|
for (var mpi in options.materials[mat.name]) {
|
|
var newMatProp = options.materials[mat.name][mpi];
|
|
(function (mat, mpi) {
|
|
if (mpi == "map") {
|
|
GetMaterialMap(newMatProp, function (matMpi) {
|
|
mat[mpi] = matMpi;
|
|
mat.needsUpdate = true;
|
|
});
|
|
} else if (mpi == "color") {
|
|
if (typeof mat["ambient"] != "undefined")
|
|
mat["ambient"].setHex(newMatProp);
|
|
mat[mpi].setHex(newMatProp);
|
|
}
|
|
else
|
|
mat[mpi] = newMatProp;
|
|
})(mat, mpi, m);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
var diffMat = Diff(this.options.materials, options.materials);
|
|
if (diffMat) {
|
|
for (var mi in diffMat) {
|
|
var newMat = diffMat[mi];
|
|
if (this.object3d.material.materials) {
|
|
for (var m in this.object3d.material.materials) {
|
|
var mat = $this.object3d.material.materials[m];
|
|
if (mat.name == mi) {
|
|
if (newMat) {
|
|
for (var mpi in newMat) {
|
|
var newMatProp = newMat[mpi];
|
|
if (newMatProp !== null) {
|
|
(function (mat, mpi) {
|
|
if (mpi == "map")
|
|
GetMaterialMap(newMatProp, function (matMpi) {
|
|
mat[mpi] = matMpi;
|
|
mat.needsUpdate = true;
|
|
});
|
|
else if (mpi == "color") {
|
|
if (typeof mat["ambient"] != "undefined")
|
|
mat["ambient"].setHex(newMatProp);
|
|
mat[mpi].setHex(newMatProp);
|
|
} else {
|
|
if (delay) {
|
|
$this.animStart(options);
|
|
if (mat[mpi] === undefined || isNaN(newMatProp)) {
|
|
mat[mpi] = newMatProp;
|
|
setTimeout(function () {
|
|
$this.animEnd(options);
|
|
});
|
|
} else {
|
|
var change = {};
|
|
change[mpi] = newMatProp;
|
|
new TWEEN.Tween(mat).to(change, delay).easing(options.materialEasing ? options.materialEasing :
|
|
TWEEN.Easing.Cubic.EaseInOut).onComplete(function () {
|
|
$this.animEnd(options);
|
|
}).start();
|
|
}
|
|
} else
|
|
mat[mpi] = newMatProp;
|
|
}
|
|
})(mat, mpi);
|
|
} else
|
|
delete mat[mpi];
|
|
}
|
|
} else {
|
|
delete mat.map;
|
|
delete mat.opacity;
|
|
delete mat.color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
var GadgetCustomMesh3D = GadgetMesh.extend({
|
|
init: function (gadget, options) {
|
|
options = $.extend(true, {
|
|
create: function () { return null },
|
|
display: function () { },
|
|
}, options, {
|
|
});
|
|
this._super.call(this, gadget, options);
|
|
},
|
|
createObject: function () {
|
|
var $this = this;
|
|
function Callback(object3d) {
|
|
$this.objectReady(object3d);
|
|
}
|
|
var object3d = this.options.create.call(this, Callback);
|
|
if (object3d)
|
|
this.objectReady(object3d);
|
|
},
|
|
displayObject3D: function (force, options, delay) {
|
|
this._super.apply(this, arguments);
|
|
this.options.display.call(this, force, options, delay);
|
|
},
|
|
replaceMesh: function (mesh, options, delay) {
|
|
if (this.object3d) {
|
|
if (this.options.click)
|
|
this.object3d.off("mouseup");
|
|
/*
|
|
if(this.options.holdClick)
|
|
this.object3d.off("holdclick");
|
|
*/
|
|
if (this.object3d.parent)
|
|
this.object3d.parent.remove(this.object3d);
|
|
}
|
|
this.object3d = mesh;
|
|
if (this.options.visible)
|
|
this.show();
|
|
else
|
|
this.hide();
|
|
if (this.options.harbor)
|
|
threeCtx.harbor.add(this.object3d);
|
|
else
|
|
threeCtx.scene.add(this.object3d);
|
|
if (delay) {
|
|
this.displayObject3D(true, this.options);
|
|
this.displayObject3D(true, options, delay);
|
|
} else
|
|
this.displayObject3D(true, options);
|
|
},
|
|
});
|
|
|
|
var GadgetPlane3D = GadgetMesh.extend({
|
|
init: function (gadget, options) {
|
|
options = $.extend(true, {
|
|
display: function () { },
|
|
sx: 1000,
|
|
sy: 1000,
|
|
color: 0xffffff,
|
|
horizontal: true,
|
|
texture: null,
|
|
material: "basic",
|
|
side: null,
|
|
|
|
}, options, {
|
|
});
|
|
this._super.call(this, gadget, options);
|
|
},
|
|
createObject: function () {
|
|
var gg = new THREE.PlaneGeometry(this.options.sx * SCALE3D, this.options.sy * SCALE3D, 1, 1);
|
|
var matData = {
|
|
color: this.options.data,
|
|
opacity: 0,
|
|
}
|
|
if (this.options.texture) {
|
|
var tOptions = this.options.texture;
|
|
if (tOptions.file) {
|
|
GetMaterialMap(tOptions.file, function (texture) {
|
|
if (tOptions.wrapS !== undefined)
|
|
texture.wrapS = tOptions.wrapS;
|
|
if (tOptions.wrapT !== undefined)
|
|
texture.wrapT = tOptions.wrapT;
|
|
if (tOptions.repeat)
|
|
texture.repeat.set.apply(texture.repeat, tOptions.repeat);
|
|
matData.map = texture;
|
|
});
|
|
}
|
|
}
|
|
if (this.options.side !== undefined)
|
|
matData.side = this.options.side;
|
|
if (this.options.transparent !== undefined)
|
|
matData.transparent = this.options.transparent;
|
|
var gm;
|
|
switch (this.options.material) {
|
|
case "phong":
|
|
gm = new THREE.MeshPhongMaterial(matData);
|
|
break;
|
|
default:
|
|
gm = new THREE.MeshBasicMaterial(matData);
|
|
}
|
|
var mesh = new THREE.Mesh(gg, gm);
|
|
this.objectReady(mesh);
|
|
},
|
|
});
|
|
|
|
// should this class be obsoleted in favor of GadgetCustomMesh3D
|
|
var GadgetCustom3D = GadgetObject3D.extend({
|
|
init: function (gadget, options) {
|
|
options = $.extend(true, {
|
|
create: function () { return null },
|
|
display: function () { },
|
|
}, options, {
|
|
});
|
|
this._super.call(this, gadget, options);
|
|
},
|
|
createObject: function () {
|
|
var $this = this;
|
|
function Callback(object3d) {
|
|
$this.objectReady(object3d);
|
|
}
|
|
var object3d = this.options.create.call(this, Callback);
|
|
if (object3d)
|
|
this.objectReady(object3d);
|
|
},
|
|
displayObject3D: function (force, options, delay) {
|
|
this._super.apply(this, arguments);
|
|
this.options.display.call(this, force, options, delay);
|
|
},
|
|
});
|
|
|
|
var GadgetMeshFile = GadgetMesh.extend({
|
|
init: function (gadget, options) {
|
|
this._super.apply(this, arguments);
|
|
this.meshFileForceDisplay = false;
|
|
},
|
|
createObject: function () {
|
|
var $this = this;
|
|
var file = this.options.file;
|
|
var smooth = this.options.smooth;
|
|
GetResource("smoothedfilegeo|" + this.options.smooth + "|" + file, function (geometry, materials) {
|
|
if (file != $this.options.file)
|
|
return;
|
|
var materials0 = []
|
|
for (var i = 0; i < materials.length; i++)
|
|
materials0.push(materials[i].clone());
|
|
materials = materials0;
|
|
if ($this.options.flatShading)
|
|
for (var m = 0; m < materials.length; m++) {
|
|
materials[m].shading = THREE.FlatShading;
|
|
}
|
|
var mesh = new THREE.Mesh(geometry, new THREE.MultiMaterial(materials));
|
|
$this.objectReady(mesh);
|
|
if ($this.meshFileForceDisplay) {
|
|
$this.displayObject3D(true, $this.meshFileForceDisplay);
|
|
$this.meshFileForceDisplay = false;
|
|
}
|
|
});
|
|
},
|
|
displayObject3D: function (force, options, delay) {
|
|
var fileChange = (options.file != this.options.file);
|
|
if (fileChange) {
|
|
options.click = null;
|
|
//options.holdClick=null;
|
|
}
|
|
this._super.apply(this, arguments);
|
|
if (fileChange) {
|
|
if (this.object3d) {
|
|
if (this.options.click)
|
|
this.object3d.off("mouseup");
|
|
/*
|
|
if(this.options.holdClick)
|
|
this.object3d.off("holdclick");
|
|
*/
|
|
if (this.object3d.parent)
|
|
this.object3d.parent.remove(this.object3d);
|
|
this.object3d = null;
|
|
}
|
|
this.options.file = options.file;
|
|
this.meshFileForceDisplay = options;
|
|
this.createObject();
|
|
}
|
|
},
|
|
});
|
|
|
|
var Gadget3DVideo = GadgetMesh.extend({
|
|
init: function (gadget, options) {
|
|
options = $.extend(true, {
|
|
scale: [1, 1, 1],
|
|
playerSide: 1,
|
|
makeMesh: function (videoTexture, ccvVideoTexture) {
|
|
var material = new THREE.MeshBasicMaterial({
|
|
map: videoTexture,
|
|
overdraw: true,
|
|
// side:THREE.DoubleSide
|
|
});
|
|
var geometry = new THREE.PlaneGeometry(12, 9, 1, 1);
|
|
var mesh = new THREE.Mesh(geometry, material);
|
|
|
|
return mesh;
|
|
},
|
|
videoPlaying: function (on) {
|
|
},
|
|
ccvLocked: function (on) {
|
|
},
|
|
ccv: false,
|
|
ccvMargin: [.10, .10, .30, .10],
|
|
ccvWidth: 80,
|
|
ccvHeight: 60,
|
|
hideBeforeFirstLock: true,
|
|
}, options);
|
|
this._super.call(this, gadget, options);
|
|
this.videoConnected = false;
|
|
this.videoErrorCount = 0;
|
|
this.videoSkipError = false;
|
|
this.shouldBeVisible = false;
|
|
this.gotFirstLock = false;
|
|
},
|
|
objectReady: function (mesh) {
|
|
mesh.visible = false;
|
|
for (var i = 0; i < mesh.children.length; i++)
|
|
mesh.children[i].visible = false;
|
|
this.streamReady(Gadget3DVideo.isStreamReady(this.options.playerSide));
|
|
this._super.apply(this, arguments);
|
|
},
|
|
createObject: function () {
|
|
Gadget3DVideo.addAvatar(this, this.options.playerSide);
|
|
var ccvTexture = null;
|
|
if (this.ccvContextKey)
|
|
ccvTexture = Gadget3DVideo.getCCVVideoTexture(this.options.playerSide, this.ccvContextKey)
|
|
var mesh = this.options.makeMesh.call(this,
|
|
Gadget3DVideo.getVideoTexture(this.options.playerSide), ccvTexture);
|
|
if (mesh)
|
|
this.objectReady(mesh);
|
|
},
|
|
remove: function () {
|
|
Gadget3DVideo.removeAvatar(this, this.options.playerSide);
|
|
this._super.apply(this, arguments);
|
|
},
|
|
show: function () {
|
|
this.shouldBeVisible = true;
|
|
if (this.videoConnected && (this.options.ccv == false || this.gotFirstLock || !this.options.hideBeforeFirstLock))
|
|
this._super();
|
|
},
|
|
hide: function () {
|
|
this.shouldBeVisible = false;
|
|
this._super();
|
|
},
|
|
streamReady: function (on) {
|
|
this.videoConnected = on;
|
|
if (on)
|
|
this.show();
|
|
else
|
|
this.hide();
|
|
},
|
|
ccvLocked: function (locked) {
|
|
if (locked && this.shouldBeVisible) {
|
|
this.gotFirstLock = true;
|
|
this.show();
|
|
}
|
|
this.options.ccvLocked(locked);
|
|
},
|
|
});
|
|
|
|
Gadget3DVideo.streams = {};
|
|
Gadget3DVideo.avatars = { "1": [], "-1": [] };
|
|
Gadget3DVideo.textures = { "1": null, "-1": null };
|
|
Gadget3DVideo.renderLoopHooked = false;
|
|
Gadget3DVideo.ccvLibRequested = false;
|
|
Gadget3DVideo.getStream = function (playerSide) {
|
|
if (!this.streams[playerSide]) {
|
|
var vStream = {
|
|
stream: null,
|
|
avatars: this.avatars[playerSide],
|
|
video: null,
|
|
videoImage: null,
|
|
videoContext: null,
|
|
videoTexture: null,
|
|
streamReady: false,
|
|
ownVideoElement: false,
|
|
errorCount: 0,
|
|
loopCount: 0,
|
|
local: false,
|
|
ccvVideoImage: null,
|
|
ccvInProgress: false,
|
|
ccvLock: null,
|
|
ccvContexts: {},
|
|
ccvLastAnalyzed: null,
|
|
ccvLastSuccess: null,
|
|
}
|
|
var video = $("video[joclyhub-video='" + playerSide + "']");
|
|
if (video.length > 0) {
|
|
vStream.video = video;
|
|
} else {
|
|
vStream.ownVideoElement = true;
|
|
vStream.video = $("<video/>").attr("autoplay", "autoplay").width(
|
|
160).height(120).css({
|
|
visibility: "hidden",
|
|
position: "absolute",
|
|
"z-index": -1,
|
|
top: 0,
|
|
}).attr("joclyhub-video", playerSide).appendTo("body");
|
|
}
|
|
var canvas = $("canvas[joclyhub-video-canvas='" + playerSide + "']");
|
|
if (canvas.length > 0) {
|
|
vStream.videoImage = canvas;
|
|
if (this.textures[playerSide])
|
|
vStream.videoTexture = this.textures[playerSide];
|
|
else {
|
|
vStream.videoTexture = new THREE.Texture(vStream.videoImage[0]);
|
|
this.textures[playerSide] = vStream.videoTexture;
|
|
}
|
|
} else {
|
|
vStream.videoImage = this.makeCanvas(160, 120).attr("joclyhub-video-canvas", playerSide);
|
|
vStream.videoTexture = new THREE.Texture(vStream.videoImage[0]);
|
|
this.textures[playerSide] = vStream.videoTexture;
|
|
}
|
|
vStream.videoTexture.minFilter = THREE.LinearFilter;
|
|
vStream.videoTexture.magFilter = THREE.LinearFilter;
|
|
vStream.videoImageContext = vStream.videoImage[0].getContext('2d');
|
|
this.streams[playerSide] = vStream;
|
|
}
|
|
return this.streams[playerSide];
|
|
}
|
|
Gadget3DVideo.addStream = function (playerSide, stream, local) {
|
|
var $this = this;
|
|
var vStream = this.getStream(playerSide);
|
|
vStream.stream = stream;
|
|
vStream.local = local;
|
|
if (threeCtx)
|
|
threeCtx.animControl.trigger(3000);
|
|
if (!this.renderLoopHooked) {
|
|
this.renderLoopHooked = true;
|
|
if (threeCtx)
|
|
threeCtx.animateCallbacks["Gadget3DVideo"] = {
|
|
_this: $this,
|
|
callback: $this.animate,
|
|
}
|
|
}
|
|
}
|
|
Gadget3DVideo.removeStream = function (playerSide) {
|
|
var vStream = this.streams[playerSide];
|
|
if (vStream) {
|
|
if (vStream.streamReady)
|
|
for (var i = 0; i < vStream.avatars.length; i++)
|
|
vStream.avatars[i].streamReady(false);
|
|
if (vStream.ccvLastSuccess)
|
|
vStream.ccvLastSuccess.videoImage.remove();
|
|
if (vStream.ccvLastAnalyzed)
|
|
vStream.ccvLastAnalyzed.remove();
|
|
delete this.streams[playerSide];
|
|
if (this.renderLoopHooked) {
|
|
var streamCount = 0;
|
|
for (var s in this.streams)
|
|
streamCount++;
|
|
if (streamCount == 0) {
|
|
if (threeCtx)
|
|
delete threeCtx.animateCallbacks["Gadget3DVideo"];
|
|
this.renderLoopHooked = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Gadget3DVideo.addAvatar = function (avatar, playerSide) {
|
|
this.avatars[playerSide].push(avatar);
|
|
var vStream = this.getStream(playerSide);
|
|
if (!avatar.ccvContextKey)
|
|
avatar.ccvContextKey = "" + avatar.options.ccvWidth + "," + avatar.options.ccvHeight + "," + JSON.stringify(avatar.options.ccvMargin);
|
|
if (!vStream.ccvContexts[avatar.ccvContextKey]) {
|
|
var ccvContext = {
|
|
width: avatar.options.ccvWidth,
|
|
height: avatar.options.ccvHeight,
|
|
margin: avatar.options.ccvMargin,
|
|
}
|
|
ccvContext.videoImage = this.makeCanvas(ccvContext.width, ccvContext.height);
|
|
ccvContext.videoImageContext = ccvContext.videoImage[0].getContext('2d');
|
|
ccvContext.videoImageContext.fillStyle = "rgb(0,255,0)";
|
|
ccvContext.videoImageContext.fillRect(0, 0, ccvContext.width, ccvContext.height);
|
|
ccvContext.videoTexture = new THREE.Texture(ccvContext.videoImage[0]);
|
|
ccvContext.videoTexture.minFilter = THREE.LinearFilter;
|
|
ccvContext.videoTexture.magFilter = THREE.LinearFilter;
|
|
ccvContext.videoTexture.needsUpdate = true;
|
|
vStream.ccvContexts[avatar.ccvContextKey] = ccvContext;
|
|
//debugger;
|
|
}
|
|
return vStream.streamReady;
|
|
}
|
|
Gadget3DVideo.getVideoTexture = function (playerSide) {
|
|
var vStream = this.streams[playerSide];
|
|
if (vStream)
|
|
return vStream.videoTexture;
|
|
else
|
|
return null;
|
|
}
|
|
Gadget3DVideo.getCCVVideoTexture = function (playerSide, contextKey) {
|
|
var vStream = this.streams[playerSide];
|
|
if (vStream) {
|
|
var ccvContext = vStream.ccvContexts[contextKey];
|
|
if (ccvContext)
|
|
return ccvContext.videoTexture;
|
|
}
|
|
return null;
|
|
}
|
|
Gadget3DVideo.isStreamReady = function (playerSide) {
|
|
return this.streams[playerSide] && this.streams[playerSide].streamReady;
|
|
}
|
|
Gadget3DVideo.isCCVLocked = function (playerSide) {
|
|
return this.streams[playerSide] && this.streams[playerSide].ccvLock;
|
|
}
|
|
Gadget3DVideo.removeAvatar = function (avatar, playerSide) {
|
|
var vStream = this.streams[playerSide];
|
|
if (vStream)
|
|
for (var i = 0; i < vStream.avatars.length; i++)
|
|
if (avatar == vStream.avatars[i]) {
|
|
vStream.avatars.splice(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
Gadget3DVideo.animate = function () {
|
|
for (var side in this.streams) {
|
|
var vStream = this.streams[side];
|
|
try {
|
|
vStream.loopCount++;
|
|
if (vStream.video[0].getAttribute("webrtc-attached") === "1" &&
|
|
vStream.video[0].readyState === vStream.video[0].HAVE_ENOUGH_DATA) {
|
|
vStream.videoImageContext.drawImage(vStream.video[0], 0, 0,
|
|
vStream.videoImage[0].width, vStream.videoImage[0].height);
|
|
if (vStream.videoTexture) {
|
|
vStream.videoTexture.needsUpdate = true;
|
|
if (!vStream.streamReady) {
|
|
vStream.streamReady = true;
|
|
for (var i = 0; i < vStream.avatars.length; i++)
|
|
vStream.avatars[i].streamReady(true);
|
|
}
|
|
}
|
|
var ccvLocalRequested = false;
|
|
var ccvRequested = false;
|
|
for (var i = 0; i < vStream.avatars.length; i++)
|
|
if (vStream.avatars[i].options.ccv) {
|
|
ccvRequested = true;
|
|
if (vStream.local) {
|
|
ccvLocalRequested = true;
|
|
break;
|
|
}
|
|
}
|
|
if (ccvLocalRequested) {
|
|
if (typeof (ccv) == "undefined") { // ccv library not loaded
|
|
if (!this.ccvLibRequested) {
|
|
var path = null;
|
|
console.error("No CCV path available");
|
|
this.ccvLibRequested = true;
|
|
if (path) {
|
|
$("<script/>").attr("src", path + "/face.js").attr("type", "text/javascript")
|
|
.appendTo($("head"));
|
|
$("<script/>").attr("src", path + "/ccv.js").attr("type", "text/javascript")
|
|
.appendTo($("head"));
|
|
}
|
|
}
|
|
} else {
|
|
if (!vStream.ccvInProgress)
|
|
this.ccvPoll(vStream);
|
|
}
|
|
}
|
|
if (ccvRequested)
|
|
this.ccvAnimate(vStream);
|
|
threeCtx.animControl.trigger();
|
|
}
|
|
} catch (e) {
|
|
if (vStream.errorCount % 1000000 == 0)
|
|
console.warn("Gadget3DVideo.animate error", vStream.errorCount, side, e);
|
|
vStream.errorCount++;
|
|
}
|
|
}
|
|
}
|
|
Gadget3DVideo.ccvLocked = function (vStream, locking) {
|
|
for (var i = 0; i < vStream.avatars.length; i++)
|
|
vStream.avatars[i].ccvLocked(locking);
|
|
}
|
|
Gadget3DVideo.ccvPoll = function (vStream) {
|
|
vStream.ccvInProgress = true;
|
|
var width = vStream.videoImage[0].width;
|
|
var height = vStream.videoImage[0].height;
|
|
var now = Date.now();
|
|
function CCVResult(comp) {
|
|
if (comp.length == 0) {
|
|
if (vStream.ccvLock) {
|
|
vStream.ccvLock = null;
|
|
Gadget3DVideo.ccvLocked(vStream, false);
|
|
WebRTC.sendCCVMessage({
|
|
locked: false,
|
|
});
|
|
}
|
|
} else {
|
|
var face = comp[0];
|
|
var lock = vStream.ccvLock;
|
|
vStream.ccvLock = {
|
|
x: face.x,
|
|
y: face.y,
|
|
width: face.width,
|
|
height: face.height,
|
|
}
|
|
if (vStream.ccvLastSuccess)
|
|
vStream.ccvLastSuccess.videoImage.remove();
|
|
vStream.ccvLastSuccess = $.extend({
|
|
videoImage: vStream.ccvLastAnalyzed,
|
|
copied: false,
|
|
}, vStream.ccvLock);
|
|
vStream.ccvLastAnalyzed = null;
|
|
vStream.ccvLastAnalyzedContext = null;
|
|
|
|
if (!lock)
|
|
Gadget3DVideo.ccvLocked(vStream, true);
|
|
WebRTC.sendCCVMessage({
|
|
locked: true,
|
|
x: face.x,
|
|
y: face.y,
|
|
width: face.width,
|
|
height: face.height,
|
|
});
|
|
}
|
|
ReschedulePoll();
|
|
}
|
|
function ReschedulePoll() {
|
|
setTimeout(function () {
|
|
vStream.ccvInProgress = false;
|
|
}, 200);
|
|
}
|
|
if (!vStream.ccvLastAnalyzed) {
|
|
vStream.ccvLastAnalyzed = this.makeCanvas(vStream.videoImage[0].width, vStream.videoImage[0].height);
|
|
vStream.ccvLastAnalyzedContext = vStream.ccvLastAnalyzed[0].getContext("2d");
|
|
}
|
|
vStream.ccvLastAnalyzedContext.drawImage(vStream.videoImage[0], 0, 0, vStream.videoImage[0].width, vStream.videoImage[0].height);
|
|
|
|
/*
|
|
if(WebRTC.webrtcDetectedBrowser=="firefox")
|
|
ccv.detect_objects({
|
|
//"canvas" : ccv.grayscale(vStream.ccvLastAnalyzed[0]),
|
|
"canvas" : ccv.grayscale(vStream.videoImage[0]),
|
|
"cascade" : cascade,
|
|
"interval" : 5,
|
|
"min_neighbors" : 1,
|
|
"async" : false,
|
|
"async" : true,
|
|
"worker" : 1
|
|
})(CCVResult);
|
|
else
|
|
*/
|
|
CCVResult(ccv.detect_objects({
|
|
//"canvas" : ccv.grayscale(vStream.ccvLastAnalyzed[0]),
|
|
"canvas": ccv.grayscale(vStream.videoImage[0]),
|
|
"cascade": cascade,
|
|
"interval": 5,
|
|
"min_neighbors": 1,
|
|
"async": false,
|
|
"worker": 1
|
|
}));
|
|
}
|
|
Gadget3DVideo.makeCanvas = function (width, height) {
|
|
return $("<canvas/>").attr("width", width).attr("height", height).width(width).height(height)
|
|
.css({
|
|
visibility: "hidden",
|
|
position: "absolute",
|
|
"z-index": -1,
|
|
top: 0,
|
|
}).appendTo("body");
|
|
}
|
|
Gadget3DVideo.ccvAnimate = function (vStream) {
|
|
function DrawImage(ccvContext, ccvLock, source) {
|
|
var width = ccvLock.width * (1 + ccvContext.margin[1] + ccvContext.margin[3]);
|
|
var height = ccvLock.height * (1 + ccvContext.margin[0] + ccvContext.margin[2]);
|
|
var x = ccvLock.x - ccvLock.width * ccvContext.margin[3];
|
|
var y = ccvLock.y - ccvLock.height * ccvContext.margin[0];
|
|
if (x < 0) {
|
|
width += x;
|
|
x = 0;
|
|
}
|
|
if (y < 0) {
|
|
height += y;
|
|
y = 0;
|
|
}
|
|
if (x + width > source.width)
|
|
width = source.width - x;
|
|
if (y + height > source.height)
|
|
height = source.height - y;
|
|
ccvContext.videoImageContext.drawImage(source,
|
|
x, y, width, height,
|
|
0, 0,
|
|
ccvContext.width, ccvContext.height);
|
|
ccvContext.videoTexture.needsUpdate = true;
|
|
}
|
|
|
|
for (var contextKey in vStream.ccvContexts) {
|
|
var ccvContext = vStream.ccvContexts[contextKey];
|
|
if (vStream.ccvLock)
|
|
DrawImage(ccvContext, vStream.ccvLock, vStream.videoImage[0]);
|
|
else if (vStream.ccvLastSuccess && !vStream.ccvLastSuccess.copied) {
|
|
vStream.ccvLastSuccess.copied = true;
|
|
DrawImage(ccvContext, vStream.ccvLastSuccess, vStream.ccvLastSuccess.videoImage[0]);
|
|
}
|
|
}
|
|
}
|
|
Gadget3DVideo.receiveRemoteLock = function (message) {
|
|
for (var side in this.streams) {
|
|
var vStream = this.streams[side];
|
|
if (vStream.local)
|
|
continue;
|
|
var lock = vStream.ccvLock;
|
|
if (message.locked) {
|
|
vStream.ccvLock = {
|
|
x: message.x,
|
|
y: message.y,
|
|
width: message.width,
|
|
height: message.height,
|
|
};
|
|
if (vStream.ccvLastSuccess)
|
|
vStream.ccvLastSuccess.videoImage.remove();
|
|
var videoImage = this.makeCanvas(vStream.videoImage[0].width, vStream.videoImage[0].height);
|
|
var videoImageContext = videoImage[0].getContext("2d");
|
|
videoImageContext.drawImage(vStream.videoImage[0], 0, 0, vStream.videoImage[0].width, vStream.videoImage[0].height);
|
|
vStream.ccvLastSuccess = $.extend({
|
|
videoImage: videoImage,
|
|
copied: false,
|
|
}, vStream.ccvLock);
|
|
if (!lock)
|
|
Gadget3DVideo.ccvLocked(vStream, true);
|
|
} else {
|
|
if (lock) {
|
|
vStream.ccvLock = null;
|
|
Gadget3DVideo.ccvLocked(vStream, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function WebRTCHandler(event, data) {
|
|
try {
|
|
if (data.webrtcType == "mediaOn") {
|
|
if (data.ar)
|
|
AR(data.stream);
|
|
else
|
|
Gadget3DVideo.addStream(data.side, data.stream, data.local);
|
|
} if (data.webrtcType == "mediaOff") {
|
|
if (arStream)
|
|
AR(null);
|
|
else
|
|
Gadget3DVideo.removeStream(data.side);
|
|
} if (data.webrtcType == "ccv")
|
|
Gadget3DVideo.receiveRemoteLock(data.message);
|
|
} catch (e) {
|
|
console.error("xd-view webrtc error", e);
|
|
}
|
|
}
|
|
$(document).bind("joclyhub.webrtc", WebRTCHandler);
|
|
|
|
var Gadget3DVideoFile = GadgetCustomMesh3D.extend({
|
|
init: function (gadget, options) {
|
|
options = $.extend(true, {
|
|
scale: [1, 1, 1],
|
|
makeMesh: function (videoTexture) {
|
|
var material = new THREE.MeshBasicMaterial({
|
|
map: videoTexture,
|
|
overdraw: true,
|
|
});
|
|
var geometry = new THREE.PlaneGeometry(this.options.width * this.SCALE3D, this.options.height * this.SCALE3D, 1, 1);
|
|
var mesh = new THREE.Mesh(geometry, material);
|
|
|
|
return mesh;
|
|
},
|
|
width: 12,
|
|
height: 9,
|
|
}, options);
|
|
this.videoPlayer = Gadget3DVideoFile.GetVideoPlayer(options.src);
|
|
this._super.call(this, gadget, options);
|
|
},
|
|
createObject: function () {
|
|
var mesh = this.options.makeMesh.call(this, this.videoPlayer.texture);
|
|
if (mesh)
|
|
this.objectReady(mesh);
|
|
},
|
|
remove: function () {
|
|
var videoPlayer = videoPlayers[this.options.src];
|
|
if (videoPlayer) {
|
|
videoPlayer.count--;
|
|
if (videoPlayer.count == 0) {
|
|
delete threeCtx.animateCallbacks["Gadget3DVideoFile." + this.options.src];
|
|
videoPlayer.tag.remove();
|
|
videoPlayer.canvas.remove();
|
|
delete videoPlayers[this.options.src];
|
|
}
|
|
}
|
|
this._super.apply(this, arguments);
|
|
},
|
|
});
|
|
|
|
var videoPlayers = {};
|
|
Gadget3DVideoFile.GetVideoPlayer = function (url) {
|
|
var videoPlayer = videoPlayers[url];
|
|
if (!videoPlayer) {
|
|
var width = 638;
|
|
var height = 360;
|
|
var videoTag = $("<video/>").attr("autoplay", "autoplay")./*attr("muted","muted").*/attr("loop", "loop").css({
|
|
width: width,
|
|
height: height,
|
|
position: "absolute",
|
|
}).append($("<source/>").attr("src", url).attr("type", "video/webm")).appendTo("body");
|
|
videoPlayer = {
|
|
count: 1,
|
|
tag: videoTag,
|
|
canvas: Gadget3DVideo.makeCanvas(width, height),
|
|
}
|
|
videoPlayer.context = videoPlayer.canvas[0].getContext('2d');
|
|
videoPlayer.context.fillStyle = "rgb(0,255,0)";
|
|
videoPlayer.context.fillRect(0, 0, width, height);
|
|
|
|
videoPlayer.texture = new THREE.Texture(videoPlayer.canvas[0]);
|
|
videoPlayer.texture.minFilter = THREE.LinearFilter;
|
|
videoPlayer.texture.magFilter = THREE.LinearFilter;
|
|
videoPlayer.texture.needsUpdate = true;
|
|
|
|
function Animate() {
|
|
var ctx = videoPlayer.context;
|
|
ctx.drawImage(videoPlayer.tag[0], 0, 0,
|
|
width, height);
|
|
videoPlayer.texture.needsUpdate = true;
|
|
}
|
|
threeCtx.animateCallbacks["Gadget3DVideoFile." + url] = {
|
|
_this: null,
|
|
callback: Animate,
|
|
}
|
|
|
|
videoPlayers[url] = videoPlayer;
|
|
} else
|
|
videoPlayer.count++;
|
|
|
|
return videoPlayer;
|
|
}
|
|
|
|
|
|
var GadgetCamera = GadgetObject3D.extend({
|
|
init: function (gadget, options) {
|
|
this._super.call(this, gadget, options);
|
|
//this.object3d=threeCtx.camera;
|
|
this.object3d = threeCtx.body;
|
|
this.cameraObject = this.object3d.children[0];
|
|
this.targetAnim = null;
|
|
this.camTarget = threeCtx.camTarget;
|
|
},
|
|
displayObject3D: function (force, options, delay) {
|
|
var $this = this;
|
|
this.options.x = this.object3d.position.x / SCALE3D;
|
|
this.options.y = this.object3d.position.z / SCALE3D;
|
|
this.options.z = this.object3d.position.y / SCALE3D;
|
|
this._super.apply(this, arguments);
|
|
if (force ||
|
|
options.targetX * SCALE3D != threeCtx.cameraControls.camTarget.x ||
|
|
options.targetY * SCALE3D != threeCtx.cameraControls.camTarget.z ||
|
|
options.targetZ * SCALE3D != threeCtx.cameraControls.camTarget.y
|
|
) {
|
|
if (delay) {
|
|
var traveling = options.traveling;
|
|
var x0 = threeCtx.cameraControls.camTarget.x;
|
|
var y0 = threeCtx.cameraControls.camTarget.y;
|
|
var z0 = threeCtx.cameraControls.camTarget.z;
|
|
|
|
options.traveling = false;
|
|
if (this.targetAnim) {
|
|
this.targetAnim.stop();
|
|
//this.animEnd(this.targetCallback);
|
|
this.animEnd(options);
|
|
}
|
|
//this.targetCallback=callback;
|
|
this.animStart(options);
|
|
this.targetAnim = new TWEEN.Tween(threeCtx.cameraControls.camTarget).to({
|
|
x: options.targetX * SCALE3D,
|
|
y: options.targetZ * SCALE3D,
|
|
z: options.targetY * SCALE3D,
|
|
}, delay).easing(options.targetEasing ? options.targetEasing : TWEEN.Easing.Cubic.EaseInOut).onComplete(function () {
|
|
$this.targetAnim = null;
|
|
$this.animEnd(options);
|
|
}).onUpdate(function (ratio) {
|
|
if (options.targetEasingUpdate)
|
|
options.targetEasingUpdate.call($this, ratio);
|
|
if (traveling) {
|
|
var dx = threeCtx.cameraControls.camTarget.x - x0;
|
|
var dy = threeCtx.cameraControls.camTarget.y - y0;
|
|
var dz = threeCtx.cameraControls.camTarget.z - z0;
|
|
x0 = threeCtx.cameraControls.camTarget.x;
|
|
y0 = threeCtx.cameraControls.camTarget.y;
|
|
z0 = threeCtx.cameraControls.camTarget.z;
|
|
//$this.object3d.position.add(new THREE.Vector3(dx,dy,dz));
|
|
}
|
|
//$this.object3d.lookAt(threeCtx.cameraControls.camTarget);
|
|
$this.cameraObject.lookAt(threeCtx.cameraControls.camTarget);
|
|
}).start();
|
|
} else {
|
|
threeCtx.cameraControls.camTarget.x = options.targetX * SCALE3D;
|
|
threeCtx.cameraControls.camTarget.y = options.targetZ * SCALE3D;
|
|
threeCtx.cameraControls.camTarget.z = options.targetY * SCALE3D;
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
function CreateCameraGadget() {
|
|
xdv.createGadget("camera", {
|
|
"3d": {
|
|
type: "camera3d",
|
|
x: threeCtx.camera.position.x / SCALE3D,
|
|
y: threeCtx.camera.position.z / SCALE3D,
|
|
z: threeCtx.camera.position.y / SCALE3D,
|
|
targetX: threeCtx.cameraControls.camTarget.x / SCALE3D,
|
|
targetY: threeCtx.cameraControls.camTarget.z / SCALE3D,
|
|
targetZ: threeCtx.cameraControls.camTarget.y / SCALE3D,
|
|
},
|
|
});
|
|
xdv.saveGadgetProps("camera", ["targetX", "targetY", "targetZ"], "initial");
|
|
xdv.updateGadget("camera", {
|
|
"3d": {
|
|
visible: true,
|
|
},
|
|
});
|
|
}
|
|
|
|
/* ======================================== */
|
|
|
|
var avatarTypes = {
|
|
"image": GadgetImage,
|
|
"element": GadgetElement,
|
|
"canvas": GadgetCanvas,
|
|
"hexagon": GadgetHexagon,
|
|
"sprite": GadgetSprite,
|
|
"disk": GadgetDisk,
|
|
"meshfile": GadgetMeshFile,
|
|
"custom3d": GadgetCustom3D,
|
|
"plane3d": GadgetPlane3D,
|
|
"custommesh3d": GadgetCustomMesh3D,
|
|
"video3d": Gadget3DVideo,
|
|
"camera3d": GadgetCamera,
|
|
"videofile3d": Gadget3DVideoFile,
|
|
}
|
|
|
|
|
|
/* ======================================== */
|
|
|
|
var areaElements = null;
|
|
|
|
View.Game.CamAnim = {
|
|
isSupported: function () {
|
|
return !!threeCtx;
|
|
},
|
|
isRunning: function () {
|
|
return threeCtx && threeCtx.camAnim;
|
|
},
|
|
set: function (on) {
|
|
if (threeCtx)
|
|
threeCtx.setCamAnim(on);
|
|
},
|
|
}
|
|
|
|
View.Game.InitView = function () {
|
|
|
|
resourcesMap = this.resources || {};
|
|
|
|
if (this != xdv.game) {
|
|
xdv.game = this;
|
|
if (this.mWidget.find(".jocly-xdv-area").length == 0) {
|
|
area = $("<div/>").css({
|
|
"position": "absolute",
|
|
"z-index": 0,
|
|
"overflow": "hidden",
|
|
}).addClass("jocly-xdv-area").appendTo(this.mWidget);
|
|
}
|
|
}
|
|
if (areaElements) {
|
|
areaElements.appendTo(area);
|
|
areaElements = null;
|
|
}
|
|
|
|
if (!xdv.initDone) {
|
|
this.xdInit(xdv);
|
|
xdv.initDone = true;
|
|
}
|
|
|
|
var needs3DUpdate = false;
|
|
if (!currentSkin || this.mSkin != currentSkin.name) {
|
|
currentSkin = null;
|
|
for (var i = 0; i < this.mViewOptions.skins.length; i++) {
|
|
var skin = this.mViewOptions.skins[i];
|
|
if (skin.name == this.mSkin) {
|
|
currentSkin = skin;
|
|
break;
|
|
}
|
|
}
|
|
if (currentSkin == null) {
|
|
Log("!!! InitView", "skin", this.mSkin, "not found");
|
|
return;
|
|
}
|
|
xdv.unbuildGadgets();
|
|
areaElements = null;
|
|
if (currentSkin["3d"])
|
|
needs3DUpdate = true;
|
|
}
|
|
|
|
var areaWidth = Math.min(this.mGeometry.width, this.mGeometry.height
|
|
* (this.mViewOptions.preferredRatio || 1));
|
|
var areaHeight = Math.min(this.mGeometry.width / (this.mViewOptions.preferredRatio || 1), this.mGeometry.height);
|
|
var areaCenter;
|
|
if (currentSkin["3d"]) {
|
|
area.css({
|
|
left: 0,
|
|
top: 0,
|
|
width: this.mGeometry.width,
|
|
height: this.mGeometry.height,
|
|
});
|
|
areaCenter = {
|
|
x: this.mGeometry.width / 2,
|
|
y: this.mGeometry.height / 2,
|
|
};
|
|
if (!threeCtx) {
|
|
if (!THREE.Object3D._threexDomEvent) {
|
|
THREE.Object3D._threexDomEvent = new THREEx.DomEvent();
|
|
}
|
|
threeCtx = BuildThree(this, areaWidth, areaHeight);
|
|
CreateCameraGadget();
|
|
threeCtx.camera.updateProjectionMatrix();
|
|
} else {
|
|
threeCtx.renderer.setSize(this.mGeometry.width, this.mGeometry.height);
|
|
threeCtx.anaglyphEffect.setSize(this.mGeometry.width, this.mGeometry.height);
|
|
threeCtx.camera.aspect = this.mGeometry.width / this.mGeometry.height;
|
|
threeCtx.camera.updateProjectionMatrix();
|
|
}
|
|
|
|
THREE.Object3D._threexDomEvent.setDOMElement(threeCtx.renderer.domElement);
|
|
THREE.Object3D._threexDomEvent.setBoundContext(THREEx_boundContext);
|
|
THREE.Object3D._threexDomEvent.camera(threeCtx.camera);
|
|
|
|
ResumePendingResources();
|
|
|
|
threeCtx.animControl.trigger();
|
|
if (needs3DUpdate) {
|
|
|
|
var cameraData = $.extend(true, {
|
|
radius: 12,
|
|
elevationAngle: 60,
|
|
rotationAngle: 90,
|
|
distMax: 20,
|
|
distMin: 0,
|
|
elevationMax: 89,
|
|
elevationMin: 10,
|
|
startAngle: 90,
|
|
camAnim: false,
|
|
limitCamMoves: true,
|
|
enableDrag: true,
|
|
targetBounds: [3000, 3000, 3000],
|
|
target: [0, 0, 800],
|
|
fov: 55,
|
|
near: .01
|
|
}, currentSkin.camera);
|
|
|
|
// update FOV
|
|
threeCtx.camera.fov = cameraData.fov;
|
|
threeCtx.camera.near = cameraData.near;
|
|
threeCtx.camera.updateProjectionMatrix();
|
|
|
|
$.extend(threeCtx.cameraControls, {
|
|
minDistance: cameraData.distMin,
|
|
maxDistance: cameraData.distMax,
|
|
minPolarAngle: (90 - cameraData.elevationMax) * Math.PI / 180,
|
|
maxPolarAngle: (90 - cameraData.elevationMin) * Math.PI / 180,
|
|
enableDrag: cameraData.enableDrag,
|
|
targetBounds: [cameraData.targetBounds[0] * SCALE3D, cameraData.targetBounds[2] * SCALE3D, cameraData.targetBounds[1] * SCALE3D],
|
|
});
|
|
|
|
var camPosition = {
|
|
x: cameraData.radius * Math.cos(cameraData.elevationAngle * Math.PI / 180) * Math.cos(cameraData.rotationAngle * Math.PI / 180),
|
|
z: cameraData.radius * Math.cos(cameraData.elevationAngle * Math.PI / 180) * Math.sin(cameraData.rotationAngle * Math.PI / 180),
|
|
y: cameraData.radius * Math.sin(cameraData.elevationAngle * Math.PI / 180),
|
|
}
|
|
|
|
var camTarget = {
|
|
x: cameraData.target[0],
|
|
y: cameraData.target[1],
|
|
z: cameraData.target[2],
|
|
}
|
|
xdv.updateGadget("camera", {
|
|
"3d": {
|
|
x: camPosition.x / SCALE3D,
|
|
y: camPosition.z / SCALE3D,
|
|
z: camPosition.y / SCALE3D,
|
|
targetX: camTarget.x,
|
|
targetY: camTarget.y,
|
|
targetZ: camTarget.z,
|
|
},
|
|
});
|
|
threeCtx.cameraControls.camTarget.copy(camTarget);
|
|
//threeCtx.cameraControls.camera.position.copy(camPosition);
|
|
threeCtx.cameraControls.update();
|
|
|
|
var world = {
|
|
color: 0x205D7C,
|
|
fog: true,
|
|
fogNear: 10,
|
|
fogFar: 100,
|
|
lightCastShadow: true,
|
|
lightIntensity: 1.75,
|
|
lightPosition: { x: -12, y: 12, z: 12 },
|
|
//lightShadowDarkness: 0.75,
|
|
ambientLightColor: 0xbbbbbb,
|
|
skyLightPosition: { x: -45, y: 45, z: 45 },
|
|
skyLightIntensity: 2,
|
|
}
|
|
$.extend(true, world, currentSkin.world);
|
|
if (threeCtx.scene.fog) {
|
|
threeCtx.scene.remove(threeCtx.scene.fog);
|
|
delete threeCtx.scene.fog;
|
|
}
|
|
if (world.fog) {
|
|
var fogColor = world.color;
|
|
if (world.fogColor) fogColor = world.fogColor;
|
|
threeCtx.scene.fog = new THREE.Fog(fogColor, world.fogNear, world.fogFar);
|
|
}
|
|
|
|
threeCtx.world = world;
|
|
threeCtx.renderer.setClearColor(new THREE.Color(world.color), 1);
|
|
threeCtx.light.castShadow = world.lightCastShadow;
|
|
threeCtx.light.intensity = world.lightIntensity;
|
|
threeCtx.light.position.set(world.lightPosition.x, world.lightPosition.y, world.lightPosition.z);
|
|
//threeCtx.light.shadowDarkness=world.lightShadowDarkness;
|
|
threeCtx.ambientLight.color.setHex(world.ambientLightColor);
|
|
threeCtx.skyLight.intensity = world.skyLightIntensity;
|
|
threeCtx.skyLight.position.set(world.skyLightPosition.x, world.skyLightPosition.y, world.skyLightPosition.z);
|
|
}
|
|
threeCtx.renderer.domElement.style.display = "block";
|
|
} else {
|
|
area.css({
|
|
left: (this.mGeometry.width - areaWidth) / 2,
|
|
top: (this.mGeometry.height - areaHeight) / 2,
|
|
width: areaWidth,
|
|
height: areaHeight,
|
|
});
|
|
areaCenter = {
|
|
x: areaWidth / 2,
|
|
y: areaHeight / 2,
|
|
}
|
|
if (threeCtx)
|
|
threeCtx.renderer.domElement.style.display = "none";
|
|
}
|
|
|
|
this.xdBuildScene(xdv);
|
|
//xdv.updateArea(Math.min(areaWidth,areaHeight)/VSIZE,areaCenter);
|
|
xdv.updateArea(Math.max(areaWidth, areaHeight) / VSIZE, areaCenter);
|
|
}
|
|
|
|
View.Game.DestroyView = function () {
|
|
if (!xdv.game) {
|
|
Log("!!! InitView", "game already unset");
|
|
return;
|
|
}
|
|
if (resLoadingMask)
|
|
resLoadingMask.hide();
|
|
xdv.game = null;
|
|
areaElements = area.children().detach();
|
|
if (threeCtx) {
|
|
if (threeCtx.cameraControls.autoRotate)
|
|
threeCtx.cameraControls.autoRotate = false;
|
|
}
|
|
//threeCtx.animControl.stop();
|
|
}
|
|
|
|
View.Game.CloseView = function () {
|
|
xdv.unbuildGadgets();
|
|
|
|
if (threeCtx) {
|
|
THREE.Object3D._threexDomEvent.unsetBoundContext(THREEx_boundContext);
|
|
threeCtx.cameraControls.destroy();
|
|
threeCtx = null;
|
|
}
|
|
InitGlobals();
|
|
}
|
|
|
|
View.Game.xdResourceLoaded = function (res) {
|
|
if (/^map\|/.test(res))
|
|
return false;
|
|
if (resources[res] && resources[res].status == "loaded")
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
View.Game.xdLoadResources = function (ress, callback) {
|
|
var resCount = 0;
|
|
function ResLoaded() {
|
|
if (--resCount == 0)
|
|
callback();
|
|
}
|
|
for (var i = 0; i < ress.length; i++) {
|
|
resCount++;
|
|
var m = /^map\|(.*)$/.exec(ress[i]);
|
|
if (m)
|
|
GetMaterialMap(m[1], function () {
|
|
setTimeout(ResLoaded, 0);
|
|
});
|
|
else
|
|
GetResource(ress[i], function () {
|
|
setTimeout(ResLoaded, 0);
|
|
});
|
|
}
|
|
}
|
|
|
|
View.Game.xdExternalCommand = function (cmd, scope) {
|
|
switch (cmd.type) {
|
|
case 'updateCamera':
|
|
xdv.updateGadget("camera", {
|
|
"3d": cmd.camera,
|
|
}, cmd.delay || 0);
|
|
break;
|
|
case 'getCamera':
|
|
var resp = {
|
|
type: "camera",
|
|
cameraId: cmd.cameraId,
|
|
}
|
|
if (threeCtx) {
|
|
resp.camera = {
|
|
x: threeCtx.camera.position.x / SCALE3D,
|
|
y: threeCtx.camera.position.z / SCALE3D,
|
|
z: threeCtx.camera.position.y / SCALE3D,
|
|
targetX: threeCtx.cameraControls.camTarget.x / SCALE3D,
|
|
targetY: threeCtx.cameraControls.camTarget.z / SCALE3D,
|
|
targetZ: threeCtx.cameraControls.camTarget.y / SCALE3D,
|
|
}
|
|
} else {
|
|
console.warn("cannot get camera without 3D context");
|
|
resp.camera = null;
|
|
}
|
|
scope.sendEmbed(resp);
|
|
break;
|
|
case 'snapshot':
|
|
var resp = {
|
|
type: "snapshot",
|
|
snapshotId: cmd.snapshotId,
|
|
}
|
|
if (threeCtx) {
|
|
var renderer = threeCtx.renderer;
|
|
var canvas = renderer.domElement;
|
|
renderer.render(threeCtx.scene, threeCtx.camera);
|
|
resp.image = canvas.toDataURL("image/png");
|
|
} else {
|
|
console.warn("cannot get snapshot without 3D context");
|
|
resp.image = null;
|
|
}
|
|
scope.sendEmbed(resp);
|
|
break;
|
|
}
|
|
}
|
|
|
|
View.Game.ViewControl = function (cmd, options) {
|
|
options = options || {};
|
|
var promise = new Promise(function (resolve, reject) {
|
|
switch (cmd) {
|
|
case "enterAnaglyph":
|
|
if (threeCtx) {
|
|
threeCtx.anaglyph = true;
|
|
var factor = 2.5;
|
|
threeCtx.scene.scale.set(1 / factor, 1 / factor, 1 / factor);
|
|
threeCtx.camera.scale.set(factor, factor, factor);
|
|
threeCtx.animControl.trigger();
|
|
};
|
|
resolve();
|
|
break;
|
|
|
|
case "exitAnaglyph":
|
|
if (threeCtx) {
|
|
threeCtx.anaglyph = false;
|
|
threeCtx.scene.scale.set(1, 1, 1);
|
|
threeCtx.camera.scale.set(1, 1, 1);
|
|
threeCtx.animControl.trigger();
|
|
};
|
|
resolve();
|
|
break;
|
|
|
|
case "stopAnimations":
|
|
var animCount = 0;
|
|
var toBeDeleted = [];
|
|
TWEEN.getAll().forEach(function(tween) {
|
|
animCount++;
|
|
if(tween !== threeCtx.dolly)
|
|
toBeDeleted.push(tween);
|
|
});
|
|
toBeDeleted.forEach(function(tween) {
|
|
TWEEN.remove(tween);
|
|
});
|
|
resolve(animCount > 0);
|
|
break;
|
|
|
|
case "setPanorama":
|
|
if (options.pictureUrl || options.pictureData) {
|
|
xdv.removeGadget("panorama");
|
|
xdv.createGadget("panorama", {
|
|
"3d": {
|
|
type: "custommesh3d",
|
|
harbor: false,
|
|
rotate: options.rotate || 0,
|
|
create: function (callback) {
|
|
var geometry = new THREE.SphereGeometry(500, 60, 40);
|
|
geometry.scale(- 1, 1, 1);
|
|
new Promise(function (resolve, reject) {
|
|
if (options.pictureData) {
|
|
var image = new Image;
|
|
image.src = options.pictureData;
|
|
var texture = new THREE.Texture(image);
|
|
image.onload = function () {
|
|
texture.needsUpdate = true;
|
|
resolve(texture);
|
|
}
|
|
} else
|
|
resolve(new THREE.TextureLoader().load(options.pictureUrl))
|
|
}).then(function (texture) {
|
|
var material = new THREE.MeshBasicMaterial({
|
|
map: texture
|
|
});
|
|
mesh = new THREE.Mesh(geometry, material);
|
|
callback(mesh);
|
|
})
|
|
},
|
|
}
|
|
});
|
|
xdv.updateGadget("panorama", {
|
|
"3d": {
|
|
visible: true
|
|
},
|
|
});
|
|
} else {
|
|
xdv.updateGadget("panorama", {
|
|
"3d": {
|
|
visible: false
|
|
},
|
|
});
|
|
xdv.removeGadget("panorama");
|
|
}
|
|
resolve();
|
|
break;
|
|
|
|
case "takeSnapshot":
|
|
if (threeCtx) {
|
|
var canvas = threeCtx.renderer.domElement;
|
|
threeCtx.renderer.render(threeCtx.scene, threeCtx.camera);
|
|
resolve(canvas.toDataURL("image/" + (options.format || "png"), options.quality || undefined));
|
|
} else
|
|
reject(new Error("Snapshot only available on 3D views"));
|
|
break;
|
|
|
|
case "getCamera":
|
|
if (threeCtx)
|
|
resolve({
|
|
x: threeCtx.body.position.x / SCALE3D,
|
|
y: threeCtx.body.position.z / SCALE3D,
|
|
z: threeCtx.body.position.y / SCALE3D,
|
|
targetX: threeCtx.cameraControls.camTarget.x / SCALE3D,
|
|
targetY: threeCtx.cameraControls.camTarget.z / SCALE3D,
|
|
targetZ: threeCtx.cameraControls.camTarget.y / SCALE3D
|
|
});
|
|
else
|
|
reject(new Error("Camera only available on 3D views"));
|
|
break;
|
|
|
|
case 'setCamera':
|
|
if(!threeCtx)
|
|
return reject(new Error("Camera only available on 3D views"));
|
|
|
|
switch(options.type) {
|
|
case "spin":
|
|
resolve(SpinCamera(options));
|
|
break;
|
|
case "stop":
|
|
if(threeCtx.dolly) {
|
|
delete threeCtx.animateCallbacks["dolly"];
|
|
TWEEN.remove(threeCtx.dolly);
|
|
delete threeCtx.dolly;
|
|
threeCtx.animControl.stop(0);
|
|
}
|
|
break;
|
|
case "move":
|
|
default:
|
|
resolve(MoveCamera(options));
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
default:
|
|
reject(new Error("ViewControl: unsupported command " + cmd));
|
|
}
|
|
});
|
|
return promise;
|
|
}
|
|
|
|
function SpinCamera(options) {
|
|
function GetKalman() {
|
|
var R = .001;
|
|
if(typeof options.smooth!="undefined")
|
|
R = options.smooth;
|
|
return new KalmanFilter({R: R});
|
|
}
|
|
var kalman = {
|
|
x: GetKalman(),
|
|
y: GetKalman(),
|
|
}
|
|
var x0 = threeCtx.cameraControls.camTarget.x;
|
|
var y0 = threeCtx.cameraControls.camTarget.z;
|
|
var x1 = threeCtx.body.position.x;
|
|
var y1 = threeCtx.body.position.z;
|
|
var angle0 = Math.atan2(y1-y0,x1-x0);
|
|
var angle1 = angle0 - 2 * Math.PI;
|
|
if(options.direction=="ccw")
|
|
angle1 = angle0 + 2 * Math.PI;
|
|
var radius = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0));
|
|
if(threeCtx.dolly)
|
|
TWEEN.remove(threeCtx.dolly);
|
|
var state = {};
|
|
function StartSpinning() {
|
|
state.angle = angle0;
|
|
threeCtx.dolly = new TWEEN.Tween(state).to({ angle: angle1 }, (options.speed || 30) * 1000)
|
|
.onComplete( function() {
|
|
StartSpinning();
|
|
})
|
|
.onUpdate( function() {
|
|
threeCtx.animControl.trigger();
|
|
}).start();
|
|
}
|
|
threeCtx.animateCallbacks["dolly"] = {
|
|
_this: null,
|
|
callback: function() {
|
|
threeCtx.body.position.x = kalman.x.filter(x0 + radius * Math.cos(state.angle));
|
|
threeCtx.body.position.z = kalman.y.filter(y0 + radius * Math.sin(state.angle));
|
|
}
|
|
};
|
|
StartSpinning();
|
|
threeCtx.animControl.trigger();
|
|
|
|
}
|
|
|
|
function MoveCamera(options) {
|
|
function GetKalman() {
|
|
var R = .001;
|
|
if(typeof options.smooth!="undefined")
|
|
R = options.smooth;
|
|
return new KalmanFilter({R: R});
|
|
}
|
|
var kalman = {
|
|
x: GetKalman(),
|
|
y: GetKalman(),
|
|
z: GetKalman(),
|
|
targetX: GetKalman(),
|
|
targetY: GetKalman(),
|
|
targetZ: GetKalman()
|
|
}
|
|
var state = {
|
|
x: threeCtx.body.position.x,
|
|
y: threeCtx.body.position.y,
|
|
z: threeCtx.body.position.z,
|
|
targetX: threeCtx.cameraControls.camTarget.x,
|
|
targetY: threeCtx.cameraControls.camTarget.y,
|
|
targetZ: threeCtx.cameraControls.camTarget.z
|
|
}
|
|
var state1 = {
|
|
x: options.camera.x * SCALE3D,
|
|
z: options.camera.y * SCALE3D,
|
|
y: options.camera.z * SCALE3D,
|
|
targetX: options.camera.targetX * SCALE3D,
|
|
targetZ: options.camera.targetY * SCALE3D,
|
|
targetY: options.camera.targetZ * SCALE3D
|
|
}
|
|
var finalCamera = new THREE.Vector3(state1.x, state1.y, state1.z);
|
|
var finalTarget = new THREE.Vector3(state1.targetX, state1.targetY, state1.targetZ);
|
|
if(threeCtx.dolly)
|
|
TWEEN.remove(threeCtx.dolly);
|
|
threeCtx.dolly = new TWEEN.Tween(state).to(state1, options.speed * 1000)
|
|
.onUpdate( function() {
|
|
threeCtx.animControl.trigger();
|
|
}).start();
|
|
threeCtx.animateCallbacks["dolly"] = {
|
|
_this: null,
|
|
callback: function() {
|
|
threeCtx.body.position.x = kalman.x.filter(state.x);
|
|
threeCtx.body.position.y = kalman.y.filter(state.y);
|
|
threeCtx.body.position.z = kalman.z.filter(state.z);
|
|
threeCtx.cameraControls.camTarget.x = kalman.targetX.filter(state.targetX);
|
|
threeCtx.cameraControls.camTarget.y = kalman.targetY.filter(state.targetY);
|
|
threeCtx.cameraControls.camTarget.z = kalman.targetZ.filter(state.targetZ);
|
|
var cameraVec = new THREE.Vector3(
|
|
threeCtx.body.position.x,
|
|
threeCtx.body.position.y,
|
|
threeCtx.body.position.z);
|
|
if(cameraVec.distanceTo(finalCamera)<.1) {
|
|
var targetVec = new THREE.Vector3(
|
|
threeCtx.cameraControls.camTarget.x,
|
|
threeCtx.cameraControls.camTarget.y,
|
|
threeCtx.cameraControls.camTarget.z);
|
|
if(targetVec.distanceTo(finalTarget)<.1) {
|
|
delete threeCtx.animateCallbacks["dolly"];
|
|
TWEEN.remove(threeCtx.dolly);
|
|
delete threeCtx.dolly;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
threeCtx.animControl.trigger();
|
|
}
|
|
|
|
View.Board.Display = function (aGame) {
|
|
//Log("### View.Board.Display");
|
|
this.xdDisplay(xdv, aGame);
|
|
//xdv.listScene();
|
|
}
|
|
|
|
View.Board.xdInput = function (xdv, aGame) {
|
|
console.error("View.Board.xdInput must be overriden");
|
|
return {
|
|
initial: {},
|
|
getActions: function (moves, currentInput) {
|
|
return {};
|
|
},
|
|
}
|
|
}
|
|
|
|
View.Board.xdBuildHTStateMachine = function (xdv, htsm, aGame) {
|
|
var $this = this;
|
|
var inputSpec;
|
|
var clickGadgets = {}, viewGadgets = {}, highlightGadgets = [];
|
|
var inputStack, movesStack, actionStack;
|
|
function Click(action, mode) {
|
|
if (mode == "select")
|
|
htsm.smQueueEvent("E_ACTION", { action: action });
|
|
else if (mode == "cancel")
|
|
htsm.smQueueEvent("E_CANCEL", { action: action });
|
|
}
|
|
function Init(args) {
|
|
inputSpec = $this.xdInput(xdv, aGame);
|
|
inputStack = [inputSpec.initial];
|
|
// ensures moves are not duplicated
|
|
var movesMap = {};
|
|
$this.mMoves.forEach(function (move) {
|
|
movesMap[JSON.stringify(move)] = move;
|
|
});
|
|
var moves = [];
|
|
for (var m in movesMap)
|
|
moves.push(movesMap[m]);
|
|
movesStack = [moves];
|
|
actionStack = [];
|
|
}
|
|
function ShowFurnitures(args) {
|
|
if (inputSpec.furnitures)
|
|
inputSpec.furnitures.forEach(function (gadget) {
|
|
xdv.updateGadget(gadget, {
|
|
base: {
|
|
visible: true,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
function HideFurnitures(args) {
|
|
if (inputSpec.furnitures)
|
|
inputSpec.furnitures.forEach(function (gadget) {
|
|
xdv.updateGadget(gadget, {
|
|
base: {
|
|
visible: false,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
function SetAction(action, mode) {
|
|
if (mode == "select") {
|
|
if (action.pre)
|
|
action.pre.call($this);
|
|
if (action.cancel)
|
|
action.cancel.forEach(function (gid) {
|
|
clickGadgets[gid] = true;
|
|
xdv.updateGadget(gid, {
|
|
base: {
|
|
click: function () {
|
|
Click(action, "cancel");
|
|
},
|
|
},
|
|
});
|
|
});
|
|
}
|
|
if (action.click)
|
|
action.click.forEach(function (gid) {
|
|
clickGadgets[gid] = true;
|
|
xdv.updateGadget(gid, {
|
|
base: {
|
|
click: function () {
|
|
Click(action, mode);
|
|
},
|
|
},
|
|
});
|
|
});
|
|
if (typeof action.highlight == "function") {
|
|
if (typeof action.unhighlight != "function")
|
|
console.warn("No unhighlight function defined for", action);
|
|
else
|
|
highlightGadgets.push(function () {
|
|
action.unhighlight.call($this, mode);
|
|
});
|
|
action.highlight.call($this, mode);
|
|
}
|
|
if (action.view)
|
|
action.view.forEach(function (gid) {
|
|
viewGadgets[gid] = true;
|
|
xdv.updateGadget(gid, {
|
|
base: {
|
|
visible: true,
|
|
},
|
|
})
|
|
});
|
|
}
|
|
function PrepareAction(args) {
|
|
var nextActions = inputSpec.getActions.call($this, movesStack[movesStack.length - 1], inputStack[inputStack.length - 1]);
|
|
if (nextActions == null) {
|
|
htsm.smQueueEvent("E_MOVE_DONE", { move: movesStack[movesStack.length - 1][0] });
|
|
return;
|
|
}
|
|
var actionsCount = 0;
|
|
var action0;
|
|
for (var action in nextActions) {
|
|
action0 = nextActions[action];
|
|
actionsCount++;
|
|
}
|
|
if (actionsCount > 1 || (inputStack.length == 1 && !inputSpec.allowForced) || (actionsCount == 1 && !aGame.mAutoComplete && !action0.skipable)) {
|
|
for (var actId in nextActions) {
|
|
var action = nextActions[actId];
|
|
action.forced = false;
|
|
SetAction(action, "select");
|
|
}
|
|
} else if (actionsCount == 0) {
|
|
htsm.smQueueEvent("E_MOVE_DONE", { move: actionStack[actionStack.length - 1].moves[0] });
|
|
} else {
|
|
action0.forced = true;
|
|
htsm.smQueueEvent("E_ACTION", { action: action0 });
|
|
}
|
|
}
|
|
function SendMove(args) {
|
|
aGame.HumanMove(args.move);
|
|
}
|
|
function Clean(args) {
|
|
for (var gid in clickGadgets)
|
|
xdv.updateGadget(gid, {
|
|
base: {
|
|
click: null,
|
|
},
|
|
});
|
|
clickGadgets = {};
|
|
for (var gid in viewGadgets)
|
|
xdv.updateGadget(gid, {
|
|
base: {
|
|
visible: false,
|
|
},
|
|
});
|
|
viewGadgets = {};
|
|
for (var i = 0; i < highlightGadgets.length; i++)
|
|
highlightGadgets[i].call($this);
|
|
}
|
|
function Execute(action, callback) {
|
|
if (action.execute) {
|
|
var actions = action.execute;
|
|
if (typeof actions == "function")
|
|
actions = [actions];
|
|
var actionsCount = 0;
|
|
function ActionDone(action) {
|
|
if (--actionsCount == 0)
|
|
callback();
|
|
}
|
|
actions.forEach(function (action) {
|
|
actionsCount++;
|
|
setTimeout(function () {
|
|
action.call($this, ActionDone);
|
|
}, 0);
|
|
});
|
|
} else
|
|
callback();
|
|
}
|
|
function Action(args) {
|
|
movesStack.push(args.action.moves);
|
|
Execute(args.action, function () {
|
|
htsm.smQueueEvent("E_DONE", { action: args.action });
|
|
});
|
|
}
|
|
function PostAction(args) {
|
|
if (args.action.post)
|
|
args.action.post.call($this);
|
|
}
|
|
function SetCancel(args) {
|
|
if (actionStack.length > 0 && !actionStack[actionStack.length - 1].noAutoCancel)
|
|
SetAction(actionStack[actionStack.length - 1], "cancel");
|
|
}
|
|
function Validate(args) {
|
|
inputStack.push($.extend(true, {}, inputStack[inputStack.length - 1], args.action.validate));
|
|
}
|
|
function Cancel(args) {
|
|
while (actionStack.length > 0) {
|
|
var action = actionStack.pop();
|
|
inputStack.pop();
|
|
movesStack.pop();
|
|
if (action.unexecute)
|
|
action.unexecute.call($this);
|
|
if (action.post)
|
|
action.post.call($this);
|
|
if (action.forced == false)
|
|
break;
|
|
}
|
|
}
|
|
function PushAction(args) {
|
|
actionStack.push(args.action);
|
|
}
|
|
htsm.smTransition("S_INIT", "E_INIT", "S_WAIT_ACTION", [Init, ShowFurnitures]);
|
|
htsm.smEntering("S_WAIT_ACTION", [PrepareAction, SetCancel]);
|
|
htsm.smLeaving("S_WAIT_ACTION", [Clean]);
|
|
htsm.smTransition("S_WAIT_ACTION", "E_ACTION", "S_ACTION", [PushAction, Validate, Action]);
|
|
htsm.smTransition("S_WAIT_ACTION", "E_CANCEL", null, [Cancel, Clean, PrepareAction, SetCancel]);
|
|
htsm.smTransition("S_WAIT_ACTION", "E_MOVE_DONE", "S_DONE", [SendMove, HideFurnitures]);
|
|
htsm.smTransition(["S_WAIT_ACTION", "S_ACTION"], "E_END", "S_DONE", []);
|
|
htsm.smTransition("S_ACTION", "E_DONE", "S_WAIT_ACTION", [PostAction]);
|
|
htsm.smTransition("S_DONE", "E_END", null, [HideFurnitures]);
|
|
}
|
|
|
|
View.Board.HumanTurn = function (aGame) {
|
|
//Log("### View.Board.HumanTurn");
|
|
var $this = this;
|
|
htStateMachine = new HTStateMachine();
|
|
htStateMachine.init();
|
|
this.xdBuildHTStateMachine(xdv, htStateMachine, aGame);
|
|
htStateMachine.smSetInitialState("S_INIT");
|
|
htStateMachine.smQueueEvent("E_INIT", {});
|
|
htStateMachine.smPlay();
|
|
}
|
|
|
|
View.Board.HumanTurnEnd = function (aGame) {
|
|
//Log("### View.Board.HumanTurnEnd");
|
|
if (htStateMachine) {
|
|
htStateMachine.smQueueEvent("E_END", {});
|
|
htStateMachine = null;
|
|
}
|
|
}
|
|
|
|
View.Board.PlayedMove = function (aGame, aMove) {
|
|
//Log("### View.Board.PlayedMove");
|
|
return this.xdPlayedMove(xdv, aGame, aMove);
|
|
}
|
|
|
|
View.Board.xdShowEnd = function (xdv, aGame) {
|
|
return true;
|
|
}
|
|
|
|
View.Board.ShowEnd = function (aGame) {
|
|
return this.xdShowEnd(xdv, aGame);
|
|
}
|
|
|
|
/* ======================================== */
|
|
|
|
var THREEx_boundContext = "" + Math.random();
|
|
|
|
function BuildThree(aGame, areaWidth, areaHeight) {
|
|
|
|
var camera = new THREE.PerspectiveCamera(55, (area.width() / area.height()), 1, 4000);
|
|
|
|
var scene = new THREE.Scene();
|
|
|
|
var body = new THREE.Object3D();
|
|
scene.add(body);
|
|
body.add(camera);
|
|
|
|
var harbor = new THREE.Object3D();
|
|
scene.add(harbor);
|
|
|
|
var ambientLight = new THREE.AmbientLight(0xbbbbbb);
|
|
harbor.add(ambientLight);
|
|
|
|
var light = new THREE.SpotLight(0xffffff, 1.75, 0, 1.05, 1, 2); // test params here https://threejs.org/docs/?q=SpotLight#Reference/Lights/SpotLight
|
|
light.position.set(-12, 12, 12);
|
|
|
|
light.castShadow = true;
|
|
//light.shadowDarkness = .75;
|
|
|
|
light.shadow.camera.near = 1;
|
|
light.shadow.camera.far = 27;
|
|
light.shadow.camera.fov = 90;
|
|
|
|
light.shadow.mapSize.width = 4096;
|
|
light.shadow.mapSize.height = 4096;
|
|
|
|
light.target = harbor;
|
|
|
|
harbor.add(light);
|
|
|
|
var skylight = new THREE.PointLight(0xcccccc, 2, 150);//, Math.PI/5, 10);
|
|
skylight.position.set(-45, 45, 45);
|
|
harbor.add(skylight);
|
|
|
|
//light.shadowCameraVisible = false;
|
|
// skylight.shadowCameraVisible = true; nonsens! PointLight objects don't have shadow feature
|
|
|
|
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
renderer.setSize(area.width(), area.height());
|
|
//renderer.setClearColor( scene.fog.color, 1 );
|
|
|
|
var projector = new THREE.Projector();
|
|
|
|
area.append($(renderer.domElement));
|
|
|
|
renderer.gammaInput = true;
|
|
renderer.gammaOutput = true;
|
|
//renderer.shadowMapEnabled = true;
|
|
renderer.shadowMap.enabled = true;
|
|
renderer.shadowMapSoft = true;
|
|
//renderer.physicallyBasedShading = true; // gives high level of shininess specular
|
|
//renderer.shadowMapCascade = true;
|
|
|
|
var stereo = false;
|
|
var stereoEffect = new THREE.StereoEffect(renderer);
|
|
stereoEffect.setSize(area.width(), area.height());
|
|
|
|
var anaglyphEffect = new THREE.AnaglyphEffect(renderer);
|
|
anaglyphEffect.setSize(area.width(), area.height());
|
|
|
|
var gamepads = new VRGamepads({
|
|
camera: camera,
|
|
scene: scene,
|
|
resBase: aGame.config.baseURL + "res/vr/",
|
|
drag: function (position, direction, pointerObject, pointerRescale) {
|
|
var intersectPoint = null;
|
|
var pointedObject = null;
|
|
VRGetIntersect(position, direction, function (object, point) {
|
|
intersectPoint = point;
|
|
pointedObject = object;
|
|
});
|
|
return intersectPoint ? {
|
|
point: intersectPoint,
|
|
object: pointedObject
|
|
} : null;
|
|
},
|
|
click: function (position, direction) {
|
|
VRGetIntersect(position, direction, function (object, point) {
|
|
if (object)
|
|
THREE.Object3D._threexDomEvent._notify("mouseup", object, null, point);
|
|
});
|
|
},
|
|
move: function (step) {
|
|
body.position.add(step);
|
|
}
|
|
});
|
|
|
|
var vrRay = new THREE.Raycaster();
|
|
|
|
var camAnim = !!aGame.mViewOptions.camAnim;
|
|
|
|
var animateCallbacks = {};
|
|
|
|
var frameBacklog = 0;
|
|
|
|
function AnimControl() {
|
|
this.animating = false;
|
|
this.animateTimer = null;
|
|
this.nextStop = 0;
|
|
}
|
|
AnimControl.prototype = {
|
|
start: function () {
|
|
body.updateMatrixWorld();
|
|
if (this.animateTimer != null) {
|
|
clearTimeout(this.animateTimer);
|
|
this.animateTimer = null;
|
|
}
|
|
if (this.animating == false) {
|
|
this.animating = true;
|
|
this.animate();
|
|
}
|
|
},
|
|
stop: function (delay) {
|
|
if (threeCtx && vr.vrEffect && vr.vrEffect.isPresenting) {
|
|
if (this.animateTimer != null) {
|
|
clearTimeout(this.animateTimer);
|
|
this.animateTimer = null;
|
|
}
|
|
return;
|
|
}
|
|
if (delay === undefined)
|
|
delay = 200;
|
|
var now = Date.now();
|
|
var $this = this;
|
|
if (this.animating) {
|
|
if (this.animateTimer != null) {
|
|
if (this.nextStop < now + delay)
|
|
clearTimeout(this.animateTimer);
|
|
else
|
|
return;
|
|
}
|
|
this.nextStop = Math.max(this.nextStop, now + delay);
|
|
this.animateTimer = setTimeout(function () {
|
|
$this.animateTimer = null;
|
|
$this.animating = false;
|
|
}, this.nextStop - now);
|
|
}
|
|
},
|
|
trigger: function () {
|
|
if (!this.animating || this.animateTimer != null) {
|
|
this.start();
|
|
this.stop.apply(this, arguments);
|
|
}
|
|
},
|
|
animate: function () {
|
|
var $this = this;
|
|
var statsCurrentSec = 0;
|
|
var statsTic = 0;
|
|
var renderSum = 0;
|
|
var renderCount = 0;
|
|
|
|
function Animate(timestamp) {
|
|
frameBacklog--;
|
|
var t0, t1;
|
|
var showStats = false;
|
|
if (showStats) {
|
|
var sec = Math.floor(Date.now() / 1000);
|
|
if (sec == statsCurrentSec)
|
|
statsTic++;
|
|
else {
|
|
if (statsTic > 0) {
|
|
var rate = Math.round(1000 * renderSum / renderCount) / 1000;
|
|
var lag = Math.round(1000 * (window.performance.now() - timestamp)) / 1000;
|
|
/*
|
|
console.log("fps",statsTic,"render",rate,"ms","",
|
|
"lag",lag,"ms","",
|
|
"frame backlog",frameBacklog);
|
|
*/
|
|
$(statsPanel).text("fps " + statsTic);
|
|
}
|
|
statsTic = 1;
|
|
statsCurrentSec = sec;
|
|
}
|
|
}
|
|
if ($this.animating) {
|
|
frameBacklog++;
|
|
requestAnimationFrame(Animate);
|
|
}
|
|
TWEEN.update();
|
|
if (showStats)
|
|
t0 = Date.now();
|
|
if (vr.vrEffect && vr.vrEffect.isPresenting) {
|
|
gamepads.update();
|
|
var harborpad = gamepads.getHarborPad();
|
|
if (harborpad) {
|
|
harborpad.visible = false;
|
|
harborpad.getWorldPosition(ctx.harbor.position);
|
|
var scale = (harborpad.getAxes()[1] + 1.1) * .03;
|
|
ctx.harbor.scale.set(scale, scale, scale);
|
|
harborpad.getWorldQuaternion(ctx.harbor.quaternion);
|
|
} else {
|
|
ctx.harbor.position.set(0, 0, 0);
|
|
ctx.harbor.scale.set(1, 1, 1);
|
|
ctx.harbor.quaternion.copy(ctx.defaultHarborQuaternion);
|
|
}
|
|
vr.vrControls.update();
|
|
vr.vrEffect.render(scene, camera);
|
|
} else {
|
|
if (!arStream) {
|
|
ctx.harbor.position.set(0, 0, 0);
|
|
ctx.harbor.scale.set(1, 1, 1);
|
|
ctx.harbor.quaternion.copy(ctx.defaultHarborQuaternion);
|
|
}
|
|
/*
|
|
if(gamepads)
|
|
gamepads.clearAll();
|
|
*/
|
|
if (!arStream) {
|
|
cameraControls.update();
|
|
cameraOrientationControls.update();
|
|
}
|
|
if (stereo) {
|
|
gamepads.update();
|
|
stereoEffect.render(scene, camera);
|
|
} else if (ctx.anaglyph || aGame.mAnaglyph)
|
|
anaglyphEffect.render(scene, camera);
|
|
else
|
|
renderer.render(scene, camera);
|
|
}
|
|
if (showStats) {
|
|
t1 = Date.now();
|
|
renderSum += t1 - t0;
|
|
renderCount++;
|
|
}
|
|
|
|
for (var cbi in animateCallbacks) {
|
|
var cb = animateCallbacks[cbi];
|
|
cb.callback.call(cb._this);
|
|
}
|
|
}
|
|
frameBacklog++;
|
|
Animate(window.performance.now());
|
|
},
|
|
}
|
|
var animControl = new AnimControl();
|
|
|
|
var statsPanel = null;
|
|
|
|
var cameraControls = new THREE.OrbitControls(camera, body, renderer.domElement);
|
|
|
|
$.extend(cameraControls, {
|
|
autoRotate: camAnim,
|
|
animControl: animControl,
|
|
});
|
|
cameraControls.camTarget.set(0, 0.8, 0);
|
|
|
|
var canOrientation = false;
|
|
var cameraOrientationControls = new THREE.DeviceOrientationControls(body, function (controls) {
|
|
if (typeof vr != "undefined")
|
|
animControl.trigger();
|
|
if (!canOrientation && controls.enabled) {
|
|
canOrientation = true;
|
|
area.find(".vr-button").show();
|
|
}
|
|
});
|
|
|
|
|
|
if (typeof cameraControls.addEventListener == "function")
|
|
cameraControls.addEventListener('change', function () {
|
|
animControl.trigger();
|
|
});
|
|
|
|
var ctx = {
|
|
scene: scene,
|
|
renderer: renderer,
|
|
light: light,
|
|
skyLight: skylight,
|
|
ambientLight: ambientLight,
|
|
loader: new THREE.JSONLoader(),
|
|
camera: camera,
|
|
cameraControls: cameraControls,
|
|
animateCallbacks: animateCallbacks,
|
|
camTarget: cameraControls.camTarget,
|
|
animControl: animControl,
|
|
body: body,
|
|
harbor: harbor,
|
|
defaultHarborQuaternion: harbor.quaternion.clone(),
|
|
anaglyphEffect: anaglyphEffect,
|
|
anaglyph: false
|
|
};
|
|
|
|
function VRGetIntersect(position, direction, callback) {
|
|
var threexDomEvent = THREE.Object3D._threexDomEvent;
|
|
vrRay.set(position, direction);
|
|
try {
|
|
var intersects = vrRay.intersectObjects(threexDomEvent._boundObjs[threexDomEvent._boundContext]);
|
|
} catch (e) {
|
|
return callback(null, null);
|
|
}
|
|
if (intersects.length == 0)
|
|
return callback(null, null);
|
|
var intersect = intersects[0];
|
|
var object3d = threexDomEvent.getRootObject(intersect.object);
|
|
var objectCtx = threexDomEvent._objectCtxGet(object3d);
|
|
if (!objectCtx)
|
|
callback(null, null);
|
|
else
|
|
callback(object3d, intersect.point);
|
|
}
|
|
|
|
function VRSetup(ctx) {
|
|
|
|
function LookAtHarbor() {
|
|
vr.vrControls.resetPose();
|
|
}
|
|
|
|
function MakeButton() {
|
|
ctx.vrButton = document.createElement("img");
|
|
ctx.vrButton.className = "vr-button";
|
|
ctx.vrButton.setAttribute("data-vr-enter-src", aGame.config.baseURL + "res/vr/vr-enter.png");
|
|
ctx.vrButton.setAttribute("data-vr-exit-src", aGame.config.baseURL + "res/vr/vr-exit.png");
|
|
ctx.vrButton.setAttribute("src", ctx.vrButton.getAttribute("data-vr-enter-src"));
|
|
Object.assign(ctx.vrButton.style, {
|
|
position: "absolute",
|
|
bottom: "8px",
|
|
right: "8px",
|
|
cursor: "pointer",
|
|
"z-index": 2147483647
|
|
});
|
|
area[0].appendChild(ctx.vrButton);
|
|
}
|
|
|
|
function CardboardVR() {
|
|
MakeButton();
|
|
ctx.vrButton.style.display = "none";
|
|
ctx.vrButton.addEventListener("click", function () {
|
|
if (stereo) {
|
|
stereo = false;
|
|
ctx.vrButton.setAttribute("src", ctx.vrButton.getAttribute("data-vr-enter-src"));
|
|
var size = renderer.getSize();
|
|
renderer.setViewport(0, 0, size.width, size.height);
|
|
} else {
|
|
stereo = true;
|
|
ctx.vrButton.setAttribute("src", ctx.vrButton.getAttribute("data-vr-exit-src"));
|
|
}
|
|
animControl.trigger();
|
|
});
|
|
}
|
|
|
|
function PureVR() {
|
|
MakeButton();
|
|
var vrControls = new THREE.VRControls(ctx.camera);
|
|
vr.vrControls = vrControls;
|
|
if (window.lastVrEffect) {
|
|
if (window.lastVrEffect.isPresenting)
|
|
window.lastVrEffect.exitPresent();
|
|
}
|
|
var vrEffect = new THREE.VREffect(ctx.renderer);
|
|
vr.vrEffect = vrEffect;
|
|
window.lastVrEffect = vrEffect;
|
|
|
|
window.addEventListener('vrdisplaypresentchange', function (event) {
|
|
ctx.animControl.trigger()
|
|
}, false);
|
|
|
|
ctx.vrButton.addEventListener("click", function () {
|
|
if (vrEffect.isPresenting) {
|
|
vrEffect.exitPresent();
|
|
ctx.vrButton.setAttribute("src", ctx.vrButton.getAttribute("data-vr-enter-src"));
|
|
} else {
|
|
vrEffect.requestPresent();
|
|
ctx.vrButton.setAttribute("src", ctx.vrButton.getAttribute("data-vr-exit-src"));
|
|
LookAtHarbor();
|
|
}
|
|
animControl.trigger();
|
|
});
|
|
|
|
}
|
|
|
|
vr = {};
|
|
|
|
if (typeof navigator.getVRDisplays != "undefined") {
|
|
navigator.getVRDisplays()
|
|
.then(function (displays) {
|
|
if (displays.length == 0)
|
|
CardboardVR();
|
|
else
|
|
PureVR();
|
|
}).catch(function () {
|
|
CardboardVR();
|
|
});
|
|
} else
|
|
CardboardVR();
|
|
|
|
return vr;
|
|
}
|
|
|
|
var vr = VRSetup(ctx);
|
|
|
|
return $.extend(ctx, vr);
|
|
}
|
|
|
|
|
|
function GetEventPosition(event) {
|
|
if (event.originalEvent)
|
|
return GetEventPosition(event.originalEvent);
|
|
if (event.changedTouches && event.changedTouches.length > 0)
|
|
return [event.changedTouches[0].pageX, event.changedTouches[0].pageY];
|
|
if (event.touches && event.touches.length > 0)
|
|
return [event.touches[0].pageX, event.touches[0].pageY];
|
|
return [event.pageX, event.pageY];
|
|
}
|
|
|
|
function AR(stream) {
|
|
if (!!arStream == !!stream) {
|
|
console.warn("AR is already", !!stream);
|
|
return;
|
|
}
|
|
arStream = stream;
|
|
if (arStream) {
|
|
var video = $("<video/>").addClass("ar-video").attr("autoplay", "autoplay").css({
|
|
position: "absolute",
|
|
top: 0,
|
|
width: "100%",
|
|
height: "100%",
|
|
left: 0,
|
|
"z-index": -1,
|
|
backgroundColor: "#0f0",
|
|
objectFit: "cover"
|
|
}).appendTo(area.parent());
|
|
JoclyAR.attach({
|
|
element: video[0],
|
|
stream: arStream,
|
|
threeCtx: threeCtx
|
|
});
|
|
xdv.redisplayGadgets();
|
|
threeCtx.renderer.setClearColor(new THREE.Color(threeCtx.world.color), 0);
|
|
threeCtx.animControl.trigger();
|
|
} else {
|
|
var video = area.parent().find(".ar-video");
|
|
if (video.length) {
|
|
JoclyAR.detach({
|
|
element: video[0]
|
|
});
|
|
video.remove();
|
|
}
|
|
xdv.redisplayGadgets();
|
|
threeCtx.renderer.setClearColor(new THREE.Color(threeCtx.world.color), 1);
|
|
threeCtx.animControl.trigger();
|
|
}
|
|
}
|
|
|
|
})();
|