diff options
Diffstat (limited to 'src/main/kotlin/util/render/RenderInWorldContext.kt')
-rw-r--r-- | src/main/kotlin/util/render/RenderInWorldContext.kt | 550 |
1 files changed, 280 insertions, 270 deletions
diff --git a/src/main/kotlin/util/render/RenderInWorldContext.kt b/src/main/kotlin/util/render/RenderInWorldContext.kt index b61b9aa..bb58200 100644 --- a/src/main/kotlin/util/render/RenderInWorldContext.kt +++ b/src/main/kotlin/util/render/RenderInWorldContext.kt @@ -1,5 +1,3 @@ - - package moe.nea.firmament.util.render import com.mojang.blaze3d.systems.RenderSystem @@ -8,14 +6,12 @@ import java.lang.Math.pow import org.joml.Matrix4f import org.joml.Vector3f import net.minecraft.client.gl.VertexBuffer -import net.minecraft.client.render.BufferBuilder -import net.minecraft.client.render.BufferRenderer import net.minecraft.client.render.Camera -import net.minecraft.client.render.GameRenderer import net.minecraft.client.render.RenderLayer import net.minecraft.client.render.RenderPhase import net.minecraft.client.render.RenderTickCounter import net.minecraft.client.render.Tessellator +import net.minecraft.client.render.VertexConsumer import net.minecraft.client.render.VertexConsumerProvider import net.minecraft.client.render.VertexFormat import net.minecraft.client.render.VertexFormats @@ -31,273 +27,287 @@ import moe.nea.firmament.util.MC @RenderContextDSL class RenderInWorldContext private constructor( - private val tesselator: Tessellator, - val matrixStack: MatrixStack, - private val camera: Camera, - private val tickCounter: RenderTickCounter, - val vertexConsumers: VertexConsumerProvider.Immediate, + private val tesselator: Tessellator, + val matrixStack: MatrixStack, + private val camera: Camera, + private val tickCounter: RenderTickCounter, + val vertexConsumers: VertexConsumerProvider.Immediate, ) { - object RenderLayers { - val TRANSLUCENT_TRIS = RenderLayer.of("firmament_translucent_tris", - VertexFormats.POSITION_COLOR, - VertexFormat.DrawMode.TRIANGLES, - RenderLayer.DEFAULT_BUFFER_SIZE, - false, true, - RenderLayer.MultiPhaseParameters.builder() - .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) - .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) - .program(RenderPhase.COLOR_PROGRAM) - .build(false)) - val LINES = RenderLayer.of("firmament_rendertype_lines", - VertexFormats.LINES, - VertexFormat.DrawMode.LINES, - RenderLayer.DEFAULT_BUFFER_SIZE, - false, false, // do we need translucent? i dont think so - RenderLayer.MultiPhaseParameters.builder() - .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) - .program(FirmamentShaders.LINES) - .build(false) - ) - } - - fun color(color: me.shedaniel.math.Color) { - color(color.red / 255F, color.green / 255f, color.blue / 255f, color.alpha / 255f) - } - - fun color(red: Float, green: Float, blue: Float, alpha: Float) { - RenderSystem.setShaderColor(red, green, blue, alpha) - } - - fun block(blockPos: BlockPos) { - matrixStack.push() - matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat()) - buildCube(matrixStack.peek().positionMatrix, tesselator) - matrixStack.pop() - } - - enum class VerticalAlign { - TOP, BOTTOM, CENTER; - - fun align(index: Int, count: Int): Float { - return when (this) { - CENTER -> (index - count / 2F) * (1 + MC.font.fontHeight.toFloat()) - BOTTOM -> (index - count) * (1 + MC.font.fontHeight.toFloat()) - TOP -> (index) * (1 + MC.font.fontHeight.toFloat()) - } - } - } - - fun waypoint(position: BlockPos, vararg label: Text) { - text( - position.toCenterPos(), - *label, - Text.literal("§e${FirmFormatters.formatDistance(MC.player?.pos?.distanceTo(position.toCenterPos()) ?: 42069.0)}"), - background = 0xAA202020.toInt() - ) - } - - fun withFacingThePlayer(position: Vec3d, block: FacingThePlayerContext.() -> Unit) { - matrixStack.push() - matrixStack.translate(position.x, position.y, position.z) - val actualCameraDistance = position.distanceTo(camera.pos) - val distanceToMoveTowardsCamera = if (actualCameraDistance < 10) 0.0 else -(actualCameraDistance - 10.0) - val vec = position.subtract(camera.pos).multiply(distanceToMoveTowardsCamera / actualCameraDistance) - matrixStack.translate(vec.x, vec.y, vec.z) - matrixStack.multiply(camera.rotation) - matrixStack.scale(0.025F, -0.025F, 1F) - - FacingThePlayerContext(this).run(block) - - matrixStack.pop() - vertexConsumers.drawCurrentLayer() - } - - fun sprite(position: Vec3d, sprite: Sprite, width: Int, height: Int) { - texture( - position, sprite.atlasId, width, height, sprite.minU, sprite.minV, sprite.maxU, sprite.maxV - ) - } - - fun texture( - position: Vec3d, texture: Identifier, width: Int, height: Int, - u1: Float, v1: Float, - u2: Float, v2: Float, - ) { - withFacingThePlayer(position) { - texture(texture, width, height, u1, v1, u2, v2) - } - } - - fun text(position: Vec3d, vararg texts: Text, verticalAlign: VerticalAlign = VerticalAlign.CENTER, background: Int = 0x70808080) { - withFacingThePlayer(position) { - text(*texts, verticalAlign = verticalAlign, background = background) - } - } - - fun tinyBlock(vec3d: Vec3d, size: Float) { - RenderSystem.setShader(GameRenderer::getPositionColorProgram) - matrixStack.push() - matrixStack.translate(vec3d.x, vec3d.y, vec3d.z) - matrixStack.scale(size, size, size) - matrixStack.translate(-.5, -.5, -.5) - buildCube(matrixStack.peek().positionMatrix, tesselator) - matrixStack.pop() - } - - fun wireframeCube(blockPos: BlockPos, lineWidth: Float = 10F) { - RenderSystem.setShader(GameRenderer::getRenderTypeLinesProgram) - matrixStack.push() - RenderSystem.lineWidth(lineWidth / pow(camera.pos.squaredDistanceTo(blockPos.toCenterPos()), 0.25).toFloat()) - matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat()) - buildWireFrameCube(matrixStack.peek(), tesselator) - matrixStack.pop() - } - - fun line(vararg points: Vec3d, lineWidth: Float = 10F) { - line(points.toList(), lineWidth) - } - - fun tracer(toWhere: Vec3d, lineWidth: Float = 3f) { - val cameraForward = Vector3f(0f, 0f, -1f).rotate(camera.rotation) - line(camera.pos.add(Vec3d(cameraForward)), toWhere, lineWidth = lineWidth) - } - - fun line(points: List<Vec3d>, lineWidth: Float = 10F) { - RenderSystem.lineWidth(lineWidth) - val buffer = tesselator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES) - - val matrix = matrixStack.peek() - var lastNormal: Vector3f? = null - points.zipWithNext().forEach { (a, b) -> - val normal = Vector3f(b.x.toFloat(), b.y.toFloat(), b.z.toFloat()) - .sub(a.x.toFloat(), a.y.toFloat(), a.z.toFloat()) - .normalize() - val lastNormal0 = lastNormal ?: normal - lastNormal = normal - buffer.vertex(matrix.positionMatrix, a.x.toFloat(), a.y.toFloat(), a.z.toFloat()) - .color(-1) - .normal(matrix, lastNormal0.x, lastNormal0.y, lastNormal0.z) - .next() - buffer.vertex(matrix.positionMatrix, b.x.toFloat(), b.y.toFloat(), b.z.toFloat()) - .color(-1) - .normal(matrix, normal.x, normal.y, normal.z) - .next() - } - - RenderLayers.LINES.draw(buffer.end()) - } - - companion object { - private fun doLine( - matrix: MatrixStack.Entry, - buf: BufferBuilder, - i: Float, - j: Float, - k: Float, - x: Float, - y: Float, - z: Float - ) { - val normal = Vector3f(x, y, z) - .sub(i, j, k) - .normalize() - buf.vertex(matrix.positionMatrix, i, j, k) - .normal(matrix, normal.x, normal.y, normal.z) - .color(-1) - .next() - buf.vertex(matrix.positionMatrix, x, y, z) - .normal(matrix, normal.x, normal.y, normal.z) - .color(-1) - .next() - } - - - private fun buildWireFrameCube(matrix: MatrixStack.Entry, tessellator: Tessellator) { - val buf = tessellator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES) - - for (i in 0..1) { - for (j in 0..1) { - val i = i.toFloat() - val j = j.toFloat() - doLine(matrix, buf, 0F, i, j, 1F, i, j) - doLine(matrix, buf, i, 0F, j, i, 1F, j) - doLine(matrix, buf, i, j, 0F, i, j, 1F) - } - } - BufferRenderer.drawWithGlobalProgram(buf.end()) - } - - private fun buildCube(matrix: Matrix4f, tessellator: Tessellator) { - val buf = tessellator.begin(VertexFormat.DrawMode.TRIANGLES, VertexFormats.POSITION_COLOR) - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 0.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 0.0F, 1.0F, 1.0F).color(-1).next() - buf.vertex(matrix, 1.0F, 0.0F, 1.0F).color(-1).next() - RenderLayers.TRANSLUCENT_TRIS.draw(buf.end()) - } - - - fun renderInWorld(event: WorldRenderLastEvent, block: RenderInWorldContext. () -> Unit) { - RenderSystem.disableDepthTest() - RenderSystem.enableBlend() - RenderSystem.defaultBlendFunc() - RenderSystem.disableCull() - - event.matrices.push() - event.matrices.translate(-event.camera.pos.x, -event.camera.pos.y, -event.camera.pos.z) - - val ctx = RenderInWorldContext( - RenderSystem.renderThreadTesselator(), - event.matrices, - event.camera, - event.tickCounter, - event.vertexConsumers - ) - - block(ctx) - - event.matrices.pop() - - RenderSystem.setShaderColor(1F, 1F, 1F, 1F) - VertexBuffer.unbind() - RenderSystem.enableDepthTest() - RenderSystem.enableCull() - RenderSystem.disableBlend() - } - } + object RenderLayers { + val TRANSLUCENT_TRIS = RenderLayer.of("firmament_translucent_tris", + VertexFormats.POSITION_COLOR, + VertexFormat.DrawMode.TRIANGLES, + RenderLayer.CUTOUT_BUFFER_SIZE, + false, true, + RenderLayer.MultiPhaseParameters.builder() + .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) + .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) + .program(RenderPhase.POSITION_COLOR_PROGRAM) + .build(false)) + val LINES = RenderLayer.of("firmament_rendertype_lines", + VertexFormats.LINES, + VertexFormat.DrawMode.LINES, + RenderLayer.CUTOUT_BUFFER_SIZE, + false, false, // do we need translucent? i dont think so + RenderLayer.MultiPhaseParameters.builder() + .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) + .program(FirmamentShaders.LINES) + .build(false) + ) + val COLORED_QUADS = RenderLayer.of( + "firmament_quads", + VertexFormats.POSITION_COLOR, + VertexFormat.DrawMode.QUADS, + RenderLayer.CUTOUT_BUFFER_SIZE, + false, true, + RenderLayer.MultiPhaseParameters.builder() + .depthTest(RenderPhase.ALWAYS_DEPTH_TEST) + .program(RenderPhase.POSITION_COLOR_PROGRAM) + .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) + .build(false) + ) + } + + @Deprecated("stateful color management is no longer a thing") + fun color(color: me.shedaniel.math.Color) { + color(color.red / 255F, color.green / 255f, color.blue / 255f, color.alpha / 255f) + } + + @Deprecated("stateful color management is no longer a thing") + fun color(red: Float, green: Float, blue: Float, alpha: Float) { + RenderSystem.setShaderColor(red, green, blue, alpha) + } + + fun block(blockPos: BlockPos, color: Int) { + matrixStack.push() + matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat()) + buildCube(matrixStack.peek().positionMatrix, vertexConsumers.getBuffer(RenderLayers.COLORED_QUADS), color) + matrixStack.pop() + } + + enum class VerticalAlign { + TOP, BOTTOM, CENTER; + + fun align(index: Int, count: Int): Float { + return when (this) { + CENTER -> (index - count / 2F) * (1 + MC.font.fontHeight.toFloat()) + BOTTOM -> (index - count) * (1 + MC.font.fontHeight.toFloat()) + TOP -> (index) * (1 + MC.font.fontHeight.toFloat()) + } + } + } + + fun waypoint(position: BlockPos, vararg label: Text) { + text( + position.toCenterPos(), + *label, + Text.literal("§e${FirmFormatters.formatDistance(MC.player?.pos?.distanceTo(position.toCenterPos()) ?: 42069.0)}"), + background = 0xAA202020.toInt() + ) + } + + fun withFacingThePlayer(position: Vec3d, block: FacingThePlayerContext.() -> Unit) { + matrixStack.push() + matrixStack.translate(position.x, position.y, position.z) + val actualCameraDistance = position.distanceTo(camera.pos) + val distanceToMoveTowardsCamera = if (actualCameraDistance < 10) 0.0 else -(actualCameraDistance - 10.0) + val vec = position.subtract(camera.pos).multiply(distanceToMoveTowardsCamera / actualCameraDistance) + matrixStack.translate(vec.x, vec.y, vec.z) + matrixStack.multiply(camera.rotation) + matrixStack.scale(0.025F, -0.025F, 1F) + + FacingThePlayerContext(this).run(block) + + matrixStack.pop() + vertexConsumers.drawCurrentLayer() + } + + fun sprite(position: Vec3d, sprite: Sprite, width: Int, height: Int) { + texture( + position, sprite.atlasId, width, height, sprite.minU, sprite.minV, sprite.maxU, sprite.maxV + ) + } + + fun texture( + position: Vec3d, texture: Identifier, width: Int, height: Int, + u1: Float, v1: Float, + u2: Float, v2: Float, + ) { + withFacingThePlayer(position) { + texture(texture, width, height, u1, v1, u2, v2) + } + } + + fun text( + position: Vec3d, + vararg texts: Text, + verticalAlign: VerticalAlign = VerticalAlign.CENTER, + background: Int = 0x70808080 + ) { + withFacingThePlayer(position) { + text(*texts, verticalAlign = verticalAlign, background = background) + } + } + + fun tinyBlock(vec3d: Vec3d, size: Float, color: Int) { + matrixStack.push() + matrixStack.translate(vec3d.x, vec3d.y, vec3d.z) + matrixStack.scale(size, size, size) + matrixStack.translate(-.5, -.5, -.5) + buildCube(matrixStack.peek().positionMatrix, vertexConsumers.getBuffer(RenderLayers.COLORED_QUADS), color) + matrixStack.pop() + vertexConsumers.draw() + } + + fun wireframeCube(blockPos: BlockPos, lineWidth: Float = 10F) { + val buf = vertexConsumers.getBuffer(RenderLayer.LINES) + matrixStack.push() + // TODO: this does not render through blocks (or water layers) anymore + RenderSystem.lineWidth(lineWidth / pow(camera.pos.squaredDistanceTo(blockPos.toCenterPos()), 0.25).toFloat()) + matrixStack.translate(blockPos.x.toFloat(), blockPos.y.toFloat(), blockPos.z.toFloat()) + buildWireFrameCube(matrixStack.peek(), buf) + matrixStack.pop() + vertexConsumers.draw() + } + + fun line(vararg points: Vec3d, lineWidth: Float = 10F) { + line(points.toList(), lineWidth) + } + + fun tracer(toWhere: Vec3d, lineWidth: Float = 3f) { + val cameraForward = Vector3f(0f, 0f, -1f).rotate(camera.rotation) + line(camera.pos.add(Vec3d(cameraForward)), toWhere, lineWidth = lineWidth) + } + + fun line(points: List<Vec3d>, lineWidth: Float = 10F) { + RenderSystem.lineWidth(lineWidth) + // TODO: replace with renderlayers + val buffer = tesselator.begin(VertexFormat.DrawMode.LINES, VertexFormats.LINES) + + val matrix = matrixStack.peek() + var lastNormal: Vector3f? = null + points.zipWithNext().forEach { (a, b) -> + val normal = Vector3f(b.x.toFloat(), b.y.toFloat(), b.z.toFloat()) + .sub(a.x.toFloat(), a.y.toFloat(), a.z.toFloat()) + .normalize() + val lastNormal0 = lastNormal ?: normal + lastNormal = normal + buffer.vertex(matrix.positionMatrix, a.x.toFloat(), a.y.toFloat(), a.z.toFloat()) + .color(-1) + .normal(matrix, lastNormal0.x, lastNormal0.y, lastNormal0.z) + .next() + buffer.vertex(matrix.positionMatrix, b.x.toFloat(), b.y.toFloat(), b.z.toFloat()) + .color(-1) + .normal(matrix, normal.x, normal.y, normal.z) + .next() + } + + RenderLayers.LINES.draw(buffer.end()) + } + // TODO: put the favourite icons in front of items again + + companion object { + private fun doLine( + matrix: MatrixStack.Entry, + buf: VertexConsumer, + i: Float, + j: Float, + k: Float, + x: Float, + y: Float, + z: Float + ) { + val normal = Vector3f(x, y, z) + .sub(i, j, k) + .normalize() + buf.vertex(matrix.positionMatrix, i, j, k) + .normal(matrix, normal.x, normal.y, normal.z) + .color(-1) + .next() + buf.vertex(matrix.positionMatrix, x, y, z) + .normal(matrix, normal.x, normal.y, normal.z) + .color(-1) + .next() + } + + + private fun buildWireFrameCube(matrix: MatrixStack.Entry, buf: VertexConsumer) { + for (i in 0..1) { + for (j in 0..1) { + val i = i.toFloat() + val j = j.toFloat() + doLine(matrix, buf, 0F, i, j, 1F, i, j) + doLine(matrix, buf, i, 0F, j, i, 1F, j) + doLine(matrix, buf, i, j, 0F, i, j, 1F) + } + } + } + + private fun buildCube(matrix: Matrix4f, buf: VertexConsumer, color: Int) { + // Y- + buf.vertex(matrix, 0F, 0F, 0F).color(color) + buf.vertex(matrix, 0F, 0F, 1F).color(color) + buf.vertex(matrix, 1F, 0F, 1F).color(color) + buf.vertex(matrix, 1F, 0F, 0F).color(color) + // Y+ + buf.vertex(matrix, 0F, 1F, 0F).color(color) + buf.vertex(matrix, 1F, 1F, 0F).color(color) + buf.vertex(matrix, 1F, 1F, 1F).color(color) + buf.vertex(matrix, 0F, 1F, 1F).color(color) + // X- + buf.vertex(matrix, 0F, 0F, 0F).color(color) + buf.vertex(matrix, 0F, 0F, 1F).color(color) + buf.vertex(matrix, 0F, 1F, 1F).color(color) + buf.vertex(matrix, 0F, 1F, 0F).color(color) + // X+ + buf.vertex(matrix, 1F, 0F, 0F).color(color) + buf.vertex(matrix, 1F, 1F, 0F).color(color) + buf.vertex(matrix, 1F, 1F, 1F).color(color) + buf.vertex(matrix, 1F, 0F, 1F).color(color) + // Z- + buf.vertex(matrix, 0F, 0F, 0F).color(color) + buf.vertex(matrix, 1F, 0F, 0F).color(color) + buf.vertex(matrix, 1F, 1F, 0F).color(color) + buf.vertex(matrix, 0F, 1F, 0F).color(color) + // Z+ + buf.vertex(matrix, 0F, 0F, 1F).color(color) + buf.vertex(matrix, 0F, 1F, 1F).color(color) + buf.vertex(matrix, 1F, 1F, 1F).color(color) + buf.vertex(matrix, 1F, 0F, 1F).color(color) + } + + + fun renderInWorld(event: WorldRenderLastEvent, block: RenderInWorldContext. () -> Unit) { + // TODO: there should be *no more global state*. the only thing we should be doing is render layers. that includes settings like culling, blending, shader color, and depth testing + // For now i will let these functions remain, but this needs to go before i do a full (non-beta) release + RenderSystem.disableDepthTest() + RenderSystem.enableBlend() + RenderSystem.defaultBlendFunc() + RenderSystem.disableCull() + + event.matrices.push() + event.matrices.translate(-event.camera.pos.x, -event.camera.pos.y, -event.camera.pos.z) + + val ctx = RenderInWorldContext( + RenderSystem.renderThreadTesselator(), + event.matrices, + event.camera, + event.tickCounter, + event.vertexConsumers + ) + + block(ctx) + + event.matrices.pop() + event.vertexConsumers.draw() + RenderSystem.setShaderColor(1F, 1F, 1F, 1F) + VertexBuffer.unbind() + RenderSystem.enableDepthTest() + RenderSystem.enableCull() + RenderSystem.disableBlend() + } + } } |