Spaces:
Running
Running
const BlockType = require('../../extension-support/block-type') | |
const BlockShape = require('../../extension-support/block-shape') | |
const ArgumentType = require('../../extension-support/argument-type') | |
const Cast = require('../../util/cast') | |
/** | |
* @param {number} x | |
* @returns {string} | |
*/ | |
function formatNumber(x) { | |
if (x >= 1e6) { | |
return x.toExponential(4) | |
} else { | |
x = Math.floor(x * 1000) / 1000 | |
return x.toFixed(Math.min(3, (String(x).split('.')[1] || '').length)) | |
} | |
} | |
function span(text) { | |
let el = document.createElement('span') | |
el.innerHTML = text | |
el.style.display = 'hidden' | |
el.style.whiteSpace = 'nowrap' | |
el.style.width = '100%' | |
el.style.textAlign = 'center' | |
return el | |
} | |
class VectorType { | |
customId = "jwVector" | |
constructor(x = 0, y = 0) { | |
this.x = isNaN(x) ? 0 : x | |
this.y = isNaN(y) ? 0 : y | |
} | |
static toVector(x) { | |
if (x instanceof VectorType) return x | |
if (x instanceof Array && x.length == 2) return new VectorType(x[0], x[1]) | |
if (String(x).split(',')) return new VectorType(Cast.toNumber(String(x).split(',')[0]), Cast.toNumber(String(x).split(',')[1])) | |
return new VectorType(0, 0) | |
} | |
jwArrayHandler() { | |
return 'Vector' | |
} | |
toString() { | |
return `${this.x},${this.y}` | |
} | |
toMonitorContent = () => span(this.toString()) | |
toReporterContent() { | |
let root = document.createElement('div') | |
root.style.display = 'flex' | |
root.style.width = "200px" | |
root.style.overflow = "hidden" | |
let details = document.createElement('div') | |
details.style.display = 'flex' | |
details.style.flexDirection = 'column' | |
details.style.justifyContent = 'center' | |
details.style.width = "100px" | |
details.appendChild(span(`<b>X:</b> ${formatNumber(this.x)}`)) | |
details.appendChild(span(`<b>Y:</b> ${formatNumber(this.y)}`)) | |
root.appendChild(details) | |
let angle = document.createElement('div') | |
angle.style.width = "100px" | |
let circle = document.createElement('div') | |
circle.style.width = "84px" | |
circle.style.height = "84px" | |
circle.style.margin = "8px" | |
circle.style.border = "4px solid black" | |
circle.style.borderRadius = "100%" | |
circle.style.boxSizing = "border-box" | |
circle.style.transform = `rotate(${this.angle}deg)` | |
let line = document.createElement('div') | |
line.style.width = "8px" | |
line.style.height = "50%" | |
line.style.background = "black" | |
line.style.position = "absolute" | |
line.style.left = "calc(50% - 4px)" | |
circle.appendChild(line) | |
angle.appendChild(circle) | |
root.appendChild(angle) | |
return root | |
} | |
/** @returns {number} */ | |
get magnitude() { return Math.hypot(this.x, this.y) } | |
/** @returns {number} */ | |
get angle() {return Math.atan2(this.x, this.y) * (180 / Math.PI)} | |
} | |
const Vector = { | |
Type: VectorType, | |
Block: { | |
blockType: BlockType.REPORTER, | |
blockShape: BlockShape.LEAF, | |
forceOutputType: "Vector", | |
disableMonitor: true | |
}, | |
Argument: { | |
shape: BlockShape.LEAF, | |
check: ["Vector"] | |
} | |
} | |
class Extension { | |
constructor() { | |
vm.jwVector = Vector | |
vm.runtime.registerSerializer( | |
"jwVector", | |
v => [v.x, v.y], | |
v => new Vector.Type(v[0], v[1]) | |
); | |
} | |
getInfo() { | |
return { | |
id: "jwVector", | |
name: "Vector", | |
color1: "#6babff", | |
menuIconURI: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCIgeG1sbnM6Yng9Imh0dHBzOi8vYm94eS1zdmcuY29tIj4KICA8ZWxsaXBzZSBzdHlsZT0ic3Ryb2tlLXdpZHRoOiAycHg7IHBhaW50LW9yZGVyOiBzdHJva2U7IGZpbGw6IHJnYigxMDcsIDE3MSwgMjU1KTsgc3Ryb2tlOiByZ2IoNjksIDEyNiwgMjA0KTsiIGN4PSIxMCIgY3k9IjEwIiByeD0iOSIgcnk9IjkiPjwvZWxsaXBzZT4KICA8cGF0aCBkPSJNIDQuMzUyIDEzLjc2NiBDIDQuMzUyIDE0LjgwNSA1LjE5NCAxNS42NDggNi4yMzUgMTUuNjQ4IEwgMTAgMTUuNjQ4IEMgMTEuMDM5IDE1LjY0OCAxMS44ODIgMTQuODA1IDExLjg4MiAxMy43NjYgTCAxMS44ODIgMTAgQyAxMS44ODIgOC45NTkgMTEuMDM5IDguMTE4IDEwIDguMTE4IEwgNi4yMzUgOC4xMTggQyA1LjE5NCA4LjExOCA0LjM1MiA4Ljk1OSA0LjM1MiAxMCBMIDQuMzUyIDEzLjc2NiBNIDguMTE3IDEzLjc2NiBDIDYuNjY4IDEzLjc2NiA1Ljc2MiAxMi4xOTUgNi40ODcgMTAuOTQyIEMgNi44MjIgMTAuMzU4IDcuNDQzIDEwIDguMTE3IDEwIEMgOS41NjcgMTAgMTAuNDcyIDExLjU2OSA5Ljc0NyAxMi44MjQgQyA5LjQxMSAxMy40MDYgOC43ODkgMTMuNzY2IDguMTE3IDEzLjc2NiBNIDcuMTc2IDkuMDU5IEwgOS4wNTggOS4wNTkgTCA5LjA1OCA1LjI5NCBDIDkuMDU4IDQuNTY5IDguMjczIDQuMTE2IDcuNjQ3IDQuNDc5IEMgNy4zNTUgNC42NDYgNy4xNzYgNC45NTcgNy4xNzYgNS4yOTQgTCA3LjE3NiA5LjA1OSBaIE0gMTAuOTQxIDEwLjk0MiBMIDEwLjk0MSAxMi44MjQgTCAxNC43MDYgMTIuODI0IEMgMTUuNDMxIDEyLjgyNCAxNS44ODMgMTIuMDM5IDE1LjUyMSAxMS40MTIgQyAxNS4zNTIgMTEuMTIxIDE1LjA0MSAxMC45NDIgMTQuNzA2IDEwLjk0MiBMIDEwLjk0MSAxMC45NDIgWiIgc3R5bGU9ImZpbGw6IHJnYigyNTUsIDI1NSwgMjU1KTsiPjwvcGF0aD4KPC9zdmc+", | |
blocks: [ | |
{ | |
opcode: 'newVector', | |
text: 'new vector x: [X] y: [Y]', | |
arguments: { | |
X: { | |
type: ArgumentType.NUMBER, | |
defaultValue: 0 | |
}, | |
Y: { | |
type: ArgumentType.NUMBER, | |
defaultValue: 0 | |
} | |
}, | |
...Vector.Block | |
}, | |
{ | |
opcode: 'newVectorFromMagnitude', | |
text: 'new vector magnitude: [X] angle: [Y]', | |
arguments: { | |
X: { | |
type: ArgumentType.NUMBER, | |
defaultValue: 1 | |
}, | |
Y: { | |
type: ArgumentType.ANGLE, | |
defaultValue: 0 | |
} | |
}, | |
...Vector.Block | |
}, | |
"---", | |
{ | |
opcode: 'vectorX', | |
text: '[VECTOR] x', | |
blockType: BlockType.REPORTER, | |
arguments: { | |
VECTOR: Vector.Argument | |
} | |
}, | |
{ | |
opcode: 'vectorY', | |
text: '[VECTOR] y', | |
blockType: BlockType.REPORTER, | |
arguments: { | |
VECTOR: Vector.Argument | |
} | |
}, | |
"---", | |
{ | |
opcode: 'add', | |
text: '[X] + [Y]', | |
arguments: { | |
X: Vector.Argument, | |
Y: Vector.Argument | |
}, | |
...Vector.Block | |
}, | |
{ | |
opcode: 'subtract', | |
text: '[X] - [Y]', | |
arguments: { | |
X: Vector.Argument, | |
Y: Vector.Argument | |
}, | |
...Vector.Block | |
}, | |
{ | |
opcode: 'multiplyA', | |
text: '[X] * [Y]', | |
arguments: { | |
X: Vector.Argument, | |
Y: { | |
type: ArgumentType.NUMBER, | |
defaultValue: 1 | |
} | |
}, | |
...Vector.Block | |
}, | |
{ | |
opcode: 'multiplyB', | |
text: '[X] * [Y]', | |
arguments: { | |
X: Vector.Argument, | |
Y: Vector.Argument | |
}, | |
...Vector.Block | |
}, | |
{ | |
opcode: 'divideA', | |
text: '[X] / [Y]', | |
arguments: { | |
X: Vector.Argument, | |
Y: { | |
type: ArgumentType.NUMBER, | |
defaultValue: 1 | |
} | |
}, | |
...Vector.Block | |
}, | |
{ | |
opcode: 'divideB', | |
text: '[X] / [Y]', | |
arguments: { | |
X: Vector.Argument, | |
Y: Vector.Argument | |
}, | |
...Vector.Block | |
}, | |
"---", | |
{ | |
opcode: 'magnitude', | |
text: 'magnitude of [VECTOR]', | |
blockType: BlockType.REPORTER, | |
arguments: { | |
VECTOR: Vector.Argument | |
} | |
}, | |
{ | |
opcode: 'angle', | |
text: 'angle of [VECTOR]', | |
blockType: BlockType.REPORTER, | |
arguments: { | |
VECTOR: Vector.Argument | |
} | |
}, | |
{ | |
opcode: 'normalize', | |
text: 'normalize [VECTOR]', | |
arguments: { | |
VECTOR: Vector.Argument | |
}, | |
...Vector.Block | |
}, | |
{ | |
opcode: 'absolute', | |
text: 'absolute [VECTOR]', | |
arguments: { | |
VECTOR: Vector.Argument | |
}, | |
...Vector.Block | |
}, | |
{ | |
opcode: 'rotate', | |
text: 'rotate [VECTOR] by [ANGLE]', | |
arguments: { | |
VECTOR: Vector.Argument, | |
ANGLE: { | |
type: ArgumentType.ANGLE, | |
defaultValue: 90 | |
} | |
}, | |
...Vector.Block | |
}, | |
] | |
}; | |
} | |
newVector(args) { | |
const X = Cast.toNumber(args.X) | |
const Y = Cast.toNumber(args.Y) | |
return new VectorType(X, Y) | |
} | |
newVectorFromMagnitude(args) { | |
return this.rotate({VECTOR: new VectorType(0, Cast.toNumber(args.X)), ANGLE: args.Y}) | |
} | |
vectorX(args) { | |
return VectorType.toVector(args.VECTOR).x | |
} | |
vectorY(args) { | |
return VectorType.toVector(args.VECTOR).y | |
} | |
add(args) { | |
const X = VectorType.toVector(args.X) | |
const Y = VectorType.toVector(args.Y) | |
return new VectorType(X.x + Y.x, X.y + Y.y) | |
} | |
subtract(args) { | |
const X = VectorType.toVector(args.X) | |
const Y = VectorType.toVector(args.Y) | |
return new VectorType(X.x - Y.x, X.y - Y.y) | |
} | |
multiplyA(args) { | |
const X = VectorType.toVector(args.X) | |
const Y = Cast.toNumber(args.Y) | |
return new VectorType(X.x * Y, X.y * Y) | |
} | |
multiplyB(args) { | |
const X = VectorType.toVector(args.X) | |
const Y = VectorType.toVector(args.Y) | |
return new VectorType(X.x * Y.x, X.y * Y.y) | |
} | |
divideA(args) { | |
const X = VectorType.toVector(args.X) | |
const Y = Cast.toNumber(args.Y) | |
return new VectorType(X.x / Y, X.y / Y) | |
} | |
divideB(args) { | |
const X = VectorType.toVector(args.X) | |
const Y = VectorType.toVector(args.Y) | |
return new VectorType(X.x / Y.x, X.y / Y.y) | |
} | |
magnitude(args) { | |
return VectorType.toVector(args.VECTOR).magnitude | |
} | |
angle(args) { | |
return VectorType.toVector(args.VECTOR).angle | |
} | |
normalize(args) { | |
const v = VectorType.toVector(args.VECTOR) | |
return new VectorType(v.x / v.magnitude, v.y / v.magnitude) | |
} | |
absolute(args) { | |
const v = VectorType.toVector(args.VECTOR) | |
return new VectorType(Math.abs(v.x), Math.abs(v.y)) | |
} | |
rotate(args) { | |
const v = VectorType.toVector(args.VECTOR) | |
const ANGLE = Cast.toNumber(args.ANGLE) / 180 * -Math.PI | |
const cos = Math.cos(ANGLE) | |
const sin = Math.sin(ANGLE) | |
return new VectorType( | |
v.x * cos - v.y * sin, | |
v.x * sin + v.y * cos | |
) | |
} | |
} | |
module.exports = Extension |