mirror of
				https://github.com/unanmed/HumanBreak.git
				synced 2025-10-26 00:12:59 +08:00 
			
		
		
		
	点光源
This commit is contained in:
		
							parent
							
								
									3c7b5906f6
								
							
						
					
					
						commit
						5e0d8ac7d4
					
				| @ -3202,6 +3202,7 @@ maps.prototype.removeBlock = function (x, y, floorId) { | ||||
|         const block = blocks[i]; | ||||
|         this.removeBlockByIndex(i, floorId); | ||||
|         this._removeBlockFromMap(floorId, block); | ||||
|         core.updateShadow(true); | ||||
|         return true; | ||||
|     } | ||||
|     return false; | ||||
| @ -3364,6 +3365,7 @@ maps.prototype.setBlock = function (number, x, y, floorId, noredraw) { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     core.updateShadow(true); | ||||
| }; | ||||
| 
 | ||||
| maps.prototype.animateSetBlock = function ( | ||||
|  | ||||
| @ -4226,7 +4226,8 @@ ui.prototype.deleteCanvas = function (name) { | ||||
| 
 | ||||
| ////// 删除所有动态canvas //////
 | ||||
| ui.prototype.deleteAllCanvas = function () { | ||||
|     return this.deleteCanvas(function () { | ||||
|     this.deleteCanvas(function () { | ||||
|         return true; | ||||
|     }); | ||||
|     if (main.mode === 'play' && !core.isReplaying()) core.initShadowCanvas(); | ||||
| }; | ||||
|  | ||||
| @ -31,6 +31,13 @@ main.floors.MT42= | ||||
|                 5, | ||||
|                 0 | ||||
|             ] | ||||
|         }, | ||||
|         "8,12": { | ||||
|             "floorId": "MT46", | ||||
|             "loc": [ | ||||
|                 7, | ||||
|                 8 | ||||
|             ] | ||||
|         } | ||||
|     }, | ||||
|     "beforeBattle": {}, | ||||
|  | ||||
| @ -1,45 +1,71 @@ | ||||
| main.floors.MT46= | ||||
| { | ||||
| "floorId": "MT46", | ||||
| "title": "冰封高原", | ||||
| "name": "46", | ||||
| "width": 15, | ||||
| "height": 15, | ||||
| "canFlyTo": true, | ||||
| "canFlyFrom": true, | ||||
| "canUseQuickShop": true, | ||||
| "cannotViewMap": false, | ||||
| "images": [], | ||||
| "ratio": 8, | ||||
| "defaultGround": "T580", | ||||
| "bgm": "winter.mp3", | ||||
| "firstArrive": [], | ||||
| "eachArrive": [], | ||||
| "parallelDo": "", | ||||
| "events": {}, | ||||
| "changeFloor": {}, | ||||
| "beforeBattle": {}, | ||||
| "afterBattle": {}, | ||||
| "afterGetItem": {}, | ||||
| "afterOpenDoor": {}, | ||||
| "autoEvent": {}, | ||||
| "cannotMove": {}, | ||||
| "cannotMoveIn": {}, | ||||
| "map": [ | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0] | ||||
|     "floorId": "MT46", | ||||
|     "title": "冰封高原", | ||||
|     "name": "46", | ||||
|     "width": 15, | ||||
|     "height": 15, | ||||
|     "canFlyTo": true, | ||||
|     "canFlyFrom": true, | ||||
|     "canUseQuickShop": true, | ||||
|     "cannotViewMap": false, | ||||
|     "images": [], | ||||
|     "ratio": 8, | ||||
|     "defaultGround": "T580", | ||||
|     "bgm": "winter.mp3", | ||||
|     "firstArrive": [], | ||||
|     "eachArrive": [], | ||||
|     "parallelDo": "", | ||||
|     "events": {}, | ||||
|     "changeFloor": {}, | ||||
|     "beforeBattle": {}, | ||||
|     "afterBattle": {}, | ||||
|     "afterGetItem": {}, | ||||
|     "afterOpenDoor": {}, | ||||
|     "autoEvent": {}, | ||||
|     "cannotMove": {}, | ||||
|     "cannotMoveIn": {}, | ||||
|     "map": [ | ||||
|     [  1,  1,  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0], | ||||
|     [  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  0], | ||||
|     [  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0], | ||||
|     [  1,  0,  0,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0], | ||||
|     [  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  0], | ||||
|     [  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  1], | ||||
|     [  0,  0,  0,  1,  0,  0,  0,  1,  1,  0,  0,  1,  0,  0,  0], | ||||
|     [  0,  0,  0,  1,  1,  1,  1,  1,  0,  0,  0,  1,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0], | ||||
|     [  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0], | ||||
|     [  0,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0], | ||||
|     [  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  1,  1,  0,  0,  0], | ||||
|     [  0,  0,  1,  0,  0,  0,  1,  1,  1,  1,  1,  0,  0,  0,  0], | ||||
|     [  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0], | ||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  0,  0,  0] | ||||
| ], | ||||
|     "bgmap": [ | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300], | ||||
|     [300,300,300,300,300,300,300,300,300,300,300,300,300,300,300] | ||||
| ], | ||||
|     "fgmap": [ | ||||
| 
 | ||||
| ], | ||||
|     "bg2map": [ | ||||
| 
 | ||||
| ], | ||||
|     "fg2map": [ | ||||
| 
 | ||||
| ] | ||||
| } | ||||
| @ -150,6 +150,8 @@ var functions_d6ad677b_427a_4623_b50f_a445a3b0ef8a = { | ||||
|             // ---------- 重绘新地图;这一步将会设置core.status.floorId ---------- //
 | ||||
|             core.drawMap(floorId); | ||||
| 
 | ||||
|             core.updateShadow(); | ||||
| 
 | ||||
|             // 切换楼层BGM
 | ||||
|             if (core.status.maps[floorId].bgm) { | ||||
|                 var bgm = core.status.maps[floorId].bgm; | ||||
|  | ||||
| @ -2,6 +2,7 @@ | ||||
| 
 | ||||
| var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = { | ||||
|     init: function () { | ||||
|         // 只看插件没用,插件是与vite样板高度融合的,所以要看的话就在游戏内的百科全书-关于游戏内点那个开源地址吧
 | ||||
|         this._afterLoadResources = function () { | ||||
|             if (!main.replayChecking && main.mode === 'play') { | ||||
|                 main.forward(); | ||||
| @ -770,12 +771,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = { | ||||
|             core.status.hero = new Proxy(hero, handler); | ||||
| 
 | ||||
|             core.status.maps[floorId].blocks.forEach(function (block) { | ||||
|                 if ( | ||||
|                     block.event.cls !== 'items' || | ||||
|                     block.event.id === 'superPotion' || | ||||
|                     block.disable | ||||
|                 ) | ||||
|                     return; | ||||
|                 if (block.event.cls !== 'items' || block.disable) return; | ||||
|                 const x = block.x, | ||||
|                     y = block.y; | ||||
|                 // v2优化,只绘制范围内的部分
 | ||||
|  | ||||
| @ -11,6 +11,9 @@ import chapter from './plugin/ui/chapter'; | ||||
| import fly from './plugin/ui/fly'; | ||||
| import chase from './plugin/chase/chase'; | ||||
| import fixed from './plugin/ui/fixed'; | ||||
| import webglUtils from './plugin/webgl/utils'; | ||||
| import shadow from './plugin/webgl/shadow'; | ||||
| import gameShadow from './plugin/webgl/gameShadow'; | ||||
| 
 | ||||
| function forward() { | ||||
|     // 每个引入的插件都要在这里执行,否则不会被转发
 | ||||
| @ -26,7 +29,10 @@ function forward() { | ||||
|         chapter(), | ||||
|         fly(), | ||||
|         chase(), | ||||
|         fixed() | ||||
|         fixed(), | ||||
|         webglUtils(), | ||||
|         shadow(), | ||||
|         gameShadow() | ||||
|     ]; | ||||
| 
 | ||||
|     // 初始化所有插件,并转发到core上
 | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| /// <reference path="../types/core.d.ts" />
 | ||||
| import { message } from 'ant-design-vue'; | ||||
| import { MessageApi } from 'ant-design-vue/lib/message'; | ||||
| import { isNil } from 'lodash'; | ||||
| @ -69,7 +70,7 @@ export function keycode(key: number) { | ||||
|  * @param css 要解析的css字符串 | ||||
|  */ | ||||
| export function parseCss(css: string): Partial<Record<CanParseCss, string>> { | ||||
|     const str = css.replace(/[\n\s\t]*/g, '').replace(/[;,]*/g, ';'); | ||||
|     const str = css.replace(/[\n\s\t]*/g, '').replace(/;*/g, ';'); | ||||
|     const styles = str.split(';'); | ||||
|     const res: Partial<Record<CanParseCss, string>> = {}; | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										123
									
								
								src/plugin/webgl/canvas.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/plugin/webgl/canvas.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,123 @@ | ||||
| const glMap: Record<string, WebGLRenderingContext> = {}; | ||||
| 
 | ||||
| /** | ||||
|  * 创建一个以webgl为绘制上下文的画布 | ||||
|  * @param id 画布id | ||||
|  * @param x 横坐标 | ||||
|  * @param y 纵坐标 | ||||
|  * @param w 宽度 | ||||
|  * @param h 高度 | ||||
|  * @param z 纵深 | ||||
|  */ | ||||
| export function createWebGLCanvas( | ||||
|     id: string, | ||||
|     x: number, | ||||
|     y: number, | ||||
|     w: number, | ||||
|     h: number, | ||||
|     z: number | ||||
| ) { | ||||
|     if (id in glMap) { | ||||
|         deleteWebGLCanvas(id); | ||||
|     } | ||||
|     const canvas = document.createElement('canvas'); | ||||
|     const gl = canvas.getContext('webgl')!; | ||||
|     const s = core.domStyle.scale; | ||||
|     canvas.style.left = `${x * s}px`; | ||||
|     canvas.style.top = `${y * s}px`; | ||||
|     canvas.style.width = `${w * s}px`; | ||||
|     canvas.style.height = `${h * s}px`; | ||||
|     canvas.style.zIndex = `${z}`; | ||||
|     canvas.width = w * s * devicePixelRatio; | ||||
|     canvas.height = h * s * devicePixelRatio; | ||||
|     core.dom.gameDraw.appendChild(canvas); | ||||
|     return gl; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 删除一个webgl画布 | ||||
|  * @param id 画布id | ||||
|  */ | ||||
| export function deleteWebGLCanvas(id: string) { | ||||
|     const gl = glMap[id]; | ||||
|     if (!gl) return; | ||||
|     const canvas = gl.canvas as HTMLCanvasElement; | ||||
|     canvas.remove(); | ||||
|     delete glMap[id]; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 获取webgl画布上下文 | ||||
|  * @param id 画布id | ||||
|  */ | ||||
| export function getWebGLCanvas(id: string): WebGLRenderingContext | null { | ||||
|     return glMap[id]; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 创建webgl程序对象 | ||||
|  * @param gl 画布webgl上下文 | ||||
|  * @param vshader 顶点着色器 | ||||
|  * @param fshader 片元着色器 | ||||
|  */ | ||||
| export function createProgram( | ||||
|     gl: WebGLRenderingContext, | ||||
|     vshader: string, | ||||
|     fshader: string | ||||
| ) { | ||||
|     // 创建着色器
 | ||||
|     const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader); | ||||
|     const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader); | ||||
| 
 | ||||
|     // 创建program
 | ||||
|     const program = gl.createProgram(); | ||||
|     if (!program) { | ||||
|         throw new Error(`Create webgl program fail!`); | ||||
|     } | ||||
| 
 | ||||
|     // 分配和连接program
 | ||||
|     gl.attachShader(program, vertexShader); | ||||
|     gl.attachShader(program, fragmentShader); | ||||
|     gl.linkProgram(program); | ||||
| 
 | ||||
|     // 检查连接是否成功
 | ||||
|     const linked = gl.getProgramParameter(program, gl.LINK_STATUS); | ||||
|     if (!linked) { | ||||
|         const err = gl.getProgramInfoLog(program); | ||||
|         throw new Error(`Program link fail: ${err}`); | ||||
|     } | ||||
| 
 | ||||
|     return program; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 加载着色器 | ||||
|  * @param gl 画布的webgl上下文 | ||||
|  * @param type 着色器类型,顶点着色器还是片元着色器 | ||||
|  * @param source 着色器源码 | ||||
|  */ | ||||
| export function loadShader( | ||||
|     gl: WebGLRenderingContext, | ||||
|     type: number, | ||||
|     source: string | ||||
| ) { | ||||
|     // 创建着色器
 | ||||
|     const shader = gl.createShader(type); | ||||
|     if (!shader) { | ||||
|         throw new ReferenceError( | ||||
|             `Your device or browser does not support webgl!` | ||||
|         ); | ||||
|     } | ||||
|     // 引入并编译着色器
 | ||||
|     gl.shaderSource(shader, source); | ||||
|     gl.compileShader(shader); | ||||
| 
 | ||||
|     // 检查是否编译成功
 | ||||
|     const compiled = gl.getShaderParameter(gl, gl.COMPILE_STATUS); | ||||
|     if (!compiled) { | ||||
|         const err = gl.getShaderInfoLog(shader); | ||||
|         throw new Error(`Shader compile fail: ${err}`); | ||||
|     } | ||||
| 
 | ||||
|     return shader; | ||||
| } | ||||
							
								
								
									
										131
									
								
								src/plugin/webgl/gameShadow.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/plugin/webgl/gameShadow.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| import { Polygon } from './polygon'; | ||||
| import { | ||||
|     Light, | ||||
|     removeAllLights, | ||||
|     setBackground, | ||||
|     setBlur, | ||||
|     setLightList, | ||||
|     setShadowNodes | ||||
| } from './shadow'; | ||||
| 
 | ||||
| export default function init() { | ||||
|     return { updateShadow, clearShadowCache, setCalShadow }; | ||||
| } | ||||
| 
 | ||||
| const shadowInfo: Partial<Record<FloorIds, Light[]>> = { | ||||
|     MT46: [ | ||||
|         { | ||||
|             id: 'mt42_1', | ||||
|             x: 85, | ||||
|             y: 85, | ||||
|             decay: 100, | ||||
|             r: 300, | ||||
|             color: '#0000' | ||||
|         } | ||||
|     ] | ||||
| }; | ||||
| const backgroundInfo: Partial<Record<FloorIds, Color>> = { | ||||
|     MT46: '#0008' | ||||
| }; | ||||
| const blurInfo: Partial<Record<FloorIds, number>> = { | ||||
|     MT46: 4 | ||||
| }; | ||||
| const immersionInfo: Partial<Record<FloorIds, number>> = { | ||||
|     MT46: 8 | ||||
| }; | ||||
| const shadowCache: Partial<Record<FloorIds, Polygon[]>> = {}; | ||||
| 
 | ||||
| let calMapShadow = true; | ||||
| 
 | ||||
| export function updateShadow(nocache: boolean = false) { | ||||
|     // 需要优化,优化成bfs
 | ||||
|     const floor = core.status.floorId; | ||||
|     if (!shadowInfo[floor] || !backgroundInfo[floor]) { | ||||
|         removeAllLights(); | ||||
|         setShadowNodes([]); | ||||
|         setBackground('#0000'); | ||||
|         return; | ||||
|     } | ||||
|     const f = core.status.thisMap; | ||||
|     const w = f.width; | ||||
|     const h = f.height; | ||||
|     const nodes: Polygon[] = []; | ||||
|     if (calMapShadow) { | ||||
|         if (shadowCache[floor] && !nocache) { | ||||
|             setShadowNodes(shadowCache[floor]!); | ||||
|         } else { | ||||
|             core.extractBlocks(); | ||||
|             const blocks = core.getMapBlocksObj(); | ||||
|             core.status.maps[floor].blocks.forEach(v => { | ||||
|                 if ( | ||||
|                     !['terrains', 'autotile', 'tileset', 'animates'].includes( | ||||
|                         v.event.cls | ||||
|                     ) | ||||
|                 ) { | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 if (v.event.noPass) { | ||||
|                     const immerse = immersionInfo[floor] ?? 4; | ||||
|                     const x = v.x; | ||||
|                     const y = v.y; | ||||
|                     let left = x * 32 + immerse; | ||||
|                     let top = y * 32 + immerse; | ||||
|                     let right = left + 32 - immerse * 2; | ||||
|                     let bottom = top + 32 - immerse * 2; | ||||
|                     const l: LocString = `${x - 1},${y}`; | ||||
|                     const r: LocString = `${x + 1},${y}`; | ||||
|                     const t: LocString = `${x},${y - 1}`; | ||||
|                     const b: LocString = `${x},${y + 1}`; | ||||
| 
 | ||||
|                     if (x === 0 || (blocks[l] && blocks[l].event.noPass)) { | ||||
|                         left -= immerse; | ||||
|                     } | ||||
|                     if (x + 1 === w || (blocks[r] && blocks[r].event.noPass)) { | ||||
|                         right += immerse; | ||||
|                     } | ||||
|                     if (y === 0 || (blocks[t] && blocks[t].event.noPass)) { | ||||
|                         top -= immerse; | ||||
|                     } | ||||
|                     if (y + 1 === h || (blocks[b] && blocks[b].event.noPass)) { | ||||
|                         bottom += immerse; | ||||
|                     } | ||||
|                     nodes.push( | ||||
|                         new Polygon([ | ||||
|                             [left, top], | ||||
|                             [right, top], | ||||
|                             [right, bottom], | ||||
|                             [left, bottom] | ||||
|                         ]) | ||||
|                     ); | ||||
|                     return; | ||||
|                 } | ||||
|             }); | ||||
|             shadowCache[floor] = nodes; | ||||
|             setShadowNodes(nodes); | ||||
|         } | ||||
|     } else { | ||||
|         setShadowNodes([]); | ||||
|         setBlur(0); | ||||
|     } | ||||
|     setLightList(shadowInfo[floor]!); | ||||
|     setBackground(backgroundInfo[floor]!); | ||||
|     setBlur(blurInfo[floor] ?? 3); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 清除某一层的墙壁缓存 | ||||
|  * @param floorId 楼层id | ||||
|  */ | ||||
| export function clearShadowCache(floorId: FloorIds) { | ||||
|     delete shadowCache[floorId]; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 设置是否不计算墙壁遮挡,对所有灯光有效 | ||||
|  * @param n 目标值 | ||||
|  */ | ||||
| export function setCalShadow(n: boolean) { | ||||
|     calMapShadow = n; | ||||
|     updateShadow(); | ||||
| } | ||||
							
								
								
									
										160
									
								
								src/plugin/webgl/martrix.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										160
									
								
								src/plugin/webgl/martrix.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,160 @@ | ||||
| import { has } from '../utils'; | ||||
| 
 | ||||
| export class Matrix extends Array<number[]> { | ||||
|     constructor(...n: number[][]) { | ||||
|         if (n.length !== n[0]?.length) { | ||||
|             throw new TypeError( | ||||
|                 `The array delivered to Matrix must has the same length of its item and itself.` | ||||
|             ); | ||||
|         } | ||||
|         super(...n); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 加上某个方阵 | ||||
|      * @param matrix 要加上的方阵 | ||||
|      */ | ||||
|     add(matrix: number[][]): Matrix { | ||||
|         if (matrix.length !== this.length) { | ||||
|             throw new TypeError( | ||||
|                 `To add a martrix, the be-added-matrix's size must equal to the to-add-matrix's.` | ||||
|             ); | ||||
|         } | ||||
|         const length = matrix.length; | ||||
|         for (let i = 0; i < length; i++) { | ||||
|             for (let j = 0; j < length; j++) { | ||||
|                 this[i][j] += matrix[i][j]; | ||||
|             } | ||||
|         } | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 让该方阵与另一个方阵相乘 | ||||
|      * @param matrix 要相乘的方阵 | ||||
|      */ | ||||
|     multipy(matrix: number[][]): Matrix { | ||||
|         if (matrix.length !== this.length) { | ||||
|             throw new TypeError( | ||||
|                 `To multipy a martrix, the be-multipied-matrix's size must equal to the to-multipy-matrix's.` | ||||
|             ); | ||||
|         } | ||||
|         const n = this.length; | ||||
|         const arr = this.map(v => v.slice()); | ||||
|         for (let i = 0; i < n; i++) { | ||||
|             for (let j = 0; j < n; j++) { | ||||
|                 for (let k = 0; k < n; k++) { | ||||
|                     this[i][j] = arr[i][k] * matrix[k][j]; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return this; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export class Matrix4 extends Matrix { | ||||
|     constructor(...n: number[][]) { | ||||
|         n ??= [ | ||||
|             [1, 0, 0, 0], | ||||
|             [0, 1, 0, 0], | ||||
|             [0, 0, 1, 0], | ||||
|             [0, 0, 0, 1] | ||||
|         ]; | ||||
|         if (n.length !== 4) { | ||||
|             throw new TypeError(`The length of delivered array must be 4.`); | ||||
|         } | ||||
|         super(...n); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 平移变换 | ||||
|      * @param x 平移横坐标 | ||||
|      * @param y 平移纵坐标 | ||||
|      * @param z 平移竖坐标 | ||||
|      */ | ||||
|     translation(x: number, y: number, z: number) { | ||||
|         this.multipy([ | ||||
|             [1, 0, 0, x], | ||||
|             [0, 1, 0, y], | ||||
|             [0, 0, 1, z], | ||||
|             [0, 0, 0, 1] | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 缩放变换 | ||||
|      * @param x 沿x轴的缩放比例 | ||||
|      * @param y 沿y轴的缩放比例 | ||||
|      * @param z 沿z轴的缩放比例 | ||||
|      */ | ||||
|     scale(x: number, y: number, z: number) { | ||||
|         this.multipy([ | ||||
|             [x, 0, 0, 0], | ||||
|             [0, y, 0, 0], | ||||
|             [0, 0, z, 0], | ||||
|             [0, 0, 0, 1] | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 旋转变换 | ||||
|      * @param x 绕x轴的旋转角度 | ||||
|      * @param y 绕y轴的旋转角度 | ||||
|      * @param z 绕z轴的旋转角度 | ||||
|      */ | ||||
|     rotate(x?: number, y?: number, z?: number): Matrix4 { | ||||
|         if (has(x) && x !== 0) { | ||||
|             const sin = Math.sin(x); | ||||
|             const cos = Math.cos(x); | ||||
|             this.multipy([ | ||||
|                 [1, 0, 0, 0], | ||||
|                 [0, cos, sin, 0], | ||||
|                 [0, -sin, cos, 0], | ||||
|                 [0, 0, 0, 1] | ||||
|             ]); | ||||
|         } | ||||
|         if (has(y) && y !== 0) { | ||||
|             const sin = Math.sin(y); | ||||
|             const cos = Math.cos(y); | ||||
|             this.multipy([ | ||||
|                 [cos, 0, -sin, 0], | ||||
|                 [0, 1, 0, 0], | ||||
|                 [sin, 0, cos, 0], | ||||
|                 [0, 0, 0, 1] | ||||
|             ]); | ||||
|         } | ||||
|         if (has(z) && z !== 0) { | ||||
|             const sin = Math.sin(z); | ||||
|             const cos = Math.cos(z); | ||||
|             this.multipy([ | ||||
|                 [cos, sin, 0, 0], | ||||
|                 [-sin, cos, 0, 0], | ||||
|                 [0, 0, 1, 0], | ||||
|                 [0, 0, 0, 1] | ||||
|             ]); | ||||
|         } | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 转置矩阵 | ||||
|      * @param target 转置目标,是赋给原矩阵还是新建一个矩阵 | ||||
|      */ | ||||
|     transpose(target: 'this' | 'new' = 'new'): Matrix4 { | ||||
|         const t = target === 'this' ? this : new Matrix4(); | ||||
|         const arr = this.map(v => v.slice()); | ||||
|         for (let i = 0; i < 4; i++) { | ||||
|             for (let j = 0; j < 4; j++) { | ||||
|                 t[i][j] = arr[j][i]; | ||||
|             } | ||||
|         } | ||||
|         return t; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 转换成列主序的Float32Array,用于webgl | ||||
|      */ | ||||
|     toWebGLFloat32Array(): Float32Array { | ||||
|         return new Float32Array(this.transpose().flat()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										89
									
								
								src/plugin/webgl/polygon.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/plugin/webgl/polygon.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| export class Polygon { | ||||
|     /** | ||||
|      * 多边形的节点 | ||||
|      */ | ||||
|     nodes: LocArr[]; | ||||
| 
 | ||||
|     private cache: Record<string, LocArr[][]> = {}; | ||||
| 
 | ||||
|     static from(...polygons: LocArr[][]) { | ||||
|         return polygons.map(v => new Polygon(v)); | ||||
|     } | ||||
| 
 | ||||
|     constructor(nodes: LocArr[]) { | ||||
|         if (nodes.length < 3) { | ||||
|             throw new Error(`Nodes number delivered is less than 3!`); | ||||
|         } | ||||
|         this.nodes = nodes; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 获取一个点光源下的阴影 | ||||
|      */ | ||||
|     shadowArea(x: number, y: number, r: number): LocArr[][] { | ||||
|         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 intersect = (nx: number, ny: number): LocArr => { | ||||
|             const k = (ny - y) / (nx - x); | ||||
|             if (k > 1 || k < -1) { | ||||
|                 if (ny < y) { | ||||
|                     const ix = x + y / k; | ||||
|                     return [2 * x - ix, 0]; | ||||
|                 } else { | ||||
|                     const ix = x + (h - y) / k; | ||||
|                     return [ix, h]; | ||||
|                 } | ||||
|             } else { | ||||
|                 if (nx < x) { | ||||
|                     const iy = y + k * x; | ||||
|                     return [0, 2 * y - iy]; | ||||
|                 } else { | ||||
|                     const iy = y + k * (w - x); | ||||
|                     return [w, iy]; | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|         const l = this.nodes.length; | ||||
|         let now = intersect(...this.nodes[0]); | ||||
|         for (let i = 0; i < l; i++) { | ||||
|             const next = (i + 1) % l; | ||||
|             const nextInter = intersect(...this.nodes[next]); | ||||
|             const start = [this.nodes[i], now]; | ||||
|             const end = [nextInter, this.nodes[next]]; | ||||
|             let path: LocArr[]; | ||||
|             if ( | ||||
|                 (now[0] === 0 && nextInter[1] === 0) || | ||||
|                 (now[1] === 0 && nextInter[0] === 0) | ||||
|             ) { | ||||
|                 path = [...start, [0, 0], ...end]; | ||||
|             } else if ( | ||||
|                 (now[0] === 0 && nextInter[1] === h) || | ||||
|                 (now[1] === h && nextInter[0] === 0) | ||||
|             ) { | ||||
|                 path = [...start, [0, h], ...end]; | ||||
|             } else if ( | ||||
|                 (now[0] === w && nextInter[1] === 0) || | ||||
|                 (now[1] === 0 && nextInter[0] === w) | ||||
|             ) { | ||||
|                 path = [...start, [w, 0], ...end]; | ||||
|             } else if ( | ||||
|                 (now[0] === w && nextInter[1] === h) || | ||||
|                 (now[1] === h && nextInter[0] === w) | ||||
|             ) { | ||||
|                 path = [...start, [w, h], ...end]; | ||||
|             } else { | ||||
|                 path = [...start, ...end]; | ||||
|             } | ||||
|             res.push(path); | ||||
|             now = nextInter; | ||||
|         } | ||||
| 
 | ||||
|         this.cache[id] = res; | ||||
| 
 | ||||
|         return res; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										468
									
								
								src/plugin/webgl/shadow.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										468
									
								
								src/plugin/webgl/shadow.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,468 @@ | ||||
| import { | ||||
|     Animation, | ||||
|     linear, | ||||
|     PathFn, | ||||
|     TimingFn, | ||||
|     Transition | ||||
| } from 'mutate-animate'; | ||||
| import { has } from '../utils'; | ||||
| import { Polygon } from './polygon'; | ||||
| 
 | ||||
| interface TransitionInfo { | ||||
|     time: number; | ||||
|     mode: TimingFn; | ||||
| } | ||||
| 
 | ||||
| export interface Light { | ||||
|     id: string; | ||||
|     x: number; | ||||
|     y: number; | ||||
|     r: number; | ||||
|     /** 衰减开始半径 */ | ||||
|     decay: number; | ||||
|     /** 颜色,每个值的范围0.0~1.0 */ | ||||
|     color: Color; | ||||
|     /** 是否可以被物体遮挡 */ | ||||
|     noShelter?: boolean; | ||||
|     /** 正在动画的属性 */ | ||||
|     _animating?: Record<string, boolean>; | ||||
|     /** 执行渐变的属性 */ | ||||
|     _transition?: Record<string, TransitionInfo>; | ||||
|     /** 表示是否是代理,只有设置渐变后才会变为true */ | ||||
|     _isProxy?: boolean; | ||||
| } | ||||
| 
 | ||||
| export default function init() { | ||||
|     core.registerAnimationFrame('shadow', true, () => { | ||||
|         if (!needRefresh) return; | ||||
|         drawShadow(); | ||||
|     }); | ||||
| 
 | ||||
|     return { | ||||
|         initShadowCanvas, | ||||
|         drawShadow, | ||||
|         addLight, | ||||
|         removeLight, | ||||
|         setLight, | ||||
|         setShadowNodes, | ||||
|         setBackground, | ||||
|         animateLight, | ||||
|         transitionLight, | ||||
|         moveLightAs, | ||||
|         getAllLights | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| let canvas: HTMLCanvasElement; | ||||
| let ctx: CanvasRenderingContext2D; | ||||
| let lights: Light[] = []; | ||||
| let needRefresh = false; | ||||
| let shadowNodes: Polygon[] = []; | ||||
| let background: Color; | ||||
| let blur = 3; | ||||
| const temp1 = document.createElement('canvas'); | ||||
| const temp2 = document.createElement('canvas'); | ||||
| const temp3 = document.createElement('canvas'); | ||||
| const ct1 = temp1.getContext('2d')!; | ||||
| const ct2 = temp2.getContext('2d')!; | ||||
| const ct3 = temp3.getContext('2d')!; | ||||
| 
 | ||||
| const animationList: Record<string, Animation> = {}; | ||||
| const transitionList: Record<string, Transition> = {}; | ||||
| 
 | ||||
| /** | ||||
|  * 初始化阴影画布 | ||||
|  */ | ||||
| export function initShadowCanvas() { | ||||
|     const w = core._PX_ ?? core.__PIXELS__; | ||||
|     const h = core._PY_ ?? core.__PIXELS__; | ||||
|     ctx = core.createCanvas('shadow', 0, 0, w, h, 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; | ||||
|     ct1.scale(s, s); | ||||
|     ct2.scale(s, s); | ||||
|     ct3.scale(s, s); | ||||
|     canvas.style.filter = `blur(${blur}px)`; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 添加一个光源 | ||||
|  * @param info 光源信息 | ||||
|  */ | ||||
| export function addLight(info: Light) { | ||||
|     lights.push(info); | ||||
|     needRefresh = true; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 移除一个光源 | ||||
|  * @param id 光源id | ||||
|  */ | ||||
| export function removeLight(id: string) { | ||||
|     const index = lights.findIndex(v => v.id === id); | ||||
|     if (index === -1) { | ||||
|         throw new ReferenceError(`You are going to remove nonexistent light!`); | ||||
|     } | ||||
|     lights.splice(index, 1); | ||||
|     needRefresh = true; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 设置一个光源的信息 | ||||
|  * @param id 光源id | ||||
|  * @param info 光源信息 | ||||
|  */ | ||||
| export function setLight(id: string, info: Partial<Light>) { | ||||
|     if (has(info.id)) delete info.id; | ||||
|     const light = lights.find(v => v.id === id); | ||||
|     if (!light) { | ||||
|         throw new ReferenceError(`You are going to set nonexistent light!`); | ||||
|     } | ||||
|     for (const [p, v] of Object.entries(info)) { | ||||
|         light[p as SelectKey<Light, number>] = v as number; | ||||
|     } | ||||
|     needRefresh = true; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 设置当前的光源列表 | ||||
|  * @param list 光源列表 | ||||
|  */ | ||||
| export function setLightList(list: Light[]) { | ||||
|     lights = list; | ||||
|     needRefresh = true; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 去除所有的光源 | ||||
|  */ | ||||
| export function removeAllLights() { | ||||
|     lights = []; | ||||
|     needRefresh = true; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 获取一个灯光 | ||||
|  * @param id 灯光id | ||||
|  */ | ||||
| export function getLight(id: string) { | ||||
|     return lights.find(v => v.id === id); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 获取所有灯光 | ||||
|  */ | ||||
| export function getAllLights() { | ||||
|     return lights; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 设置背景色 | ||||
|  * @param color 背景色 | ||||
|  */ | ||||
| export function setBackground(color: Color) { | ||||
|     background = color; | ||||
|     needRefresh = true; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 动画改变一个属性的值 | ||||
|  * @param id 灯光id | ||||
|  * @param key 动画属性,x,y,r,decay,颜色请使用animateLightColor(下个版本会加) | ||||
|  * @param n 目标值 | ||||
|  * @param time 动画时间 | ||||
|  * @param mode 动画方式,渐变函数,高级动画提供了大量内置的渐变函数 | ||||
|  * @param relative 相对方式,是绝对还是相对 | ||||
|  */ | ||||
| export function animateLight<K extends Exclude<keyof Light, 'id'>>( | ||||
|     id: string, | ||||
|     key: K, | ||||
|     n: Light[K], | ||||
|     time: number = 1000, | ||||
|     mode: TimingFn = linear(), | ||||
|     relative: boolean = false | ||||
| ) { | ||||
|     const light = getLight(id); | ||||
|     if (!has(light)) { | ||||
|         throw new ReferenceError(`You are going to animate nonexistent light`); | ||||
|     } | ||||
|     if (typeof n !== 'number') { | ||||
|         light[key] = n; | ||||
|     } | ||||
|     const ani = animationList[id] ?? (animationList[id] = new Animation()); | ||||
|     if (typeof ani.value[key] !== 'number') { | ||||
|         ani.register(key, light[key] as number); | ||||
|     } else { | ||||
|         ani.time(0) | ||||
|             .mode(linear()) | ||||
|             .absolute() | ||||
|             .apply(key, light[key] as number); | ||||
|     } | ||||
|     ani.time(time) | ||||
|         .mode(mode) | ||||
|         [relative ? 'relative' : 'absolute']() | ||||
|         .apply(key, n as number); | ||||
|     const start = Date.now(); | ||||
|     const fn = () => { | ||||
|         if (Date.now() - start > time + 50) { | ||||
|             ani.ticker.remove(fn); | ||||
|             light._animating![key] = false; | ||||
|         } | ||||
|         needRefresh = true; | ||||
|         light[key as SelectKey<Light, number>] = ani.value[key]; | ||||
|     }; | ||||
|     ani.ticker.add(fn); | ||||
|     light._animating ??= {}; | ||||
|     light._animating[key] = true; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 把一个属性设置为渐变模式 | ||||
|  * @param id 灯光id | ||||
|  * @param key 渐变的属性 | ||||
|  * @param time 渐变时长 | ||||
|  * @param mode 渐变方式,渐变函数,高级动画提供了大量内置的渐变函数 | ||||
|  */ | ||||
| export function transitionLight<K extends Exclude<keyof Light, 'id'>>( | ||||
|     id: string, | ||||
|     key: K, | ||||
|     time: number = 1000, | ||||
|     mode: TimingFn = linear() | ||||
| ) { | ||||
|     const index = lights.findIndex(v => v.id === id); | ||||
|     if (index === -1) { | ||||
|         throw new ReferenceError(`You are going to transite nonexistent light`); | ||||
|     } | ||||
|     const light = lights[index]; | ||||
|     if (typeof light[key] !== 'number') return; | ||||
|     light._transition ??= {}; | ||||
|     light._transition[key] = { time, mode }; | ||||
|     const tran = transitionList[id] ?? (transitionList[id] = new Transition()); | ||||
|     tran.value[key] = light[key] as number; | ||||
|     if (!light._isProxy) { | ||||
|         const handler: ProxyHandler<Light> = { | ||||
|             set(t, p, v) { | ||||
|                 if (typeof p === 'symbol') return false; | ||||
|                 const start = Date.now(); | ||||
|                 if ( | ||||
|                     !light._transition![p] || | ||||
|                     light._animating?.[key] || | ||||
|                     typeof v !== 'number' | ||||
|                 ) { | ||||
|                     t[p as SelectKey<Light, number>] = v; | ||||
|                     return true; | ||||
|                 } | ||||
|                 // @ts-ignore
 | ||||
|                 t[p] = light[p]; | ||||
|                 const info = light._transition![p]; | ||||
|                 tran.mode(info.mode).time(info.time); | ||||
|                 const fn = () => { | ||||
|                     if (Date.now() - start > info.time + 50) { | ||||
|                         tran.ticker.remove(fn); | ||||
|                     } | ||||
|                     needRefresh = true; | ||||
|                     t[p as SelectKey<Light, number>] = tran.value[key]; | ||||
|                 }; | ||||
|                 tran.ticker.add(fn); | ||||
|                 tran.transition(p, v); | ||||
|                 return true; | ||||
|             } | ||||
|         }; | ||||
|         lights[index] = new Proxy(light, handler); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 移动一个灯光 | ||||
|  * @param id 灯光id | ||||
|  * @param x 目标横坐标 | ||||
|  * @param y 目标纵坐标 | ||||
|  * @param time 移动时间 | ||||
|  * @param mode 移动方式,渐变函数 | ||||
|  * @param relative 相对模式,相对还是绝对 | ||||
|  */ | ||||
| export function moveLight( | ||||
|     id: string, | ||||
|     x: number, | ||||
|     y: number, | ||||
|     time: number = 1000, | ||||
|     mode: TimingFn = linear(), | ||||
|     relative: boolean = false | ||||
| ) { | ||||
|     animateLight(id, 'x', x, time, mode, relative); | ||||
|     animateLight(id, 'y', y, time, mode, relative); | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 以一个路径移动光源 | ||||
|  * @param id 灯光id | ||||
|  * @param time 移动时长 | ||||
|  * @param path 移动路径 | ||||
|  * @param mode 移动方式,渐变函数,表示移动的完成度 | ||||
|  * @param relative 相对模式,相对还是绝对 | ||||
|  */ | ||||
| export function moveLightAs( | ||||
|     id: string, | ||||
|     time: number, | ||||
|     path: PathFn, | ||||
|     mode: TimingFn = linear(), | ||||
|     relative: boolean = true | ||||
| ) { | ||||
|     const light = getLight(id); | ||||
|     if (!has(light)) { | ||||
|         throw new ReferenceError(`You are going to animate nonexistent light`); | ||||
|     } | ||||
|     const ani = animationList[id] ?? (animationList[id] = new Animation()); | ||||
|     ani.mode(linear()).time(0).move(light.x, light.y); | ||||
|     ani.time(time) | ||||
|         .mode(mode) | ||||
|         [relative ? 'relative' : 'absolute']() | ||||
|         .moveAs(path); | ||||
|     const start = Date.now(); | ||||
|     const fn = () => { | ||||
|         if (Date.now() - start > time + 50) { | ||||
|             ani.ticker.remove(fn); | ||||
|             light._animating!.x = false; | ||||
|             light._animating!.y = false; | ||||
|         } | ||||
|         needRefresh = true; | ||||
|         light.x = ani.x; | ||||
|         light.y = ani.y; | ||||
|     }; | ||||
|     ani.ticker.add(fn); | ||||
|     light._animating ??= {}; | ||||
|     light._animating.x = true; | ||||
|     light._animating.y = true; | ||||
| } | ||||
| 
 | ||||
| export function animateLightColor( | ||||
|     id: string, | ||||
|     target: Color, | ||||
|     time: number = 1000, | ||||
|     mode: TimingFn = linear() | ||||
| ) { | ||||
|     // todo
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 根据坐标数组设置物体节点 | ||||
|  * @param nodes 坐标数组 | ||||
|  */ | ||||
| export function setShadowNodes(nodes: LocArr[][]): void; | ||||
| /** | ||||
|  * 根据多边形数组设置物体节点 | ||||
|  * @param nodes 多边形数组 | ||||
|  */ | ||||
| export function setShadowNodes(nodes: Polygon[]): void; | ||||
| export function setShadowNodes(nodes: LocArr[][] | Polygon[]) { | ||||
|     if (nodes.length === 0) { | ||||
|         shadowNodes = []; | ||||
|         needRefresh = true; | ||||
|     } | ||||
|     if (nodes[0] instanceof Polygon) shadowNodes = nodes as Polygon[]; | ||||
|     else shadowNodes = Polygon.from(...(nodes as LocArr[][])); | ||||
|     needRefresh = true; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 根据坐标数组添加物体节点 | ||||
|  * @param polygons 坐标数组 | ||||
|  */ | ||||
| export function addPolygon(...polygons: LocArr[][]): void; | ||||
| /** | ||||
|  * 根据多边形数组添加物体节点 | ||||
|  * @param polygons 多边形数组 | ||||
|  */ | ||||
| export function addPolygon(...polygons: Polygon[]): void; | ||||
| export function addPolygon(...polygons: Polygon[] | LocArr[][]) { | ||||
|     if (polygons.length === 0) return; | ||||
|     if (polygons[0] instanceof Polygon) | ||||
|         shadowNodes.push(...(polygons as Polygon[])); | ||||
|     else shadowNodes.push(...Polygon.from(...(polygons as LocArr[][]))); | ||||
|     needRefresh = true; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 设置光源的虚化程度 | ||||
|  * @param n 虚化程度 | ||||
|  */ | ||||
| export function setBlur(n: number) { | ||||
|     blur = n; | ||||
|     canvas.style.filter = `blur(${n}px)`; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * 绘制阴影 | ||||
|  */ | ||||
| export function drawShadow() { | ||||
|     const w = core._PX_ ?? core.__PIXELS__; | ||||
|     const h = core._PY_ ?? core.__PIXELS__; | ||||
|     needRefresh = false; | ||||
|     ctx.clearRect(0, 0, w, h); | ||||
|     ct1.clearRect(0, 0, w, h); | ||||
|     ct2.clearRect(0, 0, w, h); | ||||
|     ct3.clearRect(0, 0, w, h); | ||||
| 
 | ||||
|     const b = core.arrayToRGBA(background); | ||||
|     ctx.globalCompositeOperation = 'source-over'; | ||||
|     ct3.globalCompositeOperation = 'source-over'; | ||||
| 
 | ||||
|     // 绘制阴影,一个光源一个光源地绘制,然后source-out获得光,然后把光叠加,再source-out获得最终阴影
 | ||||
|     for (let i = 0; i < lights.length; i++) { | ||||
|         const { x, y, r, decay, color, noShelter } = lights[i]; | ||||
|         // 绘制阴影
 | ||||
|         ct1.clearRect(0, 0, w, h); | ||||
|         ct2.clearRect(0, 0, w, h); | ||||
|         if (!noShelter) { | ||||
|             for (const polygon of shadowNodes) { | ||||
|                 const area = polygon.shadowArea(x, y, r); | ||||
|                 area.forEach(v => { | ||||
|                     ct1.beginPath(); | ||||
|                     ct1.moveTo(v[0][0], v[0][1]); | ||||
|                     for (let i = 1; i < v.length; i++) { | ||||
|                         ct1.lineTo(v[i][0], v[i][1]); | ||||
|                     } | ||||
|                     ct1.closePath(); | ||||
|                     ct1.fillStyle = '#000'; | ||||
|                     ct1.globalCompositeOperation = 'source-over'; | ||||
|                     ct1.fill(); | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|         // 存入ct2,用于绘制真实阴影
 | ||||
|         ct2.globalCompositeOperation = 'source-over'; | ||||
|         ct2.drawImage(temp1, 0, 0, w, h); | ||||
|         ct2.globalCompositeOperation = 'source-out'; | ||||
|         const gra = ct2.createRadialGradient(x, y, decay, x, y, r); | ||||
|         gra.addColorStop(0, core.arrayToRGBA(color)); | ||||
|         gra.addColorStop(1, 'transparent'); | ||||
|         ct2.fillStyle = gra; | ||||
|         ct2.beginPath(); | ||||
|         ct2.arc(x, y, r, 0, Math.PI * 2); | ||||
|         ct2.fill(); | ||||
|         ctx.drawImage(temp2, 0, 0, w, h); | ||||
|         // 再绘制ct1的阴影,然后绘制到ct3叠加
 | ||||
|         ct1.globalCompositeOperation = 'source-out'; | ||||
|         const gra2 = ct1.createRadialGradient(x, y, decay, x, y, r); | ||||
|         gra2.addColorStop(0, '#fff'); | ||||
|         gra2.addColorStop(1, '#fff0'); | ||||
|         ct1.beginPath(); | ||||
|         ct1.arc(x, y, r, 0, Math.PI * 2); | ||||
|         ct1.fillStyle = gra2; | ||||
|         ct1.fill(); | ||||
|         // 绘制到ct3上
 | ||||
|         ct3.drawImage(temp1, 0, 0, w, h); | ||||
|     } | ||||
|     // 绘制真实阴影
 | ||||
|     ct3.globalCompositeOperation = 'source-out'; | ||||
|     ct3.fillStyle = b; | ||||
|     ct3.fillRect(0, 0, w, h); | ||||
|     ctx.globalCompositeOperation = 'destination-over'; | ||||
|     ctx.drawImage(temp3, 0, 0, w, h); | ||||
| } | ||||
							
								
								
									
										8
									
								
								src/plugin/webgl/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/plugin/webgl/utils.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| export default function init() { | ||||
|     return { isWebGLSupported }; | ||||
| } | ||||
| 
 | ||||
| export const isWebGLSupported = (function () { | ||||
|     const canvas = document.createElement('canvas'); | ||||
|     return !!canvas.getContext('webgl'); | ||||
| })(); | ||||
							
								
								
									
										2
									
								
								src/types/map.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/types/map.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -61,7 +61,7 @@ interface Block<N extends Exclude<AllNumbers, 0> = Exclude<AllNumbers, 0>> { | ||||
|         /** | ||||
|          * 图块是否不可通行 | ||||
|          */ | ||||
|         nopass: boolean; | ||||
|         noPass: boolean; | ||||
| 
 | ||||
|         /** | ||||
|          * 图块高度 | ||||
|  | ||||
							
								
								
									
										7
									
								
								src/types/util.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								src/types/util.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -862,6 +862,11 @@ type SelectType<R, T> = { | ||||
|     [P in keyof R as R[P] extends T ? P : never]: R[P]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 从一个对象中选择类型是目标属性的键名 | ||||
|  */ | ||||
| type SelectKey<R, T> = keyof SelectType<R, T>; | ||||
| 
 | ||||
| /** | ||||
|  * 获取一段字符串的第一个字符 | ||||
|  */ | ||||
| @ -883,3 +888,5 @@ type NonObjectOf<T> = SelectType<T, NonObject>; | ||||
|  * 以一个字符串结尾 | ||||
|  */ | ||||
| type EndsWith<T extends string> = `${string}${T}`; | ||||
| 
 | ||||
| type KeyExcludesUnderline<T> = Excluede<keyof T, `_${string}`>; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user