@gnsx/three 0.185.16 → 0.186.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/three.cjs +10 -82600
- package/build/three.core.js +1303 -728
- package/build/three.module.js +439 -338
- package/build/three.tsl.js +74 -44
- package/build/three.webgpu.js +6036 -2184
- package/build/three.webgpu.nodes.js +6020 -2184
- package/examples/jsm/Addons.js +6 -6
- package/examples/jsm/controls/ArcballControls.js +1 -1
- package/examples/jsm/controls/FirstPersonControls.js +44 -20
- package/examples/jsm/controls/OrbitControls.js +9 -0
- package/examples/jsm/controls/TrackballControls.js +109 -4
- package/examples/jsm/csm/CSMShadowNode.js +13 -0
- package/examples/jsm/exporters/GLTFExporter.js +23 -7
- package/examples/jsm/exporters/USDZExporter.js +2 -2
- package/examples/jsm/generators/CityGenerator.js +377 -45
- package/examples/jsm/generators/ForestGenerator.js +12 -12
- package/examples/jsm/generators/TerrainGenerator.js +49 -37
- package/examples/jsm/generators/TreeGenerator.js +70 -76
- package/examples/jsm/generators/city/BenchGenerator.js +127 -0
- package/examples/jsm/generators/city/CarGenerator.js +479 -0
- package/examples/jsm/generators/city/CityGeneratorUtils.js +31 -0
- package/examples/jsm/generators/city/HydrantGenerator.js +106 -0
- package/examples/jsm/generators/city/InstancedMeshGenerator.js +86 -0
- package/examples/jsm/generators/city/PersonGenerator.js +447 -0
- package/examples/jsm/generators/city/SidewalkGenerator.js +33 -38
- package/examples/jsm/generators/city/SkyscraperGenerator.js +448 -48
- package/examples/jsm/generators/city/StreetTreeGenerator.js +231 -0
- package/examples/jsm/generators/city/StreetlightGenerator.js +100 -0
- package/examples/jsm/generators/city/TrafficlightGenerator.js +106 -0
- package/examples/jsm/generators/city/TrashcanGenerator.js +100 -0
- package/examples/jsm/geometries/BoxLineGeometry.js +1 -1
- package/examples/jsm/geometries/ParametricGeometry.js +1 -1
- package/examples/jsm/geometries/RoundedBoxGeometry.js +1 -1
- package/examples/jsm/gpgpu/CountingSort.js +271 -0
- package/examples/jsm/helpers/LightProbeGridHelper.js +60 -239
- package/examples/jsm/helpers/LightProbeGridHelperWebGL.js +221 -0
- package/examples/jsm/inspector/Extension.js +94 -0
- package/examples/jsm/inspector/Inspector.js +145 -41
- package/examples/jsm/inspector/RendererInspector.js +100 -52
- package/examples/jsm/inspector/extensions/color-grading/ColorGrading.js +2609 -0
- package/examples/jsm/inspector/extensions/color-grading/ColorWheel.js +352 -0
- package/examples/jsm/inspector/extensions/color-grading/CurveEditor.js +599 -0
- package/examples/jsm/inspector/extensions/color-grading/LUT3DStyle.js +1201 -0
- package/examples/jsm/inspector/extensions/color-grading/LUTMath.js +788 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/BrightnessModule.js +72 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/ColorWheelModule.js +112 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/ContrastModule.js +82 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/CurvesModule.js +109 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/ExposureHueModule.js +110 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/ExposureModule.js +73 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/HueModule.js +78 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/ImportedCubeModule.js +89 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/Module.js +285 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/OutputModule.js +101 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/RendererModule.js +225 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/SatVibranceModule.js +102 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/SaturationModule.js +76 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/VibranceModule.js +76 -0
- package/examples/jsm/inspector/extensions/color-grading/modules/WhiteBalanceModule.js +86 -0
- package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphEditor.js +26 -31
- package/examples/jsm/inspector/extensions/tsl-graph/TSLGraphLoader.js +1 -0
- package/examples/jsm/inspector/tabs/Memory.js +0 -2
- package/examples/jsm/inspector/tabs/Settings.js +4 -0
- package/examples/jsm/inspector/tabs/Timeline.js +223 -171
- package/examples/jsm/inspector/tabs/Viewer.js +515 -21
- package/examples/jsm/inspector/ui/List.js +15 -9
- package/examples/jsm/inspector/ui/Profiler.js +170 -14
- package/examples/jsm/inspector/ui/Style.js +269 -23
- package/examples/jsm/inspector/ui/Tab.js +2 -0
- package/examples/jsm/inspector/ui/Values.js +14 -2
- package/examples/jsm/interactive/HTMLMesh.js +19 -3
- package/examples/jsm/lighting/LightProbeGrid.js +484 -889
- package/examples/jsm/lighting/LightProbeGridUtils.js +107 -0
- package/examples/jsm/lighting/LightProbeGridWebGL.js +841 -0
- package/examples/jsm/lighting/vxgi/VXGIConeTracer.js +194 -0
- package/examples/jsm/lighting/vxgi/VXGINode.js +660 -0
- package/examples/jsm/lighting/vxgi/VXGISceneCollector.js +478 -0
- package/examples/jsm/lighting/vxgi/VXGIVolume.js +1430 -0
- package/examples/jsm/lights/SunLight.js +95 -0
- package/examples/jsm/lights/SunLightNode.js +56 -0
- package/examples/jsm/lights/SunLightShadow.js +319 -0
- package/examples/jsm/lights/SunShadowNode.js +312 -0
- package/examples/jsm/loaders/3DMLoader.js +150 -49
- package/examples/jsm/loaders/3MFLoader.js +202 -1
- package/examples/jsm/loaders/FBXLoader.js +81 -5
- package/examples/jsm/loaders/GLTFGaussianSplatLoaderExtension.js +330 -0
- package/examples/jsm/loaders/GLTFLoader.js +79 -14
- package/examples/jsm/loaders/GaussianSplatPLYLoader.js +386 -0
- package/examples/jsm/loaders/KSPLATLoader.js +463 -0
- package/examples/jsm/loaders/MaterialXLoader.js +111 -1010
- package/examples/jsm/loaders/PCDLoader.js +1 -1
- package/examples/jsm/loaders/SPLATLoader.js +139 -0
- package/examples/jsm/loaders/SPZLoader.js +561 -0
- package/examples/jsm/loaders/materialx/MaterialXArchive.js +108 -0
- package/examples/jsm/loaders/materialx/MaterialXDocument.js +878 -0
- package/examples/jsm/loaders/materialx/MaterialXHextile.js +183 -0
- package/examples/jsm/loaders/materialx/MaterialXInterfaceValidation.js +376 -0
- package/examples/jsm/loaders/materialx/MaterialXLog.js +75 -0
- package/examples/jsm/loaders/materialx/MaterialXNodeInterfaceRegistry.js +4 -0
- package/examples/jsm/loaders/materialx/MaterialXNodeLibrary.js +1021 -0
- package/examples/jsm/loaders/materialx/MaterialXSurfaceMappings.js +661 -0
- package/examples/jsm/loaders/materialx/MaterialXUtils.js +57 -0
- package/examples/jsm/loaders/materialx/compile/MaterialXCompileRegistry.js +997 -0
- package/examples/jsm/loaders/materialx/parse/MaterialXParser.js +28 -0
- package/examples/jsm/loaders/usd/USDAParser.js +15 -2
- package/examples/jsm/loaders/usd/USDCParser.js +1 -4
- package/examples/jsm/loaders/usd/USDComposer.js +80 -42
- package/examples/jsm/materials/WoodNodeMaterial.js +6 -108
- package/examples/jsm/misc/RollerCoaster.js +2 -2
- package/examples/jsm/misc/SculptGL.LICENSE.txt +21 -0
- package/examples/jsm/misc/Sculptor.js +1679 -0
- package/examples/jsm/misc/SculptorMesh.js +1504 -0
- package/examples/jsm/misc/SculptorTools.js +1180 -0
- package/examples/jsm/misc/SculptorUtils.js +345 -0
- package/examples/jsm/misc/TubePainter.js +28 -16
- package/examples/jsm/misc/VolumeSlice.js +24 -24
- package/examples/jsm/modifiers/SimplifyModifier.js +63 -556
- package/examples/jsm/objects/GaussianSplat.js +1039 -0
- package/examples/jsm/objects/Sky.js +66 -45
- package/examples/jsm/objects/SkyMesh.js +70 -51
- package/examples/jsm/physics/JoltPhysics.js +1 -1
- package/examples/jsm/postprocessing/AfterimagePass.js +4 -2
- package/examples/jsm/postprocessing/BloomPass.js +2 -2
- package/examples/jsm/postprocessing/GTAOPass.js +1 -1
- package/examples/jsm/postprocessing/OutlinePass.js +5 -5
- package/examples/jsm/postprocessing/SAOPass.js +1 -1
- package/examples/jsm/postprocessing/SSAOPass.js +1 -1
- package/examples/jsm/postprocessing/SSRPass.js +4 -2
- package/examples/jsm/postprocessing/SavePass.js +1 -1
- package/examples/jsm/postprocessing/TAARenderPass.js +1 -1
- package/examples/jsm/postprocessing/UnrealBloomPass.js +30 -13
- package/examples/jsm/renderers/CSS2DRenderer.js +29 -5
- package/examples/jsm/shaders/DepthLimitedBlurShader.js +4 -1
- package/examples/jsm/shaders/ExposureShader.js +1 -1
- package/examples/jsm/shaders/FilmShader.js +2 -1
- package/examples/jsm/transpiler/AST.js +21 -2
- package/examples/jsm/transpiler/GLSLDecoder.js +498 -8
- package/examples/jsm/transpiler/TSLEncoder.js +82 -10
- package/examples/jsm/transpiler/WGSLEncoder.js +33 -4
- package/examples/jsm/tsl/WebGLNodesHandler.js +29 -39
- package/examples/jsm/tsl/display/AfterImageNode.js +2 -0
- package/examples/jsm/tsl/display/AnaglyphPassNode.js +3 -2
- package/examples/jsm/tsl/display/BilateralBlurNode.js +5 -2
- package/examples/jsm/tsl/display/BloomNode.js +55 -30
- package/examples/jsm/tsl/display/DenoiseNode.js +21 -1
- package/examples/jsm/tsl/display/DepthOfFieldNode.js +24 -6
- package/examples/jsm/tsl/display/DotScreenNode.js +11 -11
- package/examples/jsm/tsl/display/FSR1Node.js +8 -4
- package/examples/jsm/tsl/display/GTAONode.js +199 -86
- package/examples/jsm/tsl/display/GaussianBlurNode.js +65 -52
- package/examples/jsm/tsl/display/GodraysNode.js +5 -2
- package/examples/jsm/tsl/display/ImportanceSampledEnvironment.js +2 -2
- package/examples/jsm/tsl/display/LensflareNode.js +5 -2
- package/examples/jsm/tsl/display/OITPassNode.js +352 -0
- package/examples/jsm/tsl/display/OutlineNode.js +2 -0
- package/examples/jsm/tsl/display/ParallaxBarrierPassNode.js +3 -2
- package/examples/jsm/tsl/display/RGBShiftNode.js +9 -9
- package/examples/jsm/tsl/display/RecurrentDenoiseNode.js +7 -4
- package/examples/jsm/tsl/display/RetroPassNode.js +1 -1
- package/examples/jsm/tsl/display/SMAANode.js +22 -61
- package/examples/jsm/tsl/display/SSAAPassNode.js +2 -4
- package/examples/jsm/tsl/display/SSAONode.js +436 -0
- package/examples/jsm/tsl/display/SSGINode.js +5 -2
- package/examples/jsm/tsl/display/SSRNode.js +7 -2
- package/examples/jsm/tsl/display/SSSNode.js +5 -2
- package/examples/jsm/tsl/display/SharpenNode.js +5 -4
- package/examples/jsm/tsl/display/StereoCompositePassNode.js +1 -3
- package/examples/jsm/tsl/display/StereoPassNode.js +1 -3
- package/examples/jsm/tsl/display/TAAUNode.js +22 -125
- package/examples/jsm/tsl/display/TRAANode.js +16 -142
- package/examples/jsm/tsl/display/TemporalReprojectNode.js +21 -15
- package/examples/jsm/tsl/display/depthAwareBlur.js +63 -0
- package/examples/jsm/tsl/display/hashBlur.js +3 -2
- package/examples/jsm/tsl/lighting/ClusteredLightsNode.js +38 -27
- package/examples/jsm/tsl/lighting/LightProbeGridNode.js +136 -0
- package/examples/jsm/tsl/math/voronoiNoise.js +112 -0
- package/examples/jsm/tsl/shadows/TileShadowNode.js +4 -6
- package/examples/jsm/tsl/utils/Raymarching.js +5 -5
- package/examples/jsm/tsl/utils/SoftParticles.js +60 -0
- package/examples/jsm/tsl/utils/TAAUtils.js +195 -0
- package/examples/jsm/utils/BufferGeometryUtils.js +14 -14
- package/examples/jsm/utils/GaussianSplatUtils.js +229 -0
- package/examples/jsm/utils/WebGLTextureUtils.js +1 -1
- package/examples/jsm/utils/WebGPUTextureUtils.js +1 -1
- package/examples/jsm/webxr/WebGLXRFallback.js +2 -16
- package/examples/jsm/webxr/XRControllerModelFactory.js +16 -4
- package/package.json +9 -16
- package/src/Three.Core.js +2 -1
- package/src/Three.TSL.js +73 -43
- package/src/Three.WebGPU.Nodes.js +1 -0
- package/src/Three.WebGPU.js +1 -0
- package/src/animation/AnimationClip.js +15 -2
- package/src/animation/AnimationObjectGroup.js +18 -6
- package/src/animation/AnimationUtils.js +26 -0
- package/src/animation/KeyframeTrack.js +37 -0
- package/src/constants.js +23 -1
- package/src/core/BufferAttribute.js +3 -2
- package/src/core/BufferGeometry.js +1 -1
- package/src/core/InterleavedBuffer.js +5 -1
- package/src/core/Object3D.js +40 -12
- package/src/core/Raycaster.js +4 -0
- package/src/core/RenderTarget.js +99 -9
- package/src/core/RenderTarget3D.js +12 -8
- package/src/extras/Controls.js +0 -8
- package/src/extras/PMREMGenerator.js +99 -205
- package/src/geometries/TorusGeometry.js +1 -1
- package/src/helpers/ArrowHelper.js +2 -0
- package/src/helpers/AxesHelper.js +2 -0
- package/src/helpers/Box3Helper.js +2 -0
- package/src/helpers/BoxHelper.js +2 -0
- package/src/helpers/CameraHelper.js +2 -0
- package/src/helpers/DirectionalLightHelper.js +2 -0
- package/src/helpers/GridHelper.js +2 -0
- package/src/helpers/HemisphereLightHelper.js +2 -0
- package/src/helpers/PlaneHelper.js +2 -0
- package/src/helpers/PointLightHelper.js +2 -0
- package/src/helpers/PolarGridHelper.js +2 -0
- package/src/helpers/SkeletonHelper.js +2 -0
- package/src/helpers/SpotLightHelper.js +2 -0
- package/src/lights/Light.js +0 -10
- package/src/lights/LightShadow.js +43 -12
- package/src/lights/SpotLightShadow.js +18 -0
- package/src/loaders/BufferGeometryLoader.js +5 -0
- package/src/loaders/ImageBitmapLoader.js +3 -1
- package/src/loaders/ObjectLoader.js +8 -5
- package/src/materials/Material.js +80 -57
- package/src/materials/MeshPhysicalMaterial.js +32 -0
- package/src/materials/nodes/Line2NodeMaterial.js +17 -8
- package/src/materials/nodes/MeshPhysicalNodeMaterial.js +42 -5
- package/src/materials/nodes/NodeMaterial.js +19 -13
- package/src/materials/nodes/PointsNodeMaterial.js +2 -1
- package/src/materials/nodes/manager/NodeMaterialObserver.js +282 -55
- package/src/math/Frustum.js +9 -0
- package/src/math/FrustumArray.js +1 -1
- package/src/math/MathUtils.js +5 -3
- package/src/math/Matrix4.js +2 -9
- package/src/math/Plane.js +30 -1
- package/src/math/Quaternion.js +0 -10
- package/src/math/Ray.js +95 -45
- package/src/math/Triangle.js +0 -2
- package/src/math/Vector3.js +2 -2
- package/src/math/Vector4.js +0 -4
- package/src/math/interpolants/BezierInterpolant.js +36 -23
- package/src/nodes/Nodes.js +2 -0
- package/src/nodes/TSL.js +11 -3
- package/src/nodes/accessors/Batch.js +85 -13
- package/src/nodes/accessors/BufferAttributeNode.js +1 -1
- package/src/nodes/accessors/BuiltinNode.js +1 -1
- package/src/nodes/accessors/Instance.js +19 -18
- package/src/nodes/accessors/MaterialNode.js +9 -0
- package/src/nodes/accessors/ModelNode.js +85 -9
- package/src/nodes/accessors/ReferenceBaseNode.js +1 -70
- package/src/nodes/accessors/ReferenceElementNode.js +72 -0
- package/src/nodes/accessors/ReferenceNode.js +1 -70
- package/src/nodes/accessors/Skinning.js +124 -12
- package/src/nodes/accessors/StorageTextureNode.js +7 -3
- package/src/nodes/accessors/Texture3DNode.js +1 -1
- package/src/nodes/accessors/TextureNode.js +51 -21
- package/src/nodes/accessors/TextureSizeNode.js +4 -2
- package/src/nodes/accessors/UniformArrayNode.js +30 -5
- package/src/nodes/code/CodeNode.js +1 -1
- package/src/nodes/code/FunctionNode.js +12 -2
- package/src/nodes/core/ArrayNode.js +2 -2
- package/src/nodes/core/ContextNode.js +57 -4
- package/src/nodes/core/IndexNode.js +69 -4
- package/src/nodes/core/InspectorNode.js +2 -2
- package/src/nodes/core/MRTNode.js +44 -2
- package/src/nodes/core/Node.js +6 -3
- package/src/nodes/core/NodeBuilder.js +65 -50
- package/src/nodes/core/NodeFrame.js +11 -3
- package/src/nodes/core/NodeUtils.js +88 -0
- package/src/nodes/core/PropertyNode.js +8 -0
- package/src/nodes/core/StackNode.js +0 -1
- package/src/nodes/core/StructNode.js +4 -3
- package/src/nodes/core/UniformGroupNode.js +3 -2
- package/src/nodes/core/UniformNode.js +26 -12
- package/src/nodes/core/VarNode.js +0 -1
- package/src/nodes/display/BumpMapNode.js +2 -2
- package/src/nodes/display/ColorAdjustment.js +1 -1
- package/src/nodes/display/PassNode.js +76 -15
- package/src/nodes/display/ScreenNode.js +22 -65
- package/src/nodes/fog/Fog.js +4 -4
- package/src/nodes/functions/BSDF/BRDF_GGX.js +6 -6
- package/src/nodes/functions/BSDF/BRDF_Sheen.js +1 -1
- package/src/nodes/functions/BSDF/DFGLUT.js +2 -1
- package/src/nodes/functions/BSDF/LTC.js +1 -1
- package/src/nodes/functions/PhongLightingModel.js +1 -1
- package/src/nodes/functions/PhysicalLightingModel.js +83 -25
- package/src/nodes/functions/ToonLightingModel.js +2 -2
- package/src/nodes/functions/material/getParallaxCorrectNormal.js +3 -3
- package/src/nodes/gpgpu/AtomicFunctionNode.js +64 -13
- package/src/nodes/gpgpu/BarrierNode.js +13 -4
- package/src/nodes/gpgpu/ComputeBuiltinNode.js +1 -1
- package/src/nodes/gpgpu/ComputeNode.js +20 -17
- package/src/nodes/gpgpu/SubgroupFunctionNode.js +165 -37
- package/src/nodes/gpgpu/WorkgroupInfoNode.js +44 -6
- package/src/nodes/lighting/EnvironmentNode.js +40 -4
- package/src/nodes/lighting/LightsNode.js +20 -4
- package/src/nodes/lighting/ShadowBaseNode.js +158 -1
- package/src/nodes/lighting/ShadowFilterNode.js +2 -106
- package/src/nodes/lighting/ShadowNode.js +22 -91
- package/src/nodes/materialx/{lib/mx_hsv.js → MaterialXColor.js} +3 -6
- package/src/nodes/materialx/{lib/mx_transform_color.js → MaterialXColorTransform.js} +3 -6
- package/src/nodes/materialx/MaterialXCore.js +37 -0
- package/src/nodes/materialx/MaterialXNodes.js +64 -72
- package/src/nodes/materialx/{lib/mx_noise.js → MaterialXNoise.js} +426 -197
- package/src/nodes/math/BitcountNode.js +24 -4
- package/src/nodes/math/MathNode.js +4 -4
- package/src/nodes/math/MathUtils.js +15 -4
- package/src/nodes/math/OperatorNode.js +2 -2
- package/src/nodes/math/PackFloatNode.js +29 -1
- package/src/nodes/math/Packed4x8IntegerNode.js +433 -0
- package/src/nodes/math/UnpackFloatNode.js +30 -2
- package/src/nodes/parsers/GLSLNodeFunction.js +1 -1
- package/src/nodes/pmrem/PMREMNode.js +1 -1
- package/src/nodes/pmrem/PMREMUtils.js +31 -74
- package/src/nodes/shapes/Shapes.js +1 -1
- package/src/nodes/tsl/TSLCore.js +1 -31
- package/src/nodes/utils/EventNode.js +75 -0
- package/src/nodes/utils/LoopNode.js +54 -13
- package/src/nodes/utils/PostProcessingUtils.js +23 -2
- package/src/nodes/utils/RTTNode.js +97 -32
- package/src/nodes/utils/ReflectorNode.js +8 -4
- package/src/nodes/utils/RotateNode.js +85 -8
- package/src/nodes/utils/SpriteUtils.js +58 -23
- package/src/objects/BatchedMesh.js +6 -0
- package/src/objects/InstancedMesh.js +1 -1
- package/src/objects/LOD.js +1 -1
- package/src/objects/Line.js +12 -0
- package/src/objects/Mesh.js +12 -0
- package/src/objects/Points.js +12 -0
- package/src/objects/Sprite.js +12 -0
- package/src/renderers/WebGLRenderer.js +93 -27
- package/src/renderers/common/Animation.js +12 -2
- package/src/renderers/common/Backend.js +38 -10
- package/src/renderers/common/BindGroup.js +0 -1
- package/src/renderers/common/Bindings.js +50 -4
- package/src/renderers/common/BundleGroup.js +4 -0
- package/src/renderers/common/ClippingContext.js +11 -1
- package/src/renderers/common/Color4.js +9 -0
- package/src/renderers/common/Constants.js +0 -5
- package/src/renderers/common/DirectRenderPipeline.js +233 -0
- package/src/renderers/common/Geometries.js +24 -3
- package/src/renderers/common/Info.js +3 -3
- package/src/renderers/common/InspectorBase.js +37 -8
- package/src/renderers/common/Lighting.js +11 -4
- package/src/renderers/common/Pipelines.js +6 -4
- package/src/renderers/common/QuadMesh.js +16 -2
- package/src/renderers/common/RenderContext.js +8 -0
- package/src/renderers/common/RenderList.js +53 -19
- package/src/renderers/common/RenderLists.js +58 -11
- package/src/renderers/common/RenderObject.js +14 -0
- package/src/renderers/common/RenderObjects.js +14 -9
- package/src/renderers/common/RenderPipeline.js +52 -44
- package/src/renderers/common/Renderer.js +226 -55
- package/src/renderers/common/Storage3DTexture.js +16 -0
- package/src/renderers/common/Textures.js +3 -3
- package/src/renderers/common/XRManager.js +79 -72
- package/src/renderers/common/extras/PMREMGenerator.js +76 -144
- package/src/renderers/common/nodes/NodeManager.js +144 -6
- package/src/renderers/common/nodes/NodeStorageBuffer.js +3 -1
- package/src/renderers/common/nodes/NodeUniformBuffer.js +3 -1
- package/src/renderers/shaders/DFGLUTData.js +2 -1
- package/src/renderers/shaders/ShaderChunk/defaultnormal_vertex.glsl.js +4 -2
- package/src/renderers/shaders/ShaderChunk/envmap_physical_pars_fragment.glsl.js +48 -0
- package/src/renderers/shaders/ShaderChunk/lights_fragment_begin.glsl.js +53 -4
- package/src/renderers/shaders/ShaderChunk/lights_fragment_maps.glsl.js +20 -2
- package/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js +20 -0
- package/src/renderers/shaders/ShaderChunk/lights_physical_fragment.glsl.js +6 -0
- package/src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js +61 -57
- package/src/renderers/shaders/ShaderChunk/shadowmap_pars_fragment.glsl.js +82 -0
- package/src/renderers/shaders/ShaderChunk/shadowmap_pars_vertex.glsl.js +9 -0
- package/src/renderers/shaders/ShaderChunk/shadowmap_vertex.glsl.js +8 -1
- package/src/renderers/shaders/ShaderChunk/shadowmask_pars_fragment.glsl.js +15 -0
- package/src/renderers/shaders/ShaderLib/meshphysical.glsl.js +4 -0
- package/src/renderers/shaders/ShaderLib.js +1 -0
- package/src/renderers/shaders/UniformsLib.js +16 -0
- package/src/renderers/webgl/WebGLAnimation.js +2 -2
- package/src/renderers/webgl/WebGLCapabilities.js +2 -2
- package/src/renderers/webgl/WebGLLights.js +74 -4
- package/src/renderers/webgl/WebGLMaterials.js +6 -0
- package/src/renderers/webgl/WebGLOutput.js +36 -24
- package/src/renderers/webgl/WebGLProgram.js +3 -0
- package/src/renderers/webgl/WebGLPrograms.js +8 -0
- package/src/renderers/webgl/WebGLRenderLists.js +6 -10
- package/src/renderers/webgl/WebGLShadowMap.js +47 -44
- package/src/renderers/webgl/WebGLTextures.js +7 -7
- package/src/renderers/webgl-fallback/WebGLBackend.js +153 -45
- package/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +83 -29
- package/src/renderers/webgl-fallback/utils/WebGLState.js +163 -17
- package/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +32 -5
- package/src/renderers/webgl-fallback/utils/WebGLTimestampQueryPool.js +11 -4
- package/src/renderers/webgpu/WebGPUBackend.js +513 -145
- package/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +99 -20
- package/src/renderers/webgpu/nodes/WGSLNodeFunction.js +2 -1
- package/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +3 -12
- package/src/renderers/webgpu/utils/WebGPUBindingUtils.js +24 -14
- package/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +80 -14
- package/src/renderers/webgpu/utils/WebGPUTextureUtils.js +62 -7
- package/src/renderers/webgpu/utils/WebGPUTimestampQueryPool.js +9 -3
- package/src/renderers/webgpu/utils/WebGPUUtils.js +16 -4
- package/src/renderers/webxr/WebXRManager.js +25 -2
- package/src/scenes/Scene.js +3 -3
- package/src/textures/CompressedArrayTexture.js +16 -0
- package/src/textures/Data3DTexture.js +16 -0
- package/src/textures/DataArrayTexture.js +16 -0
- package/src/textures/DepthTexture.js +3 -3
- package/src/textures/Texture.js +3 -3
- package/src/textures/{Source.js → TextureSource.js} +39 -8
- package/build/three.core.min.js +0 -6
- package/build/three.module.min.js +0 -6
- package/build/three.tsl.min.js +0 -6
- package/build/three.webgpu.min.js +0 -6
- package/build/three.webgpu.nodes.min.js +0 -6
- package/examples/jsm/libs/ecsy.module.js +0 -1792
- package/examples/jsm/loaders/LottieLoader.js +0 -130
- package/src/nodes/functions/BSDF/BRDF_GGX_Multiscatter.js +0 -52
|
@@ -0,0 +1,1679 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Portions adapted from SculptGL by Stéphane Ginier.
|
|
3
|
+
* Copyright (c) 2019 Stéphane GINIER
|
|
4
|
+
* Licensed under the MIT License; see ./SculptGL.LICENSE.txt.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
Box3,
|
|
9
|
+
BufferAttribute,
|
|
10
|
+
BufferGeometry,
|
|
11
|
+
EventDispatcher,
|
|
12
|
+
Matrix4,
|
|
13
|
+
Sphere,
|
|
14
|
+
Vector3,
|
|
15
|
+
WebGPUCoordinateSystem
|
|
16
|
+
} from '@gnsx/three';
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
getMemory,
|
|
20
|
+
intersectionRayTriangle
|
|
21
|
+
} from './SculptorUtils.js';
|
|
22
|
+
|
|
23
|
+
import { SculptorMesh } from './SculptorMesh.js';
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
subdivisionPass,
|
|
27
|
+
decimationPass,
|
|
28
|
+
getFrontVertices,
|
|
29
|
+
areaNormal,
|
|
30
|
+
areaCenter,
|
|
31
|
+
toolBrush,
|
|
32
|
+
toolFlatten,
|
|
33
|
+
toolInflate,
|
|
34
|
+
toolSmooth,
|
|
35
|
+
toolPinch,
|
|
36
|
+
toolCrease,
|
|
37
|
+
toolDrag,
|
|
38
|
+
toolScale
|
|
39
|
+
} from './SculptorTools.js';
|
|
40
|
+
|
|
41
|
+
const TOOL_DEFAULTS = {
|
|
42
|
+
clay: { size: 50, strength: 0.5, negative: false },
|
|
43
|
+
brush: { size: 50, strength: 0.5, negative: false },
|
|
44
|
+
inflate: { size: 50, strength: 0.3, negative: false },
|
|
45
|
+
smooth: { size: 50, strength: 0.75, negative: false },
|
|
46
|
+
flatten: { size: 50, strength: 0.75, negative: true },
|
|
47
|
+
pinch: { size: 50, strength: 0.75, negative: false },
|
|
48
|
+
crease: { size: 25, strength: 0.75, negative: true },
|
|
49
|
+
drag: { size: 150, strength: 0.5, negative: false },
|
|
50
|
+
scale: { size: 50, strength: 0.5, negative: false }
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const MAX_FLOAT32 = 3.4028234663852886e38;
|
|
54
|
+
const MAX_UPDATE_RANGES = 8;
|
|
55
|
+
const MAX_UPDATE_GAP_COMPONENTS = 96;
|
|
56
|
+
const STAMP_SPACING_RATIO = 0.15;
|
|
57
|
+
const TOPOLOGY_HYSTERESIS2 = 2.05 * 2.05;
|
|
58
|
+
const UNIFORM_SCALE_TOLERANCE = 1e-10;
|
|
59
|
+
const CLAY_OFFSET_RATIO = 0.1;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Fires when a pointer or programmatic stroke begins.
|
|
63
|
+
*
|
|
64
|
+
* @event Sculptor#start
|
|
65
|
+
* @type {Object}
|
|
66
|
+
*/
|
|
67
|
+
const _startEvent = { type: 'start' };
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Fires after the sculpt geometry has been updated.
|
|
71
|
+
*
|
|
72
|
+
* @event Sculptor#change
|
|
73
|
+
* @type {Object}
|
|
74
|
+
*/
|
|
75
|
+
const _changeEvent = { type: 'change' };
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Fires after the active stroke finishes and exact bounds are up to date.
|
|
79
|
+
*
|
|
80
|
+
* @event Sculptor#end
|
|
81
|
+
* @type {Object}
|
|
82
|
+
*/
|
|
83
|
+
const _endEvent = { type: 'end' };
|
|
84
|
+
const sortAscending = ( a, b ) => a - b;
|
|
85
|
+
const sortByRangeStart = ( a, b ) => a.start - b.start;
|
|
86
|
+
|
|
87
|
+
const _matInverse = new Matrix4();
|
|
88
|
+
const _v3NearWorld = new Vector3();
|
|
89
|
+
const _v3FarWorld = new Vector3();
|
|
90
|
+
const _v3NearLocal = new Vector3();
|
|
91
|
+
const _v3FarLocal = new Vector3();
|
|
92
|
+
const _v3WorldPoint = new Vector3();
|
|
93
|
+
const _v3WorldRadiusPoint = new Vector3();
|
|
94
|
+
const _v3ScreenPoint = new Vector3();
|
|
95
|
+
const _v3Temp = new Vector3();
|
|
96
|
+
|
|
97
|
+
const _tmpInter = [ 0, 0, 0 ];
|
|
98
|
+
const _tmpV1 = [ 0, 0, 0 ];
|
|
99
|
+
const _tmpV2 = [ 0, 0, 0 ];
|
|
100
|
+
const _tmpV3 = [ 0, 0, 0 ];
|
|
101
|
+
|
|
102
|
+
function validateUnitInterval( name, value ) {
|
|
103
|
+
|
|
104
|
+
if ( Number.isFinite( value ) === false || value < 0 || value > 1 ) {
|
|
105
|
+
|
|
106
|
+
throw new RangeError( `Sculptor: ${ name } must be a finite number between 0 and 1.` );
|
|
107
|
+
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return value;
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function validatePositive( name, value ) {
|
|
115
|
+
|
|
116
|
+
if ( Number.isFinite( value ) === false || value <= 0 ) {
|
|
117
|
+
|
|
118
|
+
throw new RangeError( `Sculptor: ${ name } must be a finite number greater than 0.` );
|
|
119
|
+
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return value;
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function validateSize( value ) {
|
|
127
|
+
|
|
128
|
+
if ( Number.isFinite( value ) === false || value < 5 || value > 500 ) {
|
|
129
|
+
|
|
130
|
+
throw new RangeError( 'Sculptor: size must be a finite number between 5 and 500 pixels.' );
|
|
131
|
+
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return value;
|
|
135
|
+
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function validateRayTool( tool ) {
|
|
139
|
+
|
|
140
|
+
if ( tool === 'drag' || tool === 'scale' ) {
|
|
141
|
+
|
|
142
|
+
throw new Error( `Sculptor: The ${ tool } tool requires pointer movement and cannot be used with strokeFromRay().` );
|
|
143
|
+
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function attributeMatches( attribute, source, itemSize ) {
|
|
149
|
+
|
|
150
|
+
return attribute !== undefined && attribute !== null &&
|
|
151
|
+
attribute.itemSize === itemSize &&
|
|
152
|
+
attribute.array.buffer === source.buffer &&
|
|
153
|
+
attribute.array.byteOffset === source.byteOffset &&
|
|
154
|
+
attribute.array.length === source.length;
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function createVersionedAttribute( source, itemSize, previous ) {
|
|
159
|
+
|
|
160
|
+
const attribute = new BufferAttribute( source, itemSize );
|
|
161
|
+
|
|
162
|
+
// Static usage keeps uploads version-driven in both renderers.
|
|
163
|
+
if ( previous !== undefined && previous !== null ) attribute.version = previous.version + 1;
|
|
164
|
+
|
|
165
|
+
return attribute;
|
|
166
|
+
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function createReplacementGeometry( source ) {
|
|
170
|
+
|
|
171
|
+
const geometry = new BufferGeometry();
|
|
172
|
+
geometry.name = source.name;
|
|
173
|
+
geometry.userData = source.userData;
|
|
174
|
+
geometry.boundingBox = source.boundingBox === null ? null : source.boundingBox.clone();
|
|
175
|
+
geometry.boundingSphere = source.boundingSphere === null ? null : source.boundingSphere.clone();
|
|
176
|
+
return geometry;
|
|
177
|
+
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function addVertexUpdateRanges( attribute, vertices ) {
|
|
181
|
+
|
|
182
|
+
let start = vertices[ 0 ];
|
|
183
|
+
let previous = start;
|
|
184
|
+
|
|
185
|
+
for ( let i = 1, l = vertices.length; i < l; ++ i ) {
|
|
186
|
+
|
|
187
|
+
const current = vertices[ i ];
|
|
188
|
+
|
|
189
|
+
if ( current === previous ) continue;
|
|
190
|
+
|
|
191
|
+
if ( current !== previous + 1 ) {
|
|
192
|
+
|
|
193
|
+
attribute.addUpdateRange( start * 3, ( previous - start + 1 ) * 3 );
|
|
194
|
+
start = current;
|
|
195
|
+
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
previous = current;
|
|
199
|
+
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
attribute.addUpdateRange( start * 3, ( previous - start + 1 ) * 3 );
|
|
203
|
+
|
|
204
|
+
const ranges = attribute.updateRanges;
|
|
205
|
+
ranges.sort( sortByRangeStart );
|
|
206
|
+
let writeIndex = 0;
|
|
207
|
+
|
|
208
|
+
// Merge nearby spans with pending ranges to limit upload calls.
|
|
209
|
+
for ( let i = 1, l = ranges.length; i < l; i ++ ) {
|
|
210
|
+
|
|
211
|
+
const previousRange = ranges[ writeIndex ];
|
|
212
|
+
const range = ranges[ i ];
|
|
213
|
+
const previousEnd = previousRange.start + previousRange.count;
|
|
214
|
+
|
|
215
|
+
if ( range.start - previousEnd <= MAX_UPDATE_GAP_COMPONENTS ) {
|
|
216
|
+
|
|
217
|
+
previousRange.count = Math.max( previousEnd, range.start + range.count ) - previousRange.start;
|
|
218
|
+
|
|
219
|
+
} else {
|
|
220
|
+
|
|
221
|
+
ranges[ ++ writeIndex ] = range;
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
ranges.length = writeIndex + 1;
|
|
228
|
+
|
|
229
|
+
if ( ranges.length <= MAX_UPDATE_RANGES ) return;
|
|
230
|
+
|
|
231
|
+
// Preserve the largest gaps when limiting the number of upload ranges.
|
|
232
|
+
const splitIndices = [];
|
|
233
|
+
|
|
234
|
+
for ( let i = 1, l = ranges.length; i < l; i ++ ) splitIndices.push( i );
|
|
235
|
+
|
|
236
|
+
splitIndices.sort( ( a, b ) => {
|
|
237
|
+
|
|
238
|
+
const gapA = ranges[ a ].start - ranges[ a - 1 ].start - ranges[ a - 1 ].count;
|
|
239
|
+
const gapB = ranges[ b ].start - ranges[ b - 1 ].start - ranges[ b - 1 ].count;
|
|
240
|
+
return gapB - gapA;
|
|
241
|
+
|
|
242
|
+
} );
|
|
243
|
+
splitIndices.length = MAX_UPDATE_RANGES - 1;
|
|
244
|
+
splitIndices.sort( sortAscending );
|
|
245
|
+
|
|
246
|
+
writeIndex = 0;
|
|
247
|
+
let splitIndex = 0;
|
|
248
|
+
|
|
249
|
+
for ( let i = 1, l = ranges.length; i < l; i ++ ) {
|
|
250
|
+
|
|
251
|
+
const range = ranges[ i ];
|
|
252
|
+
|
|
253
|
+
if ( i === splitIndices[ splitIndex ] ) {
|
|
254
|
+
|
|
255
|
+
ranges[ ++ writeIndex ] = range;
|
|
256
|
+
splitIndex ++;
|
|
257
|
+
|
|
258
|
+
} else {
|
|
259
|
+
|
|
260
|
+
const previousRange = ranges[ writeIndex ];
|
|
261
|
+
previousRange.count = Math.max( previousRange.start + previousRange.count, range.start + range.count ) - previousRange.start;
|
|
262
|
+
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
ranges.length = writeIndex + 1;
|
|
268
|
+
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function compactDirtyVertices( vertices, vertexCount ) {
|
|
272
|
+
|
|
273
|
+
vertices.sort( sortAscending );
|
|
274
|
+
|
|
275
|
+
let writeIndex = 0;
|
|
276
|
+
let previous = - 1;
|
|
277
|
+
|
|
278
|
+
for ( let i = 0, l = vertices.length; i < l; i ++ ) {
|
|
279
|
+
|
|
280
|
+
const vertex = vertices[ i ];
|
|
281
|
+
|
|
282
|
+
// Exclude vertices removed by later stamps in the same pointer event.
|
|
283
|
+
if ( vertex >= vertexCount ) break;
|
|
284
|
+
if ( vertex < 0 || vertex === previous ) continue;
|
|
285
|
+
|
|
286
|
+
vertices[ writeIndex ++ ] = vertex;
|
|
287
|
+
previous = vertex;
|
|
288
|
+
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
vertices.length = writeIndex;
|
|
292
|
+
return writeIndex > 0;
|
|
293
|
+
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Sculpts triangle meshes with adaptive topology.
|
|
298
|
+
*
|
|
299
|
+
* ```js
|
|
300
|
+
* const sculptor = new Sculptor( mesh, camera )
|
|
301
|
+
* .setTool( 'inflate' )
|
|
302
|
+
* .setSize( 75 )
|
|
303
|
+
* .setStrength( 0.3 );
|
|
304
|
+
* sculptor.connect( renderer.domElement );
|
|
305
|
+
* ```
|
|
306
|
+
*
|
|
307
|
+
* Replaces `mesh.geometry` with a welded geometry containing positions, normals
|
|
308
|
+
* and indices. The source geometry is unchanged and is not disposed.
|
|
309
|
+
*
|
|
310
|
+
* The mesh must use one material and a non-zero uniform world scale without
|
|
311
|
+
* shear. Skinned, instanced and batched meshes are not supported.
|
|
312
|
+
*
|
|
313
|
+
* Sculptor manages bounds, draw range and spare buffer capacity. Geometry and
|
|
314
|
+
* attributes may be replaced as capacity changes; do not cache them. Use
|
|
315
|
+
* {@link Sculptor#getGeometry} for a compact copy for export or geometry processing.
|
|
316
|
+
*
|
|
317
|
+
* @three_import import { Sculptor } from 'three/addons/misc/Sculptor.js';
|
|
318
|
+
*/
|
|
319
|
+
class Sculptor extends EventDispatcher {
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* @param {Mesh} mesh - The mesh to sculpt.
|
|
323
|
+
* @param {Camera} camera - The camera used for pointer picking.
|
|
324
|
+
*/
|
|
325
|
+
|
|
326
|
+
constructor( mesh, camera ) {
|
|
327
|
+
|
|
328
|
+
super();
|
|
329
|
+
|
|
330
|
+
if ( mesh === undefined || mesh.isMesh !== true || mesh.geometry === undefined || mesh.geometry.isBufferGeometry !== true ) {
|
|
331
|
+
|
|
332
|
+
throw new TypeError( 'Sculptor: mesh must be a Mesh with a BufferGeometry.' );
|
|
333
|
+
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if ( mesh.isSkinnedMesh === true || mesh.isInstancedMesh === true || mesh.isBatchedMesh === true ) {
|
|
337
|
+
|
|
338
|
+
throw new TypeError( 'Sculptor: SkinnedMesh, InstancedMesh and BatchedMesh are not supported.' );
|
|
339
|
+
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if ( Array.isArray( mesh.material ) ) throw new Error( 'Sculptor: Multi-material meshes are not supported.' );
|
|
343
|
+
|
|
344
|
+
if ( camera === undefined || camera.isCamera !== true ) throw new TypeError( 'Sculptor: camera must be a Camera.' );
|
|
345
|
+
|
|
346
|
+
this._toolSettings = {};
|
|
347
|
+
|
|
348
|
+
for ( const tool in TOOL_DEFAULTS ) this._toolSettings[ tool ] = { ...TOOL_DEFAULTS[ tool ] };
|
|
349
|
+
|
|
350
|
+
this._tool = 'clay';
|
|
351
|
+
this._size = TOOL_DEFAULTS.clay.size;
|
|
352
|
+
this._strength = TOOL_DEFAULTS.clay.strength;
|
|
353
|
+
this._negative = TOOL_DEFAULTS.clay.negative;
|
|
354
|
+
this._detail = 0.75;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The mesh being sculpted.
|
|
358
|
+
*
|
|
359
|
+
* @type {Mesh}
|
|
360
|
+
* @readonly
|
|
361
|
+
*/
|
|
362
|
+
this.mesh = mesh;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* The camera used for pointer picking.
|
|
366
|
+
*
|
|
367
|
+
* @type {Camera}
|
|
368
|
+
*/
|
|
369
|
+
this.camera = camera;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* The element receiving pointer events, or `null` while disconnected.
|
|
373
|
+
*
|
|
374
|
+
* @type {?HTMLElement}
|
|
375
|
+
* @default null
|
|
376
|
+
*/
|
|
377
|
+
this.domElement = null;
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Whether pointer input is enabled. Does not affect programmatic strokes.
|
|
381
|
+
*
|
|
382
|
+
* @type {boolean}
|
|
383
|
+
* @default true
|
|
384
|
+
*/
|
|
385
|
+
this.enabled = true;
|
|
386
|
+
|
|
387
|
+
this._sculptMesh = new SculptorMesh();
|
|
388
|
+
this._sculptMesh.initFromGeometry( mesh.geometry );
|
|
389
|
+
|
|
390
|
+
this._sculpting = false;
|
|
391
|
+
this._activePointerId = null;
|
|
392
|
+
this._lastPointerX = 0;
|
|
393
|
+
this._lastPointerY = 0;
|
|
394
|
+
this._hitFace = - 1;
|
|
395
|
+
this._rayOrigin = [ 0, 0, 0 ];
|
|
396
|
+
this._rayDirection = [ 0, 0, 0 ];
|
|
397
|
+
this._hitPoint = [ 0, 0, 0 ];
|
|
398
|
+
this._hitNormal = [ 0, 0, 0 ];
|
|
399
|
+
this._localRadius2 = 0;
|
|
400
|
+
this._worldRadius2 = 0;
|
|
401
|
+
this._dragDirection = [ 0, 0, 0 ];
|
|
402
|
+
this._cachedRect = null;
|
|
403
|
+
this._lastTopologyVersion = - 1;
|
|
404
|
+
this._dirtyVertices = [];
|
|
405
|
+
this._geometrySynced = false;
|
|
406
|
+
this._boundsDirty = false;
|
|
407
|
+
|
|
408
|
+
this._prepareGeometry();
|
|
409
|
+
this._syncGeometry();
|
|
410
|
+
this._computeExactBounds();
|
|
411
|
+
|
|
412
|
+
this._onPointerDown = this._onPointerDown.bind( this );
|
|
413
|
+
this._onPointerMove = this._onPointerMove.bind( this );
|
|
414
|
+
this._onPointerUp = this._onPointerUp.bind( this );
|
|
415
|
+
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Connects pointer input to a DOM element.
|
|
420
|
+
*
|
|
421
|
+
* @param {HTMLElement} element - The element receiving pointer events.
|
|
422
|
+
*/
|
|
423
|
+
connect( element ) {
|
|
424
|
+
|
|
425
|
+
if ( element === undefined || element === null || typeof element.addEventListener !== 'function' ||
|
|
426
|
+
typeof element.removeEventListener !== 'function' || typeof element.getBoundingClientRect !== 'function' ) {
|
|
427
|
+
|
|
428
|
+
throw new TypeError( 'Sculptor: element must be an EventTarget with getBoundingClientRect().' );
|
|
429
|
+
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if ( this.domElement !== null ) this.disconnect();
|
|
433
|
+
|
|
434
|
+
this.domElement = element;
|
|
435
|
+
this._cachedRect = null;
|
|
436
|
+
|
|
437
|
+
element.addEventListener( 'pointerdown', this._onPointerDown );
|
|
438
|
+
element.addEventListener( 'pointermove', this._onPointerMove );
|
|
439
|
+
element.addEventListener( 'pointerup', this._onPointerUp );
|
|
440
|
+
element.addEventListener( 'pointercancel', this._onPointerUp );
|
|
441
|
+
element.addEventListener( 'lostpointercapture', this._onPointerUp );
|
|
442
|
+
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Disconnects pointer input and finishes the active stroke.
|
|
447
|
+
*/
|
|
448
|
+
disconnect() {
|
|
449
|
+
|
|
450
|
+
const element = this.domElement;
|
|
451
|
+
this.endStroke();
|
|
452
|
+
|
|
453
|
+
if ( element === null ) return;
|
|
454
|
+
|
|
455
|
+
element.removeEventListener( 'pointerdown', this._onPointerDown );
|
|
456
|
+
element.removeEventListener( 'pointermove', this._onPointerMove );
|
|
457
|
+
element.removeEventListener( 'pointerup', this._onPointerUp );
|
|
458
|
+
element.removeEventListener( 'pointercancel', this._onPointerUp );
|
|
459
|
+
element.removeEventListener( 'lostpointercapture', this._onPointerUp );
|
|
460
|
+
this.domElement = null;
|
|
461
|
+
this._cachedRect = null;
|
|
462
|
+
this._clearHit();
|
|
463
|
+
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Disconnects the sculptor. The mesh and its geometry are not disposed.
|
|
468
|
+
*/
|
|
469
|
+
dispose() {
|
|
470
|
+
|
|
471
|
+
this.disconnect();
|
|
472
|
+
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
_prepareGeometry() {
|
|
476
|
+
|
|
477
|
+
const sourceGeometry = this.mesh.geometry;
|
|
478
|
+
const geometry = new BufferGeometry();
|
|
479
|
+
geometry.name = sourceGeometry.name;
|
|
480
|
+
geometry.userData = JSON.parse( JSON.stringify( sourceGeometry.userData ) );
|
|
481
|
+
this.mesh.geometry = geometry;
|
|
482
|
+
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
_getRect() {
|
|
486
|
+
|
|
487
|
+
if ( this.domElement === null ) {
|
|
488
|
+
|
|
489
|
+
throw new Error( 'Sculptor: connect() must be called before using pointer coordinates.' );
|
|
490
|
+
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if ( this._cachedRect === null ) this._cachedRect = this.domElement.getBoundingClientRect();
|
|
494
|
+
|
|
495
|
+
return this._cachedRect;
|
|
496
|
+
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
_unproject( clientX, clientY, z, target ) {
|
|
500
|
+
|
|
501
|
+
const rect = this._getRect();
|
|
502
|
+
const x = ( ( clientX - rect.left ) / rect.width ) * 2 - 1;
|
|
503
|
+
const y = - ( ( clientY - rect.top ) / rect.height ) * 2 + 1;
|
|
504
|
+
|
|
505
|
+
return target.set( x, y, z ).unproject( this.camera );
|
|
506
|
+
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
_project( point, target ) {
|
|
510
|
+
|
|
511
|
+
target.copy( point ).project( this.camera );
|
|
512
|
+
|
|
513
|
+
const rect = this._getRect();
|
|
514
|
+
target.x = ( target.x * 0.5 + 0.5 ) * rect.width + rect.left;
|
|
515
|
+
target.y = ( - target.y * 0.5 + 0.5 ) * rect.height + rect.top;
|
|
516
|
+
|
|
517
|
+
return target;
|
|
518
|
+
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
_updatePointerRay( clientX, clientY ) {
|
|
522
|
+
|
|
523
|
+
const camera = this.camera;
|
|
524
|
+
const nearDepth = camera.reversedDepth ? 1 : camera.coordinateSystem === WebGPUCoordinateSystem ? 0 : - 1;
|
|
525
|
+
const farDepth = camera.reversedDepth ? 0 : 1;
|
|
526
|
+
const directionDepth = nearDepth + ( farDepth - nearDepth ) * 0.1;
|
|
527
|
+
|
|
528
|
+
this._unproject( clientX, clientY, nearDepth, _v3NearWorld );
|
|
529
|
+
this._unproject( clientX, clientY, directionDepth, _v3FarWorld );
|
|
530
|
+
_v3NearLocal.copy( _v3NearWorld ).applyMatrix4( _matInverse );
|
|
531
|
+
_v3FarLocal.copy( _v3FarWorld ).applyMatrix4( _matInverse );
|
|
532
|
+
|
|
533
|
+
const rayOrigin = this._rayOrigin;
|
|
534
|
+
rayOrigin[ 0 ] = _v3NearLocal.x;
|
|
535
|
+
rayOrigin[ 1 ] = _v3NearLocal.y;
|
|
536
|
+
rayOrigin[ 2 ] = _v3NearLocal.z;
|
|
537
|
+
|
|
538
|
+
const rayDirection = this._rayDirection;
|
|
539
|
+
rayDirection[ 0 ] = _v3FarLocal.x - _v3NearLocal.x;
|
|
540
|
+
rayDirection[ 1 ] = _v3FarLocal.y - _v3NearLocal.y;
|
|
541
|
+
rayDirection[ 2 ] = _v3FarLocal.z - _v3NearLocal.z;
|
|
542
|
+
|
|
543
|
+
const length = Math.hypot( rayDirection[ 0 ], rayDirection[ 1 ], rayDirection[ 2 ] );
|
|
544
|
+
|
|
545
|
+
if ( length === 0 || Number.isFinite( length ) === false ) return false;
|
|
546
|
+
|
|
547
|
+
rayDirection[ 0 ] /= length;
|
|
548
|
+
rayDirection[ 1 ] /= length;
|
|
549
|
+
rayDirection[ 2 ] /= length;
|
|
550
|
+
return true;
|
|
551
|
+
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
_updateMeshMatrix() {
|
|
555
|
+
|
|
556
|
+
this.mesh.updateWorldMatrix( true, false );
|
|
557
|
+
|
|
558
|
+
const elements = this.mesh.matrixWorld.elements;
|
|
559
|
+
const sx2 = elements[ 0 ] * elements[ 0 ] + elements[ 1 ] * elements[ 1 ] + elements[ 2 ] * elements[ 2 ];
|
|
560
|
+
const sy2 = elements[ 4 ] * elements[ 4 ] + elements[ 5 ] * elements[ 5 ] + elements[ 6 ] * elements[ 6 ];
|
|
561
|
+
const sz2 = elements[ 8 ] * elements[ 8 ] + elements[ 9 ] * elements[ 9 ] + elements[ 10 ] * elements[ 10 ];
|
|
562
|
+
const scaleMax2 = Math.max( sx2, sy2, sz2 );
|
|
563
|
+
const scaleMin2 = Math.min( sx2, sy2, sz2 );
|
|
564
|
+
const tolerance = scaleMax2 * UNIFORM_SCALE_TOLERANCE;
|
|
565
|
+
const dotXY = elements[ 0 ] * elements[ 4 ] + elements[ 1 ] * elements[ 5 ] + elements[ 2 ] * elements[ 6 ];
|
|
566
|
+
const dotXZ = elements[ 0 ] * elements[ 8 ] + elements[ 1 ] * elements[ 9 ] + elements[ 2 ] * elements[ 10 ];
|
|
567
|
+
const dotYZ = elements[ 4 ] * elements[ 8 ] + elements[ 5 ] * elements[ 9 ] + elements[ 6 ] * elements[ 10 ];
|
|
568
|
+
|
|
569
|
+
if ( Number.isFinite( scaleMin2 ) === false || Number.isFinite( scaleMax2 ) === false ||
|
|
570
|
+
Number.isFinite( dotXY ) === false || Number.isFinite( dotXZ ) === false || Number.isFinite( dotYZ ) === false ||
|
|
571
|
+
scaleMin2 <= Number.EPSILON || scaleMax2 - scaleMin2 > tolerance ||
|
|
572
|
+
Math.abs( dotXY ) > tolerance || Math.abs( dotXZ ) > tolerance || Math.abs( dotYZ ) > tolerance ) {
|
|
573
|
+
|
|
574
|
+
throw new Error( 'Sculptor: The mesh must have a non-zero uniform world scale without shear.' );
|
|
575
|
+
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
_matInverse.copy( this.mesh.matrixWorld ).invert();
|
|
579
|
+
|
|
580
|
+
return ( sx2 + sy2 + sz2 ) / 3;
|
|
581
|
+
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
_updatePickingMatrices() {
|
|
585
|
+
|
|
586
|
+
const scale2 = this._updateMeshMatrix();
|
|
587
|
+
this.camera.updateWorldMatrix( true, false );
|
|
588
|
+
|
|
589
|
+
return scale2;
|
|
590
|
+
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
_clearHit() {
|
|
594
|
+
|
|
595
|
+
this._hitFace = - 1;
|
|
596
|
+
this._localRadius2 = 0;
|
|
597
|
+
this._worldRadius2 = 0;
|
|
598
|
+
this._hitPoint[ 0 ] = 0;
|
|
599
|
+
this._hitPoint[ 1 ] = 0;
|
|
600
|
+
this._hitPoint[ 2 ] = 0;
|
|
601
|
+
this._hitNormal[ 0 ] = 0;
|
|
602
|
+
this._hitNormal[ 1 ] = 0;
|
|
603
|
+
this._hitNormal[ 2 ] = 0;
|
|
604
|
+
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
_pickClosestFace( rayOrigin, rayDirection ) {
|
|
608
|
+
|
|
609
|
+
const sculptMesh = this._sculptMesh;
|
|
610
|
+
const candidateFaces = sculptMesh.intersectRay( rayOrigin, rayDirection );
|
|
611
|
+
const vertices = sculptMesh.getVertices();
|
|
612
|
+
const faces = sculptMesh.getFaces();
|
|
613
|
+
let distance = Infinity;
|
|
614
|
+
|
|
615
|
+
this._hitFace = - 1;
|
|
616
|
+
|
|
617
|
+
for ( let i = 0, l = candidateFaces.length; i < l; ++ i ) {
|
|
618
|
+
|
|
619
|
+
const faceIndex = candidateFaces[ i ];
|
|
620
|
+
const faceOffset = faceIndex * 4;
|
|
621
|
+
const offset1 = faces[ faceOffset ] * 3;
|
|
622
|
+
const offset2 = faces[ faceOffset + 1 ] * 3;
|
|
623
|
+
const offset3 = faces[ faceOffset + 2 ] * 3;
|
|
624
|
+
|
|
625
|
+
_tmpV1[ 0 ] = vertices[ offset1 ];
|
|
626
|
+
_tmpV1[ 1 ] = vertices[ offset1 + 1 ];
|
|
627
|
+
_tmpV1[ 2 ] = vertices[ offset1 + 2 ];
|
|
628
|
+
_tmpV2[ 0 ] = vertices[ offset2 ];
|
|
629
|
+
_tmpV2[ 1 ] = vertices[ offset2 + 1 ];
|
|
630
|
+
_tmpV2[ 2 ] = vertices[ offset2 + 2 ];
|
|
631
|
+
_tmpV3[ 0 ] = vertices[ offset3 ];
|
|
632
|
+
_tmpV3[ 1 ] = vertices[ offset3 + 1 ];
|
|
633
|
+
_tmpV3[ 2 ] = vertices[ offset3 + 2 ];
|
|
634
|
+
|
|
635
|
+
const hitDistance = intersectionRayTriangle( rayOrigin, rayDirection, _tmpV1, _tmpV2, _tmpV3, _tmpInter );
|
|
636
|
+
|
|
637
|
+
if ( hitDistance >= 0 && hitDistance < distance ) {
|
|
638
|
+
|
|
639
|
+
distance = hitDistance;
|
|
640
|
+
this._hitPoint[ 0 ] = _tmpInter[ 0 ];
|
|
641
|
+
this._hitPoint[ 1 ] = _tmpInter[ 1 ];
|
|
642
|
+
this._hitPoint[ 2 ] = _tmpInter[ 2 ];
|
|
643
|
+
this._hitFace = faceIndex;
|
|
644
|
+
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return this._hitFace !== - 1;
|
|
650
|
+
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
_intersectionRayMesh( clientX, clientY, scale2 ) {
|
|
654
|
+
|
|
655
|
+
const rect = this._getRect();
|
|
656
|
+
|
|
657
|
+
if ( Number.isFinite( clientX ) === false || Number.isFinite( clientY ) === false ||
|
|
658
|
+
Number.isFinite( rect.width ) === false || Number.isFinite( rect.height ) === false || rect.width <= 0 || rect.height <= 0 ) {
|
|
659
|
+
|
|
660
|
+
this._clearHit();
|
|
661
|
+
return false;
|
|
662
|
+
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if ( scale2 === undefined ) scale2 = this._updatePickingMatrices();
|
|
666
|
+
|
|
667
|
+
if ( this._updatePointerRay( clientX, clientY ) === false ) {
|
|
668
|
+
|
|
669
|
+
this._clearHit();
|
|
670
|
+
return false;
|
|
671
|
+
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
if ( this._pickClosestFace( this._rayOrigin, this._rayDirection ) === false ) {
|
|
675
|
+
|
|
676
|
+
this._clearHit();
|
|
677
|
+
return false;
|
|
678
|
+
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
this._updateRadii( scale2 );
|
|
682
|
+
|
|
683
|
+
return true;
|
|
684
|
+
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
_updateRadii( scale2 ) {
|
|
688
|
+
|
|
689
|
+
const hitPoint = this._hitPoint;
|
|
690
|
+
_v3WorldPoint.set( hitPoint[ 0 ], hitPoint[ 1 ], hitPoint[ 2 ] ).applyMatrix4( this.mesh.matrixWorld );
|
|
691
|
+
this._project( _v3WorldPoint, _v3ScreenPoint );
|
|
692
|
+
this._unproject( _v3ScreenPoint.x + this._size, _v3ScreenPoint.y, _v3ScreenPoint.z, _v3WorldRadiusPoint );
|
|
693
|
+
|
|
694
|
+
this._worldRadius2 = _v3WorldPoint.distanceToSquared( _v3WorldRadiusPoint );
|
|
695
|
+
this._localRadius2 = this._worldRadius2 / scale2;
|
|
696
|
+
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
_pickVerticesInSphere( radius2 ) {
|
|
700
|
+
|
|
701
|
+
const sculptMesh = this._sculptMesh;
|
|
702
|
+
const vertices = sculptMesh.getVertices();
|
|
703
|
+
const sculptFlags = sculptMesh.getVerticesSculptFlags();
|
|
704
|
+
const hitPoint = this._hitPoint;
|
|
705
|
+
const facesInCells = sculptMesh.intersectSphere( hitPoint, radius2, true );
|
|
706
|
+
const verticesInCells = sculptMesh.getVerticesFromFaces( facesInCells );
|
|
707
|
+
const sculptFlag = sculptMesh.nextSculptFlag();
|
|
708
|
+
const pickedVertices = new Uint32Array( getMemory( 4 * verticesInCells.length ), 0, verticesInCells.length );
|
|
709
|
+
let count = 0;
|
|
710
|
+
const ix = hitPoint[ 0 ];
|
|
711
|
+
const iy = hitPoint[ 1 ];
|
|
712
|
+
const iz = hitPoint[ 2 ];
|
|
713
|
+
|
|
714
|
+
for ( let i = 0, l = verticesInCells.length; i < l; ++ i ) {
|
|
715
|
+
|
|
716
|
+
const vertexIndex = verticesInCells[ i ];
|
|
717
|
+
const offset = vertexIndex * 3;
|
|
718
|
+
const dx = ix - vertices[ offset ];
|
|
719
|
+
const dy = iy - vertices[ offset + 1 ];
|
|
720
|
+
const dz = iz - vertices[ offset + 2 ];
|
|
721
|
+
|
|
722
|
+
if ( dx * dx + dy * dy + dz * dz < radius2 ) {
|
|
723
|
+
|
|
724
|
+
sculptFlags[ vertexIndex ] = sculptFlag;
|
|
725
|
+
pickedVertices[ count ++ ] = vertexIndex;
|
|
726
|
+
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return pickedVertices.slice( 0, count );
|
|
732
|
+
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
_computePickedNormal() {
|
|
736
|
+
|
|
737
|
+
if ( this._hitFace < 0 ) return false;
|
|
738
|
+
|
|
739
|
+
const sculptMesh = this._sculptMesh;
|
|
740
|
+
const faces = sculptMesh.getFaces();
|
|
741
|
+
const vertices = sculptMesh.getVertices();
|
|
742
|
+
const normals = sculptMesh.getNormals();
|
|
743
|
+
const faceOffset = this._hitFace * 4;
|
|
744
|
+
const offset1 = faces[ faceOffset ] * 3;
|
|
745
|
+
const offset2 = faces[ faceOffset + 1 ] * 3;
|
|
746
|
+
const offset3 = faces[ faceOffset + 2 ] * 3;
|
|
747
|
+
const hitPoint = this._hitPoint;
|
|
748
|
+
const dx1 = hitPoint[ 0 ] - vertices[ offset1 ];
|
|
749
|
+
const dy1 = hitPoint[ 1 ] - vertices[ offset1 + 1 ];
|
|
750
|
+
const dz1 = hitPoint[ 2 ] - vertices[ offset1 + 2 ];
|
|
751
|
+
const dx2 = hitPoint[ 0 ] - vertices[ offset2 ];
|
|
752
|
+
const dy2 = hitPoint[ 1 ] - vertices[ offset2 + 1 ];
|
|
753
|
+
const dz2 = hitPoint[ 2 ] - vertices[ offset2 + 2 ];
|
|
754
|
+
const dx3 = hitPoint[ 0 ] - vertices[ offset3 ];
|
|
755
|
+
const dy3 = hitPoint[ 1 ] - vertices[ offset3 + 1 ];
|
|
756
|
+
const dz3 = hitPoint[ 2 ] - vertices[ offset3 + 2 ];
|
|
757
|
+
const hitNormal = this._hitNormal;
|
|
758
|
+
const distance1 = Math.hypot( dx1, dy1, dz1 );
|
|
759
|
+
const distance2 = Math.hypot( dx2, dy2, dz2 );
|
|
760
|
+
const distance3 = Math.hypot( dx3, dy3, dz3 );
|
|
761
|
+
|
|
762
|
+
if ( distance1 === 0 || distance2 === 0 || distance3 === 0 ) {
|
|
763
|
+
|
|
764
|
+
const offset = distance1 === 0 ? offset1 : ( distance2 === 0 ? offset2 : offset3 );
|
|
765
|
+
hitNormal[ 0 ] = normals[ offset ];
|
|
766
|
+
hitNormal[ 1 ] = normals[ offset + 1 ];
|
|
767
|
+
hitNormal[ 2 ] = normals[ offset + 2 ];
|
|
768
|
+
|
|
769
|
+
} else {
|
|
770
|
+
|
|
771
|
+
const weight1 = 1 / distance1;
|
|
772
|
+
const weight2 = 1 / distance2;
|
|
773
|
+
const weight3 = 1 / distance3;
|
|
774
|
+
const inverseSum = 1 / ( weight1 + weight2 + weight3 );
|
|
775
|
+
|
|
776
|
+
hitNormal[ 0 ] = ( normals[ offset1 ] * weight1 + normals[ offset2 ] * weight2 + normals[ offset3 ] * weight3 ) * inverseSum;
|
|
777
|
+
hitNormal[ 1 ] = ( normals[ offset1 + 1 ] * weight1 + normals[ offset2 + 1 ] * weight2 + normals[ offset3 + 1 ] * weight3 ) * inverseSum;
|
|
778
|
+
hitNormal[ 2 ] = ( normals[ offset1 + 2 ] * weight1 + normals[ offset2 + 2 ] * weight2 + normals[ offset3 + 2 ] * weight3 ) * inverseSum;
|
|
779
|
+
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const length = Math.hypot( hitNormal[ 0 ], hitNormal[ 1 ], hitNormal[ 2 ] );
|
|
783
|
+
|
|
784
|
+
if ( length > 0 ) {
|
|
785
|
+
|
|
786
|
+
hitNormal[ 0 ] /= length;
|
|
787
|
+
hitNormal[ 1 ] /= length;
|
|
788
|
+
hitNormal[ 2 ] /= length;
|
|
789
|
+
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return true;
|
|
793
|
+
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
_dynamicTopology( pickedVertices ) {
|
|
797
|
+
|
|
798
|
+
const sculptMesh = this._sculptMesh;
|
|
799
|
+
const detail = this._detail;
|
|
800
|
+
|
|
801
|
+
if ( detail === 0 ) return pickedVertices;
|
|
802
|
+
|
|
803
|
+
const originalPickedVertices = pickedVertices;
|
|
804
|
+
const topologyVersion = sculptMesh.getTopologyVersion();
|
|
805
|
+
|
|
806
|
+
if ( pickedVertices.length === 0 ) pickedVertices = sculptMesh.getVerticesFromFaces( [ this._hitFace ] );
|
|
807
|
+
|
|
808
|
+
let faces = sculptMesh.getFacesFromVertices( pickedVertices );
|
|
809
|
+
const radius2 = this._localRadius2;
|
|
810
|
+
const hitPoint = this._hitPoint;
|
|
811
|
+
// Keep edge targets non-zero at maximum detail.
|
|
812
|
+
const edgeMax2 = radius2 * ( 1.1 - detail ) * 0.2;
|
|
813
|
+
// Keep the collapse threshold below half the split length to avoid oscillation.
|
|
814
|
+
const edgeMin2 = edgeMax2 / TOPOLOGY_HYSTERESIS2;
|
|
815
|
+
|
|
816
|
+
faces = subdivisionPass( sculptMesh, faces, hitPoint, radius2, edgeMax2 );
|
|
817
|
+
faces = decimationPass( sculptMesh, faces, hitPoint, radius2, edgeMin2 );
|
|
818
|
+
|
|
819
|
+
// Rebuild topology caches only when connectivity changes.
|
|
820
|
+
if ( sculptMesh.getTopologyVersion() === topologyVersion ) return originalPickedVertices;
|
|
821
|
+
|
|
822
|
+
let affectedVertices = sculptMesh.getVerticesFromFaces( faces );
|
|
823
|
+
|
|
824
|
+
// Include faces adjacent to smoothed vertices to update their normals,
|
|
825
|
+
// bounds and octree cells.
|
|
826
|
+
faces = sculptMesh.getFacesFromVertices( affectedVertices );
|
|
827
|
+
affectedVertices = sculptMesh.getVerticesFromFaces( faces );
|
|
828
|
+
const sculptFlags = sculptMesh.getVerticesSculptFlags();
|
|
829
|
+
const sculptFlag = sculptMesh.getSculptFlag();
|
|
830
|
+
const verticesInRadius = new Uint32Array( getMemory( affectedVertices.length * 4 ), 0, affectedVertices.length );
|
|
831
|
+
let count = 0;
|
|
832
|
+
|
|
833
|
+
for ( let i = 0, l = affectedVertices.length; i < l; ++ i ) {
|
|
834
|
+
|
|
835
|
+
const vertexIndex = affectedVertices[ i ];
|
|
836
|
+
|
|
837
|
+
if ( sculptFlags[ vertexIndex ] === sculptFlag ) verticesInRadius[ count ++ ] = vertexIndex;
|
|
838
|
+
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const result = verticesInRadius.slice( 0, count );
|
|
842
|
+
sculptMesh.updateTopology( faces, affectedVertices );
|
|
843
|
+
sculptMesh._updateGeometry( faces, affectedVertices );
|
|
844
|
+
this._markVerticesDirty( affectedVertices );
|
|
845
|
+
|
|
846
|
+
return result;
|
|
847
|
+
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
_intersectionFromRay( ray, worldRadius ) {
|
|
851
|
+
|
|
852
|
+
validatePositive( 'worldRadius', worldRadius );
|
|
853
|
+
|
|
854
|
+
if ( ray === undefined || ray === null || ray.origin?.isVector3 !== true || ray.direction?.isVector3 !== true ) {
|
|
855
|
+
|
|
856
|
+
throw new TypeError( 'Sculptor: ray must have Vector3 origin and direction properties.' );
|
|
857
|
+
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const worldOrigin = ray.origin;
|
|
861
|
+
const worldDirection = ray.direction;
|
|
862
|
+
const directionLengthSq = worldDirection.lengthSq();
|
|
863
|
+
|
|
864
|
+
if ( Number.isFinite( worldOrigin.x ) === false || Number.isFinite( worldOrigin.y ) === false || Number.isFinite( worldOrigin.z ) === false ||
|
|
865
|
+
Number.isFinite( worldDirection.x ) === false || Number.isFinite( worldDirection.y ) === false || Number.isFinite( worldDirection.z ) === false ||
|
|
866
|
+
Number.isFinite( directionLengthSq ) === false || directionLengthSq === 0 ) {
|
|
867
|
+
|
|
868
|
+
throw new RangeError( 'Sculptor: origin and direction must contain finite values, and direction must be non-zero.' );
|
|
869
|
+
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
const scale2 = this._updateMeshMatrix();
|
|
873
|
+
const worldRadius2 = worldRadius * worldRadius;
|
|
874
|
+
const localRadius = worldRadius / Math.sqrt( scale2 );
|
|
875
|
+
const localRadius2 = localRadius * localRadius;
|
|
876
|
+
|
|
877
|
+
if ( Number.isFinite( worldRadius2 ) === false || Number.isFinite( localRadius2 ) === false ||
|
|
878
|
+
localRadius2 === 0 || localRadius > MAX_FLOAT32 ) {
|
|
879
|
+
|
|
880
|
+
throw new RangeError( 'Sculptor: worldRadius is too large or too small for the mesh scale.' );
|
|
881
|
+
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
_v3NearLocal.copy( worldOrigin ).applyMatrix4( _matInverse );
|
|
885
|
+
_v3FarLocal.copy( worldDirection ).transformDirection( _matInverse );
|
|
886
|
+
|
|
887
|
+
const rayOrigin = this._rayOrigin;
|
|
888
|
+
rayOrigin[ 0 ] = _v3NearLocal.x;
|
|
889
|
+
rayOrigin[ 1 ] = _v3NearLocal.y;
|
|
890
|
+
rayOrigin[ 2 ] = _v3NearLocal.z;
|
|
891
|
+
|
|
892
|
+
const rayDirection = this._rayDirection;
|
|
893
|
+
rayDirection[ 0 ] = _v3FarLocal.x;
|
|
894
|
+
rayDirection[ 1 ] = _v3FarLocal.y;
|
|
895
|
+
rayDirection[ 2 ] = _v3FarLocal.z;
|
|
896
|
+
|
|
897
|
+
if ( this._pickClosestFace( rayOrigin, rayDirection ) === false ) {
|
|
898
|
+
|
|
899
|
+
this._clearHit();
|
|
900
|
+
return false;
|
|
901
|
+
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
this._worldRadius2 = worldRadius2;
|
|
905
|
+
this._localRadius2 = localRadius2;
|
|
906
|
+
|
|
907
|
+
return true;
|
|
908
|
+
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Applies a ray stamp, starting a stroke on a hit. Call
|
|
913
|
+
* {@link Sculptor#endStroke} after the last stamp.
|
|
914
|
+
*
|
|
915
|
+
* Returns `false` without updating the hit during a pointer stroke.
|
|
916
|
+
* Drag and Scale require pointer input.
|
|
917
|
+
*
|
|
918
|
+
* @param {Ray} ray - The world-space ray, with a non-zero direction.
|
|
919
|
+
* @param {number} worldRadius - The brush radius in world units.
|
|
920
|
+
* @return {boolean} Whether the ray hit the mesh.
|
|
921
|
+
*/
|
|
922
|
+
strokeFromRay( ray, worldRadius ) {
|
|
923
|
+
|
|
924
|
+
if ( this._activePointerId !== null ) return false;
|
|
925
|
+
|
|
926
|
+
const tool = this._tool;
|
|
927
|
+
validateRayTool( tool );
|
|
928
|
+
|
|
929
|
+
if ( this.pickFromRay( ray, worldRadius ) === false ) return false;
|
|
930
|
+
this.beginStroke();
|
|
931
|
+
if ( this._sculpting === false ) return false;
|
|
932
|
+
if ( this._tool !== tool ) validateRayTool( this._tool );
|
|
933
|
+
this._applyStroke();
|
|
934
|
+
this._syncGeometry();
|
|
935
|
+
|
|
936
|
+
return true;
|
|
937
|
+
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Begins a stroke and fires `start`. Does nothing while a stroke is active.
|
|
942
|
+
* Called automatically by pointer input or the first successful ray stamp.
|
|
943
|
+
*
|
|
944
|
+
* @return {Sculptor} A reference to this sculptor.
|
|
945
|
+
*/
|
|
946
|
+
beginStroke() {
|
|
947
|
+
|
|
948
|
+
if ( this._sculpting ) return this;
|
|
949
|
+
|
|
950
|
+
this._sculpting = true;
|
|
951
|
+
this.dispatchEvent( _startEvent );
|
|
952
|
+
return this;
|
|
953
|
+
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/**
|
|
957
|
+
* Releases pointer capture, balances the octree and updates exact bounds,
|
|
958
|
+
* then fires `end`. Does nothing while idle. Called automatically when a
|
|
959
|
+
* pointer stroke ends or is cancelled.
|
|
960
|
+
*
|
|
961
|
+
* @return {Sculptor} A reference to this sculptor.
|
|
962
|
+
*/
|
|
963
|
+
endStroke() {
|
|
964
|
+
|
|
965
|
+
if ( this._sculpting === false ) return this;
|
|
966
|
+
|
|
967
|
+
const pointerId = this._activePointerId;
|
|
968
|
+
this._activePointerId = null;
|
|
969
|
+
this._sculpting = false;
|
|
970
|
+
|
|
971
|
+
if ( pointerId !== null && this.domElement !== null && typeof this.domElement.releasePointerCapture === 'function' ) {
|
|
972
|
+
|
|
973
|
+
try {
|
|
974
|
+
|
|
975
|
+
this.domElement.releasePointerCapture( pointerId );
|
|
976
|
+
|
|
977
|
+
} catch {
|
|
978
|
+
|
|
979
|
+
// Synthetic events and detached elements may not support capture.
|
|
980
|
+
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
this._sculptMesh.balanceOctree();
|
|
986
|
+
if ( this._boundsDirty ) this._computeExactBounds();
|
|
987
|
+
this.dispatchEvent( _endEvent );
|
|
988
|
+
return this;
|
|
989
|
+
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
_applyStroke( scaleDelta = 0 ) {
|
|
993
|
+
|
|
994
|
+
const tool = this._tool;
|
|
995
|
+
const strength = this._strength;
|
|
996
|
+
const deforms = strength !== 0 || tool === 'drag' || tool === 'scale';
|
|
997
|
+
const remeshes = tool !== 'smooth' && this._detail !== 0;
|
|
998
|
+
|
|
999
|
+
if ( deforms === false && remeshes === false ) return;
|
|
1000
|
+
|
|
1001
|
+
const radius2 = this._localRadius2;
|
|
1002
|
+
let pickedVertices = this._pickVerticesInSphere( radius2 );
|
|
1003
|
+
const sculptMesh = this._sculptMesh;
|
|
1004
|
+
|
|
1005
|
+
if ( remeshes ) pickedVertices = this._dynamicTopology( pickedVertices );
|
|
1006
|
+
if ( deforms === false || pickedVertices.length === 0 ) return;
|
|
1007
|
+
|
|
1008
|
+
const hitPoint = this._hitPoint;
|
|
1009
|
+
const negative = this._negative;
|
|
1010
|
+
|
|
1011
|
+
switch ( tool ) {
|
|
1012
|
+
|
|
1013
|
+
case 'clay':
|
|
1014
|
+
case 'flatten': {
|
|
1015
|
+
|
|
1016
|
+
const frontVertices = getFrontVertices( sculptMesh, pickedVertices, this._rayDirection );
|
|
1017
|
+
const planeNormal = areaNormal( sculptMesh, frontVertices );
|
|
1018
|
+
|
|
1019
|
+
if ( planeNormal === null ) return;
|
|
1020
|
+
|
|
1021
|
+
const planePoint = areaCenter( sculptMesh, frontVertices );
|
|
1022
|
+
|
|
1023
|
+
if ( tool === 'clay' ) {
|
|
1024
|
+
|
|
1025
|
+
const offset = Math.sqrt( radius2 ) * CLAY_OFFSET_RATIO * ( negative ? - 1 : 1 );
|
|
1026
|
+
planePoint[ 0 ] += planeNormal[ 0 ] * offset;
|
|
1027
|
+
planePoint[ 1 ] += planeNormal[ 1 ] * offset;
|
|
1028
|
+
planePoint[ 2 ] += planeNormal[ 2 ] * offset;
|
|
1029
|
+
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
toolFlatten( sculptMesh, pickedVertices, planeNormal, planePoint, hitPoint, radius2, strength, negative );
|
|
1033
|
+
break;
|
|
1034
|
+
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
case 'brush':
|
|
1038
|
+
toolBrush( sculptMesh, pickedVertices, this._hitNormal, hitPoint, radius2, strength, negative );
|
|
1039
|
+
break;
|
|
1040
|
+
|
|
1041
|
+
case 'inflate':
|
|
1042
|
+
toolInflate( sculptMesh, pickedVertices, hitPoint, radius2, strength, negative );
|
|
1043
|
+
break;
|
|
1044
|
+
|
|
1045
|
+
case 'smooth':
|
|
1046
|
+
toolSmooth( sculptMesh, pickedVertices, strength );
|
|
1047
|
+
break;
|
|
1048
|
+
|
|
1049
|
+
case 'pinch':
|
|
1050
|
+
toolPinch( sculptMesh, pickedVertices, hitPoint, radius2, strength, negative );
|
|
1051
|
+
break;
|
|
1052
|
+
|
|
1053
|
+
case 'crease':
|
|
1054
|
+
toolCrease( sculptMesh, pickedVertices, this._hitNormal, hitPoint, radius2, strength, negative );
|
|
1055
|
+
break;
|
|
1056
|
+
|
|
1057
|
+
case 'drag':
|
|
1058
|
+
toolDrag( sculptMesh, pickedVertices, hitPoint, radius2, this._dragDirection );
|
|
1059
|
+
break;
|
|
1060
|
+
|
|
1061
|
+
case 'scale':
|
|
1062
|
+
toolScale( sculptMesh, pickedVertices, hitPoint, radius2, scaleDelta );
|
|
1063
|
+
break;
|
|
1064
|
+
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
const faces = sculptMesh.getFacesFromVertices( pickedVertices );
|
|
1068
|
+
const affectedVertices = sculptMesh.getVerticesFromFaces( faces );
|
|
1069
|
+
sculptMesh._updateGeometry( faces, affectedVertices );
|
|
1070
|
+
this._markVerticesDirty( affectedVertices );
|
|
1071
|
+
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
_makeStroke( clientX, clientY, scale2 ) {
|
|
1075
|
+
|
|
1076
|
+
if ( this._intersectionRayMesh( clientX, clientY, scale2 ) === false ) return false;
|
|
1077
|
+
|
|
1078
|
+
this._computePickedNormal();
|
|
1079
|
+
this._applyStroke();
|
|
1080
|
+
|
|
1081
|
+
return true;
|
|
1082
|
+
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
_sculptStroke( clientX, clientY ) {
|
|
1086
|
+
|
|
1087
|
+
const dx = clientX - this._lastPointerX;
|
|
1088
|
+
const dy = clientY - this._lastPointerY;
|
|
1089
|
+
const distance = Math.hypot( dx, dy );
|
|
1090
|
+
const minSpacing = STAMP_SPACING_RATIO * this._size;
|
|
1091
|
+
|
|
1092
|
+
if ( distance <= minSpacing ) return false;
|
|
1093
|
+
|
|
1094
|
+
const count = Math.floor( distance / minSpacing );
|
|
1095
|
+
const stepX = dx / count;
|
|
1096
|
+
const stepY = dy / count;
|
|
1097
|
+
let x = this._lastPointerX + stepX;
|
|
1098
|
+
let y = this._lastPointerY + stepY;
|
|
1099
|
+
let stamped = false;
|
|
1100
|
+
let pointerSampled = false;
|
|
1101
|
+
const scale2 = this._updatePickingMatrices();
|
|
1102
|
+
|
|
1103
|
+
for ( let i = 0; i < count; ++ i ) {
|
|
1104
|
+
|
|
1105
|
+
pointerSampled = i === count - 1;
|
|
1106
|
+
if ( this._makeStroke( x, y, scale2 ) === false ) break;
|
|
1107
|
+
|
|
1108
|
+
stamped = true;
|
|
1109
|
+
x += stepX;
|
|
1110
|
+
y += stepY;
|
|
1111
|
+
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
this._lastPointerX = clientX;
|
|
1115
|
+
this._lastPointerY = clientY;
|
|
1116
|
+
|
|
1117
|
+
if ( stamped ) this._syncGeometry();
|
|
1118
|
+
|
|
1119
|
+
return pointerSampled;
|
|
1120
|
+
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
_updateDragDirection( clientX, clientY, scale2 ) {
|
|
1124
|
+
|
|
1125
|
+
if ( this._updatePointerRay( clientX, clientY ) === false ) return false;
|
|
1126
|
+
|
|
1127
|
+
const hitPoint = this._hitPoint;
|
|
1128
|
+
const rayOrigin = this._rayOrigin;
|
|
1129
|
+
const rayDirection = this._rayDirection;
|
|
1130
|
+
const abx = rayDirection[ 0 ];
|
|
1131
|
+
const aby = rayDirection[ 1 ];
|
|
1132
|
+
const abz = rayDirection[ 2 ];
|
|
1133
|
+
const px = hitPoint[ 0 ] - rayOrigin[ 0 ];
|
|
1134
|
+
const py = hitPoint[ 1 ] - rayOrigin[ 1 ];
|
|
1135
|
+
const pz = hitPoint[ 2 ] - rayOrigin[ 2 ];
|
|
1136
|
+
const denominator = abx * abx + aby * aby + abz * abz;
|
|
1137
|
+
const projection = denominator > 0 ? ( abx * px + aby * py + abz * pz ) / denominator : 0;
|
|
1138
|
+
const x = rayOrigin[ 0 ] + abx * projection;
|
|
1139
|
+
const y = rayOrigin[ 1 ] + aby * projection;
|
|
1140
|
+
const z = rayOrigin[ 2 ] + abz * projection;
|
|
1141
|
+
|
|
1142
|
+
this._dragDirection[ 0 ] = x - hitPoint[ 0 ];
|
|
1143
|
+
this._dragDirection[ 1 ] = y - hitPoint[ 1 ];
|
|
1144
|
+
this._dragDirection[ 2 ] = z - hitPoint[ 2 ];
|
|
1145
|
+
hitPoint[ 0 ] = x;
|
|
1146
|
+
hitPoint[ 1 ] = y;
|
|
1147
|
+
hitPoint[ 2 ] = z;
|
|
1148
|
+
|
|
1149
|
+
this._updateRadii( scale2 );
|
|
1150
|
+
return true;
|
|
1151
|
+
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
_sculptStrokeDrag( clientX, clientY ) {
|
|
1155
|
+
|
|
1156
|
+
const dx = clientX - this._lastPointerX;
|
|
1157
|
+
const dy = clientY - this._lastPointerY;
|
|
1158
|
+
const distance = Math.hypot( dx, dy );
|
|
1159
|
+
|
|
1160
|
+
if ( distance === 0 ) return false;
|
|
1161
|
+
|
|
1162
|
+
const minSpacing = STAMP_SPACING_RATIO * this._size;
|
|
1163
|
+
const count = Math.max( 1, Math.floor( distance / minSpacing ) );
|
|
1164
|
+
const stepX = dx / count;
|
|
1165
|
+
const stepY = dy / count;
|
|
1166
|
+
let x = this._lastPointerX + stepX;
|
|
1167
|
+
let y = this._lastPointerY + stepY;
|
|
1168
|
+
let stamped = false;
|
|
1169
|
+
const scale2 = this._updatePickingMatrices();
|
|
1170
|
+
|
|
1171
|
+
for ( let i = 0; i < count; ++ i ) {
|
|
1172
|
+
|
|
1173
|
+
if ( this._updateDragDirection( x, y, scale2 ) === false ) break;
|
|
1174
|
+
this._computePickedNormal();
|
|
1175
|
+
this._applyStroke();
|
|
1176
|
+
stamped = true;
|
|
1177
|
+
x += stepX;
|
|
1178
|
+
y += stepY;
|
|
1179
|
+
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
this._lastPointerX = clientX;
|
|
1183
|
+
this._lastPointerY = clientY;
|
|
1184
|
+
|
|
1185
|
+
if ( stamped ) this._syncGeometry();
|
|
1186
|
+
|
|
1187
|
+
return stamped;
|
|
1188
|
+
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
_sculptStrokeScale( clientX, clientY ) {
|
|
1192
|
+
|
|
1193
|
+
const scaleDelta = clientX - this._lastPointerX;
|
|
1194
|
+
this._lastPointerX = clientX;
|
|
1195
|
+
this._lastPointerY = clientY;
|
|
1196
|
+
|
|
1197
|
+
if ( scaleDelta === 0 ) return false;
|
|
1198
|
+
|
|
1199
|
+
this._applyStroke( scaleDelta );
|
|
1200
|
+
this._syncGeometry();
|
|
1201
|
+
|
|
1202
|
+
return true;
|
|
1203
|
+
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
_markVerticesDirty( vertices ) {
|
|
1207
|
+
|
|
1208
|
+
if ( vertices.length === 0 ) return;
|
|
1209
|
+
|
|
1210
|
+
this._boundsDirty = true;
|
|
1211
|
+
const positions = this._sculptMesh.getVertices();
|
|
1212
|
+
const geometry = this.mesh.geometry;
|
|
1213
|
+
const box = geometry.boundingBox;
|
|
1214
|
+
const sphere = geometry.boundingSphere;
|
|
1215
|
+
let radius2 = sphere === null ? 0 : sphere.radius * sphere.radius;
|
|
1216
|
+
|
|
1217
|
+
for ( let i = 0, l = vertices.length; i < l; ++ i ) {
|
|
1218
|
+
|
|
1219
|
+
const vertexIndex = vertices[ i ];
|
|
1220
|
+
const offset = vertexIndex * 3;
|
|
1221
|
+
|
|
1222
|
+
this._dirtyVertices.push( vertexIndex );
|
|
1223
|
+
|
|
1224
|
+
_v3Temp.fromArray( positions, offset );
|
|
1225
|
+
|
|
1226
|
+
if ( box !== null ) box.expandByPoint( _v3Temp );
|
|
1227
|
+
if ( sphere !== null ) radius2 = Math.max( radius2, sphere.center.distanceToSquared( _v3Temp ) );
|
|
1228
|
+
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if ( sphere !== null ) sphere.radius = Math.sqrt( radius2 );
|
|
1232
|
+
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
_syncGeometry() {
|
|
1236
|
+
|
|
1237
|
+
const sculptMesh = this._sculptMesh;
|
|
1238
|
+
let geometry = this.mesh.geometry;
|
|
1239
|
+
const vertexCount = sculptMesh.getNbVertices();
|
|
1240
|
+
const indexLength = sculptMesh.getNbTriangles() * 3;
|
|
1241
|
+
const positions = sculptMesh.getVertices();
|
|
1242
|
+
const normals = sculptMesh.getRenderNormals();
|
|
1243
|
+
const indices = sculptMesh.getTriangles();
|
|
1244
|
+
let positionAttribute = geometry.getAttribute( 'position' );
|
|
1245
|
+
let normalAttribute = geometry.getAttribute( 'normal' );
|
|
1246
|
+
let indexAttribute = geometry.getIndex();
|
|
1247
|
+
const replacePosition = attributeMatches( positionAttribute, positions, 3 ) === false;
|
|
1248
|
+
const replaceNormal = attributeMatches( normalAttribute, normals, 3 ) === false;
|
|
1249
|
+
const replaceIndex = attributeMatches( indexAttribute, indices, 1 ) === false;
|
|
1250
|
+
const replaceGeometry = this._geometrySynced && ( replacePosition || replaceNormal || replaceIndex );
|
|
1251
|
+
let previousGeometry = null;
|
|
1252
|
+
const dirtyVertices = this._dirtyVertices;
|
|
1253
|
+
let hasDirtyVertices = dirtyVertices.length > 0;
|
|
1254
|
+
|
|
1255
|
+
if ( hasDirtyVertices && replaceGeometry === false ) {
|
|
1256
|
+
|
|
1257
|
+
hasDirtyVertices = compactDirtyVertices( dirtyVertices, vertexCount );
|
|
1258
|
+
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// Uploaded buffers cannot resize. Replace the geometry to release its GPU
|
|
1262
|
+
// resources without invalidating the new attributes.
|
|
1263
|
+
if ( replaceGeometry ) {
|
|
1264
|
+
|
|
1265
|
+
previousGeometry = geometry;
|
|
1266
|
+
geometry = createReplacementGeometry( previousGeometry );
|
|
1267
|
+
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
if ( replaceGeometry || replacePosition ) {
|
|
1271
|
+
|
|
1272
|
+
positionAttribute = createVersionedAttribute( positions, 3, positionAttribute );
|
|
1273
|
+
geometry.setAttribute( 'position', positionAttribute );
|
|
1274
|
+
|
|
1275
|
+
} else if ( hasDirtyVertices ) {
|
|
1276
|
+
|
|
1277
|
+
addVertexUpdateRanges( positionAttribute, dirtyVertices );
|
|
1278
|
+
positionAttribute.needsUpdate = true;
|
|
1279
|
+
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
if ( replaceGeometry || replaceNormal ) {
|
|
1283
|
+
|
|
1284
|
+
normalAttribute = createVersionedAttribute( normals, 3, normalAttribute );
|
|
1285
|
+
geometry.setAttribute( 'normal', normalAttribute );
|
|
1286
|
+
|
|
1287
|
+
} else if ( hasDirtyVertices ) {
|
|
1288
|
+
|
|
1289
|
+
addVertexUpdateRanges( normalAttribute, dirtyVertices );
|
|
1290
|
+
normalAttribute.needsUpdate = true;
|
|
1291
|
+
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const topologyVersion = sculptMesh.getTopologyVersion();
|
|
1295
|
+
const changed = this._geometrySynced && ( replaceGeometry || hasDirtyVertices || topologyVersion !== this._lastTopologyVersion );
|
|
1296
|
+
|
|
1297
|
+
if ( replaceGeometry || replaceIndex ) {
|
|
1298
|
+
|
|
1299
|
+
indexAttribute = createVersionedAttribute( indices, 1, indexAttribute );
|
|
1300
|
+
geometry.setIndex( indexAttribute );
|
|
1301
|
+
|
|
1302
|
+
} else if ( topologyVersion !== this._lastTopologyVersion ) {
|
|
1303
|
+
|
|
1304
|
+
indexAttribute.clearUpdateRanges();
|
|
1305
|
+
indexAttribute.addUpdateRange( 0, indexLength );
|
|
1306
|
+
indexAttribute.needsUpdate = true;
|
|
1307
|
+
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
geometry.setDrawRange( 0, indexLength );
|
|
1311
|
+
|
|
1312
|
+
if ( previousGeometry !== null ) {
|
|
1313
|
+
|
|
1314
|
+
this.mesh.geometry = geometry;
|
|
1315
|
+
previousGeometry.dispose();
|
|
1316
|
+
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
this._lastTopologyVersion = topologyVersion;
|
|
1320
|
+
this._dirtyVertices.length = 0;
|
|
1321
|
+
this._geometrySynced = true;
|
|
1322
|
+
|
|
1323
|
+
if ( changed ) this.dispatchEvent( _changeEvent );
|
|
1324
|
+
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
_computeExactBounds() {
|
|
1328
|
+
|
|
1329
|
+
const geometry = this.mesh.geometry;
|
|
1330
|
+
const positions = this._sculptMesh.getVertices();
|
|
1331
|
+
const length = this._sculptMesh.getNbVertices() * 3;
|
|
1332
|
+
|
|
1333
|
+
if ( geometry.boundingBox === null ) geometry.boundingBox = new Box3();
|
|
1334
|
+
if ( geometry.boundingSphere === null ) geometry.boundingSphere = new Sphere();
|
|
1335
|
+
|
|
1336
|
+
const box = geometry.boundingBox;
|
|
1337
|
+
const sphere = geometry.boundingSphere;
|
|
1338
|
+
box.makeEmpty();
|
|
1339
|
+
|
|
1340
|
+
// Spare capacity may contain deleted vertices; only bound the active prefix.
|
|
1341
|
+
for ( let i = 0; i < length; i += 3 ) box.expandByPoint( _v3Temp.fromArray( positions, i ) );
|
|
1342
|
+
|
|
1343
|
+
box.getCenter( sphere.center );
|
|
1344
|
+
let radius2 = 0;
|
|
1345
|
+
|
|
1346
|
+
for ( let i = 0; i < length; i += 3 ) {
|
|
1347
|
+
|
|
1348
|
+
radius2 = Math.max( radius2, sphere.center.distanceToSquared( _v3Temp.fromArray( positions, i ) ) );
|
|
1349
|
+
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
sphere.radius = Math.sqrt( radius2 );
|
|
1353
|
+
this._boundsDirty = false;
|
|
1354
|
+
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* Returns an independent copy of the active vertices and triangles, without
|
|
1359
|
+
* spare capacity. Suitable for export or geometry processing. The caller owns it.
|
|
1360
|
+
*
|
|
1361
|
+
* @return {BufferGeometry} A new geometry containing the active vertices and triangles.
|
|
1362
|
+
*/
|
|
1363
|
+
getGeometry() {
|
|
1364
|
+
|
|
1365
|
+
const sculptMesh = this._sculptMesh;
|
|
1366
|
+
const vertexLength = sculptMesh.getNbVertices() * 3;
|
|
1367
|
+
const geometry = new BufferGeometry();
|
|
1368
|
+
geometry.name = this.mesh.geometry.name;
|
|
1369
|
+
geometry.setAttribute( 'position', new BufferAttribute( sculptMesh.getVertices().slice( 0, vertexLength ), 3 ) );
|
|
1370
|
+
geometry.setAttribute( 'normal', new BufferAttribute( sculptMesh.getRenderNormals().slice( 0, vertexLength ), 3 ) );
|
|
1371
|
+
geometry.setIndex( new BufferAttribute( sculptMesh.getTriangles().slice( 0, sculptMesh.getNbTriangles() * 3 ), 1 ) );
|
|
1372
|
+
return geometry;
|
|
1373
|
+
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
_onPointerDown( event ) {
|
|
1377
|
+
|
|
1378
|
+
if ( this.enabled === false || event.button !== 0 || event.isPrimary === false || this._sculpting ) return;
|
|
1379
|
+
|
|
1380
|
+
this._cachedRect = null;
|
|
1381
|
+
|
|
1382
|
+
if ( this._intersectionRayMesh( event.clientX, event.clientY ) === false ) return;
|
|
1383
|
+
|
|
1384
|
+
this._computePickedNormal();
|
|
1385
|
+
this._activePointerId = event.pointerId;
|
|
1386
|
+
this._lastPointerX = event.clientX;
|
|
1387
|
+
this._lastPointerY = event.clientY;
|
|
1388
|
+
|
|
1389
|
+
if ( typeof this.domElement.setPointerCapture === 'function' ) {
|
|
1390
|
+
|
|
1391
|
+
try {
|
|
1392
|
+
|
|
1393
|
+
this.domElement.setPointerCapture( event.pointerId );
|
|
1394
|
+
|
|
1395
|
+
} catch {
|
|
1396
|
+
|
|
1397
|
+
// Synthetic events and detached elements may not support capture.
|
|
1398
|
+
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
this.beginStroke();
|
|
1404
|
+
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
_onPointerMove( event ) {
|
|
1408
|
+
|
|
1409
|
+
if ( this.enabled === false || this._sculpting === false || event.pointerId !== this._activePointerId ) return;
|
|
1410
|
+
|
|
1411
|
+
this._cachedRect = null;
|
|
1412
|
+
const tool = this._tool;
|
|
1413
|
+
|
|
1414
|
+
if ( tool === 'drag' ) {
|
|
1415
|
+
|
|
1416
|
+
this._sculptStrokeDrag( event.clientX, event.clientY );
|
|
1417
|
+
|
|
1418
|
+
} else if ( tool === 'scale' ) {
|
|
1419
|
+
|
|
1420
|
+
this._sculptStrokeScale( event.clientX, event.clientY );
|
|
1421
|
+
|
|
1422
|
+
} else {
|
|
1423
|
+
|
|
1424
|
+
const pointerSampled = this._sculptStroke( event.clientX, event.clientY );
|
|
1425
|
+
|
|
1426
|
+
// Update the cursor between stamps.
|
|
1427
|
+
if ( pointerSampled !== true && this._intersectionRayMesh( event.clientX, event.clientY ) ) this._computePickedNormal();
|
|
1428
|
+
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
_onPointerUp( event ) {
|
|
1434
|
+
|
|
1435
|
+
if ( event.pointerId === this._activePointerId ) this.endStroke();
|
|
1436
|
+
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* Returns the active sculpting tool.
|
|
1441
|
+
*
|
|
1442
|
+
* @return {('clay'|'brush'|'inflate'|'smooth'|'flatten'|'pinch'|'crease'|'drag'|'scale')} The tool name.
|
|
1443
|
+
*/
|
|
1444
|
+
getTool() {
|
|
1445
|
+
|
|
1446
|
+
return this._tool;
|
|
1447
|
+
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/**
|
|
1451
|
+
* Selects a tool and restores its size, strength and negative setting.
|
|
1452
|
+
*
|
|
1453
|
+
* @param {('clay'|'brush'|'inflate'|'smooth'|'flatten'|'pinch'|'crease'|'drag'|'scale')} value - The tool name.
|
|
1454
|
+
* @return {Sculptor} A reference to this sculptor.
|
|
1455
|
+
*/
|
|
1456
|
+
setTool( value ) {
|
|
1457
|
+
|
|
1458
|
+
if ( Object.prototype.hasOwnProperty.call( TOOL_DEFAULTS, value ) === false ) {
|
|
1459
|
+
|
|
1460
|
+
throw new RangeError( `Sculptor: Unknown tool "${ value }".` );
|
|
1461
|
+
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
if ( value === this._tool ) return this;
|
|
1465
|
+
|
|
1466
|
+
const settings = this._toolSettings[ value ];
|
|
1467
|
+
this._tool = value;
|
|
1468
|
+
this._size = settings.size;
|
|
1469
|
+
this._strength = settings.strength;
|
|
1470
|
+
this._negative = settings.negative;
|
|
1471
|
+
|
|
1472
|
+
return this;
|
|
1473
|
+
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
/**
|
|
1477
|
+
* Returns the pointer brush radius in CSS pixels.
|
|
1478
|
+
*
|
|
1479
|
+
* @return {number} The brush radius.
|
|
1480
|
+
*/
|
|
1481
|
+
getSize() {
|
|
1482
|
+
|
|
1483
|
+
return this._size;
|
|
1484
|
+
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
/**
|
|
1488
|
+
* Sets the pointer brush radius in CSS pixels.
|
|
1489
|
+
*
|
|
1490
|
+
* @param {number} value - A value between 5 and 500.
|
|
1491
|
+
* @return {Sculptor} A reference to this sculptor.
|
|
1492
|
+
*/
|
|
1493
|
+
setSize( value ) {
|
|
1494
|
+
|
|
1495
|
+
const size = validateSize( value );
|
|
1496
|
+
this._size = size;
|
|
1497
|
+
this._toolSettings[ this._tool ].size = size;
|
|
1498
|
+
return this;
|
|
1499
|
+
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
/**
|
|
1503
|
+
* Returns the strength of the active tool.
|
|
1504
|
+
*
|
|
1505
|
+
* @return {number} The tool strength.
|
|
1506
|
+
*/
|
|
1507
|
+
getStrength() {
|
|
1508
|
+
|
|
1509
|
+
return this._strength;
|
|
1510
|
+
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/**
|
|
1514
|
+
* Sets the strength of the active tool.
|
|
1515
|
+
* A value of `0` disables deformation, but not adaptive remeshing.
|
|
1516
|
+
* Drag and Scale use pointer movement instead of this setting.
|
|
1517
|
+
*
|
|
1518
|
+
* @param {number} value - A value between 0 and 1.
|
|
1519
|
+
* @return {Sculptor} A reference to this sculptor.
|
|
1520
|
+
*/
|
|
1521
|
+
setStrength( value ) {
|
|
1522
|
+
|
|
1523
|
+
const strength = validateUnitInterval( 'strength', value );
|
|
1524
|
+
this._strength = strength;
|
|
1525
|
+
this._toolSettings[ this._tool ].strength = strength;
|
|
1526
|
+
return this;
|
|
1527
|
+
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/**
|
|
1531
|
+
* Returns whether the active tool applies its inverse effect.
|
|
1532
|
+
*
|
|
1533
|
+
* @return {boolean} Whether the tool direction is inverted.
|
|
1534
|
+
*/
|
|
1535
|
+
getNegative() {
|
|
1536
|
+
|
|
1537
|
+
return this._negative;
|
|
1538
|
+
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
/**
|
|
1542
|
+
* Sets whether the active tool applies its inverse effect.
|
|
1543
|
+
*
|
|
1544
|
+
* @param {boolean} value - Whether the tool direction is inverted.
|
|
1545
|
+
* @return {Sculptor} A reference to this sculptor.
|
|
1546
|
+
*/
|
|
1547
|
+
setNegative( value ) {
|
|
1548
|
+
|
|
1549
|
+
if ( typeof value !== 'boolean' ) throw new TypeError( 'Sculptor: negative must be a boolean.' );
|
|
1550
|
+
|
|
1551
|
+
this._negative = value;
|
|
1552
|
+
this._toolSettings[ this._tool ].negative = value;
|
|
1553
|
+
return this;
|
|
1554
|
+
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* Returns the adaptive-topology detail. `0` freezes topology.
|
|
1559
|
+
*
|
|
1560
|
+
* @return {number} The detail level.
|
|
1561
|
+
*/
|
|
1562
|
+
getDetail() {
|
|
1563
|
+
|
|
1564
|
+
return this._detail;
|
|
1565
|
+
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
/**
|
|
1569
|
+
* Sets the adaptive-topology detail. Higher values produce shorter edges
|
|
1570
|
+
* relative to the brush radius; `0` freezes topology. Remeshing splits long
|
|
1571
|
+
* edges and collapses short ones, and can alter the surface even at zero strength.
|
|
1572
|
+
*
|
|
1573
|
+
* @param {number} value - A value between 0 and 1.
|
|
1574
|
+
* @return {Sculptor} A reference to this sculptor.
|
|
1575
|
+
*/
|
|
1576
|
+
setDetail( value ) {
|
|
1577
|
+
|
|
1578
|
+
this._detail = validateUnitInterval( 'detail', value );
|
|
1579
|
+
return this;
|
|
1580
|
+
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/**
|
|
1584
|
+
* Returns whether a pointer or programmatic stroke is active.
|
|
1585
|
+
*
|
|
1586
|
+
* @return {boolean} Whether a stroke is active.
|
|
1587
|
+
*/
|
|
1588
|
+
isSculpting() {
|
|
1589
|
+
|
|
1590
|
+
return this._sculpting;
|
|
1591
|
+
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* Returns whether the latest pick or stroke ray hit the mesh.
|
|
1596
|
+
*
|
|
1597
|
+
* @return {boolean} Whether the latest ray hit the mesh.
|
|
1598
|
+
*/
|
|
1599
|
+
hasHit() {
|
|
1600
|
+
|
|
1601
|
+
return this._hitFace >= 0;
|
|
1602
|
+
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
/**
|
|
1606
|
+
* Copies the current local-space hit position into the target vector.
|
|
1607
|
+
* Returns a zero vector when there is no hit.
|
|
1608
|
+
*
|
|
1609
|
+
* @param {Vector3} target - The vector to receive the hit position.
|
|
1610
|
+
* @return {Vector3} The target vector.
|
|
1611
|
+
*/
|
|
1612
|
+
getHitPoint( target ) {
|
|
1613
|
+
|
|
1614
|
+
return target.fromArray( this._hitPoint );
|
|
1615
|
+
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Copies the current local-space unit surface normal into the target vector.
|
|
1620
|
+
* Returns a zero vector when there is no hit.
|
|
1621
|
+
*
|
|
1622
|
+
* @param {Vector3} target - The vector to receive the surface normal.
|
|
1623
|
+
* @return {Vector3} The target vector.
|
|
1624
|
+
*/
|
|
1625
|
+
getHitNormal( target ) {
|
|
1626
|
+
|
|
1627
|
+
return target.fromArray( this._hitNormal );
|
|
1628
|
+
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Returns the brush radius in world units, or `0` without a hit.
|
|
1633
|
+
*
|
|
1634
|
+
* @return {number} The brush radius in world units.
|
|
1635
|
+
*/
|
|
1636
|
+
getWorldRadius() {
|
|
1637
|
+
|
|
1638
|
+
return Math.sqrt( this._worldRadius2 );
|
|
1639
|
+
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/**
|
|
1643
|
+
* Updates the current hit from a world-space ray without sculpting.
|
|
1644
|
+
*
|
|
1645
|
+
* @param {Ray} ray - The world-space ray, with a non-zero direction.
|
|
1646
|
+
* @param {number} worldRadius - The brush radius in world units.
|
|
1647
|
+
* @return {boolean} Whether the ray hit the mesh.
|
|
1648
|
+
*/
|
|
1649
|
+
pickFromRay( ray, worldRadius ) {
|
|
1650
|
+
|
|
1651
|
+
if ( this._intersectionFromRay( ray, worldRadius ) === false ) return false;
|
|
1652
|
+
|
|
1653
|
+
this._computePickedNormal();
|
|
1654
|
+
return true;
|
|
1655
|
+
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
/**
|
|
1659
|
+
* Updates the current hit from client coordinates without sculpting.
|
|
1660
|
+
*
|
|
1661
|
+
* @param {number} clientX - Horizontal client coordinate in CSS pixels.
|
|
1662
|
+
* @param {number} clientY - Vertical client coordinate in CSS pixels.
|
|
1663
|
+
* @return {boolean} Whether the pointer ray hit the mesh.
|
|
1664
|
+
*/
|
|
1665
|
+
pickFromPointer( clientX, clientY ) {
|
|
1666
|
+
|
|
1667
|
+
this._cachedRect = null;
|
|
1668
|
+
|
|
1669
|
+
if ( this._intersectionRayMesh( clientX, clientY ) === false ) return false;
|
|
1670
|
+
|
|
1671
|
+
this._computePickedNormal();
|
|
1672
|
+
|
|
1673
|
+
return true;
|
|
1674
|
+
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
export { Sculptor };
|