626 lines
20 KiB
JavaScript
626 lines
20 KiB
JavaScript
// 定义一个表示二维坐标点的类
|
||
class Point {
|
||
// 构造函数,设置x和y坐标,默认值为0
|
||
constructor(x = 0, y = 0) {
|
||
this.x = x; // 设置x坐标
|
||
this.y = y; // 设置y坐标
|
||
}
|
||
|
||
// 克隆当前点,返回一个新的Point对象
|
||
clone() {
|
||
return new Point(this.x, this.y); // 使用当前坐标创建新点
|
||
}
|
||
|
||
// 点加法,将另一个点的坐标加到当前点
|
||
add(o) {
|
||
const p = this.clone(); // 创建当前点的副本
|
||
p.x += o.x; // x坐标相加
|
||
p.y += o.y; // y坐标相加
|
||
return p; // 返回新点
|
||
}
|
||
|
||
// 点减法,从当前点减去另一个点的坐标
|
||
sub(o) {
|
||
const p = this.clone(); // 创建当前点的副本
|
||
p.x -= o.x; // x坐标相减
|
||
p.y -= o.y; // y坐标相减
|
||
return p; // 返回新点
|
||
}
|
||
|
||
// 点除法,将当前点坐标除以一个数
|
||
div(n) {
|
||
const p = this.clone(); // 创建当前点的副本
|
||
p.x /= n; // x坐标除以n
|
||
p.y /= n; // y坐标除以n
|
||
return p; // 返回新点
|
||
}
|
||
|
||
// 点乘法,将当前点坐标乘以一个数
|
||
mul(n) {
|
||
const p = this.clone(); // 创建当前点的副本
|
||
p.x *= n; // x坐标乘以n
|
||
p.y *= n; // y坐标乘以n
|
||
return p; // 返回新点
|
||
}
|
||
}
|
||
|
||
// 心形曲线类
|
||
class Heart {
|
||
constructor() {
|
||
this.points = []; // 存储心形曲线上的点
|
||
// 使用参数方程生成心形曲线
|
||
for (let i = 10; i < 30; i += 0.2) {
|
||
const t = i / Math.PI; // 参数t
|
||
// 心形曲线的x坐标公式
|
||
const x = 16 * Math.pow(Math.sin(t), 3);
|
||
// 心形曲线的y坐标公式
|
||
const y = 13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t);
|
||
this.points.push(new Point(x, y)); // 将点添加到数组中
|
||
}
|
||
this.length = this.points.length; // 存储点的数量
|
||
}
|
||
|
||
// 获取指定索引的点,并可选择缩放
|
||
get(i, scale = 1) {
|
||
return this.points[i].mul(scale); // 返回缩放后的点
|
||
}
|
||
}
|
||
|
||
// 种子类,代表树的核心元素
|
||
class Seed {
|
||
constructor(tree, point, scale = 1, color = '#FF0000') {
|
||
this.tree = tree; // 关联的树对象
|
||
this.heart = { // 心形配置
|
||
point: point, // 位置点
|
||
scale: scale, // 缩放比例
|
||
color: color, // 颜色
|
||
figure: new Heart(), // 心形图形
|
||
};
|
||
this.cirle = { // 圆形配置
|
||
point: point, // 位置点
|
||
scale: scale, // 缩放比例
|
||
color: color, // 颜色
|
||
radius: 5, // 半径
|
||
};
|
||
}
|
||
|
||
// 绘制种子
|
||
draw() {
|
||
this.drawHeart(); // 绘制心形
|
||
this.drawText(); // 绘制文字
|
||
}
|
||
|
||
// 添加位置偏移
|
||
addPosition(x, y) {
|
||
this.cirle.point = this.cirle.point.add(new Point(x, y)); // 更新圆形位置
|
||
}
|
||
|
||
// 检查是否可以移动
|
||
canMove() {
|
||
return this.cirle.point.y < (this.tree.height + 20); // 检查是否在树高度范围内
|
||
}
|
||
|
||
// 移动种子
|
||
move(x, y) {
|
||
this.clear(); // 清除原有绘制
|
||
this.drawCirle(); // 绘制圆形
|
||
this.addPosition(x, y); // 更新位置
|
||
}
|
||
|
||
// 检查是否可以缩放
|
||
canScale() {
|
||
return this.heart.scale > 0.2; // 检查缩放比例是否大于0.2
|
||
}
|
||
|
||
// 设置心形缩放比例
|
||
setHeartScale(scale) {
|
||
this.heart.scale *= scale; // 更新缩放比例
|
||
}
|
||
|
||
// 缩放种子
|
||
scale(scale) {
|
||
this.clear(); // 清除原有绘制
|
||
this.drawCirle(); // 绘制圆形
|
||
this.drawHeart(); // 绘制心形
|
||
this.setHeartScale(scale); // 更新缩放比例
|
||
}
|
||
|
||
// 绘制心形
|
||
drawHeart() {
|
||
const { ctx } = this.tree; // 获取画布上下文
|
||
const { point, color, scale, figure } = this.heart; // 解构心形配置
|
||
|
||
ctx.save(); // 保存当前绘图状态
|
||
ctx.fillStyle = color; // 设置填充颜色
|
||
ctx.translate(point.x, point.y); // 移动坐标系到心形中心
|
||
ctx.beginPath(); // 开始绘制路径
|
||
ctx.moveTo(0, 0); // 移动到起点
|
||
|
||
// 绘制心形曲线
|
||
for (let i = 0; i < figure.length; i++) {
|
||
const p = figure.get(i, scale); // 获取缩放后的点
|
||
ctx.lineTo(p.x, -p.y); // 绘制线到该点(y坐标取反)
|
||
}
|
||
|
||
ctx.closePath(); // 闭合路径
|
||
ctx.fill(); // 填充心形
|
||
ctx.restore(); // 恢复绘图状态
|
||
}
|
||
|
||
// 绘制圆形
|
||
drawCirle() {
|
||
const { ctx } = this.tree; // 获取画布上下文
|
||
const { point, color, scale, radius } = this.cirle; // 解构圆形配置
|
||
|
||
ctx.save(); // 保存当前绘图状态
|
||
ctx.fillStyle = color; // 设置填充颜色
|
||
ctx.translate(point.x, point.y); // 移动坐标系到圆心
|
||
ctx.scale(scale, scale); // 应用缩放
|
||
ctx.beginPath(); // 开始绘制路径
|
||
ctx.moveTo(0, 0); // 移动到圆心
|
||
ctx.arc(0, 0, radius, 0, 2 * Math.PI); // 绘制圆形
|
||
ctx.closePath(); // 闭合路径
|
||
ctx.fill(); // 填充圆形
|
||
ctx.restore(); // 恢复绘图状态
|
||
}
|
||
|
||
// 绘制文字
|
||
drawText() {
|
||
const { ctx } = this.tree; // 获取画布上下文
|
||
const { point, color, scale } = this.heart; // 解构心形配置
|
||
|
||
ctx.save(); // 保存当前绘图状态
|
||
ctx.strokeStyle = color; // 设置描边颜色
|
||
ctx.fillStyle = color; // 设置填充颜色
|
||
ctx.translate(point.x, point.y); // 移动坐标系到文字起点
|
||
ctx.scale(scale, scale); // 应用缩放
|
||
ctx.moveTo(0, 0); // 移动到起点
|
||
ctx.lineTo(15, 15); // 绘制第一条线
|
||
ctx.lineTo(60, 15); // 绘制第二条线
|
||
ctx.stroke(); // 描边路径
|
||
|
||
ctx.moveTo(0, 0); // 移动到新起点
|
||
ctx.scale(0.75, 0.75); // 调整缩放比例
|
||
ctx.font = "14px 微软雅黑,Verdana"; // 设置字体
|
||
ctx.fillText(" [ 点爱心 ]", 23, 10); // 绘制文字
|
||
ctx.restore(); // 恢复绘图状态
|
||
}
|
||
|
||
// 清除绘制
|
||
clear() {
|
||
let h = 0;
|
||
const { ctx } = this.tree; // 获取画布上下文
|
||
const { point, scale } = this.cirle; // 解构圆形配置
|
||
const radius = 26; // 清除区域半径
|
||
const w = h = (radius * scale); // 计算清除区域宽高
|
||
ctx.clearRect(point.x - w, point.y - h, 4 * w, 4 * h); // 清除矩形区域
|
||
}
|
||
|
||
// 检查鼠标是否悬停在元素上
|
||
hover(x, y) {
|
||
const { ctx } = this.tree; // 获取画布上下文
|
||
const pixel = ctx.getImageData(x, y, 1, 1); // 获取像素数据
|
||
return pixel.data[3] === 255; // 检查alpha通道是否为255(不透明)
|
||
}
|
||
}
|
||
|
||
// 页脚类
|
||
class Footer {
|
||
constructor(tree, width, height, speed = 2) {
|
||
this.tree = tree; // 关联的树对象
|
||
// 初始化位置点(位于树底部中间)
|
||
this.point = new Point(tree.seed.heart.point.x, tree.height - height / 2);
|
||
this.width = width; // 页脚宽度
|
||
this.height = height; // 页脚高度
|
||
this.speed = speed; // 绘制速度
|
||
this.length = 0; // 当前绘制长度
|
||
}
|
||
|
||
// 绘制页脚
|
||
draw() {
|
||
const { ctx } = this.tree; // 获取画布上下文
|
||
const { point } = this; // 解构位置点
|
||
const len = this.length / 2; // 计算当前线长的一半
|
||
|
||
ctx.save(); // 保存当前绘图状态
|
||
ctx.strokeStyle = 'rgb(35, 31, 32)'; // 设置描边颜色
|
||
ctx.lineWidth = this.height; // 设置线宽
|
||
ctx.lineCap = 'round'; // 设置线端样式
|
||
ctx.lineJoin = 'round'; // 设置线连接样式
|
||
ctx.translate(point.x, point.y); // 移动坐标系到中心点
|
||
ctx.beginPath(); // 开始绘制路径
|
||
ctx.moveTo(0, 0); // 移动到中心
|
||
ctx.lineTo(len, 0); // 向右绘制
|
||
ctx.lineTo(-len, 0); // 向左绘制
|
||
ctx.stroke(); // 描边路径
|
||
ctx.restore(); // 恢复绘图状态
|
||
|
||
// 如果当前长度小于总宽度,继续增长
|
||
if (this.length < this.width) {
|
||
this.length += this.speed; // 增加长度
|
||
}
|
||
}
|
||
}
|
||
|
||
// 树枝类
|
||
class Branch {
|
||
constructor(tree, point1, point2, point3, radius, length = 100, branchs = []) {
|
||
this.tree = tree; // 关联的树对象
|
||
this.point1 = point1; // 贝塞尔曲线起点
|
||
this.point2 = point2; // 贝塞尔曲线控制点
|
||
this.point3 = point3; // 贝塞尔曲线终点
|
||
this.radius = radius; // 树枝半径
|
||
this.length = length; // 树枝长度
|
||
this.len = 0; // 当前绘制长度
|
||
this.t = 1 / (this.length - 1); // 计算步长
|
||
this.branchs = branchs; // 子树枝数组
|
||
}
|
||
|
||
// 树枝生长
|
||
grow() {
|
||
if (this.len <= this.length) { // 如果还没长到最大长度
|
||
// 计算贝塞尔曲线上的点
|
||
const p = bezier([this.point1, this.point2, this.point3], this.len * this.t);
|
||
this.draw(p); // 绘制当前点
|
||
this.len += 1; // 增加长度
|
||
this.radius *= 0.97; // 半径逐渐减小
|
||
} else { // 如果已经长到最大长度
|
||
this.tree.removeBranch(this); // 从树中移除当前树枝
|
||
this.tree.addBranchs(this.branchs); // 添加子树枝
|
||
}
|
||
}
|
||
|
||
// 绘制树枝上的一个点
|
||
draw(p) {
|
||
const { ctx } = this.tree; // 获取画布上下文
|
||
|
||
ctx.save(); // 保存当前绘图状态
|
||
ctx.beginPath(); // 开始绘制路径
|
||
ctx.fillStyle = 'rgb(35, 31, 32)'; // 设置填充颜色
|
||
ctx.shadowColor = 'rgb(31, 35, 31)'; // 设置阴影颜色
|
||
ctx.shadowBlur = 2; // 设置阴影模糊度
|
||
ctx.moveTo(p.x, p.y); // 移动到指定点
|
||
ctx.arc(p.x, p.y, this.radius, 0, 2 * Math.PI); // 绘制圆形
|
||
ctx.closePath(); // 闭合路径
|
||
ctx.fill(); // 填充圆形
|
||
ctx.restore(); // 恢复绘图状态
|
||
}
|
||
}
|
||
|
||
// 花朵类
|
||
class Bloom {
|
||
constructor(tree, point, figure, color = null, alpha = null, angle = null, scale = 0.1, place = null, speed = null) {
|
||
this.tree = tree; // 关联的树对象
|
||
this.point = point; // 花朵位置
|
||
// 设置颜色,如果没有则随机生成
|
||
this.color = color || `rgb(255,${random(0, 255)},${random(0, 255)})`;
|
||
// 设置透明度,如果没有则随机生成(0.3-1)
|
||
this.alpha = alpha || random(0.3, 1);
|
||
// 设置角度,如果没有则随机生成(0-360度)
|
||
this.angle = angle || random(0, 360);
|
||
this.scale = scale; // 缩放比例
|
||
this.place = place; // 目标位置(用于跳跃动画)
|
||
this.speed = speed; // 速度(用于跳跃动画)
|
||
this.figure = figure; // 花朵形状
|
||
}
|
||
|
||
// 设置花朵形状
|
||
setFigure(figure) {
|
||
this.figure = figure; // 更新形状
|
||
}
|
||
|
||
// 花朵绽放动画
|
||
flower() {
|
||
this.draw(); // 绘制花朵
|
||
this.scale += 0.1; // 增加缩放比例
|
||
if (this.scale > 1) { // 如果完全绽放
|
||
this.tree.removeBloom(this); // 从树中移除
|
||
}
|
||
}
|
||
|
||
// 绘制花朵
|
||
draw() {
|
||
const { ctx } = this.tree; // 获取画布上下文
|
||
|
||
ctx.save(); // 保存当前绘图状态
|
||
ctx.fillStyle = this.color; // 设置填充颜色
|
||
ctx.globalAlpha = this.alpha; // 设置全局透明度
|
||
ctx.translate(this.point.x, this.point.y); // 移动坐标系到花朵中心
|
||
ctx.scale(this.scale, this.scale); // 应用缩放
|
||
ctx.rotate(this.angle); // 应用旋转
|
||
ctx.beginPath(); // 开始绘制路径
|
||
ctx.moveTo(0, 0); // 移动到中心
|
||
|
||
// 绘制花朵形状
|
||
for (let i = 0; i < this.figure.length; i++) {
|
||
const p = this.figure.get(i); // 获取形状上的点
|
||
ctx.lineTo(p.x, -p.y); // 绘制线到该点(y坐标取反)
|
||
}
|
||
|
||
ctx.closePath(); // 闭合路径
|
||
ctx.fill(); // 填充形状
|
||
ctx.restore(); // 恢复绘图状态
|
||
}
|
||
|
||
// 花朵跳跃动画
|
||
jump() {
|
||
const { height } = this.tree; // 获取树的高度
|
||
|
||
// 如果花朵超出边界,则移除
|
||
if (this.point.x < -20 || this.point.y > height + 20) {
|
||
this.tree.removeBloom(this);
|
||
} else {
|
||
this.draw(); // 绘制花朵
|
||
// 计算新位置(向目标位置移动)
|
||
this.point = this.place.sub(this.point).div(this.speed).add(this.point);
|
||
this.angle += 0.05; // 增加旋转角度
|
||
this.speed -= 1; // 减小速度
|
||
}
|
||
}
|
||
}
|
||
|
||
// 树类
|
||
class Tree {
|
||
constructor(canvas, width, height, opt = {}) {
|
||
this.canvas = canvas; // 画布元素
|
||
this.ctx = canvas.getContext('2d'); // 画布上下文
|
||
this.width = width; // 画布宽度
|
||
this.height = height; // 画布高度
|
||
this.opt = opt; // 配置选项
|
||
this.record = {}; // 记录快照
|
||
|
||
// 初始化各个组件
|
||
this.initSeed(); // 初始化种子
|
||
this.initFooter(); // 初始化页脚
|
||
this.initBranch(); // 初始化树枝
|
||
this.initBloom(); // 初始化花朵
|
||
}
|
||
|
||
// 初始化种子
|
||
initSeed() {
|
||
const { seed = {} } = this.opt; // 获取种子配置
|
||
// 设置x坐标,默认为画布中心
|
||
const x = seed.x || this.width / 2;
|
||
// 设置y坐标,默认为画布中心
|
||
const y = seed.y || this.height / 2;
|
||
const point = new Point(x, y); // 创建位置点
|
||
const color = seed.color || '#FF0000'; // 设置颜色,默认为红色
|
||
const scale = seed.scale || 1; // 设置缩放比例,默认为1
|
||
|
||
this.seed = new Seed(this, point, scale, color); // 创建种子对象
|
||
}
|
||
|
||
// 初始化页脚
|
||
initFooter() {
|
||
const { footer = {} } = this.opt; // 获取页脚配置
|
||
const width = footer.width || this.width; // 设置宽度,默认为画布宽度
|
||
const height = footer.height || 5; // 设置高度,默认为5
|
||
const speed = footer.speed || 2; // 设置速度,默认为2
|
||
this.footer = new Footer(this, width, height, speed); // 创建页脚对象
|
||
}
|
||
|
||
// 初始化树枝
|
||
initBranch() {
|
||
const branchs = this.opt.branch || []; // 获取树枝配置
|
||
this.branchs = []; // 初始化树枝数组
|
||
this.addBranchs(branchs); // 添加树枝
|
||
}
|
||
|
||
// 初始化花朵
|
||
initBloom() {
|
||
const { bloom = {} } = this.opt; // 获取花朵配置
|
||
const cache = []; // 初始化花朵缓存
|
||
const num = bloom.num || 500; // 设置花朵数量,默认为500
|
||
const width = bloom.width || this.width; // 设置区域宽度
|
||
const height = bloom.height || this.height; // 设置区域高度
|
||
const figure = this.seed.heart.figure; // 获取心形图形
|
||
const r = 240; // 心形半径
|
||
|
||
// 创建指定数量的花朵
|
||
for (let i = 0; i < num; i++) {
|
||
cache.push(this.createBloom(width, height, r, figure));
|
||
}
|
||
|
||
this.blooms = []; // 初始化花朵数组
|
||
this.bloomsCache = cache; // 存储花朵缓存
|
||
}
|
||
|
||
// 将画布转换为数据URL
|
||
toDataURL(type) {
|
||
return this.canvas.toDataURL(type); // 返回数据URL
|
||
}
|
||
|
||
// 绘制记录的快照
|
||
draw(k) {
|
||
const rec = this.record[k]; // 获取指定键的记录
|
||
if (!rec) return; // 如果没有记录则返回
|
||
|
||
const { ctx } = this; // 获取画布上下文
|
||
const { point, image } = rec; // 解构位置点和图像数据
|
||
|
||
ctx.save(); // 保存当前绘图状态
|
||
ctx.putImageData(image, point.x, point.y); // 绘制图像数据
|
||
ctx.restore(); // 恢复绘图状态
|
||
}
|
||
|
||
// 添加单个树枝
|
||
addBranch(branch) {
|
||
this.branchs.push(branch); // 将树枝添加到数组
|
||
}
|
||
|
||
// 添加多个树枝
|
||
addBranchs(branchs) {
|
||
for (const b of branchs) { // 遍历树枝配置
|
||
// 创建三个控制点
|
||
const p1 = new Point(b[0], b[1]);
|
||
const p2 = new Point(b[2], b[3]);
|
||
const p3 = new Point(b[4], b[5]);
|
||
const r = b[6]; // 半径
|
||
const l = b[7]; // 长度
|
||
const c = b[8]; // 子树枝配置
|
||
// 创建树枝并添加到数组
|
||
this.addBranch(new Branch(this, p1, p2, p3, r, l, c));
|
||
}
|
||
}
|
||
|
||
// 移除树枝
|
||
removeBranch(branch) {
|
||
// 过滤掉指定的树枝
|
||
this.branchs = this.branchs.filter(b => b !== branch);
|
||
}
|
||
|
||
// 检查是否可以生长
|
||
canGrow() {
|
||
return this.branchs.length > 0; // 是否有树枝需要生长
|
||
}
|
||
|
||
// 树枝生长
|
||
grow() {
|
||
for (const branch of this.branchs) { // 遍历所有树枝
|
||
if (branch) {
|
||
branch.grow(); // 让树枝生长
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加花朵
|
||
addBloom(bloom) {
|
||
this.blooms.push(bloom); // 将花朵添加到数组
|
||
}
|
||
|
||
// 移除花朵
|
||
removeBloom(bloom) {
|
||
// 过滤掉指定的花朵
|
||
this.blooms = this.blooms.filter(b => b !== bloom);
|
||
}
|
||
|
||
// 创建花朵
|
||
createBloom(width, height, radius, figure, color, alpha, angle, scale, place, speed) {
|
||
let x, y;
|
||
while (true) { // 循环直到找到合适的位置
|
||
// 随机生成x,y坐标
|
||
x = random(20, width - 20);
|
||
y = random(20, height - 20);
|
||
// 检查是否在心形区域内
|
||
if (inheart(x - width / 2, height - (height - 40) / 2 - y, radius)) {
|
||
// 创建并返回花朵对象
|
||
return new Bloom(this, new Point(x, y), figure, color, alpha, angle, scale, place, speed);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 检查是否可以开花
|
||
canFlower() {
|
||
return this.blooms.length > 0; // 是否有花朵需要绽放
|
||
}
|
||
|
||
// 花朵绽放
|
||
flower(num) {
|
||
// 从缓存中取出指定数量的花朵
|
||
const blooms = this.bloomsCache.splice(0, num);
|
||
blooms.forEach(bloom => this.addBloom(bloom)); // 添加到花朵数组
|
||
|
||
// 让所有花朵绽放
|
||
this.blooms.forEach(bloom => bloom.flower());
|
||
}
|
||
|
||
// 拍摄快照
|
||
snapshot(k, x, y, width, height) {
|
||
// 获取指定区域的图像数据
|
||
const image = this.ctx.getImageData(x, y, width, height);
|
||
// 存储快照记录
|
||
this.record[k] = {
|
||
image, // 图像数据
|
||
point: new Point(x, y), // 位置点
|
||
width, // 宽度
|
||
height // 高度
|
||
};
|
||
}
|
||
|
||
// 设置移动速度
|
||
setSpeed(k, speed) {
|
||
this.record[k || "move"].speed = speed; // 更新速度
|
||
}
|
||
|
||
// 移动元素
|
||
move(k, x, y) {
|
||
const rec = this.record[k || "move"]; // 获取记录
|
||
const { ctx } = this; // 获取画布上下文
|
||
// 解构记录属性
|
||
const { point, image, speed = 10, width, height } = rec;
|
||
|
||
// 计算新位置(考虑速度)
|
||
const i = point.x + speed < x ? point.x + speed : x;
|
||
const j = point.y + speed < y ? point.y + speed : y;
|
||
|
||
ctx.save(); // 保存当前绘图状态
|
||
ctx.clearRect(point.x, point.y, width, height); // 清除原区域
|
||
ctx.putImageData(image, i, j); // 在新位置绘制图像
|
||
ctx.restore(); // 恢复绘图状态
|
||
|
||
rec.point = new Point(i, j); // 更新位置点
|
||
rec.speed = speed * 0.95; // 降低速度
|
||
|
||
if (rec.speed < 2) { // 确保最小速度
|
||
rec.speed = 2;
|
||
}
|
||
return i < x || j < y; // 返回是否到达目标
|
||
}
|
||
|
||
jump() {
|
||
this.blooms.forEach(bloom => bloom.jump()); // 让所有花朵跳跃
|
||
|
||
// TODO 这里可以修改花朵的数量
|
||
// 如果花朵数量不足,创建新的花朵
|
||
if ((this.blooms.length && this.blooms.length < 30) || !this.blooms.length) {
|
||
const { bloom = {} } = this.opt; // 获取花朵配置
|
||
const width = bloom.width || this.width; // 区域宽度
|
||
const height = bloom.height || this.height; // 区域高度
|
||
const figure = this.seed.heart.figure; // 心形图形
|
||
const r = 240; // 心形半径
|
||
|
||
// 随机创建1-2朵新花朵
|
||
for (let i = 0; i < random(1, 2); i++) {
|
||
this.blooms.push(this.createBloom(
|
||
width / 2 + width, // x范围扩大
|
||
height, // 高度
|
||
r, // 半径
|
||
figure, // 形状
|
||
null, 1, null, 1, // 默认颜色、透明度、角度、缩放
|
||
// 随机目标位置
|
||
new Point(random(-100, 600), 720),
|
||
random(200, 300) // 随机速度
|
||
));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
function random(min, max) {
|
||
return min + Math.floor(Math.random() * (max - min + 1));
|
||
}
|
||
|
||
// 工具函数 - 计算贝塞尔曲线上的点
|
||
function bezier(cp, t) {
|
||
// 计算三次贝塞尔曲线的点
|
||
const p1 = cp[0].mul((1 - t) * (1 - t)); // 第一项
|
||
const p2 = cp[1].mul(2 * t * (1 - t)); // 第二项
|
||
const p3 = cp[2].mul(t * t); // 第三项
|
||
return p1.add(p2).add(p3); // 返回总和
|
||
}
|
||
|
||
// 工具函数 - 检查点是否在心形区域内
|
||
function inheart(x, y, r) {
|
||
// 心形曲线方程
|
||
const z = ((x / r) * (x / r) + (y / r) * (y / r) - 1) *
|
||
((x / r) * (x / r) + (y / r) * (y / r) - 1) *
|
||
((x / r) * (x / r) + (y / r) * (y / r) - 1) -
|
||
(x / r) * (x / r) * (y / r) * (y / r) * (y / r);
|
||
return z < 0; // 返回是否在心形内
|
||
}
|
||
|
||
// 导出到全局对象
|
||
window.random = random; // 随机数函数
|
||
window.bezier = bezier; // 贝塞尔曲线函数
|
||
window.Point = Point; // Point类
|
||
window.Tree = Tree; // Tree类
|