import { coverRadius, fillRectangle } from "../base/2d"; import { Animator } from "../base/Animator"; import { m4 } from "../base/m4"; import { Disposable, Scene } from "../base/Scene"; import { createProgram, createShader } from "../base/utils"; import { Area, Color } from "./common"; import { FillerData } from "./FillerData"; export class AnimatingArea { constructor( readonly area: Area, readonly x: number, readonly y: number, public progress: number, ) {} } export class AnimatableMask implements Disposable { private vertexShaderCode = /*glsl*/ ` attribute vec2 a_position; attribute vec2 a_texCoord; attribute vec4 a_areaId; attribute vec2 a_center; attribute float a_progress; attribute float a_maxRadius; uniform mat4 u_matrix; varying vec4 v_areaId; varying vec2 v_center; varying float v_progress; varying float v_maxRadius; varying vec2 v_texCoord; varying vec2 v_position; void main() { gl_Position = u_matrix * vec4(a_position, 0, 1); v_areaId = a_areaId; v_center = a_center; v_progress = a_progress; v_maxRadius = a_maxRadius; v_texCoord = a_texCoord; v_position = a_position; } `; private fragmentShaderCode = /*glsl*/ ` precision mediump float; uniform sampler2D u_map; //uniform sampler2D u_colored; //uniform sampler2D u_mask; varying vec4 v_areaId; varying vec2 v_center; varying float v_progress; varying float v_maxRadius; varying vec2 v_texCoord; varying vec2 v_position; void main() { vec4 map = texture2D(u_map, v_texCoord); float dist = distance(map, v_areaId); /* if(dist < 0.001) { if(v_progress >= 1.0 ) { gl_FragColor = vec4(1, 1, 0, 1); }else{ float dist2 = distance(v_position, v_center); if(dist2 < v_maxRadius * v_progress) { gl_FragColor = vec4(1, 1, 0, 1); }else{ gl_FragColor = vec4(0, 0, 0, 0); } } }else{ gl_FragColor = vec4(0, 0, 0, 0); } */ //gl_FragColor = vec4(1, 1, 0, 1); //gl_FragColor = map; vec4 colored = vec4(1, 1, 0, 1); if(dist < 0.001) { if( v_progress < 1.0 ) { float dist2 = distance(v_position, v_center); float r = v_maxRadius * v_progress + 0.001; if(dist2 < r) { float f = dist2 / r; if(f < v_progress) { gl_FragColor = colored; }else if(v_progress < 1.0){ float a = (f - 1.0) / (v_progress - 1.0); gl_FragColor = vec4(colored.xyz, a); }else{ gl_FragColor = colored; } }else { gl_FragColor = vec4(0,0,0,0); } }else{ gl_FragColor = colored; } }else{ gl_FragColor = vec4(0, 0, 0, 0); } } `; program: WebGLProgram; aPositionLoc: number; aTexcoordLoc: number; aAreaIdLoc: number; aCenterLoc: number; aProgressLoc: number; aMaxRadiusLoc: number; uMatrixLoc: WebGLUniformLocation; positionBuffer: WebGLBuffer; texCoordBuffer: WebGLBuffer; areaIdBuffer: WebGLBuffer; centerBuffer: WebGLBuffer; progressBuffer: WebGLBuffer; maxRadiusBuffer: WebGLBuffer; dispose(): void { let gl = this.scene.gl; gl.deleteBuffer(this.positionBuffer); gl.deleteBuffer(this.texCoordBuffer); gl.deleteBuffer(this.areaIdBuffer); gl.deleteBuffer(this.centerBuffer); gl.deleteBuffer(this.progressBuffer); gl.deleteBuffer(this.maxRadiusBuffer); gl.deleteProgram(this.program); } positionArray: Float32Array; texCoordArray: Float32Array; areaIdArray: Float32Array; centerArray: Float32Array; progressArray: Float32Array; maxRadiusArray: Float32Array; matrix: m4.Matrix4; texture: WebGLTexture; fb: WebGLFramebuffer; private animatingAreas: Array = []; get width(): number { return this.fillerData.width; } get height(): number { return this.fillerData.height; } maxCount: number; constructor( private scene: Scene, private fillerData: FillerData, ) { const gl = scene.gl; this.program = createProgram( gl, createShader(gl, gl.VERTEX_SHADER, this.vertexShaderCode)!, createShader(gl, gl.FRAGMENT_SHADER, this.fragmentShaderCode)!, )!; this.aPositionLoc = gl.getAttribLocation(this.program, "a_position"); this.aTexcoordLoc = gl.getAttribLocation(this.program, "a_texCoord"); this.aAreaIdLoc = gl.getAttribLocation(this.program, "a_areaId"); this.aCenterLoc = gl.getAttribLocation(this.program, "a_center"); this.aProgressLoc = gl.getAttribLocation(this.program, "a_progress"); this.aMaxRadiusLoc = gl.getAttribLocation(this.program, "a_maxRadius"); this.uMatrixLoc = gl.getUniformLocation(this.program, "u_matrix")!; // 此处maxCount 等于分组最大区块数并不合理,假设每个颜色分组都是一个区块, 此处maxCount会等于1, 如果用户快速点击多个区块,会造成上一个区块的晕染无法完成 // this.maxCount = fillerData.data.maxAreaCountOfGroup; this.maxCount = 5; this.positionBuffer = gl.createBuffer()!; this.texCoordBuffer = gl.createBuffer()!; this.areaIdBuffer = gl.createBuffer()!; this.centerBuffer = gl.createBuffer()!; this.progressBuffer = gl.createBuffer()!; this.maxRadiusBuffer = gl.createBuffer()!; this.matrix = m4.projectionNoflipY(fillerData.width, fillerData.height); this.texture = gl.createTexture()!; gl.bindTexture(gl.TEXTURE_2D, this.texture); gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, this.fillerData.width, this.fillerData.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null, ); //gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, this.fillerData.width, this.fillerData.height, 0, gl.RGB, gl.UNSIGNED_SHORT_5_6_5, null) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); this.fb = gl.createFramebuffer()!; gl.bindFramebuffer(gl.FRAMEBUFFER, this.fb); gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0, ); gl.bindFramebuffer(gl.FRAMEBUFFER, null); this.positionArray = new Float32Array(this.maxCount * 12); this.texCoordArray = new Float32Array(this.maxCount * 12); this.areaIdArray = new Float32Array(this.maxCount * 24); this.centerArray = new Float32Array(this.maxCount * 12); this.progressArray = new Float32Array(this.maxCount * 6); this.maxRadiusArray = new Float32Array(this.maxCount * 6); } addArea(area: Area, cx: number, cy: number, duration = 800) { let aa = new AnimatingArea(area, cx, cy, 0); let animator = new Animator( duration, () => { aa.progress = animator.value(); }, () => {}, ); this.scene.addAnimator(animator); this.animatingAreas.push(aa); } fillPoint( arr: Float32Array, offset: number, x: number, y: number, count: number, ) { for (var i = 0; i < count; i++) { arr[offset + i * 2] = x; arr[offset + i * 2 + 1] = y; } } fillNumber(arr: Float32Array, offset: number, n: number, count: number) { for (var i = 0; i < count; i++) { arr[offset + i] = n; } } flush() { console.log("animationAreas.length=", this.animatingAreas.length); if (this.animatingAreas.length <= 0) return; var vertexOffset = 0; var colorOffset = 0; var nOffset = 0; const width = this.fillerData.width; const height = this.fillerData.height; for (var i = 0; i < this.animatingAreas.length; i++) { var aa = this.animatingAreas[i]; var area = aa.area; fillRectangle( this.positionArray, vertexOffset, area.rect.x, area.rect.y, area.rect.width, area.rect.height, ); fillRectangle( this.texCoordArray, vertexOffset, area.rect.x / width, area.rect.y / height, area.rect.width / width, area.rect.height / height, ); this.fillPoint(this.centerArray, vertexOffset, aa.x, aa.y, 6); var color = new Color(area.id); color.fillFloatArray(this.areaIdArray, colorOffset, 6); var maxRadius = coverRadius(area.rect, aa.x, aa.y); // console.log('rect=', area.rect, maxRadius, aa.x, aa.y); //maxRadius = this.fillNumber(this.progressArray, nOffset, aa.progress, 6); this.fillNumber(this.maxRadiusArray, nOffset, maxRadius, 6); vertexOffset += 12; colorOffset += 24; nOffset += 6; } //console.log('areaIdArray', this.areaIdArray) const gl = this.scene.gl; gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.positionArray, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.texCoordArray, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.areaIdBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.areaIdArray, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.centerBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.centerArray, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.progressBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.progressArray, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.maxRadiusBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.maxRadiusArray, gl.STATIC_DRAW); this.draw(this.animatingAreas.length * 6); this.animatingAreas = this.animatingAreas.filter((aa) => aa.progress < 1); } private draw(n: number) { const gl = this.scene.gl; gl.bindFramebuffer(gl.FRAMEBUFFER, this.fb); //console.log('fb=', this.progressArray) gl.useProgram(this.program); gl.viewport(0, 0, this.fillerData.width, this.fillerData.height); gl.enableVertexAttribArray(this.aPositionLoc); gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); gl.vertexAttribPointer(this.aPositionLoc, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(this.aTexcoordLoc); gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer); gl.vertexAttribPointer(this.aTexcoordLoc, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(this.aAreaIdLoc); gl.bindBuffer(gl.ARRAY_BUFFER, this.areaIdBuffer); gl.vertexAttribPointer(this.aAreaIdLoc, 4, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(this.aCenterLoc); gl.bindBuffer(gl.ARRAY_BUFFER, this.centerBuffer); gl.vertexAttribPointer(this.aCenterLoc, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(this.aProgressLoc); gl.bindBuffer(gl.ARRAY_BUFFER, this.progressBuffer); gl.vertexAttribPointer(this.aProgressLoc, 1, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(this.aMaxRadiusLoc); gl.bindBuffer(gl.ARRAY_BUFFER, this.maxRadiusBuffer); gl.vertexAttribPointer(this.aMaxRadiusLoc, 1, gl.FLOAT, false, 0, 0); gl.uniformMatrix4fv(this.uMatrixLoc, false, this.matrix); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.fillerData.mapTexure); gl.drawArrays(gl.TRIANGLES, 0, n); gl.bindFramebuffer(gl.FRAMEBUFFER, null); } // 恢复上次已填色的部分 initTask() { let taskLisk = this.fillerData.taskList; const width = this.fillerData.width; const height = this.fillerData.height; for (let task of taskLisk) { let area = this.fillerData.data.areaHash.get(task); if (area) { fillRectangle( this.positionArray, 0, area.rect.x, area.rect.y, area.rect.width, area.rect.height, ); fillRectangle( this.texCoordArray, 0, area.rect.x / width, area.rect.y / height, area.rect.width / width, area.rect.height / height, ); this.fillPoint(this.centerArray, 0, area.center.x, area.center.y, 6); var color = new Color(area.id); color.fillFloatArray(this.areaIdArray, 0, 6); var maxRadius = coverRadius(area.rect, area.center.x, area.center.y); // console.log('rect=', area.rect, maxRadius, area.center.x, area.center.y); this.fillNumber(this.progressArray, 0, 1, 6); this.fillNumber(this.maxRadiusArray, 0, maxRadius, 6); const gl = this.scene.gl; gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.positionArray, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.texCoordBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.texCoordArray, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.areaIdBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.areaIdArray, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.centerBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.centerArray, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.progressBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.progressArray, gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, this.maxRadiusBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.maxRadiusArray, gl.STATIC_DRAW); this.draw(6); } } } }