repond-movers
Version:
movers for repond
215 lines (178 loc) • 6.68 kB
text/typescript
import { addToLimitedArray } from "chootils/dist/arrays";
import { getAverageSpeed } from "chootils/dist/speedAngleDistance";
import { getRefs, getState, onNextTick, setState } from "repond";
import {
defaultOptions,
defaultPhysics,
// maximumFrameTime,
physicsTimestep,
physicsTimestepInSeconds,
recentSpeedsAmount,
} from "./consts";
import { AnyMoverStateNames, ItemType, MoveMode, PhysicsConfig, PhysicsOptions, RunMoverOptions } from "./types";
import { makeMoverStateMaker, makeStateNames, normalizeDefinedPhysicsConfig } from "./utils";
/*
New options:
allow interpolating
refNames.averageSpeed
*/
export type PositionAndVelocity = {
position: number;
velocity: number;
};
const DEFAULT_SPRING_STOP_SPEED = defaultPhysics("1d").stopSpeed;
type MainValueType = number;
// manually retyped because d.ts had some trouble with nested functions?
export const moverState = makeMoverStateMaker(() => 0) as <
T_Name extends string,
T_PhysicsNames extends string,
T_InitialState extends {
value?: MainValueType; // T_ValueType
valueGoal?: MainValueType; // T_ValueType
isMoving?: boolean;
moveConfigName?: T_PhysicsNames;
moveMode?: MoveMode;
moveConfigs?: Record<T_PhysicsNames, PhysicsOptions>;
}
>(
newName: T_Name,
initialState?: T_InitialState
) => Record<T_Name, MainValueType> &
Record<`${T_Name}Goal`, MainValueType> &
Record<`${T_Name}IsMoving`, boolean> &
Record<`${T_Name}MoveMode`, MoveMode> &
(T_InitialState["moveConfigName"] extends undefined ? {} : Record<`${T_Name}MoveConfigName`, T_PhysicsNames>) &
(T_InitialState["moveConfigs"] extends undefined
? {}
: Record<`${T_Name}MoveConfigs`, Record<T_PhysicsNames, PhysicsOptions>>);
export function moverRefs<T_Name extends string>(newName: T_Name, config?: PhysicsConfig) {
const newRefs = {
velocity: 0,
recentSpeeds: [] as number[],
stateNames: makeStateNames(newName),
physicsConfigs: normalizeDefinedPhysicsConfig(config, "1d"),
};
return {
[`${newName}MoverRefs`]: newRefs,
} as Record<`${T_Name}MoverRefs`, typeof newRefs>;
}
const rerunOptions: RunMoverOptions<any> = {
frameDuration: 16.6667,
id: "",
type: "",
onSlow: undefined,
mover: "",
autoRerun: true,
};
export function runMover1d<T_ItemType extends ItemType>({
frameDuration = 16.6667,
type: itemType,
id: itemId,
mover: moverName,
autoRerun,
}: // onSlow,
RunMoverOptions<T_ItemType>) {
// repeated for all movers Start
const itemRefs = getRefs(itemType, itemId) as any;
const itemState = getState(itemType, itemId) as any;
const moverRefs = itemRefs[`${moverName}MoverRefs`];
const keys: AnyMoverStateNames = moverRefs.stateNames;
const nowStepState = {
position: itemState[keys.value],
velocity: moverRefs.velocity,
};
const prevStepState = {
position: nowStepState.position,
velocity: nowStepState.velocity,
};
const moveMode: MoveMode = itemState[keys.moveMode] ?? defaultOptions.moveMode;
const physicsConfigs = itemState[keys.physicsConfigs] ?? moverRefs.physicsConfigs;
const physicsOptions =
physicsConfigs[itemState?.[keys?.physicsConfigName]] ?? physicsConfigs[defaultOptions.physicsConfigName];
const targetPosition = itemState[keys.valueGoal];
let timeRemainingForPhysics = frameDuration;
// repeated for all movers End
prevStepState.position = nowStepState.position;
prevStepState.velocity = nowStepState.velocity;
const springStopSpeed = physicsOptions.stopSpeed ?? DEFAULT_SPRING_STOP_SPEED;
while (timeRemainingForPhysics >= physicsTimestep) {
// prevStepState = currentStepState;
// currentStepState = runPhysicsStep(
run1dPhysicsStep(nowStepState, targetPosition, moveMode, physicsOptions);
timeRemainingForPhysics -= physicsTimestep;
}
// TODO maybe set the new position based on the current position like mover 3d
let interpolatedPosition = nowStepState.position;
// caused it to stop working on iPad ?
// const remainingTimestepPercent = timeRemainingForPhysics / physicsTimestep;
// interpolatedPosition = interpolateValues(
// currentStepState.position,
// prevStepState.position,
// remainingTimestepPercent
// );
const currentSpeed = Math.abs(nowStepState.velocity);
addToLimitedArray(moverRefs.recentSpeeds, currentSpeed, recentSpeedsAmount);
moverRefs.velocity = nowStepState.velocity;
const hasEnoughSpeeds = moverRefs.recentSpeeds.length >= recentSpeedsAmount - 1;
const averageSpeed = hasEnoughSpeeds ? getAverageSpeed(moverRefs.recentSpeeds) : Infinity;
const isNearTarget = Math.abs(itemState[keys.value] - targetPosition) < 0.01;
let isStillMoving = Math.abs(averageSpeed) > 0.003;
let shouldStopMoving = !isStillMoving;
if (moveMode === "spring") {
let isStillMoving = Math.abs(averageSpeed) > springStopSpeed;
shouldStopMoving = !isStillMoving && isNearTarget;
}
if (shouldStopMoving) {
// console.log("should stop moving");
setState(`${itemType}.${keys.isMoving}`, false, itemId);
}
setState(`${itemType}.${keys.value}`, interpolatedPosition, itemId);
if (autoRerun) {
onNextTick((nextFrameDuration) => {
const newItemState = getState(itemType, itemId);
if (newItemState?.[keys.isMoving]) {
rerunOptions.frameDuration = nextFrameDuration;
rerunOptions.id = itemId;
rerunOptions.type = itemType;
rerunOptions.mover = moverName;
rerunOptions.autoRerun = autoRerun;
runMover1d(rerunOptions);
}
});
}
}
function run1dPhysicsStep(
stepState: PositionAndVelocity,
targetPosition: number,
moveMode: MoveMode = "spring",
physicsOptions: any
) {
const { stiffness, damping, mass, friction } = physicsOptions;
let newVelocity: number = stepState.velocity;
switch (moveMode) {
case "spring":
{
const positionDifference = stepState.position - targetPosition;
const springForce = positionDifference * -stiffness;
const dampingForce = stepState.velocity * damping;
const force = springForce - dampingForce;
const acceleration = force / mass;
const accelerationWithTime = acceleration * physicsTimestep;
newVelocity = stepState.velocity + accelerationWithTime;
}
break;
case "slide":
newVelocity = stepState.velocity * Math.pow(1 - friction, physicsTimestepInSeconds * 10);
break;
case "drag":
break;
case "push":
break;
default:
}
const amountMoved = newVelocity * physicsTimestepInSeconds;
let newAmount = stepState.position + amountMoved;
stepState.position = newAmount;
stepState.velocity = newVelocity;
}
// }