Re-written flat render pipeline to support transforms

FlatRender now supports arbitrary transforms
GLSL 1.40 is now used in shaders
This commit is contained in:
OLEGSHA 2020-08-03 21:44:36 +03:00
parent da61f834e5
commit 7dee1eb95f
31 changed files with 812 additions and 264 deletions

View File

@ -33,6 +33,6 @@ public class Colors {
DEBUG_CYAN = 0x00FFFF, DEBUG_CYAN = 0x00FFFF,
DEBUG_MAGENTA = 0xFF00FF, DEBUG_MAGENTA = 0xFF00FF,
DEBUG_YELLOW = 0x00FFFF; DEBUG_YELLOW = 0xFFFF00;
} }

View File

@ -26,18 +26,19 @@ public class InputHandler {
private static final EventBus INPUT_EVENT_BUS = new EventBus("Input"); private static final EventBus INPUT_EVENT_BUS = new EventBus("Input");
// ScrollEvent Start // ScrollEvent
private static class ModifiableWheelScrollEvent extends WheelScrollEvent { private static class ModifiableWheelScrollEvent extends WheelScrollEvent {
public void initialize(double xoffset, double yoffset) { public void initialize(double xoffset, double yoffset) {
this.xoffset = xoffset; this.xOffset = xoffset;
this.yoffset = yoffset; this.yOffset = yoffset;
} }
} }
private static final ModifiableWheelScrollEvent THE_SCROLL_EVENT = new ModifiableWheelScrollEvent(); private static final ModifiableWheelScrollEvent THE_SCROLL_EVENT =
new ModifiableWheelScrollEvent();
static void handleMouseWheel( static void handleMouseWheel(
long window, long window,
@ -49,7 +50,7 @@ public class InputHandler {
dispatch(THE_SCROLL_EVENT); dispatch(THE_SCROLL_EVENT);
} }
// KeyEvent Start // KeyEvent
private static class ModifiableKeyEvent extends KeyEvent { private static class ModifiableKeyEvent extends KeyEvent {
@ -62,7 +63,8 @@ public class InputHandler {
} }
private static final ModifiableKeyEvent THE_KEY_EVENT = new ModifiableKeyEvent(); private static final ModifiableKeyEvent THE_KEY_EVENT =
new ModifiableKeyEvent();
static void handleKeyInput( static void handleKeyInput(
long window, long window,
@ -76,7 +78,7 @@ public class InputHandler {
dispatch(THE_KEY_EVENT); dispatch(THE_KEY_EVENT);
} }
// CursorEvent Start // CursorMoveEvent
private static class ModifiableCursorMoveEvent extends CursorMoveEvent { private static class ModifiableCursorMoveEvent extends CursorMoveEvent {
@ -111,6 +113,8 @@ public class InputHandler {
CURSOR_POSITION.set(x, y); CURSOR_POSITION.set(x, y);
} }
// Misc
public static double getCursorX() { public static double getCursorX() {
return CURSOR_POSITION.x; return CURSOR_POSITION.x;
} }

View File

@ -44,7 +44,7 @@ public class CombinedShader extends Shader {
} }
private static String combine(String[] resources) { private static String combine(String[] resources) {
StringBuilder accumulator = new StringBuilder("#version 120\n"); StringBuilder accumulator = new StringBuilder("#version 140\n");
for (String resourceName : resources) { for (String resourceName : resources) {
Resource resource = getShaderResource(resourceName); Resource resource = getShaderResource(resourceName);

View File

@ -18,16 +18,16 @@
package ru.windcorp.optica.client.graphics.flat; package ru.windcorp.optica.client.graphics.flat;
import ru.windcorp.optica.client.graphics.Layer; import ru.windcorp.optica.client.graphics.Layer;
import ru.windcorp.optica.client.graphics.model.WorldRenderable;
public abstract class AssembledFlatLayer extends Layer { public abstract class AssembledFlatLayer extends Layer {
private final FlatRenderHelper helper = new FlatRenderHelper(); private final AssembledFlatRenderHelper helper =
new AssembledFlatRenderHelper();
private final RenderTarget target = new RenderTarget(); private final RenderTarget target = new RenderTarget();
private boolean needsReassembly = true; private boolean needsReassembly = true;
private Clip[] clips = null; private RenderTarget.Clip[] clips = null;
public AssembledFlatLayer(String name) { public AssembledFlatLayer(String name) {
super(name); super(name);
@ -57,64 +57,11 @@ public abstract class AssembledFlatLayer extends Layer {
doReassemble(); doReassemble();
} }
for (Clip clip : clips) { for (RenderTarget.Clip clip : clips) {
clip.render(helper); clip.render(helper);
} }
helper.reset(); helper.reset();
} }
public static class Clip {
private final Mask mask = new Mask();
private final WorldRenderable renderable;
public Clip(
int startX, int startY,
int endX, int endY,
WorldRenderable renderable
) {
mask.set(startX, startY, endX, endY);
this.renderable = renderable;
}
public Clip(
Mask mask,
WorldRenderable renderable
) {
this(
mask.getStartX(), mask.getStartY(),
mask.getEndX(), mask.getEndY(),
renderable
);
}
public int getStartX() {
return mask.getStartX();
}
public int getStartY() {
return mask.getStartY();
}
public int getEndX() {
return mask.getEndX();
}
public int getEndY() {
return mask.getEndY();
}
public WorldRenderable getRenderable() {
return renderable;
}
public void render(FlatRenderHelper helper) {
helper.pushMask(getStartX(), getStartY(), getEndX(), getEndY());
renderable.render(helper);
helper.popTransform();
}
}
} }

View File

@ -0,0 +1,35 @@
/*******************************************************************************
* 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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
package ru.windcorp.optica.client.graphics.flat;
import java.nio.FloatBuffer;
public class AssembledFlatRenderHelper extends FlatRenderHelper {
private FloatBuffer masks;
@Override
protected FloatBuffer getMasks() {
return masks;
}
public void setMasks(FloatBuffer masks) {
this.masks = masks;
}
}

View File

@ -0,0 +1,68 @@
/*******************************************************************************
* 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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
package ru.windcorp.optica.client.graphics.flat;
import java.nio.FloatBuffer;
import glm.mat._4.Mat4;
public class DefaultFlatRenderHelper extends FlatRenderHelper {
private final TransformedMask transformer = new TransformedMask();
protected final MaskStack maskStack = new MaskStack();
protected final boolean[] hasMask = new boolean[TRANSFORM_STACK_SIZE];
public void pushMask(Mask mask, Mat4 transform) {
pushMask(transformer.set(mask, transform), transform);
}
public void pushMask(TransformedMask mask, Mat4 transform) {
hasMask[transformStack.getSize()] = true;
pushTransform().mul(transform);
maskStack.pushMask(mask);
}
@Override
public Mat4 pushTransform() {
hasMask[transformStack.getSize()] = false;
return super.pushTransform();
}
@Override
public void popTransform() {
super.popTransform();
if (hasMask[transformStack.getSize()]) {
maskStack.popMask();
}
}
@Override
public void reset() {
super.reset();
maskStack.clear();
}
@Override
protected FloatBuffer getMasks() {
return maskStack.getBuffer();
}
}

View File

@ -17,69 +17,28 @@
*******************************************************************************/ *******************************************************************************/
package ru.windcorp.optica.client.graphics.flat; package ru.windcorp.optica.client.graphics.flat;
import java.nio.FloatBuffer;
import glm.mat._4.Mat4;
import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; import ru.windcorp.optica.client.graphics.backend.GraphicsInterface;
import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper; import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper;
public class FlatRenderHelper extends ShapeRenderHelper { public abstract class FlatRenderHelper extends ShapeRenderHelper {
private static final float MAX_DEPTH = 1 << 16; protected static final float MAX_DEPTH = 1 << 16;
private final Mask mask = new Mask(); protected final Mat4 finalTransform = new Mat4();
{ protected abstract FloatBuffer getMasks();
setupScreenTransform();
}
public FlatRenderHelper pushMask(
int startX, int startY,
int endX, int endY
) {
mask.set(startX, startY, endX, endY);
pushTransform().translate(startX, startY, 0);
return this;
}
public FlatRenderHelper pushMask(Mask mask) {
return pushMask(
mask.getStartX(), mask.getStartY(),
mask.getEndX(), mask.getEndY()
);
}
public int getStartX() {
return mask.getStartX();
}
public int getStartY() {
return mask.getStartY();
}
public int getEndX() {
return mask.getEndX();
}
public int getEndY() {
return mask.getEndY();
}
public boolean isRenderable() {
return !mask.isEmpty();
}
@Override @Override
public void reset() { public Mat4 getFinalTransform() {
super.reset();
setupScreenTransform();
mask.set(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
private void setupScreenTransform() {
float width = GraphicsInterface.getFramebufferWidth(); float width = GraphicsInterface.getFramebufferWidth();
float height = GraphicsInterface.getFramebufferHeight(); float height = GraphicsInterface.getFramebufferHeight();
getTransform().translate(-1, +1, 0) return finalTransform.identity().translate(-1, +1, 0)
.scale(2 / width, -2 / height, 1 / MAX_DEPTH); .scale(2 / width, -2 / height, 1 / MAX_DEPTH)
.mul(getTransform());
} }
} }

View File

@ -19,10 +19,12 @@ package ru.windcorp.optica.client.graphics.flat;
import static org.lwjgl.opengl.GL11.*; import static org.lwjgl.opengl.GL11.*;
import java.nio.FloatBuffer;
import com.google.common.collect.ObjectArrays; import com.google.common.collect.ObjectArrays;
import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform2Int; import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform1Int;
import ru.windcorp.optica.client.graphics.model.Face; import ru.windcorp.optica.client.graphics.backend.shaders.uniforms.Uniform2Float;
import ru.windcorp.optica.client.graphics.model.Shape; import ru.windcorp.optica.client.graphics.model.Shape;
import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper; import ru.windcorp.optica.client.graphics.model.ShapeRenderHelper;
import ru.windcorp.optica.client.graphics.model.ShapeRenderProgram; import ru.windcorp.optica.client.graphics.model.ShapeRenderProgram;
@ -42,17 +44,19 @@ public class FlatRenderProgram extends ShapeRenderProgram {
return def; return def;
} }
public static final int MASK_STACK_SIZE = 16; // As in Flat.fragment.glsl
private static final String FLAT_VERTEX_SHADER_RESOURCE = private static final String FLAT_VERTEX_SHADER_RESOURCE =
"Flat.vertex.glsl"; "Flat.vertex.glsl";
private static final String FLAT_FRAGMENT_SHADER_RESOURCE = private static final String FLAT_FRAGMENT_SHADER_RESOURCE =
"Flat.fragment.glsl"; "Flat.fragment.glsl";
private static final String private static final String
MASK_START_UNIFORM_NAME = "maskStart", MASK_COUNT_UNIFORM_NAME = "maskCount",
MASK_END_UNIFORM_NAME = "maskEnd"; MASKS_UNIFORM_NAME = "masks";
private final Uniform2Int maskStartUniform; private final Uniform1Int maskCountUniform;
private final Uniform2Int maskEndUniform; private final Uniform2Float masksUniform;
public FlatRenderProgram( public FlatRenderProgram(
String[] vertexShaderResources, String[] vertexShaderResources,
@ -63,8 +67,8 @@ public class FlatRenderProgram extends ShapeRenderProgram {
attachFragmentShader(fragmentShaderResources) attachFragmentShader(fragmentShaderResources)
); );
this.maskStartUniform = getUniform(MASK_START_UNIFORM_NAME).as2Int(); this.maskCountUniform = getUniform(MASK_COUNT_UNIFORM_NAME).as1Int();
this.maskEndUniform = getUniform(MASK_END_UNIFORM_NAME).as2Int(); this.masksUniform = getUniform(MASKS_UNIFORM_NAME).as2Float();
} }
private static String[] attachVertexShader(String[] others) { private static String[] attachVertexShader(String[] others) {
@ -77,15 +81,8 @@ public class FlatRenderProgram extends ShapeRenderProgram {
@Override @Override
public void render(ShapeRenderHelper helper, Shape shape) { public void render(ShapeRenderHelper helper, Shape shape) {
if (((FlatRenderHelper) helper).isRenderable()) {
super.render(helper, shape);
}
}
@Override
protected void renderFace(Face face) {
glDisable(GL_CULL_FACE); glDisable(GL_CULL_FACE);
super.renderFace(face); super.render(helper, shape);
glEnable(GL_CULL_FACE); glEnable(GL_CULL_FACE);
} }
@ -94,8 +91,29 @@ public class FlatRenderProgram extends ShapeRenderProgram {
super.configure(argHelper); super.configure(argHelper);
FlatRenderHelper helper = ((FlatRenderHelper) argHelper); FlatRenderHelper helper = ((FlatRenderHelper) argHelper);
maskStartUniform.set(helper.getStartX(), helper.getStartY()); configureMasks(helper.getMasks());
maskEndUniform.set(helper.getEndX(), helper.getEndY()); }
private void configureMasks(FloatBuffer masks) {
int pos = masks.position();
int limit = masks.limit();
int size = pos / TransformedMask.SIZE_IN_FLOATS;
maskCountUniform.set(size);
masks.flip();
masksUniform.set(masks);
// for (int i = 0; i < pos; ++i) {
// if (i % TransformedMask.SIZE_IN_FLOATS == 0) {
// System.out.print(" | ");
// }
// System.out.print(masks.get(i) + "; ");
// }
// System.out.println();
masks.limit(limit);
masks.position(pos);
} }
} }

View File

@ -21,14 +21,17 @@ import org.lwjgl.glfw.GLFW;
import com.google.common.eventbus.Subscribe; import com.google.common.eventbus.Subscribe;
import glm.mat._4.Mat4;
import ru.windcorp.optica.client.graphics.Colors; import ru.windcorp.optica.client.graphics.Colors;
import ru.windcorp.optica.client.graphics.backend.GraphicsInterface; import ru.windcorp.optica.client.graphics.backend.GraphicsInterface;
import ru.windcorp.optica.client.graphics.input.KeyEvent; import ru.windcorp.optica.client.graphics.input.KeyEvent;
import ru.windcorp.optica.client.graphics.model.LambdaModel;
import ru.windcorp.optica.client.graphics.texture.SimpleTexture; import ru.windcorp.optica.client.graphics.texture.SimpleTexture;
import ru.windcorp.optica.client.graphics.texture.Sprite; import ru.windcorp.optica.client.graphics.texture.Sprite;
import ru.windcorp.optica.client.graphics.texture.Texture; import ru.windcorp.optica.client.graphics.texture.Texture;
import ru.windcorp.optica.client.graphics.texture.TextureManager; import ru.windcorp.optica.client.graphics.texture.TextureManager;
import ru.windcorp.optica.client.graphics.texture.TextureSettings; import ru.windcorp.optica.client.graphics.texture.TextureSettings;
import ru.windcorp.optica.client.graphics.world.LayerWorld;
public class LayerTestUI extends AssembledFlatLayer { public class LayerTestUI extends AssembledFlatLayer {
@ -40,37 +43,46 @@ public class LayerTestUI extends AssembledFlatLayer {
private boolean flag = false; private boolean flag = false;
private static int width = 512 + 256; private static final int WIDTH = 512 + 256;
private static final int height = 64; private static final int HEIGHT = 80;
private static final int border = 5; private static final int BORDER = 5;
@Override @Override
protected void assemble(RenderTarget target) { protected void assemble(RenderTarget target) {
final int boxColor = flag ? 0xEE8888 : 0xEEEE88; final int boxColor = flag ? 0xEE8888 : 0xEEEE88;
final int borderColor = flag ? 0xAA4444 : 0xAAAA44; final int borderColor = flag ? 0xAA4444 : 0xAAAA44;
final int boxShadowColor = flag ? 0x440000 : 0x444400; final int boxShadowColor = flag ? 0x440000 : 0x444400;
int x = (getWidth() - width) / 2; int x = (getWidth() - WIDTH) / 2;
int y = getHeight() - height; int y = getHeight() - HEIGHT - 2*BORDER;
y -= 2*border;
target.fill(x + border, y + border, width, height, boxShadowColor); target.fill(x + BORDER, y + BORDER, WIDTH, HEIGHT, boxShadowColor);
target.fill(x - 1, y - 1, width + 2, height + 2, boxShadowColor); target.fill(x - 1, y - 1, WIDTH + 2, HEIGHT + 2, boxShadowColor);
target.fill(x, y, width, height, borderColor); target.fill(x, y, WIDTH, HEIGHT, borderColor);
target.fill(x + border, y + border, width - 2*border, height - 2*border, boxColor); target.fill(x + BORDER, y + BORDER, WIDTH - 2*BORDER, HEIGHT - 2*BORDER, boxColor);
final int texShadow = 2; final int texShadow = 2;
final int texSize = height - 4*border; final int texSize = HEIGHT - 4*BORDER;
target.fill(x + 2*border + texShadow, y + 2*border + texShadow, texSize, texSize, Colors.BLACK); target.pushTransform(new Mat4().identity().translate(x + 2*BORDER, y + 2*BORDER, 0));
target.drawTexture(x + 2*border, y + 2*border, texSize, texSize, qtex("compass"));
final Texture compassBg = qtex("compass_icon");
final Texture compassFg = qtex("compass_icon_arrow");
target.drawTexture(texShadow, texShadow, texSize, texSize, Colors.BLACK, compassBg);
target.drawTexture(0, 0, texSize, texSize, compassBg);
target.addCustomRenderer(new LambdaModel(LambdaModel.lambdaBuilder()
.addDynamicPart(
target.createRectagle(0, 0, texSize, texSize, 0xFFFFFF, compassFg),
mat -> mat.translate(texSize/2, texSize/2, 0).rotateZ(-LayerWorld.tmp_the_camera.getYaw()).translate(-texSize/2, -texSize/2, 0)
)
));
target.popTransform();
drawCross(target); drawCross(target);
} }
private void drawCross(RenderTarget target) { private void drawCross(RenderTarget target) {
int cx = getWidth() / 2; int cx = getWidth() / 2;
int cy = getHeight() / 2; int cy = getHeight() / 2;

View File

@ -65,6 +65,14 @@ public class Mask {
this.endY = endY; this.endY = endY;
} }
public int getWidth() {
return getEndX() - getStartX();
}
public int getHeight() {
return getEndY() - getStartY();
}
public void set(int startX, int startY, int endX, int endY) { public void set(int startX, int startY, int endX, int endY) {
this.startX = startX; this.startX = startX;
this.startY = startY; this.startY = startY;
@ -72,8 +80,14 @@ public class Mask {
this.endY = endY; this.endY = endY;
} }
public boolean isEmpty() { public void set(Mask copyFrom) {
return startX >= endX || startY >= endY; set(copyFrom.startX, copyFrom.startY, copyFrom.endX, copyFrom.endY);
}
@Override
public String toString() {
return "(" + getStartX() + "; " + getStartY() +
") -> (" + getEndX() + "; " + getEndY() + ")";
} }
} }

View File

@ -0,0 +1,46 @@
/*******************************************************************************
* 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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
package ru.windcorp.optica.client.graphics.flat;
import java.nio.FloatBuffer;
import org.lwjgl.BufferUtils;
public class MaskStack {
private final FloatBuffer buffer = BufferUtils.createFloatBuffer(
FlatRenderProgram.MASK_STACK_SIZE * TransformedMask.SIZE_IN_FLOATS
);
public void pushMask(TransformedMask mask) {
mask.writeToBuffer(buffer);
}
public void popMask() {
buffer.position(buffer.position() - TransformedMask.SIZE_IN_FLOATS);
}
public void clear() {
buffer.clear();
}
FloatBuffer getBuffer() {
return buffer;
}
}

View File

@ -21,7 +21,9 @@ import java.util.ArrayList;
import java.util.Deque; import java.util.Deque;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects;
import glm.mat._4.Mat4;
import glm.vec._3.Vec3; import glm.vec._3.Vec3;
import ru.windcorp.optica.client.graphics.Colors; import ru.windcorp.optica.client.graphics.Colors;
import ru.windcorp.optica.client.graphics.backend.Usage; import ru.windcorp.optica.client.graphics.backend.Usage;
@ -33,10 +35,53 @@ import ru.windcorp.optica.client.graphics.texture.Texture;
public class RenderTarget { public class RenderTarget {
private final List<AssembledFlatLayer.Clip> assembled = new ArrayList<>(); private static final Mat4 IDENTITY = new Mat4().identity();
private final Deque<Mask> maskStack = new LinkedList<>(); public static class Clip {
private final MaskStack masks = new MaskStack();
private final Mat4 transform;
private final WorldRenderable renderable;
public Clip(
Iterable<TransformedMask> masks,
Mat4 transform,
WorldRenderable renderable
) {
for (TransformedMask mask : masks) {
this.masks.pushMask(mask);
}
this.transform = transform == null ? IDENTITY : transform;
this.renderable = Objects.requireNonNull(renderable, "renderable");
}
public Mat4 getTransform() {
return transform;
}
public WorldRenderable getRenderable() {
return renderable;
}
public void render(AssembledFlatRenderHelper helper) {
helper.setMasks(masks.getBuffer());
helper.pushTransform().mul(getTransform());
try {
getRenderable().render(helper);
} finally {
helper.popTransform();
helper.setMasks(null);
}
}
}
private final List<Clip> assembled = new ArrayList<>();
private final Deque<TransformedMask> maskStack = new LinkedList<>();
private final Deque<Mat4> transformStack = new LinkedList<>();
private final List<Face> currentClipFaces = new ArrayList<>(); private final List<Face> currentClipFaces = new ArrayList<>();
private int depth = 0; private int depth = 0;
@ -45,20 +90,55 @@ public class RenderTarget {
reset(); reset();
} }
public void pushMaskStartEnd(int startX, int startY, int endX, int endY) { protected void assembleCurrentClipFromFaces() {
assembleCurrentClipFromFaces(); if (!currentClipFaces.isEmpty()) {
maskStack.push(intersect(getMask(), startX, startY, endX, endY)); Face[] faces = currentClipFaces.toArray(
new Face[currentClipFaces.size()]
);
currentClipFaces.clear();
Shape shape = new Shape(
Usage.STATIC, FlatRenderProgram.getDefault(), faces
);
assembled.add(new Clip(
maskStack, getTransform(), shape
));
}
} }
private Mask intersect( public Clip[] assemble() {
Mask current, assembleCurrentClipFromFaces();
int startX, int startY, int endX, int endY
) { Clip[] result = assembled.toArray(
return new Mask( new Clip[assembled.size()]
Math.max(startX, current.getStartX()), );
Math.max(startY, current.getStartY()),
Math.min(endX, current.getEndX()), reset();
Math.min(endY, current.getEndY())
return result;
}
private void reset() {
maskStack.clear();
transformStack.clear();
currentClipFaces.clear();
assembled.clear();
transformStack.add(new Mat4().identity());
depth = 0;
}
public void pushMaskStartEnd(int startX, int startY, int endX, int endY) {
assembleCurrentClipFromFaces();
pushTransform(new Mat4().identity().translate(startX, startY, 0));
maskStack.push(
new TransformedMask(
new Mask(startX, startY, endX, endY),
getTransform()
)
); );
} }
@ -76,64 +156,32 @@ public class RenderTarget {
public void popMask() { public void popMask() {
assembleCurrentClipFromFaces(); assembleCurrentClipFromFaces();
maskStack.pop(); maskStack.pop();
popTransform();
} }
public Mask getMask() { public TransformedMask getMask() {
return maskStack.getFirst(); return maskStack.getFirst();
} }
protected void assembleCurrentClipFromFaces() { public void pushTransform(Mat4 transform) {
if (!currentClipFaces.isEmpty()) {
Mask mask = getMask();
if (mask.isEmpty()) {
currentClipFaces.clear();
return;
}
Face[] faces = currentClipFaces.toArray(
new Face[currentClipFaces.size()]
);
currentClipFaces.clear();
Shape shape = new Shape(
Usage.STATIC, FlatRenderProgram.getDefault(), faces
);
assembled.add(new AssembledFlatLayer.Clip(mask, shape));
}
}
public AssembledFlatLayer.Clip[] assemble() {
assembleCurrentClipFromFaces(); assembleCurrentClipFromFaces();
transformStack.push(getTransform().mul(transform, transform));
AssembledFlatLayer.Clip[] result = assembled.toArray(
new AssembledFlatLayer.Clip[assembled.size()]
);
reset();
return result;
} }
private void reset() { public void popTransform() {
maskStack.clear(); assembleCurrentClipFromFaces();
currentClipFaces.clear(); transformStack.pop();
assembled.clear(); }
maskStack.add(new Mask(0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE)); public Mat4 getTransform() {
depth = 0; return transformStack.getFirst();
} }
public void addCustomRenderer(WorldRenderable renderable) { public void addCustomRenderer(WorldRenderable renderable) {
assembleCurrentClipFromFaces(); assembleCurrentClipFromFaces();
assembled.add(new Clip(
Mask mask = getMask(); maskStack, getTransform(), renderable
if (mask.isEmpty()) { ));
return;
}
assembled.add(new AssembledFlatLayer.Clip(mask, renderable));
} }
protected void addFaceToCurrentClip(Face face) { protected void addFaceToCurrentClip(Face face) {
@ -144,16 +192,9 @@ public class RenderTarget {
int x, int y, int width, int height, int x, int y, int width, int height,
int color, Texture texture int color, Texture texture
) { ) {
float depth = this.depth--; addFaceToCurrentClip(
createRectagleFace(x, y, width, height, color, texture)
addFaceToCurrentClip(Faces.createRectangle( );
FlatRenderProgram.getDefault(),
texture,
createVectorFromRGBInt(color),
new Vec3(x, y + height, depth), // Flip
new Vec3(width, 0, 0),
new Vec3(0, -height, 0)
));
} }
public void drawTexture( public void drawTexture(
@ -170,6 +211,38 @@ public class RenderTarget {
drawTexture(x, y, width, height, color, null); drawTexture(x, y, width, height, color, null);
} }
public void fill(int color) {
fill(
Integer.MIN_VALUE / 2, Integer.MIN_VALUE / 2,
Integer.MAX_VALUE, Integer.MAX_VALUE,
color
);
}
public Face createRectagleFace(
int x, int y, int width, int height, int color, Texture texture
) {
float depth = this.depth--;
return Faces.createRectangle(
FlatRenderProgram.getDefault(),
texture,
createVectorFromRGBInt(color),
new Vec3(x, y + height, depth), // Flip
new Vec3(width, 0, 0),
new Vec3(0, -height, 0)
);
}
public Shape createRectagle(
int x, int y, int width, int height, int color, Texture texture
) {
return new Shape(
Usage.STATIC, FlatRenderProgram.getDefault(),
createRectagleFace(x, y, width, height, color, texture)
);
}
private static Vec3 createVectorFromRGBInt(int rgb) { private static Vec3 createVectorFromRGBInt(int rgb) {
int r = (rgb & 0xFF0000) >> 16; int r = (rgb & 0xFF0000) >> 16;
int g = (rgb & 0x00FF00) >> 8; int g = (rgb & 0x00FF00) >> 8;

View File

@ -0,0 +1,158 @@
/*******************************************************************************
* 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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
package ru.windcorp.optica.client.graphics.flat;
import java.nio.FloatBuffer;
import glm.mat._4.Mat4;
import glm.vec._2.Vec2;
import glm.vec._4.Vec4;
import ru.windcorp.optica.client.graphics.backend.GraphicsInterface;
public class TransformedMask {
public static final int SIZE_IN_FLOATS = 2 * 3 * 2;
private final Vec2 origin = new Vec2();
private final Vec2 width = new Vec2();
private final Vec2 height = new Vec2();
private final Vec2 counterOrigin = new Vec2();
private final Vec2 counterWidth = new Vec2();
private final Vec2 counterHeight = new Vec2();
// Temporary values, objects cached for efficiency
private Vec4 startXstartY = null;
private Vec4 startXendY = null;
private Vec4 endXstartY = null;
private Vec4 endXendY = null;
public TransformedMask(
Vec2 origin, Vec2 width, Vec2 height,
Vec2 counterOrigin, Vec2 counterWidth, Vec2 counterHeight
) {
set(origin, width, height, counterOrigin, counterWidth, counterHeight);
}
public TransformedMask(Mask mask, Mat4 transform) {
set(mask, transform);
}
public TransformedMask() {
// Do nothing
}
public TransformedMask set(
Vec2 origin, Vec2 width, Vec2 height,
Vec2 counterOrigin, Vec2 counterWidth, Vec2 counterHeight
) {
this.origin.set(origin.x, origin.y);
this.width.set(width.x, width.y);
this.height.set(height.x, height.y);
this.counterOrigin.set(counterOrigin.x, counterOrigin.y);
this.counterWidth.set(counterWidth.x, counterWidth.y);
this.counterHeight.set(counterHeight.x, counterHeight.y);
return this;
}
public TransformedMask set(Mask mask, Mat4 transform) {
applyTransform(mask, transform);
setFields();
return this;
}
private void applyTransform(Mask mask, Mat4 transform) {
ensureTemporaryVariablesExist();
int relX = mask.getWidth();
int relY = mask.getHeight();
startXstartY.set( 0, 0, 0, 1);
startXendY .set( 0, relY, 0, 1);
endXstartY .set(relX, 0, 0, 1);
endXendY .set(relX, relY, 0, 1);
transform.mul(startXstartY);
transform.mul(startXendY);
transform.mul(endXstartY);
transform.mul(endXendY);
flipY();
}
private void ensureTemporaryVariablesExist() {
if (startXstartY == null) {
startXstartY = new Vec4();
startXendY = new Vec4();
endXstartY = new Vec4();
endXendY = new Vec4();
}
}
private void flipY() {
float height = GraphicsInterface.getFramebufferHeight();
startXstartY.y = height - startXstartY.y;
startXendY.y = height - startXendY.y;
endXstartY.y = height - endXstartY.y;
endXendY.y = height - endXendY.y;
}
private void setFields() {
origin.set(
startXstartY.x,
startXstartY.y
);
width.set(
endXstartY.x - startXstartY.x,
endXstartY.y - startXstartY.y
);
height.set(
startXendY.x - startXstartY.x,
startXendY.y - startXstartY.y
);
counterOrigin.set(
endXendY.x,
endXendY.y
);
counterWidth.set(
startXendY.x - endXendY.x,
startXendY.y - endXendY.y
);
counterHeight.set(
endXstartY.x - endXendY.x,
endXstartY.y - endXendY.y
);
}
public void writeToBuffer(FloatBuffer output) {
output.put(origin.x).put(origin.y);
output.put(width.x).put(width.y);
output.put(height.x).put(height.y);
output.put(counterOrigin.x).put(counterOrigin.y);
output.put(counterWidth.x).put(counterWidth.y);
output.put(counterHeight.x).put(counterHeight.y);
}
}

View File

@ -1,7 +1,25 @@
/*******************************************************************************
* 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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
package ru.windcorp.optica.client.graphics.input; package ru.windcorp.optica.client.graphics.input;
public abstract class WheelEvent extends InputEvent { public abstract class WheelEvent extends InputEvent {
@Override @Override
public abstract WheelEvent snapshot(); public abstract WheelEvent snapshot();
} }

View File

@ -1,51 +1,86 @@
/*******************************************************************************
* 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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
package ru.windcorp.optica.client.graphics.input; package ru.windcorp.optica.client.graphics.input;
import glm.vec._2.d.Vec2d;
public class WheelScrollEvent extends WheelEvent { public class WheelScrollEvent extends WheelEvent {
protected double xoffset; protected double xOffset;
protected double yoffset; protected double yOffset;
protected WheelScrollEvent(double xoffset, double yoffset) { protected WheelScrollEvent(double xOffset, double yOffset) {
this.xoffset = xoffset; this.xOffset = xOffset;
this.yoffset = yoffset; this.yOffset = yOffset;
} }
protected WheelScrollEvent() {} protected WheelScrollEvent() {}
public boolean isUp() { return yoffset > 0; } public boolean isUp() {
return yOffset > 0;
}
public boolean isDown() { return yoffset < 0; } public boolean isDown() {
return yOffset < 0;
}
public boolean isRight() { return xoffset > 0; } public boolean isRight() {
return xOffset > 0;
}
public boolean isLeft() { return xoffset < 0; } public boolean isLeft() {
return xOffset < 0;
}
public boolean hasVerticalMovement() { return yoffset != 0; } public boolean hasVerticalMovement() {
return yOffset != 0;
}
public boolean hasHorizontalMovement() { return xoffset != 0; } public boolean hasHorizontalMovement() {
return xOffset != 0;
}
public double getX() { return xoffset; } public double getX() {
return xOffset;
}
public double getY() { return yoffset; } public double getY() {
return yOffset;
}
@Override @Override
public WheelEvent snapshot() { public WheelEvent snapshot() {
return new StaticWheelScrollEvent(xoffset, yoffset, getTime()); return new StaticWheelScrollEvent(xOffset, yOffset, getTime());
} }
private class StaticWheelScrollEvent extends WheelScrollEvent { private class StaticWheelScrollEvent extends WheelScrollEvent {
private final double time; private final double time;
public StaticWheelScrollEvent(double xoffset, double yoffset, double time) { public StaticWheelScrollEvent(
super(xoffset, yoffset); double xOffset, double yOffset, double time
) {
super(xOffset, yOffset);
this.time = time; this.time = time;
} }
@Override @Override
public double getTime() { return time; } public double getTime() {
return time;
}
@Override @Override
public WheelEvent snapshot() { public WheelEvent snapshot() {

View File

@ -0,0 +1,125 @@
/*******************************************************************************
* 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 <https://www.gnu.org/licenses/>.
*******************************************************************************/
package ru.windcorp.optica.client.graphics.model;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import com.google.common.primitives.Booleans;
import glm.mat._4.Mat4;
public class LambdaModel extends DynamicModel {
private static final Mat4 IDENTITY = new Mat4();
@FunctionalInterface
public static interface TransformGetter {
void transform(Mat4 result);
}
private final TransformGetter[] getters;
public LambdaModel(
WorldRenderable[] parts,
Mat4[] transforms,
boolean[] dynamic,
TransformGetter[] getters
) {
super(parts, transforms, dynamic);
this.getters = getters;
}
public LambdaModel(Builder builder) {
this(
builder.getParts(), builder.getTransforms(),
builder.getDynamics(), builder.getGetters()
);
}
@Override
protected void getDynamicTransform(int shapeIndex, Mat4 result) {
getters[shapeIndex].transform(result);
}
public static Builder lambdaBuilder() {
return new Builder();
}
public static class Builder {
private final List<WorldRenderable> parts = new ArrayList<>();
private final List<Mat4> transforms = new ArrayList<>();
private final List<Boolean> dynamics = new ArrayList<>();
private final List<TransformGetter> getters = new ArrayList<>();
protected Builder() {}
private Builder addPart(
WorldRenderable part,
Mat4 transform,
TransformGetter getter
) {
parts.add(Objects.requireNonNull(part, "part"));
transforms.add(Objects.requireNonNull(transform, "transform"));
dynamics.add(getter != null);
getters.add(getter);
return this;
}
public Builder addStaticPart(
WorldRenderable part,
Mat4 transform
) {
return addPart(part, new Mat4(transform), null);
}
public Builder addDynamicPart(
WorldRenderable part,
TransformGetter getter
) {
return addPart(part, new Mat4(), getter);
}
public Builder addStaticPart(
WorldRenderable part
) {
return addStaticPart(part, IDENTITY);
}
private WorldRenderable[] getParts() {
return parts.toArray(new WorldRenderable[parts.size()]);
}
private Mat4[] getTransforms() {
return transforms.toArray(new Mat4[transforms.size()]);
}
private boolean[] getDynamics() {
return Booleans.toArray(dynamics);
}
private TransformGetter[] getGetters() {
return getters.toArray(new TransformGetter[getters.size()]);
}
}
}

View File

@ -24,7 +24,7 @@ public class ShapeRenderHelper {
protected static final int TRANSFORM_STACK_SIZE = 64; protected static final int TRANSFORM_STACK_SIZE = 64;
private final StashingStack<Mat4> transformStack = new StashingStack<>( protected final StashingStack<Mat4> transformStack = new StashingStack<>(
TRANSFORM_STACK_SIZE, Mat4::new TRANSFORM_STACK_SIZE, Mat4::new
); );

View File

@ -35,6 +35,8 @@ import ru.windcorp.optica.common.world.WorldData;
public class LayerWorld extends Layer { public class LayerWorld extends Layer {
public static Camera tmp_the_camera;
private final Camera camera = new Camera( private final Camera camera = new Camera(
new Vec3(8, 8, 8), new Vec3(8, 8, 8),
0, 0, 0, 0,
@ -59,6 +61,7 @@ public class LayerWorld extends Layer {
public LayerWorld() { public LayerWorld() {
super("World"); super("World");
tmp_the_camera = camera;
} }
@Override @Override

View File

@ -208,6 +208,26 @@ public class StashingStack<T> implements Iterable<T> {
return newElement; return newElement;
} }
/**
* Returns the specified element from the stack. Indexing starts from the
* bottom of the stack. If the index is out of bounds, an
* {@link IndexOutOfBoundsException} is thrown.
* @param index index of the element to retrieve,
* <tt>[0;&nbsp;{@link #getSize()})</tt>
* @return the requested element
* @throws IndexOutOfBoundsException if the index is negative or greater
* than head
*/
public T get(int index) {
if (index > head) {
throw new IndexOutOfBoundsException(
"Requested index " + index + " > head " + head
);
}
return (T) contents[index];
}
/** /**
* Removes all elements from the stack. * Removes all elements from the stack.
*/ */

View File

@ -1,13 +1,26 @@
#version 120 #version 140
uniform ivec2 maskStart; uniform int maskCount;
uniform ivec2 maskEnd;
uniform vec2 masks[3 * 2 * 16];
bool isInMaskPrimitive(int primitive) {
vec2 origin = masks[3 * primitive + 0];
vec2 width = masks[3 * primitive + 1];
vec2 height = masks[3 * primitive + 2];
vec2 current = gl_FragCoord.xy - origin;
mat2 matrix = mat2(width, height);
vec2 relative = inverse(matrix) * current;
return relative.x >= 0 && relative.y >= 0 && relative.x + relative.y <= 1;
}
void applyMask() { void applyMask() {
if ( for (int i = 0; i < maskCount; ++i) {
gl_FragCoord.x < maskStart.x || gl_FragCoord.x >= maskEnd.x || if (!(isInMaskPrimitive(2 * i + 0) || isInMaskPrimitive(2 * i + 1))) {
gl_FragCoord.y < maskStart.y || gl_FragCoord.y >= maskEnd.y discard;
) { }
discard;
} }
} }

View File

@ -1,4 +1,4 @@
#version 120 #version 140
void flatTransferToFragment() { void flatTransferToFragment() {
shapeTransferToFragment(); shapeTransferToFragment();

View File

@ -1,4 +1,4 @@
#version 120 #version 140
void main(void) { void main(void) {
applyTexture(); applyTexture();

View File

@ -1,4 +1,4 @@
#version 120 #version 140
void main(void) { void main(void) {
gl_Position = applyFinalTransform(vec4(inputPositions, 1.0)); gl_Position = applyFinalTransform(vec4(inputPositions, 1.0));

View File

@ -1,4 +1,4 @@
#version 120 #version 140
varying vec3 varyingColorMultiplier; varying vec3 varyingColorMultiplier;
varying vec2 varyingTextureCoords; varying vec2 varyingTextureCoords;

View File

@ -1,4 +1,4 @@
#version 120 #version 140
attribute vec3 inputPositions; attribute vec3 inputPositions;

View File

@ -1,4 +1,4 @@
#version 120 #version 140
varying vec3 varyingNormals; varying vec3 varyingNormals;

View File

@ -1,4 +1,4 @@
#version 120 #version 140
attribute vec3 inputNormals; attribute vec3 inputNormals;
varying vec3 varyingNormals; varying vec3 varyingNormals;

View File

@ -1,4 +1,4 @@
#version 120 #version 140
void main(void) { void main(void) {
applyTexture(); applyTexture();

View File

@ -1,4 +1,4 @@
#version 120 #version 140
void main(void) { void main(void) {
gl_Position = applyFinalTransform(vec4(inputPositions, 1.0)); gl_Position = applyFinalTransform(vec4(inputPositions, 1.0));

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B