physics
This commit is contained in:
@@ -12,6 +12,10 @@ import usePointerTap from '../ui/usePointerTap';
|
|||||||
import {
|
import {
|
||||||
MIN_TIMESTEP,
|
MIN_TIMESTEP,
|
||||||
MAX_TIMESTEP,
|
MAX_TIMESTEP,
|
||||||
|
MAX_ANGULAR_VELOCITY,
|
||||||
|
MAX_DYNAMIC_ROTATION,
|
||||||
|
CARD_BASE_WEIGHT_GRAMS,
|
||||||
|
CARD_PAGE_WEIGHT_GRAMS,
|
||||||
applyDomTransform,
|
applyDomTransform,
|
||||||
type WorkspaceEngine,
|
type WorkspaceEngine,
|
||||||
} from './workspaceEngine';
|
} from './workspaceEngine';
|
||||||
@@ -21,6 +25,10 @@ type Identifier = string | number;
|
|||||||
interface DocumentLike {
|
interface DocumentLike {
|
||||||
id?: Identifier | null;
|
id?: Identifier | null;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
current_version?: {
|
||||||
|
metadata?: { page_count?: number | string | null } | null;
|
||||||
|
} | null;
|
||||||
|
metadata?: { page_count?: number | string | null } | null;
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +155,10 @@ interface DragStateInternal extends EngineDragState {
|
|||||||
stackDocIds: string[] | null;
|
stackDocIds: string[] | null;
|
||||||
stackSelectionApplied: boolean;
|
stackSelectionApplied: boolean;
|
||||||
stackReplace: boolean;
|
stackReplace: boolean;
|
||||||
|
massGrams: number;
|
||||||
|
pointerRadiusScale: number;
|
||||||
|
lastPointerCanvasX: number;
|
||||||
|
lastPointerCanvasY: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type PointerEventLike = PointerEvent | ReactPointerEvent<HTMLElement>;
|
type PointerEventLike = PointerEvent | ReactPointerEvent<HTMLElement>;
|
||||||
@@ -161,6 +173,20 @@ const DRAG_HYSTERESIS_PX = 4;
|
|||||||
const DRAG_HYSTERESIS_SQUARED = DRAG_HYSTERESIS_PX * DRAG_HYSTERESIS_PX;
|
const DRAG_HYSTERESIS_SQUARED = DRAG_HYSTERESIS_PX * DRAG_HYSTERESIS_PX;
|
||||||
const EDGE_COLLISION_THRESHOLD = 0.5;
|
const EDGE_COLLISION_THRESHOLD = 0.5;
|
||||||
|
|
||||||
|
const getDocumentPageCount = (doc?: DocumentLike | null): number | null => {
|
||||||
|
const raw = doc?.current_version?.metadata?.page_count ?? doc?.metadata?.page_count;
|
||||||
|
if (raw == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const value = Number(raw);
|
||||||
|
return Number.isFinite(value) ? value : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const computeDocumentMassGrams = (doc?: DocumentLike | null): number => {
|
||||||
|
const pages = Math.max(1, Math.round(getDocumentPageCount(doc) ?? 1));
|
||||||
|
return CARD_BASE_WEIGHT_GRAMS + pages * CARD_PAGE_WEIGHT_GRAMS;
|
||||||
|
};
|
||||||
|
|
||||||
const getEventTargetElement = (event?: PointerEventLike | null): Element | null => {
|
const getEventTargetElement = (event?: PointerEventLike | null): Element | null => {
|
||||||
if (!event) {
|
if (!event) {
|
||||||
return null;
|
return null;
|
||||||
@@ -319,6 +345,7 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
|||||||
if (!doc) {
|
if (!doc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const massGrams = computeDocumentMassGrams(doc);
|
||||||
|
|
||||||
const stackDocIdsOptionRaw = options?.stackDocIds;
|
const stackDocIdsOptionRaw = options?.stackDocIds;
|
||||||
const stackDocIdsOption = Array.isArray(stackDocIdsOptionRaw)
|
const stackDocIdsOption = Array.isArray(stackDocIdsOptionRaw)
|
||||||
@@ -512,6 +539,10 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
|||||||
stackDocIds: hasStackSource ? stackDocIdsOption : null,
|
stackDocIds: hasStackSource ? stackDocIdsOption : null,
|
||||||
stackSelectionApplied: stackSelectionAppliedInitial || !hasStackSource,
|
stackSelectionApplied: stackSelectionAppliedInitial || !hasStackSource,
|
||||||
stackReplace,
|
stackReplace,
|
||||||
|
massGrams,
|
||||||
|
pointerRadiusScale: 1,
|
||||||
|
lastPointerCanvasX: pointerCanvasX,
|
||||||
|
lastPointerCanvasY: pointerCanvasY,
|
||||||
} satisfies DragStateInternal;
|
} satisfies DragStateInternal;
|
||||||
|
|
||||||
const state = dragStateRef.current;
|
const state = dragStateRef.current;
|
||||||
@@ -587,6 +618,62 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
|||||||
}
|
}
|
||||||
preventAll(event);
|
preventAll(event);
|
||||||
|
|
||||||
|
const updatePointerAngularVelocity = (
|
||||||
|
pointerCanvasX: number,
|
||||||
|
pointerCanvasY: number,
|
||||||
|
centerX: number,
|
||||||
|
centerY: number,
|
||||||
|
dtSeconds: number,
|
||||||
|
) => {
|
||||||
|
if (!Number.isFinite(dtSeconds) || dtSeconds <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const leverX = pointerCanvasX - centerX;
|
||||||
|
const leverY = pointerCanvasY - centerY;
|
||||||
|
if (!Number.isFinite(leverX) || !Number.isFinite(leverY)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const prevCanvasX = Number.isFinite(state.lastPointerCanvasX)
|
||||||
|
? state.lastPointerCanvasX
|
||||||
|
: pointerCanvasX;
|
||||||
|
const prevCanvasY = Number.isFinite(state.lastPointerCanvasY)
|
||||||
|
? state.lastPointerCanvasY
|
||||||
|
: pointerCanvasY;
|
||||||
|
const velocityCanvasX = (pointerCanvasX - prevCanvasX) / dtSeconds;
|
||||||
|
const velocityCanvasY = (pointerCanvasY - prevCanvasY) / dtSeconds;
|
||||||
|
state.lastPointerCanvasX = pointerCanvasX;
|
||||||
|
state.lastPointerCanvasY = pointerCanvasY;
|
||||||
|
if (!Number.isFinite(velocityCanvasX) || !Number.isFinite(velocityCanvasY)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const torque = leverX * velocityCanvasY - leverY * velocityCanvasX;
|
||||||
|
const influenceRadius = Math.max(state.width, state.height) / 2 || 1;
|
||||||
|
const radiusScale = clamp(Math.hypot(leverX, leverY) / influenceRadius, 0.2, 2.5);
|
||||||
|
state.pointerRadiusScale = radiusScale;
|
||||||
|
const torqueResponse = 0.0025 * radiusScale;
|
||||||
|
const angularVelocityDeg = clamp(
|
||||||
|
torque * torqueResponse,
|
||||||
|
-MAX_ANGULAR_VELOCITY,
|
||||||
|
MAX_ANGULAR_VELOCITY,
|
||||||
|
);
|
||||||
|
const mass = Math.max(state.massGrams || CARD_BASE_WEIGHT_GRAMS, CARD_BASE_WEIGHT_GRAMS);
|
||||||
|
const massScale = Math.max(mass / CARD_BASE_WEIGHT_GRAMS, 1);
|
||||||
|
state.angularVelocity = angularVelocityDeg / massScale;
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyDynamicRotation = (dtSeconds: number, dampingFactor = 0.94) => {
|
||||||
|
if (!Number.isFinite(dtSeconds) || dtSeconds <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const radiusInfluence = clamp(state.pointerRadiusScale || 1, 0.3, 3);
|
||||||
|
const response = 1.1 * radiusInfluence;
|
||||||
|
let nextDynamic = state.dynamicRotation + state.angularVelocity * dtSeconds * response;
|
||||||
|
nextDynamic = clamp(nextDynamic, -MAX_DYNAMIC_ROTATION, MAX_DYNAMIC_ROTATION);
|
||||||
|
const adjustedDamping = Math.pow(dampingFactor, 1 / Math.max(radiusInfluence, 0.8));
|
||||||
|
state.dynamicRotation = nextDynamic * adjustedDamping;
|
||||||
|
state.rotation = state.restRotation + state.dynamicRotation;
|
||||||
|
};
|
||||||
|
|
||||||
if (state.isGroup) {
|
if (state.isGroup) {
|
||||||
const containerRect = containerRef.current?.getBoundingClientRect?.();
|
const containerRect = containerRef.current?.getBoundingClientRect?.();
|
||||||
if (containerRect) {
|
if (containerRect) {
|
||||||
@@ -701,14 +788,30 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
|||||||
applyDomTransform(node, payload);
|
applyDomTransform(node, payload);
|
||||||
});
|
});
|
||||||
|
|
||||||
state.lastClientX = event.clientX;
|
const currentTimestampGroup =
|
||||||
state.lastClientY = event.clientY;
|
|
||||||
state.lastTimestamp =
|
|
||||||
(Number.isFinite(event?.timeStamp))
|
(Number.isFinite(event?.timeStamp))
|
||||||
? event.timeStamp
|
? event.timeStamp
|
||||||
: performance?.now
|
: performance?.now
|
||||||
? performance.now()
|
? performance.now()
|
||||||
: Date.now();
|
: Date.now();
|
||||||
|
const previousTimestampGroup = state.lastTimestamp ?? currentTimestampGroup;
|
||||||
|
let dtGroup = (currentTimestampGroup - previousTimestampGroup) / 1000;
|
||||||
|
if (!Number.isFinite(dtGroup) || dtGroup <= 0) {
|
||||||
|
dtGroup = MIN_TIMESTEP;
|
||||||
|
}
|
||||||
|
dtGroup = clamp(dtGroup, MIN_TIMESTEP, MAX_TIMESTEP);
|
||||||
|
|
||||||
|
state.lastClientX = event.clientX;
|
||||||
|
state.lastClientY = event.clientY;
|
||||||
|
state.lastTimestamp = currentTimestampGroup;
|
||||||
|
|
||||||
|
updatePointerAngularVelocity(pointerCanvasX, pointerCanvasY, centerX, centerY, dtGroup);
|
||||||
|
applyDynamicRotation(dtGroup, 0.96);
|
||||||
|
state.groupItems.forEach((item) => {
|
||||||
|
if (item.docId === state.docKey) {
|
||||||
|
item.displayRotation = state.rotation ?? item.displayRotation ?? 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -736,6 +839,35 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
|||||||
const pointerCanvasY = event.clientY - containerTop;
|
const pointerCanvasY = event.clientY - containerTop;
|
||||||
|
|
||||||
const entry = layoutRef.current.get(state.docKey) || {};
|
const entry = layoutRef.current.get(state.docKey) || {};
|
||||||
|
const previousCenterX = Number.isFinite(state.currentCenterX)
|
||||||
|
? state.currentCenterX
|
||||||
|
: state.originCenterX;
|
||||||
|
const previousCenterY = Number.isFinite(state.currentCenterY)
|
||||||
|
? state.currentCenterY
|
||||||
|
: state.originCenterY;
|
||||||
|
|
||||||
|
const currentTimestamp =
|
||||||
|
(Number.isFinite(event?.timeStamp))
|
||||||
|
? event.timeStamp
|
||||||
|
: performance?.now
|
||||||
|
? performance.now()
|
||||||
|
: Date.now();
|
||||||
|
const previousTimestamp = state.lastTimestamp ?? currentTimestamp;
|
||||||
|
let dt = (currentTimestamp - previousTimestamp) / 1000;
|
||||||
|
if (!Number.isFinite(dt) || dt <= 0) {
|
||||||
|
dt = MIN_TIMESTEP;
|
||||||
|
}
|
||||||
|
dt = clamp(dt, MIN_TIMESTEP, MAX_TIMESTEP);
|
||||||
|
|
||||||
|
const torqueCenterX = Number.isFinite(previousCenterX) ? previousCenterX : state.originCenterX;
|
||||||
|
const torqueCenterY = Number.isFinite(previousCenterY) ? previousCenterY : state.originCenterY;
|
||||||
|
updatePointerAngularVelocity(pointerCanvasX, pointerCanvasY, torqueCenterX, torqueCenterY, dt);
|
||||||
|
applyDynamicRotation(dt);
|
||||||
|
|
||||||
|
state.lastClientX = event.clientX;
|
||||||
|
state.lastClientY = event.clientY;
|
||||||
|
state.lastTimestamp = currentTimestamp;
|
||||||
|
|
||||||
const rotationDeg = state.rotation ?? entry.rotation ?? 0;
|
const rotationDeg = state.rotation ?? entry.rotation ?? 0;
|
||||||
const rotationRad = (rotationDeg * Math.PI) / 180;
|
const rotationRad = (rotationDeg * Math.PI) / 180;
|
||||||
const cosRot = Math.cos(rotationRad);
|
const cosRot = Math.cos(rotationRad);
|
||||||
@@ -745,13 +877,6 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
|||||||
const rotatedOffsetY =
|
const rotatedOffsetY =
|
||||||
state.localPointerOffsetX * sinRot + state.localPointerOffsetY * cosRot;
|
state.localPointerOffsetX * sinRot + state.localPointerOffsetY * cosRot;
|
||||||
|
|
||||||
const previousCenterX = Number.isFinite(state.currentCenterX)
|
|
||||||
? state.currentCenterX
|
|
||||||
: state.originCenterX;
|
|
||||||
const previousCenterY = Number.isFinite(state.currentCenterY)
|
|
||||||
? state.currentCenterY
|
|
||||||
: state.originCenterY;
|
|
||||||
|
|
||||||
const absCos = Math.abs(cosRot);
|
const absCos = Math.abs(cosRot);
|
||||||
const absSin = Math.abs(sinRot);
|
const absSin = Math.abs(sinRot);
|
||||||
const rotatedHalfWidth = absCos * halfWidth + absSin * halfHeight;
|
const rotatedHalfWidth = absCos * halfWidth + absSin * halfHeight;
|
||||||
@@ -826,23 +951,6 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
|||||||
const pointerInsideCard =
|
const pointerInsideCard =
|
||||||
Math.abs(pointerLocalX) <= halfWidth && Math.abs(pointerLocalY) <= halfHeight;
|
Math.abs(pointerLocalX) <= halfWidth && Math.abs(pointerLocalY) <= halfHeight;
|
||||||
|
|
||||||
const currentTimestamp =
|
|
||||||
(Number.isFinite(event?.timeStamp))
|
|
||||||
? event.timeStamp
|
|
||||||
: performance?.now
|
|
||||||
? performance.now()
|
|
||||||
: Date.now();
|
|
||||||
const previousTimestamp = state.lastTimestamp ?? currentTimestamp;
|
|
||||||
let dt = (currentTimestamp - previousTimestamp) / 1000;
|
|
||||||
if (!Number.isFinite(dt) || dt <= 0) {
|
|
||||||
dt = MIN_TIMESTEP;
|
|
||||||
}
|
|
||||||
dt = clamp(dt, MIN_TIMESTEP, MAX_TIMESTEP);
|
|
||||||
|
|
||||||
state.lastClientX = event.clientX;
|
|
||||||
state.lastClientY = event.clientY;
|
|
||||||
state.lastTimestamp = currentTimestamp;
|
|
||||||
|
|
||||||
const rotationForOffsetDeg = state.rotation || 0;
|
const rotationForOffsetDeg = state.rotation || 0;
|
||||||
const rotationForOffsetRad = (rotationForOffsetDeg * Math.PI) / 180;
|
const rotationForOffsetRad = (rotationForOffsetDeg * Math.PI) / 180;
|
||||||
const cosInverse = Math.cos(-rotationForOffsetRad);
|
const cosInverse = Math.cos(-rotationForOffsetRad);
|
||||||
@@ -892,16 +1000,18 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
|||||||
|
|
||||||
if (state.moved) {
|
if (state.moved) {
|
||||||
commitActiveDragTransforms([state.docKey]);
|
commitActiveDragTransforms([state.docKey]);
|
||||||
|
const finalRotation = state.rotation ?? state.restRotation;
|
||||||
const inertiaState: EngineInertiaState = {
|
const inertiaState: EngineInertiaState = {
|
||||||
docId: state.docKey,
|
docId: state.docKey,
|
||||||
restRotation: state.restRotation,
|
restRotation: finalRotation,
|
||||||
dynamicRotation: state.dynamicRotation,
|
dynamicRotation: 0,
|
||||||
angularVelocity: state.angularVelocity,
|
angularVelocity: state.angularVelocity,
|
||||||
rotation: state.rotation,
|
rotation: finalRotation,
|
||||||
width: state.width,
|
width: state.width,
|
||||||
height: state.height,
|
height: state.height,
|
||||||
dragScale: state.dragScale || 1,
|
dragScale: state.dragScale || 1,
|
||||||
lastTimestamp: state.lastTimestamp,
|
lastTimestamp: state.lastTimestamp,
|
||||||
|
massGrams: state.massGrams,
|
||||||
};
|
};
|
||||||
const docId = state.docKey;
|
const docId = state.docKey;
|
||||||
finishDrag(event.pointerId);
|
finishDrag(event.pointerId);
|
||||||
@@ -953,16 +1063,18 @@ const useDocumentDrag = (options: UseDocumentDragOptions) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
commitActiveDragTransforms([state.docKey]);
|
commitActiveDragTransforms([state.docKey]);
|
||||||
|
const finalRotation = state.rotation ?? state.restRotation;
|
||||||
const inertiaState: EngineInertiaState = {
|
const inertiaState: EngineInertiaState = {
|
||||||
docId: state.docKey,
|
docId: state.docKey,
|
||||||
restRotation: state.restRotation,
|
restRotation: finalRotation,
|
||||||
dynamicRotation: state.dynamicRotation,
|
dynamicRotation: 0,
|
||||||
angularVelocity: state.angularVelocity,
|
angularVelocity: state.angularVelocity,
|
||||||
rotation: state.rotation,
|
rotation: finalRotation,
|
||||||
width: state.width,
|
width: state.width,
|
||||||
height: state.height,
|
height: state.height,
|
||||||
dragScale: state.dragScale || 1,
|
dragScale: state.dragScale || 1,
|
||||||
lastTimestamp: state.lastTimestamp,
|
lastTimestamp: state.lastTimestamp,
|
||||||
|
massGrams: state.massGrams,
|
||||||
};
|
};
|
||||||
const docId = state.docKey;
|
const docId = state.docKey;
|
||||||
finishDrag(event.pointerId);
|
finishDrag(event.pointerId);
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ interface InertiaSimulationState {
|
|||||||
dragScale?: number;
|
dragScale?: number;
|
||||||
lastTimestamp: number;
|
lastTimestamp: number;
|
||||||
frameId?: number;
|
frameId?: number;
|
||||||
|
massGrams?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WorkspaceSnapshot {
|
interface WorkspaceSnapshot {
|
||||||
@@ -133,15 +134,25 @@ export const DESK_DEFAULT_CANVAS_WIDTH = 1024;
|
|||||||
export const DESK_DEFAULT_CANVAS_HEIGHT = 680;
|
export const DESK_DEFAULT_CANVAS_HEIGHT = 680;
|
||||||
export const DESK_CARD_MIN = 240;
|
export const DESK_CARD_MIN = 240;
|
||||||
export const DESK_CARD_MAX = 340;
|
export const DESK_CARD_MAX = 340;
|
||||||
|
export const CARD_PAGE_WEIGHT_GRAMS = 5;
|
||||||
|
export const CARD_BASE_WEIGHT_GRAMS = 30;
|
||||||
|
|
||||||
const DEFAULT_Z_START = 10;
|
const DEFAULT_Z_START = 10;
|
||||||
export const MIN_TIMESTEP = 1 / 120;
|
export const MIN_TIMESTEP = 1 / 120;
|
||||||
export const MAX_TIMESTEP = 1 / 20;
|
export const MAX_TIMESTEP = 1 / 20;
|
||||||
export const MAX_DYNAMIC_ROTATION = 4;
|
export const MAX_DYNAMIC_ROTATION = 7;
|
||||||
export const MAX_ANGULAR_VELOCITY = 180;
|
export const MAX_ANGULAR_VELOCITY = 210;
|
||||||
export const ANGULAR_DAMPING = 11;
|
export const ANGULAR_DAMPING = 10;
|
||||||
export const TORQUE_TO_ACCELERATION = 0.006;
|
export const TORQUE_TO_ACCELERATION = 0.007;
|
||||||
export const SETTLE_ANGULAR_VELOCITY = 1.2;
|
export const SETTLE_ANGULAR_VELOCITY = 0.45;
|
||||||
|
|
||||||
|
const getMassScale = (massGrams?: number): number => {
|
||||||
|
if (!Number.isFinite(massGrams) || Number(massGrams) <= 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
const normalized = Math.max(Number(massGrams), CARD_BASE_WEIGHT_GRAMS) / CARD_BASE_WEIGHT_GRAMS;
|
||||||
|
return Math.max(normalized, 1);
|
||||||
|
};
|
||||||
|
|
||||||
export const applyDomTransform = (
|
export const applyDomTransform = (
|
||||||
node: HTMLElement | null,
|
node: HTMLElement | null,
|
||||||
@@ -927,9 +938,11 @@ export class WorkspaceEngine {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const massScale = getMassScale(simulationState.massGrams);
|
||||||
const torqueAcceleration = torque * TORQUE_TO_ACCELERATION;
|
const torqueAcceleration = torque * TORQUE_TO_ACCELERATION;
|
||||||
let angularVelocity = simulationState.angularVelocity + torqueAcceleration * dt;
|
let angularVelocity = simulationState.angularVelocity + torqueAcceleration * dt;
|
||||||
angularVelocity = clamp(angularVelocity, -MAX_ANGULAR_VELOCITY, MAX_ANGULAR_VELOCITY);
|
const maxAngularVelocity = MAX_ANGULAR_VELOCITY / massScale;
|
||||||
|
angularVelocity = clamp(angularVelocity, -maxAngularVelocity, maxAngularVelocity);
|
||||||
|
|
||||||
const dampingConstant = Number.isFinite(dampingOverride)
|
const dampingConstant = Number.isFinite(dampingOverride)
|
||||||
? Number(dampingOverride)
|
? Number(dampingOverride)
|
||||||
@@ -938,17 +951,30 @@ export class WorkspaceEngine {
|
|||||||
angularVelocity *= dampingFactor;
|
angularVelocity *= dampingFactor;
|
||||||
|
|
||||||
let dynamicRotation = simulationState.dynamicRotation + angularVelocity * dt;
|
let dynamicRotation = simulationState.dynamicRotation + angularVelocity * dt;
|
||||||
if (dynamicRotation > MAX_DYNAMIC_ROTATION) {
|
const dynamicLimit = MAX_DYNAMIC_ROTATION / Math.sqrt(massScale);
|
||||||
dynamicRotation = MAX_DYNAMIC_ROTATION;
|
if (dynamicRotation > dynamicLimit) {
|
||||||
|
dynamicRotation = dynamicLimit;
|
||||||
angularVelocity = Math.min(angularVelocity, 0);
|
angularVelocity = Math.min(angularVelocity, 0);
|
||||||
} else if (dynamicRotation < -MAX_DYNAMIC_ROTATION) {
|
} else if (dynamicRotation < -dynamicLimit) {
|
||||||
dynamicRotation = -MAX_DYNAMIC_ROTATION;
|
dynamicRotation = -dynamicLimit;
|
||||||
angularVelocity = Math.max(angularVelocity, 0);
|
angularVelocity = Math.max(angularVelocity, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isSettled = Math.abs(angularVelocity) < (SETTLE_ANGULAR_VELOCITY * 0.6)
|
||||||
|
|| Math.abs(dynamicRotation) < (MAX_DYNAMIC_ROTATION * 0.05);
|
||||||
|
|
||||||
|
if (isSettled) {
|
||||||
|
simulationState.angularVelocity = 0;
|
||||||
|
simulationState.dynamicRotation = 0;
|
||||||
|
simulationState.rotation = simulationState.restRotation;
|
||||||
|
} else {
|
||||||
simulationState.angularVelocity = angularVelocity;
|
simulationState.angularVelocity = angularVelocity;
|
||||||
simulationState.dynamicRotation = dynamicRotation;
|
simulationState.dynamicRotation = dynamicRotation;
|
||||||
simulationState.rotation = simulationState.restRotation + dynamicRotation;
|
simulationState.rotation = simulationState.restRotation + dynamicRotation;
|
||||||
|
if (Math.sign(simulationState.angularVelocity) !== Math.sign(angularVelocity)) {
|
||||||
|
simulationState.angularVelocity = angularVelocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const rotation = simulationState.rotation;
|
const rotation = simulationState.rotation;
|
||||||
const nextEntry = { ...entry, rotation };
|
const nextEntry = { ...entry, rotation };
|
||||||
@@ -966,7 +992,6 @@ export class WorkspaceEngine {
|
|||||||
nextEntry.z,
|
nextEntry.z,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isSettled = Math.abs(angularVelocity) < SETTLE_ANGULAR_VELOCITY;
|
|
||||||
return isSettled;
|
return isSettled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -989,6 +1014,9 @@ export class WorkspaceEngine {
|
|||||||
docId: key,
|
docId: key,
|
||||||
dragScale: baseState.dragScale || 1,
|
dragScale: baseState.dragScale || 1,
|
||||||
lastTimestamp: now,
|
lastTimestamp: now,
|
||||||
|
massGrams: Number.isFinite(baseState.massGrams)
|
||||||
|
? Math.max(Number(baseState.massGrams), CARD_BASE_WEIGHT_GRAMS)
|
||||||
|
: CARD_BASE_WEIGHT_GRAMS,
|
||||||
};
|
};
|
||||||
|
|
||||||
const step = (timestamp: number) => {
|
const step = (timestamp: number) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user