define(["kick/core/Constants", "kick/core/Util", "kick/core/ChunkData", "kick/math/Aabb", "kick/math/Vec2", "kick/math/Vec3", "kick/math/Vec4", "kick/math/Mat4"],
function (Constants, Util, ChunkData, Aabb, Vec2, Vec3, Vec4, Mat4) {
"use strict";
var ASSERT = Constants._ASSERT,
MeshData;
/**
* Mesh data class.
* Allows for modifying mesh object easily.
* This is a pure data class with no WebGL dependency
* @class MeshData
* @namespace kick.mesh
* @param {Object} [config]
* @constructor
*/
MeshData = function (config) {
var data = {},
thisObj = this,
_indices = [],
_interleavedArray,
_interleavedArrayFormat,
_vertexAttrLength,
_usage = Constants.GL_STATIC_DRAW,
_meshType,
_name,
clearInterleavedData = function () {
_interleavedArray = null;
_interleavedArrayFormat = null;
_vertexAttrLength = null;
},
isVertexDataInitialized = function () {
return data.vertex;
},
isInterleavedDataInitialized = function () {
return _interleavedArray;
},
createVertexDataFromInterleavedData = function () {
var vertexLength = _interleavedArray.byteLength / (_vertexAttrLength), i, j,
attributeName,
attributeConfig,
offset = 0,
ArrayType,
floatView;
data = {};
for (i = 0; i < vertexLength; i++) {
for (attributeName in _interleavedArrayFormat) {
if (_interleavedArrayFormat.hasOwnProperty(attributeName)) {
attributeConfig = _interleavedArrayFormat[attributeName];
ArrayType = attributeConfig.type === Constants.GL_FLOAT ? Float32Array : Int32Array;
if (i === 0) {
data[attributeName] = new ArrayType(vertexLength * attributeConfig.size);
}
floatView = new ArrayType(_interleavedArray, offset + attributeConfig.pointer);
for (j = 0; j < attributeConfig.size; j++) {
data[attributeName][i * attributeConfig.size + j] = floatView[j];
}
}
}
offset += _vertexAttrLength;
}
},
/**
* @method createGetterSetter
* @private
* @param {Number} type GL\_FLOAT or GL\_INT
* @param {string} name
*/
createGetterSetter = function (type, name) {
if (type === Constants.GL_FLOAT || type === Constants.GL_INT) {
var TypedArrayType = (type === Constants.GL_FLOAT) ? Float32Array : Int32Array;
return {
get: function () {
if (!isVertexDataInitialized() && isInterleavedDataInitialized()) {
createVertexDataFromInterleavedData();
}
return data[name];
},
set: function (newValue) {
if (newValue) {
if (data[name] && data[name].length === newValue.length) {
data[name].set(newValue);
} else {
data[name] = new TypedArrayType(newValue);
}
} else {
data[name] = null;
}
clearInterleavedData();
}
};
} else if (ASSERT) {
Util.fail("Unexpected type");
}
},
/**
* @method createInterleavedData
* @private
*/
createInterleavedData = function () {
var lengthOfVertexAttributes = [],
names = [],
types = [],
length = 0,
vertexAttributes = [],
data,
i,
j,
k,
vertex = thisObj.vertex,
vertexLen = vertex ? vertex.length / 3 : 0,
vertexOffset = 0,
description = {},
dataArrayBuffer,
floatView,
intView,
dataSrc,
dataSrcLen,
SIZE_OF_FLOAT_OR_INT = 4,
addAttributes = function (name, size, type) {
var array = thisObj[name];
if (array) {
lengthOfVertexAttributes.push(size);
names.push(name);
types.push(type);
vertexAttributes.push(array);
description[name] = {
pointer: length * 4,
size: size,
normalized: false,
type: type,
name: name
};
length += size;
}
};
addAttributes("vertex", 3, Constants.GL_FLOAT);
addAttributes("normal", 3, Constants.GL_FLOAT);
addAttributes("uv1", 2, Constants.GL_FLOAT);
addAttributes("uv2", 2, Constants.GL_FLOAT);
addAttributes("tangent", 4, Constants.GL_FLOAT);
addAttributes("color", 4, Constants.GL_FLOAT);
addAttributes("int1", 1, Constants.GL_INT);
addAttributes("int2", 2, Constants.GL_INT);
addAttributes("int3", 3, Constants.GL_INT);
addAttributes("int4", 4, Constants.GL_INT);
// copy data into array
if (_interleavedArray && _interleavedArray.length === length * vertexLen * SIZE_OF_FLOAT_OR_INT){
dataArrayBuffer = _interleavedArray;
} else {
dataArrayBuffer = new ArrayBuffer(length * vertexLen * SIZE_OF_FLOAT_OR_INT);
}
floatView = new Float32Array(dataArrayBuffer, 0);
intView = new Int32Array(dataArrayBuffer, 0);
for (i = 0; i < vertexLen; i++) {
for (j = 0; j < names.length; j++) {
dataSrc = vertexAttributes[j];
dataSrcLen = lengthOfVertexAttributes[j];
if (types[j] === Constants.GL_FLOAT) {
data = floatView;
} else {
data = intView;
}
for (k = 0; k < dataSrcLen; k++) {
data[vertexOffset] = dataSrc[i * dataSrcLen + k];
vertexOffset += 1;
}
}
}
_interleavedArray = dataArrayBuffer;
_interleavedArrayFormat = description;
_vertexAttrLength = length * SIZE_OF_FLOAT_OR_INT;
};
/**
* Saves the MeshData into binary form (ArrayBuffer)
* @method serialize
* @return ArrayBuffer
*/
this.serialize = function () {
var subMeshes,
numberOfSubMeshes,
i,
chunkData = new ChunkData();
chunkData.setArrayBuffer(1, thisObj.interleavedArray);
chunkData.setString(2, JSON.stringify(thisObj.interleavedArrayFormat));
chunkData.setString(3, thisObj.name || "MeshData");
subMeshes = thisObj.subMeshes;
numberOfSubMeshes = subMeshes.length;
chunkData.setNumber(4, numberOfSubMeshes);
chunkData.setNumber(5, thisObj.vertexAttrLength);
chunkData.setNumber(6, thisObj.usage);
for (i = 0; i < numberOfSubMeshes; i++) {
chunkData.set(10 + i, subMeshes[i]);
}
return chunkData.serialize();
};
/**
* Restores the
* @method deserialize
* @param {ArrayBuffer} data
* @return Boolean
*/
this.deserialize = function (data) {
var chunkData = new ChunkData(),
numberOfSubMeshes,
submeshes,
i;
if (chunkData.deserialize(data)) {
thisObj.interleavedArray = chunkData.getArrayBuffer(1);
thisObj.interleavedArrayFormat = JSON.parse(chunkData.getString(2));
thisObj.name = chunkData.getString(3);
numberOfSubMeshes = chunkData.getNumber(4);
thisObj.vertexAttrLength = chunkData.getNumber(5);
thisObj.usage = chunkData.getNumber(6) || Constants.GL_STATIC_DRAW;
submeshes = [];
for (i = 0; i < numberOfSubMeshes; i++) {
submeshes[i] = chunkData.get(10 + i);
}
thisObj.subMeshes = submeshes;
return true;
}
return false;
};
Object.defineProperties(this, {
/**
* Note that this property is not cached. Use kick.mesh.Mesh.aabb for a cached version.
* Readonly
* @property aabb
* @type kick.math.Aabb
*/
aabb: {
get: function () {
var vertexLength,
aabb,
i,
vertex = thisObj.vertex;
if (!vertex) {
return null;
}
vertexLength = vertex.length;
aabb = Aabb.create();
for (i = 0; i < vertexLength; i += 3) {
Aabb.addPointIndexed(aabb, aabb, vertex, i);
}
return aabb;
}
},
/**
* Must be either GL_STATIC_DRAW, GL_DYNAMIC_DRAW or GL_STREAM_DRAW.
* @property usage
* @type Number
* @default GL_STATIC_DRAW
*/
usage: {
get: function () {
return _usage;
},
set: function (newValue) {
if (ASSERT) {
if (newValue !== Constants.GL_STATIC_DRAW && newValue !== Constants.GL_DYNAMIC_DRAW && newValue !== Constants.GL_STREAM_DRAW) {
Util.fail("MeshData.usage Must be either GL_STATIC_DRAW, GL_DYNAMIC_DRAW or GL_STREAM_DRAW");
}
}
_usage = newValue;
}
},
/**
* @property name
* @type string
*/
name: {
get: function () {
return _name;
},
set: function (newValue) {
_name = newValue;
}
},
/**
* @property interleavedArray
* @type Float32Array
*/
interleavedArray: {
get: function () {
if ((!isInterleavedDataInitialized()) && isVertexDataInitialized()) {
createInterleavedData();
}
return _interleavedArray;
},
set: function (newValue) {
if (ASSERT) {
if (newValue && !(newValue instanceof ArrayBuffer)) {
Util.fail("MeshData.interleavedArray must be an ArrayBuffer");
}
}
if (!newValue) {
clearInterleavedData();
} else {
_interleavedArray = newValue;
}
}
},
/**
* Describes the interleaved array format.<br>
* The description is an object with a number of properties.<br>
* Each property name corresponds to the name of the vertex attribute.<br>
* Each property has the format <br>
* @example
* {
* pointer: 0, // {Number}
* size: 0, //{Number} number of elements
* normalized: 0, // {Boolean} should be normalized or not
* type: 0 // {GL_FLOAT or GL_INT}
* }
* <br>
* Example:<br>
* @example
* var vertexOffset = meshData.interleavedArrayFormat["vertex"].pointer;
*
* @property interleavedArrayFormat
* @type Object
*/
interleavedArrayFormat: {
get: function () {
if ((!isInterleavedDataInitialized()) && isVertexDataInitialized()) {
createInterleavedData();
}
return _interleavedArrayFormat;
},
set: function (newValue) {
if (ASSERT) {
var n,
object;
if (newValue !== null) {
for (n in newValue) {
if (newValue.hasOwnProperty(n)){
object = newValue[n];
if (typeof object === "object") {
if (typeof (object.pointer) !== "number" ||
typeof (object.size) !== "number" ||
typeof (object.normalized) !== "boolean" ||
typeof (object.type) !== "number") {
Util.fail("Invalid object signature - expected {pointer:,size:,normalized:,type:}");
}
}
}
}
}
}
if (!newValue) {
clearInterleavedData();
} else {
_interleavedArrayFormat = newValue;
}
}
},
/**
* The length of vertexAttributes for one vertex in bytes
* @property vertexAttrLength
* @type Number
*/
vertexAttrLength: {
get: function () {
if ((!isInterleavedDataInitialized()) && isVertexDataInitialized()) {
createInterleavedData();
}
return _vertexAttrLength;
},
set: function (newValue) {
if (ASSERT) {
if (typeof newValue !== "number" || newValue < 0) {
Util.fail("Invalid MeshData.vertexAttrLength - expected a real number");
}
}
if (!newValue) {
clearInterleavedData();
} else {
_vertexAttrLength = newValue;
}
}
},
/**
* Vertex attribute.
* Vertex (vec3)
* @property vertex
* @type Array_Number
*/
vertex: createGetterSetter(Constants.GL_FLOAT, "vertex"),
/**
* Vertex attribute.
* Normal (vec3)
* @property normal
* @type Array_Number
*/
normal: createGetterSetter(Constants.GL_FLOAT, "normal"),
/**
* Vertex attribute.
* UV1 (vec2)
* @property uv1
* @type Array_Number
*/
uv1: createGetterSetter(Constants.GL_FLOAT, "uv1"),
/**
* Vertex attribute.
* UV2 (vec2)
* @property uv2
* @type Array_Number
*/
uv2: createGetterSetter(Constants.GL_FLOAT, "uv2"),
/**
* Vertex attribute.
* Tangent (vec4)
* @property tangent
* @type Array_Number
*/
tangent: createGetterSetter(Constants.GL_FLOAT, "tangent"),
/**
* Vertex attribute.
* Color (vec4)
* @property color
* @type Array_Number
*/
color: createGetterSetter(Constants.GL_FLOAT, "color"),
/**
* Vertex attribute.
* Integer attribute (one Int32)
* @property int1
* @type Array_Number
*/
int1: createGetterSetter(Constants.GL_INT, "int1"),
/**
* Vertex attribute.
* Integer attribute (two Int32)
* @property int2
* @type Array_Number
*/
int2: createGetterSetter(Constants.GL_INT, "int2"),
/**
* Vertex attribute.
* Integer attribute (three Int32)
* @property int3
* @type Array_Number
*/
int3: createGetterSetter(Constants.GL_INT, "int3"),
/**
* Vertex attribute.
* Integer attribute (four Int32)
* @property int4
* @type Array_Number
*/
int4: createGetterSetter(Constants.GL_INT, "int4"),
/**
* Vertex attribute.
* indices (integer).
* indices is shortcut for subMeshes[0]
* @property indices
* @type Array_Number
*/
indices: {
get: function () {
if (_indices === 0) {
return null;
}
return _indices[0];
},
set: function (newValue) {
if (newValue && !(newValue instanceof Uint16Array)) {
newValue = new Uint16Array(newValue);
}
if (_indices[0] && isVertexDataInitialized()) {
clearInterleavedData();
}
if (newValue) {
_indices[0] = newValue;
}
}
},
/**
* Indices (integer) - One index array for each submesh
* @property subMeshes
* @type Array_Array_Number
*/
subMeshes: {
get: function () {
return _indices;
},
set: function (newValue) {
var i;
for (i = 0; i < newValue.length; i++) {
if (newValue[i] && !(newValue[i] instanceof Uint16Array)) {
newValue[i] = new Uint16Array(newValue[i]);
}
}
_indices = newValue;
}
},
/**
* Must be GL\_TRIANGLES, GL\_TRIANGLE\_FAN, GL\_TRIANGLE\_STRIP, GL\_POINTS, or GL\_LINES
* @property meshType
* @type Number
*/
meshType: {
get: function () {
return _meshType;
},
set: function (newValue) {
if (ASSERT) {
if (newValue !== Constants.GL_LINES &&
newValue !== Constants.GL_TRIANGLES &&
newValue !== Constants.GL_TRIANGLE_FAN &&
newValue !== Constants.GL_TRIANGLE_STRIP &&
newValue !== Constants.GL_POINTS
) {
Util.fail("MeshData.meshType must be `GL_TRIANGLES, GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP` or `GL_POINTS`");
}
}
_meshType = newValue;
}
}
});
/**
* @method isValid
* @return {Boolean} if mesh is considered valid
*/
this.isValid = function () {
if (!isVertexDataInitialized() && isInterleavedDataInitialized()) {
createVertexDataFromInterleavedData();
}
var vertexCount = data.vertex.length / 3,
j,
i;
for (j = 0; j < _indices.length; j++) {
for (i = _indices[j].length - 1; i >= 0; i--) {
if (_indices[j][i] >= vertexCount) {
Util.warn("_indices[" + j + "][" + i + "] >= vertexCount");
return false;
}
}
}
return true;
};
/**
* @method isVertexDataInitialized
* @return {Boolean} return true if vertex data is initialized
*/
this.isVertexDataInitialized = isVertexDataInitialized;
/**
* @method isInterleavedDataInitialized
* @return {Boolean} return true if interleaved data is initialized
*/
this.isInterleavedDataInitialized = isInterleavedDataInitialized;
/**
* Creates a copy of the mesh and transform the vertex positions of the MeshData with a mat4.
* Note that normals are not modified - so they may need to renormalized.
* @method transform
* @param {kick.math.Mat4} transformMatrix
* @return {kick.mesh.MeshData} transformed mesh
*/
this.transform = function (transformMatrix) {
var copy = new MeshData(this),
wrappedVec3Array = Vec3.wrapArray(copy.vertex),
j;
for (j = wrappedVec3Array.length - 1; j >= 0; j--) {
Mat4.multiplyVec3(wrappedVec3Array[j], transformMatrix, wrappedVec3Array[j]);
}
return copy;
};
/**
* Combine two meshes and returns the combined mesh as a new Mesh object.<br>
* The two meshes must have the same meshType. Only vertex attributes existing in
* both mesh objects are transferred<br>
* Triangle fans cannot be combined
* @method combine
* @param {kick.mesh.MeshData} secondMesh
* @param {kick.math.Mat4} transform Optional transformation matrix
* @return {kick.mesh.MeshData} mesh object or null if incompatible objects
*/
this.combine = function (secondMesh, transform) {
if (thisObj.meshType !== secondMesh.meshType || thisObj.meshType === Constants.GL_TRIANGLE_FAN) {
if (ASSERT) {
if (thisObj.meshType !== secondMesh.meshType) {
Util.fail("Mesh.combine does not support different meshTypes");
} else {
Util.fail("Mesh.combine does not support triangle fans");
}
return null;
}
return null;
}
var dataNames = ["vertex", "normal", "uv1", "uv2", "tangent", "color", "int1", "int2", "int3", "int4", "indices"],
i,
name,
appendObject,
newConfig;
for (i = dataNames.length - 1; i >= 0; i--) {
name = dataNames[i];
if (!thisObj[name] || !secondMesh[name]) {
dataNames.splice(i, 1); // remove dataName from array
}
}
appendObject = function (config, source, indexOffset) {
var i, j, name, data, len;
for (i = dataNames.length - 1; i >= 0; i--) {
name = dataNames[i];
if (!config[name]) { // if undefined
config[name] = Util.typedArrayToArray(source[name]);
} else {
data = source[name];
if (indexOffset && name === "indices") {
// take a copy
data = new Uint16Array(data);
// add offset to copy
len = data.length;
for (j = 0; j < len; j++) {
data[j] += indexOffset;
}
}
for (j = 0; j < data.length; j++) {
config[name].push(data[j]);
}
}
}
};
newConfig = {
meshType: thisObj.meshType
};
if (transform) {
secondMesh = secondMesh.transform(transform);
}
appendObject(newConfig, thisObj, 0);
appendObject(newConfig, secondMesh, this.vertex.length / 3);
if (thisObj.meshType === Constants.GL_TRIANGLE_STRIP) {
// create two degenerate triangles to connect the two triangle strips
newConfig.indices.splice(thisObj.indices, 0, newConfig.indices[thisObj.indices.length], newConfig.indices[thisObj.indices.length + 1]);
}
return new MeshData(newConfig);
};
(function init() {
if (!config) {
config = {};
}
var copyVertexData = function () {
thisObj.vertex = config.vertex ? new Float32Array(config.vertex) : null;
thisObj.normal = config.normal ? new Float32Array(config.normal) : null;
thisObj.uv1 = config.uv1 ? new Float32Array(config.uv1) : null;
thisObj.uv2 = config.uv2 ? new Float32Array(config.uv2) : null;
thisObj.tangent = config.tangent ? new Float32Array(config.tangent) : null;
thisObj.color = config.color ? new Float32Array(config.color) : null;
thisObj.int1 = config.int1 ? new Int32Array(config.int1) : null;
thisObj.int2 = config.int2 ? new Int32Array(config.int2) : null;
thisObj.int3 = config.int3 ? new Int32Array(config.int3) : null;
thisObj.int4 = config.int4 ? new Int32Array(config.int4) : null;
},
copyInterleavedData = function () {
thisObj.interleavedArray = config.interleavedArray;
thisObj.interleavedArrayFormat = config.interleavedArrayFormat;
thisObj.vertexAttrLength = config.vertexAttrLength;
};
if (config instanceof MeshData) {
if (config.isVertexDataInitialized()) {
copyVertexData();
} else {
if (ASSERT) {
if (!config.isInterleavedDataInitialized()) {
Util.fail("Either vertex or interleaved data should be initialized");
}
}
copyInterleavedData();
}
} else {
if (config.vertex) {
copyVertexData();
} else if (config.interleavedArray) {
copyInterleavedData();
}
}
thisObj.name = config.name;
thisObj.indices = config.indices;
thisObj.meshType = Util.hasProperty(config,"meshType") ? config.meshType : Constants.GL_TRIANGLES;
}());
};
/**
* Create an array of empty UV coordinates
* @method createUv1
*/
MeshData.prototype.createUv1 = function () {
var vertexCount = this.vertex.length / 3;
this.uv1 = new Float32Array(vertexCount*2);
};
/**
* Create an array of empty UV coordinates
* @method createUv2
*/
MeshData.prototype.createUv2 = function () {
var vertexCount = this.vertex.length / 3;
this.uv2 = new Float32Array(vertexCount * 2);
};
/**
* Recalculate the angle weighted vertex normals based on the triangle mesh
* @method recalculateNormals
* @return {Boolean} false if meshtype is not GL\_TRIANGLES or GL\_TRIANGLE\_STRIP
*/
MeshData.prototype.recalculateNormals = function () {
var vertexCount = this.vertex.length / 3,
triangles = Util.convertSubMeshesToTriangleIndices(this.subMeshes, this.meshType, true),
triangleCount = triangles.length / 3,
vertex = Vec3.wrapArray(this.vertex),
a,
normalArrayRef = {},
normalArray = Vec3.array(vertexCount, normalArrayRef),
v1v2 = Vec3.create(),
v1v3 = Vec3.create(),
v2v3Alias = v1v3,
temp = v1v2,
weight1,
weight2,
normal = Vec3.create(),
i1,
i2,
i3,
v1,
v2,
v3;
if (!triangles) {
return false;
}
for (a = 0; a < triangleCount; a++) {
i1 = triangles[a * 3];
i2 = triangles[a * 3 + 1];
i3 = triangles[a * 3 + 2];
v1 = vertex[i1];
v2 = vertex[i2];
v3 = vertex[i3];
Vec3.subtract(v1v2, v2, v1);
Vec3.subtract(v1v3, v3, v1);
Vec3.normalize(v1v2, v1v2);
Vec3.normalize(v1v3, v1v3);
Vec3.cross(normal, v1v2, v1v3);
Vec3.normalize(normal, normal);
weight1 = Math.acos(Math.max(-1, Math.min(1, Vec3.dot(v1v2, v1v3))));
Vec3.subtract(v2v3Alias, v3, v2);
Vec3.normalize(v2v3Alias, v2v3Alias);
weight2 = Math.PI - Math.acos(Math.max(-1, Math.min(1, Vec3.dot(v1v2, v2v3Alias))));
Vec3.add(normalArray[i1], normalArray[i1], Vec3.scale(temp, normal, weight1));
Vec3.add(normalArray[i2], normalArray[i2], Vec3.scale(temp, normal, weight2));
Vec3.add(normalArray[i3], normalArray[i3], Vec3.scale(temp, normal, Math.PI - weight1 - weight2));
}
for (a = 0; a < vertexCount; a++) {
Vec3.normalize(normalArray[a], normalArray[a]);
}
this.normal = normalArrayRef.mem;
};
/**
* Recalculates the tangents on a triangle mesh.<br>
* Algorithm is based on<br>
* Lengyel, Eric. “Computing Tangent Space Basis Vectors for an Arbitrary Mesh”.<br>
* Terathon Software 3D Graphics Library, 2001.<br>
* http://www.terathon.com/code/tangent.html
* @method recalculateTangents
* @return {Boolean} false if meshtype is not supported
*/
MeshData.prototype.recalculateTangents = function () {
var vertex = Vec3.wrapArray(this.vertex),
vertexCount = vertex.length,
normal = Vec3.wrapArray(this.normal),
texcoord = Vec2.wrapArray(this.uv1),
triangles = Util.convertSubMeshesToTriangleIndices(this.subMeshes, this.meshType, true),
triangleCount = triangles.length / 3,
tangentBuffer = new Float32Array(vertexCount * 4),
tangent = Vec4.wrapArray(tangentBuffer),
tan1 = Vec3.array(vertexCount),
tan2 = Vec3.array(vertexCount),
a,
tmpFloat = 0,
tmpVec3 = Vec3.create(),
i1,
i2,
i3,
v1,
v2,
v3,
w1,
w2,
w3,
x1,
x2,
y1,
y2,
z1,
z2,
s1,
s2,
t1,
t2,
r,
t,
n,
sdir,
tdir;
if (!triangles) {
return false;
}
for (a = 0; a < triangleCount; a++) {
i1 = triangles[a * 3];
i2 = triangles[a * 3 + 1];
i3 = triangles[a * 3 + 2];
v1 = vertex[i1];
v2 = vertex[i2];
v3 = vertex[i3];
w1 = texcoord[i1];
w2 = texcoord[i2];
w3 = texcoord[i3];
x1 = v2[0] - v1[0];
x2 = v3[0] - v1[0];
y1 = v2[1] - v1[1];
y2 = v3[1] - v1[1];
z1 = v2[2] - v1[2];
z2 = v3[2] - v1[2];
s1 = w2[0] - w1[0];
s2 = w3[0] - w1[0];
t1 = w2[1] - w1[1];
t2 = w3[1] - w1[1];
r = 1.0 / (s1 * t2 - s2 * t1);
sdir = Vec3.clone([(t2 * x1 - t1 * x2) * r,
(t2 * y1 - t1 * y2) * r,
(t2 * z1 - t1 * z2) * r]);
tdir = Vec3.clone([(s1 * x2 - s2 * x1) * r,
(s1 * y2 - s2 * y1) * r,
(s1 * z2 - s2 * z1) * r]);
Vec3.add(tan1[i1], tan1[i1], sdir);
Vec3.add(tan1[i2], tan1[i2], sdir);
Vec3.add(tan1[i3], tan1[i3], sdir);
Vec3.add(tan2[i1], tan2[i1], tdir);
Vec3.add(tan2[i2], tan2[i2], tdir);
Vec3.add(tan2[i3], tan2[i3], tdir);
}
for (a = 0; a < vertexCount; a++) {
n = normal[a];
t = tan1[a];
// Gram-Schmidt orthogonalize
// tangent[a] = (t - n * Dot(n, t)).Normalize();
tmpFloat = Vec3.dot(n, t);
Vec3.scale(tmpVec3, n, tmpFloat);
Vec3.subtract(tmpVec3, t, tmpVec3);
Vec3.normalize(tmpVec3, tmpVec3);
Vec3.copy(tangent[a], tmpVec3);
// Calculate handedness
// tangent[a].w = (Dot(Cross(n, t), tan2[a]) < 0.0F) ? -1.0F : 1.0F;
tangent[a][3] = (Vec3.dot(Vec3.cross(Vec3.create(), n, t), tan2[a]) < 0.0) ? -1.0 : 1.0;
}
this.tangent = tangentBuffer;
return true;
};
return MeshData;
});