1 | import { Vec } from './vector';
|
2 |
|
3 |
|
4 | export class Geometry {
|
5 | constructor(
|
6 | public vertices: Vec[],
|
7 | public faces: number[][],
|
8 | public normals: Vec[],
|
9 | public texCoords: Vec[]
|
10 | ) {}
|
11 |
|
12 | |
13 |
|
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 |
|
35 |
|
36 |
|
37 |
|
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 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
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 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 | const faces: number[][] = [
|
79 |
|
80 | [0, 1, 2],
|
81 | [2, 1, 3],
|
82 |
|
83 | [5, 4, 7],
|
84 | [7, 4, 6],
|
85 |
|
86 | [4, 0, 6],
|
87 | [6, 0, 2],
|
88 |
|
89 | [7, 5, 1],
|
90 | [1, 7, 3],
|
91 |
|
92 | [1, 0, 5],
|
93 | [5, 0, 4],
|
94 |
|
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 |
|
110 |
|
111 |
|
112 | static cube(size = 1.0) {
|
113 | return Geometry.box(size, size, size);
|
114 | }
|
115 |
|
116 | |
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
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 |
|
166 |
|
167 |
|
168 |
|
169 |
|
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 |
|
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 |
|
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 |
|
217 |
|
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 | }
|