This package has been deprecated

Author message:

This package as been renamed. See @bimdata/2d-engine

@bimdata/viewer2d
TypeScript icon, indicating that this package has built-in type declarations

0.5.2 • Public • Published

Viewer2d

An easy to use 2D viewer to load model with objects that you can select, highlight, hide, update geometries...

CI

Quick start

Simple example

This simple example shows how to load the viewer with two simple objects and interact (select, highlight) with them.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>BIMData Viewer2D</title>
    <style>
      * {
        margin: 0px;
        padding: 0px;
      }
    </style>
  </head>

  <body style="background-color: gainsboro;">
    <canvas id="canvas2d" style="width: 100vw; height: 100vh;"></canvas>

    <script type="module">
      import makeViewer from "https://www.unpkg.com/@bimdata/viewer2d@0.2.3/dist/viewer2D.esm.js";

      const canvas = document.getElementById("canvas2d");

      const viewer = makeViewer({ canvas });

      // Select object on click, fitview on right-click.
      viewer.picker.on("pick", ({ object, rightClick }) => {
        if (rightClick) {
          viewer.camera.fitView(object.geometry.bounds);
        } else {
          object.selected = !object.selected;
        }
      });

      // Highlight object on hover.
      viewer.picker.on("hover", ({ object }) => {
        object.highlighted = true;
      });
      viewer.picker.on("hover-out", ({ object }) => {
        object.highlighted = false;
      });
      viewer.canvas.addEventListener("mouseleave", () => {
        const highlightedObjectIds = viewer.scene.highlightedObjects.map(
          o => o.id
        );
        viewer.scene.unhighlightObjects(highlightedObjectIds);
      });

      // Add a model with two simple objects. A line and a circle.
      const model = viewer.scene.addModel({
        objects: [
          {
            lineWidth: 2,
            lineOpacity: 0.8,
            lineColor: 0x0000ff,
            geometry: [
              [0, 0, 50, 50],
              [0, 50, 50, 0],
            ],
          },
          {
            textureOpacity: 0.5,
            texture: "solid",
            textureTint: 0xff00ff,
            geometry: [
              {
                type: "arc",
                x: 25,
                y: 25,
                radius: 20,
              },
            ],
          },
        ],
      });

      // Fit view the model once loaded.
      viewer.camera.fitView(model.bounds);
    </script>
  </body>
</html>

The result:

simple example

More complex example

Of course you can do more that just drawing circles and lines. In this example, a complete building storey is drawn and doors and space names are displayed using addons. These addons are simple javascript code that use the viewer2D API.

complex example

To see more about these addons:

Features

The viewer2D provide tools to customise and interact with models/objects you load.

  • Draw object geometry with line and texture cutsomization.
  • Show, hide, select and highlight objects.
  • The camera can scale, rotate, pan and fitview objects/models.
  • ...

See the API reference to know all available features.

Install

npm

npm i @bimdata/viewer2d
import makeViewer from "@bimdata/viewer2d";

script tag

<script src="https://www.unpkg.com/@bimdata/viewer2d@0.2.3"></script>

Add makeViewer available on the window object.

Or on script type module:

<script type="module">
  import makeViewer from "https://www.unpkg.com/@bimdata/viewer2d@0.2.3/dist/viewer2D.esm.js";

  // have fun
</script>

API

Init factory

Viewer2D is created using a factory you get as default export from the package:

import makeViewer from "@bimdata/viewer2d"; // webpack / rollup

const canvas = document.getElementById("canvas");
const viewer = makeViewer({ canvas });

canvas is mandatory and another properties can be passed to customized the viewer2d behavior:

Property Type Description
canvas HTMLCanvasElement Required.
autoStart boolean Default = true. If false, you must call viewer.ticker.start() to start rendering.

The factory returns the viewer with the following interface:

interface Viewer extends {
  canvas: HTMLCanvasElement;
  scene: Scene;
  ui: UI;
  camera: Camera;
  destroy(): void;
  renderer: Renderer;
  settings: Settings;
  ticker: Ticker;
  picker: Picker;
  utils: {
    vector2D: Vector2DUtils;
  };
}

Scene

The viewer scene is where model and objects lives. You can add/remove models and objects and getters and setters allow read/write objects in batch.

It has the following interface:

interface Scene extends Readonly<EventHandler> {
  readonly viewer: Viewer;
  readonly textureManager: TextureManager;
  readonly styles: { default: Style; selected: Style; highlight: Style };

  // Models
  readonly models: Model[];
  readonly modelsMap: Map<number, Model>;
  addModel(modelData: ModelData): Model;
  removeModel(id: number): boolean;

  // Objects
  readonly objects: SceneObject[];
  readonly objectsMap: Map<number, SceneObject>;
  addObject(objectData: SceneObjectData, model?: Model): SceneObject;
  removeObject(objectId: number): boolean;

  // Objects setters
  showObjects(ids: number[]): void;
  hideObjects(ids: number[]): void;
  selectObjects(ids: number[]): void;
  deselectObjects(ids: number[]): void;
  highlightObjects(ids: number[]): void;
  unhighlightObjects(ids: number[]): void;
  setObjectsPickable(ids: number[]): void;
  setObjectsUnpickable(ids: number[]): void;

  // Object getters
  readonly shownObjects: SceneObject[];
  readonly hiddenObjects: SceneObject[];
  readonly selectedObjects: SceneObject[];
  readonly unselectedObjects: SceneObject[];
  readonly highlightedObjects: SceneObject[];
  readonly unhighlightedObjects: SceneObject[];
  readonly pickableObjects: SceneObject[];
  readonly unpickableObjects: SceneObject[];
}

It also contains styles and the texture manager.

Styles

Styles define how objects are drawn by default when they are visible, selected and highlighted. It can be customized by changing the default styles (WARNING: updating styles.default won't affect already loaded objects) or loading objects with non default style.

A style has the following interface:

interface Style {
  // TEXTURE
  texture?: string;
  textureTint?: number;
  textureOpacity?: number;
  // LINE
  lineWidth?: number;
  lineColor?: number;
  lineOpacity?: number;
  lineDash?: number[];
}

You can change textureTint and lineColor using hexadecimal numbers.

viewer.scene.styles.default.textureTint = 0xff0000; // For red. (RGB)

textureOpacity and lineOpacity are numbers between 0 and 1.

lineDash is an array of numbers describing the dash pattern you want to use. See here for more informations.

Texture Manager

The Texture Manager has the following interface:

interface TextureManager {
  textureMatrix: TextureMatrix;
  load(
    name: string,
    src: string,
    width?: number,
    height?: number
  ): Nullable<Object>;
}

// and the interface of the texture matrix
interface TextureMatrix {
  rotate(angle: number): void;
  scale(x: number, y?: number): void;
  translate(x: number, y: number): void;
}

The viewer only have "solid" texture built in but it is possible to load more:

await viewer.scene.textureManager.load(
  "myTextureName",
  "./my/textures/folder/myTexture.png"
);

NOTE: this is async code and must be awaited in order to the texture to ba available in the viewer.

This is what you can get: (wall are textured using a cross hatch texture)

wall texture raw

Notice that the textrure is aligned with the axis. If the model is not aligned with the axis, you can use the Texture Manager textureMatrix to rotate the textures.

viewer.scene.textureManager.textureMatrix.rotate(30);

wall texture rotated

Model and Objects

model and object have the following interfaces:

interface Model {
  readonly id: number;
  readonly scene: Scene;
  readonly objects: SceneObject[];
  addObject(objectData: SceneObjectData): SceneObject;
  removeObject(objectId: number): boolean;

  // Related with its objects gemetries
  readonly bounds: Bounds;
  readonly center: Point;
}

interface SceneObject {
  readonly id: number;
  readonly scene: Scene;
  readonly model: Model;
  readonly geometry: Geometry;

  // Scene state properties
  visible: boolean;
  selected: boolean;
  highlighted: boolean;
  pickable: boolean;

  // User custom properties
  readonly uuid?: string;
  readonly area?: number;
  readonly zIndex?: number;
  readonly type?: string;
}

Objects have geometries. An object geometry have the following interface:

interface Geometry {
  readonly shapes: Shape[];
  getShape(index?: number): Shape | undefined;
  addShape(shape: ShapeData | PointsData, index?: number): Shape;
  removeShape(index?: number): Shape | undefined;
  readonly bounds: Bounds;
  readonly center: Point;
}

The viewer handle two shape types: line and arc.

interface Line {
  readonly type: ShapeType.line;
  readonly points: Point[];
  getPoint(index?: number): Point | undefined;
  addPoint(point: Point, index?: number): Point;
  removePoint(index?: number): Point | undefined;
}

interface Arc {
  readonly type: ShapeType.arc;
  x: number;
  y: number;
  radius: number;
  startAngle: number;
  endAngle: number;
  anticlockwise?: boolean;
}

When adding objects on a scene, shapes can be provide to object using the geometry property. By default, array of numbers will be displayed as lines.

const model = viewer.scene.addModel({
  objects: [
    {
      geometry: [
        [0, 0, 50, 50], // a line
        {
          type: "line", // another line
          points: [0, 50, 50, 0],
        },
        {
          type: "arc", // a default arc => full circle
          x: 25,
          y: 25,
          radius: 20,
        },
      ],
    },
  ],
});

Events

The following events are emitted by the viewer2d scene :

  • "model-added", payload: the added model.
  • "model-removed", payload: the removed model.
  • "object-added", payload: the added object.
  • "object-removed", payload: the removed object.
  • "object-update", payload: { object, property, value?, oldValue? }. No value and oldValue for the "geometry" property.

Events can be listened using scene.on:

viewer.scene.on("model-added", model =>
  console.log(`A model is loaded with the id ${model.id}`)
);

UI

The UI is the only component connected to a DOM element, listening to events. It has the following interface:

interface UI extends EventHandler<UIHandlerEvents> {
  connect(el: HTMLElement): void;
  disconnect(): boolean;
}

The camera and the picker listen to the UI events. Disconnecting the UI will make them not reactive to user interactions.

Events

  • "click", payload: { position, keys }
  • "right-click", payload: { position, keys }
  • "move", payload: { position, keys }
  • "drag", payload: { dx, dy, keys }
  • "scroll", payload: { position, dx, dy, keys }
  • "exit", no payload, when the mouse leave el.

Camera

The camera is binded on the mouse events. It has the following interface:

interface Camera extends Readonly<EventHandler> {
  fitView(bbox: number[] | Bounds): void;
  controller: CameraController;
  translate(dx: number, dy: number): void;
  scale(factor: number, position: Point): void;
  rotate(angle: number, position: Point): void;
  transform: PIXIMatrix;
  getPosition(): Point;
  getRotation(): number;
  getScale(): Point;
}

It is possible to customise the camera behaviour by changing the properties of the camera.controller that has the following interface:

interface CameraController {
  translatable: boolean;
  rotatable: boolean;
  scallable: boolean;
}

All properties are true by default.

Picker

The picker allows to get objects under the mouse pointer.

interface Picker extends Readonly<EventHandler> {
  pick(x: number, y: number): SceneObject | undefined;
}

You may be mainly interseted by the picker events:

  • "pick", payload : { object, position, keys, rightClick: boolean }
  • "pick-nothing", payload: { position, keys, rightClick: boolean }
  • "hover-surface", payload: { object, position }
  • "hover", payload: { object, position }
  • "hover-out", payload: { object, position }
  • "hover-off", payload: { position }
viewer.camera.controller.on("pick", ({ object }) => {
  object.selected = !object.selected;
});

Settings

Settings are predefined values/keys that define the viewer2d behaviour. It has the following interface:

interface Settings extends Readonly<EventHandler> {
  rotateKey: string; // default "shift"
  scaleSpeed: number; // default 0.002
  rotateSpeed: number; // default 0.1
  maxScale: number; // default 100
  minScale: number; // default 0.1
  fitViewRatio: number; // 1
  pickingOrder: string; // default "area"
}

pickingOrder property only accept "area" or "zIndex". It represents the object property used to sort objects before draw and will influence the way objects can be picked.

Ticker

Use viewer.ticker.start/stop() to start or stop the rendering.

Use viewer.ticker.add/addOnce(name: string, callBack: Function) to schedule a callback on the next tick. The callback will be called with dt, the delta time since the next tick. WARNING: adding many tasks with the same name will overide the previous tasks and only the last added will be called on the next tick. This can be used to do not overdue a callback that must be done only once per tick.

Renderer

Renderer emits events you can use for addons:

  • "resize", payload: { dx: number, dy: number }. Emited when the renderer resizes.
  • "pre-draw". Emited before the draw phase.
  • "post-draw". Emited after the draw phase.

Vector2DUtils

It exposes some methods to work with 2D vectors:

interface Vector2DUtils {
  distance(v1: Vector2D, v2: Vector2D): number;
  sub(v1: Vector2D, v2: Vector2D): Vector2D;
  add(v1: Vector2D, v2: Vector2D): Vector2D;
  normalize(v: Vector2D): Vector2D;
  length(v: Vector2D): number;
  dot(v1: Vector2D, v2: Vector2D): number;
  cross(v1: Vector2D, v2: Vector2D): number;
  rotateAround(v: Vector2D, center: Vector2D, angle: number): Vector2D;
  angle(v1: Vector2D, v2: Vector2D): number;
}

Development

Build on change for development and serve:

npm run dev

Unit tests: (jest)

npm run test:unit

E2e tests: (cypress)

npm run build:prod
npm run test:e2e

To e2e test on development (Runs a build in production mode and opens a chromium window instead of a headless run):

npm run test:e2e-dev

Readme

Keywords

Package Sidebar

Install

npm i @bimdata/viewer2d

Weekly Downloads

33

Version

0.5.2

License

MIT

Unpacked Size

8.48 MB

Total Files

35

Last publish

Collaborators

  • amineau-bimdata
  • amoki
  • bimdata-io
  • gaellelrx
  • kurtil
  • nykori