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
					
				
							
								
								
									
										4
									
								
								idea.md
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								idea.md
									
									
									
									
									
								
							| @ -26,6 +26,10 @@ | |||||||
| 
 | 
 | ||||||
| ### 第三章 战争 | ### 第三章 战争 | ||||||
| 
 | 
 | ||||||
|  | #### 技能 | ||||||
|  | 
 | ||||||
|  | 闪避:每 M 回合闪避一次,减少 N%的伤害 | ||||||
|  | 
 | ||||||
| ## 机制 | ## 机制 | ||||||
| 
 | 
 | ||||||
| ### 通用 | ### 通用 | ||||||
|  | |||||||
| @ -3202,6 +3202,7 @@ maps.prototype.removeBlock = function (x, y, floorId) { | |||||||
|         const block = blocks[i]; |         const block = blocks[i]; | ||||||
|         this.removeBlockByIndex(i, floorId); |         this.removeBlockByIndex(i, floorId); | ||||||
|         this._removeBlockFromMap(floorId, block); |         this._removeBlockFromMap(floorId, block); | ||||||
|  |         core.updateShadow(true); | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|     return false; |     return false; | ||||||
| @ -3364,6 +3365,7 @@ maps.prototype.setBlock = function (number, x, y, floorId, noredraw) { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     core.updateShadow(true); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| maps.prototype.animateSetBlock = function ( | maps.prototype.animateSetBlock = function ( | ||||||
|  | |||||||
| @ -4226,7 +4226,8 @@ ui.prototype.deleteCanvas = function (name) { | |||||||
| 
 | 
 | ||||||
| ////// 删除所有动态canvas //////
 | ////// 删除所有动态canvas //////
 | ||||||
| ui.prototype.deleteAllCanvas = function () { | ui.prototype.deleteAllCanvas = function () { | ||||||
|     return this.deleteCanvas(function () { |     this.deleteCanvas(function () { | ||||||
|         return true; |         return true; | ||||||
|     }); |     }); | ||||||
|  |     if (main.mode === 'play' && !core.isReplaying()) core.initShadowCanvas(); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -31,6 +31,13 @@ main.floors.MT42= | |||||||
|                 5, |                 5, | ||||||
|                 0 |                 0 | ||||||
|             ] |             ] | ||||||
|  |         }, | ||||||
|  |         "8,12": { | ||||||
|  |             "floorId": "MT46", | ||||||
|  |             "loc": [ | ||||||
|  |                 7, | ||||||
|  |                 8 | ||||||
|  |             ] | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     "beforeBattle": {}, |     "beforeBattle": {}, | ||||||
|  | |||||||
| @ -26,20 +26,46 @@ main.floors.MT46= | |||||||
|     "cannotMove": {}, |     "cannotMove": {}, | ||||||
|     "cannotMoveIn": {}, |     "cannotMoveIn": {}, | ||||||
|     "map": [ |     "map": [ | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  1,  1,  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  1,  0,  0,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  1], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  0,  0,  0,  1,  0,  0,  0,  1,  1,  0,  0,  1,  0,  0,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  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,  0,  0,  0,  0], |     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  0,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  1,  1,  0,  0,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0], |     [  0,  0,  1,  0,  0,  0,  1,  1,  1,  1,  1,  0,  0,  0,  0], | ||||||
|     [  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  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,  0,  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.status.floorId ---------- //
 | ||||||
|             core.drawMap(floorId); |             core.drawMap(floorId); | ||||||
| 
 | 
 | ||||||
|  |             core.updateShadow(); | ||||||
|  | 
 | ||||||
|             // 切换楼层BGM
 |             // 切换楼层BGM
 | ||||||
|             if (core.status.maps[floorId].bgm) { |             if (core.status.maps[floorId].bgm) { | ||||||
|                 var bgm = core.status.maps[floorId].bgm; |                 var bgm = core.status.maps[floorId].bgm; | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ | |||||||
| 
 | 
 | ||||||
| var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = { | var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = { | ||||||
|     init: function () { |     init: function () { | ||||||
|  |         // 只看插件没用,插件是与vite样板高度融合的,所以要看的话就在游戏内的百科全书-关于游戏内点那个开源地址吧
 | ||||||
|         this._afterLoadResources = function () { |         this._afterLoadResources = function () { | ||||||
|             if (!main.replayChecking && main.mode === 'play') { |             if (!main.replayChecking && main.mode === 'play') { | ||||||
|                 main.forward(); |                 main.forward(); | ||||||
| @ -770,12 +771,7 @@ var plugins_bb40132b_638b_4a9f_b028_d3fe47acc8d1 = { | |||||||
|             core.status.hero = new Proxy(hero, handler); |             core.status.hero = new Proxy(hero, handler); | ||||||
| 
 | 
 | ||||||
|             core.status.maps[floorId].blocks.forEach(function (block) { |             core.status.maps[floorId].blocks.forEach(function (block) { | ||||||
|                 if ( |                 if (block.event.cls !== 'items' || block.disable) return; | ||||||
|                     block.event.cls !== 'items' || |  | ||||||
|                     block.event.id === 'superPotion' || |  | ||||||
|                     block.disable |  | ||||||
|                 ) |  | ||||||
|                     return; |  | ||||||
|                 const x = block.x, |                 const x = block.x, | ||||||
|                     y = block.y; |                     y = block.y; | ||||||
|                 // v2优化,只绘制范围内的部分
 |                 // v2优化,只绘制范围内的部分
 | ||||||
|  | |||||||
| @ -11,6 +11,9 @@ import chapter from './plugin/ui/chapter'; | |||||||
| import fly from './plugin/ui/fly'; | import fly from './plugin/ui/fly'; | ||||||
| import chase from './plugin/chase/chase'; | import chase from './plugin/chase/chase'; | ||||||
| import fixed from './plugin/ui/fixed'; | 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() { | function forward() { | ||||||
|     // 每个引入的插件都要在这里执行,否则不会被转发
 |     // 每个引入的插件都要在这里执行,否则不会被转发
 | ||||||
| @ -26,7 +29,10 @@ function forward() { | |||||||
|         chapter(), |         chapter(), | ||||||
|         fly(), |         fly(), | ||||||
|         chase(), |         chase(), | ||||||
|         fixed() |         fixed(), | ||||||
|  |         webglUtils(), | ||||||
|  |         shadow(), | ||||||
|  |         gameShadow() | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     // 初始化所有插件,并转发到core上
 |     // 初始化所有插件,并转发到core上
 | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | /// <reference path="../types/core.d.ts" />
 | ||||||
| import { message } from 'ant-design-vue'; | import { message } from 'ant-design-vue'; | ||||||
| import { MessageApi } from 'ant-design-vue/lib/message'; | import { MessageApi } from 'ant-design-vue/lib/message'; | ||||||
| import { isNil } from 'lodash'; | import { isNil } from 'lodash'; | ||||||
| @ -69,7 +70,7 @@ export function keycode(key: number) { | |||||||
|  * @param css 要解析的css字符串 |  * @param css 要解析的css字符串 | ||||||
|  */ |  */ | ||||||
| export function parseCss(css: string): Partial<Record<CanParseCss, string>> { | 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 styles = str.split(';'); | ||||||
|     const res: Partial<Record<CanParseCss, string>> = {}; |     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]; |     [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 EndsWith<T extends string> = `${string}${T}`; | ||||||
|  | 
 | ||||||
|  | type KeyExcludesUnderline<T> = Excluede<keyof T, `_${string}`>; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user