Related
took the code from the one other forum. This code makes resizeble rectangles. I corrected a little for displaying pictures.
if anyone knows, tell me how to calculate the position of the MovableCircle so that the initial aspect ratio is preserved.
I've implemented part of the algorithm for eBottomRight and eTopLeft, but it still works very bad and doesn't work for BottomLeft and TopRight points. I want that it will be look like Krita or PureRef resize behaviour.
Thanks for any help, regards max
this example gif: https://media.giphy.com/media/7XBNv61efV7S9DbgJO/giphy.gif
calc part:
void MovableCircle::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
auto pos = mapToScene(event->pos() + _shiftMouseCoords);
qreal xl = (pos.x() == 0) ? .1 : pos.x();
qreal yl = (pos.y() == 0) ? .1 : pos.y();
qreal arl = qAbs(xl / yl);
if (circlePos_ == eBottomRight) {
if (arl > aspectRatio_) {
pos.setX(yl * aspectRatio_);
} else {
pos.setY(xl / aspectRatio_);
}
}
if (circlePos_ == eTopLeft) {
LOG_WARNING(logger, "Circle Pos: ", circlePos_, ", ", pos.x(), " ", pos.y());
LOG_WARNING(logger, "Init Aspect Ratio: ", aspectRatio_, ", Current AspectRatio:", arl);
if (arl > aspectRatio_) {
LOG_DEBUG(logger, "> Before: ", pos.x(), ", ", pos.y());
pos.setY(xl / aspectRatio_);
LOG_DEBUG(logger, "> After: ", pos.x(), ", ", pos.y());
} else {
LOG_DEBUG(logger, "< Before: ", pos.x(), ", ", pos.y());
pos.setX(yl * aspectRatio_);
LOG_DEBUG(logger, "< After: ", pos.x(), ", ", pos.y());
}
}
setPos(pos);
emit circleMoved();
}
MovableCircle class:
class MovableCircle : public QGraphicsObject
{
Q_OBJECT
public:
enum ECirclePos {
eTopLeft = 0,
eTopRight,
eBottomRight,
eBottomLeft,
};
explicit MovableCircle(ECirclePos cp, double ar, QGraphicsItem *parent = 0);
private:
QRectF boundingRect() const;
QPainterPath shape() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
void mousePressEvent(QGraphicsSceneMouseEvent *event);
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
QPointF _shiftMouseCoords;
private:
double aspectRatio_;
ECirclePos circlePos_;
signals:
void circleMoved();
};
MovableCircle::MovableCircle(ECirclePos cp, double ar, QGraphicsItem *parent) :
QGraphicsObject(parent), aspectRatio_(ar), circlePos_(cp)
{
setFlag(ItemClipsToShape, true);
setCursor(QCursor(Qt::PointingHandCursor));
}
QRectF MovableCircle::boundingRect() const
{
qreal adjust = 0.5;
return QRectF(-5 - adjust, -5 - adjust,
10 + adjust, 10 + adjust);
}
QPainterPath MovableCircle::shape() const
{
QPainterPath path;
qreal adjust = 0.5;
path.addEllipse(-5 - adjust, -5 - adjust,
10 + adjust, 10 + adjust);
return path;
}
void MovableCircle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
painter->setBrush(QBrush(QColor(0, 160, 230)));
painter->setPen(QPen(QColor(0, 160, 230)));
painter->drawEllipse(-5, -5, 10, 10);
}
void MovableCircle::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
_shiftMouseCoords = this->pos() - mapToScene(event->pos());
this->setCursor(QCursor(Qt::ClosedHandCursor));
}
void MovableCircle::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
auto pos = mapToScene(event->pos() + _shiftMouseCoords);
qreal xl = (pos.x() == 0) ? .1 : pos.x();
qreal yl = (pos.y() == 0) ? .1 : pos.y();
qreal arl = qAbs(xl / yl);
if (circlePos_ == eBottomRight) {
if (arl > aspectRatio_) {
pos.setX(yl * aspectRatio_);
} else {
pos.setY(xl / aspectRatio_);
}
}
if (circlePos_ == eTopLeft) {
if (arl > aspectRatio_) {
pos.setY(xl / aspectRatio_);
} else {
pos.setX(yl * aspectRatio_);
}
}
setPos(pos);
emit circleMoved();
}
void MovableCircle::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
Q_UNUSED(event);
this->setCursor(QCursor(Qt::PointingHandCursor));
}
MoveItem class:
class MoveItem : public QObject, public QGraphicsItem//public QGraphicsObject
{
Q_OBJECT
Q_INTERFACES(QGraphicsItem)
public:
explicit MoveItem(uint64_t& zc, QGraphicsItem *parent = 0);
~MoveItem();
signals:
protected:
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void wheelEvent(QGraphicsSceneWheelEvent *event) override;
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
private:
QPointF shiftMouseCoords_;
QImage qimage_;
QPixmap pixmap_;
uint64_t& zCounter_;
MovableCircle *_topLeftCircle, *_topRightCircle, *_bottomLeftCircle, *_bottomRightCircle;
QSizeF _size;
QRectF rect_;
public slots:
};
MoveItem::MoveItem(uint64_t& zc, QGraphicsItem *parent) :
QGraphicsItem(parent), zCounter_(zc)
{
setZValue(zCounter_);
qimage_ = QImage("kot.png");
pixmap_ = QPixmap::fromImage(qimage_);
_size = pixmap_.size();
rect_ = qimage_.rect();
setAcceptHoverEvents(true);
setFlag(QGraphicsItem::ItemIsMovable, true);
double ar = _size.width() / _size.height();
// Top Left
_topLeftCircle = new MovableCircle(MovableCircle::eTopLeft, ar, this);
_topLeftCircle->setPos(0, 0);
// Top Right
_topRightCircle = new MovableCircle(MovableCircle::eTopRight, ar, this);
_topRightCircle->setPos(_size.width(), 0);
// Bottom Right
_bottomRightCircle = new MovableCircle(MovableCircle::eBottomRight, ar, this);
_bottomRightCircle->setPos(_size.width(), _size.height());
// Bottom Left
_bottomLeftCircle = new MovableCircle(MovableCircle::eBottomLeft, ar, this);
_bottomLeftCircle->setPos(0, _size.height());
// Signals
// If a delimiter point has been moved, so force the item to redraw
connect(_topLeftCircle, &MovableCircle::circleMoved, this, [this](){
_bottomLeftCircle->setPos( _topLeftCircle->pos().x(), _bottomLeftCircle->pos().y());
_topRightCircle->setPos(_topRightCircle->pos().x(), _topLeftCircle->pos().y());
update(); // force to Repaint
});
connect(_topRightCircle, &MovableCircle::circleMoved, this, [this](){
_topLeftCircle->setPos(_topLeftCircle->pos().x(), _topRightCircle->pos().y());
_bottomRightCircle->setPos(_topRightCircle->pos().x(), _bottomRightCircle->pos().y());
update(); // force to Repaint
});
connect(_bottomLeftCircle, &MovableCircle::circleMoved, this, [this](){
_topLeftCircle->setPos(_bottomLeftCircle->pos().x(), _topLeftCircle->pos().y());
_bottomRightCircle->setPos(_bottomRightCircle->pos().x(), _bottomLeftCircle->pos().y());
update(); // force to Repaint
});
connect(_bottomRightCircle, &MovableCircle::circleMoved, this, [this](){
_bottomLeftCircle->setPos(_bottomLeftCircle->pos().x(), _bottomRightCircle->pos().y());
_topRightCircle->setPos(_bottomRightCircle->pos().x(), _topRightCircle->pos().y());
update(); // force to Repaint
});
}
MoveItem::~MoveItem()
{
}
QRectF MoveItem::boundingRect() const
{
qreal distX = sqrt(pow(_topLeftCircle->x() - _topRightCircle->x(),2) +
pow(_topLeftCircle->y() - _topRightCircle->y(),2)); // eucledian distance
qreal distY = sqrt(pow(_topLeftCircle->x() - _bottomLeftCircle->x(),2) +
pow(_topLeftCircle->y() - _bottomLeftCircle->y(),2)); // eucledian distance
return QRectF(qMin(_topLeftCircle->pos().x(), _topRightCircle->pos().x()) ,
qMin(_topLeftCircle->pos().y(), _bottomLeftCircle->pos().y()),
distX, distY);
}
void MoveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
painter->drawImage(boundingRect(), qimage_);
painter->setPen(QPen(QColor(0, 160, 230),2));
painter->drawRect(boundingRect());
Q_UNUSED(widget);
}
void MoveItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
this->setPos(mapToScene(event->pos()+ shiftMouseCoords_));
}
void MoveItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
setZValue(++zCounter_);
shiftMouseCoords_ = (this->pos() - mapToScene(event->pos()))/scale();
if (event->button() == Qt::LeftButton) {
if (event->modifiers() == Qt::ShiftModifier) {
} else {
QGraphicsItem::mousePressEvent(event);
}
}
}
void MoveItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
if (event->modifiers() == Qt::ShiftModifier) {
} else {
QGraphicsItem::mousePressEvent(event);
}
}
}
I found a solution using vector math.(thanks to my colleague Dima Chernikov)
ABCD - our picture.
K' - cursor point.
D2 - the point we are looking for(new position of D)
gif example: https://media.giphy.com/media/uffXKjNNy5ykzpvsR2/giphy.gif
(circlePos_ == eBottomLeft) in code
code: (I will most likely redo it later using templates. but now it is more clear for understanding)
void MovableCircle::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
auto pos = mapToScene(event->pos() + _shiftMouseCoords);
qreal xl = pos.x();
qreal yl = pos.y();
auto rect = parentItem()->boundingRect();
QPointF a(rect.x(), rect.y());
QPointF b(rect.x() + rect.width(), rect.y());
QPointF c(rect.x() + rect.width(), rect.y() + rect.height());
QPointF d(rect.x(), rect.y() + rect.height());
if (circlePos_ == eTopRight) {
Vec2d dc(c.x()-d.x(), c.y()-d.y());
Vec2d cb(b.x()-c.x(), b.y()-c.y());
Vec2d db(b.x()-d.x(), b.y()-d.y());
Vec2d dk(pos.x()-d.x(), pos.y()-d.y());
auto dc_len = dc.length();
auto cb_len = cb.length();
auto db_len = db.length();
auto dk_len = dk.length();
auto dkdb_dot = Vec2d<qreal>::dot(db, dk);
auto cos_kdb = dkdb_dot/(db_len*dk_len);
auto dd2_len = dk_len * cos_kdb;
auto x =(dd2_len * dc_len) / (std::sqrt(cb_len * cb_len + dc_len * dc_len));
auto y = std::sqrt(dd2_len * dd2_len - x * x);
if (x < 10 || y < 10) return;
pos.setX(d.x()+x);
pos.setY(d.y()-y);
}
if (circlePos_ == eBottomRight) {
Vec2d ad(d.x()-a.x(), d.y()-a.y());
Vec2d dc(c.x()-d.x(), c.y()-d.y());
Vec2d ac(c.x()-a.x(), c.y()-a.y());
Vec2d ak(pos.x()-a.x(), pos.y()-a.y());
auto ad_len = ad.length();
auto dc_len = dc.length();
auto ac_len = ac.length();
auto ak_len = ak.length();
auto akac_dot = Vec2d<qreal>::dot(ac, ak);
auto cos_kac = akac_dot/(ac_len*ak_len);
auto ad2_len = ak_len * cos_kac;
auto x =(ad2_len * dc_len) / (std::sqrt(ad_len * ad_len + dc_len * dc_len));
auto y = std::sqrt(ad2_len * ad2_len - x * x);
if (x < 10 || y < 10) return;
pos.setX(a.x()+x);
pos.setY(a.y()+y);
}
if (circlePos_ == eTopLeft) {
qDebug()<<this->parentItem()->boundingRect();
Vec2d cb(b.x()-c.x(), b.y()-c.y());
Vec2d ba(a.x()-b.x(), a.y()-b.y());
Vec2d ca(a.x()-c.x(), a.y()-c.y());
Vec2d ck(pos.x()-c.x(), pos.y()-c.y());
auto cb_len = cb.length();
auto ba_len = ba.length();
auto ca_len = ca.length();
auto ck_len = ck.length();
auto ckca_dot = Vec2d<qreal>::dot(ca, ck);
auto cos_kca = ckca_dot/(ca_len*ck_len);
auto cd2_len = ck_len * cos_kca;
auto y =(cd2_len * cb_len) / (std::sqrt(ba_len * ba_len + cb_len * cb_len));
auto x = std::sqrt(cd2_len * cd2_len - y * y);
if (x < 10 || y < 10) return;
pos.setX(c.x()-x);
pos.setY(c.y()-y);
}
if (circlePos_ == eBottomLeft) {
qDebug()<<this->parentItem()->boundingRect();
Vec2d ba(a.x()-b.x(), a.y()-b.y());
Vec2d ad(d.x()-a.x(), d.y()-a.y());
Vec2d bd(d.x()-b.x(), d.y()-b.y());
Vec2d bk(pos.x()-b.x(), pos.y()-b.y());
auto ba_len = ba.length();
auto ad_len = ad.length();
auto bd_len = bd.length();
auto bk_len = bk.length();
auto bkbd_dot = Vec2d<qreal>::dot(bd, bk);
auto cos_kdb = bkbd_dot/(bd_len*bk_len);
auto bd2_len = bk_len * cos_kdb;
auto x =(bd2_len * ba_len) / (std::sqrt(ad_len * ad_len + ba_len * ba_len));
auto y = std::sqrt(bd2_len * bd2_len - x * x);
if (x < 10 || y < 10) return;
pos.setX(b.x()-x);
pos.setY(b.y()+y);
}
setPos(pos);
emit circleMoved();
}
I have downloaded the demo program for Nucklear in LWJGL, i've managed to compile it and have it to succesfully work. Then i've tried to implement the same code into my game engine and nothing shows up, unless i disable glClear(GL_COLOR_BUFFER_BIT). At that point i can see the little Nuklear window flickering.
This is my Window class
package Engine.Renderer;
import Engine.Messages.AppMsg;
import Engine.IntApplication;
import Engine.Messages.Type;
import Simulator.Application;
import org.lwjgl.nuklear.NkColorf;
import org.lwjgl.nuklear.NkMouse;
import org.lwjgl.opengl.*;
import org.lwjgl.system.Callback;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static org.lwjgl.glfw.Callbacks.*;
import static org.lwjgl.glfw.GLFW.*;
import static org.lwjgl.nuklear.Nuklear.*;
import static org.lwjgl.opengl.ARBDebugOutput.*;
import static org.lwjgl.opengl.ARBDebugOutput.GL_DEBUG_SEVERITY_LOW_ARB;
import static org.lwjgl.opengl.GL11C.*;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.*;
public class Window
{
public Window(int width, int height, String title)
{
m_Width = width;
m_Height = height;
m_Title = title;
if(!glfwInit())
{
///TODO: LOG
Runtime.getRuntime().exit(1);
}
glfwDefaultWindowHints();
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
m_Window = glfwCreateWindow(m_Width, m_Height, m_Title, NULL, NULL);
if(m_Window == NULL)
{
///TODO: LOG
Runtime.getRuntime().exit(1);
}
glfwMakeContextCurrent(m_Window);
GLCapabilities caps = GL.createCapabilities();
glfwShowWindow(m_Window);
Callback debugProc = GLUtil.setupDebugMessageCallback();
if (caps.OpenGL43) {
GL43.glDebugMessageControl(GL43.GL_DEBUG_SOURCE_API, GL43.GL_DEBUG_TYPE_OTHER, GL43.GL_DEBUG_SEVERITY_NOTIFICATION, (IntBuffer)null, false);
} else if (caps.GL_KHR_debug) {
KHRDebug.glDebugMessageControl(
KHRDebug.GL_DEBUG_SOURCE_API,
KHRDebug.GL_DEBUG_TYPE_OTHER,
KHRDebug.GL_DEBUG_SEVERITY_NOTIFICATION,
(IntBuffer)null,
false
);
} else if (caps.GL_ARB_debug_output) {
glDebugMessageControlARB(GL_DEBUG_SOURCE_API_ARB, GL_DEBUG_TYPE_OTHER_ARB, GL_DEBUG_SEVERITY_LOW_ARB, (IntBuffer)null, false);
}
SetUpWindow();
renderer = new GUIRenderer();
}
public void Draw()
{
}
public void processEvents()
{
try (MemoryStack stack = stackPush()) {
IntBuffer w = stack.mallocInt(1);
IntBuffer h = stack.mallocInt(1);
glfwGetWindowSize(m_Window, w, h);
m_Width = w.get(0);
m_Height = h.get(0);
glfwGetFramebufferSize(m_Window, w, h);
m_DisplayWidth = w.get(0);
m_DisplayHeight = h.get(0);
}
nk_input_begin(NuklearContainer.ctx);
glfwPollEvents();
NkMouse mouse = NuklearContainer.ctx.input().mouse();
if (mouse.grab()) {
glfwSetInputMode(m_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
} else if (mouse.grabbed()) {
float prevX = mouse.prev().x();
float prevY = mouse.prev().y();
glfwSetCursorPos(m_Window, prevX, prevY);
mouse.pos().x(prevX);
mouse.pos().y(prevY);
} else if (mouse.ungrab()) {
glfwSetInputMode(m_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}
nk_input_end(NuklearContainer.ctx);
try (MemoryStack stack = stackPush()) {
IntBuffer width = stack.mallocInt(1);
IntBuffer height = stack.mallocInt(1);
glfwGetWindowSize(m_Window, width, height);
glViewport(0, 0, width.get(0), height.get(0));
NkColorf bg = NkColorf.create().r(0.10f).g(0.18f).b(0.24f).a(1.0f);
glClearColor(bg.r(), bg.g(), bg.b(), bg.a());
}
//glClear(GL_COLOR_BUFFER_BIT);
renderer.Render();
glfwSwapBuffers(m_Window);
}
public void CleanUp()
{
glfwFreeCallbacks(m_Window);
glfwDestroyWindow(m_Window);
glfwTerminate();
}
private void SetUpWindow()
{
glfwSetKeyCallback(m_Window, (window, key, scancode, action, mods) ->
{
AppMsg msg = new AppMsg();
msg.key = key;
msg.scancode = scancode;
msg.mods = mods;
if(action == 0)
msg.type = Type.KEYUP;
if(action == 1)
msg.type = Type.KEYDOWN;
if(action == 2)
msg.type = Type.KEYREPEAT;
IntApplication.OnMsgProc(msg);
});
glfwSetMouseButtonCallback(m_Window, (window, button, action, mods) ->
{
AppMsg msg = new AppMsg();
msg.button = button;
msg.mods = mods;
if(action == 0)
msg.type = Type.MOUSEUP;
if(action == 1)
msg.type = Type.MOUSEDOWN;
Application.OnMsgProc(msg);
});
glfwSetCursorPosCallback(m_Window, (window, xpos, ypos) ->
{
AppMsg msg = new AppMsg();
msg.xpos = xpos;
msg.ypos = ypos;
msg.type = Type.MOUSEMOVE;
Application.OnMsgProc(msg);
});
glfwSetWindowCloseCallback(m_Window, (window) ->
IntApplication.OnClose());
nk_init(NuklearContainer.ctx, NuklearContainer.ALLOCATOR, null);
NuklearContainer.ctx.clip()
.copy((handle, text, len) -> {
if (len == 0) {
return;
}
try (MemoryStack stack = stackPush()) {
ByteBuffer str = stack.malloc(len + 1);
memCopy(text, memAddress(str), len);
str.put(len, (byte)0);
glfwSetClipboardString(m_Window, str);
}
})
.paste((handle, edit) -> {
long text = nglfwGetClipboardString(m_Window);
if (text != NULL) {
nnk_textedit_paste(edit, text, nnk_strlen(text));
}
});
}
private long m_Window;
static int m_Width, m_Height;
static int m_DisplayWidth, m_DisplayHeight;
private String m_Title;
GUIRenderer renderer;
}
And here is my GUIRender class
package Engine.Renderer;
import static org.lwjgl.nuklear.Nuklear.*;
import static org.lwjgl.opengl.GL20C.*;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
import static org.lwjgl.stb.STBTruetype.*;
import static org.lwjgl.stb.STBTruetype.stbtt_GetCodepointHMetrics;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.*;
import static org.lwjgl.system.MemoryUtil.memAddress;
import Engine.IOUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Objects;
import org.lwjgl.nuklear.*;
import org.lwjgl.stb.STBTTAlignedQuad;
import org.lwjgl.stb.STBTTFontinfo;
import org.lwjgl.stb.STBTTPackContext;
import org.lwjgl.stb.STBTTPackedchar;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.Platform;
public class GUIRenderer extends Renderer
{
public GUIRenderer()
{
super();
try
{
this.ttf = IOUtil.ioResourceToByteBuffer("C:/Windows/Fonts/Arial.ttf", 512 * 1024);
} catch (IOException e)
{
throw new RuntimeException(e);
}
if (!m_Initialized)
Initialize();
try (MemoryStack stack = stackPush()) {
NkRect rect = NkRect.mallocStack(stack);
if (nk_begin(
NuklearContainer.ctx,
"Hello World",
nk_rect(50, 50, 230, 250, rect),
NK_WINDOW_BORDER | NK_WINDOW_NO_INPUT | NK_WINDOW_NO_INPUT | NK_WINDOW_NO_INPUT | NK_WINDOW_TITLE
)) {
nk_layout_row_static(NuklearContainer.ctx, 20, 80, 1);
nk_label(NuklearContainer.ctx, "background:", NK_TEXT_LEFT);
}
}
nk_end(NuklearContainer.ctx);
}
private void Initialize()
{
String NK_SHADER_VERSION = Platform.get() == Platform.MACOSX ? "#version 150\n" : "#version 300 es\n";
String vertex_shader =
NK_SHADER_VERSION +
"uniform mat4 ProjMtx;\n" +
"in vec2 Position;\n" +
"in vec2 TexCoord;\n" +
"in vec4 Color;\n" +
"out vec2 Frag_UV;\n" +
"out vec4 Frag_Color;\n" +
"void main() {\n" +
" Frag_UV = TexCoord;\n" +
" Frag_Color = Color;\n" +
" gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" +
"}\n";
String fragment_shader =
NK_SHADER_VERSION +
"precision mediump float;\n" +
"uniform sampler2D Texture;\n" +
"in vec2 Frag_UV;\n" +
"in vec4 Frag_Color;\n" +
"out vec4 Out_Color;\n" +
"void main(){\n" +
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n" +
"}\n";
nk_buffer_init(NuklearContainer.cmds, NuklearContainer.ALLOCATOR, BUFFER_INITIAL_SIZE);
m_Program = glCreateProgram();
m_Vertex_Shader = glCreateShader(GL_VERTEX_SHADER);
m_Fragment_Shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(m_Vertex_Shader, vertex_shader);
glShaderSource(m_Fragment_Shader, fragment_shader);
glCompileShader(m_Vertex_Shader);
glCompileShader(m_Fragment_Shader);
if (glGetShaderi(m_Vertex_Shader, GL_COMPILE_STATUS) != GL_TRUE)
{
throw new IllegalStateException();
}
if (glGetShaderi(m_Fragment_Shader, GL_COMPILE_STATUS) != GL_TRUE)
{
throw new IllegalStateException();
}
glAttachShader(m_Program, m_Vertex_Shader);
glAttachShader(m_Program, m_Fragment_Shader);
glLinkProgram(m_Program);
if (glGetProgrami(m_Program, GL_LINK_STATUS) != GL_TRUE)
{
throw new IllegalStateException();
}
m_Uniform_Texture = glGetUniformLocation(m_Program, "Texture");
m_Uniform_Proj = glGetUniformLocation(m_Program, "ProjMtx");
int attrib_pos = glGetAttribLocation(m_Program, "Position");
int attrib_uv = glGetAttribLocation(m_Program, "TexCoord");
int attrib_col = glGetAttribLocation(m_Program, "Color");
{
// buffer setup
m_Vbo = glGenBuffers();
m_Ebo = glGenBuffers();
m_Vao = glGenVertexArrays();
glBindVertexArray(m_Vao);
glBindBuffer(GL_ARRAY_BUFFER, m_Vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Ebo);
glEnableVertexAttribArray(attrib_pos);
glEnableVertexAttribArray(attrib_uv);
glEnableVertexAttribArray(attrib_col);
glVertexAttribPointer(attrib_pos, 2, GL_FLOAT, false, 20, 0);
glVertexAttribPointer(attrib_uv, 2, GL_FLOAT, false, 20, 8);
glVertexAttribPointer(attrib_col, 4, GL_UNSIGNED_BYTE, true, 20, 16);
}
{
// null texture setup
int nullTexID = glGenTextures();
NuklearContainer.null_texture.texture().id(nullTexID);
NuklearContainer.null_texture.uv().set(0.5f, 0.5f);
glBindTexture(GL_TEXTURE_2D, nullTexID);
try (MemoryStack stack = stackPush())
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, stack.ints(0xFFFFFFFF));
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
FontInit();
}
private void FontInit()
{
int BITMAP_W = 1024;
int BITMAP_H = 1024;
int FONT_HEIGHT = 18;
int fontTexID = glGenTextures();
STBTTFontinfo fontInfo = STBTTFontinfo.create();
STBTTPackedchar.Buffer cdata = STBTTPackedchar.create(95);
float scale;
float descent;
try (MemoryStack stack = stackPush()) {
stbtt_InitFont(fontInfo, ttf);
scale = stbtt_ScaleForPixelHeight(fontInfo, FONT_HEIGHT);
IntBuffer d = stack.mallocInt(1);
stbtt_GetFontVMetrics(fontInfo, null, d, null);
descent = d.get(0) * scale;
ByteBuffer bitmap = memAlloc(BITMAP_W * BITMAP_H);
STBTTPackContext pc = STBTTPackContext.mallocStack(stack);
stbtt_PackBegin(pc, bitmap, BITMAP_W, BITMAP_H, 0, 1, NULL);
stbtt_PackSetOversampling(pc, 4, 4);
stbtt_PackFontRange(pc, ttf, 0, FONT_HEIGHT, 32, cdata);
stbtt_PackEnd(pc);
// Convert R8 to RGBA8
ByteBuffer texture = memAlloc(BITMAP_W * BITMAP_H * 4);
for (int i = 0; i < bitmap.capacity(); i++) {
texture.putInt((bitmap.get(i) << 24) | 0x00FFFFFF);
}
texture.flip();
glBindTexture(GL_TEXTURE_2D, fontTexID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, BITMAP_W, BITMAP_H, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
memFree(texture);
memFree(bitmap);
}
NuklearContainer.default_font
.width((handle, h, text, len) -> {
float text_width = 0;
try (MemoryStack stack = stackPush()) {
IntBuffer unicode = stack.mallocInt(1);
int glyph_len = nnk_utf_decode(text, memAddress(unicode), len);
int text_len = glyph_len;
if (glyph_len == 0) {
return 0;
}
IntBuffer advance = stack.mallocInt(1);
while (text_len <= len && glyph_len != 0) {
if (unicode.get(0) == NK_UTF_INVALID) {
break;
}
/* query currently drawn glyph information */
stbtt_GetCodepointHMetrics(fontInfo, unicode.get(0), advance, null);
text_width += advance.get(0) * scale;
/* offset next glyph */
glyph_len = nnk_utf_decode(text + text_len, memAddress(unicode), len - text_len);
text_len += glyph_len;
}
}
return text_width;
})
.height(FONT_HEIGHT)
.query((handle, font_height, glyph, codepoint, next_codepoint) -> {
try (MemoryStack stack = stackPush()) {
FloatBuffer x = stack.floats(0.0f);
FloatBuffer y = stack.floats(0.0f);
STBTTAlignedQuad q = STBTTAlignedQuad.mallocStack(stack);
IntBuffer advance = stack.mallocInt(1);
stbtt_GetPackedQuad(cdata, BITMAP_W, BITMAP_H, codepoint - 32, x, y, q, false);
stbtt_GetCodepointHMetrics(fontInfo, codepoint, advance, null);
NkUserFontGlyph ufg = NkUserFontGlyph.create(glyph);
ufg.width(q.x1() - q.x0());
ufg.height(q.y1() - q.y0());
ufg.offset().set(q.x0(), q.y0() + (FONT_HEIGHT + descent));
ufg.xadvance(advance.get(0) * scale);
ufg.uv(0).set(q.s0(), q.t0());
ufg.uv(1).set(q.s1(), q.t1());
}
})
.texture(it -> it
.id(fontTexID));
nk_style_set_font(NuklearContainer.ctx, NuklearContainer.default_font);
}
public void Render()
{
try (MemoryStack stack = stackPush())
{
// setup global state
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_SCISSOR_TEST);
glActiveTexture(GL_TEXTURE0);
// setup program
glUseProgram(m_Program);
glUniform1i(m_Uniform_Texture, 0);
glUniformMatrix4fv(m_Uniform_Proj, false, stack.floats(
2.0f / Window.m_Width, 0.0f, 0.0f, 0.0f,
0.0f, -2.0f / Window.m_Height, 0.0f, 0.0f,
0.0f, 0.0f, -1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f
));
glViewport(0, 0, Window.m_DisplayWidth, Window.m_DisplayHeight);
}
{
// convert from command queue into draw list and draw to screen
// allocate vertex and element buffer
glBindVertexArray(m_Vao);
glBindBuffer(GL_ARRAY_BUFFER, m_Vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_Ebo);
glBufferData(GL_ARRAY_BUFFER, max_vertex_buffer, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_element_buffer, GL_STREAM_DRAW);
// load draw vertices & elements directly into vertex + element buffer
ByteBuffer vertices = Objects.requireNonNull(glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY, max_vertex_buffer, null));
ByteBuffer elements = Objects.requireNonNull(glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY, max_element_buffer, null));
try (MemoryStack stack = stackPush())
{
// fill convert configuration
NkConvertConfig config = NkConvertConfig.callocStack(stack)
.vertex_layout(VERTEX_LAYOUT)
.vertex_size(20)
.vertex_alignment(4)
.null_texture(NuklearContainer.null_texture)
.circle_segment_count(22)
.curve_segment_count(22)
.arc_segment_count(22)
.global_alpha(1.0f)
.shape_AA(NK_ANTI_ALIASING_ON)
.line_AA(NK_ANTI_ALIASING_ON);
// setup buffers to load vertices and elements
NkBuffer vbuf = NkBuffer.mallocStack(stack);
NkBuffer ebuf = NkBuffer.mallocStack(stack);
nk_buffer_init_fixed(vbuf, vertices/*, max_vertex_buffer*/);
nk_buffer_init_fixed(ebuf, elements/*, max_element_buffer*/);
nk_convert(NuklearContainer.ctx, NuklearContainer.cmds, vbuf, ebuf, config);
}
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
glUnmapBuffer(GL_ARRAY_BUFFER);
// iterate over and execute each draw command
float fb_scale_x = (float) Window.m_DisplayWidth / (float) Window.m_Width;
float fb_scale_y = (float) Window.m_DisplayHeight / (float) Window.m_Height;
long offset = NULL;
for (NkDrawCommand cmd = nk__draw_begin(NuklearContainer.ctx, NuklearContainer.cmds); cmd != null; cmd = nk__draw_next(cmd, NuklearContainer.cmds, NuklearContainer.ctx))
{
if (cmd.elem_count() == 0)
{
continue;
}
glBindTexture(GL_TEXTURE_2D, cmd.texture().id());
glScissor(
(int) (cmd.clip_rect().x() * fb_scale_x),
(int) ((Window.m_Height - (int) (cmd.clip_rect().y() + cmd.clip_rect().h())) * fb_scale_y),
(int) (cmd.clip_rect().w() * fb_scale_x),
(int) (cmd.clip_rect().h() * fb_scale_y)
);
glDrawElements(GL_TRIANGLES, cmd.elem_count(), GL_UNSIGNED_SHORT, offset);
offset += cmd.elem_count() * 2;
}
nk_clear(NuklearContainer.ctx);
}
glUseProgram(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glDisable(GL_BLEND);
glDisable(GL_SCISSOR_TEST);
}
private boolean m_Initialized;
public boolean IsInitialized() { return m_Initialized; }
private int BUFFER_INITIAL_SIZE = 4 * 1024;
long max_vertex_buffer = 512 * 1024;
long max_element_buffer = 128 * 1024;
private NkDrawVertexLayoutElement.Buffer VERTEX_LAYOUT = NkDrawVertexLayoutElement.create(4)
.position(0).attribute(NK_VERTEX_POSITION).format(NK_FORMAT_FLOAT).offset(0)
.position(1).attribute(NK_VERTEX_TEXCOORD).format(NK_FORMAT_FLOAT).offset(8)
.position(2).attribute(NK_VERTEX_COLOR).format(NK_FORMAT_R8G8B8A8).offset(16)
.position(3).attribute(NK_VERTEX_ATTRIBUTE_COUNT).format(NK_FORMAT_COUNT).offset(0)
.flip();
private ByteBuffer ttf;
}
I'm really sorry if the code is huge, but even just the GLFWDemo is 600+ lines long.
I have a list of 2d points which is a closed loop, 2D, concave polygon.
I want to generate a second polygon, which is fully inside the first polygon and each vertex/edge of the first polygon has a constant distance to each vertex/edge of the second polygon.
Basically, the first polygon would be "outer wall" and the second would be "inner wall", with the distance between two walls constant.
How to do something like that?
For the case that you do not care about self-intersections, the construction is pretty straightforward:
For each vertex of the polygon:
Take the previous and next line segment
Compute the normals of these line segments
Shift the line segments along the normal
Compute the intersection of the shifted line segments
Below is a MCVE implemented in Java/Swing. The actual computation takes place in computeOffsetPolygonPoints, and it should be easy to translate this to other languages and APIs.
For the case that you also have to handle self-intersections, things might become trickier. Then it would be necessary to define the intended result, particularly for the case that the polygon itself self-intersects...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;
public class InnerPolygonShape
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
InnerPolygonShapePanel innerPolygonShapePanel =
new InnerPolygonShapePanel();
JSlider offsetSlider = new JSlider(0, 100, 40);
offsetSlider.addChangeListener(e ->
{
double alpha = offsetSlider.getValue() / 100.0;
double offset = -50.0 + alpha * 100.0;
innerPolygonShapePanel.setOffset(offset);
});
f.getContentPane().setLayout(new BorderLayout());
f.getContentPane().add(innerPolygonShapePanel, BorderLayout.CENTER);
f.getContentPane().add(offsetSlider, BorderLayout.SOUTH);
f.setSize(800,800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class InnerPolygonShapePanel extends JPanel
implements MouseListener, MouseMotionListener
{
private final List<Point2D> points;
private Point2D draggedPoint;
private double offset = -10.0;
public InnerPolygonShapePanel()
{
this.points = new ArrayList<Point2D>();
points.add(new Point2D.Double(132,532));
points.add(new Point2D.Double(375,458));
points.add(new Point2D.Double(395,267));
points.add(new Point2D.Double(595,667));
addMouseListener(this);
addMouseMotionListener(this);
}
public void setOffset(double offset)
{
this.offset = offset;
repaint();
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLACK);
paint(g, points);
List<Point2D> offsetPolygonPoints =
computeOffsetPolygonPoints(points, offset);
g.setColor(Color.BLUE);
paint(g, offsetPolygonPoints);
}
private static void paint(Graphics2D g, List<Point2D> points)
{
for (int i = 0; i < points.size(); i++)
{
int i0 = i;
int i1 = (i + 1) % points.size();
Point2D p0 = points.get(i0);
Point2D p1 = points.get(i1);
g.draw(new Line2D.Double(p0, p1));
}
g.setColor(Color.RED);
for (Point2D p : points)
{
double r = 5;
g.draw(new Ellipse2D.Double(p.getX()-r, p.getY()-r, r+r, r+r));
}
}
private static List<Point2D> computeOffsetPolygonPoints(
List<Point2D> points, double offset)
{
List<Point2D> result = new ArrayList<Point2D>();
Point2D absoluteLocation = new Point2D.Double();
for (int i = 0; i < points.size(); i++)
{
// Consider three consecutive points (previous, current, next)
int ip = (i - 1 + points.size()) % points.size();
int ic = i;
int in = (i + 1) % points.size();
Point2D pp = points.get(ip);
Point2D pc = points.get(ic);
Point2D pn = points.get(in);
// Compute the line segments between the previous and the current
// point, and the current and the next point, and compute their
// normal
Point2D line0 = difference(pc, pp);
Point2D direction0 = normalize(line0);
Point2D normal0 = rotateCw(direction0);
Point2D line1 = difference(pn, pc);
Point2D direction1 = normalize(line1);
Point2D normal1 = rotateCw(direction1);
// Shift both line segments along the normal
Point2D segment0p0 = add(pp, offset, normal0);
Point2D segment0p1 = add(pc, offset, normal0);
Point2D segment1p0 = add(pc, offset, normal1);
Point2D segment1p1 = add(pn, offset, normal1);
// Compute the intersection between the shifted line segments
intersect(
segment0p0.getX(), segment0p0.getY(),
segment0p1.getX(), segment0p1.getY(),
segment1p0.getX(), segment1p0.getY(),
segment1p1.getX(), segment1p1.getY(),
null, absoluteLocation);
result.add(new Point2D.Double(
absoluteLocation.getX(), absoluteLocation.getY()));
}
return result;
}
#Override
public void mouseDragged(MouseEvent e)
{
if (draggedPoint != null)
{
draggedPoint.setLocation(e.getX(), e.getY());
repaint();
}
}
#Override
public void mousePressed(MouseEvent e)
{
final double thresholdSquared = 10 * 10;
Point2D p = e.getPoint();
Point2D closestPoint = null;
double minDistanceSquared = Double.MAX_VALUE;
for (Point2D point : points)
{
double dd = point.distanceSq(p);
if (dd < thresholdSquared && dd < minDistanceSquared)
{
minDistanceSquared = dd;
closestPoint = point;
}
}
draggedPoint = closestPoint;
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedPoint = null;
}
#Override
public void mouseMoved(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseClicked(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseEntered(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseExited(MouseEvent e)
{
// Nothing to do here
}
private static Point2D difference(Point2D p0, Point2D p1)
{
double dx = p0.getX() - p1.getX();
double dy = p0.getY() - p1.getY();
return new Point2D.Double(dx, dy);
}
private static Point2D add(Point2D p0, double factor, Point2D p1)
{
double x0 = p0.getX();
double y0 = p0.getY();
double x1 = p1.getX();
double y1 = p1.getY();
return new Point2D.Double(x0 + factor * x1, y0 + factor * y1);
}
private static Point2D rotateCw(Point2D p)
{
return new Point2D.Double(p.getY(), -p.getX());
}
private static Point2D normalize(Point2D p)
{
double x = p.getX();
double y = p.getY();
double length = Math.hypot(x, y);
return new Point2D.Double(x / length, y / length);
}
// From https://github.com/javagl/Geom/blob/master/src/main/java/
// de/javagl/geom/Intersections.java
private static final double DOUBLE_EPSILON = 1e-6;
/**
* Computes the intersection of the specified lines.
*
* Ported from
* http://www.geometrictools.com/LibMathematics/Intersection/
* Wm5IntrSegment2Segment2.cpp
*
* #param s0x0 x-coordinate of point 0 of line segment 0
* #param s0y0 y-coordinate of point 0 of line segment 0
* #param s0x1 x-coordinate of point 1 of line segment 0
* #param s0y1 y-coordinate of point 1 of line segment 0
* #param s1x0 x-coordinate of point 0 of line segment 1
* #param s1y0 y-coordinate of point 0 of line segment 1
* #param s1x1 x-coordinate of point 1 of line segment 1
* #param s1y1 y-coordinate of point 1 of line segment 1
* #param relativeLocation Optional location that stores the
* relative location of the intersection point on
* the given line segments
* #param absoluteLocation Optional location that stores the
* absolute location of the intersection point
* #return Whether the lines intersect
*/
public static boolean intersect(
double s0x0, double s0y0,
double s0x1, double s0y1,
double s1x0, double s1y0,
double s1x1, double s1y1,
Point2D relativeLocation,
Point2D absoluteLocation)
{
double dx0 = s0x1 - s0x0;
double dy0 = s0y1 - s0y0;
double dx1 = s1x1 - s1x0;
double dy1 = s1y1 - s1y0;
double invLen0 = 1.0 / Math.sqrt(dx0*dx0+dy0*dy0);
double invLen1 = 1.0 / Math.sqrt(dx1*dx1+dy1*dy1);
double dir0x = dx0 * invLen0;
double dir0y = dy0 * invLen0;
double dir1x = dx1 * invLen1;
double dir1y = dy1 * invLen1;
double dot = dotPerp(dir0x, dir0y, dir1x, dir1y);
if (Math.abs(dot) > DOUBLE_EPSILON)
{
if (relativeLocation != null || absoluteLocation != null)
{
double c0x = s0x0 + dx0 * 0.5;
double c0y = s0y0 + dy0 * 0.5;
double c1x = s1x0 + dx1 * 0.5;
double c1y = s1y0 + dy1 * 0.5;
double cdx = c1x - c0x;
double cdy = c1y - c0y;
double dot0 = dotPerp(cdx, cdy, dir0x, dir0y);
double dot1 = dotPerp(cdx, cdy, dir1x, dir1y);
double invDot = 1.0/dot;
double s0 = dot1*invDot;
double s1 = dot0*invDot;
if (relativeLocation != null)
{
double n0 = (s0 * invLen0) + 0.5;
double n1 = (s1 * invLen1) + 0.5;
relativeLocation.setLocation(n0, n1);
}
if (absoluteLocation != null)
{
double x = c0x + s0 * dir0x;
double y = c0y + s0 * dir0y;
absoluteLocation.setLocation(x, y);
}
}
return true;
}
return false;
}
/**
* Returns the perpendicular dot product, i.e. the length
* of the vector (x0,y0,0)x(x1,y1,0).
*
* #param x0 Coordinate x0
* #param y0 Coordinate y0
* #param x1 Coordinate x1
* #param y1 Coordinate y1
* #return The length of the cross product vector
*/
private static double dotPerp(double x0, double y0, double x1, double y1)
{
return x0*y1 - y0*x1;
}
}
I was trying to translate a Turing Diffusion model from JavaScript to JavaFX as seen here:
CODE
VIDEO
... but my code is not behaving properly. I believe is a combination of bad logic and the way I am updating the UI. Any help would be greatly appreciated. Code compiles and runs.
package application;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.ScatterChart;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Slider;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
public class Main extends Application {
private double dA = 1;
private double dB = 0.5;
private double feed = 0.055;
private double k = 0.062;
// Concentrate [x][y][t]
private double[][][] A = new double[100][100][100];
private double[][][] B = new double[100][100][100];
final NumberAxis xAxis = new NumberAxis(0, 100, 10);
final NumberAxis yAxis = new NumberAxis(0, 100, 10);
final ScatterChart<Number,Number> sc = new ScatterChart<Number,Number>(xAxis,yAxis);
Slider slider = new Slider(0,99,1);
VBox root = new VBox();
#SuppressWarnings("rawtypes")
XYChart.Series vizualize_A = new XYChart.Series();
#SuppressWarnings("rawtypes")
XYChart.Series vizualize_B = new XYChart.Series();
#SuppressWarnings("rawtypes")
XYChart.Data data_A;
#SuppressWarnings("rawtypes")
XYChart.Data data_B;
#SuppressWarnings("unchecked")
#Override
public void start(Stage primaryStage) {
try {
xAxis.setLabel("x");
yAxis.setLabel("y");
sc.setTitle("Turing Diffusion");
initArray();
update();
draw(0);
sc.getData().setAll(vizualize_A, vizualize_B);
sc.setLegendVisible(false);
sc.setAnimated(false);
sc.setPrefSize(500, 500);
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMajorTickUnit(10f);
slider.setBlockIncrement(1f);
slider.setValue(0);
root.getChildren().addAll(sc, slider);
Scene scene = new Scene(root,700,550);
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
slider.valueProperty().addListener((obs, oldval, newVal) -> {
draw(newVal.intValue());
Platform.runLater(new Runnable() {
#Override
public void run() {
sc.getData().setAll(vizualize_A, vizualize_B);
}
});
});
}
private void initArray(){
for (int m = 0; m <= (100-1); m++ ){
// y: j
for (int j = 0; j <= (100-1); j++){
// x: i
for (int i = 0; i <= (100-1); i++){
A[i][j][m] = 1;
// B[i][j][m] = 1;
if (i >= 20 && i<= 35 && j >= 35 && j <= 75 && m == 0) {
B[i][j][m] = 1;
} else {
B[i][j][m] = 0;
}
}
}
}
}
Rectangle rect_A[][][] = new Rectangle[100][100][100];
Rectangle rect_B[][][] = new Rectangle[100][100][100];
#SuppressWarnings({ "rawtypes", "unchecked" })
public void draw(int time){
vizualize_A.getData().clear();
vizualize_B.getData().clear();
sc.getData().remove(vizualize_A);
sc.getData().remove(vizualize_B);
// y: j
for (int j = 0; j <= (100-1); j++){
// x: i
for (int i = 0; i <= (100-1); i++){
int m = time;
data_A = new XYChart.Data(i, j);
data_B = new XYChart.Data(i, j);
// Type A
rect_A[i][j][m] = new Rectangle(5, 5);
rect_A[i][j][m].setFill(Color.RED);
data_A.setNode(rect_A[i][j][m]);
data_A.getNode().setOpacity(A[i][j][m]);
vizualize_A.getData().add(data_A);
// Type B
rect_B[i][j][m] = new Rectangle(5, 5);
rect_B[i][j][m].setFill(Color.BLACK);
data_B.setNode(rect_B[i][j][m]);
data_B.getNode().setOpacity(B[i][j][m]);
vizualize_B.getData().add(data_B);
}
}
}
public void update(){
for (int m = 1; m <= (100-2); m++ ){
// y: j
for (int j = 1; j <= (100-2); j++){
// x: i
for (int i = 1; i <= (100-2); i++){
A[i][j][m+1] = A[i][j][m] + (dA * laplace(A, i, j, m)) - (A[i][j][m] * Math.pow(B[i][j][m] ,2)) + (feed * (1 - A[i][j][m]));
B[i][j][m+1] = B[i][j][m] + (dB * laplace(B, i, j, m)) + (A[i][j][m] * Math.pow(B[i][j][m] ,2)) - (( k + feed) + B[i][j][m]);
A[i][j][m+1] = Math.min(Math.max(A[i][j][m+1], (double) 0), (double) 1);
B[i][j][m+1] = Math.min(Math.max(B[i][j][m+1], (double) 0), (double) 1);
}
}
}
}
public double laplace(double[][][] xArray, int x, int y, int time){
double laValue = 0;
laValue = laValue + xArray[x][y][time] * -1;
laValue = laValue + xArray[x - 1][y][time] * 0.2;
laValue = laValue + xArray[x + 1][y][time] * 0.2;
laValue = laValue + xArray[x][y + 1][time] * 0.2;
laValue = laValue + xArray[x][y - 1][time] * 0.2;
laValue = laValue + xArray[x - 1][y - 1][time] * 0.05;
laValue = laValue + xArray[x + 1][y - 1][time] * 0.05;
laValue = laValue + xArray[x + 1][y + 1][time] * 0.05;
laValue = laValue + xArray[x - 1][y + 1][time] * 0.05;
return laValue;
}
public static void main(String[] args) {
launch(args);
}
}
Update: Fixed one error in the update equations.
The issue:
You start update at "time" m=1. However m is the time of the state you read from, which means you start reading from the second state, not the initial state:
public void update(){
for (int m = 0; m <= (100-2); m++){ // fix here
Note that the following code contains a few modifications, like displaying the code in a Canvas (a chart seems to be a sorry choice to display the data) only doing the necessary initialisations and changing the index order (using "time" first allows you to pass the data for one "frame" around as double[][]):
private static final double dA = 1;
private static final double dB = 0.5;
private static final double feed = 0.055;
private static final double k = 0.062;
private static final int FRAMES = 200;
private static final int WIDTH = 100;
private static final int HEIGHT = 100;
private double[][][] A = new double[FRAMES][WIDTH][HEIGHT];
private double[][][] B = new double[FRAMES][WIDTH][HEIGHT];
Slider slider = new Slider(0, FRAMES - 1, 1);
VBox root = new VBox();
PixelWriter writer;
#Override
public void start(Stage primaryStage) {
Canvas canvas = new Canvas(WIDTH, HEIGHT);
writer = canvas.getGraphicsContext2D().getPixelWriter();
try {
initArray();
update();
draw(0);
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMajorTickUnit(10f);
slider.setBlockIncrement(1f);
slider.setValue(0);
root.getChildren().addAll(canvas, slider);
Scene scene = new Scene(root, 700, 550);
primaryStage.setScene(scene);
primaryStage.show();
} catch (Exception e) {
e.printStackTrace();
}
slider.valueProperty().addListener((obs, oldval, newVal) -> {
draw(newVal.intValue());
});
}
private void initArray() {
double[][] state = A[0];
for (int j = 0; j < WIDTH; j++) {
Arrays.fill(state[j], 1);
}
// fill border
for (double[][] s : A) {
Arrays.fill(s[0], 1);
Arrays.fill(s[WIDTH - 1], 1);
for (int i = 1; i < (WIDTH - 1); i++) {
double[] line = s[i];
line[0] = 1;
line[HEIGHT - 1] = 1;
}
}
state = B[0];
for (int i = 20; i <= 35; i++) {
Arrays.fill(state[i], 35, 75 + 1, 1);
}
}
public void draw(int time) {
double[][] stateA = A[time];
double[][] stateB = B[time];
for (int i = 0; i < WIDTH; i++) {
double[] lineA = stateA[i];
double[] lineB = stateB[i];
for (int j = 0; j < HEIGHT; j++) {
double a = lineA[j];
double b = lineB[j];
double result = Math.max(0, a - b);
writer.setArgb(i, j,
0xff000000
| (((int) (0xff * result)) << 16));
}
}
}
public void update() {
for (int m = 0; m < (FRAMES - 1); m++) {
double[][] sourceA = A[m];
double[][] sourceB = B[m];
double[][] targetA = A[m + 1];
double[][] targetB = B[m + 1];
for (int i = 1; i <= (WIDTH - 2); i++) {
double[] sourceLineA = sourceA[i];
double[] sourceLineB = sourceB[i];
double[] targetLineA = targetA[i];
double[] targetLineB = targetB[i];
for (int j = 1; j <= (HEIGHT - 2); j++) {
double a = sourceLineA[j];
double b = sourceLineB[j];
targetLineA[j] = Math.max(0, Math.min(1, a
+ dA * laplace(sourceA, i, j)
- a * b * b
+ (feed * (1 - a))));
targetLineB[j] = Math.max(0, Math.min(1, b
+ dB * laplace(sourceB, i, j)
+ (a * b * b)
- ((k + feed) * b)));
}
}
}
}
public double laplace(double[][] xArray, int x, int y) {
return 0.2 * (xArray[x - 1][y] + xArray[x + 1][y] + xArray[x][y + 1] + xArray[x][y - 1])
+ 0.05 * (xArray[x + 1][y + 1] + xArray[x + 1][y - 1] + xArray[x - 1][y + 1] + xArray[x - 1][y - 1])
- xArray[x][y];
}
public static void main(String[] args) {
launch(args);
}
I copied the example from this site click
It is working well after fixing some minor things and extending the shader for my purpose. Now i want to move/translate the texture to the right side of the screen.
For that case i have to add a ModelViewProjection Matrix to my code and as well to the shader, right?
After doing this the Texture is not showing up anymore :-( What am I doing wrong?
So the code is working until i change in my shader gl_position from :
gl_Position= a_position to gl_Position= a_position * ModelViewProjectionMatrix
There is no error but also no texture...
The output for the ModelViewProjection is : [0.0,0.0,0.0,0.0] ... why?
This is the Source-Code :
Activity:
//Activity
public class MainActivity extends Activity {
private MainView mView;
//private WakeLock mWL;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mView = new MainView(this);
setContentView(mView);
}
#Override
protected void onPause() {
mView.onPause();
super.onPause();
}
#Override
protected void onResume() {
super.onResume();
mView.onResume();
}
}
SurfaceView
//View
class MainView extends GLSurfaceView {
CameraRenderer mRenderer;
MainView(Context context) {
super(context);
mRenderer = new CameraRenderer(this);
setEGLContextClientVersion(2);
setRenderer(mRenderer);
//setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
public void surfaceCreated(SurfaceHolder holder) {
super.surfaceCreated(holder);
}
public void surfaceDestroyed(SurfaceHolder holder) {
mRenderer.close();
super.surfaceDestroyed(holder);
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
super.surfaceChanged(holder, format, w, h);
}
}
CameraRenderer:
public class CameraRenderer implements GLSurfaceView.Renderer,
SurfaceTexture.OnFrameAvailableListener {
private final String vss = "uniform mat4 uMVPMatrix;\n"
+ "attribute vec2 vPosition;\n"
+ "attribute vec2 vTexCoord;\n"
+ ""
+ "varying vec2 texCoord;\n"
+ "vec4 Distort(vec4 p){ \n"
+ " float BarrelPower = 1.0; \n"
+ " vec2 v = p.xy / p.w; \n"
+ " float radius = length(v); \n"
+ " if (radius > 0.0){ \n"
+ " float theta = atan(v.y,v.x); \n"
+ " radius = pow(radius, BarrelPower);\n"
+ " v.x = radius * cos(theta); \n"
+ " v.y = radius * sin(theta); \n"
+ " p.xy = v.xy * p.w; \n"
+ " }"
+ " \n"
+ " return p; \n"
+ " } \n"
+ "void main() {\n"
+" vec4 a_position = vec4 ( vPosition.x, vPosition.y, 0.0, 1.0 );"
+ "vec4 test = uMVPMatrix * a_position; \n"
+ "texCoord = vTexCoord;\n"
+ "gl_Position = test;\n"
+ "}";
private final String fss = "#extension GL_OES_EGL_image_external : require\n"
+ "precision mediump float;\n"
+ "uniform samplerExternalOES sTexture;\n"
+ "varying vec2 texCoord;\n" + "void main() {\n"
+ " gl_FragColor = texture2D(sTexture,texCoord);\n" + "}";
private int[] hTex;
private FloatBuffer pVertexRight;
private FloatBuffer pTexCoordRight;
private int hProgram;
private Camera mCamera;
private SurfaceTexture mSTexture;
private boolean mUpdateST = false;
private MainView mView;
private float[] mMVPMatrix = new float[16];
private float[] mProjMatrix = new float[16];
private float[] mMMatrix = new float[16];
private float[] mVMatrix = new float[16];
private int muMVPMatrixHandle;
private int maPositionHandle;
private int maTextureHandle;
CameraRenderer(MainView view) {
mView = view;
float[] vtmpRight = { 0.35f, -0.35f, -0.35f, -0.35f, 0.35f, 0.35f,
-0.35f, 0.35f };
float[] ttmpRight = { 0.35f, 0.35f, 0.0f, 0.35f, 0.35f, 0.0f, 0.0f,
0.0f };
pVertexRight = ByteBuffer.allocateDirect(8 * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
pVertexRight.put(vtmpRight);
pVertexRight.position(0);
pTexCoordRight = ByteBuffer.allocateDirect(8 * 4)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
pTexCoordRight.put(ttmpRight);
pTexCoordRight.position(0);
}
public void close() {
mUpdateST = false;
mSTexture.release();
mCamera.stopPreview();
mCamera = null;
deleteTex();
}
public void onSurfaceCreated(GL10 unused,
javax.microedition.khronos.egl.EGLConfig config) {
// String extensions = GLES20.glGetString(GLES20.GL_EXTENSIONS);
// Log.i("mr", "Gl extensions: " + extensions);
// Assert.assertTrue(extensions.contains("OES_EGL_image_external"));
initTex();
mSTexture = new SurfaceTexture(hTex[0]);
mSTexture.setOnFrameAvailableListener(this);
mCamera = Camera.open();
try {
mCamera.setPreviewTexture(mSTexture);
Log.i("GLES20", "setPreviewTexture success");
} catch (IOException ioe) {
}
GLES20.glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
hProgram = loadShader(vss, fss);
}
public void onDrawFrame(GL10 unused) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
synchronized (this) {
if (mUpdateST) {
mSTexture.updateTexImage();
mUpdateST = false;
}
}
GLES20.glUseProgram(hProgram);
int ph = GLES20.glGetAttribLocation(hProgram, "vPosition");
int tch = GLES20.glGetAttribLocation(hProgram, "vTexCoord");
int th = GLES20.glGetUniformLocation(hProgram, "sTexture");
muMVPMatrixHandle = GLES20.glGetUniformLocation(hProgram, "uMVPMatrix");
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, hTex[0]);
GLES20.glUniform1i(th, 0);
GLES20.glVertexAttribPointer(ph, 2, GLES20.GL_FLOAT, false, 4 * 2,
pVertexRight);
GLES20.glVertexAttribPointer(tch, 2, GLES20.GL_FLOAT, false, 4 * 2,
pTexCoordRight);
GLES20.glEnableVertexAttribArray(ph);
GLES20.glEnableVertexAttribArray(tch);
Matrix.setRotateM(mMMatrix, 0, 0, 0, 0, 1.0f);
Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
GLES20.glUniformMatrix4fv( muMVPMatrixHandle, 1, false, mMVPMatrix, 0 );
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
// right
GLES20.glFlush();
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
Camera.Parameters param = mCamera.getParameters();
List<Size> psize = param.getSupportedPreviewSizes();
if (psize.size() > 0) {
int i;
for (i = 0; i < psize.size(); i++) {
if (psize.get(i).width < width || psize.get(i).height < height)
break;
}
if (i > 0)
i--;
param.setPreviewSize(psize.get(i).width, psize.get(i).height);
Log.i("mr", "ssize: " + psize.get(i).width + ", "
+ psize.get(i).height);
}
mCamera.setParameters(param);
mCamera.startPreview();
}
private void initTex() {
hTex = new int[1];
GLES20.glGenTextures(1, hTex, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, hTex[0]);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
}
private void deleteTex() {
GLES20.glDeleteTextures(1, hTex, 0);
}
public synchronized void onFrameAvailable(SurfaceTexture st) {
mUpdateST = true;
// mView.requestRender();
}
private static int loadShader(String vss, String fss) {
int vshader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
GLES20.glShaderSource(vshader, vss);
GLES20.glCompileShader(vshader);
int[] compiled = new int[1];
GLES20.glGetShaderiv(vshader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e("Shader", "Could not compile vshader");
Log.v("Shader",
"Could not compile vshader:"
+ GLES20.glGetShaderInfoLog(vshader));
GLES20.glDeleteShader(vshader);
vshader = 0;
} else {
Log.i("GLES20", "compile vertex success");
}
int fshader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
GLES20.glShaderSource(fshader, fss);
GLES20.glCompileShader(fshader);
GLES20.glGetShaderiv(fshader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e("Shader", "Could not compile fshader");
Log.v("Shader",
"Could not compile fshader:"
+ GLES20.glGetShaderInfoLog(fshader));
GLES20.glDeleteShader(fshader);
fshader = 0;
} else {
Log.i("GLES20", "compile fragment success");
}
int program = GLES20.glCreateProgram();
GLES20.glAttachShader(program, vshader);
GLES20.glAttachShader(program, fshader);
GLES20.glLinkProgram(program);
return program;
}
}
I cant's see where you set the values from the matrix mMVPMatrix to the uniform uMVPMatrix. You should do something like this before drawing the arrays:
GLES20.glUniformMatrix4fv( muMVPMatrixHandle, 1, false, mMVPMatrix, 0 );
EDIT: #derhass #reto-koradi thank you for correcting my answer
Last Post and additionally defining the viewFrustum did it. Thanks a lot ! :-)
onSurfaceChange(width,height){
...
float ratio = (float) width / height;
Matrix.frustumM(mProjectionMatrix, 0, ratio, -ratio, -1, 1, 3, 7);
....