diff --git a/src/render/common.ts b/src/render/common.ts index 951ac5cf..441df2f3 100755 --- a/src/render/common.ts +++ b/src/render/common.ts @@ -30,4 +30,10 @@ export enum EGLTexWrapType { GL_REPEAT, //设置为gl对应的常量 GL_MIRRORED_REPEAT, GL_CLAMP_TO_EDGE +} + +export enum EPlaneLoc { + FRONT, // 在平面的正面 + BACK, // 在平面的背面 + COPLANAR // 与平面共面 } \ No newline at end of file diff --git a/src/render/core/Primitives.ts b/src/render/core/Primitives.ts new file mode 100755 index 00000000..4407dccb --- /dev/null +++ b/src/render/core/Primitives.ts @@ -0,0 +1,128 @@ +import { vec2, vec3, vec4, mat4 } from "../math"; +import { GLAttribBits, GLAttribState } from "../webgl/WebGLAttribState"; +import { GLStaticMesh } from "../webgl/WebGLMesh"; + +export class GeometryData { + + /** + * @var 点的位置 + */ + public positions: vec3[] = []; + + /** + * @var uv坐标 + */ + public uvs: vec2[] = []; + + /** + * @var 法线坐标 + */ + public normals: vec3[] = []; + + /** + * @var 颜色坐标 + */ + public colors: vec4[] = []; + + /** + * @var 切线坐标 + */ + public tangents: vec4[] = []; + + /** + * @var 索引 + */ + public indices: number[] = []; + + /** + * 生成一个attrib bit + * @returns + */ + public getAttribBits(): GLAttribBits { + + if (this.positions.length === 0) { + throw new Error("position data is required"); + } + + let ret: GLAttribBits = GLAttribState.POSITION_BIT; + + if (this.uvs.length > 0) { + ret |= GLAttribState.TEXCOORD_BIT; + } + + if (this.normals.length > 0) { + ret |= GLAttribState.NORMAL_BIT; + } + + if (this.colors.length > 0) { + ret |= GLAttribState.COLOR_BIT; + } + + if (this.tangents.length > 0) { + ret |= GLAttribState.TANGENT_BIT; + } + + return ret; + } + + public makeStaticVAO(gl: WebGLRenderingContext, needNormal: boolean = false, needUV: boolean = true): GLStaticMesh { + + let bits: GLAttribBits = this.getAttribBits(); + + if (needNormal === false) { + bits &= ~GLAttribState.NORMAL_BIT; + } + + if (needUV === false) { + bits &= ~GLAttribState.TEXCOORD_BIT; + } + + let stride: number = GLAttribState.getVertexBytesStride(bits); + let step: number = stride / Float32Array.BYTES_PER_ELEMENT; + let arrayBuffer: ArrayBuffer = new ArrayBuffer(stride * this.positions.length); + let buffer: Float32Array = new Float32Array(arrayBuffer); + + for (let i: number = 0; i < this.positions.length; i++) { + let j: number = i * step; + let idx: number = 0; + + //位置 + buffer[j + (idx++)] = this.positions[i].x; + buffer[j + (idx++)] = this.positions[i].y; + buffer[j + (idx++)] = this.positions[i].z; + + //法线 + if (bits & GLAttribState.NORMAL_BIT) { + buffer[j + (idx++)] = this.normals[i].x; + buffer[j + (idx++)] = this.normals[i].y; + buffer[j + (idx++)] = this.normals[i].z; + } + + //纹理 + if (bits & GLAttribState.TEXCOORD_BIT) { + buffer[j + (idx++)] = this.uvs[i].x; + buffer[j + (idx++)] = this.uvs[i].y; + } + + //颜色 + if (bits & GLAttribState.COLOR_BIT) { + buffer[j + (idx++)] = this.colors[i].x; + buffer[j + (idx++)] = this.colors[i].y; + buffer[j + (idx++)] = this.colors[i].z; + buffer[j + (idx++)] = this.colors[i].w; + } + + //切线 + if (bits & GLAttribState.TANGENT_BIT) { + buffer[j + (idx++)] = this.tangents[i].x; + buffer[j + (idx++)] = this.tangents[i].y; + buffer[j + (idx++)] = this.tangents[i].z; + buffer[j + (idx++)] = this.tangents[i].w; + } + } + + let mesh: GLStaticMesh = new GLStaticMesh(gl, bits, buffer, this.indices.length > 0 ? new Uint16Array(this.indices) : null); + + return mesh; + } +} \ No newline at end of file diff --git a/src/render/math/Util.ts b/src/render/math/Util.ts index 1f3cd2e6..4c81e67e 100755 --- a/src/render/math/Util.ts +++ b/src/render/math/Util.ts @@ -1,3 +1,7 @@ +import { EPSILON, EPlaneLoc } from "../common"; +import { vec2, vec3, vec4, mat4 } from "."; +import { quat } from "./Quat"; + export class Util { /** * 判断参数x是否是2的n次方,即x是不是1、2、4、8、16、32、64、..... @@ -26,4 +30,385 @@ export class Util { } return x + 1; } + + /** + * 角度/弧度互转函数 + * @param degree + * @returns + */ + public static toRadian(degree: number): number { + return degree * Math.PI / 180; + } + + /** + * 浮点容差相等函数 + * @param left + * @param right + * @returns + */ + public static numberEuaqls(left: number, right: number): boolean { + + if (Math.abs(left - right) > EPSILON) { + return false; + } + return true; + } + + /** + * + * @param x + * @param min + * @param max + * @returns + */ + public static clamp(x: number, min: number, max: number): number { + return (x < min) ? min : (x > max) ? max : x; + } + + /** + * + * @param localPt + * @param mvp + * @param viewport + * @param viewportPt + * @returns + */ + public static obj2GLViewportSpace(localPt: vec3, mvp: mat4, viewport: Int32Array | Float32Array, viewportPt: vec3): boolean { + + let v: vec4 = new vec4([localPt.x, localPt.y, localPt.z, 1.0]); + //这里相当于 v = mvp * v, 将顶点从local坐标系变换到投影坐标系 + mvp.multiplyVec4(v, v); + + //如果变换后的w为0,则返回false + if (v.w === 0.0) { + return false; + } + + // 将裁剪坐标系的点的x / y / z分量除以w,得到normalized坐标值[ -1 , 1 ]之间 + v.x /= v.w; + v.y /= v.w; + v.z /= v.w; + + // [-1 , 1]标示的点变换到视口坐标系 + v.x = v.x * 0.5 + 0.5; + v.y = v.y * 0.5 + 0.5; + v.z = v.z * 0.5 + 0.5; + + // 视口坐标系再变换到屏幕坐标系 + viewportPt.x = v.x * viewport[2] + viewport[0]; + viewportPt.y = v.y * viewport[3] + viewport[1]; + viewportPt.z = v.z; + + return true; + } + + /** + * 给出3个点,计算法向 + * @param a + * @param b + * @param c + * @param result + * @returns + */ + public static computeNormal(a: vec3, b: vec3, c: vec3, result: vec3 | null): vec3 { + if (!result) { + result = new vec3(); + } + + let l0: vec3 = new vec3(); + let l1: vec3 = new vec3(); + + vec3.difference(b, a, l0); + vec3.difference(c, a, l1); + vec3.cross(l0, l1, result); + result.normalize(); + + return result; + } + + /** + * 通过3个点,创建一个平面 + * @param a + * @param b + * @param c + * @param result + * @returns + */ + public static planeFromPoints(a: vec3, b: vec3, c: vec3, result: vec4 | null = null): vec4 { + if (!result) { + result = new vec4(); + } + + let normal: vec3 = new vec3(); + Util.computeNormal(a, b, c, normal); + let d: number = -vec3.dot(normal, a); + + result.x = normal.x; + result.y = normal.y; + result.z = normal.z; + result.w = d; + + return result; + } + + /** + * 通过平面的一个点和法线创建一个平面 + * @param p + * @param normal + * @param result + * @returns + */ + public static planeFromPointNormal(p: vec3, normal: vec3, result: vec4 | null = null): vec4 { + if (!result) { + result = new vec4(); + } + let d: number = -vec3.dot(normal, p); + + result.x = normal.x; + result.y = normal.y; + result.z = normal.z; + result.w = d; + + return result; + } + + /** + * 通过多边形创建一个屏幕 + * @param polygon + * @returns + */ + public static planeFromPolygon(polygon: vec3[]): vec4 { + if (polygon.length < 3) { + throw new Error("The number of vertices of polygon must be greater than or equal to 3 "); + } + return Util.planeFromPoints(polygon[0], polygon[1], polygon[2]); + } + + /** + * 点到平面距离 + * @param plane + * @param point + * @returns + */ + public static planeDistanceFromPoint(plane: vec4, point: vec3): number { + return (point.x * plane.x + point.y * plane.y + point.z * plane.z + plane.w); + } + + /** + * 判断点和平面的关系 + * @param plane + * @param point + * @returns + */ + public static planeTestPoint(plane: vec4, point: vec3): EPlaneLoc { + let num: number = Util.planeDistanceFromPoint(plane, point); + if (num > EPSILON) { + return EPlaneLoc.FRONT; + } else if (num < - EPSILON) { + return EPlaneLoc.BACK; + } else { + return EPlaneLoc.COPLANAR; + } + } + + /** + * 平面单位化 + * @param plane + * @returns + */ + public static planeNormalize(plane: vec4): number { + let length: number; + length = Math.sqrt(plane.x * plane.x + plane.y * plane.y + plane.z * plane.z); + if (length === 0) { + throw new Error("The plane area is 0"); + } + plane.x /= length; + plane.y /= length; + plane.z /= length; + plane.w /= length; + return length; + } + + /** + * 获取包围盒 + * @param v + * @param mins + * @param maxs + */ + public static boundBoxAddPoint(v: vec3, mins: vec3, maxs: vec3): void { + + if (v.x < mins.x) { + mins.x = v.x; + } + if (v.x > maxs.x) { + maxs.x = v.x; + } + + if (v.y < mins.y) { + mins.y = v.y; + } + if (v.y > maxs.y) { + maxs.y = v.y; + } + + if (v.z < mins.z) { + mins.z = v.z; + } + if (v.z > maxs.z) { + maxs.z = v.z; + } + } + + /** + * 清除包围盒 + * @param mins + * @param maxs + * @param value + */ + public static boundBoxClear(mins: vec3, maxs: vec3, value: number = Infinity): void { + mins.x = mins.y = mins.z = value; // 初始化时,让mins表示浮点数的最大范围 + maxs.x = maxs.y = maxs.z = -value; // 初始化是,让maxs表示浮点数的最小范围 + } + + /** + * 获取包围盒的中心点坐标 + * @param mins + * @param maxs + * @param out + * @returns + */ + public static boundBoxGetCenter(mins: vec3, maxs: vec3, out: vec3 | null = null): vec3 { + if (out === null) { + out = new vec3(); + } + //(maxs + mins) * 0.5 + vec3.sum(mins, maxs, out); + out.scale(0.5); + return out; + } + + /** + * 获取包围盒的8个顶点 + * @param mins + * @param maxs + * @param pts8 + * /3--------/7 | + * / | / | + * / | / | + * 1---------5 | + * | /2- - -|- -6 + * | / | / + * |/ | / + * 0---------4/ + */ + public static boundBoxGet8Points(mins: vec3, maxs: vec3, pts8: vec3[]): void { + + //获取中心点 + let center: vec3 = Util.boundBoxGetCenter(mins, maxs); + + //获取最大点到中心点之间的距离向量 + let maxs2center: vec3 = vec3.difference(center, maxs); + + pts8.push(new vec3([center.x + maxs2center.x, center.y + maxs2center.y, center.z + maxs2center.z])); // 0 + pts8.push(new vec3([center.x + maxs2center.x, center.y - maxs2center.y, center.z + maxs2center.z])); // 1 + pts8.push(new vec3([center.x + maxs2center.x, center.y + maxs2center.y, center.z - maxs2center.z])); // 2 + pts8.push(new vec3([center.x + maxs2center.x, center.y - maxs2center.y, center.z - maxs2center.z])); // 3 + pts8.push(new vec3([center.x - maxs2center.x, center.y + maxs2center.y, center.z + maxs2center.z])); // 4 + pts8.push(new vec3([center.x - maxs2center.x, center.y - maxs2center.y, center.z + maxs2center.z])); // 5 + pts8.push(new vec3([center.x - maxs2center.x, center.y + maxs2center.y, center.z - maxs2center.z])); // 6 + pts8.push(new vec3([center.x - maxs2center.x, center.y - maxs2center.y, center.z - maxs2center.z])); // 7 + } + + /** + * 包围盒坐标变换 + * @param mat + * @param mins + * @param maxs + */ + public static boundBoxTransform(mat: mat4, mins: vec3, maxs: vec3): void { + // 获得局部坐标系表示的AABB的8个顶点坐标 + let pts: vec3[] = []; + Util.boundBoxGet8Points(mins, maxs, pts); + + //变换后的顶点 + let out: vec3 = new vec3(); + + for (let i: number = 0; i < pts.length; i++) { + // 将局部坐标表示的顶点变换到mat坐标空间中去,变换后的结果放在out变量中 + mat.multiplyVec3(pts[i], out); + Util.boundBoxAddPoint(out, mins, maxs); + } + } + + /** + * 判断某个点是否在包围盒内 + * @param point + * @param mins + * @param maxs + */ + public static boundBoxContainPoint(point: vec3, mins: vec3, maxs: vec3): boolean { + return (point.x >= mins.x && point.x <= maxs.x && point.y >= mins.y && point.y <= maxs.y && point.z >= mins.z && point.z <= maxs.z); + } + + /** + * + * @param min1 判断包围盒1 是的在包围盒2内 + * @param max1 + * @param min2 + * @param max2 + * @returns + */ + public static boundBoxBoundBoxOverlap(min1: vec3, max1: vec3, min2: vec3, max2: vec3): boolean { + if (min1.x > max2.x) return false; + if (max1.x < min2.x) return false; + if (min1.y > max2.y) return false; + if (max1.y < min2.y) return false; + if (min1.z > max2.z) return false; + if (max1.z < min2.z) return false; + return true; + } + + /** + * 交换y轴 和 z轴 + * @param v + * @param scale + */ + public static convertVec3IDCoord2GLCoord(v: vec3, scale: number = 10.0): void { + let f: number = v.y; + v.y = v.z; + v.z = -f; + if (!Util.numberEuaqls(scale, 0) && !Util.numberEuaqls(scale, 1.0)) { + v.x /= scale; + v.y /= scale; + v.z /= scale; + } + } + + /** + * + * @param v + */ + public static convertVec2IDCoord2GLCoord(v: vec2): void { + v.y = 1.0 - v.y; + } + + /** + * + * @param pos + * @param q + * @param dest + * @returns + */ + public static matrixFrom(pos: vec3, q: quat, dest: mat4 | null = null): mat4 { + if (dest === null) { + dest = new mat4(); + } + + q.toMat4(dest); + dest.values[12] = pos.x; + dest.values[13] = pos.y; + dest.values[14] = pos.z; + + return dest; + } } \ No newline at end of file