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_DASHED_OPACITY = 1.0;
|
||||
|
||||
// PANNING
|
||||
export const PANNING_ZERO_TOUCH_THRESHOLD = 200;
|
||||
|
||||
// SIZES
|
||||
export const POINTER_HISTORY_SIZE = 20;
|
||||
export const OBJECT_HISTORY_SIZE = 1e5;
|
||||
|
||||
@ -140,7 +140,7 @@ export class Display {
|
||||
ctx.resetTransform();
|
||||
}
|
||||
|
||||
drawBox({start, end}) {
|
||||
drawBox(start, end) {
|
||||
const ctx = this.ctx;
|
||||
ctx.strokeStyle = 'rgb(0, 255, 0)';
|
||||
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`
|
||||
// `parentEl` is the containing element for `itemEl`
|
||||
// `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}) {
|
||||
if (items.length < 2) {
|
||||
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 {
|
||||
sim = undefined;
|
||||
@ -11,10 +12,6 @@ export class Panning {
|
||||
this.sim = sim;
|
||||
}
|
||||
|
||||
handlePointerDown({x, y}) {
|
||||
this.initializeTouch({x, y});
|
||||
}
|
||||
|
||||
initializeTouch({x, y}) {
|
||||
this.touchStart = {
|
||||
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 normal panning, calculation only happens at pointer up.
|
||||
handlePointerMove({x, y}) {
|
||||
@ -42,49 +46,46 @@ export class Panning {
|
||||
dy: x - this.touchStart.y,
|
||||
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() {
|
||||
if (this.touchStart && this.touchLatest) {
|
||||
if (this.touchLatest.dt === 0) {
|
||||
if (this.touchLatest.dt < PANNING_ZERO_TOUCH_THRESHOLD) {
|
||||
this.velocity = zero;
|
||||
}
|
||||
this.touchStart = undefined;
|
||||
|
||||
if (this.sim.getOption('compensate.fastPanning')) {
|
||||
this.velocity = zero;
|
||||
} else {
|
||||
this.updateVelocity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
frame(elapsedTime) {
|
||||
const {touchStart: start, touchLatest: latest} = this;
|
||||
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
|
||||
if (!this.paused) {
|
||||
// elapsedTime is scaled by time scale, is that what we want?
|
||||
// Yes because if panning.velocity == obj.velocity, object should stay in view
|
||||
const delta = mult(this.velocity, elapsedTime);
|
||||
display.viewOrigin = add(display.viewOrigin, delta);
|
||||
}
|
||||
|
||||
// Update what's considered start
|
||||
if (start && latest) {
|
||||
this.initializeTouch(this.touchLatest);
|
||||
display.viewOrigin = add(display.viewOrigin, mult(this.velocity, elapsedTime));
|
||||
}
|
||||
|
||||
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) {
|
||||
this.velocity = velocity;
|
||||
if (!this.sim.playing) {
|
||||
|
||||
15
pointer.js
15
pointer.js
@ -3,24 +3,21 @@ import {
|
||||
MODE_OBJECT_SELECT,
|
||||
MODE_PAN_VIEW,
|
||||
POINTER_HISTORY_SIZE,
|
||||
TOOLBAR_CLASSNAME,
|
||||
ZOOM_IN_FACTOR,
|
||||
ZOOM_OUT_FACTOR,
|
||||
ZOOM_OUT_FACTOR
|
||||
} from './config.js';
|
||||
|
||||
export class Pointer {
|
||||
sim = undefined;
|
||||
|
||||
pointerHistory = [];
|
||||
touchStart = undefined; // {x: undefined, y: undefined, t: undefined};
|
||||
touchLatest = undefined; // {x: undefined, y: undefined, t: undefined};
|
||||
suppressClick = false;
|
||||
|
||||
constructor(sim) {
|
||||
this.sim = sim;
|
||||
|
||||
// Monitor mouse movements
|
||||
const el = window;
|
||||
const el = this.sim.display.canvas;
|
||||
|
||||
el.addEventListener('pointermove', e => {
|
||||
if (this.sim.getOption('debug.cursorInfo')) {
|
||||
@ -30,14 +27,6 @@ export class Pointer {
|
||||
});
|
||||
|
||||
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});
|
||||
});
|
||||
|
||||
|
||||
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 {
|
||||
sim = undefined;
|
||||
@ -67,8 +67,14 @@ export class Select {
|
||||
this.selectedSingle = this.selectedGroup[0] ?? undefined;
|
||||
}
|
||||
|
||||
frame() {
|
||||
frame(elapsedTime) {
|
||||
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'}],
|
||||
velocityScale: ['Velocity<br>Vec Scale', 'number', 80, {showIf: 'display.velocity'}],
|
||||
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: {
|
||||
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 {ObjectTool} from './tool/object.js';
|
||||
import {ObjectsTool} from './tool/objects.js';
|
||||
@ -11,6 +12,9 @@ import {Toolbar} from './toolbar.js';
|
||||
export function initializeTools(sim) {
|
||||
sim.toolbarGroups = {
|
||||
left: new ToolbarGroup(sim)
|
||||
.addToolbar(new Toolbar(sim, 'Camera')
|
||||
.addTool(new CameraTool())
|
||||
)
|
||||
.addToolbar(new Toolbar(sim, 'Tools')
|
||||
.addTool(new Zoom())
|
||||
.addTool(new PlayPause())
|
||||
|
||||
@ -33,6 +33,10 @@ div[id=simulator] > canvas {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
div.lhg-toolbar-group button {
|
||||
/* opacity: 0.8; */
|
||||
}
|
||||
|
||||
/* normal toolbar group */
|
||||
div.lhg-toolbar-group div.lhg-tool {
|
||||
width: 12em;
|
||||
@ -102,7 +106,7 @@ div.lhg-tool button, div.lhg-tool input {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.lhg-tool button:hover {
|
||||
div.lhg-tool button:hover, div.lhg-tool input:hover {
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
@ -122,7 +126,7 @@ div.lhg-toolbar-header > * {
|
||||
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;
|
||||
border-color: #000;
|
||||
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) {
|
||||
const optionEl = this.visitItem(next, path);
|
||||
// const option = {itemEl: optionEl};
|
||||
group.items.push(next);
|
||||
if (this.shouldShow(next)) {
|
||||
groupEl.appendChild(optionEl);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user