@bimdata/2d-engine
Version:
An object oriented 2D viewer to load thousands of objects that you can select, highlight, hide, move with high performances
846 lines (755 loc) • 21.5 kB
TypeScript
// TODO these imports will not work if @pixi is not a dependency
type PIXIContainer = import("../node_modules/@pixi/display/index").Container;
type PIXIMatrix = import("../node_modules/@pixi/math/index").Matrix;
type PIXITransform = import("../node_modules/@pixi/math/index").Matrix;
type PIXIRenderer = import("../node_modules/@pixi/core/index").Renderer;
type PIXIRenderTexture =
import("../node_modules/@pixi/core/index").RenderTexture;
type PIXITexture = import("../node_modules/@pixi/core/index").Texture;
type PIXIGeometry = import("../node_modules/@pixi/core/index").Geometry;
type PIXIGraphics = import("../node_modules/@pixi/graphics/index").Graphics;
/**
* A factory to create a viewer
*/
declare module "@bimdata/2d-engine" {
export default E2D.makeViewer;
}
interface DOMMouseEvent extends MouseEvent {}
declare namespace E2D {
function makeViewer(cfg: ViewerCfg): Viewer;
interface ViewerCfg extends LayerOptions {
canvas: HTMLCanvasElement;
autoStart?: boolean;
}
interface Destroyable {
destroy(): void;
}
interface Viewer extends Destroyable {
readonly canvas: HTMLCanvasElement;
readonly scene: Scene;
readonly camera: Camera;
readonly renderer: Renderer;
readonly textureManager: TextureManager;
readonly styles: { default: Style; selected: Style; highlight: Style };
readonly settings: Settings;
readonly constants: Constants;
readonly picker: Picker;
readonly snapper: Snapper;
readonly ticker: Ticker;
readonly ui: UI;
readonly utils: {
readonly vector2D: Vector2DUtils;
makeTimedListProcessor(
callback: Function,
maxTimeMs?: number
): TimedListProcessor;
makeScheduler(): Scheduler;
geometry: GeometryUtils;
svg: SvgUtils;
};
}
interface SvgUtils {
parsePath(d: string): E2D.PathData[];
makeModelDataFromSVG(svg: SVGElement): E2D.ModelData;
}
interface GeometryUtils {
geometrize(viewer: Viewer, model: Model): GeometrizedModel;
binarize(geometrizedModel: E2D.GeometrizedModel): ArrayBuffer;
parse(buffer: ArrayBuffer): GeometrizedModel;
versions: Array<Object>;
getBinary(modelData: ModelData): Promise<ArrayBuffer>;
}
interface UI
extends EventHandler<UIHandlerEvents<UIMouseEvent>>,
Destroyable {
pick: boolean;
translatable: boolean;
rotatable: boolean;
scallable: boolean;
}
interface HTMLElementMouseEvent extends DOMMouseEvent {
readonly canvasPosition: Point;
readonly position: Point;
readonly righClick: boolean;
}
interface UIMouseEvent extends HTMLElementMouseEvent {
readonly object?: SceneObject;
readonly previousObject?: SceneObject;
readonly mouseDownCanvasPosition?: Point;
readonly mouseDownPosition?: Point;
}
interface HTMLElementUIHandler
extends EventHandler<HTMLElementUIHandlerEvents>,
Destroyable {
rightClick: boolean;
mouseDownCanvasPosition?: Point;
}
type HTMLElementUIHandlerEvents = UIHandlerEvents<HTMLElementMouseEvent>;
type UIHandlerEvents<T> = {
click: T;
mousemove: T;
mousedown: T;
mouseup: T;
wheel: T;
contextmenu: T;
mouseleave: T;
};
enum LINE_CAP {
BUTT,
ROUND,
SQUARE,
}
enum LINE_JOIN {
BEVEL,
MITER,
ROUND,
}
type Constants = {
LINE_CAP: {
BUTT: LINE_CAP;
ROUND: LINE_CAP;
SQUARE: LINE_CAP;
};
LINE_JOIN: {
BEVEL: LINE_JOIN;
MITER: LINE_JOIN;
ROUND: LINE_JOIN;
};
DEFAULT_MODEL_ID: string;
};
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;
/**
* @param angle in radians anticlockwise.
*/
rotateAround(v: Vector2D, center: Vector2D, angle: number): Vector2D;
/**
* @returns { number } angle in radians anticlockwise.
*/
angle(v1: Vector2D, v2: Vector2D): number;
}
interface Settings {
dragKey: string;
rotateKey: string;
clickMaxOffset: number;
scaleSpeed: number;
scaleDeltaMax: number;
rotateSpeed: number;
maxScale: number;
minScale: number;
fitViewRatio: number;
snapMargin: number;
curves: {
adaptive: boolean;
maxLength: number;
maxSegments: number;
minSegments: number;
};
}
interface Ticker extends EventHandler<{ tick: number }>, Scheduler {
start(): void;
stop(): void;
running: boolean;
}
interface Scheduler {
tasks: Map<string, Task>;
run(...args: any[]): void;
add(name: string, fn: Function, priority?: number, once?: boolean): void;
addOnce(name: string, fn: Function, priority?: number): void;
remove(name: string): boolean;
clear(): void;
}
interface Task {
name: string;
fn: Function;
priority: number;
once: boolean;
}
interface Picker {
pick(x: number, y: number): SceneObject | undefined;
oncePickingReady(name: string, callback: Function): Promise<void>;
}
interface Snapper {
getSnap(canvasPosition: Point, options?: SnapOptions): Point;
}
interface SnapOptions {
canvasDistance?: number;
snapVertices?: boolean;
snapLines?: boolean;
}
/**
* A camera allows to see the world. Two coordinate systems can be manipulated using it:
* - the world coordinates, mentionned as `position`.
* - the canvas coordinates, mentionned as `canvasPosition`.
*
* A camera emits events:
* - "update", payload { MatrixExtended } : the camera transform. Emited when the camera is updated.
*
* To express the zoom level, the camera has two units:
* - zoom, expressed in percentages, as 100 times the scale.
* - scale ]0,+Infitiy[.
*
*/
interface Camera
extends Transformable,
Positionable,
Readonly<EventHandler<{ update: PIXITransform }>>,
Destroyable {
fitView(
target: Scene | Scene[] | Model | Model[] | SceneObject | SceneObject[]
): void;
/**
* @param factor ]-Infinity, +Infinity[
* @param origin the zoom transform center.
*/
zoomIn(factor?: number, origin?: Point): void;
/**
* @param factor ]-Infinity, +Infinity[
* @param origin the zoom transform center.
*/
zoomOut(factor?: number, origin?: Point): void;
/**
* @param angle in degree clockwise.
* @param origin the rotation transform center.
*/
transform: PIXIMatrix;
position: Point;
/**
* @returns { number } the camera rotation angle in degree clockwise.
*/
rotation: number;
/**
* @returns { number } ]0, +Infinity[
*/
getScale(): number;
/**
* @returns { number } ]0, +Infinity[
*/
zoom: number;
/**
* Returns the position of the given canvas position.
*/
getPosition(canvasPosition: Point): Point;
/**
* Returns the canvas position of the given position.
*/
getCanvasPosition(position: Point): Point;
getViewpoint(): Viewpoint;
setViewpoint(viewpoint: Viewpoint): Viewpoint;
}
interface Viewpoint {
x: number;
y: number;
/**
* degree clockwise
*/
rotation: number;
/**
* ]-Infinity, +Infinity[
*/
zoom: number;
}
interface Point {
x: number;
y: number;
}
interface ReactivePoint extends Point, ReactiveEntity {
index: number;
}
interface Vector2D extends Point {}
type Events = {
[eventName: string]: Object;
};
interface EventHandler<T extends Events> {
on<N extends keyof T>(eventName: N, callback: (arg: T[N]) => void): number;
once<N extends keyof T>(
eventName: N,
callback: (arg: T[N]) => void
): number;
off(subscriptionId: number): void;
emit<N extends keyof T>(eventName: N, payload: T[N]): void;
clear(): void;
}
interface Typed<T> {
type: T;
}
enum EntityType {
scene = "scene",
model = "model",
object = "object",
shape = "shape",
geometry = "geometry",
}
interface Scene
extends Readonly<EventHandler<SceneEvents>>,
Typed<EntityType.scene>,
Positionable {
dirty: boolean;
cache: Object;
readonly viewer: Viewer;
generateReport(): SceneReport;
// Models
readonly orphanObjectsModel: Model;
readonly models: Model[];
readonly modelsMap: Map<string, Model>;
addModel(modelData: ModelData): Promise<Model>;
removeModel(id: string): boolean;
_removedModels: Model[];
// Objects
readonly objects: SceneObject[];
readonly objectsMap: Map<string, SceneObject>;
readonly pickingIds: Map<number, SceneObject>;
addObject(objectData: SceneObjectData, model?: Model): SceneObject;
removeObject(objectId: string): boolean;
_shapeId: number;
_geometryId: number;
// Objects setters
showObjects(ids: string[]): void;
hideObjects(ids: string[]): void;
selectObjects(ids: string[]): void;
deselectObjects(ids: string[]): void;
highlightObjects(ids: string[]): void;
unhighlightObjects(ids: string[]): void;
setObjectsPickable(ids: string[]): void;
setObjectsUnpickable(ids: string[]): 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[];
}
type SceneEvents = {
"model-added": Model;
"model-removed": Model;
"object-added": SceneObject;
"object-removed": SceneObject;
"object-update": {
object: SceneObject;
property: keyof SceneStateObjectData | keyof Style | "shapes";
value?: number | Array<number> | string;
oldValue?: number | Array<number> | string;
};
};
interface ModelData {
readonly id?: string;
readonly objects?: SceneObjectData[];
readonly textures?: TextureData[];
}
interface TextureData {
name: string;
uri: string;
width?: number;
height?: number;
resolution?: number;
}
/**
* A model is where objects live.
*/
interface Model extends Transformable, Positionable, Typed<EntityType.model> {
readonly id: string;
dirty: boolean;
readonly scene: Scene;
addObject(objectData: SceneObjectData): SceneObject;
removeObject(objectId: string): boolean;
readonly objects: SceneObject[];
}
interface GeometrizedModel {
readonly id: string;
objects: GeometrizedObject[];
textures: TextureData[];
}
interface Translate {
dx: number;
dy: number;
}
interface SceneStateObjectData {
visible: boolean;
selected: boolean;
highlighted: boolean;
pickable: boolean;
}
interface SceneObjectData extends SceneStateObjectData {
readonly id?: string;
readonly zIndex?: number;
readonly pickingZIndex?: number;
readonly shapes?: ShapeData[];
readonly texts?: Text[];
readonly geometries?: Geometry[];
}
interface SceneObject
extends Transformable,
Positionable,
Typed<EntityType.object> {
readonly scene: Scene;
readonly model: Model;
readonly id: string;
readonly readonly?: boolean;
readonly zIndex: number;
readonly pickingId: number;
readonly pickingZIndex: number;
readonly pickingMargin: number;
readonly shapes: Shape[];
getShape(index?: number): Shape;
addShape(shapeData: ShapeData, index?: number): Shape;
removeShape(index?: number): Shape;
readonly texts?: Text[];
getText(index?: number): Text;
addText(text: Text, index?: number): Text;
removeText(index?: number): Text;
readonly geometries?: Geometry[];
flags: number;
}
type SceneObjectFactory = (
objectData: SceneObjectData,
model: Model
) => SceneObject;
interface GeometrizedObject {
id: string;
zIndex: number;
pickingZIndex: number;
pickable: boolean;
texts: Text;
readonly: boolean;
aabb: AABB;
geometries: GeometryData[];
}
interface GeometryData {
type: "line" | "fill";
points: number[];
indices: number[];
color: number | string;
alpha: number;
native?: boolean;
readonly aabb: AABB;
}
interface Geometry extends GeometryData, Positionable {
readonly id: number;
readonly object: SceneObject;
readonly style: Style; // simplified style with only `lineColor`, `lineOpacity`, `fill` & `fillOpacity`
}
interface Drawable {
draw(
graphics: PIXIGraphics,
translate: E2D.Translate,
lineSegments?: number[],
startPath?: boolean
): void;
}
interface Shape extends ReactiveEntity, Positionable {
readonly id: number;
readonly type: EntityType.shape;
readonly object: SceneObject;
readonly binary?: DataView;
readonly paths?: Path[];
readonly style: Style;
getPath(index?: number): Path | undefined;
addPath(path: PathData, index?: number): Path;
removePath(index?: number): Path | undefined;
}
interface ShapeData {
paths: Array<PathData>;
style: Style;
}
interface PathDataAbstract {
type?: PathType;
close?: boolean;
start?: boolean;
}
type PathData = LineData | ArcData | CurveData;
interface LineData extends PathDataAbstract {
type: PathType.line;
points: number[];
}
interface Line extends Path {
readonly type: PathType.line;
cache: LineCache;
readonly points: Point[];
getPoint(index?: number): Point | undefined;
addPoint(point: Point, index?: number): Point;
removePoint(index?: number): Point | undefined;
}
interface LineCache {
aabb?: AABB;
center?: Point;
}
interface ArcData extends PathDataAbstract {
type: PathType.arc;
x: number;
y: number;
radius: number;
startAngle?: number; // (0 is at the 3 o'clock position of the arc's circle)
endAngle?: number;
anticlockwise?: boolean;
}
interface Arc extends Path {
readonly type: PathType.arc;
cache: ArcCache;
x: number;
y: number;
radius: number;
startAngle: number;
endAngle: number;
anticlockwise?: boolean;
}
interface ArcCache {
aabb?: AABB;
}
interface CurveData extends PathDataAbstract {
type: PathType.curve;
x: number;
y: number;
toX: number;
toY: number;
cpX1: number;
cpY1: number;
cpX2: number;
cpY2: number;
}
interface Curve extends Path {
readonly type: PathType.curve;
cache: CurveCache;
x: number;
y: number;
/**
* The x coordinate of the first control point.
*/
cpX1: number;
/**
* The y coordinate of the first control point.
*/
cpY1: number;
/**
* The x coordinate of the second control point.
*/
cpX2: number;
/**
* The y coordinate of the second control point.
*/
cpY2: number;
toX: number;
toY: number;
}
interface CurveCache {
aabb?: AABB;
center?: Point;
}
enum PathType {
line = "line",
arc = "arc",
curve = "curve",
}
interface Path extends ReactiveEntity, Drawable, Positionable, Transformable {
readonly type: PathType;
getAABB(): AABB;
getCenter(): Point;
cache: any;
readonly close: boolean;
readonly start: boolean;
}
type AABB = [minX: number, minY: number, maxX: number, maxY: number];
interface Positionable {
readonly aabb: AABB;
readonly center: Point;
}
interface Transformable {
move(position: Point): void;
translate(dx: number, dy: number): void;
/**
* @param angle in degree, clockwise
* @param origin
*/
rotate(angle: number, origin?: Point): void;
scale(ds: number, origin?: Point): void;
}
interface ReactiveEntity {
onUpdate?(cfg: any): void;
}
interface Style {
// FILL
fill?: number | string; // number for hex color, string for a registered texture name
fillOpacity?: number;
// LINE
lineWidth?: number;
lineColor?: number;
lineOpacity?: number;
lineDash?: number[];
lineCap?: LINE_CAP;
lineJoin?: LINE_JOIN;
nonScalingLineWidth?: boolean;
performanceLine?: boolean;
}
interface TextureManager extends Destroyable {
modelTextures: Map<string, Map<string, NamedPIXITexture>>;
textureMatrix: TextureMatrix;
load(modelId: string, texturesData: TextureData[]): Promise<void>;
unload(modelId: string): boolean;
}
interface NamedPIXITexture extends PIXITexture {
name: string;
}
interface TextureMatrix
extends Readonly<
EventHandler<{
update: { rotate?: number; scale?: Point; translate?: Point };
}>
> {
readonly matrix: PIXIMatrix;
rotate(angle: number): void;
scale(x: number, y?: number): void;
translate(x: number, y: number): void;
}
interface TextStyle {
fill: string;
fontFamily: string | string[];
fontSize: number | string;
fontStyle: string;
fontWeight: string;
lineHeight: number;
} // other properties availables https://pixijs.download/v6.0.3/docs/PIXI.TextStyle.html
interface Text extends TextStyle {
content?: string;
x?: number;
y?: number;
anchor?: [number, number];
rotation: number;
scale: number;
}
interface SceneReport {
addedObjects?: SceneObject[];
removedObjects?: SceneObject[];
visibleObjects?: SceneObject[];
selectedObjects?: SceneObject[];
highlightedObjects?: SceneObject[];
pickableObjects?: SceneObject[];
stylizedObjects?: SceneObject[];
shapedObjects?: SceneObject[];
textObjects?: SceneObject[];
}
interface Renderer extends Readonly<EventHandler<RendererEvents>> {
readonly viewer: Viewer;
readonly renderer: PIXIRenderer;
transform: PIXIMatrix;
/**
* @param { boolean } [force=false] The Renderer does not render each frame if nothing change. If `true`, it redraws the in memory shapes on the canvas.
* @param { boolean } [clear=false] Clear the in memory shapes to redraw from scratch.
*/
render(force?: boolean, clear?: boolean): void;
transformUpdated: boolean;
offset: Translate;
readonly layers: Map<string, Layer>;
resize(): void;
makeShapeDrawer(
style: Style,
textures?: Map<string, NamedPIXITexture>,
textureMatrix?: PIXIMatrix
): Drawer;
makeShapePickingDrawer(
snap?: boolean,
scaleDependant?: boolean
): ShapePickingDrawer;
makeGeometryDrawer(
style: Style,
textures?: Map<string, NamedPIXITexture>,
textureMatrix?: PIXIMatrix
): Drawer;
geometrize(model: Model): GeometrizedModel;
}
type RendererEvents = {
resize: { dx: number; dy: number };
"pre-draw": undefined;
"post-draw": undefined;
};
interface Drawer extends Destroyable {
displayObject: PIXIContainer;
clear(): void;
draw(elements: Shape[] | Geometry[]): void;
readonly scaleDependant: boolean;
readonly initialLineWidth?: number;
lineWidth?: number;
}
interface PickingDrawer extends Drawer {
registerOnceDrawn(name: string, callback: Function): void;
}
interface ShapePickingDrawer extends PickingDrawer {}
interface Layer extends Destroyable {
name: string;
visible: boolean;
renderer: Renderer;
renderTexture: PIXIRenderTexture;
stage: PIXIContainer;
resize(width: number, height: number): void;
addObjects(objects: SceneObject[]): void;
updateObjects(objects: SceneObject[]): void;
removeObjects(objects: SceneObject): void;
render(forceDirty?: boolean): void;
dirty: boolean;
clear(): void;
}
interface PickingLayer extends Layer {
pickColorAtCanvasPosition(
position: Point,
snap?: boolean
): number | undefined;
registerOnceDrawn(name: string, callback: Function): void;
}
interface BatchLayer extends Layer {
objects: SceneObject[];
stage: DirtyableContainer;
batches: Batch[];
render(forceDirty?: boolean, matrix?: PIXIMatrix): void;
}
interface LayerOptions {
select?: boolean;
highlight?: boolean;
picking?: boolean;
showPicking?: boolean;
snap?: boolean;
showSnap?: boolean;
text?: boolean;
}
interface LayerHandlers {
handleAddedObjects(): void;
handleRemovedObjects(): void;
handleUpdatedObjects(): void;
}
interface DirtyableContainer extends PIXIContainer {
dirty: boolean;
}
interface Batch extends Destroyable {
readonly drawer: Drawer;
dirty: boolean;
addEntities(entities: any[]): void;
removeEntities(ids: number[]): void;
entities: Map<number, any>;
}
interface EntityBatch<T> extends Batch {
type: EntityType;
modelId: string;
key: string;
zIndex: number;
entities: Map<number, T>;
addEntity(entities: T[]): void;
}
interface GeometryBatch extends EntityBatch<Geometry> {
type: EntityType.geometry;
}
interface ShapeBatch extends EntityBatch<Shape> {
type: EntityType.shape;
}
interface TimedListProcessor {
readonly done: boolean;
list: any[];
start(list: any[], onceDone?: Function): void;
stop(): void;
}
}