Spaces:
Runtime error
Runtime error
| 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 | |
| }; | |