Пример #1
0
    @Override
    public void renderTo(Target<?> target) {

      /* render fence */

      List<VectorXYZ> pointsWithEle = line.getElevationProfile().getPointsWithEle();

      List<VectorXYZ> vsFence = createVerticalTriangleStrip(pointsWithEle, 0, height);
      List<List<VectorXZ>> texCoordListsFence = wallTexCoordLists(vsFence, CHAIN_LINK_FENCE);

      target.drawTriangleStrip(CHAIN_LINK_FENCE, vsFence, texCoordListsFence);

      List<VectorXYZ> pointsWithEleBack = new ArrayList<VectorXYZ>(pointsWithEle);
      Collections.reverse(pointsWithEleBack);

      List<VectorXYZ> vsFenceBack = createVerticalTriangleStrip(pointsWithEleBack, 0, height);
      List<List<VectorXZ>> texCoordListsFenceBack =
          wallTexCoordLists(vsFenceBack, CHAIN_LINK_FENCE);

      target.drawTriangleStrip(CHAIN_LINK_FENCE, vsFenceBack, texCoordListsFenceBack);

      /* render poles */

      List<VectorXZ> polePositions =
          GeometryUtil.equallyDistributePointsAlong(
              2f, false, line.getStartNode().getPos(), line.getEndNode().getPos());

      for (VectorXZ polePosition : polePositions) {

        VectorXYZ base = polePosition.xyz(line.getElevationProfile().getEleAt(polePosition));
        target.drawColumn(CHAIN_LINK_FENCE_POST, null, base, height, width, width, false, true);
      }
    }
Пример #2
0
    @Override
    public void renderTo(Target<?> target) {

      double directionAngle = parseDirection(node.getTags(), PI);
      VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);

      Material boxMaterial = null;
      Material poleMaterial = null;
      Type type = null;

      // post boxes differ widely in appearance, hence we draw them only for known operators or
      // brands
      if (node.getTags()
          .containsAny(asList("operator", "brand"), asList("Deutsche Post AG", "Deutsche Post"))) {
        boxMaterial = POSTBOX_DEUTSCHEPOST;
        poleMaterial = STEEL;
        type = Type.WALL;
      } else if (node.getTags().contains("operator", "Royal Mail")) {
        boxMaterial = POSTBOX_ROYALMAIL;
        type = Type.PILLAR;
      } else {
        // no rendering, unknown operator or brand for post box //TODO log info
        return;
      }

      assert (type != Type.WALL || poleMaterial != null)
          : "post box of type wall requires a pole material";

      // default dimensions will differ depending on the post box type
      float height = 0f;
      float width = 0f;

      switch (type) {
        case WALL:
          height = parseHeight(node.getTags(), 0.8f);
          width = parseWidth(node.getTags(), 0.3f);

          target.drawBox(poleMaterial, getBase(), faceVector, height, 0.08, 0.08);

          target.drawBox(
              boxMaterial,
              getBase().add(faceVector.mult(width / 2 - 0.08 / 2)).addY(height),
              faceVector,
              width,
              width,
              width);
          break;
        case PILLAR:
          height = parseHeight(node.getTags(), 2f);
          width = parseWidth(node.getTags(), 0.5f);

          target.drawColumn(boxMaterial, null, getBase(), height - 0.1, width, width, false, false);
          target.drawColumn(
              boxMaterial, null, getBase().addY(height - 0.1), 0.1, width + 0.1, 0, true, true);
          break;
        default:
          assert false : "unknown post box type";
      }
    }
Пример #3
0
    @Override
    public void renderTo(Target<?> target) {

      double directionAngle = parseDirection(node.getTags(), PI);
      VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);

      Material machineMaterial = null;
      Material poleMaterial = STEEL;
      Type type = null;

      if (node.getTags().contains("vending", "bicycle_tube")
          && node.getTags().containsAny("operator", asList("Continental", "continental"))) {
        machineMaterial = new ImmutableMaterial(Lighting.FLAT, Color.ORANGE);
      } else if (node.getTags().contains("vending", "bicycle_tube")) {
        machineMaterial = new ImmutableMaterial(Lighting.FLAT, Color.BLUE);
      } else if (node.getTags().contains("vending", "cigarettes")) {
        machineMaterial = new ImmutableMaterial(Lighting.FLAT, new Color(0.8f, 0.73f, 0.5f));
      } else if (node.getTags().contains("vending", "condoms")) {
        machineMaterial = new ImmutableMaterial(Lighting.FLAT, new Color(0.39f, 0.15f, 0.11f));
      }

      // get Type of vending machine
      if (isInWall(node)) {
        type = Type.WALL;
      } else {
        type = Type.PILLAR;
      }

      // default dimensions will differ depending on the post box type
      float height = 0f;

      switch (type) {
        case WALL:
          break;
        case PILLAR:
          height = parseHeight(node.getTags(), 1.8f);

          target.drawBox(
              poleMaterial,
              getBase().add(new VectorXYZ(0, 0, -0.05).rotateY(faceVector.angle())),
              faceVector,
              height - 0.3,
              0.1,
              0.1);
          target.drawBox(
              machineMaterial,
              getBase().addY(height - 1).add(new VectorXYZ(0, 0, 0.1).rotateY(directionAngle)),
              faceVector,
              1,
              1,
              0.2);

          break;
        default:
          assert false : "unknown or unsupported Vending machine Type";
      }
    }
Пример #4
0
    @Override
    public void renderTo(Target<?> target) {
      if (!isInHighway(node)) {
        float height = parseHeight(node.getTags(), 3f);
        float signHeight = 0.7f;
        float signWidth = 0.4f;

        Material poleMaterial = STEEL;

        double directionAngle = parseDirection(node.getTags(), PI);

        VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);

        target.drawColumn(
            poleMaterial, null, getBase(), height - signHeight, 0.05, 0.05, true, true);
        /* draw sign */
        target.drawBox(
            BUS_STOP_SIGN,
            getBase().addY(height - signHeight),
            faceVector,
            signHeight,
            signWidth,
            0.02);
        /*  draw timetable */
        target.drawBox(
            poleMaterial,
            getBase().addY(1.2f).add(new VectorXYZ(0.055f, 0, 0f).rotateY(directionAngle)),
            faceVector,
            0.31,
            0.01,
            0.43);

        // TODO Add Shelter and bench
      }
    }
Пример #5
0
    @Override
    public void renderTo(Target<?> target) {

      double directionAngle = parseDirection(node.getTags(), PI);
      VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);
      target.drawColumn(WOOD, null, getBase(), 1.5, 0.05, 0.05, false, true);
      target.drawBox(WOOD, getBase().addY(1.2), faceVector, 0.4, 0.4, 0.1);
    }
Пример #6
0
    @Override
    public void renderTo(Target<?> target) {

      float width = parseWidth(node.getTags(), 4);
      float height = parseHeight(node.getTags(), 3.5f);
      float minHeight = height / 5;

      double directionAngle = parseDirection(node.getTags(), PI);

      VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);
      VectorXZ boardVector = faceVector.rightNormal();

      /* draw board */

      VectorXYZ[] vsPoster = {
        getBase().add(boardVector.mult(width / 2)).addY(height),
        getBase().add(boardVector.mult(width / 2)).addY(minHeight),
        getBase().add(boardVector.mult(-width / 2)).addY(height),
        getBase().add(boardVector.mult(-width / 2)).addY(minHeight)
      };

      List<VectorXYZ> vsListPoster = asList(vsPoster);

      target.drawTriangleStrip(
          ADVERTISING_POSTER,
          vsListPoster,
          texCoordLists(vsListPoster, ADVERTISING_POSTER, STRIP_FIT));

      VectorXYZ[] vsBoard = {vsPoster[2], vsPoster[3], vsPoster[0], vsPoster[1]};

      List<VectorXYZ> vsListBoard = asList(vsBoard);

      target.drawTriangleStrip(
          CONCRETE, vsListBoard, texCoordLists(vsListBoard, CONCRETE, STRIP_WALL));

      /* draw poles */

      VectorXZ[] poles = {
        node.getPos().add(boardVector.mult(-width / 4)),
        node.getPos().add(boardVector.mult(+width / 4))
      };

      for (VectorXZ pole : poles) {
        target.drawBox(CONCRETE, pole.xyz(getBase().y), faceVector, minHeight, 0.2, 0.1);
      }
    }
Пример #7
0
    @Override
    public void renderTo(Target<?> target) {

      /* render bars */

      List<VectorXYZ> vsLowFront =
          createVerticalTriangleStrip(
              line.getElevationProfile().getPointsWithEle(), 0.2f * height, 0.5f * height);
      List<VectorXYZ> vsLowBack =
          createVerticalTriangleStrip(
              line.getElevationProfile().getPointsWithEle(), 0.5f * height, 0.2f * height);

      target.drawTriangleStrip(material, vsLowFront, null);
      target.drawTriangleStrip(material, vsLowBack, null);

      List<VectorXYZ> vsHighFront =
          createVerticalTriangleStrip(
              line.getElevationProfile().getPointsWithEle(), 0.65f * height, 0.95f * height);
      List<VectorXYZ> vsHighBack =
          createVerticalTriangleStrip(
              line.getElevationProfile().getPointsWithEle(), 0.95f * height, 0.65f * height);

      target.drawTriangleStrip(material, vsHighFront, null);
      target.drawTriangleStrip(material, vsHighBack, null);

      /* render poles */

      List<VectorXZ> polePositions =
          GeometryUtil.equallyDistributePointsAlong(
              1f, false, line.getStartNode().getPos(), line.getEndNode().getPos());

      for (VectorXZ polePosition : polePositions) {

        VectorXYZ base = polePosition.xyz(line.getElevationProfile().getEleAt(polePosition));
        target.drawColumn(material, null, base, height, width, width, false, true);
      }
    }
Пример #8
0
    @Override
    public void renderTo(Target<?> target) {

      boolean summit =
          node.getTags().containsKey("summit:cross") || node.getTags().contains("natural", "peak");

      float height = parseHeight(node.getTags(), summit ? 4f : 2f);
      float width = parseHeight(node.getTags(), height * 2 / 3);

      double thickness = min(height, width) / 8;

      /* determine material and direction */

      Material material = null;

      // TODO parse color

      if (material == null) {
        material = Materials.getSurfaceMaterial(node.getTags().getValue("material"));
      }

      if (material == null) {
        material = Materials.getSurfaceMaterial(node.getTags().getValue("surface"), Materials.WOOD);
      }

      double directionAngle = parseDirection(node.getTags(), PI);
      VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);

      /* draw cross */

      target.drawBox(material, getBase(), faceVector, height, thickness, thickness);

      target.drawBox(
          material,
          getBase().addY(height - width / 2 - thickness / 2),
          faceVector,
          thickness,
          width,
          thickness);
    }
Пример #9
0
    @Override
    public void renderTo(Target<?> target) {

      double ele = getBase().y;

      double directionAngle = parseDirection(node.getTags(), PI);

      Material boxMaterial = POSTBOX_DEUTSCHEPOST;
      Material otherMaterial = STEEL;

      VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);
      VectorXZ rightVector = faceVector.rightNormal();

      // shape depends on type
      if (node.getTags().contains("type", "Rondell")) {

        float height = parseHeight(node.getTags(), 2.2f);
        float width = parseWidth(node.getTags(), 3f);
        float rondelWidth = width * 2 / 3;
        float boxWidth = width * 1 / 3;
        float roofOverhang = 0.3f;

        /* draw rondel */
        target.drawColumn(
            boxMaterial,
            null,
            getBase().add(rightVector.mult(-rondelWidth / 2)),
            height,
            rondelWidth / 2,
            rondelWidth / 2,
            false,
            true);
        /* draw box */
        target.drawBox(
            boxMaterial,
            getBase().add(rightVector.mult(boxWidth / 2)).add(faceVector.mult(-boxWidth / 2)),
            faceVector,
            height,
            boxWidth,
            boxWidth);
        /* draw roof */
        target.drawColumn(
            otherMaterial,
            null,
            getBase().addY(height),
            0.1,
            rondelWidth / 2 + roofOverhang / 2,
            rondelWidth / 2 + roofOverhang / 2,
            true,
            true);

      } else if (node.getTags().contains("type", "Paketbox")) {

        float height = parseHeight(node.getTags(), 1.5f);
        float width = parseHeight(node.getTags(), 1.0f);
        float depth = width;

        target.drawBox(boxMaterial, getBase(), faceVector, height, width * 2, depth * 2);

      } else { // type=Schrank or type=24/7 Station (they look roughly the same) or no type
               // (fallback)

        float height = parseHeight(node.getTags(), 2.2f);
        float width = parseWidth(node.getTags(), 3.5f);
        float depth = width / 3;
        float roofOverhang = 0.3f;

        /* draw box */
        target.drawBox(boxMaterial, getBase(), faceVector, height, width, depth);
        /*  draw small roof */
        target.drawBox(
            otherMaterial,
            node.getPos().add(faceVector.mult(roofOverhang)).xyz(ele + height),
            faceVector,
            0.1,
            width,
            depth + roofOverhang * 2);
      }
    }
Пример #10
0
    @Override
    public void renderTo(Target<?> target) {

      double directionAngle = parseDirection(node.getTags(), PI);
      VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);

      Material roofMaterial = null;
      Material poleMaterial = null;
      Type type = null;

      // get Type of Phone
      if (isInWall(node)) {
        type = Type.WALL;
      } else {
        type = Type.CELL;
      }

      // Phones differ widely in appearance, hence we draw them only for known operators or brands
      if (node.getTags()
          .containsAny(
              asList("operator", "brand"),
              asList("Deutsche Telekom AG", "Deutsche Telekom", "Telekom"))) {
        roofMaterial = TELEKOM_MANGENTA;
        poleMaterial = STEEL;
      } else if (node.getTags().containsAny(asList("operator", "brand"), "British Telecom")) {
        roofMaterial = POSTBOX_ROYALMAIL;
        poleMaterial = POSTBOX_ROYALMAIL;
      } else {
        // no rendering, unknown operator or brand //TODO log info
        return;
      }

      // default dimensions may differ depending on the phone type
      float height = 0f;
      float width = 0f;

      switch (type) {
        case WALL:
          break;
        case CELL:
          height = parseHeight(node.getTags(), 2.1f);
          width = parseWidth(node.getTags(), 0.8f);

          target.drawBox(GLASS, getBase(), faceVector, height - 0.2, width - 0.06, width - 0.06);
          target.drawBox(roofMaterial, getBase().addY(height - 0.2), faceVector, 0.2, width, width);
          target.drawBox(
              poleMaterial,
              getBase()
                  .add(
                      new VectorXYZ((width / 2 - 0.05), 0, (width / 2 - 0.05))
                          .rotateY(directionAngle)),
              faceVector,
              height - 0.2,
              0.1,
              0.1);
          target.drawBox(
              poleMaterial,
              getBase()
                  .add(
                      new VectorXYZ(-(width / 2 - 0.05), 0, (width / 2 - 0.05))
                          .rotateY(directionAngle)),
              faceVector,
              height - 0.2,
              0.1,
              0.1);
          target.drawBox(
              poleMaterial,
              getBase().add(new VectorXYZ(0, 0, -(width / 2 - 0.05)).rotateY(directionAngle)),
              faceVector,
              height - 0.2,
              width,
              0.1);

          break;
        default:
          assert false : "unknown or unsupported phone type";
      }
    }
Пример #11
0
    @Override
    public void renderTo(Target<?> target) {

      float height = parseHeight(node.getTags(), 0.5f);
      float width = parseWidth(node.getTags(), 1);
      float depth = width / 2f;

      /* determine material */

      Material material = null;

      // TODO parse color

      if (material == null) {
        material = Materials.getSurfaceMaterial(node.getTags().getValue("material"));
      }

      if (material == null) {
        material =
            Materials.getSurfaceMaterial(
                node.getTags().getValue("surface"), Materials.GRITBIN_DEFAULT);
      }

      double directionAngle = parseDirection(node.getTags(), PI);

      VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);
      VectorXZ boardVector = faceVector.rightNormal();

      /* draw box */
      target.drawBox(material, getBase(), faceVector, height, width, depth);

      /* draw lid */
      List<VectorXYZ> vs = new ArrayList<VectorXYZ>();
      vs.add(getBase().addY(height + 0.2));
      vs.add(
          getBase().add(boardVector.mult(width / 2)).add(faceVector.mult(depth / 2)).addY(height));
      vs.add(
          getBase().add(boardVector.mult(-width / 2)).add(faceVector.mult(depth / 2)).addY(height));
      vs.add(
          getBase()
              .add(boardVector.mult(-width / 2))
              .add(faceVector.mult(-depth / 2))
              .addY(height));
      vs.add(
          getBase().add(boardVector.mult(width / 2)).add(faceVector.mult(-depth / 2)).addY(height));
      vs.add(
          getBase().add(boardVector.mult(width / 2)).add(faceVector.mult(depth / 2)).addY(height));

      target.drawTriangleFan(material.brighter(), vs, null);
    }
Пример #12
0
    @Override
    public void renderTo(Target<?> target) {

      float distanceX = 3f;
      float distanceZ = 1.6f;

      int n = -1;
      int m = 0;

      if (node.getTags().containsAny(asList("recycling:glass_bottles", "recycling:glass"), "yes")) {
        n++;
      }
      if (node.getTags().contains("recycling:paper", "yes")) {
        n++;
      }
      if (node.getTags().contains("recycling:clothes", "yes")) {
        n++;
      }

      if (node.getTags().contains("recycling:paper", "yes")) {
        drawContainer(
            target,
            "paper",
            getBase()
                .add(
                    new VectorXYZ((distanceX * (-n / 2 + m)), 0f, (distanceZ / 2))
                        .rotateY(faceVector.angle())));
        drawContainer(
            target,
            "paper",
            getBase()
                .add(
                    new VectorXYZ((distanceX * (-n / 2 + m)), 0f, -(distanceZ / 2))
                        .rotateY(faceVector.angle())));
        m++;
      }
      if (node.getTags().containsAny(asList("recycling:glass_bottles", "recycling:glass"), "yes")) {
        drawContainer(
            target,
            "white_glass",
            getBase()
                .add(
                    new VectorXYZ((distanceX * (-n / 2 + m)), 0f, (distanceZ / 2))
                        .rotateY(faceVector.angle())));
        drawContainer(
            target,
            "coloured_glass",
            getBase()
                .add(
                    new VectorXYZ((distanceX * (-n / 2 + m)), 0f, -(distanceZ / 2))
                        .rotateY(faceVector.angle())));
        m++;
      }
      if (node.getTags().contains("recycling:clothes", "yes")) {
        drawContainer(
            target,
            "clothes",
            getBase()
                .add(new VectorXYZ((distanceX * (-n / 2 + m)), 0f, 0).rotateY(faceVector.angle())));
      }
    }
Пример #13
0
  private static final class RecyclingContainer extends NoOutlineNodeWorldObject
      implements RenderableToAllTargets {

    double directionAngle = parseDirection(node.getTags(), PI);
    VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);

    public RecyclingContainer(MapNode node) {
      super(node);
    }

    @Override
    public GroundState getGroundState() {
      return GroundState.ON;
    }

    @Override
    public void renderTo(Target<?> target) {

      float distanceX = 3f;
      float distanceZ = 1.6f;

      int n = -1;
      int m = 0;

      if (node.getTags().containsAny(asList("recycling:glass_bottles", "recycling:glass"), "yes")) {
        n++;
      }
      if (node.getTags().contains("recycling:paper", "yes")) {
        n++;
      }
      if (node.getTags().contains("recycling:clothes", "yes")) {
        n++;
      }

      if (node.getTags().contains("recycling:paper", "yes")) {
        drawContainer(
            target,
            "paper",
            getBase()
                .add(
                    new VectorXYZ((distanceX * (-n / 2 + m)), 0f, (distanceZ / 2))
                        .rotateY(faceVector.angle())));
        drawContainer(
            target,
            "paper",
            getBase()
                .add(
                    new VectorXYZ((distanceX * (-n / 2 + m)), 0f, -(distanceZ / 2))
                        .rotateY(faceVector.angle())));
        m++;
      }
      if (node.getTags().containsAny(asList("recycling:glass_bottles", "recycling:glass"), "yes")) {
        drawContainer(
            target,
            "white_glass",
            getBase()
                .add(
                    new VectorXYZ((distanceX * (-n / 2 + m)), 0f, (distanceZ / 2))
                        .rotateY(faceVector.angle())));
        drawContainer(
            target,
            "coloured_glass",
            getBase()
                .add(
                    new VectorXYZ((distanceX * (-n / 2 + m)), 0f, -(distanceZ / 2))
                        .rotateY(faceVector.angle())));
        m++;
      }
      if (node.getTags().contains("recycling:clothes", "yes")) {
        drawContainer(
            target,
            "clothes",
            getBase()
                .add(new VectorXYZ((distanceX * (-n / 2 + m)), 0f, 0).rotateY(faceVector.angle())));
      }
    }

    private void drawContainer(Target<?> target, String trash, VectorXYZ pos) {

      if ("clothes".equals(trash)) {
        target.drawBox(
            new ImmutableMaterial(Lighting.FLAT, new Color(0.82f, 0.784f, 0.75f)),
            pos,
            faceVector,
            2,
            1,
            1);
      } else { // "paper" || "white_glass" || "coloured_glass"
        float width = 1.5f;
        float height = 1.6f;

        Material colourFront = null;
        Material colourBack = null;

        if ("paper".equals(trash)) {
          colourFront = new ImmutableMaterial(Lighting.FLAT, Color.BLUE);
          colourBack = new ImmutableMaterial(Lighting.FLAT, Color.BLUE);
        } else if ("white_glass".equals(trash)) {
          colourFront = new ImmutableMaterial(Lighting.FLAT, Color.WHITE);
          colourBack = new ImmutableMaterial(Lighting.FLAT, Color.WHITE);
        } else { // "coloured_glass"
          colourFront = new ImmutableMaterial(Lighting.FLAT, new Color(0.18f, 0.32f, 0.14f));
          colourBack = new ImmutableMaterial(Lighting.FLAT, new Color(0.39f, 0.15f, 0.11f));
        }

        target.drawBox(STEEL, pos, faceVector, height, width, width);
        target.drawBox(
            colourFront,
            pos.add(
                new VectorXYZ((width / 2 - 0.10), 0.1f, (width / 2 - 0.1)).rotateY(directionAngle)),
            faceVector,
            height - 0.2,
            0.202,
            0.202);
        target.drawBox(
            colourBack,
            pos.add(
                new VectorXYZ(-(width / 2 - 0.10), 0.1f, (width / 2 - 0.1))
                    .rotateY(directionAngle)),
            faceVector,
            height - 0.2,
            0.202,
            0.202);
        target.drawBox(
            colourFront,
            pos.add(
                new VectorXYZ((width / 2 - 0.10), 0.1f, -(width / 2 - 0.1))
                    .rotateY(directionAngle)),
            faceVector,
            height - 0.2,
            0.202,
            0.202);
        target.drawBox(
            colourBack,
            pos.add(
                new VectorXYZ(-(width / 2 - 0.10), 0.1f, -(width / 2 - 0.1))
                    .rotateY(directionAngle)),
            faceVector,
            height - 0.2,
            0.202,
            0.202);
      }
    }
  }
Пример #14
0
    @Override
    public void renderTo(Target<?> target) {

      float width = parseWidth(node.getTags(), 1.5f);

      /* determine material */

      Material material = null;

      // TODO parse color

      if (material == null) {
        material = Materials.getSurfaceMaterial(node.getTags().getValue("material"));
      }

      if (material == null) {
        material = Materials.getSurfaceMaterial(node.getTags().getValue("surface"), Materials.WOOD);
      }

      /* calculate vectors and corners */

      double directionAngle = parseDirection(node.getTags(), PI);

      VectorXZ faceVector = VectorXZ.fromAngle(directionAngle);
      VectorXZ boardVector = faceVector.rightNormal();

      List<VectorXZ> cornerOffsets = new ArrayList<VectorXZ>(4);
      cornerOffsets.add(faceVector.mult(+0.25).add(boardVector.mult(+width / 2)));
      cornerOffsets.add(faceVector.mult(+0.25).add(boardVector.mult(-width / 2)));
      cornerOffsets.add(faceVector.mult(-0.25).add(boardVector.mult(+width / 2)));
      cornerOffsets.add(faceVector.mult(-0.25).add(boardVector.mult(-width / 2)));

      /* draw seat and backrest */

      target.drawBox(material, getBase().addY(0.5), faceVector, 0.05, width, 0.5);

      if (!node.getTags().contains("backrest", "no")) {

        target.drawBox(
            material,
            getBase().add(faceVector.mult(-0.23)).addY(0.5),
            faceVector,
            0.5,
            width,
            0.04);
      }

      /* draw poles */

      for (VectorXZ cornerOffset : cornerOffsets) {
        VectorXZ polePos = node.getPos().add(cornerOffset.mult(0.8));
        target.drawBox(material, polePos.xyz(getBase().y), faceVector, 0.5, 0.08, 0.08);
      }
    }