Skip to content

Commit 694c72d

Browse files
committed
Added object importing and modularized Inspector
1 parent bfe7ba1 commit 694c72d

7 files changed

Lines changed: 275 additions & 102 deletions

File tree

index.html

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,18 @@ <h1><font face="Arial">Down with canvas</font></h1>
2020

2121
<tr valign="top">
2222

23-
<td width="25%" bgcolor="#1a1a1a">
23+
<td nowrap width="25%" bgcolor="#1a1a1a">
2424
<h3><font face="Arial">Create</font></h3>
2525
<div id="primitive-toolbar"></div>
2626
<hr>
2727
<div id="primitive-options"></div>
2828
<hr>
29-
<h3><font face="Arial">Import .OBJ</font></h3>
30-
<input type="file" id="file-picker"/>
3129
</td>
3230

3331
<td width="50%" align="center" bgcolor="black">
3432
<svg
35-
width="400px"
36-
height="300px"
33+
width="800px"
34+
height="600px"
3735
viewBox="-1 -1 2 2"
3836
preserveAspectRatio="none"
3937
stroke-width ="0.01"

src/rendering/types/node.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { inverse_mat4, mul_mat4_vec4 } from "../../math/matrix_operators";
55

66
export class Node {
77
mesh: Mesh;
8-
position: vec3 = vec3(0,0,0);
9-
rotation: vec3 = vec3(0, 0, 0);
10-
scale_vec: vec3 = vec3(1, 1, 1);
11-
radius:vec3 = vec3(1,1,1);
12-
radius_reciprocal:vec3 = vec3(1,1,1);
8+
public position: vec3 = vec3(0,0,0);
9+
public rotation: vec3 = vec3(0, 0, 0);
10+
public scale_vec: vec3 = vec3(1, 1, 1);
11+
public radius:vec3 = vec3(1,1,1);
12+
public radius_reciprocal:vec3 = vec3(1,1,1);
1313

1414
model: mat4 = identity();
1515
inverse_model: mat4 = identity();
@@ -38,7 +38,7 @@ export class Node {
3838

3939
intersects_with(line:Line){
4040
if(!this.inverse_model) return false;
41-
41+
4242

4343

4444
const local_origin = mul_mat4_vec4(this.inverse_model,vec4(line.point[0],line.point[1],line.point[2],1.0));

src/ui/inspector.ts

Lines changed: 99 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import type { Node } from "../rendering/types/node";
2+
13
export class Inspector {
24
private container: HTMLElement;
3-
public current_node: any | null = null;
5+
public current_node: Node | null = null;
46

57
private move_speed = 0.5;
68
private rot_speed = 0.26;
@@ -14,7 +16,7 @@ export class Inspector {
1416
this.setup_keyboard_controls();
1517
}
1618

17-
public inspect(node: any | null) {
19+
public inspect(node: Node | null) {
1820
this.current_node = node;
1921
this.container.innerHTML = "";
2022

@@ -23,40 +25,58 @@ export class Inspector {
2325
return;
2426
}
2527

26-
this.container.innerHTML = `<h3>Action Inspector</h3>`;
27-
this.container.innerHTML += `<p style="font-size: 12px; color: gray;">WASD to move X/Z. Q/E to move Y.</p>`;
28-
29-
this.create_action_row("Move X",
28+
const h3 = document.createElement("h3");
29+
const title_font = document.createElement("font");
30+
title_font.setAttribute("face", "Arial");
31+
title_font.innerText = "Action Inspector";
32+
h3.appendChild(title_font);
33+
this.container.appendChild(h3);
34+
35+
const hint = document.createElement("font");
36+
hint.setAttribute("size", "2");
37+
hint.setAttribute("color", "gray");
38+
hint.setAttribute("face", "Arial");
39+
hint.innerText = "WASD to move X/Z. Q/E to move Y.";
40+
this.container.appendChild(hint);
41+
42+
this.container.appendChild(document.createElement("br"));
43+
this.container.appendChild(document.createElement("br"));
44+
45+
const table = document.createElement("table");
46+
table.setAttribute("border", "0");
47+
table.setAttribute("cellpadding", "2");
48+
this.container.appendChild(table);
49+
this.create_action_row(table, "Move X",
3050
() => { node.position[0] -= this.move_speed; node.update_matrix(); },
3151
() => { node.position[0] += this.move_speed; node.update_matrix(); }
3252
);
33-
this.create_action_row("Move Y",
53+
this.create_action_row(table, "Move Y",
3454
() => { node.position[1] -= this.move_speed; node.update_matrix(); },
3555
() => { node.position[1] += this.move_speed; node.update_matrix(); }
3656
);
37-
this.create_action_row("Move Z",
57+
this.create_action_row(table, "Move Z",
3858
() => { node.position[2] -= this.move_speed; node.update_matrix(); },
3959
() => { node.position[2] += this.move_speed; node.update_matrix(); }
4060
);
4161

42-
this.container.appendChild(document.createElement("hr"));
62+
this.add_divider(table);
4363

44-
this.create_action_row("Rotate X",
64+
this.create_action_row(table, "Rotate X",
4565
() => { node.rotation[0] -= this.rot_speed; node.update_matrix(); },
4666
() => { node.rotation[0] += this.rot_speed; node.update_matrix(); }
4767
);
48-
this.create_action_row("Rotate Y",
68+
this.create_action_row(table, "Rotate Y",
4969
() => { node.rotation[1] -= this.rot_speed; node.update_matrix(); },
5070
() => { node.rotation[1] += this.rot_speed; node.update_matrix(); }
5171
);
52-
this.create_action_row("Rotate Z",
72+
this.create_action_row(table, "Rotate Z",
5373
() => { node.rotation[2] -= this.rot_speed; node.update_matrix(); },
5474
() => { node.rotation[2] += this.rot_speed; node.update_matrix(); }
5575
);
5676

57-
this.container.appendChild(document.createElement("hr"));
77+
this.add_divider(table);
5878

59-
this.create_action_row("Scale",
79+
this.create_action_row(table, "Scale",
6080
() => {
6181
node.scale_vec[0] -= this.scale_factor;
6282
node.scale_vec[1] -= this.scale_factor;
@@ -70,35 +90,85 @@ export class Inspector {
7090
node.update_matrix();
7191
}
7292
);
93+
94+
this.add_divider(table);
95+
96+
if (node.mesh && node.mesh.albedo) {
97+
this.create_color_row(table, "Color", node.mesh.albedo);
98+
}
7399
}
74100

75101
private clear() {
76-
this.container.innerHTML = `<p style="color: gray;">Select an object to inspect.</p>`;
102+
this.container.innerHTML = `<font color="gray" face="Arial">Select an object to inspect.</font>`;
77103
}
78104

79-
private create_action_row(label: string, on_minus: () => void, on_plus: () => void) {
80-
const wrapper = document.createElement("div");
81-
wrapper.style.marginBottom = "8px";
82-
83-
const label_el = document.createElement("span");
84-
label_el.innerText = `${label}: `;
85-
label_el.style.display = "inline-block";
86-
label_el.style.width = "70px";
105+
private add_divider(table: HTMLElement) {
106+
const tr = document.createElement("tr");
107+
const td = document.createElement("td");
108+
td.setAttribute("colspan", "3");
109+
td.appendChild(document.createElement("hr"));
110+
tr.appendChild(td);
111+
table.appendChild(tr);
112+
}
113+
114+
private create_action_row(table: HTMLElement, label: string, on_minus: () => void, on_plus: () => void) {
115+
const tr = document.createElement("tr");
87116

117+
const td_label = document.createElement("td");
118+
const font = document.createElement("font");
119+
font.setAttribute("face", "Arial");
120+
font.innerText = `${label}: `;
121+
td_label.appendChild(font);
122+
123+
const td_minus = document.createElement("td");
88124
const btn_minus = document.createElement("button");
89125
btn_minus.innerText = " - ";
90-
btn_minus.style.width = "30px";
91126
btn_minus.onclick = on_minus;
127+
td_minus.appendChild(btn_minus);
92128

129+
const td_plus = document.createElement("td");
93130
const btn_plus = document.createElement("button");
94131
btn_plus.innerText = " + ";
95-
btn_plus.style.width = "30px";
96132
btn_plus.onclick = on_plus;
133+
td_plus.appendChild(btn_plus);
134+
135+
tr.appendChild(td_label);
136+
tr.appendChild(td_minus);
137+
tr.appendChild(td_plus);
138+
table.appendChild(tr);
139+
}
140+
141+
private create_color_row(table: HTMLElement, label: string, color_vec: Float32Array | number[]) {
142+
const tr = document.createElement("tr");
143+
144+
const td_label = document.createElement("td");
145+
const font = document.createElement("font");
146+
font.setAttribute("face", "Arial");
147+
font.innerText = `${label}: `;
148+
td_label.appendChild(font);
149+
150+
const td_input = document.createElement("td");
151+
td_input.setAttribute("colspan", "2");
152+
153+
const color_picker = document.createElement("input");
154+
color_picker.type = "color";
155+
156+
const r = Math.max(0, Math.min(255, Math.round(color_vec[0] * 255))).toString(16).padStart(2, '0');
157+
const g = Math.max(0, Math.min(255, Math.round(color_vec[1] * 255))).toString(16).padStart(2, '0');
158+
const b = Math.max(0, Math.min(255, Math.round(color_vec[2] * 255))).toString(16).padStart(2, '0');
159+
color_picker.value = `#${r}${g}${b}`;
160+
161+
color_picker.addEventListener("input", (e) => {
162+
const hex = (e.target as HTMLInputElement).value;
163+
color_vec[0] = parseInt(hex.substring(1, 3), 16) / 255.0;
164+
color_vec[1] = parseInt(hex.substring(3, 5), 16) / 255.0;
165+
color_vec[2] = parseInt(hex.substring(5, 7), 16) / 255.0;
166+
});
97167

98-
wrapper.appendChild(label_el);
99-
wrapper.appendChild(btn_minus);
100-
wrapper.appendChild(btn_plus);
101-
this.container.appendChild(wrapper);
168+
td_input.appendChild(color_picker);
169+
tr.appendChild(td_label);
170+
tr.appendChild(td_input);
171+
table.appendChild(tr);
102172
}
103173

104174
private setup_keyboard_controls() {

src/ui/scene_manager.ts

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { Node } from "../rendering/types/node";
1616
import type { EditorMode } from "./state_manager";
1717
import { EditorState } from "./state_manager";
1818

19+
import { parse_obj } from "../utils/parser";
20+
1921
export class SceneManager {
2022
public scene: Scene;
2123
public nodes: Node[] = [];
@@ -174,6 +176,50 @@ export class SceneManager {
174176

175177
this.animation_id = requestAnimationFrame(this.loop);
176178
}
179+
180+
private get_mouse_ray(e: MouseEvent): Line | null {
181+
const rect = this.target_element.getBoundingClientRect();
182+
const mouse_x = e.clientX - rect.left;
183+
const mouse_y = e.clientY - rect.top;
184+
185+
const x_ndc = (2.0 * mouse_x) / rect.width - 1.0;
186+
const y_ndc = 1.0 - (2.0 * mouse_y) / rect.height;
187+
188+
const view = this.viewport.get_view_matrix();
189+
const projection = this.viewport.get_projection();
190+
const vp = mul_mat4(projection, view);
191+
const inv_vp = inverse_mat4(vp);
192+
193+
if (!inv_vp) return null;
194+
195+
const near_vec = vec4(x_ndc, y_ndc, -1.0, 1.0);
196+
const near_unproj = mul_mat4_vec4(inv_vp, near_vec);
197+
const near_point = vec3(
198+
near_unproj[0] / near_unproj[3],
199+
near_unproj[1] / near_unproj[3],
200+
near_unproj[2] / near_unproj[3]
201+
);
202+
203+
const far_vec = vec4(x_ndc, y_ndc, 1.0, 1.0);
204+
const far_unproj = mul_mat4_vec4(inv_vp, far_vec);
205+
const far_point = vec3(
206+
far_unproj[0] / far_unproj[3],
207+
far_unproj[1] / far_unproj[3],
208+
far_unproj[2] / far_unproj[3]
209+
);
210+
211+
const direction = vec3(
212+
far_point[0] - near_point[0],
213+
far_point[1] - near_point[1],
214+
far_point[2] - near_point[2]
215+
);
216+
normalize_vec3(direction);
217+
218+
return {
219+
point: this.viewport.camera_pos,
220+
directional_vector: direction
221+
};
222+
}
177223
private setup_raycasting() {
178224
this.target_element.addEventListener("mousedown", (e: MouseEvent) => {
179225
if (e.button !== 0) return;
@@ -182,53 +228,25 @@ export class SceneManager {
182228
this.last_mx = e.clientX;
183229
this.last_my = e.clientY;
184230

185-
const rect = this.target_element.getBoundingClientRect();
186-
const mouse_x = e.clientX - rect.left;
187-
const mouse_y = e.clientY - rect.top;
188-
189-
const x_ndc = (2.0 * mouse_x) / rect.width - 1.0;
190-
const y_ndc = 1.0 - (2.0 * mouse_y) / rect.height;
191-
192-
const view = this.viewport.get_view_matrix();
193-
const projection = this.viewport.get_projection();
194-
const vp = mul_mat4(projection, view);
195-
const inv_vp = inverse_mat4(vp);
196-
197-
if (!inv_vp) return;
198-
199-
const near_vec = vec4(x_ndc, y_ndc, -1.0, 1.0);
200-
const near_unproj = mul_mat4_vec4(inv_vp, near_vec);
201-
const near_point = vec3(
202-
near_unproj[0] / near_unproj[3],
203-
near_unproj[1] / near_unproj[3],
204-
near_unproj[2] / near_unproj[3]
205-
);
206-
207-
const far_vec = vec4(x_ndc, y_ndc, 1.0, 1.0);
208-
const far_unproj = mul_mat4_vec4(inv_vp, far_vec);
209-
const far_point = vec3(
210-
far_unproj[0] / far_unproj[3],
211-
far_unproj[1] / far_unproj[3],
212-
far_unproj[2] / far_unproj[3]
213-
);
214-
215-
const direction = vec3(
216-
far_point[0] - near_point[0],
217-
far_point[1] - near_point[1],
218-
far_point[2] - near_point[2]
219-
);
220-
normalize_vec3(direction);
221-
222-
const ray: Line = {
223-
point: this.viewport.camera_pos,
224-
directional_vector: direction
225-
};
231+
const ray = this.get_mouse_ray(e);
232+
if(!ray) return;
226233

227234
this.editor_state.handle_mouse_down(ray, e.clientX, e.clientY, this.nodes);
228235
this.selected_node = this.editor_state.selected_node;
229236
});
230237
window.addEventListener("mousemove", (e: MouseEvent) => {
231-
if (!this.is_dragging) return;
238+
if (!this.is_dragging && this.editor_state.selected_node){
239+
const ray = this.get_mouse_ray(e);
240+
if(!ray) return;
241+
const is_hovering =
242+
this.editor_state.gizmo_x.intersects_with(ray) ||
243+
this.editor_state.gizmo_y.intersects_with(ray) ||
244+
this.editor_state.gizmo_z.intersects_with(ray);
245+
this.target_element.style.cursor = is_hovering ? "pointer" : "default";
246+
return;
247+
}
248+
249+
if(!this.is_dragging) return;
232250

233251
const dx = e.clientX - this.last_mx;
234252
const dy = e.clientY - this.last_my;

src/ui/state_manager.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import type { Line, mat4 } from "../math/types";
44
import { Inspector } from "./inspector";
55
import { create_arrow } from "../rendering/utils/primitives";
66
import type { Scene } from "../rendering/types/scene";
7-
import { scale } from "../math/transformations";
87
import { mul_mat4_vec4, scalar_mult_vec3 } from "../math/matrix_operators";
98

109
export type EditorMode = "IDLE" | "TRANSLATE_X" | "TRANSLATE_Y" | "TRANSLATE_Z";

0 commit comments

Comments
 (0)