Skip to content

Commit 918539d

Browse files
authored
feat: add picking assets on hover (#9)
* Add structure for picking, change assets sotring from a list to hash map, add pointer * render picking * determine asset id under the pointer * Draw border around picked asset * add comment, replace passing test screneshto name as array with just a string * remove unnecessayr buffer size during picking * round down number of verticies
1 parent caa3a48 commit 918539d

File tree

21 files changed

+624
-657
lines changed

21 files changed

+624
-657
lines changed

crate/src/lib.rs

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,85 +15,117 @@ macro_rules! err {
1515
// there is no way to specify panci message, so we need to do console.error and then panci any value
1616
}
1717

18-
//to remove and replace with util
19-
macro_rules! angle_diff {
20-
($beta:expr, $alpha:expr) => {{
21-
let phi = ($beta - $alpha).abs() % (2.0 * MATH_PI); // This is either the distance or 2*Math.PI - distance
22-
if phi > MATH_PI {
23-
(2.0 * MATH_PI) - phi
24-
} else {
25-
phi
26-
}
27-
}}
28-
}
29-
18+
mod line;
3019
mod texture;
20+
mod types;
3121

32-
use gloo_utils::format::JsValueSerdeExt; // for transforming JsValue into serde
22+
use std::collections::HashMap;
23+
24+
use gloo_utils::format::JsValueSerdeExt;
25+
use line::Line;
3326
use serde::{Deserialize, Serialize};
34-
use texture::{Texture, VertexPoint};
27+
use texture::Texture;
28+
use types::{Point, VertexPoint};
3529
use wasm_bindgen::prelude::*;
3630

3731
#[wasm_bindgen]
3832
pub struct State {
39-
textures: Vec<Texture>,
33+
assets: HashMap<usize, Texture>,
34+
hovered_asset_id: usize, // 0 -> no asset is hovered
4035
}
4136

4237
#[wasm_bindgen]
4338
impl State {
4439
pub fn new(width: f32, height: f32) -> State {
45-
State { textures: vec![] }
40+
State {
41+
assets: HashMap::new(),
42+
hovered_asset_id: 0,
43+
}
4644
}
4745

48-
pub fn add_texture(&mut self, raw_points: JsValue, id: usize) {
46+
pub fn add_texture(&mut self, id: usize, raw_points: JsValue, texture_id: usize) {
4947
let serde = raw_points.into_serde();
5048
let points: Vec<VertexPoint> = if serde.is_ok() {
5149
serde.unwrap()
5250
} else {
5351
err!("add_texture received not copatible data from JS. Failed at conversion to Rust types.");
5452
};
5553

56-
self.textures.push(Texture::new(points, id));
54+
self.assets.insert(id, Texture::new(id, points, texture_id));
5755
}
5856

59-
pub fn get_shader_input(&self, index: usize) -> JsValue {
60-
let mut vertex_data: Vec<f32> = vec![];
61-
let mut texture_id: usize = 0;
62-
if index < self.textures.len() {
63-
texture_id = self.textures[index].id;
64-
self.textures[index].add_vertex(&mut vertex_data);
65-
}
57+
pub fn get_shader_input(&self, id: usize) -> JsValue {
58+
let asset: &Texture = if self.assets.contains_key(&id) {
59+
self.assets.get(&id).unwrap()
60+
} else {
61+
err!("asset with id {id} not found");
62+
};
6663

6764
let payload = ShaderInput {
68-
texture_id,
69-
vertex_data,
65+
texture_id: asset.texture_id,
66+
vertex_data: asset.get_vertex_data(),
7067
};
68+
7169
serde_wasm_bindgen::to_value(&payload).unwrap()
70+
}
71+
72+
pub fn get_shader_pick_input(&self, id: usize) -> JsValue {
73+
let asset: &Texture = if self.assets.contains_key(&id) {
74+
self.assets.get(&id).unwrap()
75+
} else {
76+
err!("asset with id {id} not found");
77+
};
78+
79+
let payload = ShaderInput {
80+
texture_id: asset.texture_id,
81+
vertex_data: asset.get_vertex_pick_data(),
82+
};
7283

73-
// js_sys::Float32Array::from(&result[..])
84+
serde_wasm_bindgen::to_value(&payload).unwrap()
7485
}
7586

76-
pub fn update_points(&mut self, texture_id: usize, raw_points: JsValue) {
87+
pub fn update_points(&mut self, id: usize, raw_points: JsValue) {
88+
let asset = self.assets.get_mut(&id).unwrap();
89+
7790
let serde = raw_points.into_serde();
7891
let points: Vec<Point> = if serde.is_ok() {
7992
serde.unwrap()
8093
} else {
8194
err!("add_texture received not copatible data from JS. Failed at conversion to Rust types.");
8295
};
8396

84-
let texture_option = self
85-
.textures
86-
.iter_mut()
87-
.find(|texture| texture.id == texture_id);
97+
asset.update_coords(points);
98+
}
8899

89-
texture_option.unwrap().update_coords(points);
100+
pub fn update_hover(&mut self, id: usize) {
101+
self.hovered_asset_id = id
90102
}
91-
}
92103

93-
#[derive(Serialize, Deserialize)]
94-
struct Point {
95-
x: f32,
96-
y: f32,
104+
pub fn get_border(&self) -> Vec<f32> {
105+
if self.assets.contains_key(&self.hovered_asset_id) {
106+
let asset: &Texture = self.assets.get(&self.hovered_asset_id).unwrap();
107+
108+
asset
109+
.points
110+
.iter()
111+
.enumerate()
112+
.flat_map(|(index, point)| {
113+
Line::get_vertex_data(
114+
point,
115+
if index == 3 {
116+
&asset.points[0]
117+
} else {
118+
&asset.points[index + 1]
119+
},
120+
20.0,
121+
(1.0, 0.0, 0.0, 1.0),
122+
)
123+
})
124+
.collect::<Vec<f32>>()
125+
} else {
126+
vec![]
127+
}
128+
}
97129
}
98130

99131
#[derive(Serialize, Deserialize)]

crate/src/line.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use crate::types::{Color, HasCoords, Point};
2+
3+
pub struct Line {}
4+
5+
impl Line {
6+
pub fn get_vertex_data<P: HasCoords>(
7+
points_a: &P,
8+
point_b: &P,
9+
width: f32,
10+
color: Color,
11+
) -> Vec<f32> {
12+
let half_width = width / 2.0;
13+
let parallel_angle = (point_b.y() - points_a.y()).atan2(point_b.x() - points_a.x());
14+
let angle = parallel_angle + std::f32::consts::PI / 2.0; // perpendicular angle
15+
let ax = points_a.x() - half_width * parallel_angle.cos();
16+
let ay = points_a.y() - half_width * parallel_angle.sin();
17+
let bx = point_b.x() + half_width * parallel_angle.cos();
18+
let by = point_b.y() + half_width * parallel_angle.sin();
19+
20+
let vertex_data: [Point; 6] = [
21+
Point {
22+
x: ax - half_width * angle.cos(),
23+
y: ay - half_width * angle.sin(),
24+
},
25+
Point {
26+
x: ax + half_width * angle.cos(),
27+
y: ay + half_width * angle.sin(),
28+
},
29+
Point {
30+
x: bx + half_width * angle.cos(),
31+
y: by + half_width * angle.sin(),
32+
},
33+
Point {
34+
x: bx - half_width * angle.cos(),
35+
y: by - half_width * angle.sin(),
36+
},
37+
Point {
38+
x: ax - half_width * angle.cos(),
39+
y: ay - half_width * angle.sin(),
40+
},
41+
Point {
42+
x: bx + half_width * angle.cos(),
43+
y: by + half_width * angle.sin(),
44+
},
45+
];
46+
47+
vertex_data
48+
.iter()
49+
.flat_map(|point| {
50+
vec![
51+
point.x, point.y, 0.0, 1.0, color.0, color.1, color.2, color.3,
52+
]
53+
})
54+
.collect()
55+
}
56+
}

crate/src/texture.rs

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
1-
use serde::{Deserialize, Serialize};
2-
use wasm_bindgen::convert::FromWasmAbi;
3-
4-
use crate::Point;
5-
6-
#[derive(Serialize, Deserialize)]
7-
pub struct VertexPoint {
8-
x: f32,
9-
y: f32,
10-
u: f32,
11-
v: f32,
12-
}
1+
use crate::{Point, VertexPoint};
132

143
pub struct Texture {
15-
points: Vec<VertexPoint>,
16-
pub id: usize,
4+
id: f32, // for picking
5+
pub points: Vec<VertexPoint>, // for border drawing
6+
pub texture_id: usize,
177
}
188

199
impl Texture {
20-
pub fn new(points: Vec<VertexPoint>, id: usize) -> Texture {
21-
Texture { points, id }
10+
pub fn new(id: usize, points: Vec<VertexPoint>, texture_id: usize) -> Texture {
11+
Texture {
12+
id: id as f32,
13+
points,
14+
texture_id,
15+
}
2216
}
2317

24-
pub fn add_vertex(&self, verticies: &mut Vec<f32>) {
18+
pub fn get_vertex_data(&self) -> Vec<f32> {
2519
let points: [&VertexPoint; 6] = [
2620
&self.points[0],
2721
&self.points[1],
@@ -30,14 +24,10 @@ impl Texture {
3024
&self.points[3],
3125
&self.points[0],
3226
];
33-
points.iter().for_each(|point| {
34-
verticies.push(point.x);
35-
verticies.push(point.y);
36-
verticies.push(0.0);
37-
verticies.push(1.0);
38-
verticies.push(point.u);
39-
verticies.push(point.v);
40-
});
27+
points
28+
.iter()
29+
.flat_map(|point| vec![point.x, point.y, 0.0, 1.0, point.u, point.v])
30+
.collect()
4131
}
4232

4333
pub fn update_coords(&mut self, new_points: Vec<Point>) {
@@ -49,4 +39,19 @@ impl Texture {
4939
point.y = new_points[index].y;
5040
});
5141
}
42+
43+
pub fn get_vertex_pick_data(&self) -> Vec<f32> {
44+
let points: [&VertexPoint; 6] = [
45+
&self.points[0],
46+
&self.points[1],
47+
&self.points[2],
48+
&self.points[2],
49+
&self.points[3],
50+
&self.points[0],
51+
];
52+
points
53+
.iter()
54+
.flat_map(|point| vec![point.x, point.y, 0.0, 1.0, point.u, point.v, self.id])
55+
.collect()
56+
}
5257
}

crate/src/types.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
// Define a trait for any struct that has x and y fields
4+
pub trait HasCoords {
5+
fn x(&self) -> f32;
6+
fn y(&self) -> f32;
7+
}
8+
9+
#[derive(Serialize, Deserialize)]
10+
pub struct Point {
11+
pub x: f32,
12+
pub y: f32,
13+
}
14+
15+
impl HasCoords for Point {
16+
fn x(&self) -> f32 {
17+
self.x
18+
}
19+
fn y(&self) -> f32 {
20+
self.y
21+
}
22+
}
23+
24+
#[derive(Serialize, Deserialize)]
25+
pub struct VertexPoint {
26+
pub x: f32,
27+
pub y: f32,
28+
pub u: f32,
29+
pub v: f32,
30+
}
31+
32+
impl HasCoords for VertexPoint {
33+
fn x(&self) -> f32 {
34+
self.x
35+
}
36+
fn y(&self) -> f32 {
37+
self.y
38+
}
39+
}
40+
41+
pub type Color = (f32, f32, f32, f32);

integration-tests/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
import initCreator from "../src/index"
33

4+
const ASSET_ID = 1
5+
46
async function test() {
57
const canvas = document.querySelector<HTMLCanvasElement>("canvas")!
68
const creator = await initCreator(canvas)
@@ -13,14 +15,14 @@ async function test() {
1315
const img = new Image()
1416
img.src = URL.createObjectURL(files[0])
1517
img.onload = () => {
16-
creator.addImage(img)
18+
creator.addImage(ASSET_ID, img)
1719
}
1820
})
1921

2022
let offset = 100
2123
const updateImgPositionBtn = document.querySelector<HTMLButtonElement>('#img-position')!
2224
updateImgPositionBtn.addEventListener('click', () => {
23-
creator.updatePoints(0, [
25+
creator.updatePoints(ASSET_ID, [
2426
{ x: offset, y: offset },
2527
{ x: offset + 400, y: offset },
2628
{ x: offset + 400, y: offset + 400 },

integration-tests/tests/creator.spec.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,24 @@ test('visible image after upload', async ({ page }, testinfo) => {
2121
// await expect(page).toHaveScreenshot('webgpu-report.png');
2222

2323
await page.goto('/')
24+
await page.waitForLoadState("networkidle")
2425

26+
/** =========PNG IMAGE UPLOAD============ */
2527
const fileInput = page.locator('input[type="file"]')
2628
const testImagePath = path.join(__dirname, '../image-sample.png')
2729
await fileInput.setInputFiles(testImagePath)
2830

2931
const canvas = page.locator('canvas')
32+
await expect(canvas).toHaveScreenshot('after-upload.png')
3033

31-
await expect(canvas).toBeVisible()
32-
33-
await expect(canvas).toHaveScreenshot(['after-upload.png'])
34-
34+
/** =========IMAGES POSITION UPDATES============ */
3535
const moveImgBtn = page.locator('#img-position')
3636
await moveImgBtn.click()
3737
await expect(canvas).toHaveScreenshot('after-move.png')
38+
39+
/** =========DISPLAYS BORDER AROUND HOVERED ASSET============ */
40+
await page.mouse.move(200, 300)
41+
await expect(canvas).toHaveScreenshot('after-hover.png')
3842
})
3943

4044

224 KB
Loading

0 commit comments

Comments
 (0)