not baaad
This commit is contained in:
parent
16074232aa
commit
fbdac9ebf1
44 changed files with 2152 additions and 454 deletions
|
|
@ -1,6 +1,93 @@
|
|||
"use strict";
|
||||
|
||||
(function (global) {
|
||||
|
||||
function rectLocalPoint(rect, x, y) {
|
||||
const dx = x - (rect.cx ?? 0);
|
||||
const dy = y - (rect.cy ?? 0);
|
||||
const c = Number.isFinite(rect.cos) ? rect.cos : Math.cos(rect.angle || 0);
|
||||
const ss = Number.isFinite(rect.sin) ? rect.sin : Math.sin(rect.angle || 0);
|
||||
return { x: dx * c + dy * ss, y: -dx * ss + dy * c };
|
||||
}
|
||||
|
||||
function rectWorldVector(rect, x, y) {
|
||||
const c = Number.isFinite(rect.cos) ? rect.cos : Math.cos(rect.angle || 0);
|
||||
const ss = Number.isFinite(rect.sin) ? rect.sin : Math.sin(rect.angle || 0);
|
||||
return { x: x * c - y * ss, y: x * ss + y * c };
|
||||
}
|
||||
|
||||
function circleBounceOffOrientedRect(obj, rect, radius, restitution, worldRef, opts = {}) {
|
||||
const hitRadius = Math.max(2, Number(radius || obj.r || obj.radius || 12) || 12);
|
||||
const minBounceSpeed = Math.max(0, Number(opts.minBounceSpeed ?? rect.minBounceSpeed ?? 0) || 0);
|
||||
const local = rectLocalPoint(rect, obj.x, obj.y);
|
||||
let clx = clamp(local.x, -(rect.halfW || 0), rect.halfW || 0);
|
||||
let cly = clamp(local.y, -(rect.halfH || 0), rect.halfH || 0);
|
||||
let dx = local.x - clx;
|
||||
let dy = local.y - cly;
|
||||
let d = Math.hypot(dx, dy);
|
||||
if (d >= hitRadius) {
|
||||
const px = obj.prevX;
|
||||
const py = obj.prevY;
|
||||
if (!Number.isFinite(px) || !Number.isFinite(py)) return false;
|
||||
const prev = rectLocalPoint(rect, px, py);
|
||||
const expanded = { left: -(rect.halfW || 0) - hitRadius, right: (rect.halfW || 0) + hitRadius, top: -(rect.halfH || 0) - hitRadius, bottom: (rect.halfH || 0) + hitRadius };
|
||||
const insideNow = local.x >= expanded.left && local.x <= expanded.right && local.y >= expanded.top && local.y <= expanded.bottom;
|
||||
const insidePrev = prev.x >= expanded.left && prev.x <= expanded.right && prev.y >= expanded.top && prev.y <= expanded.bottom;
|
||||
if (!insideNow && !insidePrev) return false;
|
||||
const sx = local.x - prev.x;
|
||||
const sy = local.y - prev.y;
|
||||
const candidates = [];
|
||||
if (prev.x < expanded.left && sx > 0) candidates.push({ nx: -1, ny: 0, t: (expanded.left - prev.x) / Math.max(sx, 0.001) });
|
||||
if (prev.x > expanded.right && sx < 0) candidates.push({ nx: 1, ny: 0, t: (prev.x - expanded.right) / Math.max(-sx, 0.001) });
|
||||
if (prev.y < expanded.top && sy > 0) candidates.push({ nx: 0, ny: -1, t: (expanded.top - prev.y) / Math.max(sy, 0.001) });
|
||||
if (prev.y > expanded.bottom && sy < 0) candidates.push({ nx: 0, ny: 1, t: (prev.y - expanded.bottom) / Math.max(-sy, 0.001) });
|
||||
if (!candidates.length) return false;
|
||||
candidates.sort((a, b) => a.t - b.t);
|
||||
dx = candidates[0].nx;
|
||||
dy = candidates[0].ny;
|
||||
d = 1;
|
||||
} else if (d < 0.001) {
|
||||
const left = Math.abs(local.x + (rect.halfW || 0));
|
||||
const right = Math.abs((rect.halfW || 0) - local.x);
|
||||
const top = Math.abs(local.y + (rect.halfH || 0));
|
||||
const bottom = Math.abs((rect.halfH || 0) - local.y);
|
||||
const m = Math.min(left, right, top, bottom);
|
||||
if (m === left) { dx = -1; dy = 0; clx = -(rect.halfW || 0); cly = local.y; }
|
||||
else if (m === right) { dx = 1; dy = 0; clx = rect.halfW || 0; cly = local.y; }
|
||||
else if (m === top) { dx = 0; dy = -1; clx = local.x; cly = -(rect.halfH || 0); }
|
||||
else { dx = 0; dy = 1; clx = local.x; cly = rect.halfH || 0; }
|
||||
d = 1;
|
||||
}
|
||||
const nxLocal = dx / d;
|
||||
const nyLocal = dy / d;
|
||||
const n = rectWorldVector(rect, nxLocal, nyLocal);
|
||||
const nx = n.x;
|
||||
const ny = n.y;
|
||||
const worldClosest = rectWorldVector(rect, clx, cly);
|
||||
obj.x = (rect.cx || 0) + worldClosest.x + nx * (hitRadius + 0.5);
|
||||
obj.y = (rect.cy || 0) + worldClosest.y + ny * (hitRadius + 0.5);
|
||||
const vx = obj.vx || 0;
|
||||
const vy = obj.vy || 0;
|
||||
const toward = vx * nx + vy * ny;
|
||||
if (toward < 0) {
|
||||
obj.vx = vx - (1 + restitution) * toward * nx;
|
||||
obj.vy = vy - (1 + restitution) * toward * ny;
|
||||
} else {
|
||||
obj.vx = vx + nx * 36;
|
||||
obj.vy = vy + ny * 36;
|
||||
}
|
||||
if (minBounceSpeed > 0) {
|
||||
const normalSpeed = (obj.vx || 0) * nx + (obj.vy || 0) * ny;
|
||||
if (normalSpeed < minBounceSpeed) {
|
||||
const add = minBounceSpeed - normalSpeed;
|
||||
obj.vx = (obj.vx || 0) + nx * add;
|
||||
obj.vy = (obj.vy || 0) + ny * add;
|
||||
}
|
||||
}
|
||||
if (Number.isFinite(obj.spinVelocity)) obj.spinVelocity = clamp((obj.spinVelocity || 0) + (nx >= 0 ? -1 : 1) * 7.0, -46, 46);
|
||||
return true;
|
||||
}
|
||||
|
||||
function bounceInsideWorld(obj, worldRef, opts = {}) {
|
||||
if (!obj || !worldRef) return false;
|
||||
const p = Math.max(opts.padding ?? 28, CONFIG.worldPadding || 30);
|
||||
|
|
@ -16,6 +103,120 @@
|
|||
return bounced;
|
||||
}
|
||||
|
||||
|
||||
function bounceCircleOffRect(obj, rect, radius, restitution = 1.08, worldRef = null, opts = {}) {
|
||||
if (!obj || !rect) return false;
|
||||
if (rect.oriented) return circleBounceOffOrientedRect(obj, rect, radius, restitution, worldRef, opts);
|
||||
const hitRadius = Math.max(2, Number(radius || obj.r || obj.radius || 12) || 12);
|
||||
const minBounceSpeed = Math.max(0, Number(opts.minBounceSpeed ?? rect.minBounceSpeed ?? 0) || 0);
|
||||
const ensureMinNormalSpeed = (nx, ny) => {
|
||||
if (!(minBounceSpeed > 0)) return;
|
||||
const normalSpeed = (obj.vx || 0) * nx + (obj.vy || 0) * ny;
|
||||
if (normalSpeed >= minBounceSpeed) return;
|
||||
const add = minBounceSpeed - normalSpeed;
|
||||
obj.vx = (obj.vx || 0) + nx * add;
|
||||
obj.vy = (obj.vy || 0) + ny * add;
|
||||
};
|
||||
const cx = clamp(obj.x, rect.left, rect.right);
|
||||
const cy = clamp(obj.y, rect.top, rect.bottom);
|
||||
let dx = obj.x - cx;
|
||||
let dy = obj.y - cy;
|
||||
let d = Math.hypot(dx, dy);
|
||||
if (d >= hitRadius) {
|
||||
const px = obj.prevX;
|
||||
const py = obj.prevY;
|
||||
const expanded = { left: rect.left - hitRadius, right: rect.right + hitRadius, top: rect.top - hitRadius, bottom: rect.bottom + hitRadius };
|
||||
if (!Number.isFinite(px) || !Number.isFinite(py) || !worldRef?.segmentIntersectsRect?.(px, py, obj.x, obj.y, expanded)) return false;
|
||||
const vx = obj.vx || 0;
|
||||
const vy = obj.vy || 0;
|
||||
const candidates = [];
|
||||
const sx = obj.x - px;
|
||||
const sy = obj.y - py;
|
||||
if (px < expanded.left && sx > 0) candidates.push({ nx: -1, ny: 0, t: (expanded.left - px) / Math.max(sx, 0.001) });
|
||||
if (px > expanded.right && sx < 0) candidates.push({ nx: 1, ny: 0, t: (px - expanded.right) / Math.max(-sx, 0.001) });
|
||||
if (py < expanded.top && sy > 0) candidates.push({ nx: 0, ny: -1, t: (expanded.top - py) / Math.max(sy, 0.001) });
|
||||
if (py > expanded.bottom && sy < 0) candidates.push({ nx: 0, ny: 1, t: (py - expanded.bottom) / Math.max(-sy, 0.001) });
|
||||
let nx = 0;
|
||||
let ny = 0;
|
||||
if (candidates.length) {
|
||||
candidates.sort((a, b) => a.t - b.t);
|
||||
nx = candidates[0].nx;
|
||||
ny = candidates[0].ny;
|
||||
} else if (Math.abs(vx) >= Math.abs(vy)) nx = vx >= 0 ? -1 : 1;
|
||||
else ny = vy >= 0 ? -1 : 1;
|
||||
const toward = vx * nx + vy * ny;
|
||||
if (nx < 0) obj.x = expanded.left - 0.5;
|
||||
else if (nx > 0) obj.x = expanded.right + 0.5;
|
||||
if (ny < 0) obj.y = expanded.top - 0.5;
|
||||
else if (ny > 0) obj.y = expanded.bottom + 0.5;
|
||||
if (nx) obj.y = clamp(obj.y, expanded.top, expanded.bottom);
|
||||
if (ny) obj.x = clamp(obj.x, expanded.left, expanded.right);
|
||||
if (toward < 0) {
|
||||
obj.vx = vx - (1 + restitution) * toward * nx;
|
||||
obj.vy = vy - (1 + restitution) * toward * ny;
|
||||
} else {
|
||||
obj.vx = vx + nx * 36;
|
||||
obj.vy = vy + ny * 36;
|
||||
}
|
||||
ensureMinNormalSpeed(nx, ny);
|
||||
if (Number.isFinite(obj.spinVelocity)) obj.spinVelocity = clamp((obj.spinVelocity || 0) + (nx >= 0 ? -1 : 1) * 7.0, -46, 46);
|
||||
return true;
|
||||
}
|
||||
if (d < 0.001) {
|
||||
const left = Math.abs(obj.x - rect.left);
|
||||
const right = Math.abs(rect.right - obj.x);
|
||||
const top = Math.abs(obj.y - rect.top);
|
||||
const bottom = Math.abs(rect.bottom - obj.y);
|
||||
const m = Math.min(left, right, top, bottom);
|
||||
if (m === left) { dx = -1; dy = 0; }
|
||||
else if (m === right) { dx = 1; dy = 0; }
|
||||
else if (m === top) { dx = 0; dy = -1; }
|
||||
else { dx = 0; dy = 1; }
|
||||
d = 1;
|
||||
}
|
||||
const nx = dx / d;
|
||||
const ny = dy / d;
|
||||
const vx = obj.vx || 0;
|
||||
const vy = obj.vy || 0;
|
||||
const toward = vx * nx + vy * ny;
|
||||
obj.x = cx + nx * (hitRadius + 0.5);
|
||||
obj.y = cy + ny * (hitRadius + 0.5);
|
||||
if (toward < 0) {
|
||||
obj.vx = vx - (1 + restitution) * toward * nx;
|
||||
obj.vy = vy - (1 + restitution) * toward * ny;
|
||||
} else {
|
||||
obj.vx = vx + nx * 36;
|
||||
obj.vy = vy + ny * 36;
|
||||
}
|
||||
ensureMinNormalSpeed(nx, ny);
|
||||
if (Number.isFinite(obj.spinVelocity)) obj.spinVelocity = clamp((obj.spinVelocity || 0) + (nx >= 0 ? -1 : 1) * 7.0, -46, 46);
|
||||
return true;
|
||||
}
|
||||
|
||||
function resolveKinematicBounceFenceCollision(item, worldRef, dt = 0.016, opts = {}) {
|
||||
if (!item || !worldRef?.nearbySolidObstacleRects) return false;
|
||||
const speed = Math.hypot(item.vx || 0, item.vy || 0);
|
||||
const radius = Math.max(4, (item.r || item.radius || 12) + (opts.obstaclePadding ?? 2));
|
||||
const searchRadius = Math.max(96, radius + speed * Math.max(dt || 0.016, 0.016) + 90);
|
||||
let bounced = false;
|
||||
const rects = worldRef.nearbySolidObstacleRects(item.x, item.y, searchRadius, { include: it => it?.type === "bounce_fence" || it?.type === "bounce_fence_v", exclude: item, maxChecks: 16 }) || [];
|
||||
for (const rect of rects) {
|
||||
if (!rect?.bounce) continue;
|
||||
if (bounceCircleOffRect(item, rect, radius, Number(rect.restitution || 1.08), worldRef, opts)) {
|
||||
bounced = true;
|
||||
const maxSpeed = opts.bounceFenceMaxSpeed ?? 760;
|
||||
item.vx = clamp(item.vx || 0, -maxSpeed, maxSpeed);
|
||||
item.vy = clamp(item.vy || 0, -maxSpeed, maxSpeed);
|
||||
const now = worldRef.time || 0;
|
||||
if ((item.lastBounceFenceEffectAt || -999) + 0.08 <= now) {
|
||||
item.lastBounceFenceEffectAt = now;
|
||||
worldRef.effects?.push(new Effect("ring", item.x, item.y, { size: Math.max(12, radius * 1.15), life: 0.16, color: "rgba(82,153,230,0.45)" }));
|
||||
}
|
||||
}
|
||||
}
|
||||
return bounced;
|
||||
}
|
||||
|
||||
function applyKinematicItemMotion(item, worldRef, dt, opts = {}) {
|
||||
if (!item || !worldRef) return 0;
|
||||
const stopSpeed = opts.stopSpeed ?? 0.05;
|
||||
|
|
@ -34,7 +235,11 @@
|
|||
item.x += (item.vx || 0) * dt;
|
||||
item.y += (item.vy || 0) * dt;
|
||||
bounceInsideWorld(item, worldRef, opts);
|
||||
const friction = Math.pow(opts.frictionBase ?? 0.68, dt * (opts.frictionRate ?? 1));
|
||||
resolveKinematicBounceFenceCollision(item, worldRef, dt, opts);
|
||||
const ice = (worldRef?.groundType || "soil") === "ice";
|
||||
const baseFriction = ice ? Math.max(Number(opts.frictionBase ?? 0.68), 0.93) : (opts.frictionBase ?? 0.68);
|
||||
const frictionRate = ice ? Math.min(Number(opts.frictionRate ?? 1), 0.55) : (opts.frictionRate ?? 1);
|
||||
const friction = Math.pow(baseFriction, dt * frictionRate);
|
||||
item.vx *= friction;
|
||||
item.vy *= friction;
|
||||
if (opts.spin !== false) {
|
||||
|
|
@ -50,5 +255,5 @@
|
|||
return speed;
|
||||
}
|
||||
|
||||
global.TarinaiPhysics = { bounceInsideWorld, applyKinematicItemMotion };
|
||||
global.TarinaiPhysics = { bounceInsideWorld, bounceCircleOffRect, resolveKinematicBounceFenceCollision, applyKinematicItemMotion };
|
||||
})(typeof window !== "undefined" ? window : globalThis);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue