UNPKG

6.04 kBPlain TextView Raw
1import { Vec } from './vector';
2
3/** @class Geometry */
4export class Geometry {
5 constructor(
6 public vertices: Vec[],
7 public faces: number[][],
8 public normals: Vec[],
9 public texCoords: Vec[]
10 ) {}
11
12 /**
13 * converts to triangle array
14 */
15 toTriangles() {
16 const { faces, vertices } = this;
17 return faces
18 .map((face) => {
19 if (face.length === 3) {
20 return face.map((vertexIndex) => vertices[vertexIndex]);
21 }
22 if (face.length === 4) {
23 const q = face.map((vertexIndex) => vertices[vertexIndex]);
24 return [q[0], q[1], q[3], q[3], q[1], q[2]];
25 }
26 throw Error('not supported');
27 })
28 .flat()
29 .map((v) => v.toArray())
30 .flat();
31 }
32
33 /**
34 * Calculate the surface normal of a triangle
35 * @param p1 3d vector of point 1
36 * @param p2 3d vector of point 2
37 * @param p3 3d vector of point 3
38 */
39 static calculateSurfaceNormal(p1: Vec, p2: Vec, p3: Vec): Vec {
40 const u = p2.sub(p1);
41 const v = p3.sub(p1);
42 return new Vec(
43 u.x * v.z - u.z * v.y,
44 u.z * v.x - u.x * v.z,
45 u.x * v.y - u.y * v.x
46 );
47 }
48
49 /**
50 * Create a box geometry with the sizes a * b * c,
51 * centered at (0, 0, 0), 2 triangles per side.
52 *
53 * @name box
54 * @param {number} sizeA
55 * @param {number} sizeB
56 * @param {number} sizeC
57 */
58 static box(sizeA = 1.0, sizeB = 1.0, sizeC = 1.0) {
59 const a = sizeA * 0.5;
60 const b = sizeB * 0.5;
61 const c = sizeC * 0.5;
62 const vertices = [
63 [-a, -b, -c],
64 [a, -b, -c],
65 [-a, b, -c],
66 [a, b, -c],
67 [-a, -b, c],
68 [a, -b, c],
69 [-a, b, c],
70 [a, b, c],
71 ].map((v) => new Vec(...v));
72 // 0______1
73 // 4/|____5/|
74 // |2|____|_|3
75 // |/ ____|/
76 // 6 7
77
78 const faces: number[][] = [
79 // back
80 [0, 1, 2],
81 [2, 1, 3],
82 // front
83 [5, 4, 7],
84 [7, 4, 6],
85 // left
86 [4, 0, 6],
87 [6, 0, 2],
88 // right
89 [7, 5, 1],
90 [1, 7, 3],
91 // top
92 [1, 0, 5],
93 [5, 0, 4],
94 // bottom
95 [2, 3, 6],
96 [6, 3, 7],
97 ];
98 const normals = faces.map((f) =>
99 Geometry.calculateSurfaceNormal(
100 vertices[f[0]],
101 vertices[f[1]],
102 vertices[f[2]]
103 )
104 );
105 return new Geometry(vertices, faces, normals, []);
106 }
107
108 /**
109 * create a cube
110 * @param size
111 */
112 static cube(size = 1.0) {
113 return Geometry.box(size, size, size);
114 }
115
116 /**
117 * create a plane grid mesh
118 * @param x x-coord of the top left corner
119 * @param y y-coord of the top left corner
120 * @param width width of the plane
121 * @param height height of the plane
122 * @param rows number of rows
123 * @param cols number of columns
124 */
125 static grid(
126 x: number,
127 y: number,
128 width: number,
129 height: number,
130 rows: number,
131 cols: number
132 ): Geometry {
133 const deltaX = width / cols;
134 const deltaY = height / rows;
135 const vertices: Vec[] = Array((cols + 1) * (rows + 1))
136 .fill(0)
137 .map((_, i) => {
138 const ix = i % cols;
139 const iy = (i / cols) | 0;
140 return new Vec(x + ix * deltaX, y + iy * deltaY, 0);
141 });
142 const faces: number[][] = Array(rows * cols)
143 .fill(0)
144 .map((_, i) => {
145 const ix = i % cols;
146 const iy = (i / cols) | 0;
147 const idx = iy * rows + ix;
148 return [
149 [idx, idx + 1, idx + rows],
150 [idx + 1, idx + rows + 1, idx + rows],
151 ];
152 })
153 .flat(1);
154 const normals = faces.map((f) =>
155 Geometry.calculateSurfaceNormal(
156 vertices[f[0]],
157 vertices[f[1]],
158 vertices[f[2]]
159 )
160 );
161 return new Geometry(vertices, faces, normals, []);
162 }
163
164 /**
165 * Create sphere geometry
166 * @param r radius
167 * @param sides number of sides (around the sphere)
168 * @param segments number of segments (from top to bottom)
169 * @see adapted from https://vorg.github.io/pex/docs/pex-gen/Sphere.html
170 */
171 static sphere(r = 0.5, sides = 36, segments = 18) {
172 const vertices: Vec[] = [];
173 const texCoords = [];
174 const faces = [];
175
176 const dphi = 360 / sides;
177 const dtheta = 180 / segments;
178
179 const evalPos = (theta: number, phi: number) => {
180 const deg = Math.PI / 180.0;
181 var pos = new Vec(
182 r * Math.sin(theta * deg) * Math.sin(phi * deg),
183 r * Math.cos(theta * deg),
184 r * Math.sin(theta * deg) * Math.cos(phi * deg)
185 );
186 return pos;
187 };
188 for (let segment = 0; segment <= segments; segment++) {
189 const theta = segment * dtheta;
190 for (let side = 0; side <= sides; side++) {
191 const phi = side * dphi;
192 const pos = evalPos(theta, phi);
193 const texCoord = new Vec(phi / 360.0, theta / 180.0);
194
195 vertices.push(pos);
196 texCoords.push(texCoord);
197
198 if (segment === segments) continue;
199 if (side === sides) continue;
200
201 if (segment == 0) {
202 // first segment uses triangles
203 faces.push([
204 segment * (sides + 1) + side,
205 (segment + 1) * (sides + 1) + side,
206 (segment + 1) * (sides + 1) + side + 1,
207 ]);
208 } else if (segment == segments - 1) {
209 // last segment also uses triangles
210 faces.push([
211 segment * (sides + 1) + side,
212 (segment + 1) * (sides + 1) + side + 1,
213 segment * (sides + 1) + side + 1,
214 ]);
215 } else {
216 // A --- B
217 // D --- C
218 const A = segment * (sides + 1) + side;
219 const B = (segment + 1) * (sides + 1) + side;
220 const C = (segment + 1) * (sides + 1) + side + 1;
221 const D = segment * (sides + 1) + side + 1;
222
223 faces.push([A, B, D]);
224 faces.push([B, C, D]);
225 }
226 }
227 }
228 const normals = faces.map((f) =>
229 Geometry.calculateSurfaceNormal(
230 vertices[f[0]],
231 vertices[f[1]],
232 vertices[f[2]]
233 )
234 );
235 return new Geometry(vertices, faces, normals, texCoords);
236 }
237}