mirror of https://github.com/sveltejs/svelte
parent
291703d291
commit
e9f0ffbd29
@ -1,35 +1,184 @@
|
||||
<script>
|
||||
import { spring } from 'svelte/motion';
|
||||
import { spring } from "svelte/motion";
|
||||
import { framerate } from "svelte/environment";
|
||||
import { derived } from "svelte/store";
|
||||
|
||||
let coords = spring({ x: 50, y: 50 }, {
|
||||
stiffness: 0.1,
|
||||
damping: 0.25
|
||||
});
|
||||
const s = spring(50);
|
||||
let prev_time = now();
|
||||
let prev_value = 50;
|
||||
const velocity = derived(
|
||||
s,
|
||||
(v) =>
|
||||
(-prev_value + (prev_value = v)) / (-prev_time + (prev_time = now())),
|
||||
0.0
|
||||
);
|
||||
let l_time;
|
||||
$: canvas && Draw($velocity, $s);
|
||||
let { mass, stiffness, damping, soft } = s;
|
||||
$: s.soft = soft;
|
||||
$: s.mass = mass;
|
||||
$: s.stiffness = stiffness;
|
||||
$: s.damping = damping;
|
||||
let solver;
|
||||
|
||||
let size = spring(10);
|
||||
function solve_spring(target, prev_velocity) {
|
||||
const target_ = target;
|
||||
const delta = target - $s;
|
||||
if (soft || 1 <= damping / (2.0 * Math.sqrt(stiffness * mass))) {
|
||||
const angular_frequency = -Math.sqrt(stiffness / mass);
|
||||
solver = (t) =>
|
||||
target_ -
|
||||
(delta + t * (-angular_frequency * delta - prev_velocity)) *
|
||||
Math.exp(t * angular_frequency);
|
||||
} else {
|
||||
const damping_frequency = Math.sqrt(
|
||||
4.0 * mass * stiffness - damping ** 2
|
||||
);
|
||||
const leftover =
|
||||
(damping * delta - 2.0 * mass * prev_velocity) / damping_frequency;
|
||||
const dfm = (0.5 * damping_frequency) / mass;
|
||||
const dm = -(0.5 * damping) / mass;
|
||||
let f = 0.0;
|
||||
solver = (t) =>
|
||||
target_ -
|
||||
(Math.cos((f = t * dfm)) * delta + Math.sin(f) * leftover) *
|
||||
Math.exp(t * dm);
|
||||
}
|
||||
reset_time = now();
|
||||
s.set((target__ = target));
|
||||
}
|
||||
let target__ = 50;
|
||||
let canvas;
|
||||
const start_time = now();
|
||||
let reset_time = start_time;
|
||||
const canvas_history = [];
|
||||
let max_x, min_x, max_y, min_y, canvas_width, canvas_height;
|
||||
let step, length;
|
||||
let last_index;
|
||||
let ctx;
|
||||
const XC = (x) => ((x - min_x) / (max_x - min_x)) * canvas_width;
|
||||
const YC = (y) =>
|
||||
canvas_height - ((y - min_y) / (max_y - min_y)) * canvas_height;
|
||||
const get_index = (i = 0) =>
|
||||
(i + Math.floor((prev_time - start_time) / framerate)) % length;
|
||||
function Draw() {
|
||||
if (!step) {
|
||||
max_y = canvas_height / 2;
|
||||
min_y = -canvas_height / 2;
|
||||
max_x = canvas_width / 1000;
|
||||
min_x = -max_x;
|
||||
step = framerate / 1000; //framerate / canvas_width;
|
||||
length = Math.floor(max_x / step);
|
||||
canvas_history.length = length;
|
||||
canvas_history.fill(0);
|
||||
ctx = canvas.getContext("2d");
|
||||
}
|
||||
ctx.lineWidth = 12;
|
||||
let offset = (prev_time - reset_time) / 1000;
|
||||
const start_index = get_index(0);
|
||||
if (last_index === (last_index = start_index) || !solver) return;
|
||||
ctx.clearRect(0, 0, canvas_width, canvas_height);
|
||||
ctx.beginPath();
|
||||
let x = min_x,
|
||||
y = 0,
|
||||
i = start_index + 1;
|
||||
ctx.moveTo(XC(x), YC(y));
|
||||
for (x += step; i <= length; i++)
|
||||
ctx.lineTo(XC(x), YC(canvas_history[i])), (x += step);
|
||||
for (i = 0; i < start_index; i++)
|
||||
ctx.lineTo(XC(x), YC(canvas_history[i])), (x += step);
|
||||
ctx.lineTo(
|
||||
XC(x),
|
||||
YC((canvas_history[start_index] = canvas_height / 2 - prev_value))
|
||||
);
|
||||
if (Math.abs(prev_value - solver(offset)) > 20) {
|
||||
solve_spring(target__, $velocity);
|
||||
offset = 0;
|
||||
}
|
||||
x = 0;
|
||||
while (x <= max_x)
|
||||
ctx.lineTo(
|
||||
XC((x += step)),
|
||||
canvas_height / 2 - YC(solver(x + step + offset))
|
||||
);
|
||||
ctx.stroke();
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
svg { width: 100%; height: 100%; margin: -8px }
|
||||
circle { fill: #ff3e00 }
|
||||
:global(body) {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
svg {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
circle {
|
||||
fill: tomato;
|
||||
}
|
||||
canvas {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:window
|
||||
on:mousemove={(e) => solve_spring(e.clientY, $velocity)}
|
||||
bind:innerWidth={canvas_width}
|
||||
bind:innerHeight={canvas_height} />
|
||||
<div style="position: absolute; right: 1em;">
|
||||
<label>
|
||||
<h3>stiffness ({coords.stiffness})</h3>
|
||||
<input bind:value={coords.stiffness} type="range" min="0" max="1" step="0.01">
|
||||
<h3>velocity</h3>
|
||||
<progress
|
||||
value={!Number.isNaN($velocity) && Number.isFinite($velocity) && $velocity + 10}
|
||||
min={0}
|
||||
max={20} />
|
||||
</label>
|
||||
<label>
|
||||
<h3>speed</h3>
|
||||
<progress
|
||||
value={!Number.isNaN($velocity) && Number.isFinite($velocity) && Math.abs($velocity)}
|
||||
min={0}
|
||||
max={20} />
|
||||
</label>
|
||||
<label>
|
||||
<h3>y {$s.toFixed(0)}</h3>
|
||||
<progress value={!Number.isNaN($s) && $s} min={0} max={1000} />
|
||||
</label>
|
||||
<label>
|
||||
<h3>target {target__}</h3>
|
||||
<progress value={!Number.isNaN(target__) && target__} min={0} max={1000} />
|
||||
</label>
|
||||
<label>
|
||||
<h3>stiffness ({stiffness})</h3>
|
||||
<input bind:value={stiffness} type="range" min="10" max="200" step="0.01" />
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<h3>damping ({coords.damping})</h3>
|
||||
<input bind:value={coords.damping} type="range" min="0" max="1" step="0.01">
|
||||
<h3>damping ({damping})</h3>
|
||||
<input bind:value={damping} type="range" min="0.1" max="20" step="0.01" />
|
||||
</label>
|
||||
<label>
|
||||
<h3>mass ({mass})</h3>
|
||||
<input bind:value={mass} type="range" min="0.1" max="20" step="0.01" />
|
||||
</label>
|
||||
<label>
|
||||
<h3>
|
||||
soft
|
||||
<input bind:checked={soft} type="checkbox" />
|
||||
</h3>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<svg
|
||||
on:mousemove="{e => coords.set({ x: e.clientX, y: e.clientY })}"
|
||||
on:mousedown="{() => size.set(30)}"
|
||||
on:mouseup="{() => size.set(10)}"
|
||||
>
|
||||
<circle cx={$coords.x} cy={$coords.y} r={$size}/>
|
||||
<svg>
|
||||
<circle
|
||||
cx={-15+canvas_width / 2}
|
||||
cy={$s}
|
||||
r={30} />
|
||||
</svg>
|
||||
<canvas bind:this={canvas} width={canvas_width} height={canvas_height} />
|
||||
|
@ -1,62 +1,98 @@
|
||||
<script>
|
||||
export let is_custom;
|
||||
export let x, y;
|
||||
let rect;
|
||||
let rect2;
|
||||
export let x1 = 200,
|
||||
y1 = 1400,
|
||||
x2 = 1200,
|
||||
y2 = 400;
|
||||
let selected1 = false,
|
||||
selected2 = false;
|
||||
export let bezier = [0, 0, 0, 0];
|
||||
const radius = 30;
|
||||
$: {
|
||||
if (rect2) {
|
||||
const { x, y, width, height } = rect2.getBoundingClientRect();
|
||||
bezier = [
|
||||
Math.max(0, Math.min(1, (x1 - 200) / 1000)),
|
||||
-(y1 - 1400) / 1000,
|
||||
Math.max(0, Math.min(1, 1 + (x2 - 1200) / 1000)),
|
||||
1 - (y2 - 400) / 1000,
|
||||
].map((v) => Math.round(v * 100) / 100);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.grid-line {
|
||||
stroke:#ccc;
|
||||
stroke: #ccc;
|
||||
opacity: 0.5;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.grid-line-xy {
|
||||
stroke: tomato;
|
||||
stroke-width: 2;
|
||||
circle {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:options namespace="svg" />
|
||||
|
||||
<rect
|
||||
x=0
|
||||
y=0
|
||||
width=1400
|
||||
height=1800
|
||||
stroke=#ccc
|
||||
bind:this={rect}
|
||||
x="0"
|
||||
y="0"
|
||||
width="1400"
|
||||
height="1800"
|
||||
stroke="#ccc"
|
||||
style="opacity: 0.5"
|
||||
fill=none
|
||||
stroke-width=2
|
||||
/>
|
||||
fill="none"
|
||||
stroke-width="2" />
|
||||
|
||||
{#each { length: 8 } as _, i}
|
||||
{#if i < 6}
|
||||
<path
|
||||
d="M{(i+1) * 200} 0 L{(i+1)*200} 1802"
|
||||
class="grid-line"
|
||||
/>
|
||||
<path d="M{(i + 1) * 200} 0 L{(i + 1) * 200} 1802" class="grid-line" />
|
||||
{/if}
|
||||
<path
|
||||
d="M0 {(i+1) * 200} L1400 {(i+1)*200} "
|
||||
class="grid-line"
|
||||
/>
|
||||
<path d="M0 {(i + 1) * 200} L1400 {(i + 1) * 200} " class="grid-line" />
|
||||
{/each}
|
||||
|
||||
<path
|
||||
style="transform: translateX({x+200}px)"
|
||||
d="M0 0 L0 1800"
|
||||
class="grid-line-xy"
|
||||
/>
|
||||
<path
|
||||
style="transform: translateY({y}px)"
|
||||
d="M0 400 L1400 400"
|
||||
class="grid-line-xy"
|
||||
/>
|
||||
<rect
|
||||
x=200
|
||||
y=400
|
||||
width=1000
|
||||
height=1000
|
||||
stroke=#999
|
||||
fill=none
|
||||
stroke-width=2
|
||||
/>
|
||||
bind:this={rect2}
|
||||
x="200"
|
||||
y="400"
|
||||
width="1000"
|
||||
height="1000"
|
||||
stroke="#999"
|
||||
fill="none"
|
||||
stroke-width="4" />
|
||||
<svelte:window
|
||||
on:mousemove={(e) => {
|
||||
const { x, y, width, height } = rect.getBoundingClientRect();
|
||||
const _x1 = Math.min(1200, Math.max(200, (e.clientX - x) * (1400 / width)));
|
||||
const _y1 = Math.min(1800, Math.max(0, (e.clientY - y) * (1800 / height)));
|
||||
if (selected1) (x1 = _x1), (y1 = _y1);
|
||||
else if (selected2) (x2 = _x1), (y2 = _y1);
|
||||
}}
|
||||
on:mouseup={() => {
|
||||
selected1 = selected2 = false;
|
||||
}} />
|
||||
<slot />
|
||||
{#if is_custom}
|
||||
<path d="M200 1400 L{x1} {y1} " stroke="#333333d9" stroke-width="10" />
|
||||
<path d="M1200 400 L{x2} {y2} " stroke="#333333d9" stroke-width="10" />
|
||||
<circle
|
||||
cx={x1}
|
||||
cy={y1}
|
||||
r={radius}
|
||||
fill="#333"
|
||||
stroke="transparent"
|
||||
stroke-width="100"
|
||||
on:mousedown={() => (selected1 = true)} />
|
||||
<circle
|
||||
cx={x2}
|
||||
cy={y2}
|
||||
r={radius}
|
||||
fill="#333"
|
||||
stroke="transparent"
|
||||
stroke-width="100"
|
||||
on:mousedown={() => (selected2 = true)} />
|
||||
{/if}
|
||||
|
Loading…
Reference in new issue