diff --git a/src/plugin/layout/layout.ts b/src/plugin/layout/layout.ts new file mode 100644 index 0000000..8810e47 --- /dev/null +++ b/src/plugin/layout/layout.ts @@ -0,0 +1,269 @@ +type CanvasStyle = string | CanvasPattern | CanvasGradient; + +export class Layout { + /** 画布 */ + canvas: HTMLCanvasElement; + /** 绘制上下文 */ + ctx: CanvasRenderingContext2D; + + static readonly CLEAR: number = 1; + static readonly MASK: number = 2; + static readonly IMAGE: number = 4; + + static readonly FILL: number = 1; + static readonly STROKE: number = 2; + + constructor(canvas: HTMLCanvasElement) { + this.canvas = canvas; + this.ctx = canvas.getContext('2d')!; + } + + image(layout: Layout | HTMLCanvasElement | Path2D, type: number): Layout; + image( + layout: Layout | HTMLCanvasElement | Path2D, + type: number, + x: number, + y: number + ): Layout; + image( + layout: Layout | HTMLCanvasElement | Path2D, + type: number, + x: number, + y: number, + w: number, + h: number + ): Layout; + image( + layout: Layout | HTMLCanvasElement | Path2D, + type: number, + sx: number, + sy: number, + sw: number, + sh: number, + dx: number, + dy: number, + dw: number, + dh: number + ): Layout; + image( + layout: Layout | HTMLCanvasElement | Path2D, + type: number, + sx?: number, + sy?: number, + sw?: number, + sh?: number, + dx?: number, + dy?: number, + dw?: number, + dh?: number + ) { + return this; + } + + /** + * 绘制文字 + * @param str 文字 + * @param type 绘制类型,FILL表示填充,STROKE表示描边,FILL | STROKE 表示既填充又描边 + * @param x 横坐标 + * @param y 纵坐标 + * @param maxWidth 最大宽度 + */ + text( + str: string, + type: number, + x: number, + y: number, + maxWidth?: number + ): Layout { + return this; + } + + /** + * 根据路径进行绘制 + * @param path 路径 + * @param type 绘制类型 + */ + path(path: Path2D, type: number): Layout { + return this; + } + + /** + * 保存画布状态 + */ + save(): Layout { + return this; + } + + /** + * 回退画布状态 + */ + restore(): Layout { + return this; + } + + /** + * 设置填充样式 + * @param style 样式 + */ + fillStyle(style: CanvasStyle): Layout { + return this; + } + + /** + * 设置描边样式 + * @param style 样式 + */ + strokeStyle(style: CanvasStyle): Layout { + return this; + } + + /** + * 设置文本对齐 + * @param align 文本左右对齐方式 + */ + textAlign(align: CanvasTextAlign): Layout { + return this; + } + + /** + * 设置文本基线 + * @param align 文本基线,即文本上下对齐方式 + */ + textBaseline(align: CanvasTextBaseline): Layout { + return this; + } + + /** + * 设置滤镜 + * @param filter 滤镜 + */ + filter(filter: string): Layout { + return this; + } + + /** + * 设置阴影信息 + * @param shadow 阴影信息 + */ + shadow(shadow: Partial): Layout { + return this; + } + + /** + * 设置线宽(描边宽度,包括字体描边) + * @param width 宽度 + */ + lineWidth(width: number): Layout { + return this; + } + + /** + * 设置线尾样式 + * @param cap 线尾样式 + */ + lineCap(cap: CanvasLineCap): Layout { + return this; + } + + /** + * 设置线段连接方式样式 + * @param join 线段连接方式 + */ + lineJoin(join: CanvasLineJoin): Layout { + return this; + } + + /** + * 设置画布的字体 + * @param font 字体 + */ + font(font: string): Layout { + return this; + } + + /** + * 设置画布之后绘制的不透明度 + * @param alpha 不透明度 + */ + alpha(alpha: number): Layout { + return this; + } + + /** + * 设置虚线样式 + * @param dash 虚线样式 + */ + lineDash(dash: number[]): Layout { + return this; + } + + /** + * 放缩画布 + * @param x 横向放缩量 + * @param y 纵向放缩量 + */ + scale(x: number, y: number): Layout { + return this; + } + + /** + * 旋转画布 + * @param rad 顺时针旋转的弧度数 + */ + rotate(rad: number): Layout { + return this; + } + + /** + * 平移画布 + * @param x 水平平移量 + * @param y 竖直平移量 + */ + translate(x: number, y: number): Layout { + return this; + } + + /** + * 重设变换矩阵 + */ + transform(): Layout; + /** + * 叠加变换矩阵(当前画布的矩阵乘以传入的矩阵) + * 矩阵说明: + * [a c e] + * [b d f] + * [0 0 0] + * @param a 水平缩放 + * @param b 垂直倾斜 + * @param c 水平倾斜 + * @param d 垂直缩放 + * @param e 水平移动 + * @param f 垂直移动 + */ + transform( + a: number, + b: number, + c: number, + d: number, + e: number, + f: number + ): Layout; + transform( + a?: number, + b?: number, + c?: number, + d?: number, + e?: number, + f?: number + ) { + return this; + } + + /** + * 设置混合方式,像image的蒙版功能与擦除功能本质上也是通过设置混合方式实现的 + * @param value 混合方式 + */ + composite(value: GlobalCompositeOperation): Layout { + return this; + } +} diff --git a/src/plugin/particle/render.ts b/src/plugin/particle/render.ts index 88fef18..7094093 100644 --- a/src/plugin/particle/render.ts +++ b/src/plugin/particle/render.ts @@ -4,7 +4,7 @@ import { createProgram } from '../webgl/canvas'; import { Matrix4 } from '../webgl/matrix'; import { isWebGLSupported } from '../webgl/utils'; import { Camera, Position3D } from './camera'; -import { Particle, ParticleColor, ParticleOne } from './particle'; +import { Particle, ParticleColor } from './particle'; // 顶点着色器与片元着色器 // 很像C对吧(但这不是C,是glsl @@ -286,47 +286,3 @@ export class Renderer { throw new Error(`Your service or browser does not support webgl!`); } } - -window.addEventListener('load', async () => { - const renderer = new Renderer( - 480 * core.domStyle.scale, - 480 * core.domStyle.scale - ); - const particle = new Particle(); - const camera = new Camera(); - renderer.bindCamera(camera); - particle.appendTo(renderer); - renderer.append(core.dom.gameDraw); - camera.lookAt([1, 1, 5], [0, 0, 0], [0, 1, 0]); - camera.setPerspective(20, 1, 1, 100); - - particle.setColor([0.3, 0.6, 0.7, 1.0]); - particle.setRadius(2); - particle.setDensity(5000); - particle.setThreshold({ - posX: 0.2, - posY: 0.2, - posZ: 10, - radius: 0, - color: 0 - }); - particle.generate(); - - renderer.canvas.style.position = 'absolute'; - renderer.canvas.style.zIndex = '160'; - - renderer.render(); - - await sleep(5000); - const now: Position3D = [1, 1, 5]; - const path = circle(1, 1000, [0, 0]); - let f = 0; - new Ticker().add(() => { - camera.lookAt(now, [0, 0, 0], [0, 1, 0]); - const [x, y] = path(f / 1000 / 2000); - f++; - now[0] = x; - now[1] = y; - renderer.render(); - }); -}); diff --git a/src/plugin/shadow/gameShadow.ts b/src/plugin/shadow/gameShadow.ts index e5f2e21..2ec1e48 100644 --- a/src/plugin/shadow/gameShadow.ts +++ b/src/plugin/shadow/gameShadow.ts @@ -13,11 +13,11 @@ export default function init() { } const shadowInfo: Partial> = { - MT46: [ + MT43: [ { id: 'mt42_1', - x: 85, - y: 85, + x: 280, + y: 220, decay: 100, r: 300, color: '#0000' @@ -25,13 +25,13 @@ const shadowInfo: Partial> = { ] }; const backgroundInfo: Partial> = { - MT46: '#0008' + MT43: '#0008' }; const blurInfo: Partial> = { - MT46: 4 + MT43: 4 }; const immersionInfo: Partial> = { - MT46: 8 + MT43: 8 }; const shadowCache: Partial> = {}; diff --git a/src/plugin/shadow/polygon.ts b/src/plugin/shadow/polygon.ts index 4248458..1a031d2 100644 --- a/src/plugin/shadow/polygon.ts +++ b/src/plugin/shadow/polygon.ts @@ -14,7 +14,7 @@ export class Polygon { if (nodes.length < 3) { throw new Error(`Nodes number delivered is less than 3!`); } - this.nodes = nodes; + this.nodes = nodes.map(v => [v[0] + 32, v[1] + 32]); } /** @@ -24,12 +24,14 @@ export class Polygon { const id = `${x},${y}`; if (this.cache[id]) return this.cache[id]; const res: LocArr[][] = []; - const w = core._PX_ ?? core.__PIXELS__; - const h = core._PY_ ?? core.__PIXELS__; + const w = (core._PX_ ?? core.__PIXELS__) + 64; + const h = (core._PY_ ?? core.__PIXELS__) + 64; + + const aspect = h / w; const intersect = (nx: number, ny: number): LocArr => { const k = (ny - y) / (nx - x); - if (k > 1 || k < -1) { + if (k > aspect || k < -aspect) { if (ny < y) { const ix = x + y / k; return [2 * x - ix, 0]; diff --git a/src/plugin/shadow/shadow.ts b/src/plugin/shadow/shadow.ts index e30242e..194137c 100644 --- a/src/plugin/shadow/shadow.ts +++ b/src/plugin/shadow/shadow.ts @@ -76,15 +76,15 @@ const transitionList: Record = {}; export function initShadowCanvas() { const w = core._PX_ ?? core.__PIXELS__; const h = core._PY_ ?? core.__PIXELS__; - ctx = core.createCanvas('shadow', 0, 0, w, h, 55); + ctx = core.createCanvas('shadow', -32, -32, w + 64, h + 64, 55); canvas = ctx.canvas; const s = core.domStyle.scale * devicePixelRatio; - temp1.width = w * s; - temp1.height = h * s; - temp2.width = w * s; - temp2.height = h * s; - temp3.width = w * s; - temp3.height = h * s; + temp1.width = (w + 64) * s; + temp1.height = (h + 64) * s; + temp2.width = (w + 64) * s; + temp2.height = (h + 64) * s; + temp3.width = (w + 64) * s; + temp3.height = (h + 64) * s; ct1.scale(s, s); ct2.scale(s, s); ct3.scale(s, s); @@ -401,8 +401,8 @@ export function setBlur(n: number) { * 绘制阴影 */ export function drawShadow() { - const w = core._PX_ ?? core.__PIXELS__; - const h = core._PY_ ?? core.__PIXELS__; + const w = (core._PX_ ?? core.__PIXELS__) + 64; + const h = (core._PY_ ?? core.__PIXELS__) + 64; needRefresh = false; ctx.clearRect(0, 0, w, h); ct1.clearRect(0, 0, w, h); @@ -421,7 +421,7 @@ export function drawShadow() { ct2.clearRect(0, 0, w, h); if (!noShelter) { for (const polygon of shadowNodes) { - const area = polygon.shadowArea(x, y, r); + const area = polygon.shadowArea(x + 32, y + 32, r); area.forEach(v => { ct1.beginPath(); ct1.moveTo(v[0][0], v[0][1]); diff --git a/src/plugin/webgl/utils.ts b/src/plugin/webgl/utils.ts index 999219c..f72c360 100644 --- a/src/plugin/webgl/utils.ts +++ b/src/plugin/webgl/utils.ts @@ -1,3 +1,5 @@ +import { has } from '../utils'; + export default function init() { return { isWebGLSupported }; } @@ -6,3 +8,253 @@ export const isWebGLSupported = (function () { const canvas = document.createElement('canvas'); return !!canvas.getContext('webgl'); })(); + +const cssColors = { + black: '#000000', + silver: '#c0c0c0', + gray: '#808080', + white: '#ffffff', + maroon: '#800000', + red: '#ff0000', + purple: '#800080', + fuchsia: '#ff00ff', + green: '#008000', + lime: '#00ff00', + olive: '#808000', + yellow: '#ffff00', + navy: '#000080', + blue: '#0000ff', + teal: '#008080', + aqua: '#00ffff', + orange: '#ffa500', + aliceblue: '#f0f8ff', + antiquewhite: '#faebd7', + aquamarine: '#7fffd4', + azure: '#f0ffff', + beige: '#f5f5dc', + bisque: '#ffe4c4', + blanchedalmond: '#ffebcd', + blueviolet: '#8a2be2', + brown: '#a52a2a', + burlywood: '#deb887', + cadetblue: '#5f9ea0', + chartreuse: '#7fff00', + chocolate: '#d2691e', + coral: '#ff7f50', + cornflowerblue: '#6495ed', + cornsilk: '#fff8dc', + crimson: '#dc143c', + cyan: '#00ffff', + darkblue: '#00008b', + darkcyan: '#008b8b', + darkgoldenrod: '#b8860b', + darkgray: '#a9a9a9', + darkgreen: '#006400', + darkgrey: '#a9a9a9', + darkkhaki: '#bdb76b', + darkmagenta: '#8b008b', + darkolivegreen: '#556b2f', + darkorange: '#ff8c00', + darkorchid: '#9932cc', + darkred: '#8b0000', + darksalmon: '#e9967a', + darkseagreen: '#8fbc8f', + darkslateblue: '#483d8b', + darkslategray: '#2f4f4f', + darkslategrey: '#2f4f4f', + darkturquoise: '#00ced1', + darkviolet: '#9400d3', + deeppink: '#ff1493', + deepskyblue: '#00bfff', + dimgray: '#696969', + dimgrey: '#696969', + dodgerblue: '#1e90ff', + firebrick: '#b22222', + floralwhite: '#fffaf0', + forestgreen: '#228b22', + gainsboro: '#dcdcdc', + ghostwhite: '#f8f8ff', + gold: '#ffd700', + goldenrod: '#daa520', + greenyellow: '#adff2f', + grey: '#808080', + honeydew: '#f0fff0', + hotpink: '#ff69b4', + indianred: '#cd5c5c', + indigo: '#4b0082', + ivory: '#fffff0', + khaki: '#f0e68c', + lavender: '#e6e6fa', + lavenderblush: '#fff0f5', + lawngreen: '#7cfc00', + lemonchiffon: '#fffacd', + lightblue: '#add8e6', + lightcoral: '#f08080', + lightcyan: '#e0ffff', + lightgoldenrodyellow: '#fafad2', + lightgray: '#d3d3d3', + lightgreen: '#90ee90', + lightgrey: '#d3d3d3', + lightpink: '#ffb6c1', + lightsalmon: '#ffa07a', + lightseagreen: '#20b2aa', + lightskyblue: '#87cefa', + lightslategray: '#778899', + lightslategrey: '#778899', + lightsteelblue: '#b0c4de', + lightyellow: '#ffffe0', + limegreen: '#32cd32', + linen: '#faf0e6', + magenta: '#ff00ff', + mediumaquamarine: '#66cdaa', + mediumblue: '#0000cd', + mediumorchid: '#ba55d3', + mediumpurple: '#9370db', + mediumseagreen: '#3cb371', + mediumslateblue: '#7b68ee', + mediumspringgreen: '#00fa9a', + mediumturquoise: '#48d1cc', + mediumvioletred: '#c71585', + midnightblue: '#191970', + mintcream: '#f5fffa', + mistyrose: '#ffe4e1', + moccasin: '#ffe4b5', + navajowhite: '#ffdead', + oldlace: '#fdf5e6', + olivedrab: '#6b8e23', + orangered: '#ff4500', + orchid: '#da70d6', + palegoldenrod: '#eee8aa', + palegreen: '#98fb98', + paleturquoise: '#afeeee', + palevioletred: '#db7093', + papayawhip: '#ffefd5', + peachpuff: '#ffdab9', + peru: '#cd853f', + pink: '#ffc0cb', + plum: '#dda0dd', + powderblue: '#b0e0e6', + rosybrown: '#bc8f8f', + royalblue: '#4169e1', + saddlebrown: '#8b4513', + salmon: '#fa8072', + sandybrown: '#f4a460', + seagreen: '#2e8b57', + seashell: '#fff5ee', + sienna: '#a0522d', + skyblue: '#87ceeb', + slateblue: '#6a5acd', + slategray: '#708090', + slategrey: '#708090', + snow: '#fffafa', + springgreen: '#00ff7f', + steelblue: '#4682b4', + tan: '#d2b48c', + thistle: '#d8bfd8', + tomato: '#ff6347', + turquoise: '#40e0d0', + violet: '#ee82ee', + wheat: '#f5deb3', + whitesmoke: '#f5f5f5', + yellowgreen: '#9acd32', + transparent: '#0000' +}; + +/** + * 颜色字符串转rgb数组 + * @param color 颜色字符串 + */ +export function parseColor(color: string): RGBArray { + if (color.startsWith('rgb')) { + // rgb + const match = color.match(/rgba?\([\d\,\s\.%]+\)/); + if (!has(match)) throw new Error(`Invalid color is delivered!`); + const l = color.includes('a'); + return match[0] + .slice(l ? 5 : 4, -1) + .split(',') + .map((v, i) => { + const vv = v.trim(); + if (vv.endsWith('%')) { + if (i === 3) { + return parseInt(vv) / 100; + } else { + return (parseInt(vv) * 255) / 100; + } + } else return parseFloat(vv); + }) + .slice(0, l ? 4 : 3) as RGBArray; + } else if (color.startsWith('#')) { + // 十六进制 + const content = color.slice(1); + if (![3, 4, 6, 8].includes(content.length)) { + throw new Error(`Invalid color is delivered!`); + } + + if (content.length <= 4) { + const res = content + .split('') + .map(v => Number(`0x${v}${v}`)) as RGBArray; + if (res.length === 4) res[3]! /= 255; + return res; + } else { + const res = Array(content.length / 2) + .fill(1) + .map((v, i) => + Number(`0x${content[i * 2]}${content[i * 2 + 1]}`) + ) as RGBArray; + if (res.length === 4) res[3]! /= 255; + return res; + } + } else if (color.startsWith('hsl')) { + // hsl,转成rgb后输出 + const match = color.match(/hsla?\([\d\,\s\.%]+\)/); + if (!has(match)) throw new Error(`Invalid color is delivered!`); + const l = color.includes('a'); + const hsl = match[0] + .slice(l ? 5 : 4, -1) + .split(',') + .map(v => { + const vv = v.trim(); + if (vv.endsWith('%')) return parseInt(vv) / 100; + else return parseFloat(vv); + }); + const rgb = hslToRgb(hsl[0], hsl[1], hsl[2]); + return (l ? rgb.concat([hsl[3]]) : rgb) as RGBArray; + } else { + // 单词 + const rgb = cssColors[color as keyof typeof cssColors]; + if (!has(rgb)) { + throw new Error(`Invalid color is delivered!`); + } + return parseColor(rgb); + } +} + +/** + * hsl转rgb + * @param h 色相 + * @param s 饱和度 + * @param l 亮度 + */ +export function hslToRgb(h: number, s: number, l: number) { + if (s == 0) { + return [0, 0, 0]; + } else { + const hue2rgb = (p: number, q: number, t: number) => { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + }; + + const q = l < 0.5 ? l * (1 + s) : l + s - l * s; + const p = 2 * l - q; + const r = hue2rgb(p, q, h + 1 / 3); + const g = hue2rgb(p, q, h); + const b = hue2rgb(p, q, h - 1 / 3); + return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; + } +}