diff --git a/.gitignore b/.gitignore index 25ea3d2e..45c23200 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ Cargo.lock # VSCode .vscode +*.code-workspace diff --git a/demo/client/src/meshes.ts b/demo/client/src/meshes.ts index 5859e42e..18db9753 100644 --- a/demo/client/src/meshes.ts +++ b/demo/client/src/meshes.ts @@ -403,7 +403,9 @@ export class PathfinderMeshData implements Meshes, MeshDataCounts, const expandedPathID = newPathIndex + 1; const originalPathID = pathIDs[newPathIndex]; - const bVertexCopyResult = copyVertices(['bVertexPositions', 'bVertexLoopBlinnData'], + const bVertexCopyResult = copyVertices(['bVertexPositions', + 'bVertexLoopBlinnData', + 'bVertexNormals'], 'bVertexPathRanges', expandedArrays, expandedRanges, diff --git a/demo/client/src/renderer.ts b/demo/client/src/renderer.ts index a728a3a4..d83fc730 100644 --- a/demo/client/src/renderer.ts +++ b/demo/client/src/renderer.ts @@ -285,6 +285,12 @@ export abstract class Renderer { this.pathColorsBufferTextures[meshIndex].bind(gl, uniforms, textureUnit); } + setEmboldenAmountUniform(objectIndex: number, uniforms: UniformMap): void { + const gl = this.renderContext.gl; + const emboldenAmount = this.emboldenAmount; + gl.uniform2f(uniforms.uEmboldenAmount, emboldenAmount[0], emboldenAmount[1]); + } + renderTaskTypeForObject(objectIndex: number): RenderTaskType { return 'color'; } @@ -420,14 +426,14 @@ export abstract class Renderer { this.setFramebufferSizeUniform(directInteriorProgram.uniforms); this.setHintsUniform(directInteriorProgram.uniforms); this.setPathColorsUniform(objectIndex, directInteriorProgram.uniforms, 0); + this.setEmboldenAmountUniform(objectIndex, directInteriorProgram.uniforms); this.pathTransformBufferTextures[meshIndex] .bind(gl, directInteriorProgram.uniforms, 1); if (renderingMode === 'color-depth') { const strategy = antialiasingStrategy as MCAAMulticolorStrategy; strategy.bindEdgeDepthTexture(gl, directInteriorProgram.uniforms, 2); } - const coverInteriorRange = getMeshIndexRange(meshes.coverInteriorIndexRanges, - pathRange); + const coverInteriorRange = getMeshIndexRange(meshes.coverInteriorIndexRanges, pathRange); if (!this.pathIDsAreInstanced) { gl.drawElements(gl.TRIANGLES, coverInteriorRange.length, @@ -458,10 +464,8 @@ export abstract class Renderer { // TODO(pcwalton): Cache these. const directCurveProgramName = this.directCurveProgramName(); const directCurveProgram = renderContext.shaderPrograms[directCurveProgramName]; - if (this.implicitCoverCurveVAO == null) { - this.implicitCoverCurveVAO = renderContext.vertexArrayObjectExt - .createVertexArrayOES(); - } + if (this.implicitCoverCurveVAO == null) + this.implicitCoverCurveVAO = renderContext.vertexArrayObjectExt.createVertexArrayOES(); renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.implicitCoverCurveVAO); this.initImplicitCoverCurveVAO(objectIndex, instanceRange); @@ -470,6 +474,7 @@ export abstract class Renderer { this.setFramebufferSizeUniform(directCurveProgram.uniforms); this.setHintsUniform(directCurveProgram.uniforms); this.setPathColorsUniform(objectIndex, directCurveProgram.uniforms, 0); + this.setEmboldenAmountUniform(objectIndex, directCurveProgram.uniforms); this.pathTransformBufferTextures[meshIndex].bind(gl, directCurveProgram.uniforms, 1); if (renderingMode === 'color-depth') { const strategy = antialiasingStrategy as MCAAMulticolorStrategy; @@ -592,10 +597,18 @@ export abstract class Renderer { false, B_LOOP_BLINN_DATA_SIZE, B_LOOP_BLINN_DATA_SIGN_OFFSET); + gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bVertexNormals); + gl.vertexAttribPointer(directCurveProgram.attributes.aNormalAngle, + 1, + gl.FLOAT, + false, + FLOAT32_SIZE, + 0); gl.enableVertexAttribArray(directCurveProgram.attributes.aPosition); gl.enableVertexAttribArray(directCurveProgram.attributes.aTexCoord); gl.enableVertexAttribArray(directCurveProgram.attributes.aPathID); gl.enableVertexAttribArray(directCurveProgram.attributes.aSign); + gl.enableVertexAttribArray(directCurveProgram.attributes.aNormalAngle); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, meshes.coverCurveIndices); } @@ -632,8 +645,17 @@ export abstract class Renderer { .vertexAttribDivisorANGLE(directInteriorProgram.attributes.aPathID, 1); } + gl.bindBuffer(gl.ARRAY_BUFFER, meshes.bVertexNormals); + gl.vertexAttribPointer(directInteriorProgram.attributes.aNormalAngle, + 1, + gl.FLOAT, + false, + FLOAT32_SIZE, + 0); + gl.enableVertexAttribArray(directInteriorProgram.attributes.aPosition); gl.enableVertexAttribArray(directInteriorProgram.attributes.aPathID); + gl.enableVertexAttribArray(directInteriorProgram.attributes.aNormalAngle); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, meshes.coverInteriorIndices); } diff --git a/demo/client/src/xcaa-strategy.ts b/demo/client/src/xcaa-strategy.ts index a326cb54..34d174f3 100644 --- a/demo/client/src/xcaa-strategy.ts +++ b/demo/client/src/xcaa-strategy.ts @@ -221,7 +221,7 @@ export abstract class XCAAStrategy extends AntialiasingStrategy { this.setAADepthState(renderer); } - protected setAAUniforms(renderer: Renderer, uniforms: UniformMap): void { + protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void { const renderContext = renderer.renderContext; const gl = renderContext.gl; @@ -395,7 +395,7 @@ export abstract class MCAAStrategy extends XCAAStrategy { gl.useProgram(lineProgram.program); const uniforms = lineProgram.uniforms; - this.setAAUniforms(renderer, uniforms); + this.setAAUniforms(renderer, uniforms, objectIndex); for (const direction of DIRECTIONS) { const vao = this.lineVAOs[direction]; @@ -431,7 +431,7 @@ export abstract class MCAAStrategy extends XCAAStrategy { gl.useProgram(curveProgram.program); const uniforms = curveProgram.uniforms; - this.setAAUniforms(renderer, uniforms); + this.setAAUniforms(renderer, uniforms, objectIndex); for (const direction of DIRECTIONS) { const vao = this.curveVAOs[direction]; @@ -687,7 +687,7 @@ export abstract class MCAAStrategy extends XCAAStrategy { const coverProgram = renderContext.shaderPrograms.mcaaCover; gl.useProgram(coverProgram.program); renderContext.vertexArrayObjectExt.bindVertexArrayOES(this.coverVAO); - this.setAAUniforms(renderer, coverProgram.uniforms); + this.setAAUniforms(renderer, coverProgram.uniforms, objectIndex); const bQuadRange = renderer.meshData[meshIndex].bQuadPathRanges; const count = calculateCountFromIndexRanges(pathRange, bQuadRange); @@ -751,12 +751,9 @@ export class ECAAStrategy extends XCAAStrategy { this.antialiasCurvesOfObject(renderer, objectIndex); } - protected setAAUniforms(renderer: Renderer, uniforms: UniformMap): void { - super.setAAUniforms(renderer, uniforms); - - const renderContext = renderer.renderContext; - const emboldenAmount = renderer.emboldenAmount; - renderContext.gl.uniform2f(uniforms.uEmboldenAmount, emboldenAmount[0], emboldenAmount[1]); + protected setAAUniforms(renderer: Renderer, uniforms: UniformMap, objectIndex: number): void { + super.setAAUniforms(renderer, uniforms, objectIndex); + renderer.setEmboldenAmountUniform(objectIndex, uniforms); } protected getResolveProgram(renderContext: RenderContext): PathfinderShaderProgram { @@ -929,7 +926,7 @@ export class ECAAStrategy extends XCAAStrategy { gl.useProgram(lineProgram.program); const uniforms = lineProgram.uniforms; - this.setAAUniforms(renderer, uniforms); + this.setAAUniforms(renderer, uniforms, objectIndex); const vao = this.lineVAO; renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); @@ -957,7 +954,7 @@ export class ECAAStrategy extends XCAAStrategy { gl.useProgram(curveProgram.program); const uniforms = curveProgram.uniforms; - this.setAAUniforms(renderer, uniforms); + this.setAAUniforms(renderer, uniforms, objectIndex); const vao = this.curveVAO; renderContext.vertexArrayObjectExt.bindVertexArrayOES(vao); diff --git a/partitioner/src/partitioner.rs b/partitioner/src/partitioner.rs index 802df4bf..d79f84b8 100644 --- a/partitioner/src/partitioner.rs +++ b/partitioner/src/partitioner.rs @@ -177,10 +177,24 @@ impl<'a> Partitioner<'a> { let next_active_edge_index = self.find_point_between_active_edges(endpoint_index); let endpoint = &self.endpoints[endpoint_index as usize]; - self.emit_b_quads_around_active_edge(next_active_edge_index, endpoint.position.x); + let emission_result = self.emit_b_quads_around_active_edge(next_active_edge_index, + endpoint.position.x); self.add_new_edges_for_min_point(endpoint_index, next_active_edge_index); + // Add supporting interior triangles if necessary. + match emission_result { + BQuadEmissionResult::BQuadEmittedAbove | BQuadEmissionResult::BQuadEmittedAround => { + self.add_supporting_interior_triangle(next_active_edge_index, + next_active_edge_index - 1, + next_active_edge_index + 2); + self.add_supporting_interior_triangle(next_active_edge_index + 1, + next_active_edge_index - 1, + next_active_edge_index + 2); + } + _ => {} + } + let prev_endpoint_index = self.prev_endpoint_of(endpoint_index); let next_endpoint_index = self.next_endpoint_of(endpoint_index); let new_point = self.create_point_from_endpoint(next_endpoint_index); @@ -277,8 +291,28 @@ impl<'a> Partitioner<'a> { // TODO(pcwalton): Collapse the two duplicate endpoints that this will create together if // possible (i.e. if they have the same parity). - self.emit_b_quads_around_active_edge(active_edge_indices[0], endpoint.position.x); - self.emit_b_quads_around_active_edge(active_edge_indices[1], endpoint.position.x); + let b_quad_emission_results = [ + self.emit_b_quads_around_active_edge(active_edge_indices[0], endpoint.position.x), + self.emit_b_quads_around_active_edge(active_edge_indices[1], endpoint.position.x), + ]; + + // Add supporting interior triangles if necessary. + match b_quad_emission_results[0] { + BQuadEmissionResult::BQuadEmittedAbove | BQuadEmissionResult::BQuadEmittedAround => { + self.add_supporting_interior_triangle(active_edge_indices[0], + active_edge_indices[0] - 1, + active_edge_indices[0] + 2) + } + _ => {} + } + match b_quad_emission_results[1] { + BQuadEmissionResult::BQuadEmittedBelow | BQuadEmissionResult::BQuadEmittedAround => { + self.add_supporting_interior_triangle(active_edge_indices[1], + active_edge_indices[1] - 2, + active_edge_indices[1] + 1) + } + _ => {} + } self.heap.pop(); @@ -837,6 +871,17 @@ impl<'a> Partitioner<'a> { self.subdivide_active_edge_again_at_t(subdivision, t, bottom) } + fn add_supporting_interior_triangle(&mut self, + active_edge_index: u32, + upper_active_edge_index: u32, + lower_active_edge_index: u32) { + self.library.cover_indices.interior_indices.extend([ + self.active_edges[active_edge_index as usize].left_vertex_index, + self.active_edges[upper_active_edge_index as usize].left_vertex_index, + self.active_edges[lower_active_edge_index as usize].left_vertex_index, + ].into_iter()); + } + fn already_visited_point(&self, point: &Point) -> bool { // FIXME(pcwalton): This makes the visited vector too big. let index = point.endpoint_index as usize; @@ -1093,6 +1138,9 @@ impl<'a> Partitioner<'a> { } } + // FIXME(pcwalton): This creates incorrect normals for vertical lines. I think we should + // probably calculate normals for the path vertices first and then lerp them to calculate these + // B-vertex normals. That would be simpler, faster, and more correct, I suspect. fn update_vertex_normals_for_new_b_quad(&mut self, b_quad: &BQuad) { self.update_vertex_normal_for_b_quad_edge(b_quad.upper_left_vertex_index, b_quad.upper_control_point_vertex_index, diff --git a/shaders/gles2/common.inc.glsl b/shaders/gles2/common.inc.glsl index 675a9cff..58eb7d0e 100644 --- a/shaders/gles2/common.inc.glsl +++ b/shaders/gles2/common.inc.glsl @@ -114,6 +114,10 @@ int convertWindowDepthValueToPathIndex(float depthValue) { return int(pathIndex); } +vec2 dilatePosition(vec2 position, float normalAngle, vec2 amount) { + return position + vec2(cos(normalAngle), -sin(normalAngle)) * amount; +} + bool computeMCAAQuadPosition(out vec2 outPosition, inout vec2 leftPosition, inout vec2 rightPosition, @@ -164,8 +168,8 @@ bool computeECAAQuadPosition(out vec2 outPosition, float leftNormalAngle, float rightNormalAngle, vec2 emboldenAmount) { - leftPosition += vec2(cos(leftNormalAngle), -sin(leftNormalAngle)) * emboldenAmount; - rightPosition += vec2(cos(rightNormalAngle), -sin(rightNormalAngle)) * emboldenAmount; + leftPosition = dilatePosition(leftPosition, leftNormalAngle, emboldenAmount); + rightPosition = dilatePosition(rightPosition, rightNormalAngle, emboldenAmount); leftPosition = hintPosition(leftPosition, hints); rightPosition = hintPosition(rightPosition, hints); diff --git a/shaders/gles2/direct-curve.vs.glsl b/shaders/gles2/direct-curve.vs.glsl index 26e51e9b..717ba1c0 100644 --- a/shaders/gles2/direct-curve.vs.glsl +++ b/shaders/gles2/direct-curve.vs.glsl @@ -12,6 +12,7 @@ precision highp float; uniform mat4 uTransform; uniform vec4 uHints; +uniform vec2 uEmboldenAmount; uniform ivec2 uPathColorsDimensions; uniform ivec2 uPathTransformDimensions; uniform sampler2D uPathColors; @@ -21,6 +22,7 @@ attribute vec2 aPosition; attribute vec2 aTexCoord; attribute float aPathID; attribute float aSign; +attribute float aNormalAngle; varying vec4 vColor; varying vec2 vTexCoord; @@ -31,7 +33,8 @@ void main() { vec4 pathTransform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions); - vec2 position = hintPosition(aPosition, uHints); + vec2 position = dilatePosition(aPosition, aNormalAngle, uEmboldenAmount); + position = hintPosition(position, uHints); position = transformVertexPositionST(position, pathTransform); position = transformVertexPosition(position, uTransform); diff --git a/shaders/gles2/direct-interior.vs.glsl b/shaders/gles2/direct-interior.vs.glsl index 4f604189..4a7f183e 100644 --- a/shaders/gles2/direct-interior.vs.glsl +++ b/shaders/gles2/direct-interior.vs.glsl @@ -12,6 +12,7 @@ precision highp float; uniform mat4 uTransform; uniform vec4 uHints; +uniform vec2 uEmboldenAmount; uniform ivec2 uPathColorsDimensions; uniform ivec2 uPathTransformDimensions; uniform sampler2D uPathColors; @@ -19,6 +20,7 @@ uniform sampler2D uPathTransform; attribute vec2 aPosition; attribute float aPathID; +attribute float aNormalAngle; varying vec4 vColor; @@ -27,7 +29,8 @@ void main() { vec4 pathTransform = fetchFloat4Data(uPathTransform, pathID, uPathTransformDimensions); - vec2 position = hintPosition(aPosition, uHints); + vec2 position = dilatePosition(aPosition, aNormalAngle, uEmboldenAmount); + position = hintPosition(position, uHints); position = transformVertexPositionST(position, pathTransform); position = transformVertexPosition(position, uTransform); diff --git a/shaders/gles2/ecaa-curve.vs.glsl b/shaders/gles2/ecaa-curve.vs.glsl index 96f76314..bae69f1a 100644 --- a/shaders/gles2/ecaa-curve.vs.glsl +++ b/shaders/gles2/ecaa-curve.vs.glsl @@ -58,9 +58,10 @@ void main() { leftNormalAngle, rightNormalAngle, uEmboldenAmount)) { - controlPointPosition += vec2(cos(controlPointNormalAngle), - -sin(controlPointNormalAngle)) * uEmboldenAmount; - controlPointPosition = hintPosition(aControlPointPosition, uHints); + controlPointPosition = dilatePosition(controlPointPosition, + controlPointNormalAngle, + uEmboldenAmount); + controlPointPosition = hintPosition(controlPointPosition, uHints); controlPointPosition = transformVertexPositionST(controlPointPosition, transform); controlPointPosition = transformVertexPositionST(controlPointPosition, uTransformST); controlPointPosition = convertClipToScreenSpace(controlPointPosition, uFramebufferSize);