123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- import { Nullable } from "types";
- import { AbstractMesh, LinesMesh, InstancedMesh, VertexBuffer } from "Mesh";
- import { Vector3 } from "Math";
- import { IDisposable } from "scene";
- import { Observer } from "Tools";
- import { LineEdgesRenderer } from "Rendering";
- import { ShaderMaterial, Effect, Material } from "Materials";
- import { Camera } from "Cameras";
- import { Engine } from "Engine";
- import { Node } from "node";
- declare module "Mesh/AbstractMesh" {
- export interface AbstractMesh {
- /**
- * Disables the mesh edge rendering mode
- * @returns the currentAbstractMesh
- */
- disableEdgesRendering(): AbstractMesh;
- /**
- * Enables the edge rendering mode on the mesh.
- * This mode makes the mesh edges visible
- * @param epsilon defines the maximal distance between two angles to detect a face
- * @param checkVerticesInsteadOfIndices indicates that we should check vertex list directly instead of faces
- * @returns the currentAbstractMesh
- * @see https://www.babylonjs-playground.com/#19O9TU#0
- */
- enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean): AbstractMesh;
- /**
- * Gets the edgesRenderer associated with the mesh
- */
- edgesRenderer: Nullable<EdgesRenderer>;
- }
- }
- AbstractMesh.prototype.disableEdgesRendering = function(): AbstractMesh {
- if (this._edgesRenderer) {
- this._edgesRenderer.dispose();
- this._edgesRenderer = null;
- }
- return this;
- };
- AbstractMesh.prototype.enableEdgesRendering = function(epsilon = 0.95, checkVerticesInsteadOfIndices = false): AbstractMesh {
- this.disableEdgesRendering();
- this._edgesRenderer = new EdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
- return this;
- };
- Object.defineProperty(AbstractMesh.prototype, "edgesRenderer", {
- get: function(this: AbstractMesh) {
- return this._edgesRenderer;
- },
- enumerable: true,
- configurable: true
- });
- declare module "Mesh/LinesMesh" {
- export interface LinesMesh {
- /**
- * Enables the edge rendering mode on the mesh.
- * This mode makes the mesh edges visible
- * @param epsilon defines the maximal distance between two angles to detect a face
- * @param checkVerticesInsteadOfIndices indicates that we should check vertex list directly instead of faces
- * @returns the currentAbstractMesh
- * @see https://www.babylonjs-playground.com/#19O9TU#0
- */
- enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean): AbstractMesh;
- }
- }
- LinesMesh.prototype.enableEdgesRendering = function(epsilon = 0.95, checkVerticesInsteadOfIndices = false): AbstractMesh {
- this.disableEdgesRendering();
- this._edgesRenderer = new LineEdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
- return this;
- };
- declare module "Mesh/InstancedLinesMesh" {
- export interface InstancedLinesMesh {
- /**
- * Enables the edge rendering mode on the mesh.
- * This mode makes the mesh edges visible
- * @param epsilon defines the maximal distance between two angles to detect a face
- * @param checkVerticesInsteadOfIndices indicates that we should check vertex list directly instead of faces
- * @returns the current InstancedLinesMesh
- * @see https://www.babylonjs-playground.com/#19O9TU#0
- */
- enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean): InstancedLinesMesh;
- }
- }
- InstancedLinesMesh.prototype.enableEdgesRendering = function(epsilon = 0.95, checkVerticesInsteadOfIndices = false): InstancedLinesMesh {
- LinesMesh.prototype.enableEdgesRendering.apply(this, arguments);
- return this;
- };
- /**
- * FaceAdjacencies Helper class to generate edges
- */
- class FaceAdjacencies {
- public edges = new Array<number>();
- public p0: Vector3;
- public p1: Vector3;
- public p2: Vector3;
- public edgesConnectedCount = 0;
- }
- /**
- * Defines the minimum contract an Edges renderer should follow.
- */
- export interface IEdgesRenderer extends IDisposable {
- /**
- * Gets or sets a boolean indicating if the edgesRenderer is active
- */
- isEnabled: boolean;
- /**
- * Renders the edges of the attached mesh,
- */
- render(): void;
- /**
- * Checks wether or not the edges renderer is ready to render.
- * @return true if ready, otherwise false.
- */
- isReady(): boolean;
- }
- /**
- * This class is used to generate edges of the mesh that could then easily be rendered in a scene.
- */
- export class EdgesRenderer implements IEdgesRenderer {
- /**
- * Define the size of the edges with an orthographic camera
- */
- public edgesWidthScalerForOrthographic = 1000.0;
- /**
- * Define the size of the edges with a perspective camera
- */
- public edgesWidthScalerForPerspective = 50.0;
- protected _source: AbstractMesh;
- protected _linesPositions = new Array<number>();
- protected _linesNormals = new Array<number>();
- protected _linesIndices = new Array<number>();
- protected _epsilon: number;
- protected _indicesCount: number;
- protected _lineShader: ShaderMaterial;
- protected _ib: WebGLBuffer;
- protected _buffers: { [key: string]: Nullable<VertexBuffer> } = {};
- protected _checkVerticesInsteadOfIndices = false;
- private _meshRebuildObserver: Nullable<Observer<AbstractMesh>>;
- private _meshDisposeObserver: Nullable<Observer<Node>>;
- /** Gets or sets a boolean indicating if the edgesRenderer is active */
- public isEnabled = true;
- /**
- * Creates an instance of the EdgesRenderer. It is primarily use to display edges of a mesh.
- * Beware when you use this class with complex objects as the adjacencies computation can be really long
- * @param source Mesh used to create edges
- * @param epsilon sum of angles in adjacency to check for edge
- * @param checkVerticesInsteadOfIndices
- * @param generateEdgesLines - should generate Lines or only prepare resources.
- */
- constructor(source: AbstractMesh, epsilon = 0.95, checkVerticesInsteadOfIndices = false, generateEdgesLines = true) {
- this._source = source;
- this._checkVerticesInsteadOfIndices = checkVerticesInsteadOfIndices;
- this._epsilon = epsilon;
- this._prepareRessources();
- if (generateEdgesLines) {
- this._generateEdgesLines();
- }
- this._meshRebuildObserver = this._source.onRebuildObservable.add(() => {
- this._rebuild();
- });
- this._meshDisposeObserver = this._source.onDisposeObservable.add(() => {
- this.dispose();
- });
- }
- protected _prepareRessources(): void {
- if (this._lineShader) {
- return;
- }
- this._lineShader = new ShaderMaterial("lineShader", this._source.getScene(), "line",
- {
- attributes: ["position", "normal"],
- uniforms: ["worldViewProjection", "color", "width", "aspectRatio"]
- });
- this._lineShader.disableDepthWrite = true;
- this._lineShader.backFaceCulling = false;
- }
- /** @hidden */
- public _rebuild(): void {
- var buffer = this._buffers[VertexBuffer.PositionKind];
- if (buffer) {
- buffer._rebuild();
- }
- buffer = this._buffers[VertexBuffer.NormalKind];
- if (buffer) {
- buffer._rebuild();
- }
- var scene = this._source.getScene();
- var engine = scene.getEngine();
- this._ib = engine.createIndexBuffer(this._linesIndices);
- }
- /**
- * Releases the required resources for the edges renderer
- */
- public dispose(): void {
- this._source.onRebuildObservable.remove(this._meshRebuildObserver);
- this._source.onDisposeObservable.remove(this._meshDisposeObserver);
- var buffer = this._buffers[VertexBuffer.PositionKind];
- if (buffer) {
- buffer.dispose();
- this._buffers[VertexBuffer.PositionKind] = null;
- }
- buffer = this._buffers[VertexBuffer.NormalKind];
- if (buffer) {
- buffer.dispose();
- this._buffers[VertexBuffer.NormalKind] = null;
- }
- this._source.getScene().getEngine()._releaseBuffer(this._ib);
- this._lineShader.dispose();
- }
- protected _processEdgeForAdjacencies(pa: number, pb: number, p0: number, p1: number, p2: number): number {
- if (pa === p0 && pb === p1 || pa === p1 && pb === p0) {
- return 0;
- }
- if (pa === p1 && pb === p2 || pa === p2 && pb === p1) {
- return 1;
- }
- if (pa === p2 && pb === p0 || pa === p0 && pb === p2) {
- return 2;
- }
- return -1;
- }
- protected _processEdgeForAdjacenciesWithVertices(pa: Vector3, pb: Vector3, p0: Vector3, p1: Vector3, p2: Vector3): number {
- if (pa.equalsWithEpsilon(p0) && pb.equalsWithEpsilon(p1) || pa.equalsWithEpsilon(p1) && pb.equalsWithEpsilon(p0)) {
- return 0;
- }
- if (pa.equalsWithEpsilon(p1) && pb.equalsWithEpsilon(p2) || pa.equalsWithEpsilon(p2) && pb.equalsWithEpsilon(p1)) {
- return 1;
- }
- if (pa.equalsWithEpsilon(p2) && pb.equalsWithEpsilon(p0) || pa.equalsWithEpsilon(p0) && pb.equalsWithEpsilon(p2)) {
- return 2;
- }
- return -1;
- }
- /**
- * Checks if the pair of p0 and p1 is en edge
- * @param faceIndex
- * @param edge
- * @param faceNormals
- * @param p0
- * @param p1
- * @private
- */
- protected _checkEdge(faceIndex: number, edge: number, faceNormals: Array<Vector3>, p0: Vector3, p1: Vector3): void {
- var needToCreateLine;
- if (edge === undefined) {
- needToCreateLine = true;
- } else {
- var dotProduct = Vector3.Dot(faceNormals[faceIndex], faceNormals[edge]);
- needToCreateLine = dotProduct < this._epsilon;
- }
- if (needToCreateLine) {
- this.createLine(p0, p1, this._linesPositions.length / 3);
- }
- }
- /**
- * push line into the position, normal and index buffer
- * @protected
- */
- protected createLine(p0: Vector3, p1: Vector3, offset: number) {
- // Positions
- this._linesPositions.push(
- p0.x, p0.y, p0.z,
- p0.x, p0.y, p0.z,
- p1.x, p1.y, p1.z,
- p1.x, p1.y, p1.z
- );
- // Normals
- this._linesNormals.push(
- p1.x, p1.y, p1.z, -1,
- p1.x, p1.y, p1.z, 1,
- p0.x, p0.y, p0.z, -1,
- p0.x, p0.y, p0.z, 1
- );
- // Indices
- this._linesIndices.push(
- offset, offset + 1, offset + 2,
- offset, offset + 2, offset + 3
- );
- }
- /**
- * Generates lines edges from adjacencjes
- * @private
- */
- _generateEdgesLines(): void {
- var positions = this._source.getVerticesData(VertexBuffer.PositionKind);
- var indices = this._source.getIndices();
- if (!indices || !positions) {
- return;
- }
- // First let's find adjacencies
- var adjacencies = new Array<FaceAdjacencies>();
- var faceNormals = new Array<Vector3>();
- var index: number;
- var faceAdjacencies: FaceAdjacencies;
- // Prepare faces
- for (index = 0; index < indices.length; index += 3) {
- faceAdjacencies = new FaceAdjacencies();
- var p0Index = indices[index];
- var p1Index = indices[index + 1];
- var p2Index = indices[index + 2];
- faceAdjacencies.p0 = new Vector3(positions[p0Index * 3], positions[p0Index * 3 + 1], positions[p0Index * 3 + 2]);
- faceAdjacencies.p1 = new Vector3(positions[p1Index * 3], positions[p1Index * 3 + 1], positions[p1Index * 3 + 2]);
- faceAdjacencies.p2 = new Vector3(positions[p2Index * 3], positions[p2Index * 3 + 1], positions[p2Index * 3 + 2]);
- var faceNormal = Vector3.Cross(faceAdjacencies.p1.subtract(faceAdjacencies.p0), faceAdjacencies.p2.subtract(faceAdjacencies.p1));
- faceNormal.normalize();
- faceNormals.push(faceNormal);
- adjacencies.push(faceAdjacencies);
- }
- // Scan
- for (index = 0; index < adjacencies.length; index++) {
- faceAdjacencies = adjacencies[index];
- for (var otherIndex = index + 1; otherIndex < adjacencies.length; otherIndex++) {
- var otherFaceAdjacencies = adjacencies[otherIndex];
- if (faceAdjacencies.edgesConnectedCount === 3) { // Full
- break;
- }
- if (otherFaceAdjacencies.edgesConnectedCount === 3) { // Full
- continue;
- }
- var otherP0 = indices[otherIndex * 3];
- var otherP1 = indices[otherIndex * 3 + 1];
- var otherP2 = indices[otherIndex * 3 + 2];
- for (var edgeIndex = 0; edgeIndex < 3; edgeIndex++) {
- var otherEdgeIndex: number = 0;
- if (faceAdjacencies.edges[edgeIndex] !== undefined) {
- continue;
- }
- switch (edgeIndex) {
- case 0:
- if (this._checkVerticesInsteadOfIndices) {
- otherEdgeIndex = this._processEdgeForAdjacenciesWithVertices(faceAdjacencies.p0, faceAdjacencies.p1, otherFaceAdjacencies.p0, otherFaceAdjacencies.p1, otherFaceAdjacencies.p2);
- } else {
- otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3], indices[index * 3 + 1], otherP0, otherP1, otherP2);
- }
- break;
- case 1:
- if (this._checkVerticesInsteadOfIndices) {
- otherEdgeIndex = this._processEdgeForAdjacenciesWithVertices(faceAdjacencies.p1, faceAdjacencies.p2, otherFaceAdjacencies.p0, otherFaceAdjacencies.p1, otherFaceAdjacencies.p2);
- } else {
- otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3 + 1], indices[index * 3 + 2], otherP0, otherP1, otherP2);
- }
- break;
- case 2:
- if (this._checkVerticesInsteadOfIndices) {
- otherEdgeIndex = this._processEdgeForAdjacenciesWithVertices(faceAdjacencies.p2, faceAdjacencies.p0, otherFaceAdjacencies.p0, otherFaceAdjacencies.p1, otherFaceAdjacencies.p2);
- } else {
- otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3 + 2], indices[index * 3], otherP0, otherP1, otherP2);
- }
- break;
- }
- if (otherEdgeIndex === -1) {
- continue;
- }
- faceAdjacencies.edges[edgeIndex] = otherIndex;
- otherFaceAdjacencies.edges[otherEdgeIndex] = index;
- faceAdjacencies.edgesConnectedCount++;
- otherFaceAdjacencies.edgesConnectedCount++;
- if (faceAdjacencies.edgesConnectedCount === 3) {
- break;
- }
- }
- }
- }
- // Create lines
- for (index = 0; index < adjacencies.length; index++) {
- // We need a line when a face has no adjacency on a specific edge or if all the adjacencies has an angle greater than epsilon
- var current = adjacencies[index];
- this._checkEdge(index, current.edges[0], faceNormals, current.p0, current.p1);
- this._checkEdge(index, current.edges[1], faceNormals, current.p1, current.p2);
- this._checkEdge(index, current.edges[2], faceNormals, current.p2, current.p0);
- }
- // Merge into a single mesh
- var engine = this._source.getScene().getEngine();
- this._buffers[VertexBuffer.PositionKind] = new VertexBuffer(engine, this._linesPositions, VertexBuffer.PositionKind, false);
- this._buffers[VertexBuffer.NormalKind] = new VertexBuffer(engine, this._linesNormals, VertexBuffer.NormalKind, false, false, 4);
- this._ib = engine.createIndexBuffer(this._linesIndices);
- this._indicesCount = this._linesIndices.length;
- }
- /**
- * Checks wether or not the edges renderer is ready to render.
- * @return true if ready, otherwise false.
- */
- public isReady(): boolean {
- return this._lineShader.isReady();
- }
- /**
- * Renders the edges of the attached mesh,
- */
- public render(): void {
- var scene = this._source.getScene();
- if (!this.isReady() || !scene.activeCamera) {
- return;
- }
- var engine = scene.getEngine();
- this._lineShader._preBind();
- if (this._source.edgesColor.a !== 1) {
- engine.setAlphaMode(Engine.ALPHA_COMBINE);
- } else {
- engine.setAlphaMode(Engine.ALPHA_DISABLE);
- }
- // VBOs
- engine.bindBuffers(this._buffers, this._ib, <Effect>this._lineShader.getEffect());
- scene.resetCachedMaterial();
- this._lineShader.setColor4("color", this._source.edgesColor);
- if (scene.activeCamera.mode === Camera.ORTHOGRAPHIC_CAMERA) {
- this._lineShader.setFloat("width", this._source.edgesWidth / this.edgesWidthScalerForOrthographic);
- } else {
- this._lineShader.setFloat("width", this._source.edgesWidth / this.edgesWidthScalerForPerspective);
- }
- this._lineShader.setFloat("aspectRatio", engine.getAspectRatio(scene.activeCamera));
- this._lineShader.bind(this._source.getWorldMatrix());
- // Draw order
- engine.drawElementsType(Material.TriangleFillMode, 0, this._indicesCount);
- this._lineShader.unbind();
- }
- }
|