Skip to content

Commit c7d57d2

Browse files
committed
Added animations
1 parent 1d1e3bf commit c7d57d2

4 files changed

Lines changed: 269 additions & 40 deletions

File tree

devlog.md

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,2 @@
1-
We've got an UI up and running!
2-
3-
This part is the bane of my existence because there's not much technical and fun to do, and its mostly a way to display (and subsequently drag the development of) an already finished project. But, unfortunately, it's necessary. Otherwise, I'd be doing the equivalent of building a computer with no screen, no user input and output - there's no way to know it even works!
4-
5-
I thought of some different ideas (making a physics engine, orbit simulation, animations, etc) but ultimately settled for just making a simple inspector where a user can create primitive shapes, move them around, add lights, etc. This is by all means not at all a complicated task, but it certainly is for a developer like me.
6-
7-
We've got some of this up and running! Not my proudest work and it's due some changes, but it will suffice for now. I expect I will be able to ship this project soon.
8-
9-
Attached, our UI!
10-
111
**Commits**
12-
[Commit 626dfa9](https://url.jam06452.uk/132w37n): Added geometry normalization for imported vertices
13-
[Commit e5b2ad7](https://url.jam06452.uk/vmcjuu): Added new primitives
14-
[Commit 85b98f5](https://url.jam06452.uk/1h03mo4): Generalized 3d ngon creation
15-
[Commit ca18aa8](https://url.jam06452.uk/1t7tg3s): Finished basic inspector
2+
[]

src/ui/inspector.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,46 @@ export class Inspector {
133133
});
134134
}
135135
}
136+
137+
const any_node = node as any;
138+
if (any_node.animations && any_node.animations.length > 0) {
139+
this.add_divider(table);
140+
141+
const tr_anim = document.createElement("tr");
142+
const td_anim = document.createElement("td");
143+
td_anim.setAttribute("colspan", "3");
144+
const b_anim = document.createElement("b");
145+
const font_anim = document.createElement("font");
146+
font_anim.setAttribute("face", "Arial");
147+
font_anim.innerText = "Animations";
148+
b_anim.appendChild(font_anim);
149+
td_anim.appendChild(b_anim);
150+
tr_anim.appendChild(td_anim);
151+
table.appendChild(tr_anim);
152+
153+
any_node.animations.forEach((anim: any, idx: number) => {
154+
const tr = document.createElement("tr");
155+
const td_name = document.createElement("td");
156+
td_name.setAttribute("colspan", "2");
157+
const f_name = document.createElement("font");
158+
f_name.setAttribute("face", "Arial");
159+
f_name.innerText = anim.name;
160+
td_name.appendChild(f_name);
161+
162+
const td_btn = document.createElement("td");
163+
const btn = document.createElement("button");
164+
btn.innerText = "Remove";
165+
btn.onclick = () => {
166+
any_node.animations.splice(idx, 1);
167+
this.inspect(node);
168+
};
169+
td_btn.appendChild(btn);
170+
171+
tr.appendChild(td_name);
172+
tr.appendChild(td_btn);
173+
table.appendChild(tr);
174+
});
175+
}
136176
}
137177

138178
private clear() {

src/ui/scene_manager.ts

Lines changed: 117 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ import { EditorState } from "./state_manager";
1818

1919
import { parse_obj } from "../utils/parser";
2020
import * as primitives from "../rendering/utils/primitives";
21-
21+
import { Rotator, Oscillator, Orbit } from "./animation";
2222

2323
export class SceneManager {
2424
public scene: Scene;
2525
public nodes: Node[] = [];
2626

2727
private viewport: Viewport;
2828
private inspector: Inspector;
29-
private editor_state:EditorState
29+
private editor_state: EditorState
3030

3131
private target_element: HTMLElement;
3232
private wireframe_checkbox: HTMLInputElement;
@@ -36,15 +36,20 @@ export class SceneManager {
3636
private animation_id: number | null = null;
3737
public selected_node: Node | null = null;
3838

39-
private last_mx:number = 0;
40-
private last_my:number = 0;
39+
private last_mx: number = 0;
40+
private last_my: number = 0;
4141

4242
private is_dragging = false;
4343

4444
public current_triangles: number = 0;
4545
public current_capacity: number = 50000;
4646
public readonly MAX_TRIANGLES: number = 100000;
4747

48+
private is_playing = false;
49+
private current_time = 0;
50+
private last_frame_time = 0;
51+
private initial_states: Map<Node, any> = new Map();
52+
4853
constructor(
4954
svg_container_id: string,
5055
toolbar_id: string,
@@ -65,25 +70,49 @@ export class SceneManager {
6570

6671
this.viewport = new Viewport(svg_container_id);
6772
this.inspector = new Inspector(inspector_id);
68-
this.editor_state = new EditorState(this.inspector,this.scene);
73+
this.editor_state = new EditorState(this.inspector, this.scene);
6974

7075
this.setup_lights();
7176
this.update_stats_ui();
7277
this.setup_raycasting();
7378

74-
initialize_toolbar(toolbar_id, options_id, (geo: Geometry) => {
75-
this.add_node(geo);
76-
}, () => this.add_point_light());
79+
initialize_toolbar(
80+
toolbar_id,
81+
options_id,
82+
(geo: Geometry, color: number[]) => {
83+
this.add_node(geo, color);
84+
},
85+
(intensity: number, radius: number, color: number[]) => {
86+
this.add_point_light(intensity, radius, color);
87+
},
88+
(action: "play" | "pause" | "stop") => {
89+
this.handle_playback(action);
90+
},
91+
(type: string, params: number[]) => {
92+
if (!this.selected_node) {
93+
alert("Select a node first!");
94+
return;
95+
}
96+
const any_node = this.selected_node as any;
97+
if (!any_node.animations) any_node.animations = [];
98+
99+
if (type === "Rotator") any_node.animations.push(new Rotator(params[0], params[1]));
100+
else if (type === "Oscillator") any_node.animations.push(new Oscillator(params[0], params[1], params[2], params[3]));
101+
else if (type === "Orbit") any_node.animations.push(new Orbit(params[0], params[1], params[2], params[3]));
102+
103+
this.inspector.inspect(this.selected_node);
104+
}
105+
);
77106
}
78107

79108
private setup_lights() {
80109
const sun_light = new Light(vec3(10, 10, 10), vec3(1.0, 0.95, 0.9), 3.0, 200.0);
81-
const leskow_light = new Light(vec3(5,0,0),vec3(0.8,0.2,0.0), 2.0, 100.0);
110+
const leskow_light = new Light(vec3(5, 0, 0), vec3(0.8, 0.2, 0.0), 2.0, 100.0);
82111
this.scene.add_light(sun_light);
83112
this.scene.add_light(leskow_light);
84113
}
85114

86-
public add_node(geo: Geometry) {
115+
public add_node(geo: Geometry, color_arr: number[] = [0.7, 0.7, 0.7]) {
87116
const new_triangles = geo.indices.length / 3;
88117

89118
if (this.current_triangles + new_triangles > this.MAX_TRIANGLES) {
@@ -95,7 +124,7 @@ export class SceneManager {
95124
this.expand_capacity(this.current_triangles + new_triangles);
96125
}
97126

98-
const mesh = this.scene.add_mesh(geo, vec3(0.7, 0.7, 0.7), 0.5);
127+
const mesh = this.scene.add_mesh(geo, vec3(color_arr[0], color_arr[1], color_arr[2]), 0.5);
99128

100129
const node = new Node(mesh);
101130
this.nodes.push(node);
@@ -106,13 +135,14 @@ export class SceneManager {
106135
this.select_node(node);
107136
}
108137

109-
public add_point_light() {
110-
const new_light = new Light(vec3(0, 0, 0), vec3(1.0, 1.0, 1.0), 2.0, 50.0);
138+
public add_point_light(intensity: number, radius: number, color_arr: number[] = [1.0, 1.0, 1.0]) {
139+
const light_color = vec3(color_arr[0], color_arr[1], color_arr[2]);
140+
const new_light = new Light(vec3(0, 0, 0), light_color, intensity, radius);
111141
this.scene.add_light(new_light);
112142

113143
const bulb_geo = primitives.create_sphere(0.2, 8, 8);
114144

115-
const mesh = this.scene.add_mesh(bulb_geo, vec3(1.0, 1.0, 0.0), 0.5);
145+
const mesh = this.scene.add_mesh(bulb_geo, light_color, 0.5);
116146

117147
const light_node = new Node(mesh, new_light);
118148

@@ -125,7 +155,6 @@ export class SceneManager {
125155
if (new_capacity < required_triangles) new_capacity = required_triangles;
126156
if (new_capacity > this.MAX_TRIANGLES) new_capacity = this.MAX_TRIANGLES;
127157

128-
129158
this.scene.resize_buffers(new_capacity);
130159
this.string_buffer.resize(new_capacity * 60);
131160
this.current_capacity = new_capacity;
@@ -148,14 +177,74 @@ export class SceneManager {
148177
this.editor_state.select(this.selected_node);
149178
}
150179

180+
public handle_playback(action: "play" | "pause" | "stop") {
181+
if (action === "play") {
182+
if (!this.is_playing) {
183+
this.last_frame_time = performance.now();
184+
this.is_playing = true;
185+
for (const node of this.nodes) {
186+
if (!this.initial_states.has(node)) {
187+
this.initial_states.set(node, {
188+
pos: [...node.position],
189+
rot: [...node.rotation]
190+
});
191+
}
192+
}
193+
}
194+
} else if (action === "pause") {
195+
this.is_playing = false;
196+
} else if (action === "stop") {
197+
this.is_playing = false;
198+
this.current_time = 0;
199+
for (const node of this.nodes) {
200+
const state = this.initial_states.get(node);
201+
if (state) {
202+
node.position[0] = state.pos[0];
203+
node.position[1] = state.pos[1];
204+
node.position[2] = state.pos[2];
205+
node.rotation[0] = state.rot[0];
206+
node.rotation[1] = state.rot[1];
207+
node.rotation[2] = state.rot[2];
208+
node.update_matrix();
209+
}
210+
}
211+
this.initial_states.clear();
212+
if (this.selected_node) {
213+
this.editor_state.update_gizmo_transforms();
214+
this.inspector.inspect(this.selected_node);
215+
}
216+
}
217+
}
218+
151219
public start() {
152220
if (this.animation_id !== null) {
153221
cancelAnimationFrame(this.animation_id);
154222
}
223+
this.last_frame_time = performance.now();
155224
this.loop();
156225
}
157226

158227
private loop = () => {
228+
const now = performance.now();
229+
const dt = (now - this.last_frame_time) / 1000.0;
230+
this.last_frame_time = now;
231+
232+
if (this.is_playing) {
233+
this.current_time += dt;
234+
for (const node of this.nodes) {
235+
const any_node = node as any;
236+
if (any_node.animations && any_node.animations.length > 0) {
237+
for (const anim of any_node.animations) {
238+
anim.apply(node, this.current_time, dt);
239+
}
240+
node.update_matrix();
241+
}
242+
}
243+
if (this.selected_node && (this.selected_node as any).animations && (this.selected_node as any).animations.length > 0) {
244+
this.inspector.inspect(this.selected_node);
245+
}
246+
}
247+
159248
const camera_pos = this.viewport.camera_pos;
160249
const view = this.viewport.get_view_matrix();
161250
const projection = this.viewport.get_projection();
@@ -230,6 +319,7 @@ export class SceneManager {
230319
directional_vector: direction
231320
};
232321
}
322+
233323
private setup_raycasting() {
234324
this.target_element.addEventListener("mousedown", (e: MouseEvent) => {
235325
if (e.button !== 0) return;
@@ -239,24 +329,25 @@ export class SceneManager {
239329
this.last_my = e.clientY;
240330

241331
const ray = this.get_mouse_ray(e);
242-
if(!ray) return;
332+
if (!ray) return;
243333

244334
this.editor_state.handle_mouse_down(ray, e.clientX, e.clientY, this.nodes);
245335
this.selected_node = this.editor_state.selected_node;
246336
});
337+
247338
window.addEventListener("mousemove", (e: MouseEvent) => {
248-
if (!this.is_dragging && this.editor_state.selected_node){
339+
if (!this.is_dragging && this.editor_state.selected_node) {
249340
const ray = this.get_mouse_ray(e);
250-
if(!ray) return;
341+
if (!ray) return;
251342
const is_hovering =
252-
this.editor_state.gizmo_x.intersects_with(ray) ||
253-
this.editor_state.gizmo_y.intersects_with(ray) ||
254-
this.editor_state.gizmo_z.intersects_with(ray);
343+
this.editor_state.gizmo_x.intersects_with(ray) ||
344+
this.editor_state.gizmo_y.intersects_with(ray) ||
345+
this.editor_state.gizmo_z.intersects_with(ray);
255346
this.target_element.style.cursor = is_hovering ? "pointer" : "default";
256347
return;
257348
}
258349

259-
if(!this.is_dragging) return;
350+
if (!this.is_dragging) return;
260351

261352
const dx = e.clientX - this.last_mx;
262353
const dy = e.clientY - this.last_my;
@@ -267,14 +358,15 @@ export class SceneManager {
267358
if (this.editor_state.mode !== "IDLE") {
268359
const view = this.viewport.get_view_matrix();
269360
const projection = this.viewport.get_projection();
270-
const vp = mul_mat4(projection,view);
361+
const vp = mul_mat4(projection, view);
271362

272-
const {width,height} = this.viewport.get_dimensions();
273-
this.editor_state.handle_mouse_move(e.clientX, e.clientY,vp,width,height);
363+
const { width, height } = this.viewport.get_dimensions();
364+
this.editor_state.handle_mouse_move(e.clientX, e.clientY, vp, width, height);
274365
} else {
275366
this.viewport.orbit(dx, dy);
276367
}
277368
});
369+
278370
window.addEventListener("mouseup", () => {
279371
this.is_dragging = false;
280372
this.editor_state.handle_mouse_up();

0 commit comments

Comments
 (0)