@carbonenginejs/core-math 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,103 @@
1
+ import { num } from "../../num.js";
2
+ import { triangulate } from "./earcut.js";
3
+
4
+
5
+ // Ported from ThreeJS
6
+
7
+ /**
8
+ * Calculates the area of a points polygon
9
+ * @param {Array<Float32Array|Array>} points
10
+ * @returns {number}
11
+ */
12
+ export function getShapeArea(points)
13
+ {
14
+ const n = points.length;
15
+ let a = 0.0;
16
+ for (let p = n - 1, q = 0; q < n; p = q++)
17
+ {
18
+ a += points[p][0] * points[q][1] - points[p][0] * points[p][1];
19
+ }
20
+ return a * 0.5;
21
+ }
22
+
23
+ /**
24
+ * Checks if clockwise oriented points
25
+ * @param {Array<Float32Array|Array>} pts
26
+ * @returns {boolean}
27
+ */
28
+ export function isShapeClockWise(points)
29
+ {
30
+ return getShapeArea(points) < 0;
31
+ }
32
+
33
+ /**
34
+ * Removes duplicate start and end points
35
+ * @param {Array<Float32Array|Array>} points
36
+ */
37
+ function removeDupEndPts(points)
38
+ {
39
+ const len = points.length;
40
+ if (len <= 2) return;
41
+
42
+ const
43
+ start = points[0],
44
+ end = points[len - 1];
45
+
46
+ for (let x = 0; x < start.length; x++)
47
+ {
48
+ if (!num.equals(start[x], end[x])) return;
49
+ }
50
+
51
+ points.pop();
52
+ }
53
+
54
+ /**
55
+ * Adds a points to positions array
56
+ * @param {Array} positions
57
+ * @param {Array<Float32Array>} points
58
+ */
59
+ function addContour(positions, points)
60
+ {
61
+ for (let i = 0; i < points.length; i++)
62
+ {
63
+ for (let x = 0; x < points[i].length; x++)
64
+ {
65
+ positions.push(points[i][x]);
66
+ }
67
+ }
68
+ }
69
+
70
+ /**
71
+ *
72
+ * @param points
73
+ * @param holes
74
+ * @returns {*[]}
75
+ */
76
+ export function triangulateShape(points, holes = [])
77
+ {
78
+ const
79
+ positions = [], // flat array of positions like [ x0,y0, x1,y1, x2,y2, ... ]
80
+ holeIndices = [], // array of hole indices
81
+ faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ]
82
+
83
+ removeDupEndPts(points);
84
+ addContour(positions, points);
85
+
86
+ let holeIndex = points.length;
87
+ holes.forEach(removeDupEndPts);
88
+ for (let i = 0; i < holes.length; i++)
89
+ {
90
+ holeIndices.push(holeIndex);
91
+ holeIndex += holes[i].length;
92
+ addContour(positions, holes[i]);
93
+ }
94
+
95
+ const triangles = triangulate(positions, holeIndices);
96
+ for (let i = 0; i < triangles.length; i += 3)
97
+ {
98
+ faces.push(triangles.slice(i, i + 3));
99
+ }
100
+ return faces;
101
+ }
102
+
103
+
@@ -0,0 +1,7 @@
1
+ export * from "./box.js";
2
+ export * from "./cylinder.js";
3
+ export * from "./plane.js";
4
+ export * from "./shape.js";
5
+ export * from "./sphere.js";
6
+ export * from "./torus.js";
7
+ export * from "./json.js";
@@ -0,0 +1,115 @@
1
+ import { toArray } from "../utils.js";
2
+ import { calculateNormals, calculateTangents } from "../vertex.js";
3
+
4
+
5
+ function isAreaLike(value)
6
+ {
7
+ if (!value) return false;
8
+ if (Array.isArray(value)) return !!value.length && typeof value[0] === "object" && value[0] !== null && "start" in value[0];
9
+ return typeof value === "object" && "start" in value;
10
+ }
11
+
12
+
13
+ /**
14
+ * Converts index and vertex buffer data to geometry json
15
+ * @param {Array|TypedArray} indices
16
+ * @param {Array|TypedArray} positions
17
+ * @param {Array|TypedArray} uvs
18
+ * @param {Array|TypedArray|Array|Object} [normals]
19
+ * @param {Array|Object} [areas]
20
+ * @param {Array|TypedArray} [tangents]
21
+ * @throw If required data is not provided
22
+ * @returns {Object}
23
+ */
24
+ export function toJSON(indices, positions, uvs, normals, areas, tangents)
25
+ {
26
+ if (!indices || !positions || !uvs || !indices.length || !positions.length || !uvs.length)
27
+ {
28
+ throw new Error("Invalid inputs");
29
+ }
30
+
31
+ if (isAreaLike(normals))
32
+ {
33
+ const temp = areas;
34
+ areas = normals;
35
+ normals = temp;
36
+ }
37
+
38
+ areas = toArray(areas || { start: 0, count: indices.length });
39
+
40
+ if (!normals || !normals.length)
41
+ {
42
+ normals = calculateNormals(indices, positions);
43
+ }
44
+
45
+ if (!tangents || !tangents.length)
46
+ {
47
+ tangents = calculateTangents(indices, positions, uvs, areas, normals);
48
+ }
49
+
50
+ return {
51
+ meshes: [ {
52
+ name: "",
53
+ vertex: {
54
+ position: positions,
55
+ texcoord0: uvs,
56
+ tangent: tangents,
57
+ normal: normals,
58
+ texcoord1: null,
59
+ binormal: null,
60
+ blendIndice: null,
61
+ blendWeight: null
62
+ },
63
+ indices: areas.map(area =>
64
+ {
65
+ return {
66
+ bytesPerIndex: area.bytesPerIndex || 2,
67
+ start: area.start,
68
+ count: area.count,
69
+ faces: indices.slice(area.start, area.start + area.count)
70
+ };
71
+ })
72
+ } ]
73
+ };
74
+ }
75
+
76
+
77
+ /**
78
+ * Creates a mesh container from geometry json
79
+ * @param {Object} tw2
80
+ * @param {Object} json
81
+ * @param {String} [name=""]
82
+ * @param {Object} [autoCreateMeshAreas={}]
83
+ * @returns {Object}
84
+ */
85
+ export function toContainer(tw2, json, name = "", autoCreateMeshAreas = {})
86
+ {
87
+ const
88
+ container = new tw2.EveChildMesh(),
89
+ mesh = container.mesh = new tw2.Tw2Mesh(),
90
+ res = mesh.geometryResource = new tw2.Tw2GeometryRes();
91
+
92
+ container.name = name;
93
+ res.UpdateFromJSON(json);
94
+ res.OnPrepared();
95
+
96
+ Object
97
+ .keys(autoCreateMeshAreas)
98
+ .forEach(areaName =>
99
+ {
100
+ const effect = autoCreateMeshAreas[areaName] || {};
101
+ for (let m = 0; m < res.meshes.length; m++)
102
+ {
103
+ for (let a = 0; a < res.meshes[m].areas.length; a++)
104
+ {
105
+ const meshArea = new tw2.Tw2MeshArea();
106
+ meshArea.meshIndex = m;
107
+ meshArea.index = a;
108
+ meshArea.effect = tw2.Tw2Effect.from(effect);
109
+ mesh[areaName].push(meshArea);
110
+ }
111
+ }
112
+ });
113
+
114
+ return container;
115
+ }
@@ -0,0 +1,148 @@
1
+ import { num } from "../num.js";
2
+ import { vec2 } from "../vec2.js";
3
+ import { vec3 } from "../vec3.js";
4
+ import { toJSON } from "./json.js";
5
+
6
+ /**
7
+ * Creates a lathe
8
+ * @author Three.js converted
9
+ **/
10
+ export function createLathe(options)
11
+ {
12
+ let {
13
+ points = [
14
+ vec2.fromValues(0, -0.5),
15
+ vec2.fromValues(0.5, 0),
16
+ vec2.fromValues(0, 0.5)
17
+ ],
18
+ segments = 12,
19
+ phiStart = 0,
20
+ phiLength = Math.PI * 2
21
+ } = options;
22
+
23
+ segments = Math.floor(segments);
24
+
25
+ // clamp phiLength so it's in range of [ 0, 2PI ]
26
+ phiLength = num.clamp(phiLength, 0, Math.PI * 2);
27
+
28
+ // buffers
29
+ const
30
+ indices = [],
31
+ positions = [],
32
+ uvs = [],
33
+ initNormals = [],
34
+ normals = [];
35
+
36
+ // helper variables
37
+ const
38
+ inverseSegments = 1.0 / segments,
39
+ normal = vec3.alloc(),
40
+ curNormal = vec3.alloc(),
41
+ prevNormal = vec3.alloc();
42
+
43
+ let dx = 0,
44
+ dy = 0;
45
+
46
+ // pre-compute normals for initial "meridian"
47
+ for (let j = 0; j <= (points.length - 1); j++)
48
+ {
49
+ switch (j)
50
+ {
51
+ case 0: // special handling for 1st vertex on path
52
+ dx = points[j + 1][0] - points[j][0];
53
+ dy = points[j + 1][1] - points[j][1];
54
+
55
+ normal[0] = dy * 1.0;
56
+ normal[1] = -dx;
57
+ normal[2] = dy * 0.0;
58
+
59
+ vec3.copy(prevNormal, normal);
60
+ vec3.normalize(normal, normal);
61
+ initNormals.push(normal[0], normal[1], normal[2]);
62
+ break;
63
+
64
+ case (points.length - 1): // special handling for last Vertex on path
65
+ initNormals.push(prevNormal[0], prevNormal[1], prevNormal[2]);
66
+ break;
67
+
68
+ default: // default handling for all positions in between
69
+ dx = points[j + 1][0] - points[j][0];
70
+ dy = points[j + 1][1] - points[j][1];
71
+
72
+ normal[0] = dy * 1.0;
73
+ normal[1] = -dx;
74
+ normal[2] = dy * 0.0;
75
+ vec3.copy(curNormal, normal);
76
+
77
+ normal[0] += prevNormal[0];
78
+ normal[1] += prevNormal[1];
79
+ normal[2] += prevNormal[2];
80
+ vec3.normalize(normal, normal);
81
+
82
+ initNormals.push(normal[0], normal[1], normal[2]);
83
+ vec3.copy(prevNormal, curNormal);
84
+ }
85
+ }
86
+
87
+ // generate positions, uvs and normals
88
+ for (let i = 0; i <= segments; i++)
89
+ {
90
+ const
91
+ phi = phiStart + i * inverseSegments * phiLength,
92
+ sin = Math.sin(phi),
93
+ cos = Math.cos(phi);
94
+
95
+ for (let j = 0; j <= (points.length - 1); j++)
96
+ {
97
+ // vertex
98
+ positions.push(
99
+ points[j][0] * sin,
100
+ points[j][1],
101
+ points[j][0] * cos
102
+ );
103
+ // uv
104
+ uvs.push(
105
+ i / segments,
106
+ j / (points.length - 1)
107
+ );
108
+ // normal
109
+ normals.push(
110
+ initNormals[3 * j + 0] * sin,
111
+ initNormals[3 * j + 1],
112
+ initNormals[3 * j + 0] * cos
113
+ );
114
+ }
115
+ }
116
+
117
+ vec3.unalloc(normal);
118
+ vec3.unalloc(curNormal);
119
+ vec3.unalloc(prevNormal);
120
+
121
+ // indices
122
+ for (let i = 0; i < segments; i++)
123
+ {
124
+ for (let j = 0; j < (points.length - 1); j++)
125
+ {
126
+ const
127
+ base = j + i * points.length,
128
+ a = base,
129
+ b = base + points.length,
130
+ c = base + points.length + 1,
131
+ d = base + 1;
132
+
133
+ // faces
134
+ indices.push(a, b, d);
135
+ indices.push(c, d, b);
136
+ }
137
+ }
138
+
139
+ const result = toJSON(indices, positions, uvs, normals);
140
+ result.factory = createLathe;
141
+ result.options = {
142
+ points,
143
+ segments,
144
+ phiStart,
145
+ phiLength,
146
+ };
147
+ return result;
148
+ }
File without changes
@@ -0,0 +1,76 @@
1
+ import { toJSON } from "./json.js";
2
+
3
+ /**
4
+ * Creates a plane
5
+ * @author Three.js converted
6
+ * @param {Object} [options={}]
7
+ * @param {Number} [options.width=1]
8
+ * @param {Number} [options.height=1]
9
+ * @param {Number} [widthSegments=1]
10
+ * @param {Number} [heightSegments=1]
11
+ * @returns {Object}
12
+ */
13
+ export function createPlane(options = {})
14
+ {
15
+ const {
16
+ width = 1,
17
+ height = 1,
18
+ widthSegments = 1,
19
+ heightSegments = 1
20
+ } = options;
21
+
22
+ const
23
+ indices = [],
24
+ positions = [],
25
+ normals = [],
26
+ uvs = [];
27
+
28
+ const
29
+ widthHalf = width / 2,
30
+ heightHalf = height / 2,
31
+ gridX = Math.floor(widthSegments),
32
+ gridY = Math.floor(heightSegments),
33
+ gridX1 = gridX + 1,
34
+ gridY1 = gridY + 1,
35
+ segmentWidth = width / gridX,
36
+ segmentHeight = height / gridY;
37
+
38
+ for (let iy = 0; iy < gridY1; iy++)
39
+ {
40
+ const y = iy * segmentHeight - heightHalf;
41
+ for (let ix = 0; ix < gridX1; ix++)
42
+ {
43
+ const x = ix * segmentWidth - widthHalf;
44
+ positions.push(x, -y, 0);
45
+ normals.push(0, 0, 1);
46
+ uvs.push(ix / gridX);
47
+ uvs.push(1 - (iy / gridY));
48
+ }
49
+ }
50
+
51
+ for (let iy = 0; iy < gridY; iy++)
52
+ {
53
+ for (let ix = 0; ix < gridX; ix++)
54
+ {
55
+ const
56
+ a = ix + gridX1 * iy,
57
+ b = ix + gridX1 * (iy + 1),
58
+ c = (ix + 1) + gridX1 * (iy + 1),
59
+ d = (ix + 1) + gridX1 * iy;
60
+
61
+ indices.push(a, b, d);
62
+ indices.push(b, c, d);
63
+ }
64
+ }
65
+
66
+ // Normalize
67
+ const result = toJSON(indices, positions, uvs, normals);
68
+ result.factory = createPlane;
69
+ result.options = {
70
+ width,
71
+ height,
72
+ widthSegments,
73
+ heightSegments
74
+ };
75
+ return result;
76
+ }
@@ -0,0 +1,95 @@
1
+ import { toArray } from "../utils.js";
2
+ import { isShapeClockWise, triangulateShape } from "./helpers/misc.js";
3
+ import { toJSON } from "./json.js";
4
+
5
+ /**
6
+ * Creates a shape
7
+ * @author Three.js converted
8
+ **/
9
+ export function createShape(shapes)
10
+ {
11
+ shapes = toArray(shapes);
12
+
13
+ const
14
+ indices = [],
15
+ positions = [],
16
+ normals = [],
17
+ uvs = [];
18
+
19
+ let areaStart = 0,
20
+ areaCount = 0,
21
+ areas = [];
22
+
23
+ for (let i = 0; i < shapes.length; i++)
24
+ {
25
+ addShape(shapes[i]);
26
+ addArea(areaStart, areaCount, i);
27
+ areaStart += areaCount;
28
+ areaCount = 0;
29
+ }
30
+
31
+ function addArea(start, count, index)
32
+ {
33
+ areas.push({ start, count, index });
34
+ }
35
+
36
+ function addShape(shape)
37
+ {
38
+ let shapeVertices = shape.positions,
39
+ shapeHoles = shape.holes || [];
40
+
41
+ const indexOffset = positions.length / 3;
42
+
43
+ // check direction of positions
44
+ if (isShapeClockWise(shapeVertices) === false)
45
+ {
46
+ shapeVertices = shapeVertices.reverse();
47
+ }
48
+
49
+ for (let i = 0, l = shapeHoles.length; i < l; i++)
50
+ {
51
+ const shapeHole = shapeHoles[i];
52
+ if (isShapeClockWise(shapeHole) === true)
53
+ {
54
+ shapeHoles[i] = shapeHole.reverse();
55
+ }
56
+ }
57
+
58
+ const faces = triangulateShape(shapeVertices, shapeHoles);
59
+
60
+ // join positions of inner and outer paths to a single array
61
+ for (let i = 0, l = shapeHoles.length; i < l; i++)
62
+ {
63
+ const shapeHole = shapeHoles[i];
64
+ shapeVertices = shapeVertices.concat(shapeHole);
65
+ }
66
+
67
+ // positions, normals, uvs
68
+ for (let i = 0, l = shapeVertices.length; i < l; i++)
69
+ {
70
+ const vertex = shapeVertices[i];
71
+ positions.push(vertex[0], vertex[1], 0);
72
+ normals.push(0, 0, 1);
73
+ uvs.push(vertex[0], vertex[1]); // world uvs
74
+ }
75
+
76
+ // indices
77
+ for (let i = 0, l = faces.length; i < l; i++)
78
+ {
79
+ const
80
+ face = faces[i],
81
+ a = face[0] + indexOffset,
82
+ b = face[1] + indexOffset,
83
+ c = face[2] + indexOffset;
84
+
85
+ indices.push(a, b, c);
86
+ areaCount += 3;
87
+ }
88
+ }
89
+
90
+ // Normalize
91
+ const result = toJSON(indices, positions, uvs, areas, normals);
92
+ result.factory = createShape;
93
+ result.options = shapes;
94
+ return result;
95
+ }
@@ -0,0 +1,116 @@
1
+ import { vec3 } from "../vec3.js";
2
+ import { toJSON } from "./json.js";
3
+
4
+ /**
5
+ * Creates a sphere
6
+ * @author Three.js converted
7
+ *
8
+ *
9
+ **/
10
+ export function createSphere(options = {})
11
+ {
12
+
13
+ let {
14
+ radius = 1,
15
+ widthSegments = 32,
16
+ heightSegments = 16,
17
+ phiStart = 0,
18
+ phiLength = Math.PI * 2,
19
+ thetaStart = 0,
20
+ thetaLength = Math.PI
21
+ } = options;
22
+
23
+ widthSegments = Math.max(3, Math.floor(widthSegments));
24
+ heightSegments = Math.max(2, Math.floor(heightSegments));
25
+
26
+ const
27
+ thetaEnd = Math.min(thetaStart + thetaLength, Math.PI),
28
+ grid = [],
29
+ vertex = vec3.alloc(),
30
+ normal = vec3.alloc();
31
+
32
+ let index = 0;
33
+
34
+ // buffers
35
+ const
36
+ indices = [],
37
+ vertices = [],
38
+ normals = [],
39
+ uvs = [];
40
+
41
+ // generate vertices, normals and uvs
42
+ for (let iy = 0; iy <= heightSegments; iy++)
43
+ {
44
+ const
45
+ verticesRow = [],
46
+ v = iy / heightSegments;
47
+
48
+ // special case for the poles
49
+ let uOffset = 0;
50
+ if (iy === 0 && thetaStart === 0)
51
+ {
52
+ uOffset = 0.5 / widthSegments;
53
+ }
54
+ else if (iy === heightSegments && thetaEnd === Math.PI)
55
+ {
56
+ uOffset = -0.5 / widthSegments;
57
+ }
58
+
59
+ for (let ix = 0; ix <= widthSegments; ix++)
60
+ {
61
+ const u = ix / widthSegments;
62
+
63
+ // vertex
64
+ vertex[0] = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength);
65
+ vertex[1] = radius * Math.cos(thetaStart + v * thetaLength);
66
+ vertex[2] = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength);
67
+ vertices.push(vertex[0], vertex[1], vertex[2]);
68
+
69
+ // normal
70
+ vec3.copy(normal, vertex);
71
+ vec3.normalize(normal, normal);
72
+ normals.push(normal[0], normal[1], normal[2]);
73
+
74
+ // uv
75
+ uvs.push(u + uOffset, 1 - v);
76
+ verticesRow.push(index++);
77
+ }
78
+
79
+ grid.push(verticesRow);
80
+ }
81
+
82
+ // indices
83
+ for (let iy = 0; iy < heightSegments; iy++)
84
+ {
85
+ for (let ix = 0; ix < widthSegments; ix++)
86
+ {
87
+ const
88
+ a = grid[iy][ix + 1],
89
+ b = grid[iy][ix],
90
+ c = grid[iy + 1][ix],
91
+ d = grid[iy + 1][ix + 1];
92
+
93
+ if (iy !== 0 || thetaStart > 0) indices.push(a, b, d);
94
+ if (iy !== heightSegments - 1 || thetaEnd < Math.PI) indices.push(b, c, d);
95
+ }
96
+ }
97
+
98
+ vec3.unalloc(vertex);
99
+ vec3.unalloc(normal);
100
+
101
+ const result = toJSON(indices, vertices, uvs, normals);
102
+ result.shape = {
103
+ factory: createSphere,
104
+ options: {
105
+ radius,
106
+ widthSegments,
107
+ heightSegments,
108
+ phiStart,
109
+ phiLength,
110
+ thetaStart,
111
+ thetaLength
112
+ }
113
+ };
114
+
115
+ return result;
116
+ }