diff --git a/src/main/java/ru/windcorp/optica/client/TestLayer.java b/src/main/java/ru/windcorp/optica/client/TestLayer.java deleted file mode 100644 index 69e3a57..0000000 --- a/src/main/java/ru/windcorp/optica/client/TestLayer.java +++ /dev/null @@ -1,167 +0,0 @@ -/******************************************************************************* - * Optica - * Copyright (C) 2020 Wind Corporation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *******************************************************************************/ -package ru.windcorp.optica.client; - -import org.lwjgl.glfw.GLFW; - -import com.google.common.eventbus.Subscribe; - -import glm.mat._3.Mat3; -import glm.mat._4.Mat4; -import glm.vec._3.Vec3; -import ru.windcorp.optica.client.graphics.Layer; -import ru.windcorp.optica.client.graphics.backend.GraphicsBackend; -import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; -import ru.windcorp.optica.client.graphics.input.KeyEvent; -import ru.windcorp.optica.client.graphics.input.CursorMoveEvent; -import ru.windcorp.optica.client.graphics.model.Model; -import ru.windcorp.optica.client.graphics.model.DynamicModel; -import ru.windcorp.optica.client.graphics.model.Shapes; -import ru.windcorp.optica.client.graphics.texture.SimpleTexture; -import ru.windcorp.optica.client.graphics.texture.Sprite; -import ru.windcorp.optica.client.graphics.texture.Texture; -import ru.windcorp.optica.client.graphics.texture.TextureManager; -import ru.windcorp.optica.client.graphics.world.Camera; -import ru.windcorp.optica.client.graphics.world.WorldRenderer; - -public class TestLayer extends Layer { - - private final Model model; - - private final Camera camera = new Camera( - new Vec3(), - 0, (float) Math.PI, - (float) Math.toRadians(70) - ); - - private final Vec3 velocity = new Vec3(); - private final Vec3 tmp = new Vec3(); - - private final Mat3 angMat = new Mat3(); - - private int movementX = 0; - private int movementY = 0; - private int movementZ = 0; - - public TestLayer() { - super("Test"); - - Texture top = qtex("grass_top"); - Texture side = qtex("grass_side"); - Texture bottom = qtex("grass_bottom"); - - model = new DynamicModel(DynamicModel.builder() - .addDynamicPart( - new Shapes.PppBuilder(top, bottom, side, side, side, side) - .create() - ) - ) { - @Override - protected void getDynamicTransform(int shapeIndex, Mat4 result) { - result.translate(0, 0, +5); - } - }; - } - - private Texture qtex(String name) { - return new SimpleTexture(new Sprite(TextureManager.load(name, false))); - } - - @Override - protected void initialize() { - GraphicsInterface.subscribeToInputEvents(this); - } - - private final WorldRenderer renderer = new WorldRenderer(); - - @Override - protected void doRender() { - camera.apply(renderer); - - angMat.set().rotateY(-camera.getYaw()); - - tmp.set(movementX, 0, movementZ); - angMat.mul_(tmp); // bug in jglm - tmp.y = movementY; - tmp.mul(0.1f); - tmp.sub(velocity); - tmp.mul(0.1f); - velocity.add(tmp); - camera.move(velocity); - - model.render(renderer); - - renderer.reset(); - } - - private boolean flag = true; - - @Subscribe - public void onKeyEvent(KeyEvent event) { - if (event.isRepeat()) return; - - int multiplier = event.isPress() ? 1 : -1; - - switch (event.getKey()) { - case GLFW.GLFW_KEY_W: - movementZ += -1 * multiplier; - break; - case GLFW.GLFW_KEY_S: - movementZ += +1 * multiplier; - break; - case GLFW.GLFW_KEY_A: - movementX += -1 * multiplier; - break; - case GLFW.GLFW_KEY_D: - movementX += +1 * multiplier; - break; - case GLFW.GLFW_KEY_SPACE: - movementY += +1 * multiplier; - break; - case GLFW.GLFW_KEY_LEFT_SHIFT: - movementY += -1 * multiplier; - break; - - case GLFW.GLFW_KEY_ESCAPE: - if (!event.isPress()) return; - - if (flag) { - GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_NORMAL); - } else { - GLFW.glfwSetInputMode(GraphicsBackend.getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); - } - - flag = !flag; - break; - } - } - - @Subscribe - public void onMouseMoved(CursorMoveEvent event) { - if (!flag) return; - - final float yawScale = 0.002f; - final float pitchScale = yawScale; - - camera.turn( - (float) (event.getChangeY() * pitchScale), - (float) (event.getChangeX() * yawScale) - ); - } - -} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java index 2971afc..9ff3b2f 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/Pixels.java @@ -29,24 +29,30 @@ class Pixels { private final int bufferWidth; private final int bufferHeight; - private final boolean filtered; + private final TextureSettings settings; + + private final int width; + private final int height; public Pixels( ByteBuffer data, int bufferWidth, int bufferHeight, - boolean filtered + int width, int height, + TextureSettings settings ) { this.data = data; + this.width = width; + this.height = height; this.bufferWidth = bufferWidth; this.bufferHeight = bufferHeight; - this.filtered = filtered; + this.settings = settings; } public int load() { int handle = glGenTextures(); glBindTexture(GL_TEXTURE_2D, handle); - if (filtered) { + if (settings.isFiltered()) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { @@ -74,4 +80,28 @@ class Pixels { return handle; } + public ByteBuffer getData() { + return data; + } + + public int getBufferWidth() { + return bufferWidth; + } + + public int getBufferHeight() { + return bufferHeight; + } + + public int getContentWidth() { + return width; + } + + public int getContentHeight() { + return height; + } + + public TextureSettings getSettings() { + return settings; + } + } diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/PngLoader.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/PngLoader.java new file mode 100644 index 0000000..0f18d77 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/PngLoader.java @@ -0,0 +1,109 @@ +package ru.windcorp.optica.client.graphics.texture; + +import java.awt.Graphics2D; +import java.awt.color.ColorSpace; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Hashtable; +import javax.imageio.ImageIO; +import org.lwjgl.BufferUtils; + +import ru.windcorp.optica.common.util.BinUtil; + +class PngLoader { + + private static final ColorModel COLOR_MODEL = new ComponentColorModel( + ColorSpace.getInstance(ColorSpace.CS_sRGB), // Use RGB + new int[] {8, 8, 8, 8}, // Use every bit + true, // Has alpha + false, // Not premultiplied + ComponentColorModel.TRANSLUCENT, // Can have any alpha + DataBuffer.TYPE_BYTE // Alpha is one byte + ); + + private static final Hashtable CANVAS_PROPERTIES = new Hashtable<>(); + + private static final java.awt.Color CANVAS_BACKGROUND = new java.awt.Color(0, 0, 0, 0); + + public static Pixels loadPngImage( + InputStream pngStream, + TextureSettings settings + ) throws IOException { + + BufferedImage readResult = ImageIO.read(pngStream); + + int width = readResult.getWidth(); + int height = readResult.getHeight(); + + int bufferWidth = BinUtil.roundToGreaterPowerOf2(width); + int bufferHeight = BinUtil.roundToGreaterPowerOf2(height); + + WritableRaster raster = createRaster(bufferWidth, bufferHeight); + + BufferedImage canvas = createCanvas(raster); + + Graphics2D g = canvas.createGraphics(); + try { + g.setColor(CANVAS_BACKGROUND); + g.fillRect(0, 0, width, height); + g.drawImage( + readResult, + 0, 0, width, height, + 0, height, width, 0, // Flip the image + null + ); + } finally { + g.dispose(); + } + + return new Pixels( + extractBytes(raster), + width, height, + bufferWidth, bufferHeight, + settings + ); + } + + private static WritableRaster createRaster( + int bufferWidth, + int bufferHeight + ) { + return Raster.createInterleavedRaster( + DataBuffer.TYPE_BYTE, // Storage model + bufferWidth, // Buffer width + bufferHeight, // Buffer height + 4, // RGBA + null // Location (here (0; 0)) + ); + } + + private static BufferedImage createCanvas(WritableRaster raster) { + return new BufferedImage( + COLOR_MODEL, // Color model + raster, // Backing raster + false, // Raster is not premultipied + CANVAS_PROPERTIES // Properties + ); + } + + private static ByteBuffer extractBytes(WritableRaster raster) { + byte[] data = ( + (DataBufferByte) raster.getDataBuffer() + ).getData(); + + ByteBuffer buffer = BufferUtils.createByteBuffer(data.length); + buffer.put(data); + buffer.flip(); + + return buffer; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/TextureManager.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/TextureManager.java index 419b8e8..e5f7145 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/texture/TextureManager.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/TextureManager.java @@ -17,128 +17,202 @@ *******************************************************************************/ package ru.windcorp.optica.client.graphics.texture; -import java.awt.Graphics; -import java.awt.color.ColorSpace; -import java.awt.image.BufferedImage; -import java.awt.image.ColorModel; -import java.awt.image.ComponentColorModel; -import java.awt.image.DataBuffer; -import java.awt.image.DataBufferByte; -import java.awt.image.Raster; -import java.awt.image.WritableRaster; +import java.io.IOException; +import java.io.InputStream; import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Hashtable; -import javax.imageio.ImageIO; +import java.util.HashMap; +import java.util.Map; import ru.windcorp.optica.client.graphics.backend.RenderTaskQueue; import ru.windcorp.optica.common.resource.Resource; import ru.windcorp.optica.common.resource.ResourceManager; +import ru.windcorp.optica.common.util.ByteBufferInputStream; public class TextureManager { - private static final ColorModel COLOR_MODEL = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_sRGB), // Use RGB - new int[] {8, 8, 8, 8}, // Use every bit - true, // Has alpha - false, // Not premultiplied - ComponentColorModel.TRANSLUCENT, // Can have any alpha - DataBuffer.TYPE_BYTE // Alpha is one byte - ); - - private static final Hashtable CANVAS_PROPERTIES = new Hashtable<>(); - private static final java.awt.Color CANVAS_BACKGROUND = - new java.awt.Color(0, 0, 0, 0); - private static final String TEXTURE_ASSETS_PREFIX = "assets/textures/"; + private static final Map LOADED_PIXELS = + new HashMap<>(); + + private static final Map LOADED_PRIMITIVES = + new HashMap<>(); + + private static Pixels getCachedPixels(String name) { + return LOADED_PIXELS.get(name); + } + + private static Pixels getCachedPixels(Resource resource) { + return getCachedPixels(resource.getName()); + } + + private static Pixels cachePixels(Pixels pixels, String name) { + LOADED_PIXELS.put(name, pixels); + return pixels; + } + + private static Pixels cachePixels(Pixels pixels, Resource resource) { + return cachePixels(pixels, resource.getName()); + } + + private static TexturePrimitive getCachedPrimitive(String name) { + return LOADED_PRIMITIVES.get(name); + } + + private static TexturePrimitive getCachedPrimitive(Resource resource) { + return getCachedPrimitive(resource.getName()); + } + + private static TexturePrimitive cachePrimitive( + TexturePrimitive primitive, + String name + ) { + LOADED_PRIMITIVES.put(name, primitive); + return primitive; + } + + private static TexturePrimitive cachePrimitive( + TexturePrimitive primitive, + Resource resource + ) { + return cachePrimitive(primitive, resource.getName()); + } + private static Resource getResource(String textureName) { return ResourceManager.getResource( TEXTURE_ASSETS_PREFIX + textureName + ".png" ); } - public static TexturePrimitive load(String textureName, boolean filtered) { - TexturePrimitive result = loadToByteBuffer(textureName, filtered); - RenderTaskQueue.invokeLater(result::load); - return result; - } - - public static TexturePrimitive loadToByteBuffer( - String textureName, boolean filter + public static Pixels createOpenGLBuffer( + InputStream stream, + TextureSettings settings ) { - Resource resource = getResource(textureName); - - BufferedImage source = readImage(resource); - - int bufferWidth = toPowerOf2(source.getWidth()), - bufferHeight = toPowerOf2(source.getHeight()); - - WritableRaster raster = Raster.createInterleavedRaster( - DataBuffer.TYPE_BYTE, // Storage model - bufferWidth, // Buffer width - bufferHeight, // Buffer height - 4, // RGBA - null // Location (here (0; 0)) - ); - - BufferedImage canvas = new BufferedImage( - COLOR_MODEL, // Color model - raster, // Backing raster - false, // Raster is not premultipied - CANVAS_PROPERTIES // Properties - ); - - Graphics g = canvas.createGraphics(); - g.setColor(CANVAS_BACKGROUND); - g.fillRect(0, 0, source.getWidth(), source.getHeight()); - g.drawImage( - source, - 0, 0, source.getWidth(), source.getHeight(), - 0, source.getHeight(), source.getWidth(), 0, // Flip the image - null - ); - g.dispose(); - - byte[] data = ( - (DataBufferByte) canvas.getRaster().getDataBuffer() - ).getData(); - - ByteBuffer buffer = ByteBuffer.allocateDirect(data.length); - buffer.order(ByteOrder.nativeOrder()); - buffer.put(data); - buffer.flip(); - - Pixels pixels = new Pixels(buffer, bufferWidth, bufferHeight, filter); - - TexturePrimitive result = new TexturePrimitive( - pixels, - source.getWidth(), - source.getHeight(), - bufferWidth, - bufferHeight - ); - - return result; - } - - private static BufferedImage readImage(Resource resource) { try { - return ImageIO.read(resource.getInputStream()); - } catch (Exception e) { - throw new RuntimeException("too bad. refresh project u stupid. must be " + resource.getName(), e); + return PngLoader.loadPngImage(stream, settings); + } catch (IOException e) { + throw new RuntimeException("u stupid. refresh ur project"); } } - private static int toPowerOf2(int i) { - - // TODO optimize - - int result = 1; - do { - result *= 2; - } while (result < i); + public static Pixels createOpenGLBuffer( + Resource resource, + TextureSettings settings + ) { + Pixels cache = getCachedPixels(resource); + if (cache != null) return cache; + return cachePixels( + createOpenGLBuffer(resource.getInputStream(), settings), + resource + ); + } + + public static Pixels createOpenGLBuffer( + String textureName, + TextureSettings settings + ) { + Pixels cache = getCachedPixels(textureName); + if (cache != null) return cache; + return cachePixels( + createOpenGLBuffer(getResource(textureName), settings), + textureName + ); + } + + public static Pixels createOpenGLBuffer( + ByteBuffer bytes, + TextureSettings settings + ) { + bytes.mark(); + try { + return createOpenGLBuffer( + new ByteBufferInputStream(bytes), settings + ); + } finally { + bytes.reset(); + } + } + + public static TexturePrimitive createTexturePrimitive( + InputStream stream, + TextureSettings settings + ) { + Pixels pixels = createOpenGLBuffer(stream, settings); + TexturePrimitive result = new TexturePrimitive(pixels); + return result; + } + + public static TexturePrimitive createTexturePrimitive( + Resource resource, + TextureSettings settings + ) { + TexturePrimitive primitive = getCachedPrimitive(resource); + if (primitive != null) return primitive; + return cachePrimitive( + createTexturePrimitive(resource.getInputStream(), settings), + resource + ); + } + + public static TexturePrimitive createTexturePrimitive( + String textureName, + TextureSettings settings + ) { + TexturePrimitive primitive = getCachedPrimitive(textureName); + if (primitive != null) return primitive; + return cachePrimitive( + createTexturePrimitive(getResource(textureName), settings), + textureName + ); + } + + public static TexturePrimitive createTexturePrimitive( + ByteBuffer bytes, + TextureSettings settings + ) { + bytes.mark(); + try { + return createTexturePrimitive( + new ByteBufferInputStream(bytes), settings + ); + } finally { + bytes.reset(); + } + } + + public static TexturePrimitive load( + InputStream stream, + TextureSettings settings + ) { + TexturePrimitive result = createTexturePrimitive(stream, settings); + if (!result.isLoaded()) RenderTaskQueue.invokeLater(result::load); return result; } + public static TexturePrimitive load( + Resource resource, + TextureSettings settings + ) { + return load(resource.getInputStream(), settings); + } + + public static TexturePrimitive load( + String textureName, + TextureSettings settings + ) { + return load(getResource(textureName), settings); + } + + public static TexturePrimitive load( + ByteBuffer bytes, + TextureSettings settings + ) { + bytes.mark(); + try { + return load(new ByteBufferInputStream(bytes), settings); + } finally { + bytes.reset(); + } + } + } \ No newline at end of file diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/TexturePrimitive.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/TexturePrimitive.java index a2833a4..40c8916 100644 --- a/src/main/java/ru/windcorp/optica/client/graphics/texture/TexturePrimitive.java +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/TexturePrimitive.java @@ -28,43 +28,28 @@ public class TexturePrimitive implements OpenGLDeletable { private static final int NOT_LOADED = -1; private int handle = NOT_LOADED; - private Pixels pixelsToLoad; + private Pixels pixels; + + public TexturePrimitive(Pixels pixels) { + this.pixels = pixels; + } - private final int width; - private final int height; - private final int bufferWidth; - private final int bufferHeight; - - protected TexturePrimitive( - Pixels pixels, - int width, int height, - int bufferWidth, int bufferHeight - ) { - this.pixelsToLoad = pixels; - this.width = width; - this.height = height; - this.bufferWidth = bufferWidth; - this.bufferHeight = bufferHeight; - - OpenGLObjectTracker.register(this); - } - - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } - public int getBufferWidth() { - return bufferWidth; + return pixels.getBufferWidth(); } public int getBufferHeight() { - return bufferHeight; + return pixels.getBufferHeight(); } - + + public int getWidth() { + return pixels.getContentWidth(); + } + + public int getHeight() { + return pixels.getContentHeight(); + } + public boolean isLoaded() { return handle != NOT_LOADED; } @@ -83,13 +68,12 @@ public class TexturePrimitive implements OpenGLDeletable { protected void load() { if (isLoaded()) return; - handle = pixelsToLoad.load(); + handle = pixels.load(); + OpenGLObjectTracker.register(this); if (handle < 0) { throw new RuntimeException("oops"); } - - pixelsToLoad = null; } @Override diff --git a/src/main/java/ru/windcorp/optica/client/graphics/texture/TextureSettings.java b/src/main/java/ru/windcorp/optica/client/graphics/texture/TextureSettings.java new file mode 100644 index 0000000..3613cac --- /dev/null +++ b/src/main/java/ru/windcorp/optica/client/graphics/texture/TextureSettings.java @@ -0,0 +1,15 @@ +package ru.windcorp.optica.client.graphics.texture; + +public class TextureSettings { + + private final boolean isFiltered; + + public TextureSettings(boolean isFiltered) { + this.isFiltered = isFiltered; + } + + public boolean isFiltered() { + return isFiltered; + } + +} diff --git a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenders.java b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenders.java index d8d58fe..7c1bbca 100644 --- a/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenders.java +++ b/src/main/java/ru/windcorp/optica/client/world/renders/BlockRenders.java @@ -24,18 +24,22 @@ import ru.windcorp.optica.client.graphics.texture.SimpleTexture; import ru.windcorp.optica.client.graphics.texture.Sprite; import ru.windcorp.optica.client.graphics.texture.Texture; import ru.windcorp.optica.client.graphics.texture.TextureManager; +import ru.windcorp.optica.client.graphics.texture.TextureSettings; import ru.windcorp.optica.client.world.renders.bro.BlockRenderOpaqueCube; public class BlockRenders { + private static final Map BLOCK_RENDERS = + new HashMap<>(); + + private static final TextureSettings TEXTURE_SETTINGS = + new TextureSettings(false); + private static Texture grassTop = qtex("grass_top"); private static Texture grassSide = qtex("grass_side"); private static Texture dirtT = qtex("grass_bottom"); private static Texture stoneT = qtex("stone"); private static Texture glassT = qtex("glass_clear"); - - private static final Map BLOCK_RENDERS = - new HashMap<>(); private BlockRenders() {} @@ -59,7 +63,9 @@ public class BlockRenders { } private static Texture qtex(String name) { - return new SimpleTexture(new Sprite(TextureManager.load(name, false))); + return new SimpleTexture(new Sprite( + TextureManager.load(name, TEXTURE_SETTINGS) + )); } } diff --git a/src/main/java/ru/windcorp/optica/common/util/BinUtil.java b/src/main/java/ru/windcorp/optica/common/util/BinUtil.java new file mode 100644 index 0000000..9c09d89 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/util/BinUtil.java @@ -0,0 +1,18 @@ +package ru.windcorp.optica.common.util; + +public class BinUtil { + + public static int closestGreaterPowerOf2(int x) { + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; + } + + public static int roundToGreaterPowerOf2(int x) { + return closestGreaterPowerOf2(x - 1); + } + +} diff --git a/src/main/java/ru/windcorp/optica/common/util/ByteBufferInputStream.java b/src/main/java/ru/windcorp/optica/common/util/ByteBufferInputStream.java new file mode 100644 index 0000000..42e3c8b --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/util/ByteBufferInputStream.java @@ -0,0 +1,35 @@ +package ru.windcorp.optica.common.util; + +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * @author Mike + * Houston, adapted by Javapony + */ +public class ByteBufferInputStream extends InputStream { + + private final ByteBuffer buffer; + + public ByteBufferInputStream(ByteBuffer buffer) { + this.buffer = buffer; + } + + public int read() { + if (!buffer.hasRemaining()) { + return -1; + } + return buffer.get() & 0xFF; + } + + public int read(byte[] bytes, int off, int len) { + if (!buffer.hasRemaining()) { + return -1; + } + + len = Math.min(len, buffer.remaining()); + buffer.get(bytes, off, len); + return len; + } + +} \ No newline at end of file diff --git a/src/main/java/ru/windcorp/optica/common/util/ByteBufferOutputStream.java b/src/main/java/ru/windcorp/optica/common/util/ByteBufferOutputStream.java new file mode 100644 index 0000000..c2237a6 --- /dev/null +++ b/src/main/java/ru/windcorp/optica/common/util/ByteBufferOutputStream.java @@ -0,0 +1,32 @@ +package ru.windcorp.optica.common.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; + +public class ByteBufferOutputStream extends OutputStream { + + private final ByteBuffer buffer; + + public ByteBufferOutputStream(ByteBuffer buffer) { + this.buffer = buffer; + } + + public void write(int b) throws IOException { + try { + buffer.put((byte) b); + } catch (BufferOverflowException e) { + throw new IOException(e); + } + } + + public void write(byte[] bytes, int off, int len) throws IOException { + try { + buffer.put(bytes, off, len); + } catch (BufferOverflowException e) { + throw new IOException(e); + } + } + +} \ No newline at end of file diff --git a/src/test/java/ru/windcorp/optica/util/BinUtilTest.java b/src/test/java/ru/windcorp/optica/util/BinUtilTest.java new file mode 100644 index 0000000..6f648e0 --- /dev/null +++ b/src/test/java/ru/windcorp/optica/util/BinUtilTest.java @@ -0,0 +1,58 @@ +package ru.windcorp.optica.util; + +import static org.junit.Assert.assertEquals; + +import java.util.Random; + +import org.junit.Test; + +import ru.windcorp.optica.common.util.BinUtil; + +public class BinUtilTest { + + @Test + public void cornerCases() { + test(1); + test(2); + test(3); + test(4); + test(7); + test(8); + test(9); + + test(1023); + test(1024); + test(1025); + + test((1 << 16) - 1); + test(1 << 16); + test((1 << 16) + 1); + } + + @Test + public void random() { + Random random = new Random(0); + + for (int i = 0; i < 10000; ++i) { + test(random.nextInt((1 << 30) - 2) + 1); + } + } + + void test(int x) { + assertEquals("Round, x = " + x, referenceRound(x), BinUtil.roundToGreaterPowerOf2(x)); + assertEquals("Greater, x = " + x, referenceGreater(x), BinUtil.closestGreaterPowerOf2(x)); + } + + int referenceGreater(int x) { + int p; + for (p = 1; p <= x; p *= 2); + return p; + } + + int referenceRound(int x) { + int p; + for (p = 1; p < x; p *= 2); + return p; + } + +}