added camera vector
This commit is contained in:
parent
54ed2838f7
commit
71854d2a95
@ -19,6 +19,9 @@ export const PATH_TRACES_OPACITY = 0.8;
|
|||||||
export const PATH_TRACES_WIDTH = 1.5;
|
export const PATH_TRACES_WIDTH = 1.5;
|
||||||
export const PATH_TRACES_DASHED_OPACITY = 1.0;
|
export const PATH_TRACES_DASHED_OPACITY = 1.0;
|
||||||
|
|
||||||
|
// PANNING
|
||||||
|
export const PANNING_ZERO_TOUCH_THRESHOLD = 200;
|
||||||
|
|
||||||
// SIZES
|
// SIZES
|
||||||
export const POINTER_HISTORY_SIZE = 20;
|
export const POINTER_HISTORY_SIZE = 20;
|
||||||
export const OBJECT_HISTORY_SIZE = 1e5;
|
export const OBJECT_HISTORY_SIZE = 1e5;
|
||||||
|
|||||||
@ -140,7 +140,7 @@ export class Display {
|
|||||||
ctx.resetTransform();
|
ctx.resetTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
drawBox({start, end}) {
|
drawBox(start, end) {
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
ctx.strokeStyle = 'rgb(0, 255, 0)';
|
ctx.strokeStyle = 'rgb(0, 255, 0)';
|
||||||
ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y);
|
ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y);
|
||||||
|
|||||||
@ -2,6 +2,10 @@
|
|||||||
// `item` must let us read/write property `hidden`
|
// `item` must let us read/write property `hidden`
|
||||||
// `parentEl` is the containing element for `itemEl`
|
// `parentEl` is the containing element for `itemEl`
|
||||||
// `itemEl` is the
|
// `itemEl` is the
|
||||||
|
//
|
||||||
|
// The idea is that item remains a member of items, but
|
||||||
|
// its elementmay be added and removed from the parent element.
|
||||||
|
// We use the items array to determine the placement of itemEl
|
||||||
export function show({items, item, parentEl, itemEl}) {
|
export function show({items, item, parentEl, itemEl}) {
|
||||||
if (items.length < 2) {
|
if (items.length < 2) {
|
||||||
parentEl.appendChild(itemEl);
|
parentEl.appendChild(itemEl);
|
||||||
|
|||||||
63
panning.js
63
panning.js
@ -1,4 +1,5 @@
|
|||||||
import {add, copy, div, mult, sub, zero} from "./vector.js";
|
import {PANNING_ZERO_TOUCH_THRESHOLD} from "./config.js";
|
||||||
|
import {add, copy, div, mult, zero} from "./vector.js";
|
||||||
|
|
||||||
export class Panning {
|
export class Panning {
|
||||||
sim = undefined;
|
sim = undefined;
|
||||||
@ -11,10 +12,6 @@ export class Panning {
|
|||||||
this.sim = sim;
|
this.sim = sim;
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePointerDown({x, y}) {
|
|
||||||
this.initializeTouch({x, y});
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeTouch({x, y}) {
|
initializeTouch({x, y}) {
|
||||||
this.touchStart = {
|
this.touchStart = {
|
||||||
x,
|
x,
|
||||||
@ -30,6 +27,13 @@ export class Panning {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePointerDown({x, y}) {
|
||||||
|
this.initializeTouch({x, y});
|
||||||
|
if (this.paused) {
|
||||||
|
this.paused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// With fast panning, panning velocity calculation happens every move;
|
// With fast panning, panning velocity calculation happens every move;
|
||||||
// With normal panning, calculation only happens at pointer up.
|
// With normal panning, calculation only happens at pointer up.
|
||||||
handlePointerMove({x, y}) {
|
handlePointerMove({x, y}) {
|
||||||
@ -42,49 +46,46 @@ export class Panning {
|
|||||||
dy: x - this.touchStart.y,
|
dy: x - this.touchStart.y,
|
||||||
dt: this.sim.rawTime - this.touchStart.t,
|
dt: this.sim.rawTime - this.touchStart.t,
|
||||||
};
|
};
|
||||||
if (this.sim.getOption('compensate.fastPanning')) {
|
|
||||||
this.updateVelocity();
|
// Convert pointer velocity to simulation scale
|
||||||
|
let velocity = div(this.sim.pointer.latestVelocity, this.sim.display.scale);
|
||||||
|
|
||||||
|
// Optional time scale compensation
|
||||||
|
if (this.sim.getOption('compensate.timeScale')) {
|
||||||
|
velocity = div(velocity, this.sim.timeScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Additional scaling factor
|
||||||
|
velocity = mult(velocity, this.sim.getOption('display.panningSpeed'));
|
||||||
|
|
||||||
|
// TODO: Make it easier to slow down the camera
|
||||||
|
|
||||||
|
// Add pointer velocity to current panning velocity
|
||||||
|
this.velocity = add(this.velocity, velocity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePointerUp() {
|
handlePointerUp() {
|
||||||
if (this.touchStart && this.touchLatest) {
|
if (this.touchStart && this.touchLatest) {
|
||||||
if (this.touchLatest.dt === 0) {
|
if (this.touchLatest.dt < PANNING_ZERO_TOUCH_THRESHOLD) {
|
||||||
this.velocity = zero;
|
this.velocity = zero;
|
||||||
}
|
}
|
||||||
this.touchStart = undefined;
|
this.touchStart = undefined;
|
||||||
|
|
||||||
if (this.sim.getOption('compensate.fastPanning')) {
|
if (this.sim.getOption('compensate.fastPanning')) {
|
||||||
this.velocity = zero;
|
this.velocity = zero;
|
||||||
} else {
|
|
||||||
this.updateVelocity();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frame(elapsedTime) {
|
frame(elapsedTime) {
|
||||||
const {touchStart: start, touchLatest: latest} = this;
|
|
||||||
const {display} = this.sim;
|
const {display} = this.sim;
|
||||||
|
|
||||||
// Direct translate, unless using fast panning
|
|
||||||
if (start && latest && !this.sim.getOption('compensate.fastPanning')) {
|
|
||||||
// start and latest are in screen coordinates, need to convert to sim scale
|
|
||||||
const delta = div(sub(latest, start), display.scale);
|
|
||||||
display.viewOrigin = sub(start.viewOrigin, delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply update to viewOrigin based on panning
|
// Apply update to viewOrigin based on panning
|
||||||
if (!this.paused) {
|
if (!this.paused) {
|
||||||
// elapsedTime is scaled by time scale, is that what we want?
|
// elapsedTime is scaled by time scale, is that what we want?
|
||||||
// Yes because if panning.velocity == obj.velocity, object should stay in view
|
// Yes because if panning.velocity == obj.velocity, object should stay in view
|
||||||
const delta = mult(this.velocity, elapsedTime);
|
display.viewOrigin = add(display.viewOrigin, mult(this.velocity, elapsedTime));
|
||||||
display.viewOrigin = add(display.viewOrigin, delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update what's considered start
|
|
||||||
if (start && latest) {
|
|
||||||
this.initializeTouch(this.touchLatest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.sim.getOption('debug.panningInfo')) {
|
if (this.sim.getOption('debug.panningInfo')) {
|
||||||
@ -96,18 +97,6 @@ export class Panning {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVelocity() {
|
|
||||||
// Convert pointer velocity to simulation scale, and multiply by -1
|
|
||||||
// because the camera is panning opposite to the pointer velocity.
|
|
||||||
let velocity = div(this.sim.pointer.latestVelocity, -this.sim.display.scale);
|
|
||||||
if (this.sim.getOption('compensate.timeScale')) {
|
|
||||||
velocity = div(velocity, this.sim.timeScale);
|
|
||||||
}
|
|
||||||
// Also add current panning
|
|
||||||
velocity = add(velocity, this.velocity);
|
|
||||||
this.velocity = velocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
setVelocity(velocity) {
|
setVelocity(velocity) {
|
||||||
this.velocity = velocity;
|
this.velocity = velocity;
|
||||||
if (!this.sim.playing) {
|
if (!this.sim.playing) {
|
||||||
|
|||||||
15
pointer.js
15
pointer.js
@ -3,24 +3,21 @@ import {
|
|||||||
MODE_OBJECT_SELECT,
|
MODE_OBJECT_SELECT,
|
||||||
MODE_PAN_VIEW,
|
MODE_PAN_VIEW,
|
||||||
POINTER_HISTORY_SIZE,
|
POINTER_HISTORY_SIZE,
|
||||||
TOOLBAR_CLASSNAME,
|
|
||||||
ZOOM_IN_FACTOR,
|
ZOOM_IN_FACTOR,
|
||||||
ZOOM_OUT_FACTOR,
|
ZOOM_OUT_FACTOR
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
|
|
||||||
export class Pointer {
|
export class Pointer {
|
||||||
sim = undefined;
|
sim = undefined;
|
||||||
|
|
||||||
pointerHistory = [];
|
pointerHistory = [];
|
||||||
touchStart = undefined; // {x: undefined, y: undefined, t: undefined};
|
|
||||||
touchLatest = undefined; // {x: undefined, y: undefined, t: undefined};
|
|
||||||
suppressClick = false;
|
suppressClick = false;
|
||||||
|
|
||||||
constructor(sim) {
|
constructor(sim) {
|
||||||
this.sim = sim;
|
this.sim = sim;
|
||||||
|
|
||||||
// Monitor mouse movements
|
// Monitor mouse movements
|
||||||
const el = window;
|
const el = this.sim.display.canvas;
|
||||||
|
|
||||||
el.addEventListener('pointermove', e => {
|
el.addEventListener('pointermove', e => {
|
||||||
if (this.sim.getOption('debug.cursorInfo')) {
|
if (this.sim.getOption('debug.cursorInfo')) {
|
||||||
@ -30,14 +27,6 @@ export class Pointer {
|
|||||||
});
|
});
|
||||||
|
|
||||||
el.addEventListener('pointerdown', e => {
|
el.addEventListener('pointerdown', e => {
|
||||||
let target = e.target;
|
|
||||||
while (target && !target.classList?.contains(TOOLBAR_CLASSNAME)) {
|
|
||||||
target = target.parentNode;
|
|
||||||
}
|
|
||||||
if (target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.handlePointerDown({x: e.clientX, y: e.clientY});
|
this.handlePointerDown({x: e.clientX, y: e.clientY});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
12
select.js
12
select.js
@ -1,4 +1,4 @@
|
|||||||
import {copy} from './vector.js';
|
import {add, copy, mult} from './vector.js';
|
||||||
|
|
||||||
export class Select {
|
export class Select {
|
||||||
sim = undefined;
|
sim = undefined;
|
||||||
@ -67,8 +67,14 @@ export class Select {
|
|||||||
this.selectedSingle = this.selectedGroup[0] ?? undefined;
|
this.selectedSingle = this.selectedGroup[0] ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame() {
|
frame(elapsedTime) {
|
||||||
if (!this.box.start) return;
|
if (!this.box.start) return;
|
||||||
this.sim.display.drawBox(this.box)
|
// If panning, let's update the position of our box so it doesn't drift away
|
||||||
|
const {velocity} = this.sim.panning;
|
||||||
|
const delta = mult(velocity, elapsedTime);
|
||||||
|
this.box.start = add(this.box.start, delta);
|
||||||
|
this.box.end = add(this.box.end, delta);
|
||||||
|
// Display the box
|
||||||
|
this.sim.display.drawBox(this.box.start, this.box.end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,8 @@ export const simOptions = {
|
|||||||
dashedTraces: ['Dashed', 'boolean', false, {tall: true, showIf: 'display.traces'}],
|
dashedTraces: ['Dashed', 'boolean', false, {tall: true, showIf: 'display.traces'}],
|
||||||
velocityScale: ['Velocity<br>Vec Scale', 'number', 80, {showIf: 'display.velocity'}],
|
velocityScale: ['Velocity<br>Vec Scale', 'number', 80, {showIf: 'display.velocity'}],
|
||||||
accelerationScale: ['Accel<br>Vec Scale', 'number', 800, {showIf: 'display.acceleration'}],
|
accelerationScale: ['Accel<br>Vec Scale', 'number', 800, {showIf: 'display.acceleration'}],
|
||||||
zoomVectors: ['Zoom Vectors', 'boolean', true]
|
zoomVectors: ['Zoom Vectors', 'boolean', true],
|
||||||
|
panningSpeed: ['Pan<br>Speed', 'number', 0.1],
|
||||||
},
|
},
|
||||||
compensate: {
|
compensate: {
|
||||||
timeScale: ['Time Scale Compensator', 'boolean', false, {wide: true}],
|
timeScale: ['Time Scale Compensator', 'boolean', false, {wide: true}],
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import {CameraTool} from './tool/camera.js';
|
||||||
import {ModeSwitch} from './tool/modes.js';
|
import {ModeSwitch} from './tool/modes.js';
|
||||||
import {ObjectTool} from './tool/object.js';
|
import {ObjectTool} from './tool/object.js';
|
||||||
import {ObjectsTool} from './tool/objects.js';
|
import {ObjectsTool} from './tool/objects.js';
|
||||||
@ -11,6 +12,9 @@ import {Toolbar} from './toolbar.js';
|
|||||||
export function initializeTools(sim) {
|
export function initializeTools(sim) {
|
||||||
sim.toolbarGroups = {
|
sim.toolbarGroups = {
|
||||||
left: new ToolbarGroup(sim)
|
left: new ToolbarGroup(sim)
|
||||||
|
.addToolbar(new Toolbar(sim, 'Camera')
|
||||||
|
.addTool(new CameraTool())
|
||||||
|
)
|
||||||
.addToolbar(new Toolbar(sim, 'Tools')
|
.addToolbar(new Toolbar(sim, 'Tools')
|
||||||
.addTool(new Zoom())
|
.addTool(new Zoom())
|
||||||
.addTool(new PlayPause())
|
.addTool(new PlayPause())
|
||||||
|
|||||||
@ -33,6 +33,10 @@ div[id=simulator] > canvas {
|
|||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.lhg-toolbar-group button {
|
||||||
|
/* opacity: 0.8; */
|
||||||
|
}
|
||||||
|
|
||||||
/* normal toolbar group */
|
/* normal toolbar group */
|
||||||
div.lhg-toolbar-group div.lhg-tool {
|
div.lhg-toolbar-group div.lhg-tool {
|
||||||
width: 12em;
|
width: 12em;
|
||||||
@ -102,7 +106,7 @@ div.lhg-tool button, div.lhg-tool input {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.lhg-tool button:hover {
|
div.lhg-tool button:hover, div.lhg-tool input:hover {
|
||||||
background-color: #444;
|
background-color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +126,7 @@ div.lhg-toolbar-header > * {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.lhg-tool .lhg-tool-info {
|
div.lhg-tool .lhg-tool-info, div.lhg-tool .lhg-tool-info:hover {
|
||||||
background-color: #111;
|
background-color: #111;
|
||||||
border-color: #000;
|
border-color: #000;
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
|
|||||||
42
tool/camera.js
Normal file
42
tool/camera.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import {VELOCITY_VECTOR_COLOR} from '../config.js';
|
||||||
|
import {Tool} from '../tool.js';
|
||||||
|
import {add, components, direction, div, magnitude} from '../vector.js';
|
||||||
|
|
||||||
|
export class CameraTool extends Tool {
|
||||||
|
setContainer(container) {
|
||||||
|
super.setContainer(container);
|
||||||
|
|
||||||
|
// Use the main sim display, but create a placeholder and draw inside it.
|
||||||
|
// That way we aren't blocking the main display more than necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.div.style.width = '150px';
|
||||||
|
this.div.style.height = '150px';
|
||||||
|
}
|
||||||
|
|
||||||
|
frame() {
|
||||||
|
if (!this.container.expanded) return;
|
||||||
|
|
||||||
|
const {display, panning} = this.sim;
|
||||||
|
const {left, top, width, height} = this.div.getBoundingClientRect();
|
||||||
|
const vecScale = this.sim.getOption('display.velocityScale');
|
||||||
|
|
||||||
|
// Draw a vector for the camera velocity
|
||||||
|
const offset = add(display.viewOrigin, div({x: left, y: top}, display.scale));
|
||||||
|
const start = add(offset, div({x: width, y: height}, 2 * display.scale));
|
||||||
|
let speed = magnitude(panning.velocity);
|
||||||
|
let arrowLength = Math.log10(speed + 1) * vecScale;
|
||||||
|
const arrowDirection = direction(panning.velocity);
|
||||||
|
if (!this.sim.getOption('display.zoomVectors')) {
|
||||||
|
arrowLength /= display.scale;
|
||||||
|
}
|
||||||
|
const end = add(start, components(arrowLength, arrowDirection));
|
||||||
|
display.drawArrow(start.x, start.y, end.x, end.y, {
|
||||||
|
style: VELOCITY_VECTOR_COLOR,
|
||||||
|
ifShort: 'head',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -60,7 +60,6 @@ export class OptionsTool extends Tool {
|
|||||||
}
|
}
|
||||||
for (const next of item.items) {
|
for (const next of item.items) {
|
||||||
const optionEl = this.visitItem(next, path);
|
const optionEl = this.visitItem(next, path);
|
||||||
// const option = {itemEl: optionEl};
|
|
||||||
group.items.push(next);
|
group.items.push(next);
|
||||||
if (this.shouldShow(next)) {
|
if (this.shouldShow(next)) {
|
||||||
groupEl.appendChild(optionEl);
|
groupEl.appendChild(optionEl);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user