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")! this.maxCount = fillerData.data.maxAreaCountOfGroup 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) } } } }