2017-04-17 15:10:04 -04:00
|
|
|
const Cast = require('../util/cast');
|
|
|
|
const MathUtil = require('../util/math-util');
|
|
|
|
const Timer = require('../util/timer');
|
2016-06-29 23:08:10 -04:00
|
|
|
|
2017-04-17 15:10:04 -04:00
|
|
|
const Scratch3MotionBlocks = function (runtime) {
|
2016-06-29 20:56:55 -04:00
|
|
|
/**
|
|
|
|
* The runtime instantiating this block package.
|
|
|
|
* @type {Runtime}
|
|
|
|
*/
|
|
|
|
this.runtime = runtime;
|
2016-10-23 17:55:31 -04:00
|
|
|
};
|
2016-06-29 20:56:55 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the block primitives implemented by this package.
|
2017-02-01 15:59:50 -05:00
|
|
|
* @return {object.<string, Function>} Mapping of opcode to Function.
|
2016-06-29 20:56:55 -04:00
|
|
|
*/
|
2016-10-23 12:41:45 -04:00
|
|
|
Scratch3MotionBlocks.prototype.getPrimitives = function () {
|
2016-06-29 20:56:55 -04:00
|
|
|
return {
|
2016-10-24 11:02:19 -04:00
|
|
|
motion_movesteps: this.moveSteps,
|
|
|
|
motion_gotoxy: this.goToXY,
|
|
|
|
motion_goto: this.goTo,
|
|
|
|
motion_turnright: this.turnRight,
|
|
|
|
motion_turnleft: this.turnLeft,
|
|
|
|
motion_pointindirection: this.pointInDirection,
|
|
|
|
motion_pointtowards: this.pointTowards,
|
|
|
|
motion_glidesecstoxy: this.glide,
|
|
|
|
motion_ifonedgebounce: this.ifOnEdgeBounce,
|
|
|
|
motion_setrotationstyle: this.setRotationStyle,
|
|
|
|
motion_changexby: this.changeX,
|
|
|
|
motion_setx: this.setX,
|
|
|
|
motion_changeyby: this.changeY,
|
|
|
|
motion_sety: this.setY,
|
|
|
|
motion_xposition: this.getX,
|
|
|
|
motion_yposition: this.getY,
|
|
|
|
motion_direction: this.getDirection
|
2016-06-29 20:56:55 -04:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2016-06-29 23:08:10 -04:00
|
|
|
Scratch3MotionBlocks.prototype.moveSteps = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const steps = Cast.toNumber(args.STEPS);
|
|
|
|
const radians = MathUtil.degToRad(90 - util.target.direction);
|
|
|
|
const dx = steps * Math.cos(radians);
|
|
|
|
const dy = steps * Math.sin(radians);
|
2016-06-29 23:08:10 -04:00
|
|
|
util.target.setXY(util.target.x + dx, util.target.y + dy);
|
|
|
|
};
|
|
|
|
|
2016-06-29 20:56:55 -04:00
|
|
|
Scratch3MotionBlocks.prototype.goToXY = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const x = Cast.toNumber(args.X);
|
|
|
|
const y = Cast.toNumber(args.Y);
|
2016-09-12 13:14:16 -04:00
|
|
|
util.target.setXY(x, y);
|
2016-06-29 20:56:55 -04:00
|
|
|
};
|
|
|
|
|
2016-10-04 18:19:52 -04:00
|
|
|
Scratch3MotionBlocks.prototype.goTo = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
let targetX = 0;
|
|
|
|
let targetY = 0;
|
2016-10-04 18:19:52 -04:00
|
|
|
if (args.TO === '_mouse_') {
|
|
|
|
targetX = util.ioQuery('mouse', 'getX');
|
|
|
|
targetY = util.ioQuery('mouse', 'getY');
|
|
|
|
} else if (args.TO === '_random_') {
|
2017-04-17 15:10:04 -04:00
|
|
|
const stageWidth = this.runtime.constructor.STAGE_WIDTH;
|
|
|
|
const stageHeight = this.runtime.constructor.STAGE_HEIGHT;
|
2016-10-04 18:19:52 -04:00
|
|
|
targetX = Math.round(stageWidth * (Math.random() - 0.5));
|
|
|
|
targetY = Math.round(stageHeight * (Math.random() - 0.5));
|
|
|
|
} else {
|
2017-04-17 15:10:04 -04:00
|
|
|
const goToTarget = this.runtime.getSpriteTargetByName(args.TO);
|
2016-10-04 18:19:52 -04:00
|
|
|
if (!goToTarget) return;
|
|
|
|
targetX = goToTarget.x;
|
|
|
|
targetY = goToTarget.y;
|
|
|
|
}
|
|
|
|
util.target.setXY(targetX, targetY);
|
|
|
|
};
|
|
|
|
|
2016-06-29 20:56:55 -04:00
|
|
|
Scratch3MotionBlocks.prototype.turnRight = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const degrees = Cast.toNumber(args.DEGREES);
|
2016-09-12 13:14:16 -04:00
|
|
|
util.target.setDirection(util.target.direction + degrees);
|
2016-06-29 23:08:10 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
Scratch3MotionBlocks.prototype.turnLeft = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const degrees = Cast.toNumber(args.DEGREES);
|
2016-09-12 13:14:16 -04:00
|
|
|
util.target.setDirection(util.target.direction - degrees);
|
2016-06-29 23:08:10 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
Scratch3MotionBlocks.prototype.pointInDirection = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const direction = Cast.toNumber(args.DIRECTION);
|
2016-09-12 13:14:16 -04:00
|
|
|
util.target.setDirection(direction);
|
2016-06-29 20:56:55 -04:00
|
|
|
};
|
|
|
|
|
2016-10-04 15:20:53 -04:00
|
|
|
Scratch3MotionBlocks.prototype.pointTowards = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
let targetX = 0;
|
|
|
|
let targetY = 0;
|
2016-10-04 15:20:53 -04:00
|
|
|
if (args.TOWARDS === '_mouse_') {
|
|
|
|
targetX = util.ioQuery('mouse', 'getX');
|
|
|
|
targetY = util.ioQuery('mouse', 'getY');
|
|
|
|
} else {
|
2017-04-17 15:10:04 -04:00
|
|
|
const pointTarget = this.runtime.getSpriteTargetByName(args.TOWARDS);
|
2016-10-04 15:20:53 -04:00
|
|
|
if (!pointTarget) return;
|
|
|
|
targetX = pointTarget.x;
|
|
|
|
targetY = pointTarget.y;
|
|
|
|
}
|
|
|
|
|
2017-04-17 15:10:04 -04:00
|
|
|
const dx = targetX - util.target.x;
|
|
|
|
const dy = targetY - util.target.y;
|
|
|
|
const direction = 90 - MathUtil.radToDeg(Math.atan2(dy, dx));
|
2016-10-04 15:20:53 -04:00
|
|
|
util.target.setDirection(direction);
|
|
|
|
};
|
|
|
|
|
2016-09-12 13:26:10 -04:00
|
|
|
Scratch3MotionBlocks.prototype.glide = function (args, util) {
|
|
|
|
if (!util.stackFrame.timer) {
|
|
|
|
// First time: save data for future use.
|
|
|
|
util.stackFrame.timer = new Timer();
|
|
|
|
util.stackFrame.timer.start();
|
|
|
|
util.stackFrame.duration = Cast.toNumber(args.SECS);
|
|
|
|
util.stackFrame.startX = util.target.x;
|
|
|
|
util.stackFrame.startY = util.target.y;
|
|
|
|
util.stackFrame.endX = Cast.toNumber(args.X);
|
|
|
|
util.stackFrame.endY = Cast.toNumber(args.Y);
|
|
|
|
if (util.stackFrame.duration <= 0) {
|
|
|
|
// Duration too short to glide.
|
|
|
|
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
|
|
|
return;
|
|
|
|
}
|
2016-10-17 23:23:16 -04:00
|
|
|
util.yield();
|
2016-09-12 13:26:10 -04:00
|
|
|
} else {
|
2017-04-17 15:10:04 -04:00
|
|
|
const timeElapsed = util.stackFrame.timer.timeElapsed();
|
2016-09-12 13:26:10 -04:00
|
|
|
if (timeElapsed < util.stackFrame.duration * 1000) {
|
|
|
|
// In progress: move to intermediate position.
|
2017-04-17 15:10:04 -04:00
|
|
|
const frac = timeElapsed / (util.stackFrame.duration * 1000);
|
|
|
|
const dx = frac * (util.stackFrame.endX - util.stackFrame.startX);
|
|
|
|
const dy = frac * (util.stackFrame.endY - util.stackFrame.startY);
|
2016-09-12 13:26:10 -04:00
|
|
|
util.target.setXY(
|
|
|
|
util.stackFrame.startX + dx,
|
|
|
|
util.stackFrame.startY + dy
|
|
|
|
);
|
2016-10-17 23:23:16 -04:00
|
|
|
util.yield();
|
2016-09-12 13:26:10 -04:00
|
|
|
} else {
|
|
|
|
// Finished: move to final position.
|
|
|
|
util.target.setXY(util.stackFrame.endX, util.stackFrame.endY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-10-17 23:17:55 -04:00
|
|
|
Scratch3MotionBlocks.prototype.ifOnEdgeBounce = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const bounds = util.target.getBounds();
|
2016-10-17 23:17:55 -04:00
|
|
|
if (!bounds) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Measure distance to edges.
|
|
|
|
// Values are positive when the sprite is far away,
|
|
|
|
// and clamped to zero when the sprite is beyond.
|
2017-04-17 15:10:04 -04:00
|
|
|
const stageWidth = this.runtime.constructor.STAGE_WIDTH;
|
|
|
|
const stageHeight = this.runtime.constructor.STAGE_HEIGHT;
|
|
|
|
const distLeft = Math.max(0, (stageWidth / 2) + bounds.left);
|
|
|
|
const distTop = Math.max(0, (stageHeight / 2) - bounds.top);
|
|
|
|
const distRight = Math.max(0, (stageWidth / 2) - bounds.right);
|
|
|
|
const distBottom = Math.max(0, (stageHeight / 2) + bounds.bottom);
|
2016-10-17 23:17:55 -04:00
|
|
|
// Find the nearest edge.
|
2017-04-17 15:10:04 -04:00
|
|
|
let nearestEdge = '';
|
|
|
|
let minDist = Infinity;
|
2016-10-17 23:17:55 -04:00
|
|
|
if (distLeft < minDist) {
|
|
|
|
minDist = distLeft;
|
|
|
|
nearestEdge = 'left';
|
|
|
|
}
|
|
|
|
if (distTop < minDist) {
|
|
|
|
minDist = distTop;
|
|
|
|
nearestEdge = 'top';
|
|
|
|
}
|
|
|
|
if (distRight < minDist) {
|
|
|
|
minDist = distRight;
|
|
|
|
nearestEdge = 'right';
|
|
|
|
}
|
|
|
|
if (distBottom < minDist) {
|
|
|
|
minDist = distBottom;
|
|
|
|
nearestEdge = 'bottom';
|
|
|
|
}
|
|
|
|
if (minDist > 0) {
|
|
|
|
return; // Not touching any edge.
|
|
|
|
}
|
|
|
|
// Point away from the nearest edge.
|
2017-04-17 15:10:04 -04:00
|
|
|
const radians = MathUtil.degToRad(90 - util.target.direction);
|
|
|
|
let dx = Math.cos(radians);
|
|
|
|
let dy = -Math.sin(radians);
|
2016-10-23 17:55:31 -04:00
|
|
|
if (nearestEdge === 'left') {
|
2016-10-17 23:17:55 -04:00
|
|
|
dx = Math.max(0.2, Math.abs(dx));
|
2016-10-23 17:55:31 -04:00
|
|
|
} else if (nearestEdge === 'top') {
|
2016-10-17 23:17:55 -04:00
|
|
|
dy = Math.max(0.2, Math.abs(dy));
|
2016-10-23 17:55:31 -04:00
|
|
|
} else if (nearestEdge === 'right') {
|
2016-10-17 23:17:55 -04:00
|
|
|
dx = 0 - Math.max(0.2, Math.abs(dx));
|
2016-10-23 17:55:31 -04:00
|
|
|
} else if (nearestEdge === 'bottom') {
|
2016-10-17 23:17:55 -04:00
|
|
|
dy = 0 - Math.max(0.2, Math.abs(dy));
|
|
|
|
}
|
2017-04-17 15:10:04 -04:00
|
|
|
const newDirection = MathUtil.radToDeg(Math.atan2(dy, dx)) + 90;
|
2016-10-17 23:17:55 -04:00
|
|
|
util.target.setDirection(newDirection);
|
|
|
|
// Keep within the stage.
|
2017-04-17 15:10:04 -04:00
|
|
|
const fencedPosition = util.target.keepInFence(util.target.x, util.target.y);
|
2016-10-17 23:17:55 -04:00
|
|
|
util.target.setXY(fencedPosition[0], fencedPosition[1]);
|
|
|
|
};
|
|
|
|
|
2016-09-28 17:09:04 -04:00
|
|
|
Scratch3MotionBlocks.prototype.setRotationStyle = function (args, util) {
|
|
|
|
util.target.setRotationStyle(args.STYLE);
|
|
|
|
};
|
|
|
|
|
2016-06-29 23:51:21 -04:00
|
|
|
Scratch3MotionBlocks.prototype.changeX = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const dx = Cast.toNumber(args.DX);
|
2016-09-12 13:14:16 -04:00
|
|
|
util.target.setXY(util.target.x + dx, util.target.y);
|
2016-06-29 23:51:21 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
Scratch3MotionBlocks.prototype.setX = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const x = Cast.toNumber(args.X);
|
2016-09-12 13:14:16 -04:00
|
|
|
util.target.setXY(x, util.target.y);
|
2016-06-29 23:51:21 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
Scratch3MotionBlocks.prototype.changeY = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const dy = Cast.toNumber(args.DY);
|
2016-09-12 13:14:16 -04:00
|
|
|
util.target.setXY(util.target.x, util.target.y + dy);
|
2016-06-29 23:51:21 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
Scratch3MotionBlocks.prototype.setY = function (args, util) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const y = Cast.toNumber(args.Y);
|
2016-09-12 13:14:16 -04:00
|
|
|
util.target.setXY(util.target.x, y);
|
2016-06-29 23:51:21 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
Scratch3MotionBlocks.prototype.getX = function (args, util) {
|
|
|
|
return util.target.x;
|
|
|
|
};
|
|
|
|
|
|
|
|
Scratch3MotionBlocks.prototype.getY = function (args, util) {
|
|
|
|
return util.target.y;
|
|
|
|
};
|
|
|
|
|
2016-07-01 12:56:45 -04:00
|
|
|
Scratch3MotionBlocks.prototype.getDirection = function (args, util) {
|
|
|
|
return util.target.direction;
|
|
|
|
};
|
|
|
|
|
2016-06-29 20:56:55 -04:00
|
|
|
module.exports = Scratch3MotionBlocks;
|