UNPKG

@xeokit/xeokit-sdk

Version:

3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision

571 lines (522 loc) 18.4 kB
import {Component} from '../Component.js'; import {Node} from "../nodes/Node.js"; import {Mesh} from "../mesh/Mesh.js"; import {PhongMaterial} from "../materials/PhongMaterial.js"; import {buildPlaneGeometry} from "../geometry/builders/buildPlaneGeometry.js"; import {Texture} from "../materials/Texture.js"; import {buildGridGeometry} from "../geometry/builders/buildGridGeometry.js"; import {ReadableGeometry} from "../geometry/ReadableGeometry.js"; import {math} from "../math/math.js"; import {worldToRTCPos} from "../math/rtcCoords.js"; const tempVec3 = math.vec3(); const tempVec3b = math.vec3(); const tempVec3c = math.vec3(); const zeroVec = math.vec3([0, -1, 0]); const tempQuat = math.vec4([0, 0, 0, 1]); /** * @desc A plane-shaped 3D object containing a bitmap image. * * Use ````ImagePlane```` to embed bitmap images in your scenes. * * As shown in the examples below, ````ImagePlane```` is useful for creating ground planes from satellite maps and embedding 2D plan * view images in cross-section slicing planes. * * # Example 1: Create a ground plane from a satellite image * * In our first example, we'll load the Schependomlaan model, then use * an ````ImagePlane```` to create a ground plane, which will contain * a satellite image sourced from Google Maps. * * <img src="http://xeokit.io/img/docs/ImagePlane/schependomlaanGoogleSatMapMed.png"> * * [<img src="http://xeokit.io/img/docs/ImagePlane/ImagePlane.png">](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#ImagePlane_groundPlane) * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#ImagePlane_groundPlane)] * * ````javascript * import {Viewer, ImagePlane, XKTLoaderPlugin} from "xeokit-sdk.es.js"; * * const viewer = new Viewer({ * canvasId: "myCanvas", * transparent: true * }); * * viewer.camera.eye = [-8.31, 42.21, 54.30]; * viewer.camera.look = [-0.86, 15.40, 14.90]; * viewer.camera.up = [0.10, 0.83, -0.54]; * * const xktLoader = new XKTLoaderPlugin(viewer); * * xktLoader.load({ // Load IFC model * id: "myModel", * src: "./models/xkt/Schependomlaan.xkt", * edges: true, * * rotation: [0, 22, 0], // Rotate, position and scale the model to align it correctly with the ImagePlane * position: [-8, 0, 15], * scale: [1.1, 1.1, 1.1] * }); * * new ImagePlane(viewer.scene, { * src: "./images/schependomlaanSatMap.png", // Google satellite image; accepted file types are PNG and JPEG * visible: true, // Show the ImagePlane * gridVisible: true, // Show the grid - grid is only visible when ImagePlane is also visible * size: 190, // Size of ImagePlane's longest edge * position: [0, -1, 0], // World-space position of ImagePlane's center * rotation: [0, 0, 0], // Euler angles for X, Y and Z * opacity: 1.0, // Fully opaque * collidable: false, // ImagePlane does not contribute to Scene boundary * clippable: true, // ImagePlane can be clipped by SectionPlanes * pickable: true // Allow the ground plane to be picked * }); * ```` *<br> * * # Example 2: Embed an image in a cross-section plane * * In our second example, we'll load the Schependomlaan model again, then slice it in half with * a {@link SectionPlanesPlugin}, then use an ````ImagePlane```` to embed a 2D plan view image in the slicing plane. * * <img src="http://xeokit.io/img/docs/ImagePlane/schependomlaanPlanViewMed.png"> * * [<img src="http://xeokit.io/img/docs/ImagePlane/ImagePlane_planView.png">](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#ImagePlane_imageInSectionPlane) * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/scenegraph/#ImagePlane_imageInSectionPlane)] * * ````javascript * import {Viewer, XKTLoaderPlugin, SectionPlanesPlugin, ImagePlane} from "xeokit-sdk.es.js"; * * const viewer = new Viewer({ * canvasId: "myCanvas", * transparent: true * }); * * viewer.camera.eye = [-9.11, 20.01, 5.13]; * viewer.camera.look = [9.07, 0.77, -9.78]; * viewer.camera.up = [0.47, 0.76, -0.38]; * * const xktLoader = new XKTLoaderPlugin(viewer); * * const sectionPlanes = new SectionPlanesPlugin(viewer, { * overviewVisible: false * }); * * model = xktLoader.load({ * id: "myModel", * src: "./models/xkt/schependomlaan/schependomlaan.xkt", * metaModelSrc: "./metaModels/schependomlaan/metaModel.json", * edges: true, * }); * * const sectionPlane = sectionPlanes.createSectionPlane({ * id: "mySectionPlane", * pos: [10.95, 1.95, -10.35], * dir: [0.0, -1.0, 0.0] * }); * * const imagePlane = new ImagePlane(viewer.scene, { * src: "./images/schependomlaanPlanView.png", // Plan view image; accepted file types are PNG and JPEG * visible: true, * gridVisible: true, * size: 23.95, * position: sectionPlane.pos, * dir: sectionPlane.dir, * collidable: false, * opacity: 0.75, * clippable: false, // Don't allow ImagePlane to be clipped by the SectionPlane * pickable: false // Don't allow ImagePlane to be picked * }); * ```` */ class ImagePlane extends Component { /** * @constructor * @param {Component} [owner] Owner component. When destroyed, the owner will destroy this ````ImagePlane```` as well. * @param {*} [cfg] ````ImagePlane```` configuration * @param {String} [cfg.id] Optional ID, unique among all components in the parent {@link Scene}, generated automatically when omitted. * @param {Boolean} [cfg.visible=true] Indicates whether or not this ````ImagePlane```` is visible. * @param {Boolean} [cfg.gridVisible=true] Indicates whether or not the grid is visible. Grid is only visible when ````ImagePlane```` is also visible. * @param {Number[]} [cfg.position=[0,0,0]] World-space position of the ````ImagePlane````. * @param {Number[]} [cfg.size=1] World-space size of the longest edge of the ````ImagePlane````. Note that * ````ImagePlane```` sets its aspect ratio to match its image. If we set a value of ````1000````, and the image * has size ````400x300````, then the ````ImagePlane```` will then have size ````1000 x 750````. * @param {Number[]} [cfg.rotation=[0,0,0]] Local rotation of the ````ImagePlane````, as Euler angles given in degrees, for each of the X, Y and Z axis. * @param {Number[]} [cfg.matrix=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]] Modelling transform matrix for the ````ImagePlane````. Overrides the ````position````, ````size```, ````rotation```` and ````dir```` parameters. * @param {Boolean} [cfg.collidable=true] Indicates if the ````ImagePlane```` is initially included in boundary calculations. * @param {Boolean} [cfg.clippable=true] Indicates if the ````ImagePlane```` is initially clippable. * @param {Boolean} [cfg.pickable=true] Indicates if the ````ImagePlane```` is initially pickable. * @param {Number} [cfg.opacity=1.0] ````ImagePlane````'s initial opacity factor, multiplies by the rendered fragment alpha. * @param {String} [cfg.src] URL of image. Accepted file types are PNG and JPEG. * @param {HTMLImageElement} [cfg.image] An ````HTMLImageElement```` to source the image from. Overrides ````src````. */ constructor(owner, cfg = {}) { super(owner, cfg); this._src = null; this._image = null; this._pos = math.vec3(); this._origin = math.vec3(); this._rtcPos = math.vec3(); this._dir = math.vec3(); this._size = 1.0; this._imageSize = math.vec2(); this._texture = new Texture(this); this._plane = new Mesh(this, { geometry: new ReadableGeometry(this, buildPlaneGeometry({ center: [0, 0, 0], xSize: 1, zSize: 1, xSegments: 10, zSegments: 10 })), material: new PhongMaterial(this, { diffuse: [0, 0, 0], ambient: [0, 0, 0], specular: [0, 0, 0], diffuseMap: this._texture, emissiveMap: this._texture, backfaces: true }), clippable: cfg.clippable }); this._grid = new Mesh(this, { geometry: new ReadableGeometry(this, buildGridGeometry({ size: 1, divisions: 10 })), material: new PhongMaterial(this, { diffuse: [0.0, 0.0, 0.0], ambient: [0.0, 0.0, 0.0], emissive: [0.2, 0.8, 0.2] }), position: [0, 0.001, 0.0], clippable: cfg.clippable }); this._node = new Node(this, { rotation: [0, 0, 0], position: [0, 0, 0], scale: [1, 1, 1], clippable: false, children: [ this._plane, this._grid ] }); this._gridVisible = false; this.visible = true; this.gridVisible = cfg.gridVisible; this.position = cfg.position; this.rotation = cfg.rotation; this.dir = cfg.dir; this.size = cfg.size; this.collidable = cfg.collidable; this.clippable = cfg.clippable; this.pickable = cfg.pickable; this.opacity = cfg.opacity; if (cfg.image) { this.image = cfg.image; } else { this.src = cfg.src; } } /** * Sets if this ````ImagePlane```` is visible or not. * * Default value is ````true````. * * @param {Boolean} visible Set ````true```` to make this ````ImagePlane```` visible. */ set visible(visible) { this._plane.visible = visible; this._grid.visible = (this._gridVisible && visible); } /** * Gets if this ````ImagePlane```` is visible or not. * * Default value is ````true````. * * @returns {Boolean} Returns ````true```` if visible. */ get visible() { return this._plane.visible; } /** * Sets if this ````ImagePlane````'s grid is visible or not. * * Default value is ````false````. * * Grid is only visible when ````ImagePlane```` is also visible. * * @param {Boolean} visible Set ````true```` to make this ````ImagePlane````'s grid visible. */ set gridVisible(visible) { visible = (visible !== false); this._gridVisible = visible; this._grid.visible = (this._gridVisible && this.visible); } /** * Gets if this ````ImagePlane````'s grid is visible or not. * * Default value is ````false````. * * @returns {Boolean} Returns ````true```` if visible. */ get gridVisible() { return this._gridVisible; } /** * Sets an ````HTMLImageElement```` to source the image from. * * Sets {@link Texture#src} null. * * @type {HTMLImageElement} */ set image(image) { this._image = image; if (this._image) { this._imageSize[0] = image.width; this._imageSize[1] = image.height; this._updatePlaneSizeFromImage(); this._src = null; this._texture.image = this._image; } } /** * Gets the ````HTMLImageElement```` the ````ImagePlane````'s image is sourced from, if set. * * Returns null if not set. * * @type {HTMLImageElement} */ get image() { return this._image; } /** * Sets an image file path that the ````ImagePlane````'s image is sourced from. * * Accepted file types are PNG and JPEG. * * Sets {@link Texture#image} null. * * @type {String} */ set src(src) { this._src = src; if (this._src) { this._image = null; const image = new Image(); image.onload = () => { this._texture.image = image; this._imageSize[0] = image.width; this._imageSize[1] = image.height; this._updatePlaneSizeFromImage(); }; image.src = this._src; } } /** * Gets the image file path that the ````ImagePlane````'s image is sourced from, if set. * * Returns null if not set. * * @type {String} */ get src() { return this._src; } /** * Sets the World-space position of this ````ImagePlane````. * * Default value is ````[0, 0, 0]````. * * @param {Number[]} value New position. */ set position(value) { this._pos.set(value || [0, 0, 0]); worldToRTCPos(this._pos, this._origin, this._rtcPos); this._node.origin = this._origin; this._node.position = this._rtcPos; } /** * Gets the World-space position of this ````ImagePlane````. * * Default value is ````[0, 0, 0]````. * * @returns {Number[]} Current position. */ get position() { return this._pos; } /** * Sets the direction of this ````ImagePlane```` using Euler angles. * * Default value is ````[0, 0, 0]````. * * @param {Number[]} value Euler angles for ````X````, ````Y```` and ````Z```` axis rotations. */ set rotation(value) { this._node.rotation = value; } /** * Gets the direction of this ````ImagePlane```` as Euler angles. * * @returns {Number[]} Euler angles for ````X````, ````Y```` and ````Z```` axis rotations. */ get rotation() { return this._node.rotation; } /** * Sets the World-space size of the longest edge of the ````ImagePlane````. * * Note that ````ImagePlane```` sets its aspect ratio to match its image. If we set a value of ````1000````, and * the image has size ````400x300````, then the ````ImagePlane```` will then have size ````1000 x 750````. * * Default value is ````1.0````. * * @param {Number} size New World-space size of the ````ImagePlane````. */ set size(size) { this._size = (size === undefined || size === null) ? 1.0 : size; if (this._image) { this._updatePlaneSizeFromImage() } } /** * Gets the World-space size of the longest edge of the ````ImagePlane````. * * Returns {Number} World-space size of the ````ImagePlane````. */ get size() { return this._size; } /** * Sets the direction of this ````ImagePlane```` as a direction vector. * * Default value is ````[0, 0, -1]````. * * @param {Number[]} dir New direction vector. */ set dir(dir) { this._dir.set(dir || [0, 0, -1]); if (dir) { const origin = this.scene.center; const negDir = [-this._dir[0], -this._dir[1], -this._dir[2]]; math.subVec3(origin, this.position, tempVec3); const dist = -math.dotVec3(negDir, tempVec3); math.normalizeVec3(negDir); math.mulVec3Scalar(negDir, dist, tempVec3b); math.vec3PairToQuaternion(zeroVec, dir, tempQuat); this._node.quaternion = tempQuat; } } /** * Gets the direction of this ````ImagePlane```` as a direction vector. * * @returns {Number[]} value Current direction. */ get dir() { return this._dir; } /** * Sets if this ````ImagePlane```` is included in boundary calculations. * * Default is ````true````. * * @type {Boolean} */ set collidable(value) { this._node.collidable = (value !== false); } /** * Gets if this ````ImagePlane```` is included in boundary calculations. * * Default is ````true````. * * @type {Boolean} */ get collidable() { return this._node.collidable; } /** * Sets if this ````ImagePlane```` is clippable. * * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}. * * Default is ````true````. * * @type {Boolean} */ set clippable(value) { this._node.clippable = (value !== false); } /** * Gets if this ````ImagePlane```` is clippable. * * Clipping is done by the {@link SectionPlane}s in {@link Scene#sectionPlanes}. * * Default is ````true````. * * @type {Boolean} */ get clippable() { return this._node.clippable; } /** * Sets if this ````ImagePlane```` is pickable. * * Default is ````true````. * * @type {Boolean} */ set pickable(value) { this._node.pickable = (value !== false); } /** * Gets if this ````ImagePlane```` is pickable. * * Default is ````true````. * * @type {Boolean} */ get pickable() { return this._node.pickable; } /** * Sets the opacity factor for this ````ImagePlane````. * * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas. * * @type {Number} */ set opacity(opacity) { this._node.opacity = opacity; } /** * Gets this ````ImagePlane````'s opacity factor. * * This is a factor in range ````[0..1]```` which multiplies by the rendered fragment alphas. * * @type {Number} */ get opacity() { return this._node.opacity; } /** * @destroy */ destroy() { super.destroy(); } _updatePlaneSizeFromImage() { const size = this._size; const width = this._imageSize[0]; const height = this._imageSize[1]; if (width > height) { const aspect = height / width; this._node.scale = [size, 1.0, size * aspect]; } else { const aspect = width / height; this._node.scale = [size * aspect, 1.0, size]; } } } export {ImagePlane};