1272 lines
28 KiB
JavaScript
1272 lines
28 KiB
JavaScript
//GENERATED_IMPORTS_START
|
|
//GENERATED_IMPORTS_END
|
|
|
|
//CUSTOM_IMPORTS_START
|
|
//CUSTOM_IMPORTS_END
|
|
|
|
/**
|
|
|
|
GENERATED_INHERITED_START
|
|
|
|
GENERATED_INHERITED_END
|
|
|
|
TEMPLATE_OPTIONS_START
|
|
TEMPLATE_OPTIONS_END
|
|
|
|
CUSTOM_OPTIONS_START
|
|
CUSTOM_OPTIONS_END
|
|
|
|
TEMPLATE_STATIC_OPTIONS_START
|
|
TEMPLATE_STATIC_OPTIONS_END
|
|
|
|
CUSTOM_STATIC_OPTIONS_START
|
|
CUSTOM_STATIC_OPTIONS_END
|
|
|
|
TEMPLATE_METHODS_START
|
|
TEMPLATE_METHODS_END
|
|
|
|
CUSTOM_METHODS_START
|
|
CUSTOM_METHODS_END
|
|
|
|
TEMPLATE_STATIC_METHODS_START
|
|
TEMPLATE_STATIC_METHODS_END
|
|
|
|
CUSTOM_STATIC_METHODS_START
|
|
CUSTOM_STATIC_METHODS_END
|
|
|
|
**/
|
|
|
|
class Utils {
|
|
|
|
//GENERATED_CONSTRUCTOR_START
|
|
constructor(options) {
|
|
|
|
if (typeof options === 'undefined') {
|
|
options = {};
|
|
}
|
|
|
|
//GENERATED_TEMPLATE_OPTIONS_INIT_START
|
|
//GENERATED_TEMPLATE_OPTIONS_INIT_END
|
|
|
|
//GENERATED_OPTIONS_INIT_START
|
|
//GENERATED_OPTIONS_INIT_END
|
|
|
|
//CUSTOM_OPTIONS_INIT_START
|
|
//CUSTOM_OPTIONS_INIT_END
|
|
|
|
Object.assign(this, options);
|
|
|
|
//CUSTOM_BEFORE_INIT_START
|
|
//CUSTOM_BEFORE_INIT_END
|
|
|
|
//CUSTOM_AFTER_INIT_START
|
|
//CUSTOM_AFTER_INIT_END
|
|
}
|
|
//GENERATED_CONSTRUCTOR_END
|
|
|
|
//GENERATED_TEMPLATE_METHODS_START
|
|
//GENERATED_TEMPLATE_METHODS_END
|
|
|
|
//GENERATED_METHODS_START
|
|
//GENERATED_METHODS_END
|
|
|
|
//GENERATED_TEMPLATE_STATIC_METHODS_START
|
|
//GENERATED_TEMPLATE_STATIC_METHODS_END
|
|
|
|
//GENERATED_STATIC_METHODS_START
|
|
//GENERATED_STATIC_METHODS_END
|
|
|
|
//CUSTOM_IMPLEMENTATION_START
|
|
static GetFirstParent(object, constructor) {
|
|
|
|
if (Utils.UndefinedOrNull(constructor)) {
|
|
throw new Error('You need to specify a constructor');
|
|
}
|
|
|
|
if (object.parent === null) {
|
|
return null;
|
|
}
|
|
|
|
if (object.parent instanceof constructor) {
|
|
return object.parent;
|
|
} else {
|
|
return Utils.GetFirstParent(object.parent, constructor);
|
|
}
|
|
|
|
};
|
|
|
|
static SyntaxHighlight(json) {
|
|
if (typeof json != 'string') {
|
|
json = JSON.stringify(json, undefined, 2);
|
|
}
|
|
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
return json.replace(/("(\u[a-zA-Z0-9]{4}|\[^u]|[^\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
|
|
let cls = 'number';
|
|
if (/^"/.test(match)) {
|
|
if (/:$/.test(match)) {
|
|
cls = 'key';
|
|
} else {
|
|
cls = 'string';
|
|
}
|
|
} else if (/true|false/.test(match)) {
|
|
cls = 'boolean';
|
|
} else if (/null/.test(match)) {
|
|
cls = 'null';
|
|
}
|
|
return '<span class="' + cls + '">' + match + '</span>';
|
|
});
|
|
};
|
|
|
|
static GetParentProject(component) {
|
|
|
|
if (Utils.UndefinedOrNull(component.parent)) {
|
|
throw new Error('Parent not found');
|
|
}
|
|
|
|
if (component.parent instanceof R3.Project) {
|
|
return component.parent;
|
|
}
|
|
|
|
return Utils.GetParentProject(component.parent);
|
|
};
|
|
|
|
static GetParents(component, parents) {
|
|
|
|
if (Utils.UndefinedOrNull(parents)) {
|
|
parents = [];
|
|
}
|
|
|
|
if (Utils.UndefinedOrNull(component.parent)) {
|
|
return parents;
|
|
}
|
|
|
|
parents.push(component.parent);
|
|
|
|
return Utils.GetParents(component.parent, parents);
|
|
|
|
};
|
|
|
|
/**
|
|
* @return {boolean}
|
|
*/
|
|
static Instance(component) {
|
|
return Utils.Defined(component) && Utils.Defined(component.instance);
|
|
};
|
|
|
|
/**
|
|
* Utils.RemoveFromSelect
|
|
* @param select
|
|
* @param id
|
|
* @returns {boolean}
|
|
* @constructor
|
|
*/
|
|
static RemoveFromSelect(select, id) {
|
|
|
|
let i;
|
|
|
|
for (i = 0; i < select.options.length; i++) {
|
|
if (select.options[i].value === id) {
|
|
select.remove(i);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Utils.GetSelectIndex
|
|
*
|
|
* Get the select index of given id
|
|
*
|
|
* @param select
|
|
* @param id
|
|
* @returns boolean true if successful
|
|
*
|
|
* @constructor
|
|
*/
|
|
static SetSelectIndex(select, id) {
|
|
for (let i = 0; i < select.options.length; i++) {
|
|
if (select.options[i].value === id) {
|
|
select.selectedIndex = i;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
static SortSelect(select) {
|
|
|
|
let tmp = [];
|
|
let i;
|
|
|
|
for (i = 1; i < select.options.length; i++) {
|
|
tmp[i-1] = [];
|
|
tmp[i-1][0] = select.options[i].text;
|
|
tmp[i-1][1] = select.options[i].value;
|
|
}
|
|
|
|
tmp.sort();
|
|
|
|
select.options = [select.options[0]];
|
|
|
|
for (i = 0; i < tmp.length; i++) {
|
|
select.options[i+1] = new Option(tmp[i][0], tmp[i][1]);
|
|
}
|
|
|
|
return;
|
|
};
|
|
|
|
/**
|
|
* Gets the parent of object whith property of optional type constructor. If index is specified, get the parent of the
|
|
* object with property[index] - which means the property should be an array
|
|
* @param object
|
|
* @param property
|
|
* @param index
|
|
* @param constructor
|
|
* @returns {*}
|
|
* @constructor
|
|
*/
|
|
static GetParent(object, property, index, constructor) {
|
|
|
|
if (Utils.UndefinedOrNull(constructor)) {
|
|
constructor = null;
|
|
}
|
|
|
|
if (Utils.UndefinedOrNull(index)) {
|
|
index = null;
|
|
}
|
|
|
|
if (object.parent) {
|
|
/**
|
|
* Parent defined
|
|
*/
|
|
if (object.parent.hasOwnProperty(property)) {
|
|
|
|
if (constructor) {
|
|
|
|
if (index) {
|
|
|
|
if (object.parent[property][index] instanceof constructor) {
|
|
return object.parent[property][index];
|
|
} else {
|
|
|
|
if (typeof object.parent.getParent === 'function') {
|
|
return object.parent.getParent(property, index, constructor);
|
|
} else {
|
|
console.warn('getParent not defined on API object : ' + object.parent + ' - you should avoid having these messsages');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if (object.parent[property] instanceof constructor) {
|
|
return object.parent[property];
|
|
} else {
|
|
|
|
if (typeof object.parent.getParent === 'function') {
|
|
return object.parent.getParent(property, index, constructor);
|
|
} else {
|
|
console.warn('getParent not defined on API object : ' + object.parent + ' - you should avoid having these messsages');
|
|
return null;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if (index) {
|
|
return object.parent[property][index];
|
|
} else {
|
|
return object.parent[property];
|
|
}
|
|
|
|
}
|
|
} else {
|
|
|
|
/**
|
|
* This parent does not have the property - go a level higher
|
|
*/
|
|
if (typeof object.parent.getParent === 'function') {
|
|
return object.parent.getParent(property, index, constructor);
|
|
} else {
|
|
console.warn('getParent not defined on API object : ' + object.parent + ' - you should avoid having these messsages');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
/**
|
|
* No parent defined
|
|
*/
|
|
console.warn('property : ' + property + ' of type ' + constructor + ' was not found in the parent chain');
|
|
return null;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
/**
|
|
* Strips image extension from given path
|
|
* @param imagePath
|
|
* @constructor
|
|
*/
|
|
static StripImageExtension(imagePath) {
|
|
return imagePath.replace(/(\.png$|\.gif$|\.jpeg$|\.jpg$)/,'')
|
|
};
|
|
|
|
/**
|
|
* Returns true if unloaded
|
|
* @param component
|
|
* @returns {boolean}
|
|
* @constructor
|
|
*/
|
|
static Unloaded(component) {
|
|
if (
|
|
Utils.UndefinedOrNull(component) ||
|
|
Utils.UndefinedOrNull(component.instance)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
*
|
|
* @param component
|
|
* @returns {boolean}
|
|
* @constructor
|
|
*/
|
|
static Loaded(component) {
|
|
if (component && component.instance) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
static BuildVectorSource(result, name, dimension) {
|
|
|
|
if (dimension === 2) {
|
|
result[name] = {};
|
|
result[name].x = false;
|
|
result[name].y = false;
|
|
return;
|
|
}
|
|
|
|
if (dimension === 3) {
|
|
result[name] = {};
|
|
result[name].x = false;
|
|
result[name].y = false;
|
|
result[name].y = false;
|
|
return;
|
|
}
|
|
|
|
if (dimension === 4) {
|
|
result[name] = {};
|
|
result[name].x = false;
|
|
result[name].y = false;
|
|
result[name].z = false;
|
|
result[name].w = false;
|
|
return;
|
|
}
|
|
|
|
console.warn('unknown dimension : ' + dimension);
|
|
};
|
|
|
|
/**
|
|
* Returns all 'instances' of the array, or null if an 'instance' is undefined
|
|
* @constructor
|
|
* @param array
|
|
*/
|
|
static GetArrayInstances(array) {
|
|
return array.reduce(
|
|
function(result, object) {
|
|
|
|
if (result === null) {
|
|
return result;
|
|
}
|
|
|
|
if (Utils.UndefinedOrNull(object.instance)) {
|
|
result = null;
|
|
} else {
|
|
result.push(object.instance);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
[]
|
|
);
|
|
};
|
|
|
|
static SortFacesByMaterialIndex(faces) {
|
|
|
|
/**
|
|
* Sorts faces according to material index because later we will create
|
|
* groups for each vertice group
|
|
*/
|
|
faces.sort(function(a, b) {
|
|
|
|
if (a.materialIndex < b.materialIndex) {
|
|
return -1;
|
|
}
|
|
|
|
if (a.materialIndex > b.materialIndex) {
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
|
|
return faces;
|
|
};
|
|
|
|
static BuildQuaternionSource(result, name) {
|
|
result[name] = {};
|
|
result[name].axis = {};
|
|
result[name].axis.x = false;
|
|
result[name].axis.y = false;
|
|
result[name].axis.z = false;
|
|
result[name].angle = false;
|
|
result[name].x = false;
|
|
result[name].y = false;
|
|
result[name].z = false;
|
|
result[name].w = false;
|
|
};
|
|
|
|
static GetRuntime() {
|
|
|
|
let result = null;
|
|
|
|
R3.Event.Emit(
|
|
R3.Event.GET_RUNTIME,
|
|
null,
|
|
function(runtime) {
|
|
result = runtime;
|
|
}
|
|
);
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* Returns the window size or null
|
|
* @returns {*}
|
|
* @constructor
|
|
*/
|
|
static GetWindowSize() {
|
|
|
|
let size = null;
|
|
|
|
R3.Event.Emit(
|
|
R3.Event.GET_WINDOW_SIZE,
|
|
null,
|
|
function(data) {
|
|
size = data;
|
|
}.bind(this)
|
|
);
|
|
|
|
return size;
|
|
|
|
};
|
|
|
|
/**
|
|
* Convenience function to update object width and height members with window size
|
|
* @param object
|
|
* @constructor
|
|
*/
|
|
static UpdateWindowSize(object) {
|
|
let size = Utils.GetWindowSize();
|
|
object.width = size.width;
|
|
object.height = size.height;
|
|
};
|
|
|
|
/**
|
|
* Returns id of object with the name if it exists in the array, otherwise null
|
|
* @param name
|
|
* @param array
|
|
* @returns {*}
|
|
* @constructor
|
|
*/
|
|
static ObjectIdWithNameInArray(name, array) {
|
|
|
|
return array.reduce(
|
|
function(result, object) {
|
|
|
|
if (result) {
|
|
return result;
|
|
}
|
|
|
|
if (name === object.name) {
|
|
return object.id;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
null
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Gets random int exclusive of maximum but inclusive of minimum
|
|
* @param min
|
|
* @param max
|
|
* @returns {*}
|
|
* @constructor
|
|
*/
|
|
static GetRandomInt(min, max) {
|
|
min = Math.ceil(min);
|
|
max = Math.floor(max);
|
|
return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
|
|
};
|
|
|
|
/**
|
|
* Gets random int inclusive of minimum and maximum
|
|
* @param min
|
|
* @param max
|
|
* @returns {*}
|
|
* @constructor
|
|
*/
|
|
static GetRandomIntInclusive(min, max) {
|
|
min = Math.ceil(min);
|
|
max = Math.floor(max);
|
|
return Math.floor(Math.random() * (max - min + 1)) + min; //The maximum is inclusive and the minimum is inclusive
|
|
};
|
|
|
|
static InterpolateArray(data, fitCount) {
|
|
|
|
let linearInterpolate = function(before, after, atPoint) {
|
|
return before + (after - before) * atPoint;
|
|
};
|
|
|
|
let newData = [];
|
|
|
|
let springFactor = Number((data.length - 1) / (fitCount - 1));
|
|
|
|
newData[0] = data[0]; // for new allocation
|
|
|
|
for ( let i = 1; i < fitCount - 1; i++) {
|
|
let tmp = i * springFactor;
|
|
let before = Number(Math.floor(tmp)).toFixed();
|
|
let after = Number(Math.ceil(tmp)).toFixed();
|
|
let atPoint = tmp - before;
|
|
newData[i] = linearInterpolate(data[before], data[after], atPoint);
|
|
}
|
|
|
|
newData[fitCount - 1] = data[data.length - 1]; // for new allocation
|
|
|
|
return newData;
|
|
};
|
|
|
|
/**
|
|
* Undefined or null check
|
|
* @param variable
|
|
* @returns {boolean}
|
|
* @constructor
|
|
*/
|
|
static UndefinedOrNull(
|
|
variable
|
|
) {
|
|
return typeof variable === 'undefined' || variable === null;
|
|
};
|
|
|
|
/**
|
|
* The variable is not undefined and not null
|
|
* @param variable
|
|
* @returns {boolean}
|
|
* @constructor
|
|
*/
|
|
static Defined(
|
|
variable
|
|
) {
|
|
return typeof variable !== 'undefined' && variable !== null;
|
|
};
|
|
|
|
/**
|
|
* Gets function parameters
|
|
* @param fn
|
|
* @constructor
|
|
*/
|
|
static GetParameters(fn) {
|
|
|
|
let FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
|
|
let FN_ARG_SPLIT = /,/;
|
|
let FN_ARG = /^\s*(_?)(.+?)\s*$/;
|
|
let STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\'|[^'\r\n])*')|("(?:\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;
|
|
|
|
let parameters,
|
|
fnText,
|
|
argDecl;
|
|
|
|
if (typeof fn !== 'function') {
|
|
parameters = [];
|
|
fnText = fn.toString().replace(STRIP_COMMENTS, '');
|
|
argDecl = fnText.match(FN_ARGS);
|
|
argDecl[1].split(FN_ARG_SPLIT).forEach(function(arg) {
|
|
arg.replace(FN_ARG, function(all, underscore, name) {
|
|
parameters.push(name);
|
|
});
|
|
});
|
|
} else {
|
|
throw Error("not a function")
|
|
}
|
|
|
|
return parameters;
|
|
};
|
|
|
|
/**
|
|
* Returns either an ID of the object or Null
|
|
* @param object
|
|
* @returns {null}
|
|
* @constructor
|
|
*/
|
|
static IdOrNull(object) {
|
|
if (Utils.UndefinedOrNull(object)) {
|
|
return null;
|
|
} else {
|
|
if (Utils.UndefinedOrNull(object.id)) {
|
|
console.warn('saving an object reference with no ID : ', object);
|
|
return null;
|
|
}
|
|
return object.id;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Limit a property to values between -pi and +pi
|
|
* @param property
|
|
* @param objectProperty
|
|
* @returns {{configurable?: boolean, enumerable?: boolean, value?, writable?: boolean, get?: Function, set?: Function}}
|
|
* @constructor
|
|
*/
|
|
static LimitToPI(property, objectProperty) {
|
|
|
|
let store = objectProperty;
|
|
|
|
return {
|
|
get : function() {
|
|
return store;
|
|
},
|
|
set : function(value) {
|
|
while (value > Math.PI) {
|
|
value -= (Math.PI * 2);
|
|
}
|
|
|
|
while (value < -(Math.PI)) {
|
|
value += (Math.PI * 2);
|
|
}
|
|
|
|
store = value;
|
|
}
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Returns an array of IDs representing the objects
|
|
* @param array
|
|
* @returns []
|
|
* @constructor
|
|
*/
|
|
static IdArrayOrEmptyArray(array) {
|
|
if (Utils.UndefinedOrNull(array)) {
|
|
return [];
|
|
} else {
|
|
|
|
return array.map(function(item) {
|
|
|
|
if (Utils.UndefinedOrNull(item.id)) {
|
|
throw new Error('No ID found while trying to store IDs to array');
|
|
}
|
|
|
|
return item.id
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generates a random ID
|
|
* @returns {string}
|
|
* @constructor
|
|
*/
|
|
static RandomId(length) {
|
|
|
|
if (Utils.UndefinedOrNull(length)) {
|
|
length = 10;
|
|
}
|
|
|
|
return Math.random().toString(36).substr(2, length);
|
|
};
|
|
|
|
static InvertWindingOrder(triangles) {
|
|
|
|
for (let i = 0; i < triangles.length; i++) {
|
|
let v1 = triangles[i].v1;
|
|
triangles[i].v1 = triangles[i].v2;
|
|
triangles[i].v2 = v1;
|
|
|
|
let backupUV = triangles[i].triangle.v1uv;
|
|
triangles[i].triangle.v1uv = triangles[i].triangle.v2uv;
|
|
triangles[i].triangle.v2uv = backupUV;
|
|
}
|
|
|
|
return triangles;
|
|
};
|
|
|
|
/**
|
|
* Inverts a mesh winding order (and its instance)
|
|
* @param mesh R3.D3.Mesh
|
|
* @returns {*}
|
|
* @constructor
|
|
*/
|
|
static InvertMeshWindingOrder(mesh) {
|
|
|
|
mesh.faces.forEach(
|
|
function(face) {
|
|
|
|
let tmpV1 = face.v1;
|
|
face.v1 = face.v2;
|
|
face.v2 = tmpV1;
|
|
|
|
let tmpV1uv = face.v1uv;
|
|
face.v1uv = face.v2uv;
|
|
face.v2uv = tmpV1uv;
|
|
|
|
}.bind(this)
|
|
);
|
|
|
|
//mesh.computeNormals = true;
|
|
//mesh.createInstance();
|
|
};
|
|
|
|
/**
|
|
* This function resets a the winding order of a mesh from a reference point V (the average center of the mesh)
|
|
*/
|
|
static ResetWindingOrder(faces, vertices) {
|
|
|
|
let vertexList = new R3.API.Vector3.Points();
|
|
|
|
for (let v = 0; v < vertices.length; v++) {
|
|
vertexList.add(new R3.API.Vector3(
|
|
vertices[v].position.x,
|
|
vertices[v].position.y,
|
|
vertices[v].position.z
|
|
));
|
|
}
|
|
|
|
let V = vertexList.average();
|
|
|
|
let triangles = [];
|
|
|
|
for (let s = 0; s < faces.length; s += 3) {
|
|
|
|
let v0 = faces[s];
|
|
let v1 = faces[s+1];
|
|
let v2 = faces[s+2];
|
|
|
|
triangles.push(
|
|
{
|
|
v0 : v0,
|
|
v1 : v1,
|
|
v2 : v2,
|
|
edges : [
|
|
{v0: v0, v1: v1},
|
|
{v0: v1, v1: v2},
|
|
{v0: v2, v1: v0}
|
|
],
|
|
winding : 0,
|
|
edgeIndex : -1,
|
|
processed : false
|
|
}
|
|
);
|
|
}
|
|
|
|
for (let i = 0; i < triangles.length; i++) {
|
|
if (
|
|
R3.API.Vector3.clockwise(
|
|
vertices[triangles[i].v0].position,
|
|
vertices[triangles[i].v1].position,
|
|
vertices[triangles[i].v2].position,
|
|
V
|
|
)
|
|
) {
|
|
console.log('clockwise');
|
|
let bv1 = triangles[i].v1;
|
|
triangles[i].v1 = triangles[i].v2;
|
|
triangles[i].v2 = bv1;
|
|
} else {
|
|
console.log('not clockwise');
|
|
}
|
|
}
|
|
|
|
return triangles;
|
|
};
|
|
|
|
/**
|
|
* This function resets the winding order for triangles in faces, given an initial triangle and orientation edge
|
|
* used pseudocode from
|
|
* http://stackoverflow.com/questions/17036970/how-to-correct-winding-of-triangles-to-counter-clockwise-direction-of-a-3d-mesh
|
|
* We need to use a graph traversal algorithm,
|
|
* lets assume we have method that returns neighbor of triangle on given edge
|
|
*
|
|
* neighbor_on_egde( next_tria, edge )
|
|
*
|
|
* to_process = set of pairs triangle and orientation edge, initial state is one good oriented triangle with any edge on it
|
|
* processed = set of processed triangles; initial empty
|
|
*
|
|
* while to_process is not empty:
|
|
* next_tria, orientation_edge = to_process.pop()
|
|
* add next_tria in processed
|
|
* if next_tria is not opposite oriented than orientation_edge:
|
|
* change next_tria (ABC) orientation (B<->C)
|
|
* for each edge (AB) in next_tria:
|
|
* neighbor_tria = neighbor_on_egde( next_tria, edge )
|
|
* if neighbor_tria exists and neighbor_tria not in processed:
|
|
* to_process add (neighbor_tria, edge opposite oriented (BA))
|
|
* @param faces R3.D3.Face[]
|
|
* @param orientationEdge R3.API.Vector2
|
|
* @returns {Array}
|
|
*/
|
|
static FixWindingOrder(faces, orientationEdge) {
|
|
|
|
/**
|
|
* Checks if a Face belonging to a TriangleEdge has already been processed
|
|
* @param processed TriangleEdge[]
|
|
* @param triangle Face
|
|
* @returns {boolean}
|
|
*/
|
|
function inProcessed(processed, triangle) {
|
|
|
|
for (let i = 0; i < processed.length; i++) {
|
|
if (processed[i].triangle.equals(triangle)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns a neighbouring triangle on a specific edge - preserving the edge orientation
|
|
* @param edge R3.API.Vector2
|
|
* @param faces R3.D3.Face[]
|
|
* @param currentTriangle
|
|
* @returns {*}
|
|
*/
|
|
function neighbourOnEdge(edge, faces, currentTriangle) {
|
|
|
|
for (let i = 0; i < faces.length; i++) {
|
|
if (
|
|
(faces[i].v0 === edge.x && faces[i].v1 === edge.y) ||
|
|
(faces[i].v1 === edge.x && faces[i].v2 === edge.y) ||
|
|
(faces[i].v2 === edge.x && faces[i].v0 === edge.y) ||
|
|
(faces[i].v0 === edge.y && faces[i].v1 === edge.x) ||
|
|
(faces[i].v1 === edge.y && faces[i].v2 === edge.x) ||
|
|
(faces[i].v2 === edge.y && faces[i].v0 === edge.x)
|
|
) {
|
|
|
|
let triangle = new R3.D3.API.Face(
|
|
null,
|
|
null,
|
|
faces[i].v0index,
|
|
faces[i].v1index,
|
|
faces[i].v2index,
|
|
faces[i].materialIndex,
|
|
faces[i].uvs
|
|
);
|
|
|
|
if (triangle.equals(currentTriangle)) {
|
|
continue;
|
|
}
|
|
|
|
return new R3.D3.TriangleEdge(
|
|
triangle,
|
|
edge
|
|
);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
let toProcess = [
|
|
new R3.D3.TriangleEdge(
|
|
new R3.D3.API.Face(
|
|
null,
|
|
null,
|
|
faces[0].v0index,
|
|
faces[0].v1index,
|
|
faces[0].v2index,
|
|
faces[0].materialIndex,
|
|
faces[0].uvs
|
|
),
|
|
orientationEdge
|
|
)
|
|
];
|
|
|
|
let processed = [];
|
|
|
|
while (toProcess.length > 0) {
|
|
|
|
let triangleEdge = toProcess.pop();
|
|
|
|
/**
|
|
* If edge is the same orientation (i.e. the edge order is the same as the given triangle edge) it needs to be reversed
|
|
* to have the same winding order)
|
|
*/
|
|
if (
|
|
(triangleEdge.triangle.v0index === triangleEdge.edge.x &&
|
|
triangleEdge.triangle.v1index === triangleEdge.edge.y) ||
|
|
(triangleEdge.triangle.v1index === triangleEdge.edge.x &&
|
|
triangleEdge.triangle.v2index === triangleEdge.edge.y) ||
|
|
(triangleEdge.triangle.v2index === triangleEdge.edge.x &&
|
|
triangleEdge.triangle.v0index === triangleEdge.edge.y)
|
|
) {
|
|
let backupV = triangleEdge.triangle.v1index;
|
|
triangleEdge.triangle.v1index = triangleEdge.triangle.v2index;
|
|
triangleEdge.triangle.v2index = backupV;
|
|
|
|
// let backupUV = triangleEdge.triangle.v1uv;
|
|
// triangleEdge.triangle.v1uv = triangleEdge.triangle.v2uv;
|
|
// triangleEdge.triangle.v2uv = backupUV;
|
|
//
|
|
let backupUV = triangleEdge.triangle.uvs[0][1];
|
|
triangleEdge.triangle.uvs[0][1] = triangleEdge.triangle.uvs[0][2];
|
|
triangleEdge.triangle.uvs[0][2] = backupUV;
|
|
}
|
|
|
|
processed.push(triangleEdge);
|
|
|
|
let edges = [
|
|
new R3.API.Vector2(
|
|
triangleEdge.triangle.v0index,
|
|
triangleEdge.triangle.v1index
|
|
),
|
|
new R3.API.Vector2(
|
|
triangleEdge.triangle.v1index,
|
|
triangleEdge.triangle.v2index
|
|
),
|
|
new R3.API.Vector2(
|
|
triangleEdge.triangle.v2index,
|
|
triangleEdge.triangle.v0index
|
|
)
|
|
];
|
|
|
|
for (let j = 0; j < edges.length; j++) {
|
|
let neighbour = neighbourOnEdge(edges[j], faces, triangleEdge.triangle);
|
|
if (neighbour && !inProcessed(processed, neighbour.triangle)) {
|
|
toProcess.push(neighbour);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* In processed - we will have some duplicates - only add the unique ones
|
|
* @type {Array}
|
|
*/
|
|
let triangles = [];
|
|
for (let i = 0; i < processed.length; i++) {
|
|
let found = false;
|
|
for (let k = 0; k < triangles.length; k++) {
|
|
if (triangles[k].equals(processed[i].triangle)){
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
triangles.push(processed[i].triangle);
|
|
}
|
|
}
|
|
|
|
return triangles;
|
|
};
|
|
|
|
/**
|
|
* This is a work-around function to fix polys which don't triangulate because
|
|
* they could lie on Z-plane (XZ or YZ)) - we translate the poly to the origin, systematically rotate the poly around
|
|
* Z then Y axis
|
|
* @param verticesFlat []
|
|
* @param grain is the amount to systematically rotate the poly by - a finer grain means a more accurate maximum XY
|
|
* @return []
|
|
*/
|
|
static FixPolyZPlane(verticesFlat, grain) {
|
|
|
|
if ((verticesFlat.length % 3) !== 0 && !(verticesFlat.length > 9)) {
|
|
console.log("The vertices are not in the right length : " + verticesFlat.length);
|
|
}
|
|
|
|
let vertices = [];
|
|
|
|
let points = new R3.API.Quaternion.Points();
|
|
|
|
for (let i = 0; i < verticesFlat.length; i += 3) {
|
|
points.add(new R3.API.Vector3(
|
|
verticesFlat[i],
|
|
verticesFlat[i + 1],
|
|
verticesFlat[i + 2]
|
|
));
|
|
}
|
|
|
|
points.toOrigin();
|
|
|
|
points.maximizeXDistance(grain);
|
|
|
|
points.maximizeYDistance(grain);
|
|
|
|
for (i = 0; i < points.vectors.length; i++) {
|
|
vertices.push(
|
|
[
|
|
points.vectors[i].x,
|
|
points.vectors[i].y
|
|
]
|
|
);
|
|
}
|
|
|
|
return vertices;
|
|
};
|
|
|
|
static MovingAverage(period) {
|
|
let nums = [];
|
|
return function(num) {
|
|
nums.push(num);
|
|
if (nums.length > period)
|
|
nums.splice(0,1); // remove the first element of the array
|
|
let sum = 0;
|
|
for (let i in nums)
|
|
sum += nums[i];
|
|
let n = period;
|
|
if (nums.length < period)
|
|
n = nums.length;
|
|
return(sum/n);
|
|
}
|
|
};
|
|
|
|
static Intersect(a, b) {
|
|
|
|
let t;
|
|
|
|
/**
|
|
* Loop over shortest array
|
|
*/
|
|
if (b.length > a.length) {
|
|
t = b;
|
|
b = a;
|
|
a = t;
|
|
}
|
|
|
|
return a.filter(
|
|
/**
|
|
* Check if exists
|
|
* @param e
|
|
* @returns {boolean}
|
|
*/
|
|
function(e) {
|
|
return (b.indexOf(e) > -1);
|
|
}
|
|
).filter(
|
|
/**
|
|
* Remove Duplicates
|
|
* @param e
|
|
* @param i
|
|
* @param c
|
|
* @returns {boolean}
|
|
*/
|
|
function(e, i, c) {
|
|
return c.indexOf(e) === i;
|
|
}
|
|
);
|
|
};
|
|
|
|
static Difference(a, b) {
|
|
|
|
let t;
|
|
|
|
/**
|
|
* Loop over shortest array
|
|
*/
|
|
if (b.length > a.length) {
|
|
t = b;
|
|
b = a;
|
|
a = t;
|
|
}
|
|
|
|
return a.filter(
|
|
/**
|
|
* Check if exists
|
|
* @param e
|
|
* @returns {boolean}
|
|
*/
|
|
function(e) {
|
|
return (b.indexOf(e) === -1);
|
|
}
|
|
).filter(
|
|
/**
|
|
* Remove Duplicates
|
|
* @param e
|
|
* @param i
|
|
* @param c
|
|
* @returns {boolean}
|
|
*/
|
|
function(e, i, c) {
|
|
return c.indexOf(e) === i;
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Push only if not in there already
|
|
* @param array
|
|
* @param object
|
|
* @constructor
|
|
*/
|
|
static PushUnique(array, object) {
|
|
if (array.indexOf(object) === -1) {
|
|
array.push(object);
|
|
}
|
|
};
|
|
|
|
static RemoveFromArray(array, object) {
|
|
let index = array.indexOf(object);
|
|
if (index !== -1) {
|
|
array.splice(index, 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether or not the object is empty
|
|
* @param obj
|
|
* @returns {boolean}
|
|
* @constructor
|
|
*/
|
|
static IsEmpty(obj) {
|
|
return (Object.keys(obj).length === 0 && obj.constructor === Object);
|
|
};
|
|
|
|
static IsString(member) {
|
|
return (typeof member === 'string');
|
|
};
|
|
|
|
static IsBoolean(member) {
|
|
return (member === true || member === false);
|
|
};
|
|
|
|
static IsColor(member) {
|
|
return (member instanceof R3.Color);
|
|
};
|
|
|
|
static IsNumber(member) {
|
|
return (typeof member === 'number');
|
|
};
|
|
|
|
static IsVector2(member) {
|
|
return (
|
|
member instanceof R3.API.Vector2 ||
|
|
member instanceof R3.Vector2
|
|
);
|
|
};
|
|
|
|
static IsVector3(member) {
|
|
return (
|
|
member instanceof R3.API.Vector3 ||
|
|
member instanceof R3.Vector3
|
|
);
|
|
};
|
|
|
|
static IsVector4(member) {
|
|
return (
|
|
member instanceof R3.API.Vector4 ||
|
|
member instanceof R3.Vector4 ||
|
|
member instanceof R3.API.Quaternion ||
|
|
member instanceof R3.Quaternion
|
|
);
|
|
};
|
|
|
|
static IsObject(member) {
|
|
let type = typeof member;
|
|
return type === 'function' || type === 'object' && !!member;
|
|
};
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
static LowerUnderscore(name) {
|
|
let string = name.toLowerCase().replace(/\s+/g, '_');
|
|
string = string.replace(/-/g, '_');
|
|
string = string.replace(/\_+/g, '_');
|
|
return string;
|
|
};
|
|
|
|
static UpperCaseWordsSpaces(input) {
|
|
|
|
let word = input.replace(/[-_]/g, ' ');
|
|
|
|
word = word.replace(/\s+/, ' ');
|
|
|
|
let words = word.split(' ');
|
|
|
|
return words.reduce(
|
|
function(result, word) {
|
|
result += word[0].toUpperCase() + word.substr(1);
|
|
return result + ' ';
|
|
},
|
|
''
|
|
).trim();
|
|
};
|
|
|
|
/**
|
|
* @return {string}
|
|
*/
|
|
static UpperCaseUnderscore(word) {
|
|
|
|
let str = '';
|
|
|
|
word.split('').map(
|
|
function(letter){
|
|
if (letter === letter.toUpperCase()) {
|
|
str += '_' + letter;
|
|
} else {
|
|
str += letter.toUpperCase();
|
|
}
|
|
});
|
|
|
|
str = str.replace(new RegExp('^_'),'');
|
|
|
|
return str;
|
|
};
|
|
|
|
/**
|
|
* Returns Left Padded Text - ex. length 5, padchar 0, string abc = '00abc'
|
|
* @param length
|
|
* @param padChar
|
|
* @param string
|
|
* @returns {string}
|
|
* @constructor
|
|
*/
|
|
static PaddedText(length, padChar, string) {
|
|
|
|
let pad = "";
|
|
|
|
for (let x = 0; x < length; x++) {
|
|
pad += padChar;
|
|
}
|
|
|
|
return pad.substring(0, pad.length - string.length) + string;
|
|
};
|
|
//CUSTOM_IMPLEMENTATION_END
|
|
|
|
}
|
|
|
|
//GENERATED_TEMPLATE_STATIC_OPTIONS_INIT_START
|
|
//GENERATED_TEMPLATE_STATIC_OPTIONS_INIT_END
|
|
|
|
//GENERATED_STATIC_OPTIONS_INIT_START
|
|
//GENERATED_STATIC_OPTIONS_INIT_END
|
|
|
|
//GENERATED_OUT_OF_CLASS_IMPLEMENTATION_START
|
|
//GENERATED_OUT_OF_CLASS_IMPLEMENTATION_END
|
|
|
|
//CUSTOM_OUT_OF_CLASS_IMPLEMENTATION_START
|
|
//CUSTOM_OUT_OF_CLASS_IMPLEMENTATION_END
|
|
|
|
module.exports = Utils;
|