Add files via upload
This commit is contained in:
41
index.html
Normal file
41
index.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>好棒 NiceBaseball by NiceChord+</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="p5.min.js"></script>
|
||||
<script src="p5.sound.min.js"></script>
|
||||
<script src="webmidi.js"></script>
|
||||
<script src="sketch.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main">
|
||||
<div class="center">
|
||||
<h3>好棒 NiceBaseball</h3>
|
||||
by Wiwi Kuan (NiceChord+)
|
||||
|
||||
<div id="sketch-holder">
|
||||
<!-- Our sketch will go here! -->
|
||||
</div>
|
||||
</div>
|
||||
<div id="controls" class="center">
|
||||
<div><h5>選擇 MIDI 裝置</h5>
|
||||
<input id="slider" type="range" min="0" max="0" value="0">
|
||||
<div id="device">Select Input: </div>
|
||||
</div>
|
||||
<br />
|
||||
<div id="description"><h5>說明</h5>
|
||||
<div class="left">
|
||||
使用 WebMIDI 技術,只支援 Brave 和 Google Chrome,開始前請用滑鼠按一下視窗。<br /><br />
|
||||
<b>投手:</b>C#5 和 D#5 左右移動,一起彈下 C5-E5-G5 來投球,你按下的力度和時間差會影響球路哦!<br /><br />
|
||||
<b>打者:</b>彈下 C3 來揮棒。<br /><br />
|
||||
C4 把球丟回給投手,C2 重設全部分數。<br /><br />
|
||||
你在三個出局數內,能打出幾支安打呢?
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
3
p5.min.js
vendored
Normal file
3
p5.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4390
p5.play.js
Normal file
4390
p5.play.js
Normal file
File diff suppressed because it is too large
Load Diff
3
p5.sound.min.js
vendored
Normal file
3
p5.sound.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
628
sketch.js
Normal file
628
sketch.js
Normal file
@@ -0,0 +1,628 @@
|
||||
const pi = 3.14159265358979323846;
|
||||
let score = {
|
||||
strikes: 0,
|
||||
balls: 0,
|
||||
outs: 0,
|
||||
hits: 0
|
||||
}
|
||||
let midiSelectSlider;
|
||||
let canPitch, canSwing;
|
||||
let ball = {
|
||||
startX: 250, // 球開始座標
|
||||
startY: 50,
|
||||
angle: 0, // ball angle
|
||||
spinVel: 0.01, // ball spin velocity
|
||||
velX: 0, // ball velocity
|
||||
velY: 0,
|
||||
x: 250, // ball position
|
||||
y: 50,
|
||||
size: 50, // ball size
|
||||
speed: 0.05, // ball speed
|
||||
desiredX: 250, // ball desired position
|
||||
desiredXVel: 0,
|
||||
desiredY: 50,
|
||||
status: "ready",
|
||||
strike: false,
|
||||
hit: false,
|
||||
randomX: 0
|
||||
};
|
||||
let bat = {
|
||||
startAngle: pi * 1.5,
|
||||
endAngle: pi * -0.5,
|
||||
desiredAngle: pi * 1.5,
|
||||
angle: pi * 1.5,
|
||||
vel: 0,
|
||||
speed: 0.3,
|
||||
status: "ready",
|
||||
swung: false // 揮棒記錄
|
||||
};
|
||||
let pitchInput = {
|
||||
c: false,
|
||||
e: false,
|
||||
g: false,
|
||||
cTime: 0,
|
||||
eTime: 0,
|
||||
gTime: 0,
|
||||
cVel: 0,
|
||||
eVel: 0,
|
||||
gVel: 0
|
||||
};
|
||||
let pitcherDir = "C";
|
||||
let umpireReady = true;
|
||||
let messageOn = false;
|
||||
let messageType = "";
|
||||
|
||||
|
||||
function preload() {
|
||||
// images
|
||||
ballImg = loadImage('assets/ball.png');
|
||||
batImg = loadImage('assets/bat.png');
|
||||
plateImg = loadImage('assets/plate.png');
|
||||
|
||||
// SFX
|
||||
soundFormats('mp3');
|
||||
swishSFX = loadSound('assets/swish');
|
||||
swingSFX = loadSound('assets/swing');
|
||||
hitSFX = loadSound('assets/hit');
|
||||
catchSFX = loadSound('assets/catch');
|
||||
ballFourSFX = loadSound('assets/ballfour');
|
||||
fairBallSFX = loadSound('assets/fairball');
|
||||
outSFX = loadSound('assets/out');
|
||||
gameOverSFX = loadSound('assets/gameover');
|
||||
|
||||
|
||||
}
|
||||
|
||||
function setup() {
|
||||
let canvas = createCanvas(500, 800);
|
||||
getAudioContext().suspend();
|
||||
|
||||
canvas.parent('sketch-holder');
|
||||
colorMode(HSB, 100)
|
||||
frameRate(120);
|
||||
textAlign(CENTER, CENTER);
|
||||
// Init MIDI
|
||||
WebMidi.enable(function (err) { //check if WebMidi.js is enabled
|
||||
if (err) {
|
||||
console.log("WebMidi could not be enabled.", err);
|
||||
} else {
|
||||
console.log("WebMidi enabled!");
|
||||
}
|
||||
|
||||
//name our visible MIDI input and output ports
|
||||
console.log("---");
|
||||
console.log("Inputs Ports: ");
|
||||
for (i = 0; i < WebMidi.inputs.length; i++) {
|
||||
console.log(i + ": " + WebMidi.inputs[i].name);
|
||||
}
|
||||
|
||||
console.log("---");
|
||||
console.log("Output Ports: ");
|
||||
for (i = 0; i < WebMidi.outputs.length; i++) {
|
||||
console.log(i + ": " + WebMidi.outputs[i].name);
|
||||
}
|
||||
midiSelectSlider = select("#slider");
|
||||
midiSelectSlider.attribute("max", WebMidi.inputs.length - 1);
|
||||
midiSelectSlider.changed(inputChanged);
|
||||
midiIn = WebMidi.inputs[midiSelectSlider.value()]
|
||||
inputChanged();
|
||||
});
|
||||
resetGame();
|
||||
}
|
||||
|
||||
function draw() { // 主 Loop
|
||||
background(66, 20, 20);
|
||||
drawField();
|
||||
drawScore();
|
||||
updateBat();
|
||||
drawBat();
|
||||
calculatePitch();
|
||||
udpateBall()
|
||||
drawBall();
|
||||
checkHit();
|
||||
umpire();
|
||||
drawMessage();
|
||||
|
||||
// bat.angle -= 0.05;
|
||||
}
|
||||
|
||||
function checkHit() { // 檢查有沒有打到球
|
||||
textAlign(CENTER, CENTER)
|
||||
//let debugText = floor(mouseX) + "," + floor(mouseY);
|
||||
fill(255);
|
||||
stroke(255);
|
||||
//text(debugText, mouseX, mouseY - 10);
|
||||
let dist = 25;
|
||||
let len = 125;
|
||||
let ang = bat.angle - (pi * 0.25);
|
||||
let lineX1 = dist * cos(ang - 0.1) + 170;
|
||||
let lineX2 = (dist + len) * cos(ang - 0.1) + 170;
|
||||
let lineY1 = dist * sin(ang - 0.1) + 600;
|
||||
let lineY2 = (dist + len) * sin(ang - 0.1) + 600;
|
||||
let lineX3 = dist * cos(ang + 0.1) + 170;
|
||||
let lineX4 = (dist + len) * cos(ang + 0.1) + 170;
|
||||
let lineY3 = dist * sin(ang + 0.1) + 600;
|
||||
let lineY4 = (dist + len) * sin(ang + 0.1) + 600;
|
||||
//line(lineX1, lineY1, lineX2, lineY2); // bat visualizer
|
||||
// line(lineX3, lineY3, lineX4, lineY4);
|
||||
//let bottomText = "ball: " + ball.x + ", " + ball.y;
|
||||
//text(bottomText, 250, 750);
|
||||
// 用兩條線代表球棒(避免穿牆),檢查跟球是否重疊
|
||||
|
||||
if (lineCircle(lineX1, lineY1, lineX2, lineY2, ball.x, ball.y, ball.size / 2) || lineCircle(lineX3, lineY3, lineX4, lineY4, ball.x, ball.y, ball.size / 2)) {
|
||||
if (bat.angle < pi && bat.status == "swinging" && !ball.hit) {
|
||||
fill(100, 70, 100);
|
||||
ellipse(ball.x, ball.y, ball.size);
|
||||
// console.log("hit" + ball.x + ", " + ball.y + " " + bat.angle);
|
||||
ball.hit = true;
|
||||
|
||||
hitSFX.play();
|
||||
flyBall(ang, bat.vel, ball.x, ball.y, ball.speed); // ball is hit!
|
||||
console.log(ang, bat.vel, ball.x, ball.y, ball.speed);
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function umpire() { // 判斷好球壞球
|
||||
//stroke(255);
|
||||
//line(200, 600, 300, 600); // plate visualizer
|
||||
//line(200, 660, 300, 660);
|
||||
if (lineCircle(200, 600, 300, 600, ball.x, ball.y, ball.size / 2) || lineCircle(200, 660, 300, 660, ball.x, ball.y, ball.size / 2)) {
|
||||
console.log("strike zone passed.");
|
||||
ball.strike = true;
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function drawScore() {
|
||||
textFont('Verdana');
|
||||
textSize(80);
|
||||
fill(66, 20, 80, 20);
|
||||
noStroke();
|
||||
textAlign(RIGHT, TOP);
|
||||
textStyle(BOLD);
|
||||
// text(score.outs, 480, 20);
|
||||
text(score.hits, 130, 600);
|
||||
|
||||
let scoreText = "STRIKE " + "⚾️".repeat(score.strikes) + "\nBALLS " + "⚾️".repeat(score.balls) + "\nOUTS " + "⚾️".repeat(score.outs);
|
||||
textSize(18);
|
||||
fill(66, 10, 80);
|
||||
textAlign(LEFT, CENTER);
|
||||
textStyle(BOLD);
|
||||
text(scoreText, 300, 720);
|
||||
|
||||
|
||||
}
|
||||
|
||||
function drawMessage() {
|
||||
if (messageOn) {
|
||||
let type = messageType;
|
||||
let bigText, smallText = "";
|
||||
if (type == "hit") {
|
||||
bigText = "HIT!"
|
||||
smallText = ""
|
||||
}
|
||||
if (type == "foul") {
|
||||
bigText = "FOUL"
|
||||
smallText = ""
|
||||
}
|
||||
if (type == "strike") {
|
||||
bigText = "STRIKE"
|
||||
smallText = ""
|
||||
}
|
||||
if (type == "strikeOut") {
|
||||
bigText = "STRIKE OUT!"
|
||||
smallText = ""
|
||||
}
|
||||
if (type == "ball") {
|
||||
bigText = "BALL"
|
||||
smallText = ""
|
||||
}
|
||||
if (type == "ballFour") {
|
||||
bigText = "BALL FOUR!"
|
||||
smallText = ""
|
||||
}
|
||||
if (score.outs == 3) {
|
||||
bigText = "GAME OVER"
|
||||
smallText = "Hits: " + score.hits;
|
||||
|
||||
}
|
||||
textSize(32);
|
||||
textAlign(CENTER, CENTER);
|
||||
text(bigText, 250, 350);
|
||||
textStyle(NORMAL);
|
||||
textSize(24);
|
||||
text(smallText, 250, 400);
|
||||
}
|
||||
}
|
||||
|
||||
function resetGame() {
|
||||
bat.swung = false; // 揮棒記錄
|
||||
messageOn = false; // 訊息關閉
|
||||
returnBat();
|
||||
returnBall();
|
||||
if (score.outs == 3) {
|
||||
resetScore();
|
||||
}
|
||||
}
|
||||
|
||||
function pitcherPos(dir) { // 移動投手位置
|
||||
if (ball.status == "ready") {
|
||||
if (dir == "L") {
|
||||
ball.desiredX -= 4;
|
||||
ball.desiredX = constrain(ball.desiredX, 150, 350);
|
||||
}
|
||||
if (dir == "R") {
|
||||
ball.desiredX += 4;
|
||||
ball.desiredX = constrain(ball.desiredX, 150, 350);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function udpateBall() { // 球的移動邏輯
|
||||
// change pitcher position
|
||||
pitcherPos(pitcherDir);
|
||||
// move ball to desired x, y
|
||||
ball.desiredX += constrain(ball.desiredXVel, -500, 1000);
|
||||
if (frameCount % 4) {
|
||||
ball.desiredX += random(-ball.randomX, ball.randomX);
|
||||
}
|
||||
ball.velX = (ball.desiredX - ball.x) * ball.speed;
|
||||
ball.velY = (ball.desiredY - ball.y) * ball.speed;
|
||||
if (abs(ball.desiredX - ball.x) < 1) {
|
||||
ball.x = ball.desiredX;
|
||||
}
|
||||
if (abs(ball.desiredY - ball.y) < 1) {
|
||||
ball.y = ball.desiredY;
|
||||
}
|
||||
if (ball.y > 900 && ball.status == "pitching") { // 球被捕手接到了
|
||||
ball.status = "catched";
|
||||
catchSFX.play();
|
||||
ball.desiredXVel = 0;
|
||||
if (ball.strike || bat.swung) {
|
||||
score.strikes++;
|
||||
messageOn = true;
|
||||
messageType = "strike";
|
||||
console.log("strikes: " + score.strikes);
|
||||
if (score.strikes == 3) { // strike out
|
||||
messageOn = true;
|
||||
messageType = "strikeOut";
|
||||
outSFX.play();
|
||||
score.outs++;
|
||||
if (score.outs == 3) {
|
||||
gameOverSFX.play();
|
||||
}
|
||||
score.strikes = 0;
|
||||
score.balls = 0;
|
||||
}
|
||||
ball.strike = false;
|
||||
} else {
|
||||
score.balls++;
|
||||
messageOn = true;
|
||||
messageType = "ball";
|
||||
console.log("balls: " + score.balls);
|
||||
if (score.balls == 4) { // ball four
|
||||
messageOn = true;
|
||||
messageType = "ballFour";
|
||||
ballFourSFX.play();
|
||||
score.hits++;
|
||||
score.balls = 0;
|
||||
score.strikes = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
ball.x += ball.velX;
|
||||
ball.y += ball.velY;
|
||||
ball.angle += ball.spinVel;
|
||||
}
|
||||
|
||||
function ballDrop() { // 判斷球落點!
|
||||
if (ball.status == "flying") {
|
||||
ball.desiredXVel = 0;
|
||||
let ballDrop = calAngle(250, 700, ball.x, ball.y);
|
||||
if (ballDrop < -0.78 && ballDrop > -2.36) {
|
||||
messageOn = true;
|
||||
messageType = "hit";
|
||||
console.log("Fair Ball: " + ballDrop);
|
||||
fairBallSFX.play();
|
||||
score.hits++;
|
||||
score.balls = 0;
|
||||
score.strikes = 0;
|
||||
} else {
|
||||
messageOn = true;
|
||||
messageType = "foul";
|
||||
console.log("Foul Ball: " + ballDrop);
|
||||
if (score.strikes < 2) {
|
||||
score.strikes++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function calculatePitch() { // 計算球路
|
||||
if (ball.status == "ready" && pitchInput.c == true && pitchInput.e == true && pitchInput.g == true) { // Check C, E, G all pressed
|
||||
// calculate spin
|
||||
let spin = pitchInput.gVel - pitchInput.cVel // C & G control spin
|
||||
spin = map(spin, -0.5, 0.5, -1, 1, true);
|
||||
ball.desiredXVel = spin * -120;
|
||||
ball.spinVel = spin;
|
||||
// calculate speed
|
||||
let speed = (pitchInput.cVel + pitchInput.eVel * 2 + pitchInput.gVel) / 4;
|
||||
speed = map(speed, 0.2, 0.8, 0.01, 0.06, true);
|
||||
ball.speed = speed;
|
||||
ball.randomX = map(abs(spin), 0, 0.5, 220, 0, true) * map(speed, 0.01, 0.03, 1, 0, true) // the less spin & the less speed, the more random X movement
|
||||
console.log(abs(spin), speed);
|
||||
ball.desiredX += spin * map(speed, 0.01, 0.07, 800, -50, true);
|
||||
|
||||
// if input too slow
|
||||
let duration = max([pitchInput.cTime, pitchInput.eTime, pitchInput.gTime]) - min([pitchInput.cTime, pitchInput.eTime, pitchInput.gTime]);
|
||||
console.log(duration);
|
||||
if (duration > 100) {
|
||||
ball.speed = 0.007; // 輸入期間超過 100ms 的話,球變超慢
|
||||
ball.desiredXVel = ball.desiredXVel * 0.05; // 球變不曲
|
||||
ball.spinVel = ball.spinVel * 0.2 // 球變不曲
|
||||
ball.randomX = 0;
|
||||
}
|
||||
|
||||
// pitch!
|
||||
ball.status = "pitching";
|
||||
console.log("pitch!")
|
||||
pitchBall();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function flyBall(batAng, batVel, ballX, ballY, ballSpeed) { // 球被打到了,計算飛行方向
|
||||
ball.status = "flying";
|
||||
ball.randomX = 0;
|
||||
let flyDist = 1000; // fly distance
|
||||
ball.desiredX = flyDist * cos(batAng - 1.4) + ballX;
|
||||
ball.desiredXVel = 10 * cos(batAng - 1);
|
||||
ball.desiredY = flyDist * sin(batAng - 1.4) + ballY;
|
||||
ball.spinVel += cos(batAng - 1);
|
||||
ball.speed = 0.03; //
|
||||
setTimeout(ballDrop, 600); // 0.6 秒後判斷球落點
|
||||
}
|
||||
|
||||
function pitchBall() {
|
||||
ball.desiredY = 1300; // 投球就是將球想要去的 y 座標設到 1300
|
||||
swishSFX.play();
|
||||
}
|
||||
|
||||
function returnBall() { // return the ball to original position
|
||||
pitchInput.c = false;
|
||||
pitchInput.e = false;
|
||||
pitchInput.g = false;
|
||||
ball.speed = 0.2;
|
||||
ball.spinVel = random(-0.02, 0.02);
|
||||
ball.desiredX = ball.startX;
|
||||
ball.desiredXVel = 0;
|
||||
ball.desiredY = ball.startY;
|
||||
pitcherDir = "C";
|
||||
ball.hit = false;
|
||||
ball.status = "ready";
|
||||
ball.strike = false;
|
||||
ball.randomX = 0;
|
||||
}
|
||||
|
||||
function drawBall() {
|
||||
push();
|
||||
translate(ball.x, ball.y);
|
||||
rotate(ball.angle);
|
||||
imageMode(CENTER);
|
||||
image(ballImg, 0, 0, ball.size, ball.size);
|
||||
pop();
|
||||
}
|
||||
|
||||
function updateBat() {
|
||||
bat.vel = (bat.desiredAngle - bat.angle) * bat.speed;
|
||||
bat.angle = bat.angle + bat.vel;
|
||||
if (abs(bat.angle - bat.desiredAngle) < 0.01) {
|
||||
bat.angle = bat.desiredAngle;
|
||||
}
|
||||
}
|
||||
|
||||
function drawBat() {
|
||||
push();
|
||||
translate(170, 600);
|
||||
rotate(bat.angle);
|
||||
image(batImg, 40, -40, 105, 105);
|
||||
pop();
|
||||
}
|
||||
|
||||
function swingBat(vel) {
|
||||
if (bat.status == "ready") {
|
||||
bat.speed = map(vel, 0.2, 0.8, 0.1, 0.4, true);
|
||||
console.log(bat.speed);
|
||||
bat.desiredAngle = bat.endAngle;
|
||||
bat.status = "swinging";
|
||||
swingSFX.play();
|
||||
bat.swung = true;
|
||||
setTimeout(returnBat, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function returnBat() {
|
||||
bat.desiredAngle = bat.startAngle;
|
||||
bat.speed = 0.1;
|
||||
bat.status = "ready";
|
||||
}
|
||||
|
||||
function drawField() {
|
||||
stroke(30);
|
||||
line(250, 700, 0, 450);
|
||||
line(250, 700, 500, 450);
|
||||
imageMode(CENTER);
|
||||
image(plateImg, 250, 650, 110, 110);
|
||||
|
||||
}
|
||||
|
||||
function resetScore() {
|
||||
score.strikes = 0;
|
||||
score.balls = 0;
|
||||
score.outs = 0;
|
||||
score.hits = 0;
|
||||
}
|
||||
|
||||
function noteOn(note, vel, ms) {
|
||||
if (note == 36) { // C3
|
||||
resetScore();
|
||||
resetGame();
|
||||
}
|
||||
if (note == 48) { // C3
|
||||
swingBat(vel);
|
||||
}
|
||||
if (note == 60) {
|
||||
resetGame();
|
||||
}
|
||||
if (note == 72) {
|
||||
pitchInput.c = true;
|
||||
pitchInput.cVel = vel;
|
||||
pitchInput.cTime = ms;
|
||||
}
|
||||
if (note == 76) {
|
||||
pitchInput.e = true;
|
||||
pitchInput.eVel = vel;
|
||||
pitchInput.eTime = ms;
|
||||
}
|
||||
if (note == 79) {
|
||||
pitchInput.g = true;
|
||||
pitchInput.gVel = vel;
|
||||
pitchInput.gTime = ms;
|
||||
}
|
||||
if (note == 73) {
|
||||
pitcherDir = "L";
|
||||
}
|
||||
if (note == 75) {
|
||||
pitcherDir = "R";
|
||||
}
|
||||
}
|
||||
|
||||
function noteOff(note, vel, ms) {
|
||||
if (note == 73) {
|
||||
pitcherDir = "C";
|
||||
}
|
||||
if (note == 75) {
|
||||
pitcherDir = "C";
|
||||
}
|
||||
}
|
||||
|
||||
function inputChanged() {
|
||||
midiIn.removeListener();
|
||||
midiIn = WebMidi.inputs[midiSelectSlider.value()];
|
||||
midiIn.addListener('noteon', "all", function (e) {
|
||||
console.log(e.note.number, e.velocity, e.timestamp);
|
||||
noteOn(e.note.number, e.velocity, e.timestamp); // number:
|
||||
});
|
||||
midiIn.addListener('noteoff', "all", function (e) {
|
||||
noteOff(e.note.number, e.velocity, e.timestamp);
|
||||
})
|
||||
console.log(midiIn.name);
|
||||
select("#device").html(midiIn.name);
|
||||
};
|
||||
|
||||
function mouseClicked() {
|
||||
resetGame();
|
||||
userStartAudio();
|
||||
}
|
||||
|
||||
|
||||
// line-circle collision
|
||||
|
||||
function lineCircle(x1, y1, x2, y2, cx, cy, r) {
|
||||
|
||||
// is either end INSIDE the circle?
|
||||
// if so, return true immediately
|
||||
let inside1 = pointCircle(x1, y1, cx, cy, r);
|
||||
let inside2 = pointCircle(x2, y2, cx, cy, r);
|
||||
if (inside1 || inside2) return true;
|
||||
|
||||
// get length of the line
|
||||
let distX = x1 - x2;
|
||||
let distY = y1 - y2;
|
||||
let len = sqrt((distX * distX) + (distY * distY));
|
||||
|
||||
// get dot product of the line and circle
|
||||
let dot = (((cx - x1) * (x2 - x1)) + ((cy - y1) * (y2 - y1))) / pow(len, 2);
|
||||
|
||||
// find the closest point on the line
|
||||
let closestX = x1 + (dot * (x2 - x1));
|
||||
let closestY = y1 + (dot * (y2 - y1));
|
||||
|
||||
// is this point actually on the line segment?
|
||||
// if so keep going, but if not, return false
|
||||
let onSegment = linePoint(x1, y1, x2, y2, closestX, closestY);
|
||||
if (!onSegment) return false;
|
||||
|
||||
// optionally, draw a circle at the closest
|
||||
// point on the line
|
||||
|
||||
|
||||
// get distance to closest point
|
||||
distX = closestX - cx;
|
||||
distY = closestY - cy;
|
||||
let distance = sqrt((distX * distX) + (distY * distY));
|
||||
|
||||
if (distance <= r) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// POINT/CIRCLE
|
||||
function pointCircle(px, py, cx, cy, r) {
|
||||
|
||||
// get distance between the point and circle's center
|
||||
// using the Pythagorean Theorem
|
||||
let distX = px - cx;
|
||||
let distY = py - cy;
|
||||
let distance = sqrt((distX * distX) + (distY * distY));
|
||||
|
||||
// if the distance is less than the circle's
|
||||
// radius the point is inside!
|
||||
if (distance <= r) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// LINE/POINT
|
||||
function linePoint(x1, y1, x2, y2, px, py) {
|
||||
|
||||
// get distance from the point to the two ends of the line
|
||||
let d1 = dist(px, py, x1, y1);
|
||||
let d2 = dist(px, py, x2, y2);
|
||||
|
||||
// get the length of the line
|
||||
let lineLen = dist(x1, y1, x2, y2);
|
||||
|
||||
// since floats are so minutely accurate, add
|
||||
// a little buffer zone that will give collision
|
||||
let buffer = 0.1; // higher # = less accurate
|
||||
|
||||
// if the two distances are equal to the line's
|
||||
// length, the point is on the line!
|
||||
// note we use the buffer here to give a range,
|
||||
// rather than one #
|
||||
if (d1 + d2 >= lineLen - buffer && d1 + d2 <= lineLen + buffer) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function calAngle(cx, cy, ex, ey) {
|
||||
var dy = ey - cy;
|
||||
var dx = ex - cx;
|
||||
var theta = Math.atan2(dy, dx); // range (-PI, PI]
|
||||
return theta;
|
||||
}
|
548
style.css
Normal file
548
style.css
Normal file
@@ -0,0 +1,548 @@
|
||||
/*! style.css v1.0.0 | ISC License | https://github.com/ungoldman/style.css */
|
||||
html {
|
||||
color: rgb(210, 209, 202);
|
||||
background-color: #202328;
|
||||
box-sizing: border-box;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "avenir next", avenir, "segoe ui", "fira sans", roboto, noto, "droid sans", "liberation sans", "lucida grande", "helvetica neue", helvetica, "franklin gothic medium", "century gothic", cantarell, oxygen, ubuntu, sans-serif;
|
||||
font-size: calc(14px + 0.25vw);
|
||||
line-height: 1.55;
|
||||
-webkit-font-kerning: normal;
|
||||
font-kerning: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-feature-settings: "kern", "liga"1, "calt"0;
|
||||
font-feature-settings: "kern", "liga"1, "calt"0;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#main {
|
||||
display: flex;
|
||||
margin-top: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
#controls {
|
||||
width: 400px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#sketch-holder {
|
||||
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
footer,
|
||||
header,
|
||||
nav,
|
||||
section,
|
||||
figcaption,
|
||||
figure,
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
p,
|
||||
blockquote,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
table,
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.25em;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-weight: 500;
|
||||
line-height: 1.25em;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
h1 small,
|
||||
h2 small,
|
||||
h3 small,
|
||||
h4 small,
|
||||
h5 small,
|
||||
h6 small {
|
||||
color: #777;
|
||||
font-size: 0.7em;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h1 code,
|
||||
h2 code,
|
||||
h3 code,
|
||||
h4 code,
|
||||
h5 code,
|
||||
h6 code {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.75em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2.25em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.75em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1.15em;
|
||||
color: #575757;
|
||||
}
|
||||
|
||||
p {
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
-webkit-text-decoration-skip: objects;
|
||||
color: #0074d9;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline-width: 0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
a:active,
|
||||
a:focus,
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
ul ol,
|
||||
ol ol {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
ul ul,
|
||||
ul ol,
|
||||
ol ul,
|
||||
ol ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
ul ul ol,
|
||||
ul ol ol,
|
||||
ol ul ol,
|
||||
ol ol ol {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
li>p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
padding: 0 1rem;
|
||||
color: #7d7d7d;
|
||||
border-left: 4px solid #d6d6d6;
|
||||
}
|
||||
|
||||
blockquote> :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
blockquote> :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: inherit;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
code,
|
||||
pre,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: menlo, inconsolata, consolas, "fira mono", "noto mono", "droid sans mono", "liberation mono", "dejavu sans mono", "ubuntu mono", monaco, "courier new", monospace;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
pre,
|
||||
code {
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
word-wrap: normal;
|
||||
padding: 1em;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: transparent;
|
||||
display: inline;
|
||||
padding: 0;
|
||||
line-height: inherit;
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
pre code::before,
|
||||
pre code::after {
|
||||
content: normal;
|
||||
}
|
||||
|
||||
pre>code {
|
||||
border: 0;
|
||||
font-size: 1em;
|
||||
white-space: pre;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0.2em 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code::before,
|
||||
code::after {
|
||||
letter-spacing: -0.2em;
|
||||
content: '\00a0';
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: #e6e6e6;
|
||||
background-image: linear-gradient(#fafafa, #e6e6e6);
|
||||
background-repeat: repeat-x;
|
||||
border: 1px solid #d6d6d6;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 1px 0 #d6d6d6;
|
||||
color: #303030;
|
||||
display: inline-block;
|
||||
line-height: 0.95em;
|
||||
margin: 0 1px;
|
||||
padding: 5px 5px 1px;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
word-break: normal;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
table th,
|
||||
table td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
table th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
table tr:nth-child(2n) {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
overflow: visible;
|
||||
background: transparent;
|
||||
height: 4px;
|
||||
padding: 0;
|
||||
margin: 1em 0;
|
||||
background-color: #e7e7e7;
|
||||
border: 0 none;
|
||||
}
|
||||
|
||||
hr::before {
|
||||
display: table;
|
||||
content: '';
|
||||
}
|
||||
|
||||
hr::after {
|
||||
display: table;
|
||||
clear: both;
|
||||
content: '';
|
||||
}
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
border: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
figure img {
|
||||
background: white;
|
||||
border: 1px solid #c7c7c7;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
font-style: italic;
|
||||
font-size: 0.75em;
|
||||
font-weight: 200;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none;
|
||||
text-decoration: underline;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
dl {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dt {
|
||||
padding: 0;
|
||||
margin-top: 1em;
|
||||
font-size: 1em;
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
padding: 0 1em;
|
||||
margin-bottom: 1.25em;
|
||||
}
|
||||
|
||||
audio,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
button,
|
||||
html [type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
legend {
|
||||
color: inherit;
|
||||
display: table;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
details,
|
||||
menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
4427
webmidi.js
Normal file
4427
webmidi.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user