[SOLVED] Need help on mesh subclass


I don’t know how to continue.

I tested my code with swing and custom painting (2D of cause). When everything was fine, I transformed the code to jme3 and 3D.
But result is completely different, although I use the same code.
May be I have overlooked something, but I have no idea about it.

As a test image I tried to create a lower case letter “N”, which looks ok from swing …

same code run by jme3:

Here is the code …
first the Application class

package de.schwarzrot.jme3;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.jayfella.camera.FocusCameraState;
import com.jme3.app.SimpleApplication;
import com.jme3.bounding.BoundingBox;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.debug.Arrow;

import de.schwarzrot.util.IGeoLineParser;
import de.schwarzrot.widgets.jme3.Arc;
import de.schwarzrot.widgets.jme3.ICreator3D;
import de.schwarzrot.widgets.jme3.Line;

public class Preview3DCreator extends SimpleApplication implements ICreator3D {
   public Preview3DCreator() {
      geoCache = new HashMap<Integer, Geometry>();

   public void createArc(Vector3f from, Vector3f to, Vector3f center, int lineNum, boolean clockwise) {
      Arc      a  = new Arc(from, to, center, lineNum, clockwise);
      Geometry ga = new Geometry("Arc", a);
      Material m  = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

      m.setColor("Color", ColorRGBA.White);

      geoCache.put(lineNum, ga);

   public void createLine(Vector3f from, Vector3f to, int lineNum, boolean fastMove) {
      Line     l  = new Line(from, to, lineNum, fastMove);
      Geometry gl = new Geometry("Line", l);
      Material m  = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

      m.setColor("Color", fastMove ? ColorRGBA.Gray : ColorRGBA.White);
      m.getAdditionalRenderState().setLineWidth(fastMove ? 1 : 2);

      geoCache.put(lineNum, gl);

   public void processGeomFile(File f) {
      BufferedReader br   = null;
      Vector3f       last = null;
      String         line;

      try {
         br = new BufferedReader(new FileReader(f));

         while ((line = br.readLine()) != null) {
            last = geoParser.parseLine(line, last, this);
      } catch (IOException e) {
      } finally {
         if (br != null) {
            try {
            } catch (IOException ex) {

   public void setGeoParser(IGeoLineParser p) {
      this.geoParser = p;

   public void simpleInitApp() {
      Geometry geo = createOrigin();

      focusCameraState = new FocusCameraState();

   // creation of graphic primitives is too early, so have to wait for material
   // to become available
   public void wait4Material() {
      Material m = null;

      while (m == null) {
         try {
            Thread.sleep(100, 0);
         } catch (InterruptedException e) {
         if (assetManager != null) {
            m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

   protected Geometry createOrigin() {
      Arrow    a  = new Arrow(new Vector3f(1f, 0, 0));
      Geometry ga = new Geometry("Arrow", a);
      Material m  = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

      m.setColor("Color", ColorRGBA.Red);


      a  = new Arrow(new Vector3f(0, 1f, 0));
      ga = new Geometry("Arrow", a);
      m  = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

      m.setColor("Color", ColorRGBA.Green);


      a  = new Arrow(new Vector3f(0, 0, 1f));
      ga = new Geometry("Arrow", a);
      m  = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");

      m.setColor("Color", ColorRGBA.Blue);


      return ga;

   protected void reset() {

   // code from 1000ml
   private void updateCamera() {
      BoundingBox bbox = (BoundingBox) rootNode.getWorldBound();

      if (bbox == null)
      float extent = bbox.getXExtent();

      if (bbox.getYExtent() > extent)
         extent = bbox.getYExtent();
      if (bbox.getZExtent() > extent)
         extent = bbox.getZExtent();

       *  Calculate camera distance where BoundingBox is fully visible
      float dist = extent / (float) Math.tan(s_camFov * 0.5 * FastMath.DEG_TO_RAD);

      cam.setLocation(new Vector3f(0, dist * (float) Math.sin(s_camElevation),
            dist * (float) Math.cos(s_camElevation)));
      float y = bbox.getCenter().y * 0.666f;

      cam.lookAt(new Vector3f(0, y, 0), Vector3f.UNIT_Y);

   private IGeoLineParser         geoParser;
   private FocusCameraState       focusCameraState;
   private Map<Integer, Geometry> geoCache;
   private static final float     s_camFov       = 30;
   private static final float     s_camElevation = 40 * FastMath.DEG_TO_RAD;

which is embedded in a Swing panel like this:
(this Panel contains the test-data)

package de.schwarzrot.jme3;

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Logger;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;

import com.jme3.app.SimpleApplication;
import com.jme3.math.Vector3f;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeCanvasContext;
import com.jme3.util.JmeFormatter;

import de.schwarzrot.widgets.jme3.ICreator3D;

public class JME3Pane extends JPanel {
   public JME3Pane(SimpleApplication app) {
      AppSettings settings = new AppSettings(true);

      JME3Pane.app = app;


      context = (JmeCanvasContext) app.getContext();
      canvas  = context.getCanvas();
      canvas.setSize(settings.getWidth(), settings.getHeight());
      setLayout(new BorderLayout());
      add(canvas, BorderLayout.CENTER);

   protected void createPrimitives(ICreator3D app) {
      app.createLine(new Vector3f(13.279f, 0.250f, 3.000f), new Vector3f(13.279f, 0.250f, -5.000f), 330,
      app.createLine(new Vector3f(13.279f, 0.250f, -5.000f), new Vector3f(14.797f, 7.510f, -5.000f), 340,
      app.createLine(new Vector3f(14.797f, 7.510f, -5.000f), new Vector3f(16.656f, 7.510f, -5.000f), 350,
      app.createLine(new Vector3f(16.656f, 7.510f, -5.000f), new Vector3f(16.465f, 6.566f, -5.000f), 360,
      app.createArc(new Vector3f(16.465f, 6.566f, -5.000f), new Vector3f(17.082f, 7.043f, -5.000f),
            new Vector3f(20.876f, 1.496f, -5.000f), 370, true);
      app.createArc(new Vector3f(17.082f, 7.043f, -5.000f), new Vector3f(17.764f, 7.421f, -5.000f),
            new Vector3f(19.351f, 3.751f, -5.000f), 380, true);
      app.createArc(new Vector3f(17.764f, 7.421f, -5.000f), new Vector3f(19.042f, 7.681f, -5.000f),
            new Vector3f(19.031f, 4.458f, -5.000f), 390, true);
      app.createArc(new Vector3f(19.042f, 7.681f, -5.000f), new Vector3f(19.798f, 7.568f, -5.000f),
            new Vector3f(19.074f, 5.306f, -5.000f), 400, true);
      app.createArc(new Vector3f(19.798f, 7.568f, -5.000f), new Vector3f(20.450f, 7.168f, -5.000f),
            new Vector3f(19.284f, 5.997f, -5.000f), 410, true);
      app.createArc(new Vector3f(20.450f, 7.168f, -5.000f), new Vector3f(20.851f, 6.542f, -5.000f),
            new Vector3f(19.285f, 5.981f, -5.000f), 420, true);
      app.createArc(new Vector3f(20.851f, 6.542f, -5.000f), new Vector3f(20.970f, 5.808f, -5.000f),
            new Vector3f(18.815f, 5.836f, -5.000f), 430, true);
      app.createArc(new Vector3f(20.970f, 5.808f, -5.000f), new Vector3f(20.885f, 5.079f, -5.000f),
            new Vector3f(15.882f, 6.033f, -5.000f), 440, true);
      app.createArc(new Vector3f(20.885f, 5.079f, -5.000f), new Vector3f(20.744f, 4.358f, -5.000f),
            new Vector3f(1.159f, 8.548f, -5.000f), 450, true);
      app.createLine(new Vector3f(20.744f, 4.358f, -5.000f), new Vector3f(19.890f, 0.250f, -5.000f), 460,
      app.createLine(new Vector3f(19.890f, 0.250f, -5.000f), new Vector3f(17.928f, 0.250f, -5.000f), 470,
      app.createLine(new Vector3f(17.928f, 0.250f, -5.000f), new Vector3f(18.789f, 4.372f, -5.000f), 480,
      app.createArc(new Vector3f(18.789f, 4.372f, -5.000f), new Vector3f(18.898f, 4.920f, -5.000f),
            new Vector3f(-12.620f, 10.920f, -5.000f), 490, false);
      app.createArc(new Vector3f(18.898f, 4.920f, -5.000f), new Vector3f(18.980f, 5.473f, -5.000f),
            new Vector3f(14.072f, 5.920f, -5.000f), 500, false);
      app.createArc(new Vector3f(18.980f, 5.473f, -5.000f), new Vector3f(18.755f, 6.061f, -5.000f),
            new Vector3f(18.210f, 5.514f, -5.000f), 510, false);
      app.createArc(new Vector3f(18.755f, 6.061f, -5.000f), new Vector3f(18.140f, 6.279f, -5.000f),
            new Vector3f(18.188f, 5.441f, -5.000f), 520, false);
      app.createArc(new Vector3f(18.140f, 6.279f, -5.000f), new Vector3f(17.634f, 6.169f, -5.000f),
            new Vector3f(18.166f, 4.950f, -5.000f), 530, false);
      app.createArc(new Vector3f(17.634f, 6.169f, -5.000f), new Vector3f(17.183f, 5.917f, -5.000f),
            new Vector3f(18.612f, 3.881f, -5.000f), 540, false);
      app.createArc(new Vector3f(17.183f, 5.917f, -5.000f), new Vector3f(16.355f, 4.960f, -5.000f),
            new Vector3f(18.615f, 3.843f, -5.000f), 550, false);
      app.createArc(new Vector3f(16.355f, 4.960f, -5.000f), new Vector3f(16.066f, 4.108f, -5.000f),
            new Vector3f(20.738f, 2.998f, -5.000f), 560, false);
      app.createArc(new Vector3f(16.066f, 4.108f, -5.000f), new Vector3f(15.863f, 3.230f, -5.000f),
            new Vector3f(34.239f, -0.562f, -5.000f), 570, false);
      app.createLine(new Vector3f(15.863f, 3.230f, -5.000f), new Vector3f(15.241f, 0.250f, -5.000f), 580,
      app.createLine(new Vector3f(15.241f, 0.250f, -5.000f), new Vector3f(13.279f, 0.250f, -5.000f), 590,

   public static void main(String[] args) {
      JmeFormatter formatter      = new JmeFormatter();
      Handler      consoleHandler = new ConsoleHandler();


      try {
      } catch (InterruptedException ex) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            boolean simpleTest = false;


            if (simpleTest) {
               JME3Pane pane3DCanvas = new JME3Pane(new LineWidthTest());

               frame.add(pane3DCanvas, BorderLayout.CENTER);
            } else {
               Preview3DCreator app          = new Preview3DCreator();
               final JME3Pane   pane3DCanvas = new JME3Pane(app);

               frame.add(pane3DCanvas, BorderLayout.CENTER);
               SwingUtilities.invokeLater(new Runnable() {
                  public void run() {

   private static void createFrame() {
      frame = new JFrame("Test JME3-Integration");
      frame.addWindowListener(new WindowAdapter() {
         public void windowClosed(WindowEvent e) {

   private static final long        serialVersionUID = 1L;
   private static SimpleApplication app;
   private static JmeCanvasContext  context;
   private static Canvas            canvas;
   private static JFrame            frame;

and here is the point of interest, my implementation of an arc:

package de.schwarzrot.widgets.jme3;

import java.nio.FloatBuffer;

import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;

public class Arc extends Mesh {
   // based on the work of Lim, YongHoon
   public Arc(Vector3f from, Vector3f to, Vector3f center, int lineNum, boolean clockwise) {
      FloatBuffer vertexBuf = createBuffer(from, to, center);

      this.lineNum   = lineNum;
      this.clockwise = clockwise;
      setBuffer(Type.Position, 3, vertexBuf);

   public int getLineNum() {
      return lineNum;

   public boolean isClockwise() {
      return clockwise;

   private FloatBuffer createBuffer(Vector3f from, Vector3f to, Vector3f center) {
      Vector3f base = new Vector3f(1, 0, 0);
      Vector3f s    = from.subtract(center);
      Vector3f e    = to.subtract(center);
      float    r    = s.length();
      //      float    r1   = e.length();

      float a0 = base.angleBetween(s);
      float a1 = base.angleBetween(e);

      if (s.y > center.y)
         a0 = FastMath.TWO_PI - a0;
      if (e.y > center.y)
         a1 = FastMath.TWO_PI - a1;
      float arc = a1 - a0;

      if (!clockwise && a0 < a1)
         arc = FastMath.TWO_PI - arc;
      else if (clockwise && a0 > a1)
         arc = FastMath.TWO_PI - a0 + a1;

      float       step  = arc / samples;
      float       zStep = (to.z - from.z) / samples;
      float       x     = 0, y = 0, z = from.getZ(), theta = a0;
      FloatBuffer buf   = BufferUtils.createVector3Buffer(samples + 1);

      if (!clockwise && step > 0)
         step *= -1;

      for (int i = 0; i <= samples; ++i, theta += step, z += zStep) {
         x = center.x + r * FastMath.cos(theta);
         y = center.y - r * FastMath.sin(theta);
      return buf;

   private int        lineNum;
   private boolean    clockwise;
   private static int samples = 64;

Any help is appreciated

Haven’t done anything with this kinda stuff so most likely I am way offbase but I was wondering what happens if you use the guiNode? instead of rootNode?

Edit: to clarify, if you don’t convert to 3d, but use guiNode to get 2d working first and then move to 3d (rootNode) adding Z stuff.

If it were me I’d draw each arc separately and color them differently to see which one was the cause and go from there, pinpointing the issue.

Or at least printing the output to console.

Thats a good idea, looks like the top arc of the N is the problem so would be easier to isolate.

Edit: assuming thats not overlap on the bottm arc.

This code looks super weird to me. What did it look like in the swing version?

Weird because you are getting the angle between s and the X axis… and e and the X axis. (Which is either not really what you want or there are way easier ways to get that.)

…then you compare the y value of something normalized against the center to the center y value. Which in most use-cases seems like it would always be less unless center happened to be in 0…1 range.



thank you all for your attention

No, the angle is exactly what I wanted.
If there are easier ways, than I don’t know them.
As I already stated, I’m not a mathematician …
There is a lot of things I don’t know. For sure!
So if you like to show me an easier way, I would appreciate it a lot.

Good point! - That’s a copy-mistake.
Blue lines on white background is the swing output - but in that version I compared the right vectors.
Thank you very much - after hours of testing, I was blind for my own code.

And yes, I tested each arc segment, but only in the swing variant.
Didn’t know about guiNode and I’m sure, there’s a lot in jme3 I don’t know about and even can’t imagine.
Working with swing I don’t need to think twice and can focus on the “real” problem. Well, what ever might be a problem to me.
I can’t do the same with jme3 (yet?).

But new day, and cleared memory, may be I can find the other bugs …

@pspeed: So thanks a lot - with your help I’m half way done :slight_smile:

That was the result from yesterday:

and this is the result with the fixed vectors:

It seems like you already assume everything is in the x,y plane. So the angle between an x,y vector and the x axis is just the angle of the vector in the x,y plane. Indeed, all angleBetween() is going to be doing in that case is effectively an acos(x) of the passed vector.

Note that angleBetween() can only work between 0-180 degrees because it’s based on cosine which is the same when y is negative.

In the end, if the vectors are all in x,y space then the ‘angle between s and the x axis’ is just atan2(s.y, s.x).

And if the vectors are not all in x,y space then the rest of your code won’t really work anyway. For 2D problems, you’d need to project them into a 2D space… in which case it’s back to x,y vectors again.

No, not everything, just some arc-stuff.

I’m working on a frontend for linuxcnc, where I want to show the path of a milling tool. Most 3D-stuff is generated by cam and consists of straight lines, like this:

I read the output of a gcode-interpreter and transform that text into mesh primitives for jme3.

2D-stuff is 3D with projected arcs toward XY-plane.
Afaik the machines are able to perform these kinds of arcs only.
So if start-point and end-point of an arc have different Z-values, that means, the arc is a projection to XY-plane and Z-value transforms the arc into a spiral.

Thank you for your support!
I appreciated it a lot. I try to learn maths, but there’s not much readable docs out there.

Anyway, when my primitives work, than the real work just will begin. Camera does not behave like I want it to do and I guess, I have to dive deeper into jme3 to understand, where I have to change things.

Then I want to “link” the mesh-primitives to gcode-editor, which means, I have to change color of a single mesh.
Therefor the linenum properties.

That’s why I have the conditionals that looked weired to you :wink:

can I use vector from and to for atan2 or do I have to use the normalized vectors anyway?

Well, it seems like you want the angles based on center… so you’d still have to subtract center. But you need not normalize them for atan2().


I finally got it.
Had to start from scratch and do all testings again. There are too much differences between jme3 and swing …

Thanks for all your help!