Skip to content

Commit b18f978

Browse files
committed
Gizmos and state machine
1 parent b617e9e commit b18f978

6 files changed

Lines changed: 269 additions & 11 deletions

File tree

src/math/matrix_operators.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,53 @@ export function normalize_vec3(v:vec3):void{
259259
v[1] *= inv_len;
260260
v[2] *= inv_len;
261261
}
262+
263+
export function inverse_mat4(m: mat4): mat4 {
264+
const a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3];
265+
const a10 = m[4], a11 = m[5], a12 = m[6], a13 = m[7];
266+
const a20 = m[8], a21 = m[9], a22 = m[10], a23 = m[11];
267+
const a30 = m[12], a31 = m[13], a32 = m[14], a33 = m[15];
268+
269+
const b00 = a00 * a11 - a01 * a10;
270+
const b01 = a00 * a12 - a02 * a10;
271+
const b02 = a00 * a13 - a03 * a10;
272+
const b03 = a01 * a12 - a02 * a11;
273+
const b04 = a01 * a13 - a03 * a11;
274+
const b05 = a02 * a13 - a03 * a12;
275+
const b06 = a20 * a31 - a21 * a30;
276+
const b07 = a20 * a32 - a22 * a30;
277+
const b08 = a20 * a33 - a23 * a30;
278+
const b09 = a21 * a32 - a22 * a31;
279+
const b10 = a21 * a33 - a23 * a31;
280+
const b11 = a22 * a33 - a23 * a32;
281+
282+
let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
283+
284+
if (!det) {
285+
return null as any;
286+
}
287+
288+
det = 1.0 / det;
289+
290+
return mat4(
291+
(a11 * b11 - a12 * b10 + a13 * b09) * det,
292+
(a02 * b10 - a01 * b11 - a03 * b09) * det,
293+
(a31 * b05 - a32 * b04 + a33 * b03) * det,
294+
(a22 * b04 - a21 * b05 - a23 * b03) * det,
295+
296+
(a12 * b08 - a10 * b11 - a13 * b07) * det,
297+
(a00 * b11 - a02 * b08 + a03 * b07) * det,
298+
(a32 * b02 - a30 * b05 - a33 * b01) * det,
299+
(a20 * b05 - a22 * b02 + a23 * b01) * det,
300+
301+
(a10 * b10 - a11 * b08 + a13 * b06) * det,
302+
(a01 * b08 - a00 * b10 - a03 * b06) * det,
303+
(a30 * b04 - a31 * b02 + a33 * b00) * det,
304+
(a21 * b02 - a20 * b04 - a23 * b00) * det,
305+
306+
(a11 * b07 - a10 * b09 - a12 * b06) * det,
307+
(a00 * b09 - a01 * b07 + a02 * b06) * det,
308+
(a31 * b01 - a30 * b03 - a32 * b00) * det,
309+
(a20 * b03 - a21 * b01 + a22 * b00) * det
310+
);
311+
}

src/rendering/types/node.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { mat4, vec3, type Line } from "../../math/types";
1+
import { mat4, vec3, vec4, type Line } from "../../math/types";
22
import { identity, translate, rotate, scale } from "../../math/transformations";
33
import type { Mesh } from "./mesh";
4+
import { inverse_mat4, mul_mat4_vec4 } from "../../math/matrix_operators";
45

56
export class Node {
67
mesh: Mesh;
@@ -11,11 +12,12 @@ export class Node {
1112
radius_reciprocal:vec3 = vec3(1,1,1);
1213

1314
model: mat4 = identity();
15+
inverse_model: mat4 = identity();
1416

1517
constructor(mesh: Mesh) {
1618
this.mesh = mesh;
1719
this.update_matrix();
18-
this.determine_radius;
20+
this.determine_radius();
1921
}
2022

2123
private determine_radius(){
@@ -35,10 +37,14 @@ export class Node {
3537

3638

3739
intersects_with(line:Line){
38-
const [ux,uy,uz] = line.directional_vector;
39-
const px = line.point[0] - this.position[0];
40-
const py = line.point[1] - this.position[1];
41-
const pz = line.point[2] - this.position[2];
40+
const local_origin = mul_mat4_vec4(this.inverse_model,vec4(line.point[0],line.point[1],line.point[2],1.0));
41+
const local_dir = mul_mat4_vec4(this.inverse_model,vec4(line.directional_vector[0],line.directional_vector[1],line.directional_vector[2],0.0));
42+
43+
44+
const [ux,uy,uz,uu] = local_dir;
45+
const px = local_origin[0];
46+
const py = local_origin[1]
47+
const pz = local_origin[2]
4248

4349
const [recip_a,recip_b,recip_c] = this.radius_reciprocal;
4450
const A = ux * ux * recip_a + uy * uy * recip_b + uz * uz * recip_c;
@@ -52,6 +58,8 @@ export class Node {
5258
const delta = B*B - 4 * A * C;
5359
return delta >= 0;
5460
}
61+
62+
5563

5664

5765

@@ -68,5 +76,6 @@ export class Node {
6876
m = scale(m, this.scale_vec);
6977

7078
this.model = m;
79+
this.inverse_model = inverse_mat4(this.model);
7180
}
7281
}

src/rendering/utils/primitives.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,41 @@ export function create_torus(
274274
indices[ind_i++] = second + 1;
275275
}
276276
}
277+
return new Geometry(vertices, indices);
278+
}
279+
280+
export function merge_geometries(geo1:Geometry, geo2:Geometry) : Geometry{
281+
const n1 = geo1.vertices.length;
282+
const n2 = geo2.vertices.length;
283+
284+
const new_vertices = new ArrayType(n1 + n2);
285+
new_vertices.set(geo1.vertices);
286+
new_vertices.set(geo2.vertices,n1);
277287

288+
const n_i1 = geo1.indices.length;
289+
const n_i2 = geo2.indices.length;
278290

279-
return new Geometry(vertices, indices);
291+
const new_indices = new IndexingType(n_i1 + n_i2);
292+
new_vertices.set(geo1.indices);
293+
new_vertices.set(geo2.vertices,n_i1);
294+
295+
const offset = n1 / 3;
296+
for(let i = 0; i < n_i2; ++i){
297+
new_indices[n_i1 + i] = geo2.indices[i] + offset;
298+
}
299+
return new Geometry(new_vertices,new_indices);
300+
301+
}
302+
303+
export function create_arrow(
304+
shaft_radius:number = 0.05,
305+
shaft_length:number = 1.0,
306+
head_radius:number = 0.15,
307+
head_length:number = 0.3
308+
): Geometry{
309+
const shaft = create_fustrum(8, shaft_radius, shaft_radius, shaft_length);
310+
for (let i = 2; i < shaft.vertices.length; i += 3) shaft.vertices[i] += shaft_length / 2;
311+
const head = create_fustrum(8, head_radius, 0, head_length);
312+
for (let i = 2; i < head.vertices.length; i += 3) head.vertices[i] += shaft_length + (head_length / 2);
313+
return merge_geometries(shaft,head);
280314
}

src/ui/scene_manager.ts

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { vec3 } from "../math/types";
1+
import { vec3, vec4, type Line } from "../math/types";
22
import { perspective } from "../math/transformations";
3-
import { mul_mat4 } from "../math/matrix_operators";
3+
import { inverse_mat4, mul_mat4, mul_mat4_vec4, normalize_vec3 } from "../math/matrix_operators";
44
import { Scene } from "../rendering/types/scene";
5-
import { Mesh } from "../rendering/types/mesh";
65
import { Light } from "../rendering/types/light";
76
import { Geometry } from "../rendering/types/geometry";
87
import { build_scene } from "../to_html";
@@ -57,6 +56,7 @@ export class SceneManager {
5756

5857
this.setup_lights();
5958
this.update_stats_ui();
59+
this.setup_raycasting();
6060

6161
initialize_toolbar(toolbar_id, options_id, (geo: Geometry) => {
6262
this.add_node(geo);
@@ -156,4 +156,58 @@ export class SceneManager {
156156

157157
this.animation_id = requestAnimationFrame(this.loop);
158158
}
159+
private setup_raycasting() {
160+
this.target_element.addEventListener("mousedown", (e: MouseEvent) => {
161+
if (e.button !== 0) return;
162+
163+
const rect = this.target_element.getBoundingClientRect();
164+
const mouse_x = e.clientX - rect.left;
165+
const mouse_y = e.clientY - rect.top;
166+
167+
const x_ndc = (2.0 * mouse_x) / rect.width - 1.0;
168+
const y_ndc = 1.0 - (2.0 * mouse_y) / rect.height;
169+
170+
const view = this.viewport.get_view_matrix();
171+
const projection = perspective(60 * Math.PI / 180, 400 / 300, 0.1, 100);
172+
const vp = mul_mat4(projection, view);
173+
const inv_vp = inverse_mat4(vp);
174+
175+
if (!inv_vp) return;
176+
177+
const near_vec = vec4(x_ndc, y_ndc, -1.0, 1.0);
178+
const near_unproj = mul_mat4_vec4(inv_vp, near_vec);
179+
const near_point = vec3(
180+
near_unproj[0] / near_unproj[3],
181+
near_unproj[1] / near_unproj[3],
182+
near_unproj[2] / near_unproj[3]
183+
);
184+
185+
const far_vec = vec4(x_ndc, y_ndc, 1.0, 1.0);
186+
const far_unproj = mul_mat4_vec4(inv_vp, far_vec);
187+
const far_point = vec3(
188+
far_unproj[0] / far_unproj[3],
189+
far_unproj[1] / far_unproj[3],
190+
far_unproj[2] / far_unproj[3]
191+
);
192+
193+
const direction = vec3(
194+
far_point[0] - near_point[0],
195+
far_point[1] - near_point[1],
196+
far_point[2] - near_point[2]
197+
);
198+
normalize_vec3(direction);
199+
200+
const ray: Line = {
201+
point: this.viewport.camera_pos,
202+
directional_vector: direction
203+
};
204+
205+
for (const node of this.nodes) {
206+
if (node.intersects_with(ray)) {
207+
this.select_node(node);
208+
break;
209+
}
210+
}
211+
});
212+
}
159213
}

src/ui/state_manager.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { Node } from "../rendering/types/node";
2+
import { vec3 } from "../math/types";
3+
import type { Line } from "../math/types";
4+
import { Inspector } from "./inspector";
5+
import { create_arrow } from "../rendering/utils/primitives";
6+
import { Mesh } from "../rendering/types/mesh";
7+
import type { Scene } from "../rendering/types/scene";
8+
9+
export enum EditorMode {
10+
IDLE,
11+
TRANSLATE_X,
12+
TRANSLATE_Y,
13+
TRANSLATE_Z
14+
}
15+
16+
export class EditorState {
17+
public mode: EditorMode = EditorMode.IDLE;
18+
public selected_node: Node | null = null;
19+
20+
public gizmo_x!: Node;
21+
public gizmo_y!: Node;
22+
public gizmo_z!: Node;
23+
24+
private inspector: Inspector;
25+
26+
constructor(inspector: Inspector, scene:Scene) {
27+
this.inspector = inspector;
28+
this.setup_gizmos(scene);
29+
}
30+
31+
private setup_gizmos(scene:Scene) {
32+
const arrow_geo = create_arrow();
33+
34+
const mesh_z = scene.add_mesh(arrow_geo, vec3(0, 0, 1), 0);
35+
this.gizmo_z = new Node(mesh_z);
36+
37+
const mesh_x = scene.add_mesh(arrow_geo, vec3(1, 0, 0), 0);
38+
this.gizmo_x = new Node(mesh_x);
39+
this.gizmo_x.rotation[1] = Math.PI / 2;
40+
41+
const mesh_y = scene.add_mesh(arrow_geo, vec3(0, 1, 0), 0);
42+
this.gizmo_y = new Node(mesh_y);
43+
this.gizmo_y.rotation[0] = -Math.PI / 2;
44+
45+
this.gizmo_x.update_matrix();
46+
this.gizmo_y.update_matrix();
47+
this.gizmo_z.update_matrix();
48+
}
49+
50+
public select(node: Node | null) {
51+
this.selected_node = node;
52+
this.inspector.inspect(node);
53+
this.update_gizmo_transforms();
54+
}
55+
56+
public update_gizmo_transforms() {
57+
if (!this.selected_node) return;
58+
const pos = this.selected_node.position;
59+
60+
this.gizmo_x.position = vec3(pos[0], pos[1], pos[2]);
61+
this.gizmo_y.position = vec3(pos[0], pos[1], pos[2]);
62+
this.gizmo_z.position = vec3(pos[0], pos[1], pos[2]);
63+
64+
this.gizmo_x.update_matrix();
65+
this.gizmo_y.update_matrix();
66+
this.gizmo_z.update_matrix();
67+
}
68+
69+
public process_raycast(ray: Line, scene_nodes: Node[]) {
70+
if (this.selected_node) {
71+
if (this.gizmo_x.intersects_with(ray)) {
72+
this.mode = EditorMode.TRANSLATE_X;
73+
return;
74+
}
75+
if (this.gizmo_y.intersects_with(ray)) {
76+
this.mode = EditorMode.TRANSLATE_Y;
77+
return;
78+
}
79+
if (this.gizmo_z.intersects_with(ray)) {
80+
this.mode = EditorMode.TRANSLATE_Z;
81+
return;
82+
}
83+
}
84+
85+
for (const node of scene_nodes) {
86+
if (node.intersects_with(ray)) {
87+
this.select(node);
88+
return;
89+
}
90+
}
91+
92+
this.select(null);
93+
}
94+
95+
public process_drag(dx: number, dy: number) {
96+
if (!this.selected_node || this.mode === EditorMode.IDLE) return;
97+
98+
console.log(`Dragging mode: ${this.mode}, Delta: ${dx}, ${dy}`);
99+
100+
this.update_gizmo_transforms();
101+
}
102+
103+
public release_drag() {
104+
this.mode = EditorMode.IDLE;
105+
}
106+
107+
public get_active_gizmos(): Node[] {
108+
if (!this.selected_node) return [];
109+
return [this.gizmo_x, this.gizmo_y, this.gizmo_z];
110+
}
111+
}

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"strict": true,
1919
"noUnusedLocals": true,
2020
"noUnusedParameters": true,
21-
"erasableSyntaxOnly": true,
21+
"erasableSyntaxOnly": false,
2222
"noFallthroughCasesInSwitch": true,
2323
"noUncheckedSideEffectImports": true
2424
},

0 commit comments

Comments
 (0)