define(["require", "./Constants", "./EngineSingleton"], function (require, Constants, EngineSingleton) {
"use strict";
var ASSERT = Constants._ASSERT,
DEBUG = Constants._DEBUG,
packIntToFloatArrayBuffer = new ArrayBuffer(4),
packIntToFloatInt32Buffer = new Uint32Array(packIntToFloatArrayBuffer),
packIntToFloatUint8Buffer = new Uint8Array(packIntToFloatArrayBuffer),
Util;
/**
* Utility class for miscellaneous functions. The class is static and is shared between multiple instances.
* @class Util
* @namespace kick.core
*/
Util = {
/**
* Used for deserializing a configuration (replaces reference objects with actual references)
* @method deserializeConfig
* @param {Object} config
* @param {kick.scene.Scene} scene used for looking up references to gameObjects and components
*/
deserializeConfig: function (config, scene) {
var i,
engine = EngineSingleton.engine,
destArray,
ref,
reftype;
if (typeof config === 'number') {
return config;
}
if (Array.isArray(config)) {
destArray = new Array(config.length);
for (i = 0; i < config.length; i++) {
destArray[i] = Util.deserializeConfig(config[i], scene);
}
config = destArray;
} else if (config) {
ref = config.ref;
reftype = config.reftype;
if (reftype) {
if (reftype === "project") {
config = engine.project.load(ref);
} else if (reftype === "gameobject" || reftype === "component") {
config = scene.getObjectByUID(ref);
} else if (DEBUG) {
Util.warn("Unsupported ref type. Expected 'project', 'gameobject' or 'component'");
}
if (DEBUG && !config){
Util.warn("Unable to find "+reftype+" ref "+ref);
}
}
}
return config;
},
/**
* @method deepCopy
* @param {Object} src
* @param {Array_Classes} passthroughClasses=null Don't attempt to clone object of these classes (uses instanceof operator)
* @return Object
*/
deepCopy : function (object, passthroughClasses) {
var res,
isPassthrough = false,
i,
typeOfValue,
name;
passthroughClasses = passthroughClasses || [];
for (i = 0; i < passthroughClasses.length; i++) {
if (object instanceof passthroughClasses[i]) {
isPassthrough = true;
break;
}
}
typeOfValue = typeof object;
if (isPassthrough) {
res = object;
} else if (object === null || typeof (object) === "undefined") {
res = null;
} else if (Array.isArray(object) || object.buffer instanceof ArrayBuffer) { // treat typed arrays as normal arrays
res = [];
for (i = 0; i < object.length; i++) {
res[i] = Util.deepCopy(object[i], passthroughClasses);
}
} else if (typeOfValue === "object") {
res = {};
for (name in object) {
if (object.hasOwnProperty(name)) {
res[name] = Util.deepCopy(object[name], passthroughClasses);
}
}
} else {
res = object;
}
return res;
},
/**
* @method copyStaticPropertiesToObject
* @param {Object} object
* @param {Function} type constructor function
* @static
*/
copyStaticPropertiesToObject : function (object, type) {
var name;
for (name in type) {
if (type.hasOwnProperty(name)) {
Object.defineProperty(object, name, {
value: type[name]
});
}
}
},
/**
* @method hasProperty
* @param {Object} obj
* @param {String} prop
* @return {Boolean}
* @static
*/
hasProperty: function (obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
},
/**
*
* @method toCamelCase
* @param {String} str
* @param {String} [wordSeparator=""]
*/
toCamelCase: function (str, wordSeparator) {
if (!wordSeparator) {
wordSeparator = "";
}
// skip initial underscore
var i,
wasLastCharSpace = true,
charVal,
resStr = "",
isSpace;
for (i = 0; i < str.length; i++) {
charVal = str.charAt(i);
if (charVal !== "_") {
break;
}
resStr += charVal;
}
for (; i < str.length; i++) {
charVal = str.charAt(i);
isSpace = charVal === '_';
if (isSpace) {
charVal = wordSeparator;
}
resStr += wasLastCharSpace ? charVal.toUpperCase() : charVal.toLowerCase();
wasLastCharSpace = isSpace;
}
return resStr;
},
/**
* @method getJSONReference
* @param {Object} object
* @return {JSON}
*/
getJSONReference: function (object) {
var engine = EngineSingleton.engine;
if (object === null){
return null;
}
var isGameObject = object instanceof require("kick/scene/GameObject");
var isComponent = !isGameObject && object.gameObject instanceof require("kick/scene/GameObject");
if (isComponent || isGameObject) {
return {
ref: engine.getUID(object),
name: typeof object.name === 'string'? object.name : "",
reftype: isGameObject?"gameobject":"component"
};
} else {
// project type
return {
ref: object.uid,
name: object.name,
reftype: "project"
};
}
},
/**
* @method componentToJSON
* @param {kick.scene.Component} component
* @param {String} componentType=component.constructor.name
* @return {JSON}
*/
componentToJSON: function(component, componentType) {
var name,
engine = EngineSingleton.engine,
config = {},
functionReturnType = {},
res = {
type: componentType || component.constructor.name,
uid: engine.getUID(component),
config:config
},
o,
serializedObject;
if (DEBUG){
if (component === EngineSingleton.engine) {
Util.fail("Util.componentToJSON parameters has changed to Util.componentToJSON(kick.scene.Component, String)");
}
}
if (res.type === "") {
Util.fail("Cannot serialize object type. Either provide toJSON function or use explicit function name 'function SomeObject(){}' ");
}
var serializeObject = function(o) {
var result, i, r, typeofO;
if (Array.isArray(o)) {
result = [];
for (i=0;i<o.length;i++) {
r = serializeObject(o[i]);
result.push(r);
}
return result;
}
typeofO = typeof o;
if (typeofO !== 'function') {
if (o && o.buffer instanceof ArrayBuffer) {
// is typed array
return Util.typedArrayToArray(o);
} else if (typeofO === 'object') {
return Util.getJSONReference(o);
} else {
return o;
}
}
return functionReturnType;
};
// init config object
for (name in component) {
if (Util.hasProperty(component,name) && name !== "gameObject") {
o = component[name];
serializedObject = serializeObject(o);
if (serializedObject !== functionReturnType) {
config[name] = serializedObject;
}
}
}
return res;
},
/**
* For each non function attribute in config, set the attribute on object.
* @method applyConfig
* @param {Object} object
* @param {Object} config
* @param {Array_String} excludeFilter
* @static
*/
applyConfig: function (object, config, excludeFilter) {
var contains = Util.contains,
hasProperty = Util.hasProperty;
config = config || {};
excludeFilter = excludeFilter || [];
for (var name in config) {
if (typeof config[name] !== 'function' && !contains(excludeFilter, name) && hasProperty(object,name)) {
object[name] = config[name];
}
}
// force setting uid
if (config.uid && config.uid !== object.uid) {
object.uid = config.uid;
}
},
/**
* Reads a parameter from a url string.
* @method getParameter
* @param {String} url
* @param {String} parameterName
* @return {String} parameter value or null if not found.
* @static
*/
getParameter: function (url, parameterName) {
var regexpStr = "[\\?&]" + parameterName + "=([^&#]*)",
regexp = new RegExp(regexpStr),
res = regexp.exec(url);
if( res === null ) {
return null;
} else {
return res[1];
}
},
/**
* Reads a int parameter from a url string.
* @method getParameterInt
* @param {String} url
* @param {String} parameterName
* @return {String} parameter value or null if not found.
* @static
*/
getParameterInt: function(url, parameterName, notFoundValue) {
var res = Util.getParameter(url,parameterName);
if (res === null) {
return notFoundValue;
} else {
return parseInt(res, 10);
}
},
/**
* Reads a float parameter from a url string.
* @method getParameterInt
* @param {String} url
* @param {String} parameterName
* @return {String} parameter value or null if not found.
* @static
*/
getParameterFloat: function(url, parameterName, notFoundValue) {
var res = Util.getParameter(url,parameterName);
if (res === null) {
return notFoundValue;
} else {
return parseFloat(res);
}
},
/**
* Scales the image by drawing the image on a canvas object.
* @method scaleImage
* @param {Image} imageObj
* @param {Number} newWidth
* @param {Number} newHeight
* @return {Canvas} return a Canvas object (acts as a image)
* @static
*/
scaleImage: function (imageObj, newWidth, newHeight) {
// from http://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences
var canvas = document.createElement("canvas"),
ctx;
canvas.width = newWidth;
canvas.height = newHeight;
ctx = canvas.getContext("2d");
ctx.drawImage(imageObj,
0, 0, imageObj.width, imageObj.height,
0, 0, canvas.width, canvas.height);
return canvas;
},
/**
* Converts a typed array to a number array
* @method typedArrayToArray
* @static
* @param {TypedArray} typedArray
* @return {Array_Number}
*/
typedArrayToArray: function (typedArray) {
var length = typedArray.length,
res = new Array(length),
i;
for (i = 0; i < length; i++){
res[i] = typedArray[i];
}
return res;
},
/**
* Remove one element from an array - either the first instance or all instances
* @method removeElementFromArray
* @static
* @param {Array} array
* @param {Object} removeValue value to be deleted
* @param {boolean} deleteAll deletaAll objects (or exit function after first deletion)
* @return {boolean} elementRemoved
*/
removeElementFromArray: function (array, removeValue, deleteAll) {
var elementRemoved = false,
i;
for(i = array.length-1; i >= 0; i--) {
if(array[i] === removeValue) {
elementRemoved = true;
array.splice(i, 1);
if (!deleteAll) {
break;
}
}
}
return elementRemoved;
},
/**
* Removes all values from one array in another array
* @method removeElementsFromArray
* @static
* @param array {Array}
* @param removeValues {Object} value to be deleted
*/
removeElementsFromArray: function (array, removeValues) {
var i,j;
for (i = array.length-1; i >= 0; i--) {
for (j = removeValues.length - 1; j >= 0; j--) {
if (array[i] === removeValues[j]) {
array.splice(i, 1);
}
}
}
},
/**
* Insert the element into a sorted array
* @static
* @method insertSorted
* @param {Object} element
* @param {Array} sortedArray
* @param {Function} [sortFunc=kick.core.Util.numberSortFunction] has the signature foo(obj1,obj2) returns Number.
*/
insertSorted : function (element,sortedArray,sortFunc) {
var i;
if (!sortFunc) {
sortFunc = Util.numberSortFunction;
}
// assuming that the array is relative small
for (i = sortedArray.length-1; i >= 0; i--) {
if (sortFunc(sortedArray[i],element) <= 0) {
sortedArray.splice(i+1, 0, element);
return;
}
}
sortedArray.unshift(element);
},
/**
* Returns a-b
* @static
* @method numberSortFunction
* @param {Number} a
* @param {Number} b
* @return {Number} a-b
*/
numberSortFunction : function (a, b) {
return a - b;
},
/**
* Loops through array and return true if any array element strict equals the element.
* This uses the === to compare the two elements.
* @static
* @method contains
* @param {Array} array
* @param {Object} element
* @return {boolean} array contains element
*/
contains : function (array, element) {
var i;
for (i = array.length - 1; i >= 0; i--) {
if (array[i] === element) {
return true;
}
}
return false;
},
/**
* Packs a Uint32 into a kick.math.Vec4
* @static
* @method uint32ToVec4
* @param {Number} uint32
* @param {kick.math.Vec4} dest
* @return {kick.math.Vec4}
*/
uint32ToVec4 : function(uint32, dest) {
var i;
if (!dest) {
dest = new Float32Array(4);
}
packIntToFloatInt32Buffer[0] = uint32;
for (i = 0; i < 4; i++) {
dest[i] = packIntToFloatUint8Buffer[i] / 255;
}
return dest;
},
/**
* Unpacks a kick.math.Vec4 into a Uint32
* @static
* @method vec4ToUint32
* @param {kick.math.Vec4} vec4
*/
vec4ToUint32 : function(vec4) {
var i;
for (i = 0; i < 4; i++) {
packIntToFloatUint8Buffer[i] = vec4[i] * 255;
}
return packIntToFloatInt32Buffer[0];
},
/**
* Unpacks an array of uint8 into a Uint32
* @static
* @method vec4uint8ToUint32
* @param {Array_Number} vec4uint8
*/
vec4uint8ToUint32 : function (vec4uint8) {
var i;
for (i = 0; i < 4; i++){
packIntToFloatUint8Buffer[i] = vec4uint8[i];
}
return packIntToFloatInt32Buffer[0];
},
/**
* Supports up to 3 byte UTF-8 encoding (including Basic Multilingual Plane)
* @static
* @method utf8Encode
* @param {String} str
* @return Uint8Array
*/
utf8Encode:function (str) {
var res = [],
i,
charCode;
for (i = 0; i < str.length; i++) {
charCode = str.charCodeAt(i);
if (charCode < 0x007F) {
res.push(charCode);
} else if (charCode <= 0x07FF) {
res.push(0xC0 + (charCode >> 6));
res.push(0x80 + (charCode & 0x3F));
} else if (charCode <= 0xFFFF) {
res.push(0xE0 + (charCode >> 12));
res.push(0x80 + ((charCode >> 6) & 0x3F));
res.push(0x80 + (charCode & 0x3F));
} else {
if (ASSERT) {
Util.fail("Unsupported character. Charcode " + charCode);
}
}
}
return new Uint8Array(res);
},
/**
* Removes all properties (methods and attributes) of an object
* @static
* @method removeAllProperties
* @param {Object} obj
*/
removeAllProperties: function (obj) {
for (var name in obj) {
if (obj.hasOwnProperty(name)) {
delete obj[name];
}
}
},
/**
* Supports up to 3 byte UTF-8 encoding (including Basic Multilingual Plane)
* @static
* @method utf8Decode
* @param {Uint8Array} bytes
* @return String
*/
utf8Decode: function (bytes) {
var str = "",
i,
byteVal,
byte2,
byte3,
charValue;
for (i = 0; i < bytes.length; i++) {
byteVal = bytes[i];
if ((byteVal & 0x80) === 0) { // Bytes 0xxxxxxx
str += String.fromCharCode(byteVal);
} else if ((byteVal & 0xE0) === 0xC0) { // Bytes 110xxxxx
i++;
byte2 = bytes[i];
byteVal = (byteVal & 0x1F) << 6;
byte2 = byte2 & 0x3F;
charValue = byteVal + byte2;
str += String.fromCharCode(charValue);
} else if ((byteVal & 0xF0) === 0xE0) { // Bytes 1110xxxx
i++;
byte2 = bytes[i];
i++;
byte3 = bytes[i];
byteVal = (byteVal & 0x1F) << 12;
byte2 = (byte2 & 0x3F) << 6;
byte3 = byte3 & 0x3F;
charValue = byteVal + byte2 + byte3;
str += String.fromCharCode(charValue);
} else {
if (ASSERT) {
Util.fail("Unsupported encoding");
}
}
}
return str;
},
/**
* @static
* @method isPowerOfTwo
* @param {Number} x value
* @return {Number}
*/
isPowerOfTwo: function (x) {
return (x & (x - 1)) === 0;
},
/**
* @static
* @method nextHighestPowerOfTwo
* @param {Number} x value
* @return {Number}
*/
nextHighestPowerOfTwo: function (x) {
var i;
--x;
for (i = 1; i < 32; i <<= 1) {
x = x | x >> i;
}
return x + 1;
},
/**
* @static
* @method convertSubMeshesToTriangleIndices
* @param {Array} subMeshes array of array of indices
* @param {Number} primitiveType such as Constants.GL_TRIANGLES or Constants.GL_TRIANGLE_STRIP
* @param {Boolean} removeDegenerate remove degenerate triangles
* @return {Array|null} triangleIndices or null if not possible to convert
*/
convertSubMeshesToTriangleIndices: function (subMeshes, primitiveType, removeDegenerate){
var result = [],
i,
subResult;
for (i=0;i<subMeshes.length;i++){
subResult = Util.convertToTriangleIndices(subMeshes[i], primitiveType, removeDegenerate);
if (!subResult) {
return null;
}
result = result.concat(Util.typedArrayToArray(subResult));
}
return result;
},
/**
* @static
* @method convertToTriangleIndices
* @param {Array} indices index array
* @param {Number} primitiveType such as Constants.GL\_TRIANGLES or Constants.GL\_TRIANGLE\_STRIP
* @param {Boolean} removeDegenerate remove degenerate triangles
* @return {Array|null} triangleIndices or null if not possible to convert
*/
convertToTriangleIndices: function (indices, primitiveType, removeDegenerate) {
var i,
even = 1,
trianleIndices = [indices[0], indices[1], indices[2]];
if (primitiveType === Constants.GL_TRIANGLES){
return indices;
} else if (primitiveType === Constants.GL_TRIANGLE_FAN){
var res = [indices[0],indices[1],indices[2]];
for (i = 3; i <indices.length; i++) {
res.push(indices[0]);
res.push(indices[i-1]);
res.push(indices[i]);
}
return res;
} else if (primitiveType !== Constants.GL_TRIANGLE_STRIP){
return null;
}
for (i = 3; i < indices.length; i++) {
if (removeDegenerate){
if (indices[i-1] === indices[i] ||
indices[i-2] === indices[i] ||
indices[i-1] === indices[i-2]) {
continue;
}
}
if (i % 2 === even) {
trianleIndices.push(indices[i-1]);
trianleIndices.push(indices[i-2]);
trianleIndices.push(indices[i]);
} else {
trianleIndices.push(indices[i-2]);
trianleIndices.push(indices[i-1]);
trianleIndices.push(indices[i]);
}
}
return trianleIndices;
},
/**
* Invokes debugger and logs a warning
* @method warn
* @static
*/
warn:function (message) {
debugger;
console.log(message);
},
/**
* Invokes debugger and logs an error
* @method fail
* @static
*/
fail:function (message) {
debugger;
console.error(message);
}
};
return Util;
});