Package org.nlogo.gl.render

Source Code of org.nlogo.gl.render.Renderer

// (C) Uri Wilensky. https://github.com/NetLogo/NetLogo

package org.nlogo.gl.render;

import org.nlogo.api.Agent;
import org.nlogo.api.AgentException;
import org.nlogo.api.Drawing3D;
import org.nlogo.api.DrawingInterface;
import org.nlogo.api.Link;
import org.nlogo.api.Patch;
import org.nlogo.api.Patch3D;
import org.nlogo.api.Perspective;
import org.nlogo.api.PerspectiveJ;
import org.nlogo.api.Turtle;
import org.nlogo.api.ViewSettings;
import org.nlogo.api.World;
import org.nlogo.api.World3D;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;
import java.nio.DoubleBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

public class Renderer
    implements GLEventListener {
  final World world;
  final ViewSettings renderer;
  private final TurtleRenderer turtleRenderer;
  private final PatchRenderer patchRenderer;
  final WorldRenderer worldRenderer;
  final LinkRenderer linkRenderer;
  final ShapeRenderer shapeRenderer;
  final MouseState mouseState = new MouseState() ;
  final LightManager lightManager = new LightManager();

  int width;
  int height;
  private float ratio;

  public static final float WORLD_SCALE = 0.3f;
  public static final float PICK_THRESHOLD = 0.23f;

  /*
  * Collection of all opaque agents that will be rendered each frame. These
  * can be rendered in any order, as long as they are rendered before any
  * transparent objects.
  */
  private final List<Agent> opaqueAgents = new ArrayList<Agent>();

  /*
  * Collection of all partially transparent agents that will be rendered each
  * frame, ordered by their distance from the observer. (Rendering with transparency
  * requires that we sort these objects back to front).
  */
  private final PriorityQueue<Agent> transparentAgents ;

  final GLU glu = new GLU();
  ShapeManager shapeManager;
  PickListener pickListener;

  // we need to save the last matricies for mouse-x/ycor;
  // also used by context menu - jrn 5/20/05
  private DoubleBuffer modelMatrix;
  private DoubleBuffer projMatrix;
  private IntBuffer viewPort;

  public Renderer(World world,
                  ViewSettings graphicsSettings,
                  DrawingInterface drawing,
                  GLViewSettings glSettings) {
    this(world, graphicsSettings, drawing, glSettings, new ShapeRenderer(world));
  }

  public Renderer(World world,
                  ViewSettings graphicsSettings,
                  DrawingInterface drawing,
                  GLViewSettings glSettings,
                  ShapeRenderer shapeRenderer) {
    modelMatrix = DoubleBuffer.wrap(new double[16]);
    projMatrix = DoubleBuffer.wrap(new double[16]);
    viewPort = IntBuffer.wrap(new int[4]);

    this.world = world;
    transparentAgents = new PriorityQueue<Agent>(100, new Euclidean(world.observer()));
    renderer = graphicsSettings;
    this.shapeRenderer = shapeRenderer;
    turtleRenderer = createTurtleRenderer(world);
    linkRenderer = createLinkRenderer(world);
    patchRenderer = createPatchRenderer(world, drawing);
    worldRenderer = createWorldRenderer(world, patchRenderer, drawing,
        turtleRenderer, glSettings);
  }

  public Renderer(Renderer glrenderer) {
    world = glrenderer.world;
    transparentAgents = new PriorityQueue<Agent>(100, new Euclidean(world.observer()));
    renderer = glrenderer.renderer;
    worldRenderer = glrenderer.worldRenderer;
    turtleRenderer = glrenderer.turtleRenderer;
    patchRenderer = glrenderer.patchRenderer;
    linkRenderer = glrenderer.linkRenderer;
    shapeRenderer = glrenderer.shapeRenderer;
    shapeManager = glrenderer.shapeManager;
    worldRenderer.shapeManager_$eq(shapeManager);
    shapeRenderer.shapeManager_$eq(shapeManager);
  }

  public void update() {
    worldRenderer.shapeManager_$eq(shapeManager);
    shapeRenderer.shapeManager_$eq(shapeManager);
  }

  TurtleRenderer createTurtleRenderer(World world) {
    return new TurtleRenderer(world, shapeRenderer);
  }

  WorldRenderer createWorldRenderer(World world, PatchRenderer patchRenderer,
                                    DrawingInterface drawing,
                                    TurtleRenderer turtleRenderer,
                                    GLViewSettings settings) {
    return new WorldRenderer(world, patchRenderer, drawing, turtleRenderer, linkRenderer, settings);
  }

  PatchRenderer createPatchRenderer(World world,
                                    DrawingInterface drawing) {
    return new PatchRenderer(world, drawing, shapeRenderer);
  }

  LinkRenderer createLinkRenderer(World world) {
    return new LinkRenderer(world, shapeRenderer);
  }

  public ExportRenderer createExportRenderer() {
    return new ExportRenderer2D(this);
  }

  public void displayChanged(GLAutoDrawable gLDrawable,
                             boolean modeChanged,
                             boolean deviceChanged) {
  }

  public void init(GLAutoDrawable gLDrawable) {
    // Suggestion: If possible, enable any needed features and change any
    // graphics settings as close as possible to the place where they're
    // needed, instead of in here. Other parts of the application might
    // change these settings, which creates hard-to-debug issues.

    GL gl = gLDrawable.getGL();

    ClassLoader classLoader = getClass().getClassLoader();
    org.nlogo.util.SysInfo.getJOGLInfoString_$eq
        ("JOGL: " + JOGLLoader.getVersion(classLoader));

    org.nlogo.util.SysInfo.getGLInfoString_$eq(
        "OpenGL graphics: " + gl.glGetString(GL.GL_RENDERER) + "\n"
            + "OpenGL version: " + gl.glGetString(GL.GL_VERSION) + "\n"
            + "OpenGL vendor: " + gl.glGetString(GL.GL_VENDOR)
    );

    gl.glShadeModel(GL.GL_SMOOTH);                     // Enable Smooth Shading
    gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);          // Black Background
    gl.glClearDepth(1.0f);                            // Depth Buffer Setup
    gl.glEnable(GL.GL_DEPTH_TEST);              // Enables Depth Testing
    gl.glDepthFunc(GL.GL_LEQUAL);              // The Type Of Depth Testing To Do

    gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_FASTEST);

    // Lighting

    lightManager.init(gl);

    Light light1 = new DirectionalLight(new Direction(-1.0f, -0.3f, 0.4f));
    light1.ambient_$eq(new RGBA(0.25f, 0.25f, 0.25f, 1.0f));
    light1.diffuse_$eq(new RGBA(0.35f, 0.35f, 0.35f, 1.0f));
    light1.specular_$eq(new RGBA(0.0f, 0.0f, 0.0f, 0.0f));
    lightManager.addLight(light1);

    Light light2 = new DirectionalLight(new Direction(1.0f, 0.6f, -0.5f));
    light2.ambient_$eq(new RGBA(0.25f, 0.25f, 0.25f, 1.0f));
    light2.diffuse_$eq(new RGBA(0.35f, 0.35f, 0.35f, 1.0f));
    light2.specular_$eq(new RGBA(0.0f, 0.0f, 0.0f, 0.0f));
    lightManager.addLight(light2);

    // This is necessary for properly rendering scaled objects. Without this, small objects
    // may look too bright, and large objects will look flat.
    gl.glEnable(GL.GL_NORMALIZE);

    // Coloring

    gl.glColorMaterial(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE);
    gl.glEnable(GL.GL_COLOR_MATERIAL);

    // Remove back-face rendering

    gl.glCullFace(GL.GL_BACK);
    gl.glEnable(GL.GL_CULL_FACE);

    // Initialize ShapesManager

    shapeManager = new ShapeManager(gl, glu, world.turtleShapeList(), world.linkShapeList(),
                                    shapeManager == null ? null : shapeManager.customShapes,
                                    this instanceof Renderer3D);
    worldRenderer.init(gl, shapeManager);
    shapeRenderer.shapeManager_$eq(shapeManager);

    // Check for stencil support
    int StencilBits[] = new int[1];
    gl.glGetIntegerv(GL.GL_STENCIL_BITS, IntBuffer.wrap(StencilBits));
    shapeRenderer.stencilSupport_$eq(StencilBits[0] > 0);
  }

  public void reshape(GLAutoDrawable gLDrawable, int x, int y, int width, int height) {
    GL gl = gLDrawable.getGL();
    this.width = width;
    this.height = (height > 0) ? height : 1;
    ratio = (float) this.width / (float) this.height;

    mainViewport(gl);
  }

  private void mainViewport(GL gl) {
    gl.glViewport(0, 0, width, height);
    gl.glMatrixMode(GL.GL_PROJECTION);
    gl.glLoadIdentity();

    // make the z-clip proportional to the max screen edge so the world doesn't
    // just disappear before you can see the whole thing ev 1/15/05
    double zClip = Math.max
        (world.worldWidth(),
            world.worldHeight()) * 4;

    glu.gluPerspective(45.0f, ratio, 0.1, zClip);
    gl.glMatrixMode(GL.GL_MODELVIEW);
    gl.glLoadIdentity();
  }

  public void display(GLAutoDrawable gLDrawable) {
    final GL gl = gLDrawable.getGL();

    gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
    shapeManager.checkQueue(gl, glu);

    render(gl);
    gl.glFlush();
  }

  void renderClippingPlane(GL gl, double[] eqn, int plane) {
    java.nio.DoubleBuffer eqnBuffer = java.nio.DoubleBuffer.wrap(eqn);
    gl.glClipPlane(plane, eqnBuffer);
    gl.glEnable(plane);
  }

  void setClippingPlanes(GL gl) {
    // we get 6 clipping planes guaranteed (0-5).
    // there might be more we can check GL_MAX_CLIPPING_PLANES
    // ev 4/20/06

    // offset the planes a little so they don't cut off the sides of the patches

    renderClippingPlane
        (gl, new double[]
            {1.0f, 0.0, 0.0f, (float) (-(world.minPxcor() - 0.5) * WORLD_SCALE) + 0.01f},
            GL.GL_CLIP_PLANE0);
    renderClippingPlane
        (gl, new double[]
            {-1.0, 0.0, 0.0, (float) ((world.maxPxcor() + 0.5) * WORLD_SCALE) + 0.01f},
            GL.GL_CLIP_PLANE1);
    renderClippingPlane
        (gl, new double[]
            {0.0, -1.0, 0.0, (float) ((world.maxPycor() + 0.5) * WORLD_SCALE) + 0.01f},
            GL.GL_CLIP_PLANE2);
    renderClippingPlane
        (gl, new double[]
            {0.0, 1.0, 0.0, (float) (-(world.minPycor() - 0.5) * WORLD_SCALE) + 0.01f},
            GL.GL_CLIP_PLANE3);
  }

  void disableClippingPlanes(GL gl) {
    gl.glDisable(GL.GL_CLIP_PLANE0);
    gl.glDisable(GL.GL_CLIP_PLANE1);
    gl.glDisable(GL.GL_CLIP_PLANE2);
    gl.glDisable(GL.GL_CLIP_PLANE3);
    gl.glPopMatrix();
  }

  /**
   * Returns true if this agent should be rendered (i.e., it is not hidden, nor fully transparent,
   * and the observer is not riding this agent). Note: if this agent has a label, the agent will
   * be regarded as "visible".
   */
  boolean agentIsVisible(Agent agent) {
    if (agent instanceof Turtle) {
      Turtle turtle = (Turtle) agent;

      boolean riding_agent = (world.observer().perspective() == PerspectiveJ.RIDE())
          && (world.observer().targetAgent() == turtle);

      return !riding_agent && !turtle.hidden()
        && (turtle.alpha() > 0.0 || turtle.hasLabel());
    } else if (agent instanceof Link) {
      Link link = (Link) agent;
      return !link.hidden() && (link.alpha() > 0.0 || link.hasLabel());
    } else if (agent instanceof Patch3D) {
      // Patch3D supports the alpha variable, so check Patch3D
      // before checking the regular Patch.

      Patch3D patch = (Patch3D) agent;
      return patch.alpha() > 0.0 || patch.hasLabel();
    } else if (agent instanceof Patch) {
      // We will assume all patches are visible. However, perhaps
      // we should only return true for non-black patches.

      return true;
    } else {
      throw new IllegalStateException("Agent must be an instance of Turtle, Patch, or Link.");
    }
  }

  void render(GL gl) {
    // Notes:
    //
    // This render function only gets called in NetLogo 3D, or in the "3D View" of
    // NetLogo 2D.  (Rendering in NetLogo 2D, without the 3D view, happens in the
    // org.nlogo.render.Renderer class, in the paint() method.)
    //
    // In NetLogo 3D, all turtles are instances of Turtle3D, all patches are instances
    // of Patch3D, and all links are instances of Link3D. Also, world is an instance of
    // World3D, and world.observer() should give you an instance of Observer3D. Also,
    // beware of the fact that the Renderer3D class overrides some methods (e.g.,
    // renderWorld) when we're in NetLogo 3D - the overriden versions of these methods
    // will get called. However, since render() has not been overriden in Renderer3D,
    // this method still gets called.
    //
    // In the 3D view in 2D, all turtles are instances of Turtle, all patches are
    // instances of Patch, and... you get the idea. Note, however, that the non-3D
    // version of Observer still has a zcor attribute since the camera is free to
    // move about in three dimensions in the 3D view in 2D. Also, none of the
    // overrides in the Renderer3D class have any effect when we're in the 3D view
    // in 2D, so the methods that get called are the ones in this class (e.g., when
    // we call renderWorld() below, the method that gets called is
    // Renderer.renderWorld() instead of Renderer3D.renderWorld() when we're in
    // the 3D view in 2D.)
    //
    // In the code below, when we are checking if something is an instance of Turtle3D,
    // Patch3D, or Link3D, this signifies something must only be performed in
    // NetLogo 3D, and not in the 3D view in 2D. We try to minimize such cases since
    // we want this function to remain as general as possible.
    //
    // On the other hand, if we are checking if something is an instance of Turtle,
    // Patch, or Link (without the 3D suffix), this signifies something that is
    // performed in both NetLogo 3D, and in the 3D view in 2D because Turtle3D,
    // Patch3D, and Link3D extend Turtle, Patch, and Link (and thus, all Turtle3D
    // instances are also Turtle instances, and so on).
    //
    // Here are some subtleties: In NetLogo 3D, patches get rendered when you call
    // worldRenderer.renderPatchShapes(), via the following pathway:
    //     Renderer.render() (this method) calls:
    //     WorldRenderer3D.renderPatchShapes(), which calls:
    //     PatchRenderer3D.renderPatches()           <-- Patches get rendered here
    //
    // However, when you're in the 3D view in 2D, patches get rendered when you
    // call renderWorld(), via the following pathway:
    //     Renderer.render() (this method) calls:
    //     Renderer.renderWorld(), which calls:
    //     WorldRenderer.renderWorld(), which calls:
    //     PatchRenderer.renderPatches()             <-- Patches get rendered here
    //
    // This might lead you to believe that patches are getting rendered twice,
    // because we're calling renderWorld(), and then later we're calling
    // worldRenderer.renderPatchShapes(). However, this is not the case because:
    //
    //   1. WorldRenderer.renderPatchShapes(), unlike
    //      WorldRenderer3D.renderPatchShapes(), does not call
    //      patchRenderer.renderPatches(), so patches DO NOT get rendered when
    //      we call worldRenderer.renderPatchShapes() when we're in the 3D
    //      view in 2D.
    //   2. WorldRenderer3D.renderWorld(), unlike WorldRenderer.renderWorld(),
    //      does not call patchRenderer.renderPatches(), so patches do NOT
    //      get rendered when we call renderWorld() when we're in NetLogo 3D.
    //
    // This is a bit confusing since functions that have the same name are
    // doing drastically different things depending on whether we're in true
    // 3D or if we're in the 3D view in 2D. I might get around to fixing this
    // later, but I'm afraid that might be a big task, so for now I'm going
    // to leave this comment in an attempt to clarify what's going on.
    //

    gl.glLoadIdentity();

    synchronized (world) {
      Perspective perspective = world.observer().perspective();
      Agent targetAgent = world.observer().targetAgent();

      worldRenderer.observePerspective(gl);

      worldRenderer.renderCrossHairs(gl);

      lightManager.applyLighting();

      // Uncomment the code below to show the positions and directions of all the lights
      // in the world (only works in NetLogo 3D, not the 3D view in 2D).
      /*
      if (world instanceof World3D)
      {
        double observerDistance = Math.sqrt(world.observer().oxcor() * world.observer().oxcor()
              + world.observer().oycor() * world.observer().oycor()
              + world.observer().ozcor() * world.observer().ozcor());
        lightManager.showLights(glu, (World3D)world, WORLD_SCALE, observerDistance, shapeRenderer);
      }
      */

      renderWorld(gl, world);

      setClippingPlanes(gl);

      // Calculate the line scale to use for rendering outlined turtles and links,
      // as well as link stamps.
      double lineScale = calculateLineScale();

      boolean sortingNeeded = world.mayHavePartiallyTransparentObjects();

      if (!sortingNeeded) {
        worldRenderer.renderPatchShapes
            (gl, outlineAgent, renderer.fontSize(), renderer.patchSize());

        linkRenderer.renderLinks(gl, glu, renderer.fontSize(), renderer.patchSize(), outlineAgent);

        turtleRenderer.renderTurtles(gl, glu, renderer.fontSize(), renderer.patchSize(), outlineAgent);

        // Render stamps and trails
        //
        // Note: in the 3D view in 2D, stamps and trails appear as bitmaps
        // on the drawing layer, which already got rendered in renderWorld().
        //
        // In the true 3D version, we skipped the call to renderDrawing()
        // because there's the possibility that stamps and trails might be
        // transparent, and so they need to get sorted along with the rest
        // of the objects in the scene.
        //
        // However, since we've determined that no sorting is needed in this
        // block, we can safely render the drawing layer now.

        if (world instanceof World3D) {
          worldRenderer.renderDrawing(gl);
        }
      } else {
        // Make sure everything needed for transparency is enabled.
        gl.glEnable(GL.GL_BLEND);
        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);

        opaqueAgents.clear();
        transparentAgents.clear();

        for (Agent agent : world.turtles().agents()) {
          if (agentIsVisible(agent)) {
            if (agent.isPartiallyTransparent()) {
              transparentAgents.add(agent);
            } else {
              opaqueAgents.add(agent);
            }
          }
        }

        for (Agent agent : world.patches().agents()) {
          if (agentIsVisible(agent)) {
            if (agent.isPartiallyTransparent()) {
              transparentAgents.add(agent);
            } else {
              opaqueAgents.add(agent);
            }
          }
        }

        for (Agent agent : world.links().agents()) {
          if (agentIsVisible(agent)) {
            if (agent.isPartiallyTransparent()) {
              transparentAgents.add(agent);
            } else {
              opaqueAgents.add(agent);
            }
          }
        }

        // Now add stamps and trails.
        //
        // Note: in the 3D view in 2D, stamps and trails appear as bitmaps
        // on the drawing layer, which already got rendered in renderWorld().
        // We only have to worry about the true 3D version.

        if (world instanceof World3D) {
          // Link stamps
          for (org.nlogo.api.Link stamp : ((Drawing3D) world.getDrawing()).linkStamps()) {
            if (agentIsVisible(stamp)) {
              if (stamp.isPartiallyTransparent()) {
                transparentAgents.add(stamp);
              } else {
                opaqueAgents.add(stamp);
              }
            }
          }

          // Turtle stamps
          for (org.nlogo.api.Turtle stamp : ((Drawing3D) world.getDrawing()).turtleStamps()) {
            if (agentIsVisible(stamp)) {
              if (stamp.isPartiallyTransparent()) {
                transparentAgents.add(stamp);
              } else {
                opaqueAgents.add(stamp);
              }
            }
          }

          // Turtle trails
          ((WorldRenderer3D) worldRenderer).renderTrails(gl);

          // Note: We are currently not supporting transparent turtle trails
          // (trails are left by the turtles when you use the pen-down command).
          // The difficulty with these is that they are not instances of Agent,
          // so we would have to reimplement our code to use Objects instead,
          // or create some sort of Renderable interface (but this would create
          // more problems because we would need to make Agent implement
          // this Renderable interface, so either we have to create a bad dependency
          // from the org.nlogo.api package to the render package, or else we
          // have to put the Renderable interface in the org.nlogo.api package).
          // There may also be a significant performance issue since the trails
          // consist of a large number of small line segments, all of which would
          // need to be sorted each frame for transparency to work properly.
          //
          // For reference:
          // for( org.nlogo.api.DrawingLine3D line : ( (Drawing3D) world.getDrawing() ).lines() )
          // {
          //     // add trail segment to the list here
          // }
        }

        //    System.out.printf( "\t\t%d opaque objects.\n" , opaqueAgents.size() ) ;
        //    System.out.printf( "\t\t%d transparent objects (sorted).\n" , transparentAgents.size() ) ;

        // Render the opaque objects first
        for (Agent agent : opaqueAgents) {
          renderAgent(gl, agent, lineScale);
        }

        // Now render the transparent agents in sorted order (back to front)
        while (!transparentAgents.isEmpty()) {
          Agent agent = transparentAgents.remove();
          renderAgent(gl, agent, lineScale);
        }

        gl.glDisable(GL.GL_BLEND);
      }


      if (outlineAgent instanceof org.nlogo.api.Patch) {
        patchRenderer.renderOutline(gl, (org.nlogo.api.Patch) outlineAgent);
      }

      disableClippingPlanes(gl);

      //
      // Highlight agents that are being watched or followed.
      //
      if (targetAgent instanceof Turtle) {
        turtleRenderer.renderHighlight(gl, (Turtle) targetAgent);
      } else if (targetAgent instanceof org.nlogo.api.Patch) {
        patchRenderer.renderHightlight(gl, (Patch) targetAgent);
      }

      if (mouseState.on() || mouseState.pickRequest()) {
        storeMatricies(gl);

        if (mouseState.pickRequest()) {
          performPick();
        }

        if ((perspective != PerspectiveJ.OBSERVE())
            && mouseState.inside() && (mouseState.point() != null)) {
          updateMouseCors();
        }
      }
    }
  }

  void renderAgent(GL gl, Agent agent, Double lineScale) {
    if (agent instanceof Turtle) {
      turtleRenderer.renderWrappedTurtle(gl, (Turtle) agent, renderer.fontSize(),
          renderer.patchSize(), (agent == outlineAgent), lineScale);
    } else if (agent instanceof org.nlogo.api.Patch3D) {
      worldRenderer.renderIndividualPatchShapes(gl, (org.nlogo.api.Patch3D) agent, outlineAgent,
          renderer.fontSize(), renderer.patchSize());
    } else if (agent instanceof org.nlogo.api.Link) {
      linkRenderer.renderIndividualLinks(gl, glu, (Link) agent,
          renderer.fontSize(), renderer.patchSize(), outlineAgent);
    }

    // Note: If we are in the 3D view in 2D, patches have already been
    // rendered back when we called renderWorld() above. Thus, we do not
    // have to check if( agent instanceof Patch ).
  }

  void renderWorld(GL gl, World world) {
    // This version of renderWorld gets called when we're in the 3D view in 2D.
    // For NetLogo 3D, look in Renderer3D.renderWorld().
    //
    // In the 3D view in 2D, worldRenderer.renderWorld() will cause the patches
    // to get rendered. worldRenderer.renderDrawing() will cause any image
    // that was imported using the import-drawing command to get rendered, along
    // with stamps and trails.
    //
    // Patches and images are guaranteed to be opaque in the 3D view in 2D, so
    // there's no problem with rendering these here (opaque objects must be
    // rendered first, and any transparent objects afterward).
    //
    // However, we might have some problems with stamps/trails in the 3D view
    // in 2D... EDIT: Actually, when you stamp things in the 3D view in 2D, their
    // stamps become the "2D" versions of the shapes. Kind of a quirky feature,
    // but it does mean that rendering stamps/trails in here should not be a problem,
    // even if they are transparent, because they are guaranteed to never
    // obscure any transparent 3D turtles/links.

    gl.glPushMatrix();
    translateWorld(gl, world);

    worldRenderer.renderWorld(gl, renderer.fontSize(), renderer.patchSize());

    worldRenderer.renderDrawing(gl);

    gl.glPopMatrix();
  }

  /*
  * Calculates the line scale to use for rendering outlined turtles and links.
  */
  double calculateLineScale() {
    double distance;

    Perspective p = world.observer().perspective();

    if (p == PerspectiveJ.FOLLOW() || p == PerspectiveJ.RIDE()) {
      distance = world.observer().followDistance();
    } else {
      distance = world.observer().dist();
    }

    if (distance != 0) {
      double width = world.worldWidth();
      double height = world.worldHeight();

      double maxDimension = Math.max(width, height);

      if (world instanceof World3D) {
        double depth = ((World3D) world).worldDepth();
        maxDimension = Math.max(maxDimension, depth);
      }

      return 1.5 * maxDimension / distance;
    } else {
      return 0.0;
    }
  }

  private Agent outlineAgent = null;

  public void outlineAgent(Agent agent) {
    outlineAgent = agent;
  }

  public void cleanUp() {
    worldRenderer.cleanUp();
  }

  /// Picking

  // queue a request for object selection/pick
  public void queuePick(java.awt.Point mousePt, PickListener pickListener) {
    mouseState.pickRequest_$eq(true);
    mouseState.point_$eq(mousePt);
    this.pickListener = pickListener;
  }

  // pick/select objects for context menu
  void performPick() {
    List<Agent> agents = new ArrayList<Agent>(5);
    double[][] ray = generatePickRay(mouseState.point().getX(), (height - mouseState.point().getY()));
    pickPatches(agents, ray);
    pickTurtles(agents, ray);
    pickLinks(agents, ray);
    pickListener.pick(mouseState.point(), agents);
    mouseState.pickRequest_$eq(false);
  }

  // saves current transformation matricies and viewport
  private void storeMatricies(GL gl) {
    gl.glGetDoublev(GL.GL_MODELVIEW_MATRIX, modelMatrix);
    gl.glGetDoublev(GL.GL_PROJECTION_MATRIX, projMatrix);
    gl.glGetIntegerv(GL.GL_VIEWPORT, viewPort);
  }

  // generates a pick/selection ray from mouse coordinates
  double[][] generatePickRay(double mouseX, double mouseY) {
    double[][] ray = new double[2][3];

    // create pick-ray
    glu.gluUnProject(mouseX, mouseY, 0.0d, modelMatrix, projMatrix,
        viewPort, DoubleBuffer.wrap(ray[0]));
    glu.gluUnProject(mouseX, mouseY, 1.0d, modelMatrix, projMatrix,
        viewPort, DoubleBuffer.wrap(ray[1]));

    return ray;
  }

  double wrapX(double x) {
    try {
      return world.wrapX(x);
    } catch (AgentException e) {
      return x;
    }
  }

  double wrapY(double y) {
    try {
      return world.wrapY(y);
    } catch (AgentException e) {
      return y;
    }
  }

  // detects which patch a pick-ray intersects with
  void pickPatches(List<Agent> agents, double[][] ray) {
    // detect any patches in the pick-ray ( ( Renderer.WORLD_SCALE / 2 )
    // is the offset of the patches plane in the z-axis - jrn)
    double scale = (1.0 / WORLD_SCALE);
    double t = ((ray[0][2] + (Renderer.WORLD_SCALE / 2.0)) /
        (ray[0][2] - ray[1][2]));
    double xi = scale * (ray[0][0] + (ray[1][0] - ray[0][0]) * t);
    double yi = scale * (ray[0][1] + (ray[1][1] - ray[0][1]) * t);

    if ((xi < world.maxPxcor() + 0.5) && (xi >= world.minPxcor() - 0.5) &&
        (yi < world.maxPycor() + 0.5) && (yi >= world.minPycor() - 0.5)) {
      xi = wrapX(xi + world.followOffsetX());
      yi = wrapY(yi + world.followOffsetY());

      mouseState.xcor_$eq(xi);
      mouseState.ycor_$eq(yi);

      if (agents != null) {
        try {
          Patch patch = world.getPatchAt(xi, yi);
          agents.add(patch);
        } catch (AgentException e) {
          org.nlogo.util.Exceptions.ignore(e);
        }
      }
    }
  }

  // detects which turtle(s) a pick-ray intersects with
  void pickTurtles(List<Agent> agents, double[][] ray) {
    // detect any turtles in the pick-ray
    for (Agent a : world.turtles().agents()) {
      Turtle turtle = (Turtle) a;
      if (!turtle.hidden()) {
        double size = turtle.size();

        double[] coord = getTurtleCoords(turtle, size);

        // determining distance to pick ray
        double ux = ray[1][0] - ray[0][0];
        double uy = ray[1][1] - ray[0][1];
        double uz = ray[1][2] - ray[0][2];
        double vx = ray[0][0] - coord[0];
        double vy = ray[0][1] - coord[1];
        double vz = ray[0][2] - coord[2];

        double crossX = (uy * vz - uz * vy);
        double crossY = (uz * vx - ux * vz);
        double crossZ = (ux * vy - uy * vx);
        double lengt = Math.sqrt((crossX * crossX) +
            (crossY * crossY) + (crossZ * crossZ));
        double lengb = Math.sqrt((ux * ux) + (uy * uy) +
            (uz * uz));

        double dist = (lengt / lengb);

        if (dist <= size * PICK_THRESHOLD) {
          agents.add(turtle);
        }
      }
    }
  }

  // detects which turtle(s) a pick-ray intersects with
  void pickLinks(List<Agent> agents, double[][] ray) {
    // detect any turtles in the pick-ray
    for (Agent a : world.links().agents()) {
      Link link = (Link) a;
      if (!link.hidden()) {
        double[] end1 = getTurtleCoords(link.end1(), 0);
        double[] end2 = getTurtleCoords(link.end2(), 0);

        double dist = Picker.distanceFromRayToSegment(ray, end1, end2);

        if (dist <= PICK_THRESHOLD) {
          agents.add(link);
        }
      }
    }
  }

  double[] getTurtleCoords(Turtle turtle, double height) {
    return new double[]{world.wrappedObserverX(turtle.xcor()) * Renderer.WORLD_SCALE,
        world.wrappedObserverY(turtle.ycor()) * Renderer.WORLD_SCALE, 0};
  }


  /// Mouse interaction

  public void setMouseMode(boolean mode) {
    mouseState.on_$eq(mode);
  }

  public double mouseXCor() {
    return mouseState.xcor();
  }

  public double mouseYCor() {
    return mouseState.ycor();
  }

  public boolean mouseDown() {
    return mouseState.down();
  }

  public void mouseDown(boolean mouseDown) {
    mouseState.down_$eq(mouseDown);
  }

  public boolean mouseInside() {
    return mouseState.inside();
  }

  public void resetMouseCors() {
    mouseState.xcor_$eq(0);
    mouseState.ycor_$eq(0);
  }

  public void setMouseCors(java.awt.Point mousePt) {
    double[][] ray = generatePickRay(mousePt.getX(), (height - mousePt.getY()));
    pickPatches(null, ray);
    mouseState.point_$eq(mousePt);
  }

  public void updateMouseCors() {
    double[][] ray = generatePickRay(mouseState.point().getX(), (height - mouseState.point().getY()));
    pickPatches(null, ray);
  }

  public void mouseInside(double mx, double my) {
    double[][] ray = generatePickRay(mx, height - my);
    double scale = (1 / WORLD_SCALE);
    double t = ((ray[0][2] + (Renderer.WORLD_SCALE / 2)) /
        (ray[0][2] - ray[1][2]));
    double xi = scale * (ray[0][0] + (ray[1][0] - ray[0][0]) * t);
    double yi = scale * (ray[0][1] + (ray[1][1] - ray[0][1]) * t);

    mouseState.inside_$eq(
      (xi < world.maxPxcor() + 0.5) && (xi >= world.minPxcor() - 0.5) &&
      (yi < world.maxPycor() + 0.5) && (yi >= world.minPycor() - 0.5));
  }

  /// Crosshairs

  public void showCrossHairs(boolean visible) {
    worldRenderer.showCrossHairs_$eq(visible);
  }

  /// Shapes

  public void addCustomShapes(String filename)
      throws java.io.IOException,
      org.nlogo.shape.InvalidShapeDescriptionException {
    shapeManager.addCustomShapes(filename);
  }

  public void invalidateTurtleShape(String shape) {
    shapeManager.invalidateTurtleShape(shape);
  }

  public void invalidateLinkShape(String shape) {
    shapeManager.invalidateLinkShape(shape);
  }

  public void translateWorld(GL gl, World world) {
    gl.glTranslated
        ((world.maxPxcor() + world.minPxcor()) / 2.0 * Renderer.WORLD_SCALE,
            (world.maxPycor() + world.minPycor()) / 2.0 * Renderer.WORLD_SCALE,
            0.0);
  }
}
TOP

Related Classes of org.nlogo.gl.render.Renderer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.