Skip to content

Commit a19cdb6

Browse files
committed
zoom and center
1 parent 5d406ec commit a19cdb6

File tree

22 files changed

+420
-51
lines changed

22 files changed

+420
-51
lines changed

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,20 @@ Agent Management
351351
Styling
352352
---
353353
354+
> `center(double x, double y)` <br>
355+
> Move the center of the view to the point `x,y`. To keep a particular agent in the center of the view, you can put
356+
> ```c++
357+
> center(x(), y());
358+
> ```
359+
> in its `update()` method.
360+
> See `examples/teleporter` for an example.
361+
> &#x246F; New in 1.6.
362+
363+
> `zoom(double z)` <br>
364+
> Change the zoom level in the viewer. THe default is 1.0.
365+
> See `examples/teleporter` for an example.
366+
> &#x246F; New in 1.6.
367+
354368
> `void set_style(json style)` <br>
355369
> Change the agent's style, just as in the configuration file.
356370
> Any valid svg styling will work.
@@ -452,8 +466,9 @@ The brower client relays mouse click, button press, and keyboard events to the e
452466
453467
> Name: `connection`<br>
454468
> Event: Sent when a new client attaches to the enviro server.<br>
455-
> Value: An object with a string valued `id` field that should be unique to the client that has connected.
456-
> Note that this ID will be sent with all other events as well. &#x246E; New in 1.5
469+
> Value: An object with a string valued `client_id` field that should be unique to the client that has connected.
470+
> Note that this ID will be sent with all other events as well. &#x246E; New in 1.5.
471+
> Note: In v1.6 the key changed from "id" to "client_id" so it would not conflict with the "id" field in the "agent_click" event.
457472
458473
> Name: `screen_click`<br>
459474
> Event: Sent when a user clicks on the screen.<br>
@@ -488,7 +503,7 @@ To respond to events in your code, you should put elma watchers into the `init()
488503
```c++
489504
void init() {
490505
watch("agent_click", (Event& e) => {
491-
if ( e.value.id == id() ) {
506+
if ( e.value()["id"] == id() ) {
492507
std::cout << "ouch!\n";
493508
}
494509
});

client/src/enviro.js

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ var CLIENT_ID;
1111

1212
var HOST = window.location.href.substring(0, window.location.href.length - 1);
1313

14+
var ZOOM = 1;
15+
var CX=0, CY=0;
16+
var CENTER_DEF = false;
17+
1418
function generate_id(){
1519
// Source: https://gist.github.com/gordonbrander/2230317
1620
function chr4(){
1721
return Math.random().toString(16).slice(-4);
1822
}
1923
return chr4() + chr4() +
2024
'-' + chr4() +
21-
'-' + chr4() +
25+
'-' + chr4() +
2226
'-' + chr4() +
2327
'-' + chr4() + chr4() + chr4();
2428
}
@@ -54,7 +58,7 @@ class Sensor extends React.Component {
5458

5559
function post_event(data) {
5660
let data_with_id = data;
57-
data_with_id.id = CLIENT_ID;
61+
data_with_id.client_id = CLIENT_ID;
5862
fetch(HOST+':8765/event', {
5963
method: "POST",
6064
mode: 'no-cors',
@@ -72,12 +76,12 @@ class Agent extends React.Component {
7276
}
7377
}
7478

75-
click(e) {
79+
click(e) {
7680
post_event({
7781
type: "agent_click",
7882
id: this.props.agent.id,
79-
x: e.clientX - this.rect.left - this.rect.width/2,
80-
y: e.clientY - this.rect.top - this.rect.height/2
83+
x: (e.clientX - this.rect.left - this.rect.width/2)/ZOOM,
84+
y: (e.clientY - this.rect.top - this.rect.height/2)/ZOOM
8185
});
8286
e.stopPropagation();
8387
}
@@ -124,16 +128,41 @@ class Arena extends React.Component {
124128

125129
click(e) {
126130

127-
post_event({
131+
let cx = CX - this.props.w/(2*ZOOM),
132+
cy = CY - this.props.h/(2*ZOOM)
133+
134+
post_event({
128135
type: "screen_click",
129-
x: e.clientX - this.rect.left - this.rect.width/2,
130-
y: e.clientY - this.rect.top - this.rect.height/2
136+
x: (e.clientX - this.rect.left - this.rect.width/2)/ZOOM - cx,
137+
y: (e.clientY - this.rect.top - this.rect.height/2)/ZOOM - cy
131138
});
132139

133140
}
134141

142+
dist(x1, y1, x2, y2) {
143+
return Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
144+
}
145+
135146
render() {
136-
let center = `translate(${this.props.w/2} ${this.props.h/2}) scale(1)`;
147+
148+
let zoom = this.props.data.zoom,
149+
origin_x = this.props.w/(2*zoom),
150+
origin_y = this.props.h/(2*zoom),
151+
offset_x = this.props.data.center.x,
152+
offset_y = this.props.data.center.y,
153+
target_x = origin_x - offset_x,
154+
target_y = origin_y - offset_y;
155+
156+
if ( this.dist(CX,CY, target_x, target_y ) > 35 ) {
157+
CX = target_x;
158+
CY = target_y;
159+
} else {
160+
CX += 0.2 * (origin_x - offset_x - CX);
161+
CY += 0.2 * (origin_y - offset_y - CY);
162+
}
163+
164+
let center = `scale(${zoom}) translate(${CX} ${CY})`;
165+
137166
return <svg width={this.props.w}
138167
height={this.props.h}
139168
onClick={e => this.click(e)}
@@ -142,13 +171,14 @@ class Arena extends React.Component {
142171
{this.props.data.agents.map(agent => <Agent agent={agent} key={agent.id} />)}
143172
</g>
144173
</svg>
174+
145175
}
176+
146177
}
147178

148179
class Taskbar extends React.Component {
149180

150181
click(e, value) {
151-
152182
post_event({
153183
type: "button_click",
154184
value: value
@@ -202,7 +232,6 @@ class Enviro extends React.Component {
202232
.then(res => res.json())
203233
.then(
204234
res => {
205-
console.log("got config", res.config);
206235
this.setState({ mode: "connected", config: res.config }, () => {
207236
setTimeout(() => { this.update() } , 25);
208237
});
@@ -211,6 +240,7 @@ class Enviro extends React.Component {
211240
this.setState({ mode: "connecting", config: null, error: { message: "No connection" }}, () => {
212241
setTimeout(() => { this.update() } , 1000);
213242
});
243+
CENTER_DEF = false;
214244
}
215245
)
216246
}
@@ -227,9 +257,14 @@ class Enviro extends React.Component {
227257
}, () => {
228258
setTimeout(() => { this.update() } , 25);
229259
});
260+
if ( !CENTER_DEF ) {
261+
CX = window.innerWidth/(2*result.zoom) - result.center.x;
262+
CY = (window.innerWidth/2 - 41)/result.zoom - result.center.x;
263+
CENTER_DEF = true;
264+
}
265+
ZOOM = result.zoom;
230266
},
231267
(error) => {
232-
console.log(error);
233268
this.setState({
234269
mode: "connecting",
235270
error
@@ -270,8 +305,8 @@ class Enviro extends React.Component {
270305
metaKey: e.metaKey,
271306
repeat: e.repeat
272307
});
273-
});
274-
308+
});
309+
275310
}
276311

277312
render() {

examples/multiuser/src/coordinator.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ class CoordinatorController : public Process, public AgentInterface {
1212

1313
void init() {
1414
watch("connection", [&](Event e) {
15-
std::cout << "Connection from " << e.value()["id"] << "\n";
16-
Agent& a = add_agent("Guy", 0, y, 0, {{"fill","gray"},{"stroke","black"}});
17-
a.set_client_id(e.value()["id"]);
18-
y += 50;
15+
if ( ! e.value()["client_id"].is_null() ) {
16+
std::cout << "Connection from " << e.value() << "\n";
17+
Agent& a = add_agent("Guy", 0, y, 0, {{"fill","gray"},{"stroke","black"}});
18+
a.set_client_id(e.value()["client_id"]);
19+
y += 50;
20+
}
1921
});
2022
}
2123
void start() {}

examples/multiuser/src/guy.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class GuyController : public Process, public AgentInterface {
1212

1313
void init() {
1414
watch("keydown", [&](Event &e) {
15-
if ( e.value()["id"] == get_client_id() ) {
15+
if ( e.value()["client_id"] == get_client_id() ) {
1616
auto k = e.value()["key"].get<std::string>();
1717
if ( k == " " && !firing ) {
1818
Agent& bullet = add_agent("Bullet",
@@ -34,7 +34,7 @@ class GuyController : public Process, public AgentInterface {
3434
}
3535
});
3636
watch("keyup", [&](Event &e) {
37-
if ( e.value()["id"] == get_client_id() ) {
37+
if ( e.value()["client_id"] == get_client_id() ) {
3838
auto k = e.value()["key"].get<std::string>();
3939
if ( k == " " ) {
4040
firing = false;

examples/teleporter/config.json

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,26 @@
11
{
22

3-
"name": "Chase the Block!",
3+
"name": "Click to make the robot chase the block",
44
"ip": "0.0.0.0",
55
"port": 8765,
66

7+
"buttons": [
8+
{
9+
"name": "zoom_in",
10+
"label": "Zoom In",
11+
"style": { "background": "white", "borderColor": "black" }
12+
},
13+
{
14+
"name": "zoom_out",
15+
"label": "Zoom Out",
16+
"style": { "background": "white", "borderColor": "black" }
17+
},
18+
{
19+
"name": "toggle_track",
20+
"label": "Toggle Tracking",
21+
"style": { "background": "white", "borderColor": "black" }
22+
}
23+
],
724

825
"agents": [
926
{

examples/teleporter/src/chaser.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ using namespace enviro;
88
class ChaserController : public Process, public AgentInterface {
99

1010
public:
11-
ChaserController() : Process(), AgentInterface() {}
11+
ChaserController() : Process(), AgentInterface(), z(1), tracking(false) {}
1212

1313
void init() {
1414
goal_x = 0;
@@ -18,14 +18,33 @@ class ChaserController : public Process, public AgentInterface {
1818
goal_y = e.value()["y"];
1919
std::cout << "New goal: " << goal_x << ", " << goal_y << "\n";
2020
});
21+
watch("button_click", [&](Event& e) {
22+
if ( e.value()["value"] == "zoom_in" ) {
23+
z *= 2;
24+
zoom(z);
25+
} else if ( e.value()["value"] == "zoom_out" ) {
26+
z /= 2;
27+
zoom(z);
28+
} else if ( e.value()["value"] == "toggle_track" ) {
29+
tracking = !tracking;
30+
if ( !tracking ) {
31+
center(0,0);
32+
}
33+
}
34+
});
2135
}
2236
void start() {}
2337
void update() {
2438
move_toward(goal_x, goal_y);
39+
if ( tracking ) {
40+
center(x(), y());
41+
}
2542
}
2643
void stop() {}
2744

2845
double goal_x, goal_y;
46+
double z;
47+
bool tracking;
2948

3049
};
3150

examples/virus/config.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,72 +8,72 @@
88

99
{
1010
"definition": "defs/player.json",
11-
"style": { "fill": "#555", "stroke": "#888", "stroke-width": "5px", "stroke-opacity": "0.25"},
11+
"style": { "fill": "#555", "stroke": "#888", "strokeWidth": "5px", "strokeOpacity": "0.25"},
1212
"position": { "x": 400, "y": 200, "theta": 2 }
1313
},
1414
{
1515
"definition": "defs/bullet.json",
16-
"style": { "fill": "green", "stroke": "#888", "stroke-width": "5px", "stroke-opacity": "0.25" },
16+
"style": { "fill": "green", "stroke": "#888", "strokeWidth": "5px", "strokeOpacity": "0.25" },
1717
"position": {"x": -400, "y": -200, "theta": 3.14 }
1818
},
1919

2020

2121
{
2222
"definition": "defs/virus.json",
23-
"style": { "fill": "orange", "stroke": "black", "stroke-width": "10px", "stroke-opacity": "0.25" },
23+
"style": { "fill": "orange", "stroke": "black", "strokeWidth": "10px", "strokeOpacity": "0.25" },
2424
"position": { "x": 100, "y": 100, "theta": 0 }
2525
},
2626
{
2727
"definition": "defs/virus.json",
28-
"style": { "fill": "orange", "stroke": "black", "stroke-width": "10px", "stroke-opacity": "0.25" },
28+
"style": { "fill": "orange", "stroke": "black", "strokeWidth": "10px", "strokeOpacity": "0.25" },
2929
"position": { "x": 100, "y": -100, "theta": 0 }
3030
},
3131
{
3232
"definition": "defs/virus.json",
33-
"style": { "fill": "orange", "stroke": "black", "stroke-width": "10px", "stroke-opacity": "0.25" },
33+
"style": { "fill": "orange", "stroke": "black", "strokeWidth": "10px", "strokeOpacity": "0.25" },
3434
"position": { "x": -100, "y": 100, "theta": 0 }
3535
},
3636
{
3737
"definition": "defs/virus.json",
38-
"style": { "fill": "orange", "stroke": "black", "stroke-width": "10px", "stroke-opacity": "0.25" },
38+
"style": { "fill": "orange", "stroke": "black", "strokeWidth": "10px", "strokeOpacity": "0.25" },
3939
"position": { "x": -100, "y": -100, "theta": 0 }
4040
},
4141

4242

4343

4444
{
4545
"definition": "defs/cell.json",
46-
"style": { "fill": "lightblue", "stroke": "#aaa", "stroke-width": "10px", "stroke-opacity": "0.25" },
46+
"style": { "fill": "lightblue", "stroke": "#aaa", "strokeWidth": "10px", "strokeOpacity": "0.25" },
4747
"position": { "x": 0, "y": 0, "theta": 0 }
4848
},
4949
{
5050
"definition": "defs/cell.json",
51-
"style": { "fill": "lightblue", "stroke": "#aaa", "stroke-width": "10px", "stroke-opacity": "0.25" },
51+
"style": { "fill": "lightblue", "stroke": "#aaa", "strokeWidth": "10px", "strokeOpacity": "0.25" },
5252
"position": { "x": 300, "y": 0, "theta": 0 }
5353
},
5454
{
5555
"definition": "defs/cell.json",
56-
"style": { "fill": "lightblue", "stroke": "#aaa", "stroke-width": "10px", "stroke-opacity": "0.25" },
56+
"style": { "fill": "lightblue", "stroke": "#aaa", "strokeWidth": "10px", "strokeOpacity": "0.25" },
5757
"position": { "x": -300, "y": 0, "theta": 0 }
5858
},
5959
{
6060
"definition": "defs/cell.json",
61-
"style": { "fill": "lightblue", "stroke": "#aaa", "stroke-width": "10px", "stroke-opacity": "0.25" },
61+
"style": { "fill": "lightblue", "stroke": "#aaa", "strokeWidth": "10px", "strokeOpacity": "0.25" },
6262
"position": { "x": -150, "y": 200, "theta": 0 }
6363
},
6464
{
6565
"definition": "defs/cell.json",
66-
"style": { "fill": "lightblue", "stroke": "#aaa", "stroke-width": "10px", "stroke-opacity": "0.25" },
66+
"style": { "fill": "lightblue", "stroke": "#aaa", "strokeWidth": "10px", "strokeOpacity": "0.25" },
6767
"position": { "x": 150, "y": 200, "theta": 0 }
6868
},
6969
{
7070
"definition": "defs/cell.json",
71-
"style": { "fill": "lightblue", "stroke": "#aaa", "stroke-width": "10px", "stroke-opacity": "0.25" },
71+
"style": { "fill": "lightblue", "stroke": "#aaa", "strokeWidth": "10px", "strokeOpacity": "0.25" },
7272
"position": { "x": -150, "y": -200, "theta": 0 }
7373
},
7474
{
7575
"definition": "defs/cell.json",
76-
"style": { "fill": "lightblue", "stroke": "#aaa", "stroke-width": "10px", "stroke-opacity": "0.25" },
76+
"style": { "fill": "lightblue", "stroke": "#aaa", "strokeWidth": "10px", "strokeOpacity": "0.25" },
7777
"position": { "x": 150, "y": -200, "theta": 0 }
7878
}
7979

0 commit comments

Comments
 (0)