Spaces:
Running
Running
File size: 7,292 Bytes
30c32c8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
const { translateForCamera } = require('../util/pos-math');
/**
* Prepare the targets of a runtime for interpolation.
* @param {Runtime} runtime The Runtime with targets to prepare for interpolation.
*/
const setupInitialState = runtime => {
const renderer = runtime.renderer;
for (const target of runtime.targets) {
const directionAndScale = target._getRenderedDirectionAndScale();
let camData = { ...runtime.getCamera(target.cameraBound) };
camData.dir = camData.dir / 180;
camData.scale = 1 + ((camData.scale - 1) / 100);
// If sprite may have been interpolated in the previous frame, reset its renderer state.
if (renderer && target.interpolationData) {
const drawableID = target.drawableID;
renderer.updateDrawablePosition(drawableID, [target.x - camData.pos[0], target.y - camData.pos[1]]);
renderer.updateDrawableDirectionScale(
drawableID,
directionAndScale.direction - camData.dir,
[directionAndScale.scale[0] * camData.scale, directionAndScale.scale[1] * camData.scale]
);
renderer.updateDrawableEffect(drawableID, 'ghost', target.effects.ghost);
}
if (target.visible && !target.isStage) {
target.interpolationData = {
x: target.x - camData.pos[0],
y: target.y - camData.pos[1],
direction: directionAndScale.direction - camData.dir,
scale: [directionAndScale.scale[0] * camData.scale, directionAndScale.scale[1] * camData.scale],
costume: target.currentCostume,
ghost: target.effects.ghost
};
} else {
target.interpolationData = null;
}
}
};
/**
* Interpolate the position of targets.
* @param {Runtime} runtime The Runtime with targets to interpolate.
* @param {number} time Relative time in the frame in [0-1].
*/
const interpolate = (runtime, time) => {
const renderer = runtime.renderer;
if (!renderer) {
return;
}
for (const target of runtime.targets) {
// interpolationData is the initial state at the start of the frame (time 0)
// the state on the target itself is the state at the end of the frame (time 1)
const interpolationData = target.interpolationData;
if (!interpolationData) {
continue;
}
// Don't waste time interpolating sprites that are hidden.
if (
!target.visible ||
/* special thanks to CST and Cubester for this new check */
(target.effects.ghost === 100 && interpolationData.ghost === 100)
) {
continue;
}
runtime.emit(runtime.constructor.BEFORE_INTERPOLATE, target);
let camData = { ...runtime.getCamera(target.cameraBound) };
camData.scale = 1 + ((camData.scale - 1) / 100);
const drawableID = target.drawableID;
// Position interpolation.
const xDistance = target.x - interpolationData.x - camData.pos[0];
const yDistance = target.y - interpolationData.y - camData.pos[1];
const absoluteXDistance = Math.abs(xDistance);
const absoluteYDistance = Math.abs(yDistance);
if (absoluteXDistance > 0.1 || absoluteYDistance > 0.1) {
const drawable = renderer._allDrawables[drawableID];
// Large movements are likely intended to be instantaneous.
// getAABB is less accurate than getBounds, but it's much faster
const bounds = drawable.getAABB();
const tolerance = Math.min(240, Math.max(50, 1.5 * (bounds.width + bounds.height)));
const distance = Math.sqrt((absoluteXDistance ** 2) + (absoluteYDistance ** 2));
if (distance < tolerance) {
const newX = interpolationData.x + (xDistance * time);
const newY = interpolationData.y + (yDistance * time);
renderer.updateDrawablePosition(drawableID, [newX, newY]);
}
}
// Effect interpolation.
const ghostChange = target.effects.ghost - interpolationData.ghost;
const absoluteGhostChange = Math.abs(ghostChange);
// Large changes are likely intended to be instantaneous.
if (absoluteGhostChange > 0 && absoluteGhostChange < 25) {
const newGhost = target.effects.ghost + (ghostChange * time);
renderer.updateDrawableEffect(drawableID, 'ghost', newGhost);
}
// Interpolate scale and direction.
const costumeUnchanged = interpolationData.costume === target.currentCostume;
if (costumeUnchanged) {
let {direction, scale} = target._getRenderedDirectionAndScale();
direction = direction - (camData.dir / 180);
let updateDrawableDirectionScale = false;
// Interpolate direction.
if (direction !== interpolationData.direction) {
// Perfect 90 degree angles should not be interpolated.
// eg. the foreground tile clones in https://scratch.mit.edu/projects/60917032/
if (direction % 90 !== 0 || interpolationData.direction % 90 !== 0) {
const currentRadians = direction * Math.PI / 180;
const startingRadians = interpolationData.direction * Math.PI / 180;
direction = Math.atan2(
(Math.sin(currentRadians) * time) + (Math.sin(startingRadians) * (1 - time)),
(Math.cos(currentRadians) * time) + (Math.cos(startingRadians) * (1 - time))
) * 180 / Math.PI;
updateDrawableDirectionScale = true;
}
}
// Interpolate scale.
const startingScale = interpolationData.scale;
scale[0] = scale[0] * camData.scale;
scale[1] = scale[1] * camData.scale;
if (scale[0] !== startingScale[0] || scale[1] !== startingScale[1]) {
// Do not interpolate size when the sign of either scale differs.
if (
Math.sign(scale[0]) === Math.sign(startingScale[0]) &&
Math.sign(scale[1]) === Math.sign(startingScale[1])
) {
const changeX = scale[0] - startingScale[0];
const changeY = scale[1] - startingScale[1];
const absoluteChangeX = Math.abs(changeX);
const absoluteChangeY = Math.abs(changeY);
// Large changes are likely intended to be instantaneous.
if (absoluteChangeX < 100 && absoluteChangeY < 100) {
scale[0] = (startingScale[0] + (changeX * time));
scale[1] = (startingScale[1] + (changeY * time));
updateDrawableDirectionScale = true;
}
}
}
if (updateDrawableDirectionScale) {
renderer.updateDrawableDirectionScale(drawableID, direction, scale);
}
}
runtime.emit(runtime.constructor.AFTER_INTERPOLATE, target);
}
};
module.exports = {
setupInitialState,
interpolate
};
|