Шутка "Свежая трава"
This commit is contained in:
Vendored
+37
-4
File diff suppressed because one or more lines are too long
@@ -190,4 +190,21 @@ window.AprilJokes.settings = {
|
||||
'y': 'ы', 'z': 'з',
|
||||
},
|
||||
},
|
||||
|
||||
fresh_grass: {
|
||||
enabled: true,
|
||||
|
||||
chance: 100,
|
||||
|
||||
keepDistance: 200,
|
||||
maxVelocity: 500,
|
||||
minVelocity: 20,
|
||||
tickPerSecond: 50,
|
||||
gravity: 20,
|
||||
correctionForce: 0.8,
|
||||
mouseForceMax: 5000,
|
||||
mouseForcePow: 5,
|
||||
mass: 1,
|
||||
collisionElasticity: 0.9,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -507,4 +507,67 @@ window.AprilJokes.settings = {
|
||||
'y': 'ы', 'z': 'з',
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* **Свежая трава**
|
||||
*
|
||||
* Предложение потрогать траву
|
||||
* @author Идея: Magia
|
||||
* @author Kozhilya
|
||||
*/
|
||||
fresh_grass: {
|
||||
enabled: true,
|
||||
|
||||
chance: 100,
|
||||
|
||||
/**
|
||||
* Расстояние, на котором трава начнёт убегать
|
||||
*/
|
||||
keepDistance: 200,
|
||||
|
||||
/**
|
||||
* Максимальная скорость картинки (в пикселях в секунду)
|
||||
*/
|
||||
maxVelocity: 500,
|
||||
|
||||
/**
|
||||
* Минимальная скорость картинки (в пикселях в секунду)
|
||||
*/
|
||||
minVelocity: 20,
|
||||
|
||||
/**
|
||||
* Количество отметок времени за секунду
|
||||
*/
|
||||
tickPerSecond: 50,
|
||||
|
||||
/**
|
||||
* Сила притяжения в центр
|
||||
*/
|
||||
gravity: 20,
|
||||
|
||||
/**
|
||||
* Сила исправления скорости
|
||||
*/
|
||||
correctionForce: 0.8,
|
||||
|
||||
/**
|
||||
* Максимальная сила отталкивания от курсора
|
||||
*/
|
||||
mouseForceMax: 5000,
|
||||
|
||||
/**
|
||||
* Степень увеличения силы отталкивания от курсора
|
||||
*/
|
||||
mouseForcePow: 5,
|
||||
|
||||
/**
|
||||
* Общая масса картинки
|
||||
*/
|
||||
mass: 1,
|
||||
|
||||
/**
|
||||
* Доля скорости, сохраняемой при столкновении со стеной
|
||||
*/
|
||||
collisionElasticity: 0.9,
|
||||
}
|
||||
};
|
||||
|
||||
+61
-1
@@ -23,7 +23,7 @@ export function getTextNodes(
|
||||
filter((_, node) => node.nodeType === Node.TEXT_NODE);
|
||||
}
|
||||
|
||||
let debugEnabled = ((scriptElement) => {
|
||||
export let debugEnabled = ((scriptElement) => {
|
||||
if (!(scriptElement instanceof HTMLScriptElement)) {
|
||||
return false;
|
||||
}
|
||||
@@ -44,3 +44,63 @@ export function debug(...data: (string|number|object)[]) {
|
||||
export function enableDebug() {
|
||||
debugEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка сенсорного экрана
|
||||
* @return {boolean}
|
||||
*/
|
||||
export function isTouchEnabled(): boolean {
|
||||
return 'ontouchstart' in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
// eslint-disable-next-line
|
||||
((navigator as any).msMaxTouchPoints ?? 0) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка, является ли переменная числом
|
||||
* @param {any} n Проверяемая переменная
|
||||
* @return {boolean} Результат проверки
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
export function isNumber(n: any): boolean {
|
||||
return Number(n) === n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка, является ли переменная целым числом
|
||||
* @param {any} n Проверяемая переменная
|
||||
* @return {boolean} Результат проверки
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
export function isInt(n: any): boolean {
|
||||
return Number(n) === n && n % 1 === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка, является ли переменная вещественным числом
|
||||
* @param {any} n Проверяемая переменная
|
||||
* @return {boolean} Результат проверки
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
export function isFloat(n: any): boolean {
|
||||
return Number(n) === n && n % 1 !== 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Тег литеральной функции, в которой вещественные числа будут
|
||||
* печататься с 2 точками после запятой
|
||||
* @param {TemplateStringsArray} strings
|
||||
* @param {any[]} values
|
||||
* @return {string}
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
export function d2(strings: TemplateStringsArray, ...values: any[]): string {
|
||||
let result = '';
|
||||
for (let i = 0; i < strings.length; i++) {
|
||||
result += strings[i];
|
||||
if (i < values.length) {
|
||||
result += isFloat(values[i]) ? values[i].toFixed(2) : values[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="april-fools__joke__fresh-grass">
|
||||
<div class="april-fools__joke__fresh-grass__window">
|
||||
<h4>А ты трогал сегодня траву?</h4>
|
||||
|
||||
<img class="april-fools__joke__fresh-grass__image"
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/13/Green_Grass.JPG/1280px-Green_Grass.JPG"
|
||||
alt="Здесь должная быть трава, но что-то сломалось... Притворись, что она здесь есть, хорошо?">
|
||||
|
||||
<small>
|
||||
Изображение травы взято с
|
||||
<a href="https://commons.wikimedia.org/wiki/File:Green_Grass.JPG">WikiMedia</a>
|
||||
под лицензией
|
||||
<a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0 DEED</a>.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -11,15 +11,19 @@
|
||||
transition: 0.4s all;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.april-fools__button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.april-fools__joke-row {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
|
||||
.april-fools__joke-row:first-of-type {
|
||||
border-top: 1px solid black;
|
||||
}
|
||||
|
||||
.april-fools__joke {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
@@ -30,9 +34,11 @@
|
||||
gap: 1ch;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.april-fools__joke:hover {
|
||||
text-shadow: 0 0 black;
|
||||
}
|
||||
|
||||
.april-fools__joke:before {
|
||||
display: block;
|
||||
flex: 0 0 2ch;
|
||||
@@ -40,26 +46,84 @@
|
||||
content: "✔";
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
input.april-fools__joke-input:checked + .april-fools__joke {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
input.april-fools__joke-input:checked + .april-fools__joke:before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.april-fools__joke .april-fools__joke-title {
|
||||
flex: 1 auto;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.april-fools__joke .april-fools__joke-description {
|
||||
flex: 1 auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.april-fools__joke .april-fools__joke-chance {
|
||||
flex: 0 0 4ch;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.april-fools__content .april-fools__reset-button {
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
/* Шутка "Свежая трава" */
|
||||
html:has(.april-fools__joke__fresh-grass),
|
||||
body:has(.april-fools__joke__fresh-grass) {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: var(--fresh-grass-body-top);
|
||||
}
|
||||
|
||||
.april-fools__joke__fresh-grass {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #008000C0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.april-fools__joke__fresh-grass__window {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.april-fools__joke__fresh-grass__window h4 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
color: #FFFFFF;
|
||||
margin: 1vh 0;
|
||||
font-size: 2vh;
|
||||
line-height: 2vh;
|
||||
text-align: center;
|
||||
}
|
||||
.april-fools__joke__fresh-grass__window small {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.april-fools__joke__fresh-grass__window img {
|
||||
display: block;
|
||||
max-width: 10vw;
|
||||
max-height: 10vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
import '../types/html.d';
|
||||
import {Joke, JokeSettings} from '../joke';
|
||||
import $ from 'jquery';
|
||||
import containerHTML from '../html/joke-fresh_glass.html';
|
||||
import {Vector} from '../lib/vector';
|
||||
import {debugEnabled} from '../common';
|
||||
|
||||
/**
|
||||
* **Свежая трава**
|
||||
*
|
||||
* Предложение потрогать траву
|
||||
* @author Идея: Magia
|
||||
* @author Kozhilya
|
||||
*/
|
||||
export class FreshGrassJoke extends Joke<FreshGrassJokeSettings> {
|
||||
id = 'fresh_grass';
|
||||
|
||||
title = 'Свежая трава';
|
||||
|
||||
description = 'Предложение потрогать траву';
|
||||
|
||||
_settings = new FreshGrassJokeSettings();
|
||||
|
||||
container: JQuery<HTMLDivElement>;
|
||||
|
||||
intervalIndex: NodeJS.Timeout;
|
||||
|
||||
movement: Movement;
|
||||
|
||||
/**
|
||||
* Запуск шутки
|
||||
*/
|
||||
start(): void {
|
||||
$(() => this.init());
|
||||
}
|
||||
|
||||
/**
|
||||
* Основная загрузка шутки
|
||||
*/
|
||||
init() {
|
||||
const $body = $(document.body);
|
||||
|
||||
document.body.style.setProperty(
|
||||
'--fresh-grass-body-top',
|
||||
`-${window.scrollY}px`,
|
||||
);
|
||||
|
||||
this.container = $(containerHTML);
|
||||
this.container.hide(0);
|
||||
$body.append(this.container);
|
||||
this.container.fadeIn(500);
|
||||
|
||||
this.fleeingGrass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Отладка
|
||||
* @param {string} str
|
||||
*/
|
||||
debug(str: string): void {
|
||||
if (!debugEnabled) return;
|
||||
|
||||
$('small', this.container).html(`<pre>${str}</pre>`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Анимация убегания травы от пользователя
|
||||
*/
|
||||
fleeingGrass(): void {
|
||||
this.movement = new Movement(this.settings);
|
||||
|
||||
const $img = $('img.april-fools__joke__fresh-grass__image', this.container);
|
||||
|
||||
$img.on('mousedown', () => {
|
||||
this.close();
|
||||
});
|
||||
|
||||
$(window).on('mousemove', (e) => {
|
||||
this.movement.mouse = new Vector(e.clientX - window.innerWidth / 2,
|
||||
e.clientY - window.innerHeight / 2);
|
||||
});
|
||||
|
||||
this.intervalIndex = setInterval(() => {
|
||||
const img = $img[0].getBoundingClientRect();
|
||||
this.movement.step(img);
|
||||
|
||||
// this.debug(this.movement.debugLines.join('\n'));
|
||||
|
||||
$img.css('transform', this.movement.state.position.toTransform());
|
||||
}, Math.floor(1000 * this.movement.dt));
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрыть окно
|
||||
*/
|
||||
close(): void {
|
||||
clearInterval(this.intervalIndex);
|
||||
|
||||
this.container.fadeOut({
|
||||
duration: 500,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
complete: () => {
|
||||
this.container.remove();
|
||||
|
||||
scrollTo(0,
|
||||
-parseInt(document.body.style.getPropertyValue(
|
||||
'--fresh-grass-body-top')));
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type MovementData = {
|
||||
position: Vector;
|
||||
velocity: Vector;
|
||||
acceleration: Vector;
|
||||
};
|
||||
|
||||
/**
|
||||
* Класс обработки движения
|
||||
*/
|
||||
class Movement {
|
||||
settings: FreshGrassJokeSettings;
|
||||
state: MovementData;
|
||||
mouse: Vector;
|
||||
dt: number;
|
||||
last: MovementData;
|
||||
debugLines: string[];
|
||||
forces: { [key in string]: Vector };
|
||||
|
||||
/**
|
||||
* Конструктор
|
||||
* @param {FreshGrassJokeSettings} settings
|
||||
*/
|
||||
constructor(settings: FreshGrassJokeSettings) {
|
||||
this.settings = settings;
|
||||
this.state = {
|
||||
position: new Vector(),
|
||||
velocity: Vector.fromLengthAngle(Math.random() * 2 * Math.PI,
|
||||
Math.random() *
|
||||
(this.settings.maxVelocity - this.settings.minVelocity) +
|
||||
this.settings.minVelocity),
|
||||
acceleration: new Vector(),
|
||||
};
|
||||
this.mouse = new Vector();
|
||||
this.dt = 1.0 / this.settings.tickPerSecond;
|
||||
}
|
||||
|
||||
/**
|
||||
* Координата движения
|
||||
*/
|
||||
get position() {
|
||||
return this.state.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Скорость движения
|
||||
*/
|
||||
get velocity() {
|
||||
return this.state.velocity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ускорение движения
|
||||
*/
|
||||
get acceleration() {
|
||||
return this.state.acceleration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Произвести вычисление
|
||||
* @param {DOMRect} img Изображение
|
||||
*/
|
||||
step(img: DOMRect) {
|
||||
this.last = this.state;
|
||||
this.debugLines = [];
|
||||
this.forces = {};
|
||||
|
||||
// Обработка сил
|
||||
this.processForces();
|
||||
this.processMovement();
|
||||
this.processCollisions(img);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка сил
|
||||
*/
|
||||
processForces() {
|
||||
// Притяжение к центру
|
||||
this.forces.gravity = Vector.fromLengthAngle(
|
||||
this.last.position.angle + Math.PI,
|
||||
this.settings.gravity,
|
||||
);
|
||||
|
||||
// Коррекция скорости
|
||||
if (this.last.velocity.length < this.settings.minVelocity) {
|
||||
this.forces.correction = Vector.fromLengthAngle(
|
||||
this.last.velocity.angle,
|
||||
this.settings.correctionForce *
|
||||
Math.pow(this.last.velocity.length - this.settings.minVelocity, 2),
|
||||
);
|
||||
} else if (this.last.velocity.length > this.settings.maxVelocity) {
|
||||
this.forces.correction = Vector.fromLengthAngle(
|
||||
this.last.velocity.angle + Math.PI,
|
||||
this.settings.correctionForce *
|
||||
Math.pow(this.last.velocity.length - this.settings.maxVelocity, 2),
|
||||
);
|
||||
}
|
||||
|
||||
// Отталкивание от мыши
|
||||
const toMouseVector = this.mouse.add(this.last.position.reverse());
|
||||
if (toMouseVector.length < this.settings.keepDistance &&
|
||||
toMouseVector.length > 0) {
|
||||
this.forces.running = Vector.fromLengthAngle(
|
||||
toMouseVector.angle + Math.PI,
|
||||
this.settings.mouseForceMax -
|
||||
Math.pow(
|
||||
Math.abs(toMouseVector.length / this.settings.keepDistance - 1),
|
||||
this.settings.mouseForcePow),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка движения
|
||||
*/
|
||||
processMovement() {
|
||||
// Ускорение
|
||||
this.debugLines.push('Forces:');
|
||||
let acceleration: Vector = new Vector();
|
||||
|
||||
for (const [name, force] of Object.entries(this.forces)) {
|
||||
acceleration = acceleration.add(force);
|
||||
this.debugLines.push(` [${name}] ${force}`);
|
||||
}
|
||||
|
||||
acceleration = acceleration.multiply(1 / this.settings.mass);
|
||||
|
||||
this.state = {
|
||||
position: this.last.position,
|
||||
velocity: this.last.velocity,
|
||||
acceleration: acceleration,
|
||||
};
|
||||
|
||||
this.state.velocity = this.velocity.add(
|
||||
this.acceleration.multiply(this.dt));
|
||||
this.state.position = this.position.add(
|
||||
this.velocity.multiply(this.dt));
|
||||
|
||||
this.debugLines.push(`acceleration: ${this.acceleration}`);
|
||||
this.debugLines.push(`velocity: ${this.velocity}`);
|
||||
this.debugLines.push(`position: ${this.position}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка столкновения с границами окна
|
||||
* @param {DOMRect} img Изображение
|
||||
*/
|
||||
processCollisions(img: DOMRect) {
|
||||
const limitX = (window.innerWidth - img.width) / 2;
|
||||
const limitY = (window.innerHeight - img.height) / 2;
|
||||
|
||||
if (this.position.x < -limitX) {
|
||||
// Left
|
||||
this.position.x = -2 * limitX - this.position.x;
|
||||
this.velocity.x = -this.velocity.x * this.settings.collisionElasticity;
|
||||
}
|
||||
|
||||
if (this.position.x > limitX) {
|
||||
// Right
|
||||
this.position.x = 2 * limitX - this.position.x;
|
||||
this.velocity.x = -this.velocity.x * this.settings.collisionElasticity;
|
||||
}
|
||||
|
||||
if (this.position.y < -limitY) {
|
||||
// Top
|
||||
this.position.y = -2 * limitY - this.position.y;
|
||||
this.velocity.y = -this.velocity.y * this.settings.collisionElasticity;
|
||||
}
|
||||
|
||||
if (this.position.y > limitY) {
|
||||
// Bottom
|
||||
this.position.y = 2 * limitY - this.position.y;
|
||||
this.velocity.y = -this.velocity.y * this.settings.collisionElasticity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Нормально распределённая случайная величина
|
||||
* @param {number} mean
|
||||
* @param {number} stdev
|
||||
* @return {number}
|
||||
* @private
|
||||
*/
|
||||
private randomGaussian(mean: number = 0, stdev: number = 1): number {
|
||||
const u = 1 - Math.random(); // Converting [0,1) to (0,1]
|
||||
const v = Math.random();
|
||||
const z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
|
||||
// Transform to the desired mean and standard deviation:
|
||||
return z * stdev + mean;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Класс настроек для шутки "Свежая трава"
|
||||
*/
|
||||
export class FreshGrassJokeSettings implements JokeSettings {
|
||||
enabled: boolean = true;
|
||||
|
||||
chance: number = 100;
|
||||
|
||||
/**
|
||||
* Расстояние, на котором трава начнёт убегать
|
||||
*/
|
||||
keepDistance: number = 200;
|
||||
|
||||
/**
|
||||
* Максимальная скорость картинки (в пикселях в секунду)
|
||||
*/
|
||||
maxVelocity: number = 500;
|
||||
|
||||
/**
|
||||
* Минимальная скорость картинки (в пикселях в секунду)
|
||||
*/
|
||||
minVelocity: number = 20;
|
||||
|
||||
/**
|
||||
* Количество отметок времени за секунду
|
||||
*/
|
||||
tickPerSecond: number = 50;
|
||||
|
||||
/**
|
||||
* Сила притяжения в центр
|
||||
*/
|
||||
gravity: number = 20;
|
||||
|
||||
/**
|
||||
* Сила исправления скорости
|
||||
*/
|
||||
correctionForce: number = 0.8;
|
||||
|
||||
/**
|
||||
* Максимальная сила отталкивания от курсора
|
||||
*/
|
||||
mouseForceMax: number = 5000;
|
||||
|
||||
/**
|
||||
* Степень увеличения силы отталкивания от курсора
|
||||
*/
|
||||
mouseForcePow: number = 5;
|
||||
|
||||
/**
|
||||
* Общая масса картинки
|
||||
*/
|
||||
mass: number = 1;
|
||||
|
||||
/**
|
||||
* Доля скорости, сохраняемой при столкновении со стеной
|
||||
*/
|
||||
collisionElasticity: number = 0.9;
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import {d2} from '../common';
|
||||
|
||||
/**
|
||||
* Класс вектора
|
||||
*/
|
||||
export class Vector {
|
||||
/**
|
||||
* Координата X
|
||||
*/
|
||||
x: number;
|
||||
|
||||
/**
|
||||
* Координата Y
|
||||
*/
|
||||
y: number;
|
||||
|
||||
/**
|
||||
* Конструктор вектора
|
||||
* @param {number} x Координата X
|
||||
* @param {number} y Координата Y
|
||||
*/
|
||||
constructor(x: number = 0, y: number = 0) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Угол наклона вектора
|
||||
*/
|
||||
get angle() {
|
||||
return Math.atan2(this.y, this.x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Угол наклона вектора
|
||||
* @param {number} angle
|
||||
*/
|
||||
set angle(angle: number) {
|
||||
const length = this.length;
|
||||
this.x = Math.cos(angle) * length;
|
||||
this.y = Math.sin(angle) * length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Длина вектора
|
||||
*/
|
||||
get length() {
|
||||
return Math.distance(this.x, this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Длина вектора
|
||||
* @param {number} length
|
||||
*/
|
||||
set length(length: number) {
|
||||
const angle = this.angle;
|
||||
this.x = Math.cos(angle) * length;
|
||||
this.y = Math.sin(angle) * length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сумма векторов
|
||||
* @param {Vector} a Первый вектор
|
||||
* @param {Vector} b Второй вектор
|
||||
* @return {Vector} Вектор суммы
|
||||
*/
|
||||
static addVectors(a: Vector, b: Vector): Vector {
|
||||
return new Vector(a.x + b.x, a.y + b.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Скалярное умножение векторов
|
||||
* @param {Vector} a Первый вектор
|
||||
* @param {Vector} b Второй вектор
|
||||
* @return {number} Скалярное произведение векторов
|
||||
*/
|
||||
static dotProduct(a: Vector, b: Vector): number {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать вектор по углу наклона и длине
|
||||
* @param {number} angle Угол наклона
|
||||
* @param {number} length Длина
|
||||
* @return {Vector} Вектор суммы
|
||||
*/
|
||||
static fromLengthAngle(angle: number, length: number): Vector {
|
||||
const v = new Vector();
|
||||
v.length = length;
|
||||
v.angle = angle;
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Нахождение противоположного вектора
|
||||
* @return {Vector} Противоположный вектор
|
||||
*/
|
||||
reverse(): Vector {
|
||||
return new Vector(-this.x, -this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Прибавить вектор
|
||||
* @param {Vector} a Второе слагаемое
|
||||
* @return {Vector} Вектор суммы
|
||||
*/
|
||||
add(a: Vector): Vector {
|
||||
return Vector.addVectors(this, a);
|
||||
}
|
||||
|
||||
/**
|
||||
* Умножение вектора на число
|
||||
* @param {number} a Скаляр
|
||||
* @return {Vector} Вектор-результат
|
||||
*/
|
||||
multiply(a: number): Vector {
|
||||
return new Vector(a * this.x, a * this.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразование в CSS-преобразование
|
||||
* @return {string} CSS-преобразование
|
||||
*/
|
||||
toTransform(): string {
|
||||
return `translate(${this.x}px, ${this.y}px)`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Строковое представление
|
||||
* @return {string}
|
||||
*/
|
||||
toString(): string {
|
||||
return d2`Vector(x=${this.x}, y=${this.y}, ` +
|
||||
d2`length=${this.length}, angle=${this.angle})`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Math {
|
||||
/**
|
||||
* Расчёт длины вектора
|
||||
* @param {number} x Координата X вектора
|
||||
* @param {number} y Координата X вектора
|
||||
* @return {number} Длина вектора
|
||||
*/
|
||||
distance(x: number, y: number): number;
|
||||
}
|
||||
}
|
||||
|
||||
Math.distance = (x, y) => Math.sqrt(x * x + y * y);
|
||||
@@ -10,6 +10,7 @@ import {CursorEffectsJoke} from './jokes/cursor';
|
||||
import {StupidTitleJoke} from './jokes/stupid_title';
|
||||
import {InvertedScrollJoke} from './jokes/inverted_scroll';
|
||||
import {LatinizationJoke} from './jokes/latinization';
|
||||
import {FreshGrassJoke} from './jokes/fresh_grass';
|
||||
|
||||
declare global {
|
||||
interface Window { AprilJokes: JokerClass; }
|
||||
@@ -36,6 +37,7 @@ window.AprilJokes = ((jokes: JokeList): JokerClass => {
|
||||
StupidTitleJoke,
|
||||
InvertedScrollJoke,
|
||||
LatinizationJoke,
|
||||
FreshGrassJoke,
|
||||
]);
|
||||
|
||||
interface JokeList extends Array<new () => Joke<JokeSettings>> {}
|
||||
|
||||
Reference in New Issue
Block a user