UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

1,211 lines (1,209 loc) 104 kB
import { __decorate } from "../tslib.es6.js"; import { Observable } from "../Misc/observable.js"; import { Quaternion, Matrix, Vector3, TmpVectors } from "../Maths/math.vector.js"; import { VertexBuffer } from "../Buffers/buffer.js"; import { VertexData } from "../Meshes/mesh.vertexData.js"; import { TransformNode } from "../Meshes/transformNode.js"; import { PickingInfo } from "../Collisions/pickingInfo.js"; import { BoundingInfo } from "../Culling/boundingInfo.js"; import { UniformBuffer } from "../Materials/uniformBuffer.js"; import { _MeshCollisionData } from "../Collisions/meshCollisionData.js"; import { _WarnImport } from "../Misc/devTools.js"; import { extractMinAndMax } from "../Maths/math.functions.js"; import { Color3, Color4 } from "../Maths/math.color.js"; import { Epsilon } from "../Maths/math.constants.js"; import { Axis } from "../Maths/math.axis.js"; import { RegisterClass } from "../Misc/typeStore.js"; import { nativeOverride } from "../Misc/decorators.js"; import { AbstractEngine } from "../Engines/abstractEngine.js"; function ApplyMorph(data, kind, morphTargetManager) { let getTargetData = null; switch (kind) { case VertexBuffer.PositionKind: getTargetData = (target) => target.getPositions(); break; case VertexBuffer.NormalKind: getTargetData = (target) => target.getNormals(); break; case VertexBuffer.TangentKind: getTargetData = (target) => target.getTangents(); break; case VertexBuffer.UVKind: getTargetData = (target) => target.getUVs(); break; case VertexBuffer.UV2Kind: getTargetData = (target) => target.getUV2s(); break; case VertexBuffer.ColorKind: getTargetData = (target) => target.getColors(); break; default: return; } for (let index = 0; index < data.length; index++) { let value = data[index]; for (let targetCount = 0; targetCount < morphTargetManager.numTargets; targetCount++) { const target = morphTargetManager.getTarget(targetCount); const influence = target.influence; if (influence !== 0) { const targetData = getTargetData(target); if (targetData) { value += (targetData[index] - data[index]) * influence; } } } data[index] = value; } } function ApplySkeleton(data, kind, skeletonMatrices, matricesIndicesData, matricesWeightsData, matricesIndicesExtraData, matricesWeightsExtraData) { const tempVector = TmpVectors.Vector3[0]; const finalMatrix = TmpVectors.Matrix[0]; const tempMatrix = TmpVectors.Matrix[1]; const transformFromFloatsToRef = kind === VertexBuffer.NormalKind ? Vector3.TransformNormalFromFloatsToRef : Vector3.TransformCoordinatesFromFloatsToRef; for (let index = 0, matWeightIdx = 0; index < data.length; index += 3, matWeightIdx += 4) { finalMatrix.reset(); let inf; let weight; for (inf = 0; inf < 4; inf++) { weight = matricesWeightsData[matWeightIdx + inf]; if (weight > 0) { Matrix.FromFloat32ArrayToRefScaled(skeletonMatrices, Math.floor(matricesIndicesData[matWeightIdx + inf] * 16), weight, tempMatrix); finalMatrix.addToSelf(tempMatrix); } } if (matricesIndicesExtraData && matricesWeightsExtraData) { for (inf = 0; inf < 4; inf++) { weight = matricesWeightsExtraData[matWeightIdx + inf]; if (weight > 0) { Matrix.FromFloat32ArrayToRefScaled(skeletonMatrices, Math.floor(matricesIndicesExtraData[matWeightIdx + inf] * 16), weight, tempMatrix); finalMatrix.addToSelf(tempMatrix); } } } transformFromFloatsToRef(data[index], data[index + 1], data[index + 2], finalMatrix, tempVector); tempVector.toArray(data, index); } } /** @internal */ // eslint-disable-next-line @typescript-eslint/naming-convention class _FacetDataStorage { constructor() { this.facetNb = 0; // facet number this.partitioningSubdivisions = 10; // number of subdivisions per axis in the partitioning space // eslint-disable-next-line @typescript-eslint/naming-convention this.partitioningBBoxRatio = 1.01; // the partitioning array space is by default 1% bigger than the bounding box this.facetDataEnabled = false; // is the facet data feature enabled on this mesh ? this.facetParameters = {}; // keep a reference to the object parameters to avoid memory re-allocation this.bbSize = Vector3.Zero(); // bbox size approximated for facet data this.subDiv = { // actual number of subdivisions per axis for ComputeNormals() max: 1, // eslint-disable-next-line @typescript-eslint/naming-convention X: 1, // eslint-disable-next-line @typescript-eslint/naming-convention Y: 1, // eslint-disable-next-line @typescript-eslint/naming-convention Z: 1, }; this.facetDepthSort = false; // is the facet depth sort to be computed this.facetDepthSortEnabled = false; // is the facet depth sort initialized } } /** * @internal **/ // eslint-disable-next-line @typescript-eslint/naming-convention class _InternalAbstractMeshDataInfo { constructor() { this._hasVertexAlpha = false; this._useVertexColors = true; this._numBoneInfluencers = 4; this._applyFog = true; this._receiveShadows = false; this._facetData = new _FacetDataStorage(); this._visibility = 1.0; this._skeleton = null; this._layerMask = 0x0fffffff; this._computeBonesUsingShaders = true; this._isActive = false; this._onlyForInstances = false; this._isActiveIntermediate = false; this._onlyForInstancesIntermediate = false; this._actAsRegularMesh = false; this._currentLOD = new Map(); this._collisionRetryCount = 3; this._morphTargetManager = null; this._renderingGroupId = 0; this._bakedVertexAnimationManager = null; this._material = null; this._positions = null; this._pointerOverDisableMeshTesting = false; // Collisions this._meshCollisionData = new _MeshCollisionData(); this._enableDistantPicking = false; /** @internal * Bounding info that is unnafected by the addition of thin instances */ this._rawBoundingInfo = null; /** @internal * This value will indicate us that at some point, the mesh was specifically used with the opposite winding order * We use that as a clue to force the material to sideOrientation = null */ this._sideOrientationHint = false; /** * @internal * if this is set to true, the mesh will be visible only if its parent(s) are also visible */ this._inheritVisibility = false; } } /** * Class used to store all common mesh properties */ export class AbstractMesh extends TransformNode { /** * No billboard */ static get BILLBOARDMODE_NONE() { return TransformNode.BILLBOARDMODE_NONE; } /** Billboard on X axis */ static get BILLBOARDMODE_X() { return TransformNode.BILLBOARDMODE_X; } /** Billboard on Y axis */ static get BILLBOARDMODE_Y() { return TransformNode.BILLBOARDMODE_Y; } /** Billboard on Z axis */ static get BILLBOARDMODE_Z() { return TransformNode.BILLBOARDMODE_Z; } /** Billboard on all axes */ static get BILLBOARDMODE_ALL() { return TransformNode.BILLBOARDMODE_ALL; } /** Billboard on using position instead of orientation */ static get BILLBOARDMODE_USE_POSITION() { return TransformNode.BILLBOARDMODE_USE_POSITION; } /** * Gets the number of facets in the mesh * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/facetData#what-is-a-mesh-facet */ get facetNb() { return this._internalAbstractMeshDataInfo._facetData.facetNb; } /** * Gets or set the number (integer) of subdivisions per axis in the partitioning space * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/facetData#tweaking-the-partitioning */ get partitioningSubdivisions() { return this._internalAbstractMeshDataInfo._facetData.partitioningSubdivisions; } set partitioningSubdivisions(nb) { this._internalAbstractMeshDataInfo._facetData.partitioningSubdivisions = nb; } /** * The ratio (float) to apply to the bounding box size to set to the partitioning space. * Ex : 1.01 (default) the partitioning space is 1% bigger than the bounding box * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/facetData#tweaking-the-partitioning */ // eslint-disable-next-line @typescript-eslint/naming-convention get partitioningBBoxRatio() { return this._internalAbstractMeshDataInfo._facetData.partitioningBBoxRatio; } // eslint-disable-next-line @typescript-eslint/naming-convention set partitioningBBoxRatio(ratio) { this._internalAbstractMeshDataInfo._facetData.partitioningBBoxRatio = ratio; } /** * Gets or sets a boolean indicating that the facets must be depth sorted on next call to `updateFacetData()`. * Works only for updatable meshes. * Doesn't work with multi-materials * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/facetData#facet-depth-sort */ get mustDepthSortFacets() { return this._internalAbstractMeshDataInfo._facetData.facetDepthSort; } set mustDepthSortFacets(sort) { this._internalAbstractMeshDataInfo._facetData.facetDepthSort = sort; } /** * The location (Vector3) where the facet depth sort must be computed from. * By default, the active camera position. * Used only when facet depth sort is enabled * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/facetData#facet-depth-sort */ get facetDepthSortFrom() { return this._internalAbstractMeshDataInfo._facetData.facetDepthSortFrom; } set facetDepthSortFrom(location) { this._internalAbstractMeshDataInfo._facetData.facetDepthSortFrom = location; } /** number of collision detection tries. Change this value if not all collisions are detected and handled properly */ get collisionRetryCount() { return this._internalAbstractMeshDataInfo._collisionRetryCount; } set collisionRetryCount(retryCount) { this._internalAbstractMeshDataInfo._collisionRetryCount = retryCount; } /** * gets a boolean indicating if facetData is enabled * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/facetData#what-is-a-mesh-facet */ get isFacetDataEnabled() { return this._internalAbstractMeshDataInfo._facetData.facetDataEnabled; } /** * Gets or sets the morph target manager * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/morphTargets */ get morphTargetManager() { return this._internalAbstractMeshDataInfo._morphTargetManager; } set morphTargetManager(value) { if (this._internalAbstractMeshDataInfo._morphTargetManager === value) { return; } this._internalAbstractMeshDataInfo._morphTargetManager = value; this._syncGeometryWithMorphTargetManager(); } /** * Gets or sets the baked vertex animation manager * @see https://doc.babylonjs.com/features/featuresDeepDive/animation/baked_texture_animations */ get bakedVertexAnimationManager() { return this._internalAbstractMeshDataInfo._bakedVertexAnimationManager; } set bakedVertexAnimationManager(value) { if (this._internalAbstractMeshDataInfo._bakedVertexAnimationManager === value) { return; } this._internalAbstractMeshDataInfo._bakedVertexAnimationManager = value; this._markSubMeshesAsAttributesDirty(); } /** @internal */ _syncGeometryWithMorphTargetManager() { } /** * @internal */ _updateNonUniformScalingState(value) { if (!super._updateNonUniformScalingState(value)) { return false; } this._markSubMeshesAsMiscDirty(); return true; } /** @internal */ get rawBoundingInfo() { return this._internalAbstractMeshDataInfo._rawBoundingInfo; } set rawBoundingInfo(boundingInfo) { this._internalAbstractMeshDataInfo._rawBoundingInfo = boundingInfo; } /** Set a function to call when this mesh collides with another one */ set onCollide(callback) { if (this._internalAbstractMeshDataInfo._meshCollisionData._onCollideObserver) { this.onCollideObservable.remove(this._internalAbstractMeshDataInfo._meshCollisionData._onCollideObserver); } this._internalAbstractMeshDataInfo._meshCollisionData._onCollideObserver = this.onCollideObservable.add(callback); } /** Set a function to call when the collision's position changes */ set onCollisionPositionChange(callback) { if (this._internalAbstractMeshDataInfo._meshCollisionData._onCollisionPositionChangeObserver) { this.onCollisionPositionChangeObservable.remove(this._internalAbstractMeshDataInfo._meshCollisionData._onCollisionPositionChangeObserver); } this._internalAbstractMeshDataInfo._meshCollisionData._onCollisionPositionChangeObserver = this.onCollisionPositionChangeObservable.add(callback); } /** * Gets or sets mesh visibility between 0 and 1 (default is 1) */ get visibility() { return this._internalAbstractMeshDataInfo._visibility; } /** * Gets or sets mesh visibility between 0 and 1 (default is 1) */ set visibility(value) { if (this._internalAbstractMeshDataInfo._visibility === value) { return; } const oldValue = this._internalAbstractMeshDataInfo._visibility; this._internalAbstractMeshDataInfo._visibility = value; if ((oldValue === 1 && value !== 1) || (oldValue !== 1 && value === 1)) { this._markSubMeshesAsDirty((defines) => { defines.markAsMiscDirty(); defines.markAsPrePassDirty(); }); } } /** * If set to true, a mesh will only be visible only if its parent(s) are also visible (default is false) */ get inheritVisibility() { return this._internalAbstractMeshDataInfo._inheritVisibility; } set inheritVisibility(value) { this._internalAbstractMeshDataInfo._inheritVisibility = value; } /** * Gets or sets a boolean indicating if the mesh is visible (renderable). Default is true */ get isVisible() { if (!this._isVisible || !this.inheritVisibility || !this._parentNode) { return this._isVisible; } if (this._isVisible) { let parent = this._parentNode; while (parent) { const parentVisible = parent.isVisible; if (typeof parentVisible !== "undefined") { return parentVisible; } parent = parent.parent; } } return this._isVisible; } set isVisible(value) { this._isVisible = value; } /** * Gets or sets the property which disables the test that is checking that the mesh under the pointer is the same than the previous time we tested for it (default: false). * Set this property to true if you want thin instances picking to be reported accurately when moving over the mesh. * Note that setting this property to true will incur some performance penalties when dealing with pointer events for this mesh so use it sparingly. */ get pointerOverDisableMeshTesting() { return this._internalAbstractMeshDataInfo._pointerOverDisableMeshTesting; } set pointerOverDisableMeshTesting(disable) { this._internalAbstractMeshDataInfo._pointerOverDisableMeshTesting = disable; } /** * Specifies the rendering group id for this mesh (0 by default) * @see https://doc.babylonjs.com/features/featuresDeepDive/materials/advanced/transparent_rendering#rendering-groups */ get renderingGroupId() { return this._internalAbstractMeshDataInfo._renderingGroupId; } set renderingGroupId(value) { this._internalAbstractMeshDataInfo._renderingGroupId = value; } /** Gets or sets current material */ get material() { return this._internalAbstractMeshDataInfo._material; } set material(value) { this._setMaterial(value); } /** @internal */ _setMaterial(value) { if (this._internalAbstractMeshDataInfo._material === value) { return; } // remove from material mesh map id needed if (this._internalAbstractMeshDataInfo._material && this._internalAbstractMeshDataInfo._material.meshMap) { this._internalAbstractMeshDataInfo._material.meshMap[this.uniqueId] = undefined; } this._internalAbstractMeshDataInfo._material = value; if (value && value.meshMap) { value.meshMap[this.uniqueId] = this; } if (this.onMaterialChangedObservable.hasObservers()) { this.onMaterialChangedObservable.notifyObservers(this); } if (!this.subMeshes) { return; } this.resetDrawCache(undefined, value == null); this._unBindEffect(); } /** * Gets the material used to render the mesh in a specific render pass * @param renderPassId render pass id * @returns material used for the render pass. If no specific material is used for this render pass, undefined is returned (meaning mesh.material is used for this pass) */ getMaterialForRenderPass(renderPassId) { return this._internalAbstractMeshDataInfo._materialForRenderPass?.[renderPassId]; } /** * Sets the material to be used to render the mesh in a specific render pass * @param renderPassId render pass id * @param material material to use for this render pass. If undefined is passed, no specific material will be used for this render pass but the regular material will be used instead (mesh.material) */ setMaterialForRenderPass(renderPassId, material) { this.resetDrawCache(renderPassId); if (!this._internalAbstractMeshDataInfo._materialForRenderPass) { this._internalAbstractMeshDataInfo._materialForRenderPass = []; } const currentMaterial = this._internalAbstractMeshDataInfo._materialForRenderPass[renderPassId]; if (currentMaterial?.meshMap?.[this.uniqueId]) { currentMaterial.meshMap[this.uniqueId] = undefined; } this._internalAbstractMeshDataInfo._materialForRenderPass[renderPassId] = material; if (material && material.meshMap) { material.meshMap[this.uniqueId] = this; } } /** * Gets or sets a boolean indicating that this mesh can receive realtime shadows * @see https://doc.babylonjs.com/features/featuresDeepDive/lights/shadows */ get receiveShadows() { return this._internalAbstractMeshDataInfo._receiveShadows; } set receiveShadows(value) { if (this._internalAbstractMeshDataInfo._receiveShadows === value) { return; } this._internalAbstractMeshDataInfo._receiveShadows = value; this._markSubMeshesAsLightDirty(); } /** * Gets or sets a boolean indicating that this mesh needs to use vertex alpha data to render. * This property is misnamed and should be `useVertexAlpha`. Note that the mesh will be rendered * with alpha blending when this flag is set even if vertex alpha data is missing from the geometry. */ get hasVertexAlpha() { return this._internalAbstractMeshDataInfo._hasVertexAlpha; } set hasVertexAlpha(value) { if (this._internalAbstractMeshDataInfo._hasVertexAlpha === value) { return; } this._internalAbstractMeshDataInfo._hasVertexAlpha = value; this._markSubMeshesAsAttributesDirty(); this._markSubMeshesAsMiscDirty(); } /** Gets or sets a boolean indicating that this mesh needs to use vertex color data to render (if this kind of vertex data is available in the geometry) */ get useVertexColors() { return this._internalAbstractMeshDataInfo._useVertexColors; } set useVertexColors(value) { if (this._internalAbstractMeshDataInfo._useVertexColors === value) { return; } this._internalAbstractMeshDataInfo._useVertexColors = value; this._markSubMeshesAsAttributesDirty(); } /** * Gets or sets a boolean indicating that bone animations must be computed by the GPU (true by default) */ get computeBonesUsingShaders() { return this._internalAbstractMeshDataInfo._computeBonesUsingShaders; } set computeBonesUsingShaders(value) { if (this._internalAbstractMeshDataInfo._computeBonesUsingShaders === value) { return; } this._internalAbstractMeshDataInfo._computeBonesUsingShaders = value; this._markSubMeshesAsAttributesDirty(); } /** Gets or sets the number of allowed bone influences per vertex (4 by default) */ get numBoneInfluencers() { return this._internalAbstractMeshDataInfo._numBoneInfluencers; } set numBoneInfluencers(value) { if (this._internalAbstractMeshDataInfo._numBoneInfluencers === value) { return; } this._internalAbstractMeshDataInfo._numBoneInfluencers = value; this._markSubMeshesAsAttributesDirty(); } /** Gets or sets a boolean indicating that this mesh will allow fog to be rendered on it (true by default) */ get applyFog() { return this._internalAbstractMeshDataInfo._applyFog; } set applyFog(value) { if (this._internalAbstractMeshDataInfo._applyFog === value) { return; } this._internalAbstractMeshDataInfo._applyFog = value; this._markSubMeshesAsMiscDirty(); } /** When enabled, decompose picking matrices for better precision with large values for mesh position and scling */ get enableDistantPicking() { return this._internalAbstractMeshDataInfo._enableDistantPicking; } set enableDistantPicking(value) { this._internalAbstractMeshDataInfo._enableDistantPicking = value; } /** * Gets or sets the current layer mask (default is 0x0FFFFFFF) * @see https://doc.babylonjs.com/features/featuresDeepDive/cameras/layerMasksAndMultiCam */ get layerMask() { return this._internalAbstractMeshDataInfo._layerMask; } set layerMask(value) { if (value === this._internalAbstractMeshDataInfo._layerMask) { return; } this._internalAbstractMeshDataInfo._layerMask = value; this._resyncLightSources(); } /** * Gets or sets a collision mask used to mask collisions (default is -1). * A collision between A and B will happen if A.collisionGroup & b.collisionMask !== 0 */ get collisionMask() { return this._internalAbstractMeshDataInfo._meshCollisionData._collisionMask; } set collisionMask(mask) { this._internalAbstractMeshDataInfo._meshCollisionData._collisionMask = !isNaN(mask) ? mask : -1; } /** * Gets or sets a collision response flag (default is true). * when collisionResponse is false, events are still triggered but colliding entity has no response * This helps creating trigger volume when user wants collision feedback events but not position/velocity * to respond to the collision. */ get collisionResponse() { return this._internalAbstractMeshDataInfo._meshCollisionData._collisionResponse; } set collisionResponse(response) { this._internalAbstractMeshDataInfo._meshCollisionData._collisionResponse = response; } /** * Gets or sets the current collision group mask (-1 by default). * A collision between A and B will happen if A.collisionGroup & b.collisionMask !== 0 */ get collisionGroup() { return this._internalAbstractMeshDataInfo._meshCollisionData._collisionGroup; } set collisionGroup(mask) { this._internalAbstractMeshDataInfo._meshCollisionData._collisionGroup = !isNaN(mask) ? mask : -1; } /** * Gets or sets current surrounding meshes (null by default). * * By default collision detection is tested against every mesh in the scene. * It is possible to set surroundingMeshes to a defined list of meshes and then only these specified * meshes will be tested for the collision. * * Note: if set to an empty array no collision will happen when this mesh is moved. */ get surroundingMeshes() { return this._internalAbstractMeshDataInfo._meshCollisionData._surroundingMeshes; } set surroundingMeshes(meshes) { this._internalAbstractMeshDataInfo._meshCollisionData._surroundingMeshes = meshes; } /** Gets the list of lights affecting that mesh */ get lightSources() { return this._lightSources; } /** * Gets or sets a skeleton to apply skinning transformations * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/bonesSkeletons */ set skeleton(value) { const skeleton = this._internalAbstractMeshDataInfo._skeleton; if (skeleton && skeleton.needInitialSkinMatrix) { skeleton._unregisterMeshWithPoseMatrix(this); } if (value && value.needInitialSkinMatrix) { value._registerMeshWithPoseMatrix(this); } this._internalAbstractMeshDataInfo._skeleton = value; if (!this._internalAbstractMeshDataInfo._skeleton) { this._bonesTransformMatrices = null; } this._markSubMeshesAsAttributesDirty(); } get skeleton() { return this._internalAbstractMeshDataInfo._skeleton; } // Constructor /** * Creates a new AbstractMesh * @param name defines the name of the mesh * @param scene defines the hosting scene */ constructor(name, scene = null) { super(name, scene, false); // Internal data /** @internal */ this._internalAbstractMeshDataInfo = new _InternalAbstractMeshDataInfo(); /** @internal */ this._waitingMaterialId = null; /** @internal */ this._waitingMorphTargetManagerId = null; /** * The culling strategy to use to check whether the mesh must be rendered or not. * This value can be changed at any time and will be used on the next render mesh selection. * The possible values are : * - AbstractMesh.CULLINGSTRATEGY_STANDARD * - AbstractMesh.CULLINGSTRATEGY_BOUNDINGSPHERE_ONLY * - AbstractMesh.CULLINGSTRATEGY_OPTIMISTIC_INCLUSION * - AbstractMesh.CULLINGSTRATEGY_OPTIMISTIC_INCLUSION_THEN_BSPHERE_ONLY * Please read each static variable documentation to get details about the culling process. * */ this.cullingStrategy = AbstractMesh.CULLINGSTRATEGY_BOUNDINGSPHERE_ONLY; // Events /** * An event triggered when this mesh collides with another one */ this.onCollideObservable = new Observable(); /** * An event triggered when the collision's position changes */ this.onCollisionPositionChangeObservable = new Observable(); /** * An event triggered when material is changed */ this.onMaterialChangedObservable = new Observable(); // Properties /** * Gets or sets the orientation for POV movement & rotation */ this.definedFacingForward = true; /** @internal */ this._occlusionQuery = null; /** @internal */ this._renderingGroup = null; /** Gets or sets the alpha index used to sort transparent meshes * @see https://doc.babylonjs.com/features/featuresDeepDive/materials/advanced/transparent_rendering#alpha-index */ this.alphaIndex = Number.MAX_VALUE; this._isVisible = true; /** * Gets or sets a boolean indicating if the mesh can be picked (by scene.pick for instance or through actions). Default is true */ this.isPickable = true; /** * Gets or sets a boolean indicating if the mesh can be near picked (touched by the XR controller or hands). Default is false */ this.isNearPickable = false; /** * Gets or sets a boolean indicating if the mesh can be grabbed. Default is false. * Setting this to true, while using the XR near interaction feature, will trigger a pointer event when the mesh is grabbed. * Grabbing means that the controller is using the squeeze or main trigger button to grab the mesh. * This is different from nearPickable which only triggers the event when the mesh is touched by the controller */ this.isNearGrabbable = false; /** Gets or sets a boolean indicating that bounding boxes of subMeshes must be rendered as well (false by default) */ this.showSubMeshesBoundingBox = false; /** Gets or sets a boolean indicating if the mesh must be considered as a ray blocker for lens flares (false by default) * @see https://doc.babylonjs.com/features/featuresDeepDive/environment/lenseFlare */ this.isBlocker = false; /** * Gets or sets a boolean indicating that pointer move events must be supported on this mesh (false by default) */ this.enablePointerMoveEvents = false; /** Defines color to use when rendering outline */ this.outlineColor = Color3.Red(); /** Define width to use when rendering outline */ this.outlineWidth = 0.02; /** Defines color to use when rendering overlay */ this.overlayColor = Color3.Red(); /** Defines alpha to use when rendering overlay */ this.overlayAlpha = 0.5; /** Gets or sets a boolean indicating that internal octree (if available) can be used to boost submeshes selection (true by default) */ this.useOctreeForRenderingSelection = true; /** Gets or sets a boolean indicating that internal octree (if available) can be used to boost submeshes picking (true by default) */ this.useOctreeForPicking = true; /** Gets or sets a boolean indicating that internal octree (if available) can be used to boost submeshes collision (true by default) */ this.useOctreeForCollisions = true; /** * True if the mesh must be rendered in any case (this will shortcut the frustum clipping phase) */ this.alwaysSelectAsActiveMesh = false; /** * Gets or sets a boolean indicating that the bounding info does not need to be kept in sync (for performance reason) */ this.doNotSyncBoundingInfo = false; /** * Gets or sets the current action manager * @see https://doc.babylonjs.com/features/featuresDeepDive/events/actions */ this.actionManager = null; /** * Gets or sets the ellipsoid used to impersonate this mesh when using collision engine (default is (0.5, 1, 0.5)) * @see https://doc.babylonjs.com/features/featuresDeepDive/cameras/camera_collisions */ this.ellipsoid = new Vector3(0.5, 1, 0.5); /** * Gets or sets the ellipsoid offset used to impersonate this mesh when using collision engine (default is (0, 0, 0)) * @see https://doc.babylonjs.com/features/featuresDeepDive/cameras/camera_collisions */ this.ellipsoidOffset = new Vector3(0, 0, 0); // Edges /** * Defines edge width used when edgesRenderer is enabled * @see https://www.babylonjs-playground.com/#10OJSG#13 */ this.edgesWidth = 1; /** * Defines edge color used when edgesRenderer is enabled * @see https://www.babylonjs-playground.com/#10OJSG#13 */ this.edgesColor = new Color4(1, 0, 0, 1); /** @internal */ this._edgesRenderer = null; /** @internal */ this._masterMesh = null; this._boundingInfo = null; this._boundingInfoIsDirty = true; /** @internal */ this._renderId = 0; /** @internal */ this._intersectionsInProgress = new Array(); /** @internal */ this._unIndexed = false; /** @internal */ this._lightSources = new Array(); // Loading properties /** @internal */ this._waitingData = { lods: null, actions: null, freezeWorldMatrix: null, }; /** @internal */ this._bonesTransformMatrices = null; /** @internal */ this._transformMatrixTexture = null; /** * An event triggered when the mesh is rebuilt. */ this.onRebuildObservable = new Observable(); this._onCollisionPositionChange = (collisionId, newPosition, collidedMesh = null) => { newPosition.subtractToRef(this._internalAbstractMeshDataInfo._meshCollisionData._oldPositionForCollisions, this._internalAbstractMeshDataInfo._meshCollisionData._diffPositionForCollisions); if (this._internalAbstractMeshDataInfo._meshCollisionData._diffPositionForCollisions.length() > AbstractEngine.CollisionsEpsilon) { this.position.addInPlace(this._internalAbstractMeshDataInfo._meshCollisionData._diffPositionForCollisions); } if (collidedMesh) { this.onCollideObservable.notifyObservers(collidedMesh); } this.onCollisionPositionChangeObservable.notifyObservers(this.position); }; scene = this.getScene(); scene.addMesh(this); this._resyncLightSources(); // Mesh Uniform Buffer. this._uniformBuffer = new UniformBuffer(this.getScene().getEngine(), undefined, undefined, name, !this.getScene().getEngine().isWebGPU); this._buildUniformLayout(); switch (scene.performancePriority) { case 2 /* ScenePerformancePriority.Aggressive */: this.doNotSyncBoundingInfo = true; // eslint-disable-next-line no-fallthrough case 1 /* ScenePerformancePriority.Intermediate */: this.alwaysSelectAsActiveMesh = true; this.isPickable = false; break; } } _buildUniformLayout() { this._uniformBuffer.addUniform("world", 16); this._uniformBuffer.addUniform("visibility", 1); this._uniformBuffer.create(); } /** * Transfer the mesh values to its UBO. * @param world The world matrix associated with the mesh */ transferToEffect(world) { const ubo = this._uniformBuffer; ubo.updateMatrix("world", world); ubo.updateFloat("visibility", this._internalAbstractMeshDataInfo._visibility); ubo.update(); } /** * Gets the mesh uniform buffer. * @returns the uniform buffer of the mesh. */ getMeshUniformBuffer() { return this._uniformBuffer; } /** * Returns the string "AbstractMesh" * @returns "AbstractMesh" */ getClassName() { return "AbstractMesh"; } /** * Gets a string representation of the current mesh * @param fullDetails defines a boolean indicating if full details must be included * @returns a string representation of the current mesh */ toString(fullDetails) { let ret = "Name: " + this.name + ", isInstance: " + (this.getClassName() === "InstancedMesh" ? "YES" : "NO"); ret += ", # of submeshes: " + (this.subMeshes ? this.subMeshes.length : 0); const skeleton = this._internalAbstractMeshDataInfo._skeleton; if (skeleton) { ret += ", skeleton: " + skeleton.name; } if (fullDetails) { ret += ", billboard mode: " + ["NONE", "X", "Y", null, "Z", null, null, "ALL"][this.billboardMode]; ret += ", freeze wrld mat: " + (this._isWorldMatrixFrozen || this._waitingData.freezeWorldMatrix ? "YES" : "NO"); } return ret; } /** * @internal */ _getEffectiveParent() { if (this._masterMesh && this.billboardMode !== TransformNode.BILLBOARDMODE_NONE) { return this._masterMesh; } return super._getEffectiveParent(); } /** * @internal */ _getActionManagerForTrigger(trigger, initialCall = true) { if (this.actionManager && (initialCall || this.actionManager.isRecursive)) { if (trigger) { if (this.actionManager.hasSpecificTrigger(trigger)) { return this.actionManager; } } else { return this.actionManager; } } if (!this.parent) { return null; } return this.parent._getActionManagerForTrigger(trigger, false); } /** * @internal */ // eslint-disable-next-line @typescript-eslint/no-unused-vars _rebuild(dispose = false) { this.onRebuildObservable.notifyObservers(this); if (this._occlusionQuery !== null) { this._occlusionQuery = null; } if (!this.subMeshes) { return; } for (const subMesh of this.subMeshes) { subMesh._rebuild(); } this.resetDrawCache(); } /** @internal */ _resyncLightSources() { this._lightSources.length = 0; for (const light of this.getScene().lights) { if (!light.isEnabled()) { continue; } if (light.canAffectMesh(this)) { this._lightSources.push(light); } } this._markSubMeshesAsLightDirty(); } /** * @internal */ _resyncLightSource(light) { const isIn = light.isEnabled() && light.canAffectMesh(this); const index = this._lightSources.indexOf(light); let removed = false; if (index === -1) { if (!isIn) { return; } this._lightSources.push(light); } else { if (isIn) { return; } removed = true; this._lightSources.splice(index, 1); } this._markSubMeshesAsLightDirty(removed); } /** @internal */ _unBindEffect() { for (const subMesh of this.subMeshes) { subMesh.setEffect(null); } } /** * @internal */ _removeLightSource(light, dispose) { const index = this._lightSources.indexOf(light); if (index === -1) { return; } this._lightSources.splice(index, 1); this._markSubMeshesAsLightDirty(dispose); } _markSubMeshesAsDirty(func) { if (!this.subMeshes) { return; } for (const subMesh of this.subMeshes) { for (let i = 0; i < subMesh._drawWrappers.length; ++i) { const drawWrapper = subMesh._drawWrappers[i]; if (!drawWrapper || !drawWrapper.defines || !drawWrapper.defines.markAllAsDirty) { continue; } func(drawWrapper.defines); } } } /** * @internal */ _markSubMeshesAsLightDirty(dispose = false) { this._markSubMeshesAsDirty((defines) => defines.markAsLightDirty(dispose)); } /** @internal */ _markSubMeshesAsAttributesDirty() { this._markSubMeshesAsDirty((defines) => defines.markAsAttributesDirty()); } /** @internal */ _markSubMeshesAsMiscDirty() { this._markSubMeshesAsDirty((defines) => defines.markAsMiscDirty()); } /** * Flag the AbstractMesh as dirty (Forcing it to update everything) * @param property if set to "rotation" the objects rotationQuaternion will be set to null * @returns this AbstractMesh */ // eslint-disable-next-line @typescript-eslint/no-unused-vars markAsDirty(property) { this._currentRenderId = Number.MAX_VALUE; super.markAsDirty(property); this._isDirty = true; return this; } /** * Resets the draw wrappers cache for all submeshes of this abstract mesh * @param passId If provided, releases only the draw wrapper corresponding to this render pass id * @param immediate If true, the effect will be released immediately, otherwise it will be released at the next frame */ resetDrawCache(passId, immediate = false) { if (!this.subMeshes) { return; } for (const subMesh of this.subMeshes) { subMesh.resetDrawCache(passId, immediate); } } // Methods /** * Returns true if the mesh is blocked. Implemented by child classes */ get isBlocked() { return false; } /** * Returns the mesh itself by default. Implemented by child classes * @param camera defines the camera to use to pick the right LOD level * @returns the currentAbstractMesh */ // eslint-disable-next-line @typescript-eslint/no-unused-vars getLOD(camera) { return this; } /** * Returns 0 by default. Implemented by child classes * @returns an integer */ getTotalVertices() { return 0; } /** * Returns a positive integer : the total number of indices in this mesh geometry. * @returns the number of indices or zero if the mesh has no geometry. */ getTotalIndices() { return 0; } /** * Returns null by default. Implemented by child classes * @returns null */ getIndices() { return null; } /** * Returns the array of the requested vertex data kind. Implemented by child classes * @param kind defines the vertex data kind to use * @returns null */ // eslint-disable-next-line @typescript-eslint/no-unused-vars getVerticesData(kind) { return null; } /** * Sets the vertex data of the mesh geometry for the requested `kind`. * If the mesh has no geometry, a new Geometry object is set to the mesh and then passed this vertex data. * Note that a new underlying VertexBuffer object is created each call. * If the `kind` is the `PositionKind`, the mesh BoundingInfo is renewed, so the bounding box and sphere, and the mesh World Matrix is recomputed. * @param kind defines vertex data kind: * * VertexBuffer.PositionKind * * VertexBuffer.UVKind * * VertexBuffer.UV2Kind * * VertexBuffer.UV3Kind * * VertexBuffer.UV4Kind * * VertexBuffer.UV5Kind * * VertexBuffer.UV6Kind * * VertexBuffer.ColorKind * * VertexBuffer.MatricesIndicesKind * * VertexBuffer.MatricesIndicesExtraKind * * VertexBuffer.MatricesWeightsKind * * VertexBuffer.MatricesWeightsExtraKind * @param data defines the data source * @param updatable defines if the data must be flagged as updatable (or static) * @param stride defines the vertex stride (size of an entire vertex). Can be null and in this case will be deduced from vertex data kind * @returns the current mesh */ // eslint-disable-next-line @typescript-eslint/no-unused-vars setVerticesData(kind, data, updatable, stride) { return this; } /** * Updates the existing vertex data of the mesh geometry for the requested `kind`. * If the mesh has no geometry, it is simply returned as it is. * @param kind defines vertex data kind: * * VertexBuffer.PositionKind * * VertexBuffer.UVKind * * VertexBuffer.UV2Kind * * VertexBuffer.UV3Kind * * VertexBuffer.UV4Kind * * VertexBuffer.UV5Kind * * VertexBuffer.UV6Kind * * VertexBuffer.ColorKind * * VertexBuffer.MatricesIndicesKind * * VertexBuffer.MatricesIndicesExtraKind * * VertexBuffer.MatricesWeightsKind * * VertexBuffer.MatricesWeightsExtraKind * @param data defines the data source * @param updateExtends If `kind` is `PositionKind` and if `updateExtends` is true, the mesh BoundingInfo is renewed, so the bounding box and sphere, and the mesh World Matrix is recomputed * @param makeItUnique If true, a new global geometry is created from this data and is set to the mesh * @returns the current mesh */ // eslint-disable-next-line @typescript-eslint/no-unused-vars updateVerticesData(kind, data, updateExtends, makeItUnique) { return this; } /** * Sets the mesh indices, * If the mesh has no geometry, a new Geometry object is created and set to the mesh. * @param indices Expects an array populated with integers or a typed array (Int32Array, Uint32Array, Uint16Array) * @param totalVertices Defines the total number of vertices * @returns the current mesh */ // eslint-disable-next-line @typescript-eslint/no-unused-vars setIndices(indices, totalVertices) { return this; } /** * Gets a boolean indicating if specific vertex data is present * @param kind defines the vertex data kind to use * @returns true is data kind is present */ // eslint-disable-next-line @typescript-eslint/no-unused-vars isVerticesDataPresent(kind) { return false; } /** * Returns the mesh BoundingInfo object or creates a new one and returns if it was undefined. * Note that it returns a shallow bounding of the mesh (i.e. it does not include children). * However, if the mesh contains thin instances, it will be expanded to include them. If you want the "raw" bounding data instead, then use `getRawBoundingInfo()`. * To get the full bounding of all children, call `getHierarchyBoundingVectors` instead. * @returns a BoundingInfo */ getBoundingInfo() { if (this._masterMesh) { return this._masterMesh.getBoundingInfo(); } if (this._boundingInfoIsDirty) { this._boundingInfoIsDirty = false; // this._boundingInfo is being created if undefined this._updateBoundingInfo(); } // cannot be null. return this._boundingInfo; } /** * Returns the bounding info unnafected by instance data. * @returns the bounding info of the mesh unaffected by instance data. */ getRawBoundingInfo() { return this.rawBoundingInfo ?? this.getBoundingInfo(); } /** * Overwrite the current bounding info * @param boundingInfo defines the new bounding info * @returns the current mesh */ setBoundingInfo(boundingInfo) { this._boundingInfo = boundingInfo; return this; } /** * Returns true if there is already a bounding info */ get hasBoundingInfo() { return this._boundingInfo !== null; } /** * Creates a new bounding info for the mesh * @param minimum min vector of the bounding box/sphere * @param maximum max vector of the bounding box/sphere * @param worldMatrix defines the new world matrix * @returns the new bounding info */ buildBoundingInfo(minimum, maximum, worldMatrix) { this._boundingInfo = new BoundingInfo(minimum, maximum, worldMatrix); return this._boundingInfo; } /** * Uniformly scales the mesh to fit inside of a unit cube (1 X 1 X 1 units) * @param includeDescendants Use the hierarchy's bounding box instead of the mesh's bounding box. Default is false * @param ignoreRotation ignore rotation when computing the scale (ie. object will be axis aligned). Default is false * @param predicate predicate that is passed in to getHierarchyBoundingVectors when selecting which object should be included when scaling * @returns the current mesh */ normalizeToUnitCube(includeDescendants = true, ignoreRotation = false, predicate) { return super.normalizeToUnitCube(includeDescendants, ignoreRotation, predicate); } /** Gets a boolean indicating if this mesh has skinning data and an attached skeleton */ get useBones() { return ((this.skeleton && this.getScene().skeletonsEnabled && this.isVerticesDataPresent(VertexBuffer.MatricesIndicesKind) && this.isVerticesDataPresent(VertexBuffer.MatricesWeightsKind))); } /** @internal */ _preActivate() { } /** * @internal */ // eslint-disable-next-line @typescript-eslint/no-unused-vars _preActivateForIntermediateRendering(renderId) { } /** * @internal */ // eslint-disable-next-line @typescript-eslint/no-unused-vars _activate(renderId, intermediateRendering) { this._renderId = renderId; return true; } /** @internal */ _postActivate() { // Do nothing } /** @internal */ _freeze() { // Do nothing } /** @internal */ _unFreeze() { // Do nothing }