import * as React from "react"; import { Observable } from "babylonjs/Misc/observable"; import { Tools } from "babylonjs/Misc/tools"; import { Vector3, TmpVectors } from "babylonjs/Maths/math.vector"; import { Color3 } from "babylonjs/Maths/math.color"; import { Mesh } from "babylonjs/Meshes/mesh"; import { VertexBuffer } from "babylonjs/Meshes/buffer"; import { LinesBuilder } from "babylonjs/Meshes/Builders/linesBuilder"; import { PhysicsImpostor } from "babylonjs/Physics/physicsImpostor"; import { Scene } from "babylonjs/scene"; import { PropertyChangedEvent } from "../../../../propertyChangedEvent"; import { LineContainerComponent } from "../../../../../sharedUiComponents/lines/lineContainerComponent"; import { TextLineComponent } from "../../../../../sharedUiComponents/lines/textLineComponent"; import { CheckBoxLineComponent } from "../../../../../sharedUiComponents/lines/checkBoxLineComponent"; import { Vector3LineComponent } from "../../../../../sharedUiComponents/lines/vector3LineComponent"; import { SliderLineComponent } from "../../../../../sharedUiComponents/lines/sliderLineComponent"; import { QuaternionLineComponent } from "../../../lines/quaternionLineComponent"; import { FloatLineComponent } from "../../../../../sharedUiComponents/lines/floatLineComponent"; import { LockObject } from "../../../../../sharedUiComponents/tabs/propertyGrids/lockObject"; import { GlobalState } from "../../../../globalState"; import { CustomPropertyGridComponent } from "../customPropertyGridComponent"; import { StandardMaterial } from "babylonjs/Materials/standardMaterial"; import { Color3LineComponent } from "../../../../../sharedUiComponents/lines/color3LineComponent"; import { MorphTarget } from "babylonjs/Morph/morphTarget"; import { OptionsLineComponent, ListLineOption } from "../../../../../sharedUiComponents/lines/optionsLineComponent"; import { AbstractMesh } from "babylonjs/Meshes/abstractMesh"; import { ButtonLineComponent } from "../../../../../sharedUiComponents/lines/buttonLineComponent"; import { TextInputLineComponent } from "../../../../../sharedUiComponents/lines/textInputLineComponent"; import { AnimationGridComponent } from "../animations/animationPropertyGridComponent"; import { RenderingManager } from "babylonjs/Rendering/renderingManager"; import { CommonPropertyGridComponent } from "../commonPropertyGridComponent"; import { VariantsPropertyGridComponent } from "../variantsPropertyGridComponent"; import { HexLineComponent } from "../../../../../sharedUiComponents/lines/hexLineComponent"; import { SkeletonViewer } from "babylonjs/Debug/skeletonViewer"; import { ShaderMaterial } from "babylonjs/Materials/shaderMaterial"; interface IMeshPropertyGridComponentProps { globalState: GlobalState; mesh: Mesh; lockObject: LockObject; onSelectionChangedObservable?: Observable; onPropertyChangedObservable?: Observable; } export class MeshPropertyGridComponent extends React.Component< IMeshPropertyGridComponentProps, { displayNormals: boolean; displayVertexColors: boolean; displayBoneWeights: boolean; displayBoneIndex: number; displaySkeletonMap: boolean; } > { constructor(props: IMeshPropertyGridComponentProps) { super(props); const mesh = this.props.mesh; this.state = { displayNormals: false, displayVertexColors: false, displayBoneWeights: !!(mesh.material && mesh.material.getClassName() === "BoneWeightShader"), displayBoneIndex: 0, displaySkeletonMap: false, }; } renderWireframeOver() { const mesh = this.props.mesh; const scene = mesh.getScene(); if (mesh.reservedDataStore && mesh.reservedDataStore.wireframeOver) { mesh.reservedDataStore.wireframeOver.dispose(false, true); mesh.reservedDataStore.wireframeOver = null; this.forceUpdate(); return; } var wireframeOver = mesh.clone()!; wireframeOver.reservedDataStore = { hidden: true }; // Sets up the mesh to be attached to the parent. // So all neutral in local space. wireframeOver.parent = mesh; wireframeOver.position = Vector3.Zero(); wireframeOver.scaling = new Vector3(1, 1, 1); wireframeOver.rotation = Vector3.Zero(); wireframeOver.rotationQuaternion = null; var material = new StandardMaterial("wireframeOver", scene); material.reservedDataStore = { hidden: true }; wireframeOver.material = material; material.zOffset = 1; material.disableLighting = true; material.backFaceCulling = false; material.emissiveColor = Color3.White(); material.wireframe = true; if (!mesh.reservedDataStore) { mesh.reservedDataStore = {}; } mesh.reservedDataStore.wireframeOver = wireframeOver; this.forceUpdate(); } renderNormalVectors() { const mesh = this.props.mesh; const scene = mesh.getScene(); if (mesh.reservedDataStore && mesh.reservedDataStore.normalLines) { mesh.reservedDataStore.normalLines.dispose(); mesh.reservedDataStore.normalLines = null; this.forceUpdate(); return; } var normals = mesh.getVerticesData(VertexBuffer.NormalKind); var positions = mesh.getVerticesData(VertexBuffer.PositionKind); const color = Color3.White(); const bbox = mesh.getBoundingInfo(); const diag = bbox.maximum.subtractToRef(bbox.minimum, TmpVectors.Vector3[0]); const size = diag.length() * 0.05; var lines = []; for (var i = 0; i < normals!.length; i += 3) { var v1 = Vector3.FromArray(positions!, i); var v2 = v1.add(Vector3.FromArray(normals!, i).scaleInPlace(size)); lines.push([v1, v2]); } var normalLines = LinesBuilder.CreateLineSystem("normalLines", { lines: lines }, scene); normalLines.color = color; normalLines.parent = mesh; normalLines.reservedDataStore = { hidden: true }; if (!mesh.reservedDataStore) { mesh.reservedDataStore = {}; } mesh.reservedDataStore.normalLines = normalLines; this.forceUpdate(); } displayNormals() { const mesh = this.props.mesh; const scene = mesh.getScene(); if (mesh.material && mesh.material.getClassName() === "NormalMaterial") { mesh.material.dispose(); mesh.material = mesh.reservedDataStore.originalMaterial; mesh.reservedDataStore.originalMaterial = null; this.setState({ displayNormals: false }); } else { if (!(BABYLON as any).NormalMaterial) { this.setState({ displayNormals: true }); Tools.LoadScript("https://preview.babylonjs.com/materialsLibrary/babylonjs.materials.js", () => { this.displayNormals(); }); return; } if (!mesh.reservedDataStore) { mesh.reservedDataStore = {}; } if (!mesh.reservedDataStore.originalMaterial) { mesh.reservedDataStore.originalMaterial = mesh.material; } const normalMaterial = new (BABYLON as any).NormalMaterial("normalMaterial", scene); normalMaterial.disableLighting = true; if (mesh.material) { normalMaterial.sideOrientation = mesh.material.sideOrientation; } normalMaterial.reservedDataStore = { hidden: true }; mesh.material = normalMaterial; this.setState({ displayNormals: true }); } } displayVertexColors() { const mesh = this.props.mesh; const scene = mesh.getScene(); if (mesh.material && mesh.material.reservedDataStore && mesh.material.reservedDataStore.isVertexColorMaterial) { mesh.material.dispose(); mesh.material = mesh.reservedDataStore.originalMaterial; mesh.reservedDataStore.originalMaterial = null; this.setState({ displayVertexColors: false }); } else { if (!mesh.reservedDataStore) { mesh.reservedDataStore = {}; } if (!mesh.reservedDataStore.originalMaterial) { mesh.reservedDataStore.originalMaterial = mesh.material; } const vertexColorMaterial = new StandardMaterial("vertex colors", scene); vertexColorMaterial.disableLighting = true; vertexColorMaterial.emissiveColor = Color3.White(); if (mesh.material) { vertexColorMaterial.sideOrientation = mesh.material.sideOrientation; } vertexColorMaterial.reservedDataStore = { hidden: true, isVertexColorMaterial: true }; mesh.useVertexColors = true; mesh.material = vertexColorMaterial; this.setState({ displayVertexColors: true }); } } displayBoneWeights() { const mesh = this.props.mesh; const scene = mesh.getScene(); if (mesh.material && mesh.material.getClassName() === "BoneWeightShader") { mesh.material.dispose(); mesh.material = mesh.reservedDataStore.originalMaterial; mesh.reservedDataStore.originalMaterial = null; this.setState({ displayBoneWeights: false }); } else { if (!mesh.reservedDataStore) { mesh.reservedDataStore = {}; } if (!mesh.reservedDataStore.originalMaterial) { mesh.reservedDataStore.originalMaterial = mesh.material; } if (!mesh.reservedDataStore.displayBoneIndex) { mesh.reservedDataStore.displayBoneIndex = this.state.displayBoneIndex; } if (mesh.skeleton) { const boneWeightsShader = SkeletonViewer.CreateBoneWeightShader({ skeleton: mesh.skeleton }, scene); boneWeightsShader.reservedDataStore = { hidden: true }; mesh.material = boneWeightsShader; this.setState({ displayBoneWeights: true }); } } } displaySkeletonMap() { const mesh = this.props.mesh; const scene = mesh.getScene(); if (mesh.material && mesh.material.getClassName() === "SkeletonMapShader") { mesh.material.dispose(); mesh.material = mesh.reservedDataStore.originalMaterial; mesh.reservedDataStore.originalMaterial = null; this.setState({ displaySkeletonMap: false }); } else { if (!mesh.reservedDataStore) { mesh.reservedDataStore = {}; } if (!mesh.reservedDataStore.originalMaterial) { mesh.reservedDataStore.originalMaterial = mesh.material; } if (mesh.skeleton) { const skeletonMapShader = SkeletonViewer.CreateSkeletonMapShader({ skeleton: mesh.skeleton }, scene); skeletonMapShader.reservedDataStore = { hidden: true }; mesh.material = skeletonMapShader; this.setState({ displaySkeletonMap: true }); } } } onBoneDisplayIndexChange(value: number): void { let mesh = this.props.mesh; mesh.reservedDataStore.displayBoneIndex = value; this.setState({ displayBoneIndex: value }); if (mesh.material && mesh.material.getClassName() === "BoneWeightShader") { (mesh.material as ShaderMaterial).setFloat("targetBoneIndex", value); } } onMaterialLink() { if (!this.props.onSelectionChangedObservable) { return; } const mesh = this.props.mesh; this.props.onSelectionChangedObservable.notifyObservers(mesh.material); } onSourceMeshLink() { if (!this.props.onSelectionChangedObservable) { return; } const instanceMesh = this.props.mesh as any; this.props.onSelectionChangedObservable.notifyObservers(instanceMesh.sourceMesh); } onSkeletonLink() { if (!this.props.onSelectionChangedObservable) { return; } const mesh = this.props.mesh; this.props.onSelectionChangedObservable.notifyObservers(mesh.skeleton); } convertPhysicsTypeToString(): string { const mesh = this.props.mesh; switch (mesh.physicsImpostor!.type) { case PhysicsImpostor.NoImpostor: return "No impostor"; case PhysicsImpostor.SphereImpostor: return "Sphere"; case PhysicsImpostor.BoxImpostor: return "Box"; case PhysicsImpostor.PlaneImpostor: return "Plane"; case PhysicsImpostor.MeshImpostor: return "Mesh"; case PhysicsImpostor.CylinderImpostor: return "Cylinder"; case PhysicsImpostor.ParticleImpostor: return "Particle"; case PhysicsImpostor.HeightmapImpostor: return "Heightmap"; case PhysicsImpostor.ConvexHullImpostor: return "Convex hull"; case PhysicsImpostor.RopeImpostor: return "Rope"; case PhysicsImpostor.SoftbodyImpostor: return "Soft body"; } return "Unknown"; } render() { const mesh = this.props.mesh; const scene = mesh.getScene(); const displayNormals = mesh.material != null && mesh.material.getClassName() === "NormalMaterial"; const displayVertexColors = !!(mesh.material != null && mesh.material.reservedDataStore && mesh.material.reservedDataStore.isVertexColorMaterial); const renderNormalVectors = mesh.reservedDataStore && mesh.reservedDataStore.normalLines ? true : false; const renderWireframeOver = mesh.reservedDataStore && mesh.reservedDataStore.wireframeOver ? true : false; const displayBoneWeights = mesh.material != null && mesh.material.getClassName() === "BoneWeightShader"; const displaySkeletonMap = mesh.material != null && mesh.material.getClassName() === "SkeletonMapShader"; var morphTargets: MorphTarget[] = []; if (mesh.morphTargetManager) { for (var index = 0; index < mesh.morphTargetManager.numTargets; index++) { morphTargets.push(mesh.morphTargetManager.getTarget(index)); } } var algorithmOptions = [ { label: "Accurate", value: AbstractMesh.OCCLUSION_ALGORITHM_TYPE_ACCURATE }, { label: "Conservative", value: AbstractMesh.OCCLUSION_ALGORITHM_TYPE_CONSERVATIVE }, ]; var occlusionTypeOptions = [ { label: "None", value: AbstractMesh.OCCLUSION_TYPE_NONE }, { label: "Optimistic", value: AbstractMesh.OCCLUSION_TYPE_OPTIMISTIC }, { label: "Strict", value: AbstractMesh.OCCLUSION_TYPE_STRICT }, ]; let sortedMaterials = scene.materials.slice(0).sort((a, b) => (a.name || "no name").localeCompare(b.name || "no name")); const materialOptions = sortedMaterials.map((m, i) => { return { label: m.name || "no name", value: i, }; }); materialOptions.splice(0, 0, { label: "None (Default Fallback)", value: -1, }); const targetBoneOptions: ListLineOption[] = mesh.skeleton ? mesh.skeleton.bones.filter((bone) => bone.getIndex() >= 0).sort((bone1, bone2) => bone1.getIndex() - bone2.getIndex()).map((bone, idx) => { return { label: bone.name, value: bone.getIndex(), }; }) : []; return (
{mesh.parent && this.props.globalState.onSelectionChangedObservable.notifyObservers(mesh.parent)} />} {mesh.skeleton && this.onSkeletonLink()} />} mesh.isEnabled()} onSelect={(value) => mesh.setEnabled(value)} /> {mesh.material && (!mesh.material.reservedDataStore || !mesh.material.reservedDataStore.hidden) && this.onMaterialLink()} />} {!mesh.isAnInstance && ( { if (value < 0) { mesh.material = null; } else { mesh.material = sortedMaterials[value]; } this.forceUpdate(); }} extractValue={() => (mesh.material ? sortedMaterials.indexOf(mesh.material) : -1)} onPropertyChangedObservable={this.props.onPropertyChangedObservable} /> )} {mesh.isAnInstance && this.onSourceMeshLink()} />} { mesh.dispose(); this.props.globalState.onSelectionChangedObservable.notifyObservers(null); }} /> {!mesh.rotationQuaternion && } {mesh.rotationQuaternion && } {!mesh.isAnInstance && } {mesh.isVerticesDataPresent(VertexBuffer.ColorKind) && } {mesh.isVerticesDataPresent(VertexBuffer.ColorKind) && } {scene.fogMode !== Scene.FOGMODE_NONE && } {!mesh.parent && } {mesh.morphTargetManager != null && ( {morphTargets.map((mt, i) => { return ; })} )} {mesh.useBones && } {mesh.physicsImpostor != null && ( )} mesh.edgesRenderer != null} onSelect={(value) => { if (value) { mesh.enableEdgesRendering(); } else { mesh.disableEdgesRendering(); } }} /> {!mesh.isAnInstance && ( )} {!mesh.isAnInstance && displayNormals} onSelect={() => this.displayNormals()} />} {!mesh.isAnInstance && displayVertexColors} onSelect={() => this.displayVertexColors()} />} {mesh.isVerticesDataPresent(VertexBuffer.NormalKind) && renderNormalVectors} onSelect={() => this.renderNormalVectors()} />} {!mesh.isAnInstance && renderWireframeOver} onSelect={() => this.renderWireframeOver()} />} {!mesh.isAnInstance && mesh.skeleton && displayBoneWeights} onSelect={() => this.displayBoneWeights()} />} {!mesh.isAnInstance && this.state.displayBoneWeights && mesh.skeleton && ( { this.onBoneDisplayIndexChange(value); this.forceUpdate(); }} /> )} {!mesh.isAnInstance && this.state.displayBoneWeights && mesh.skeleton && ( { this.onBoneDisplayIndexChange(value); this.forceUpdate(); }} /> )} {!mesh.isAnInstance && mesh.skeleton && displaySkeletonMap} onSelect={() => this.displaySkeletonMap()} />}
); } }